Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(624)

Side by Side Diff: chrome/browser/ui/gtk/bookmarks/bookmark_bubble_gtk.cc

Issue 231733005: Delete the GTK+ port of Chrome. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Remerge to ToT Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698