diff options
author | estade@chromium.org <estade@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-08 23:11:03 +0000 |
---|---|---|
committer | estade@chromium.org <estade@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-08 23:11:03 +0000 |
commit | af4282a5e55c5c14413654f498a0117413ab0565 (patch) | |
tree | 35e45ce4b35ffacd25939dae68f99c1dd2679850 /chrome/browser/gtk | |
parent | 53b31efebbd3ceffdde84bb62f78986620179040 (diff) | |
download | chromium_src-af4282a5e55c5c14413654f498a0117413ab0565.zip chromium_src-af4282a5e55c5c14413654f498a0117413ab0565.tar.gz chromium_src-af4282a5e55c5c14413654f498a0117413ab0565.tar.bz2 |
Make the bookmark toolbar folders act like a menu bar.
I didn't convert the page/app menu to use the new helper class in an effort to keep this patch small and reviewable.
BUG=19675
BUG=15889
TEST=fiddled with it for while, and ran it under valgrind
Review URL: http://codereview.chromium.org/200029
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@25677 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/gtk')
-rw-r--r-- | chrome/browser/gtk/bookmark_bar_gtk.cc | 97 | ||||
-rw-r--r-- | chrome/browser/gtk/bookmark_bar_gtk.h | 14 | ||||
-rw-r--r-- | chrome/browser/gtk/bookmark_menu_controller_gtk.cc | 16 | ||||
-rw-r--r-- | chrome/browser/gtk/bookmark_menu_controller_gtk.h | 4 | ||||
-rw-r--r-- | chrome/browser/gtk/menu_bar_helper.cc | 181 | ||||
-rw-r--r-- | chrome/browser/gtk/menu_bar_helper.h | 82 |
6 files changed, 361 insertions, 33 deletions
diff --git a/chrome/browser/gtk/bookmark_bar_gtk.cc b/chrome/browser/gtk/bookmark_bar_gtk.cc index eb16e2e..0504349 100644 --- a/chrome/browser/gtk/bookmark_bar_gtk.cc +++ b/chrome/browser/gtk/bookmark_bar_gtk.cc @@ -101,7 +101,8 @@ BookmarkBarGtk::BookmarkBarGtk(Profile* profile, Browser* browser, dragged_node_(NULL), toolbar_drop_item_(NULL), theme_provider_(GtkThemeProvider::GetFrom(profile)), - show_instructions_(true) { + show_instructions_(true), + menu_bar_helper_(this) { Init(profile); SetProfile(profile); @@ -310,9 +311,12 @@ void BookmarkBarGtk::BookmarkNodeAdded(BookmarkModel* model, } DCHECK(index >= 0 && index <= GetBookmarkButtonCount()); - GtkToolItem* item = CreateBookmarkToolItem(parent->GetChild(index)); + const BookmarkNode* node = parent->GetChild(index); + GtkToolItem* item = CreateBookmarkToolItem(node); gtk_toolbar_insert(GTK_TOOLBAR(bookmark_toolbar_.get()), item, index); + if (node->is_folder()) + menu_bar_helper_.Add(gtk_bin_get_child(GTK_BIN(item))); SetInstructionState(); SetChevronState(); @@ -330,6 +334,7 @@ void BookmarkBarGtk::BookmarkNodeRemoved(BookmarkModel* model, GtkWidget* to_remove = GTK_WIDGET(gtk_toolbar_get_nth_item( GTK_TOOLBAR(bookmark_toolbar_.get()), old_index)); + menu_bar_helper_.Remove(gtk_bin_get_child(GTK_BIN(to_remove))); gtk_container_remove(GTK_CONTAINER(bookmark_toolbar_.get()), to_remove); @@ -376,6 +381,8 @@ void BookmarkBarGtk::CreateAllBookmarkButtons() { for (int i = 0; i < node->GetChildCount(); ++i) { GtkToolItem* item = CreateBookmarkToolItem(node->GetChild(i)); gtk_toolbar_insert(GTK_TOOLBAR(bookmark_toolbar_.get()), item, -1); + if (node->is_folder()) + menu_bar_helper_.Add(gtk_bin_get_child(GTK_BIN(item))); } bookmark_utils::ConfigureButtonForNode(model_->other_node(), @@ -400,7 +407,7 @@ void BookmarkBarGtk::SetChevronState() { if (GTK_WIDGET_VISIBLE(overflow_button_)) extra_space = overflow_button_->allocation.width; - int overflow_idx = GetFirstHiddenBookmark(extra_space); + int overflow_idx = GetFirstHiddenBookmark(extra_space, NULL); if (overflow_idx == -1) gtk_widget_hide(overflow_button_); else @@ -409,6 +416,9 @@ void BookmarkBarGtk::SetChevronState() { void BookmarkBarGtk::RemoveAllBookmarkButtons() { gtk_util::RemoveAllChildren(bookmark_toolbar_.get()); + menu_bar_helper_.Clear(); + menu_bar_helper_.Add(other_bookmarks_button_); + menu_bar_helper_.Add(overflow_button_); } int BookmarkBarGtk::GetBookmarkButtonCount() { @@ -433,7 +443,8 @@ void BookmarkBarGtk::SetOverflowButtonAppearance() { SetChevronState(); } -int BookmarkBarGtk::GetFirstHiddenBookmark(int extra_space) { +int BookmarkBarGtk::GetFirstHiddenBookmark( + int extra_space, std::vector<GtkWidget*>* showing_folders) { int rv = 0; bool overflow = false; GList* toolbar_items = @@ -445,6 +456,10 @@ int BookmarkBarGtk::GetFirstHiddenBookmark(int extra_space) { overflow = true; break; } + if (showing_folders && + model_->GetBookmarkBarNode()->GetChild(rv)->is_folder()) { + showing_folders->push_back(gtk_bin_get_child(GTK_BIN(tool_item))); + } rv++; } @@ -534,7 +549,6 @@ GtkWidget* BookmarkBarGtk::CreateBookmarkButton(const BookmarkNode* node) { G_CALLBACK(OnClicked), this); gtk_util::SetButtonTriggersNavigation(button); } else { - // TODO(erg): This button can also be a drop target. ConnectFolderButtonEvents(button); } @@ -717,24 +731,7 @@ void BookmarkBarGtk::OnButtonDragGet(GtkWidget* widget, GdkDragContext* context, // static void BookmarkBarGtk::OnFolderClicked(GtkWidget* sender, BookmarkBarGtk* bar) { - const BookmarkNode* node = bar->GetNodeForToolButton(sender); - DCHECK(node); - DCHECK(bar->page_navigator_); - - int start_child_idx = 0; - if (sender == bar->overflow_button_) - start_child_idx = bar->GetFirstHiddenBookmark(0); - - bar->current_menu_.reset( - new BookmarkMenuController(bar->browser_, bar->profile_, - bar->page_navigator_, - GTK_WINDOW(gtk_widget_get_toplevel(sender)), - node, - start_child_idx, - false)); - GdkEventButton* event = - reinterpret_cast<GdkEventButton*>(gtk_get_current_event()); - bar->current_menu_->Popup(sender, event->button, event->time); + bar->PopupForButton(sender); } // static @@ -972,3 +969,57 @@ gboolean BookmarkBarGtk::OnSeparatorExpose(GtkWidget* widget, return TRUE; } + +// MenuBarHelper::Delegate implementation -------------------------------------- +void BookmarkBarGtk::PopupForButton(GtkWidget* button) { + const BookmarkNode* node = GetNodeForToolButton(button); + DCHECK(node); + DCHECK(page_navigator_); + + int first_hidden = GetFirstHiddenBookmark(0, NULL); + if (button != overflow_button_ && button != other_bookmarks_button_ && + node->GetParent()->IndexOfChild(node) >= first_hidden) { + return; + } + + current_menu_.reset( + new BookmarkMenuController(browser_, profile_, + page_navigator_, + GTK_WINDOW(gtk_widget_get_toplevel(button)), + node, + button == overflow_button_ ? + first_hidden : 0, + false)); + menu_bar_helper_.MenuStartedShowing(button, current_menu_->widget()); + GdkEventButton* event = + reinterpret_cast<GdkEventButton*>(gtk_get_current_event()); + current_menu_->Popup(button, event->button, event->time); +} + +void BookmarkBarGtk::PopupForButtonNextTo(GtkWidget* button, + GtkMenuDirectionType dir) { + const BookmarkNode* relative_node = GetNodeForToolButton(button); + DCHECK(relative_node); + + // Find out the order of the buttons. + std::vector<GtkWidget*> folder_list; + const int first_hidden = GetFirstHiddenBookmark(0, &folder_list); + if (first_hidden != -1) + folder_list.push_back(overflow_button_); + folder_list.push_back(other_bookmarks_button_); + + // Find the position of |button|. + int button_idx = -1; + for (size_t i = 0; i < folder_list.size(); ++i) { + if (folder_list[i] == button) { + button_idx = i; + break; + } + } + DCHECK_NE(button_idx, -1); + + // Find the GtkWidget* for the actual target button. + int shift = dir == GTK_MENU_DIR_PARENT ? -1 : 1; + button_idx = (button_idx + shift + folder_list.size()) % folder_list.size(); + PopupForButton(folder_list[button_idx]); +} diff --git a/chrome/browser/gtk/bookmark_bar_gtk.h b/chrome/browser/gtk/bookmark_bar_gtk.h index afc7e8a..c7cb22d 100644 --- a/chrome/browser/gtk/bookmark_bar_gtk.h +++ b/chrome/browser/gtk/bookmark_bar_gtk.h @@ -8,10 +8,12 @@ #include <gtk/gtk.h> #include <string> +#include <vector> #include "app/slide_animation.h" #include "base/scoped_ptr.h" #include "chrome/browser/bookmarks/bookmark_model_observer.h" +#include "chrome/browser/gtk/menu_bar_helper.h" #include "chrome/browser/gtk/view_id_util.h" #include "chrome/common/notification_observer.h" #include "chrome/common/notification_registrar.h" @@ -28,6 +30,7 @@ struct GtkThemeProvider; class BookmarkBarGtk : public AnimationDelegate, public BookmarkModelObserver, + public MenuBarHelper::Delegate, public NotificationObserver { public: explicit BookmarkBarGtk(Profile* profile, Browser* browser, @@ -76,6 +79,10 @@ class BookmarkBarGtk : public AnimationDelegate, virtual void AnimationProgressed(const Animation* animation); virtual void AnimationEnded(const Animation* animation); + // MenuBarHelper::Delegate implementation ------------------------------------ + virtual void PopupForButton(GtkWidget* button); + virtual void PopupForButtonNextTo(GtkWidget* button, + GtkMenuDirectionType dir); private: // Helper function which generates GtkToolItems for |bookmark_toolbar_|. void CreateAllBookmarkButtons(); @@ -104,7 +111,10 @@ class BookmarkBarGtk : public AnimationDelegate, // |extra_space| is how much extra space to give the toolbar during the // calculation (for the purposes of determining if ditching the chevron // would be a good idea). - int GetFirstHiddenBookmark(int extra_space); + // If non-NULL, |showing_folders| is packed with all the folders that are + // showing on the bar. + int GetFirstHiddenBookmark(int extra_space, + std::vector<GtkWidget*>* showing_folders); // Overridden from BookmarkModelObserver: @@ -252,6 +262,8 @@ class BookmarkBarGtk : public AnimationDelegate, // Whether we should show the instructional text in the bookmark bar. bool show_instructions_; + MenuBarHelper menu_bar_helper_; + // The last displayed right click menu, or NULL if no menus have been // displayed yet. scoped_ptr<BookmarkContextMenu> current_context_menu_; diff --git a/chrome/browser/gtk/bookmark_menu_controller_gtk.cc b/chrome/browser/gtk/bookmark_menu_controller_gtk.cc index e17a918b..8334fd5 100644 --- a/chrome/browser/gtk/bookmark_menu_controller_gtk.cc +++ b/chrome/browser/gtk/bookmark_menu_controller_gtk.cc @@ -97,16 +97,16 @@ BookmarkMenuController::BookmarkMenuController(Browser* browser, node_(node), ignore_button_release_(false), triggering_widget_(NULL) { - menu_.Own(gtk_menu_new()); - BuildMenu(node, start_child_index, menu_.get()); - g_signal_connect(menu_.get(), "hide", + menu_ = gtk_menu_new(); + BuildMenu(node, start_child_index, menu_); + g_signal_connect(menu_, "hide", G_CALLBACK(OnMenuHidden), this); - gtk_widget_show_all(menu_.get()); + gtk_widget_show_all(menu_); } BookmarkMenuController::~BookmarkMenuController() { profile_->GetBookmarkModel()->RemoveObserver(this); - menu_.Destroy(); + gtk_menu_popdown(GTK_MENU(menu_)); } void BookmarkMenuController::Popup(GtkWidget* widget, gint button_type, @@ -116,13 +116,13 @@ void BookmarkMenuController::Popup(GtkWidget* widget, gint button_type, triggering_widget_ = widget; gtk_chrome_button_set_paint_state(GTK_CHROME_BUTTON(widget), GTK_STATE_ACTIVE); - gtk_menu_popup(GTK_MENU(menu_.get()), NULL, NULL, + gtk_menu_popup(GTK_MENU(menu_), NULL, NULL, &MenuGtk::MenuPositionFunc, widget, button_type, timestamp); } void BookmarkMenuController::BookmarkModelChanged() { - gtk_menu_popdown(GTK_MENU(menu_.get())); + gtk_menu_popdown(GTK_MENU(menu_)); } void BookmarkMenuController::BookmarkNodeFavIconLoaded( @@ -279,7 +279,7 @@ gboolean BookmarkMenuController::OnButtonReleased( // We need to manually dismiss the popup menu because we're overriding // button-release-event. - gtk_menu_popdown(GTK_MENU(controller->menu_.get())); + gtk_menu_popdown(GTK_MENU(controller->menu_)); return TRUE; } diff --git a/chrome/browser/gtk/bookmark_menu_controller_gtk.h b/chrome/browser/gtk/bookmark_menu_controller_gtk.h index 7c5cc5f..f3182df 100644 --- a/chrome/browser/gtk/bookmark_menu_controller_gtk.h +++ b/chrome/browser/gtk/bookmark_menu_controller_gtk.h @@ -35,6 +35,8 @@ class BookmarkMenuController : public BaseBookmarkModelObserver { bool show_other_folder); virtual ~BookmarkMenuController(); + GtkWidget* widget() { return menu_; } + // Pops up the menu. |widget| must be a GtkChromeButton. void Popup(GtkWidget* widget, gint button_type, guint32 timestamp); @@ -104,7 +106,7 @@ class BookmarkMenuController : public BaseBookmarkModelObserver { // all sorts of weird non-standard things with this menu, like: // - The menu is a drag target // - The menu items have context menus. - OwnedWidgetGtk menu_; + GtkWidget* menu_; // Whether we should ignore the next button release event (because we were // dragging). diff --git a/chrome/browser/gtk/menu_bar_helper.cc b/chrome/browser/gtk/menu_bar_helper.cc new file mode 100644 index 0000000..4eb228a --- /dev/null +++ b/chrome/browser/gtk/menu_bar_helper.cc @@ -0,0 +1,181 @@ +// Copyright (c) 2009 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/menu_bar_helper.h" + +#include "base/logging.h" +#include "chrome/common/gtk_util.h" + +namespace { + +// Recursively find all the GtkMenus that are attached to menu item |child| +// and add them to |data|, which is a vector of GtkWidgets. +void PopulateSubmenus(GtkWidget* child, gpointer data) { + std::vector<GtkWidget*>* submenus = + static_cast<std::vector<GtkWidget*>*>(data); + GtkWidget* submenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(child)); + if (submenu) { + submenus->push_back(submenu); + gtk_container_foreach(GTK_CONTAINER(submenu), PopulateSubmenus, submenus); + } +} + +// Is the cursor over |menu| or one of its parent menus? +bool MotionIsOverMenu(GtkWidget* menu, GdkEventMotion* motion) { + if (motion->x >= 0 && motion->y >= 0 && + motion->x < menu->allocation.width && + motion->y < menu->allocation.height) { + return true; + } + + while (menu) { + GtkWidget* menu_item = gtk_menu_get_attach_widget(GTK_MENU(menu)); + if (!menu_item) + return false; + GtkWidget* parent = gtk_widget_get_parent(menu_item); + + if (gtk_util::WidgetContainsCursor(parent)) + return true; + menu = parent; + } + + return false; +} + +} // namespace + +MenuBarHelper::MenuBarHelper(Delegate* delegate) + : button_showing_menu_(NULL), + showing_menu_(NULL), + delegate_(delegate) { + DCHECK(delegate_); +} + +MenuBarHelper::~MenuBarHelper() { +} + +void MenuBarHelper::Add(GtkWidget* button) { + buttons_.push_back(button); +} + +void MenuBarHelper::Remove(GtkWidget* button) { + std::vector<GtkWidget*>::iterator iter = + find(buttons_.begin(), buttons_.end(), button); + if (iter == buttons_.end()) { + NOTREACHED(); + return; + } + buttons_.erase(iter); +} + +void MenuBarHelper::Clear() { + buttons_.clear(); +} + +void MenuBarHelper::MenuStartedShowing(GtkWidget* button, GtkWidget* menu) { + DCHECK(GTK_IS_MENU(menu)); + button_showing_menu_ = button; + showing_menu_ = menu; + g_signal_connect(menu, "motion-notify-event", + G_CALLBACK(OnMenuMotionNotifyThunk), this); + g_signal_connect(menu, "hide", + G_CALLBACK(OnMenuHiddenThunk), this); + g_signal_connect(menu, "move-current", + G_CALLBACK(OnMenuMoveCurrentThunk), this); + gtk_container_foreach(GTK_CONTAINER(menu), PopulateSubmenus, &submenus_); + for (size_t i = 0; i < submenus_.size(); ++i) { + g_signal_connect(submenus_[i], "motion-notify-event", + G_CALLBACK(OnMenuMotionNotifyThunk), this); + } +} + +gboolean MenuBarHelper::OnMenuMotionNotify(GtkWidget* menu, + GdkEventMotion* motion) { + // Don't do anything if pointer is in the menu. + if (MotionIsOverMenu(menu, motion)) + return FALSE; + if (buttons_.empty()) + return FALSE; + + gint x = 0; + gint y = 0; + GtkWidget* last_button = NULL; + + for (size_t i = 0; i < buttons_.size(); ++i) { + GtkWidget* button = buttons_[i]; + // Figure out coordinates relative to this button. Avoid using + // gtk_widget_get_pointer() unnecessarily. + if (i == 0) { + // We have to make this call because the menu is a popup window, so it + // doesn't share a toplevel with the buttons and we can't just use + // gtk_widget_translate_coordinates(). + gtk_widget_get_pointer(buttons_[0], &x, &y); + } else { + gint last_x = x; + gint last_y = y; + if (!gtk_widget_translate_coordinates( + last_button, button, last_x, last_y, &x, &y)) { + NOTREACHED(); + return FALSE; + } + } + + last_button = button; + + if (x >= 0 && y >= 0 && x < button->allocation.width && + y < button->allocation.height) { + if (button != button_showing_menu_) + delegate_->PopupForButton(button); + return TRUE; + } + } + + return FALSE; +} + +void MenuBarHelper::OnMenuHidden(GtkWidget* menu) { + DCHECK_EQ(showing_menu_, menu); + int matched = g_signal_handlers_disconnect_matched(showing_menu_, + G_SIGNAL_MATCH_DATA, 0, NULL, NULL, NULL, this); + DCHECK_EQ(3, matched); + + for (size_t i = 0; i < submenus_.size(); ++i) { + g_signal_handlers_disconnect_by_func(submenus_[i], + reinterpret_cast<gpointer>(OnMenuMotionNotifyThunk), this); + } + showing_menu_ = NULL; + button_showing_menu_ = NULL; + submenus_.clear(); +} + +void MenuBarHelper::OnMenuMoveCurrent(GtkWidget* menu, + GtkMenuDirectionType dir) { + // The menu directions are triggered by the arrow keys as follows + // + // PARENT left + // CHILD right + // NEXT down + // PREV up + // + // We only care about left and right. Note that for RTL, they are swapped. + switch (dir) { + case GTK_MENU_DIR_CHILD: { + GtkWidget* active_item = GTK_MENU_SHELL(menu)->active_menu_item; + // The move is going to open a submenu; don't override default behavior. + if (active_item && gtk_menu_item_get_submenu(GTK_MENU_ITEM(active_item))) + return; + // Fall through. + } + case GTK_MENU_DIR_PARENT: { + delegate_->PopupForButtonNextTo(button_showing_menu_, dir); + break; + } + default: + return; + } + + // This signal doesn't have a return value; we have to manually stop its + // propagation. + g_signal_stop_emission_by_name(menu, "move-current"); +} diff --git a/chrome/browser/gtk/menu_bar_helper.h b/chrome/browser/gtk/menu_bar_helper.h new file mode 100644 index 0000000..9b6448c --- /dev/null +++ b/chrome/browser/gtk/menu_bar_helper.h @@ -0,0 +1,82 @@ +// Copyright (c) 2009 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. +// +// This class replicates some menubar behaviors that are tricky to get right. +// It is used to create a more native feel for the bookmark bar and the +// page/app menus. + +#ifndef CHROME_BROWSER_GTK_MENU_BAR_HELPER_H_ +#define CHROME_BROWSER_GTK_MENU_BAR_HELPER_H_ + +#include <gtk/gtk.h> + +#include <vector> + +class MenuBarHelper { + public: + class Delegate { + public: + virtual ~Delegate() {} + + // Called when a the menu for a button ought to be triggered. + virtual void PopupForButton(GtkWidget* button) = 0; + virtual void PopupForButtonNextTo(GtkWidget* button, + GtkMenuDirectionType dir) = 0; + }; + + // |delegate| cannot be null. + explicit MenuBarHelper(Delegate* delegate); + ~MenuBarHelper(); + + // Must be called whenever a button's menu starts showing. It triggers the + // MenuBarHelper to start listening for certain events. + void MenuStartedShowing(GtkWidget* button, GtkWidget* menu); + + // Add |button| to the set of buttons we care about. + void Add(GtkWidget* button); + + // Remove |button| from the set of buttons we care about. + void Remove(GtkWidget* button); + + // Clear all buttons from the set. + void Clear(); + + private: + static gboolean OnMenuMotionNotifyThunk(GtkWidget* menu, + GdkEventMotion* motion, + MenuBarHelper* helper) { + return helper->OnMenuMotionNotify(menu, motion); + } + gboolean OnMenuMotionNotify(GtkWidget* menu, GdkEventMotion* motion); + + static void OnMenuHiddenThunk(GtkWidget* menu, MenuBarHelper* helper) { + helper->OnMenuHidden(menu); + } + void OnMenuHidden(GtkWidget* menu); + + static void OnMenuMoveCurrentThunk(GtkWidget* menu, + GtkMenuDirectionType dir, + MenuBarHelper* helper) { + helper->OnMenuMoveCurrent(menu, dir); + } + void OnMenuMoveCurrent(GtkWidget* menu, GtkMenuDirectionType dir); + + // The buttons for which we pop up menus. We do not own these, or even add + // refs to them. + std::vector<GtkWidget*> buttons_; + + // The button that is currently showing a menu, or NULL. + GtkWidget* button_showing_menu_; + + // The highest level menu that is currently showing, or NULL. + GtkWidget* showing_menu_; + + // All the submenus of |showing_menu_|. We connect to motion events on all + // of them. + std::vector<GtkWidget*> submenus_; + + Delegate* delegate_; +}; + +#endif // CHROME_BROWSER_GTK_MENU_BAR_HELPER_H_ |