summaryrefslogtreecommitdiffstats
path: root/chrome/browser/gtk/bookmark_bubble_gtk.cc
diff options
context:
space:
mode:
authorBen Murdoch <benm@google.com>2010-07-29 17:14:53 +0100
committerBen Murdoch <benm@google.com>2010-08-04 14:29:45 +0100
commitc407dc5cd9bdc5668497f21b26b09d988ab439de (patch)
tree7eaf8707c0309516bdb042ad976feedaf72b0bb1 /chrome/browser/gtk/bookmark_bubble_gtk.cc
parent0998b1cdac5733f299c12d88bc31ef9c8035b8fa (diff)
downloadexternal_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.zip
external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.gz
external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.bz2
Merge Chromium src@r53293
Change-Id: Ia79acf8670f385cee48c45b0a75371d8e950af34
Diffstat (limited to 'chrome/browser/gtk/bookmark_bubble_gtk.cc')
-rw-r--r--chrome/browser/gtk/bookmark_bubble_gtk.cc353
1 files changed, 353 insertions, 0 deletions
diff --git a/chrome/browser/gtk/bookmark_bubble_gtk.cc b/chrome/browser/gtk/bookmark_bubble_gtk.cc
new file mode 100644
index 0000000..d573c84
--- /dev/null
+++ b/chrome/browser/gtk/bookmark_bubble_gtk.cc
@@ -0,0 +1,353 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/gtk/bookmark_bubble_gtk.h"
+
+#include <gtk/gtk.h>
+
+#include "app/l10n_util.h"
+#include "app/resource_bundle.h"
+#include "base/basictypes.h"
+#include "base/i18n/rtl.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "chrome/browser/bookmarks/bookmark_editor.h"
+#include "chrome/browser/bookmarks/bookmark_model.h"
+#include "chrome/browser/bookmarks/bookmark_utils.h"
+#include "chrome/browser/bookmarks/recently_used_folders_combo_model.h"
+#include "chrome/browser/gtk/gtk_chrome_link_button.h"
+#include "chrome/browser/gtk/gtk_theme_provider.h"
+#include "chrome/browser/gtk/gtk_util.h"
+#include "chrome/browser/gtk/info_bubble_gtk.h"
+#include "chrome/browser/metrics/user_metrics.h"
+#include "chrome/browser/profile.h"
+#include "chrome/common/notification_service.h"
+#include "gfx/gtk_util.h"
+#include "grit/generated_resources.h"
+
+namespace {
+
+// We basically have a singleton, since a bubble is sort of app-modal. This
+// keeps track of the currently open bubble, or NULL if none is open.
+BookmarkBubbleGtk* g_bubble = NULL;
+
+// Padding between content and edge of info bubble.
+const int kContentBorder = 7;
+
+
+} // namespace
+
+// static
+void BookmarkBubbleGtk::Show(GtkWidget* anchor,
+ Profile* profile,
+ const GURL& url,
+ bool newly_bookmarked) {
+ // TODO(deanm): The Views code deals with the possibility of a bubble already
+ // being open, and then it just does nothing. I am not sure how this could
+ // happen with the style of our GTK bubble since it has a grab. I would also
+ // think that closing the previous bubble and opening the new one would make
+ // more sense, but I guess then you would commit the bubble's changes.
+ DCHECK(!g_bubble);
+ g_bubble = new BookmarkBubbleGtk(anchor, profile, url, newly_bookmarked);
+}
+
+void BookmarkBubbleGtk::InfoBubbleClosing(InfoBubbleGtk* info_bubble,
+ bool closed_by_escape) {
+ if (closed_by_escape) {
+ remove_bookmark_ = newly_bookmarked_;
+ apply_edits_ = false;
+ }
+
+ NotificationService::current()->Notify(
+ NotificationType::BOOKMARK_BUBBLE_HIDDEN,
+ Source<Profile>(profile_->GetOriginalProfile()),
+ NotificationService::NoDetails());
+}
+
+void BookmarkBubbleGtk::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ DCHECK(type == NotificationType::BROWSER_THEME_CHANGED);
+
+ gtk_chrome_link_button_set_use_gtk_theme(
+ GTK_CHROME_LINK_BUTTON(remove_button_),
+ theme_provider_->UseGtkTheme());
+
+ if (theme_provider_->UseGtkTheme()) {
+ for (std::vector<GtkWidget*>::iterator it = labels_.begin();
+ it != labels_.end(); ++it) {
+ gtk_widget_modify_fg(*it, GTK_STATE_NORMAL, NULL);
+ }
+ } else {
+ for (std::vector<GtkWidget*>::iterator it = labels_.begin();
+ it != labels_.end(); ++it) {
+ gtk_widget_modify_fg(*it, GTK_STATE_NORMAL, &gfx::kGdkBlack);
+ }
+ }
+}
+
+BookmarkBubbleGtk::BookmarkBubbleGtk(GtkWidget* anchor,
+ Profile* profile,
+ const GURL& url,
+ bool newly_bookmarked)
+ : url_(url),
+ profile_(profile),
+ theme_provider_(GtkThemeProvider::GetFrom(profile_)),
+ anchor_(anchor),
+ content_(NULL),
+ name_entry_(NULL),
+ folder_combo_(NULL),
+ bubble_(NULL),
+ factory_(this),
+ newly_bookmarked_(newly_bookmarked),
+ apply_edits_(true),
+ remove_bookmark_(false) {
+ GtkWidget* label = gtk_label_new(l10n_util::GetStringUTF8(
+ newly_bookmarked_ ? IDS_BOOMARK_BUBBLE_PAGE_BOOKMARKED :
+ IDS_BOOMARK_BUBBLE_PAGE_BOOKMARK).c_str());
+ labels_.push_back(label);
+ remove_button_ = gtk_chrome_link_button_new(
+ l10n_util::GetStringUTF8(IDS_BOOMARK_BUBBLE_REMOVE_BOOKMARK).c_str());
+ GtkWidget* edit_button = gtk_button_new_with_label(
+ l10n_util::GetStringUTF8(IDS_BOOMARK_BUBBLE_OPTIONS).c_str());
+ GtkWidget* close_button = gtk_button_new_with_label(
+ l10n_util::GetStringUTF8(IDS_DONE).c_str());
+
+ // Our content is arranged in 3 rows. |top| contains a left justified
+ // message, and a right justified remove link button. |table| is the middle
+ // portion with the name entry and the folder combo. |bottom| is the final
+ // row with a spacer, and the edit... and close buttons on the right.
+ GtkWidget* content = gtk_vbox_new(FALSE, 5);
+ gtk_container_set_border_width(GTK_CONTAINER(content), kContentBorder);
+ GtkWidget* top = gtk_hbox_new(FALSE, 0);
+
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 1);
+ gtk_box_pack_start(GTK_BOX(top), label,
+ TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(top), remove_button_,
+ FALSE, FALSE, 0);
+
+ folder_combo_ = gtk_combo_box_new_text();
+ InitFolderComboModel();
+
+ // Create the edit entry for updating the bookmark name / title.
+ name_entry_ = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(name_entry_), GetTitle().c_str());
+
+ // We use a table to allow the labels to line up with each other, along
+ // with the entry and folder combo lining up.
+ GtkWidget* table = gtk_util::CreateLabeledControlsGroup(
+ &labels_,
+ l10n_util::GetStringUTF8(IDS_BOOMARK_BUBBLE_TITLE_TEXT).c_str(),
+ name_entry_,
+ l10n_util::GetStringUTF8(IDS_BOOMARK_BUBBLE_FOLDER_TEXT).c_str(),
+ folder_combo_,
+ NULL);
+
+ GtkWidget* bottom = gtk_hbox_new(FALSE, 0);
+ // We want the buttons on the right, so just use an expanding label to fill
+ // all of the extra space on the right.
+ gtk_box_pack_start(GTK_BOX(bottom), gtk_label_new(""),
+ TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(bottom), edit_button,
+ FALSE, FALSE, 4);
+ gtk_box_pack_start(GTK_BOX(bottom), close_button,
+ FALSE, FALSE, 0);
+
+ gtk_box_pack_start(GTK_BOX(content), top, TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(content), table, TRUE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(content), bottom, TRUE, TRUE, 0);
+ // We want the focus to start on the entry, not on the remove button.
+ gtk_container_set_focus_child(GTK_CONTAINER(content), table);
+
+ InfoBubbleGtk::ArrowLocationGtk arrow_location =
+ base::i18n::IsRTL() ?
+ InfoBubbleGtk::ARROW_LOCATION_TOP_LEFT :
+ InfoBubbleGtk::ARROW_LOCATION_TOP_RIGHT;
+ bubble_ = InfoBubbleGtk::Show(anchor_,
+ NULL,
+ content,
+ arrow_location,
+ true, // match_system_theme
+ true, // grab_input
+ theme_provider_,
+ this); // delegate
+ if (!bubble_) {
+ NOTREACHED();
+ return;
+ }
+
+ g_signal_connect(content, "destroy",
+ G_CALLBACK(&OnDestroyThunk), this);
+ g_signal_connect(name_entry_, "activate",
+ G_CALLBACK(&OnNameActivateThunk), this);
+ g_signal_connect(folder_combo_, "changed",
+ G_CALLBACK(&OnFolderChangedThunk), this);
+ g_signal_connect(folder_combo_, "notify::popup-shown",
+ G_CALLBACK(&OnFolderPopupShownThunk), this);
+ g_signal_connect(edit_button, "clicked",
+ G_CALLBACK(&OnEditClickedThunk), this);
+ g_signal_connect(close_button, "clicked",
+ G_CALLBACK(&OnCloseClickedThunk), this);
+ g_signal_connect(remove_button_, "clicked",
+ G_CALLBACK(&OnRemoveClickedThunk), this);
+
+ registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
+ NotificationService::AllSources());
+ theme_provider_->InitThemesFor(this);
+}
+
+BookmarkBubbleGtk::~BookmarkBubbleGtk() {
+ DCHECK(!content_); // |content_| should have already been destroyed.
+
+ DCHECK(g_bubble);
+ g_bubble = NULL;
+
+ if (apply_edits_) {
+ ApplyEdits();
+ } else if (remove_bookmark_) {
+ BookmarkModel* model = profile_->GetBookmarkModel();
+ const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url_);
+ if (node)
+ model->Remove(node->GetParent(), node->GetParent()->IndexOfChild(node));
+ }
+}
+
+void BookmarkBubbleGtk::OnDestroy(GtkWidget* widget) {
+ // We are self deleting, we have a destroy signal setup to catch when we
+ // destroyed (via the InfoBubble being destroyed), and delete ourself.
+ content_ = NULL; // We are being destroyed.
+ delete this;
+}
+
+void BookmarkBubbleGtk::OnNameActivate(GtkWidget* widget) {
+ bubble_->Close();
+}
+
+void BookmarkBubbleGtk::OnFolderChanged(GtkWidget* widget) {
+ int index = gtk_combo_box_get_active(GTK_COMBO_BOX(folder_combo_));
+ if (index == folder_combo_model_->GetItemCount() - 1) {
+ UserMetrics::RecordAction(
+ UserMetricsAction("BookmarkBubble_EditFromCombobox"), profile_);
+ // GTK doesn't handle having the combo box destroyed from the changed
+ // signal. Since showing the editor also closes the bubble, delay this
+ // so that GTK can unwind. Specifically gtk_menu_shell_button_release
+ // will run, and we need to keep the combo box alive until then.
+ MessageLoop::current()->PostTask(FROM_HERE,
+ factory_.NewRunnableMethod(&BookmarkBubbleGtk::ShowEditor));
+ }
+}
+
+void BookmarkBubbleGtk::OnFolderPopupShown(GtkWidget* widget,
+ GParamSpec* property) {
+ // GtkComboBox grabs the keyboard and pointer when it displays its popup,
+ // which steals the grabs that InfoBubbleGtk had installed. When the popup is
+ // hidden, we notify InfoBubbleGtk so it can try to reacquire the grabs
+ // (otherwise, GTK won't activate our widgets when the user clicks in them).
+ gboolean popup_shown = FALSE;
+ g_object_get(G_OBJECT(folder_combo_), "popup-shown", &popup_shown, NULL);
+ if (!popup_shown)
+ bubble_->HandlePointerAndKeyboardUngrabbedByContent();
+}
+
+void BookmarkBubbleGtk::OnEditClicked(GtkWidget* widget) {
+ UserMetrics::RecordAction(UserMetricsAction("BookmarkBubble_Edit"),
+ profile_);
+ ShowEditor();
+}
+
+void BookmarkBubbleGtk::OnCloseClicked(GtkWidget* widget) {
+ bubble_->Close();
+}
+
+void BookmarkBubbleGtk::OnRemoveClicked(GtkWidget* widget) {
+ UserMetrics::RecordAction(UserMetricsAction("BookmarkBubble_Unstar"),
+ profile_);
+
+ apply_edits_ = false;
+ remove_bookmark_ = true;
+ bubble_->Close();
+}
+
+void BookmarkBubbleGtk::ApplyEdits() {
+ // Set this to make sure we don't attempt to apply edits again.
+ apply_edits_ = false;
+
+ BookmarkModel* model = profile_->GetBookmarkModel();
+ const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url_);
+ if (node) {
+ // NOTE: Would be nice to save a strlen and use gtk_entry_get_text_length,
+ // but it is fairly new and not always in our GTK version.
+ const std::wstring new_title(
+ UTF8ToWide(gtk_entry_get_text(GTK_ENTRY(name_entry_))));
+
+ if (new_title != node->GetTitle()) {
+ model->SetTitle(node, new_title);
+ UserMetrics::RecordAction(
+ UserMetricsAction("BookmarkBubble_ChangeTitleInBubble"),
+ profile_);
+ }
+
+ int index = gtk_combo_box_get_active(GTK_COMBO_BOX(folder_combo_));
+
+ // Last index means 'Choose another folder...'
+ if (index < folder_combo_model_->GetItemCount() - 1) {
+ const BookmarkNode* new_parent = folder_combo_model_->GetNodeAt(index);
+ if (new_parent != node->GetParent()) {
+ UserMetrics::RecordAction(
+ UserMetricsAction("BookmarkBubble_ChangeParent"), profile_);
+ model->Move(node, new_parent, new_parent->GetChildCount());
+ }
+ }
+ }
+}
+
+std::string BookmarkBubbleGtk::GetTitle() {
+ BookmarkModel* bookmark_model= profile_->GetBookmarkModel();
+ const BookmarkNode* node =
+ bookmark_model->GetMostRecentlyAddedNodeForURL(url_);
+ if (!node) {
+ NOTREACHED();
+ return std::string();
+ }
+
+ return WideToUTF8(node->GetTitle());
+}
+
+void BookmarkBubbleGtk::ShowEditor() {
+ const BookmarkNode* node =
+ profile_->GetBookmarkModel()->GetMostRecentlyAddedNodeForURL(url_);
+
+ // Commit any edits now.
+ ApplyEdits();
+
+ // Closing might delete us, so we'll cache what we need on the stack.
+ Profile* profile = profile_;
+ GtkWindow* toplevel = GTK_WINDOW(gtk_widget_get_toplevel(anchor_));
+
+ // Close the bubble, deleting the C++ objects, etc.
+ bubble_->Close();
+
+ if (node) {
+ BookmarkEditor::Show(toplevel, profile, NULL,
+ BookmarkEditor::EditDetails(node),
+ BookmarkEditor::SHOW_TREE);
+ }
+}
+
+void BookmarkBubbleGtk::InitFolderComboModel() {
+ folder_combo_model_.reset(new RecentlyUsedFoldersComboModel(
+ profile_->GetBookmarkModel(),
+ profile_->GetBookmarkModel()->GetMostRecentlyAddedNodeForURL(url_)));
+
+ // We always have nodes + 1 entries in the combo. The last entry will be
+ // the 'Select another folder...' entry that opens the bookmark editor.
+ for (int i = 0; i < folder_combo_model_->GetItemCount(); ++i) {
+ gtk_combo_box_append_text(GTK_COMBO_BOX(folder_combo_),
+ WideToUTF8(folder_combo_model_->GetItemAt(i)).c_str());
+ }
+
+ gtk_combo_box_set_active(GTK_COMBO_BOX(folder_combo_),
+ folder_combo_model_->node_parent_index());
+}