| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/ui/gtk/bookmarks/bookmark_bubble_gtk.h" | |
| 6 | |
| 7 #include <gtk/gtk.h> | |
| 8 | |
| 9 #include "base/basictypes.h" | |
| 10 #include "base/bind.h" | |
| 11 #include "base/i18n/rtl.h" | |
| 12 #include "base/logging.h" | |
| 13 #include "base/message_loop/message_loop.h" | |
| 14 #include "base/strings/string16.h" | |
| 15 #include "base/strings/utf_string_conversions.h" | |
| 16 #include "chrome/browser/bookmarks/bookmark_model.h" | |
| 17 #include "chrome/browser/bookmarks/bookmark_model_factory.h" | |
| 18 #include "chrome/browser/bookmarks/bookmark_utils.h" | |
| 19 #include "chrome/browser/chrome_notification_types.h" | |
| 20 #include "chrome/browser/profiles/profile.h" | |
| 21 #include "chrome/browser/signin/signin_promo.h" | |
| 22 #include "chrome/browser/themes/theme_properties.h" | |
| 23 #include "chrome/browser/ui/bookmarks/bookmark_editor.h" | |
| 24 #include "chrome/browser/ui/bookmarks/recently_used_folders_combo_model.h" | |
| 25 #include "chrome/browser/ui/browser.h" | |
| 26 #include "chrome/browser/ui/browser_finder.h" | |
| 27 #include "chrome/browser/ui/browser_list.h" | |
| 28 #include "chrome/browser/ui/chrome_pages.h" | |
| 29 #include "chrome/browser/ui/gtk/gtk_theme_service.h" | |
| 30 #include "chrome/browser/ui/gtk/gtk_util.h" | |
| 31 #include "chrome/browser/ui/sync/sync_promo_ui.h" | |
| 32 #include "content/public/browser/notification_source.h" | |
| 33 #include "content/public/browser/user_metrics.h" | |
| 34 #include "grit/generated_resources.h" | |
| 35 #include "ui/base/gtk/gtk_hig_constants.h" | |
| 36 #include "ui/base/l10n/l10n_util.h" | |
| 37 #include "ui/gfx/canvas_paint_gtk.h" | |
| 38 | |
| 39 using base::UserMetricsAction; | |
| 40 | |
| 41 namespace { | |
| 42 | |
| 43 enum { | |
| 44 COLUMN_NAME, | |
| 45 COLUMN_IS_SEPARATOR, | |
| 46 COLUMN_COUNT | |
| 47 }; | |
| 48 | |
| 49 // Thickness of the bubble's border. | |
| 50 const int kBubbleBorderThickness = 1; | |
| 51 | |
| 52 // Color of the bubble's border. | |
| 53 const SkColor kBubbleBorderColor = SkColorSetRGB(0x63, 0x63, 0x63); | |
| 54 | |
| 55 // Background color of the sync promo. | |
| 56 const GdkColor kPromoBackgroundColor = GDK_COLOR_RGB(0xf5, 0xf5, 0xf5); | |
| 57 | |
| 58 // Color of the border of the sync promo. | |
| 59 const SkColor kPromoBorderColor = SkColorSetRGB(0xe5, 0xe5, 0xe5); | |
| 60 | |
| 61 // Color of the text in the sync promo. | |
| 62 const GdkColor kPromoTextColor = GDK_COLOR_RGB(0x66, 0x66, 0x66); | |
| 63 | |
| 64 // Vertical padding inside the sync promo. | |
| 65 const int kPromoVerticalPadding = 15; | |
| 66 | |
| 67 // Pango markup for the "Sign in" link in the sync promo. | |
| 68 const char kPromoLinkMarkup[] = | |
| 69 "<a href='signin'><span underline='none'>%s</span></a>"; | |
| 70 | |
| 71 // Style to make the sync promo link blue. | |
| 72 const char kPromoLinkStyle[] = | |
| 73 "style \"sign-in-link\" {\n" | |
| 74 " GtkWidget::link-color=\"blue\"\n" | |
| 75 "}\n" | |
| 76 "widget \"*sign-in-link\" style \"sign-in-link\"\n"; | |
| 77 | |
| 78 gboolean IsSeparator(GtkTreeModel* model, GtkTreeIter* iter, gpointer data) { | |
| 79 gboolean is_separator; | |
| 80 gtk_tree_model_get(model, iter, COLUMN_IS_SEPARATOR, &is_separator, -1); | |
| 81 return is_separator; | |
| 82 } | |
| 83 | |
| 84 } // namespace | |
| 85 | |
| 86 BookmarkBubbleGtk* BookmarkBubbleGtk::bookmark_bubble_ = NULL; | |
| 87 | |
| 88 // static | |
| 89 void BookmarkBubbleGtk::Show(GtkWidget* anchor, | |
| 90 Profile* profile, | |
| 91 const GURL& url, | |
| 92 bool newly_bookmarked) { | |
| 93 // Sometimes Ctrl+D may get pressed more than once on top level window | |
| 94 // before the bookmark bubble window is shown and takes the keyboad focus. | |
| 95 if (bookmark_bubble_) | |
| 96 return; | |
| 97 bookmark_bubble_ = new BookmarkBubbleGtk(anchor, | |
| 98 profile, | |
| 99 url, | |
| 100 newly_bookmarked); | |
| 101 } | |
| 102 | |
| 103 void BookmarkBubbleGtk::BubbleClosing(BubbleGtk* bubble, | |
| 104 bool closed_by_escape) { | |
| 105 if (closed_by_escape) { | |
| 106 remove_bookmark_ = newly_bookmarked_; | |
| 107 apply_edits_ = false; | |
| 108 } | |
| 109 } | |
| 110 | |
| 111 void BookmarkBubbleGtk::Observe(int type, | |
| 112 const content::NotificationSource& source, | |
| 113 const content::NotificationDetails& details) { | |
| 114 DCHECK(type == chrome::NOTIFICATION_BROWSER_THEME_CHANGED); | |
| 115 | |
| 116 if (theme_service_->UsingNativeTheme()) { | |
| 117 for (std::vector<GtkWidget*>::iterator it = labels_.begin(); | |
| 118 it != labels_.end(); ++it) { | |
| 119 gtk_widget_modify_fg(*it, GTK_STATE_NORMAL, NULL); | |
| 120 } | |
| 121 } else { | |
| 122 for (std::vector<GtkWidget*>::iterator it = labels_.begin(); | |
| 123 it != labels_.end(); ++it) { | |
| 124 gtk_widget_modify_fg(*it, GTK_STATE_NORMAL, &ui::kGdkBlack); | |
| 125 } | |
| 126 } | |
| 127 | |
| 128 UpdatePromoColors(); | |
| 129 } | |
| 130 | |
| 131 BookmarkBubbleGtk::BookmarkBubbleGtk(GtkWidget* anchor, | |
| 132 Profile* profile, | |
| 133 const GURL& url, | |
| 134 bool newly_bookmarked) | |
| 135 : url_(url), | |
| 136 profile_(profile), | |
| 137 model_(BookmarkModelFactory::GetForProfile(profile)), | |
| 138 theme_service_(GtkThemeService::GetFrom(profile_)), | |
| 139 anchor_(anchor), | |
| 140 promo_(NULL), | |
| 141 promo_label_(NULL), | |
| 142 name_entry_(NULL), | |
| 143 folder_combo_(NULL), | |
| 144 bubble_(NULL), | |
| 145 newly_bookmarked_(newly_bookmarked), | |
| 146 apply_edits_(true), | |
| 147 remove_bookmark_(false), | |
| 148 factory_(this) { | |
| 149 GtkWidget* label = gtk_label_new(l10n_util::GetStringUTF8( | |
| 150 newly_bookmarked_ ? IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARKED : | |
| 151 IDS_BOOKMARK_BUBBLE_PAGE_BOOKMARK).c_str()); | |
| 152 labels_.push_back(label); | |
| 153 remove_button_ = theme_service_->BuildChromeLinkButton( | |
| 154 l10n_util::GetStringUTF8(IDS_BOOKMARK_BUBBLE_REMOVE_BOOKMARK)); | |
| 155 GtkWidget* edit_button = gtk_button_new_with_label( | |
| 156 l10n_util::GetStringUTF8(IDS_BOOKMARK_BUBBLE_OPTIONS).c_str()); | |
| 157 GtkWidget* close_button = gtk_button_new_with_label( | |
| 158 l10n_util::GetStringUTF8(IDS_DONE).c_str()); | |
| 159 | |
| 160 GtkWidget* bubble_container = gtk_vbox_new(FALSE, 0); | |
| 161 | |
| 162 // Prevent the content of the bubble to be drawn on the border. | |
| 163 gtk_container_set_border_width(GTK_CONTAINER(bubble_container), | |
| 164 kBubbleBorderThickness); | |
| 165 | |
| 166 // Our content is arranged in 3 rows. |top| contains a left justified | |
| 167 // message, and a right justified remove link button. |table| is the middle | |
| 168 // portion with the name entry and the folder combo. |bottom| is the final | |
| 169 // row with a spacer, and the edit... and close buttons on the right. | |
| 170 GtkWidget* content = gtk_vbox_new(FALSE, 5); | |
| 171 gtk_container_set_border_width( | |
| 172 GTK_CONTAINER(content), | |
| 173 ui::kContentAreaBorder - kBubbleBorderThickness); | |
| 174 GtkWidget* top = gtk_hbox_new(FALSE, 0); | |
| 175 | |
| 176 gtk_misc_set_alignment(GTK_MISC(label), 0, 1); | |
| 177 gtk_box_pack_start(GTK_BOX(top), label, | |
| 178 TRUE, TRUE, 0); | |
| 179 gtk_box_pack_start(GTK_BOX(top), remove_button_, | |
| 180 FALSE, FALSE, 0); | |
| 181 | |
| 182 InitFolderComboModel(); | |
| 183 | |
| 184 // Create the edit entry for updating the bookmark name / title. | |
| 185 name_entry_ = gtk_entry_new(); | |
| 186 gtk_entry_set_text(GTK_ENTRY(name_entry_), GetTitle().c_str()); | |
| 187 | |
| 188 // We use a table to allow the labels to line up with each other, along | |
| 189 // with the entry and folder combo lining up. | |
| 190 GtkWidget* table = gtk_util::CreateLabeledControlsGroup( | |
| 191 &labels_, | |
| 192 l10n_util::GetStringUTF8(IDS_BOOKMARK_BUBBLE_TITLE_TEXT).c_str(), | |
| 193 name_entry_, | |
| 194 l10n_util::GetStringUTF8(IDS_BOOKMARK_BUBBLE_FOLDER_TEXT).c_str(), | |
| 195 folder_combo_, | |
| 196 NULL); | |
| 197 | |
| 198 GtkWidget* bottom = gtk_hbox_new(FALSE, 0); | |
| 199 // We want the buttons on the right, so just use an expanding label to fill | |
| 200 // all of the extra space on the right. | |
| 201 gtk_box_pack_start(GTK_BOX(bottom), gtk_label_new(""), | |
| 202 TRUE, TRUE, 0); | |
| 203 gtk_box_pack_start(GTK_BOX(bottom), edit_button, | |
| 204 FALSE, FALSE, 4); | |
| 205 gtk_box_pack_start(GTK_BOX(bottom), close_button, | |
| 206 FALSE, FALSE, 0); | |
| 207 | |
| 208 gtk_box_pack_start(GTK_BOX(content), top, TRUE, TRUE, 0); | |
| 209 gtk_box_pack_start(GTK_BOX(content), table, TRUE, TRUE, 0); | |
| 210 gtk_box_pack_start(GTK_BOX(content), bottom, TRUE, TRUE, 0); | |
| 211 // We want the focus to start on the entry, not on the remove button. | |
| 212 gtk_container_set_focus_child(GTK_CONTAINER(content), table); | |
| 213 | |
| 214 gtk_box_pack_start(GTK_BOX(bubble_container), content, TRUE, TRUE, 0); | |
| 215 | |
| 216 if (SyncPromoUI::ShouldShowSyncPromo(profile_)) { | |
| 217 std::string link_text = | |
| 218 l10n_util::GetStringUTF8(IDS_BOOKMARK_SYNC_PROMO_LINK); | |
| 219 char* link_markup = g_markup_printf_escaped(kPromoLinkMarkup, | |
| 220 link_text.c_str()); | |
| 221 base::string16 link_markup_utf16; | |
| 222 base::UTF8ToUTF16(link_markup, strlen(link_markup), &link_markup_utf16); | |
| 223 g_free(link_markup); | |
| 224 | |
| 225 std::string promo_markup = l10n_util::GetStringFUTF8( | |
| 226 IDS_BOOKMARK_SYNC_PROMO_MESSAGE, | |
| 227 link_markup_utf16); | |
| 228 | |
| 229 promo_ = gtk_event_box_new(); | |
| 230 gtk_widget_set_app_paintable(promo_, TRUE); | |
| 231 | |
| 232 promo_label_ = gtk_label_new(NULL); | |
| 233 gtk_label_set_markup(GTK_LABEL(promo_label_), promo_markup.c_str()); | |
| 234 gtk_misc_set_alignment(GTK_MISC(promo_label_), 0.0, 0.0); | |
| 235 gtk_misc_set_padding(GTK_MISC(promo_label_), | |
| 236 ui::kContentAreaBorder, | |
| 237 kPromoVerticalPadding); | |
| 238 | |
| 239 // Custom link color. | |
| 240 gtk_rc_parse_string(kPromoLinkStyle); | |
| 241 | |
| 242 UpdatePromoColors(); | |
| 243 | |
| 244 gtk_container_add(GTK_CONTAINER(promo_), promo_label_); | |
| 245 gtk_box_pack_start(GTK_BOX(bubble_container), promo_, TRUE, TRUE, 0); | |
| 246 g_signal_connect(promo_, | |
| 247 "realize", | |
| 248 G_CALLBACK(&OnSyncPromoRealizeThunk), | |
| 249 this); | |
| 250 g_signal_connect(promo_, | |
| 251 "expose-event", | |
| 252 G_CALLBACK(&OnSyncPromoExposeThunk), | |
| 253 this); | |
| 254 g_signal_connect(promo_label_, | |
| 255 "activate-link", | |
| 256 G_CALLBACK(&OnSignInClickedThunk), | |
| 257 this); | |
| 258 } | |
| 259 | |
| 260 bubble_ = BubbleGtk::Show(anchor_, | |
| 261 NULL, | |
| 262 bubble_container, | |
| 263 BubbleGtk::ANCHOR_TOP_RIGHT, | |
| 264 BubbleGtk::MATCH_SYSTEM_THEME | | |
| 265 BubbleGtk::POPUP_WINDOW | | |
| 266 BubbleGtk::GRAB_INPUT, | |
| 267 theme_service_, | |
| 268 this); // delegate | |
| 269 if (!bubble_) { | |
| 270 NOTREACHED(); | |
| 271 return; | |
| 272 } | |
| 273 | |
| 274 g_signal_connect(content, "destroy", | |
| 275 G_CALLBACK(&OnDestroyThunk), this); | |
| 276 g_signal_connect(name_entry_, "activate", | |
| 277 G_CALLBACK(&OnNameActivateThunk), this); | |
| 278 g_signal_connect(folder_combo_, "changed", | |
| 279 G_CALLBACK(&OnFolderChangedThunk), this); | |
| 280 g_signal_connect(edit_button, "clicked", | |
| 281 G_CALLBACK(&OnEditClickedThunk), this); | |
| 282 g_signal_connect(close_button, "clicked", | |
| 283 G_CALLBACK(&OnCloseClickedThunk), this); | |
| 284 g_signal_connect(remove_button_, "clicked", | |
| 285 G_CALLBACK(&OnRemoveClickedThunk), this); | |
| 286 | |
| 287 registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED, | |
| 288 content::Source<ThemeService>(theme_service_)); | |
| 289 theme_service_->InitThemesFor(this); | |
| 290 } | |
| 291 | |
| 292 BookmarkBubbleGtk::~BookmarkBubbleGtk() { | |
| 293 DCHECK(bookmark_bubble_); | |
| 294 bookmark_bubble_ = NULL; | |
| 295 | |
| 296 if (apply_edits_) { | |
| 297 ApplyEdits(); | |
| 298 } else if (remove_bookmark_) { | |
| 299 const BookmarkNode* node = model_->GetMostRecentlyAddedNodeForURL(url_); | |
| 300 if (node) | |
| 301 model_->Remove(node->parent(), node->parent()->GetIndexOf(node)); | |
| 302 } | |
| 303 } | |
| 304 | |
| 305 void BookmarkBubbleGtk::OnDestroy(GtkWidget* widget) { | |
| 306 // We are self deleting, we have a destroy signal setup to catch when we | |
| 307 // destroyed (via the BubbleGtk being destroyed), and delete ourself. | |
| 308 delete this; | |
| 309 } | |
| 310 | |
| 311 void BookmarkBubbleGtk::OnNameActivate(GtkWidget* widget) { | |
| 312 bubble_->Close(); | |
| 313 } | |
| 314 | |
| 315 void BookmarkBubbleGtk::OnFolderChanged(GtkWidget* widget) { | |
| 316 int index = gtk_combo_box_get_active(GTK_COMBO_BOX(folder_combo_)); | |
| 317 if (index == folder_combo_model_->GetItemCount() - 1) { | |
| 318 content::RecordAction( | |
| 319 UserMetricsAction("BookmarkBubble_EditFromCombobox")); | |
| 320 // GTK doesn't handle having the combo box destroyed from the changed | |
| 321 // signal. Since showing the editor also closes the bubble, delay this | |
| 322 // so that GTK can unwind. Specifically gtk_menu_shell_button_release | |
| 323 // will run, and we need to keep the combo box alive until then. | |
| 324 base::MessageLoop::current()->PostTask( | |
| 325 FROM_HERE, | |
| 326 base::Bind(&BookmarkBubbleGtk::ShowEditor, factory_.GetWeakPtr())); | |
| 327 } | |
| 328 } | |
| 329 | |
| 330 void BookmarkBubbleGtk::OnEditClicked(GtkWidget* widget) { | |
| 331 content::RecordAction(UserMetricsAction("BookmarkBubble_Edit")); | |
| 332 ShowEditor(); | |
| 333 } | |
| 334 | |
| 335 void BookmarkBubbleGtk::OnCloseClicked(GtkWidget* widget) { | |
| 336 bubble_->Close(); | |
| 337 } | |
| 338 | |
| 339 void BookmarkBubbleGtk::OnRemoveClicked(GtkWidget* widget) { | |
| 340 content::RecordAction(UserMetricsAction("BookmarkBubble_Unstar")); | |
| 341 | |
| 342 apply_edits_ = false; | |
| 343 remove_bookmark_ = true; | |
| 344 bubble_->Close(); | |
| 345 } | |
| 346 | |
| 347 gboolean BookmarkBubbleGtk::OnSignInClicked(GtkWidget* widget, gchar* uri) { | |
| 348 GtkWindow* window = GTK_WINDOW(gtk_widget_get_toplevel(anchor_)); | |
| 349 Browser* browser = chrome::FindBrowserWithWindow(window); | |
| 350 chrome::ShowBrowserSignin(browser, signin::SOURCE_BOOKMARK_BUBBLE); | |
| 351 bubble_->Close(); | |
| 352 return TRUE; | |
| 353 } | |
| 354 | |
| 355 void BookmarkBubbleGtk::OnSyncPromoRealize(GtkWidget* widget) { | |
| 356 int width = gtk_util::GetWidgetSize(widget).width(); | |
| 357 gtk_util::SetLabelWidth(promo_label_, width); | |
| 358 } | |
| 359 | |
| 360 gboolean BookmarkBubbleGtk::OnSyncPromoExpose(GtkWidget* widget, | |
| 361 GdkEventExpose* event) { | |
| 362 GtkAllocation allocation; | |
| 363 gtk_widget_get_allocation(widget, &allocation); | |
| 364 | |
| 365 gfx::CanvasSkiaPaint canvas(event); | |
| 366 | |
| 367 // Draw a border on top of the promo. | |
| 368 canvas.DrawLine(gfx::Point(0, 0), | |
| 369 gfx::Point(allocation.width + 1, 0), | |
| 370 kPromoBorderColor); | |
| 371 | |
| 372 // Redraw the rounded corners of the bubble that are hidden by the | |
| 373 // background of the promo. | |
| 374 SkPaint points_paint; | |
| 375 points_paint.setColor(kBubbleBorderColor); | |
| 376 points_paint.setStrokeWidth(SkIntToScalar(1)); | |
| 377 canvas.DrawPoint(gfx::Point(0, allocation.height - 1), points_paint); | |
| 378 canvas.DrawPoint(gfx::Point(allocation.width - 1, allocation.height - 1), | |
| 379 points_paint); | |
| 380 | |
| 381 return FALSE; // Propagate expose to children. | |
| 382 } | |
| 383 | |
| 384 void BookmarkBubbleGtk::UpdatePromoColors() { | |
| 385 if (!promo_) | |
| 386 return; | |
| 387 | |
| 388 GdkColor promo_background_color; | |
| 389 | |
| 390 if (!theme_service_->UsingNativeTheme()) { | |
| 391 promo_background_color = kPromoBackgroundColor; | |
| 392 gtk_widget_set_name(promo_label_, "sign-in-link"); | |
| 393 gtk_util::SetLabelColor(promo_label_, &kPromoTextColor); | |
| 394 } else { | |
| 395 promo_background_color = theme_service_->GetGdkColor( | |
| 396 ThemeProperties::COLOR_TOOLBAR); | |
| 397 gtk_widget_set_name(promo_label_, "sign-in-link-theme-color"); | |
| 398 } | |
| 399 | |
| 400 gtk_widget_modify_bg(promo_, GTK_STATE_NORMAL, &promo_background_color); | |
| 401 | |
| 402 // No visible highlight color when the mouse is over the link. | |
| 403 gtk_widget_modify_base(promo_label_, | |
| 404 GTK_STATE_ACTIVE, | |
| 405 &promo_background_color); | |
| 406 gtk_widget_modify_base(promo_label_, | |
| 407 GTK_STATE_PRELIGHT, | |
| 408 &promo_background_color); | |
| 409 } | |
| 410 | |
| 411 void BookmarkBubbleGtk::ApplyEdits() { | |
| 412 // Set this to make sure we don't attempt to apply edits again. | |
| 413 apply_edits_ = false; | |
| 414 | |
| 415 const BookmarkNode* node = model_->GetMostRecentlyAddedNodeForURL(url_); | |
| 416 if (node) { | |
| 417 const base::string16 new_title( | |
| 418 base::UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(name_entry_)))); | |
| 419 | |
| 420 if (new_title != node->GetTitle()) { | |
| 421 model_->SetTitle(node, new_title); | |
| 422 content::RecordAction( | |
| 423 UserMetricsAction("BookmarkBubble_ChangeTitleInBubble")); | |
| 424 } | |
| 425 | |
| 426 folder_combo_model_->MaybeChangeParent( | |
| 427 node, gtk_combo_box_get_active(GTK_COMBO_BOX(folder_combo_))); | |
| 428 } | |
| 429 } | |
| 430 | |
| 431 std::string BookmarkBubbleGtk::GetTitle() { | |
| 432 const BookmarkNode* node = model_->GetMostRecentlyAddedNodeForURL(url_); | |
| 433 if (!node) { | |
| 434 NOTREACHED(); | |
| 435 return std::string(); | |
| 436 } | |
| 437 | |
| 438 return base::UTF16ToUTF8(node->GetTitle()); | |
| 439 } | |
| 440 | |
| 441 void BookmarkBubbleGtk::ShowEditor() { | |
| 442 const BookmarkNode* node = model_->GetMostRecentlyAddedNodeForURL(url_); | |
| 443 | |
| 444 // Commit any edits now. | |
| 445 ApplyEdits(); | |
| 446 | |
| 447 // Closing might delete us, so we'll cache what we need on the stack. | |
| 448 Profile* profile = profile_; | |
| 449 GtkWindow* toplevel = GTK_WINDOW(gtk_widget_get_toplevel(anchor_)); | |
| 450 | |
| 451 // Close the bubble, deleting the C++ objects, etc. | |
| 452 bubble_->Close(); | |
| 453 | |
| 454 if (node) { | |
| 455 BookmarkEditor::Show(toplevel, profile, | |
| 456 BookmarkEditor::EditDetails::EditNode(node), | |
| 457 BookmarkEditor::SHOW_TREE); | |
| 458 } | |
| 459 } | |
| 460 | |
| 461 void BookmarkBubbleGtk::InitFolderComboModel() { | |
| 462 const BookmarkNode* node = model_->GetMostRecentlyAddedNodeForURL(url_); | |
| 463 DCHECK(node); | |
| 464 | |
| 465 folder_combo_model_.reset(new RecentlyUsedFoldersComboModel(model_, node)); | |
| 466 | |
| 467 GtkListStore* store = gtk_list_store_new(COLUMN_COUNT, | |
| 468 G_TYPE_STRING, G_TYPE_BOOLEAN); | |
| 469 | |
| 470 // We always have nodes + 1 entries in the combo. The last entry is an entry | |
| 471 // that reads 'Choose Another Folder...' and when chosen Bookmark Editor is | |
| 472 // opened. | |
| 473 for (int i = 0; i < folder_combo_model_->GetItemCount(); ++i) { | |
| 474 const bool is_separator = folder_combo_model_->IsItemSeparatorAt(i); | |
| 475 const std::string name = is_separator ? | |
| 476 std::string() : base::UTF16ToUTF8(folder_combo_model_->GetItemAt(i)); | |
| 477 | |
| 478 GtkTreeIter iter; | |
| 479 gtk_list_store_append(store, &iter); | |
| 480 gtk_list_store_set(store, &iter, | |
| 481 COLUMN_NAME, name.c_str(), | |
| 482 COLUMN_IS_SEPARATOR, is_separator, | |
| 483 -1); | |
| 484 } | |
| 485 | |
| 486 folder_combo_ = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store)); | |
| 487 | |
| 488 gtk_combo_box_set_active(GTK_COMBO_BOX(folder_combo_), | |
| 489 folder_combo_model_->GetDefaultIndex()); | |
| 490 gtk_combo_box_set_row_separator_func(GTK_COMBO_BOX(folder_combo_), | |
| 491 IsSeparator, NULL, NULL); | |
| 492 g_object_unref(store); | |
| 493 | |
| 494 GtkCellRenderer* renderer = gtk_cell_renderer_text_new(); | |
| 495 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(folder_combo_), renderer, TRUE); | |
| 496 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(folder_combo_), renderer, | |
| 497 "text", COLUMN_NAME, | |
| 498 NULL); | |
| 499 } | |
| OLD | NEW |