diff options
author | derat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-30 00:48:16 +0000 |
---|---|---|
committer | derat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-30 00:48:16 +0000 |
commit | 4d023b86e67eea53d3a07dda4a516a1e74eeb69d (patch) | |
tree | aab34aa24c601d55c2d52df1704e887d853b12b2 /chrome/browser/gtk | |
parent | 2d4f7dd2730272ae457e5ef10449a747fdd96a84 (diff) | |
download | chromium_src-4d023b86e67eea53d3a07dda4a516a1e74eeb69d.zip chromium_src-4d023b86e67eea53d3a07dda4a516a1e74eeb69d.tar.gz chromium_src-4d023b86e67eea53d3a07dda4a516a1e74eeb69d.tar.bz2 |
Linux: Make InfoBubble use an override-redirect (popup) window.
This makes it work correctly in ion3 and other window managers that don't expect clients to try to move top-level windows themselves.
This implementation grabs the pointer and keyboard. By doing this and using an override-redirect window, we should be able to avoid worrying about interactions with different window managers. The only downside (alluded to in the previous code) is that window manager keybindings don't make it through until the user dismisses the bubble by clicking outside of it or hitting Enter or Escape. I don't think this will be a problem; it's no different from what happens when you open a context menu in an app.
BUG=20523
TEST=tested first-run and bookmark bubbles in Metacity, ion3, Fluxbox, KDE4, and the WM that I'm working on
Review URL: http://codereview.chromium.org/198016
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@27578 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/gtk')
-rw-r--r-- | chrome/browser/gtk/bookmark_bubble_gtk.cc | 19 | ||||
-rw-r--r-- | chrome/browser/gtk/bookmark_bubble_gtk.h | 6 | ||||
-rw-r--r-- | chrome/browser/gtk/browser_window_gtk.cc | 9 | ||||
-rw-r--r-- | chrome/browser/gtk/first_run_bubble.cc | 8 | ||||
-rw-r--r-- | chrome/browser/gtk/info_bubble_gtk.cc | 147 | ||||
-rw-r--r-- | chrome/browser/gtk/info_bubble_gtk.h | 30 |
6 files changed, 121 insertions, 98 deletions
diff --git a/chrome/browser/gtk/bookmark_bubble_gtk.cc b/chrome/browser/gtk/bookmark_bubble_gtk.cc index 8e93d77..ca4f40a 100644 --- a/chrome/browser/gtk/bookmark_bubble_gtk.cc +++ b/chrome/browser/gtk/bookmark_bubble_gtk.cc @@ -85,7 +85,7 @@ std::vector<const BookmarkNode*> PopulateFolderCombo(BookmarkModel* model, } // namespace // static -void BookmarkBubbleGtk::Show(GtkWindow* transient_toplevel, +void BookmarkBubbleGtk::Show(GtkWindow* toplevel_window, const gfx::Rect& rect, Profile* profile, const GURL& url, @@ -96,7 +96,7 @@ void BookmarkBubbleGtk::Show(GtkWindow* transient_toplevel, // 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(transient_toplevel, rect, profile, + g_bubble = new BookmarkBubbleGtk(toplevel_window, rect, profile, url, newly_bookmarked); } @@ -135,7 +135,7 @@ void BookmarkBubbleGtk::Observe(NotificationType type, } } -BookmarkBubbleGtk::BookmarkBubbleGtk(GtkWindow* transient_toplevel, +BookmarkBubbleGtk::BookmarkBubbleGtk(GtkWindow* toplevel_window, const gfx::Rect& rect, Profile* profile, const GURL& url, @@ -143,7 +143,7 @@ BookmarkBubbleGtk::BookmarkBubbleGtk(GtkWindow* transient_toplevel, : url_(url), profile_(profile), theme_provider_(GtkThemeProvider::GetFrom(profile_)), - transient_toplevel_(transient_toplevel), + toplevel_window_(toplevel_window), content_(NULL), name_entry_(NULL), folder_combo_(NULL), @@ -163,7 +163,7 @@ BookmarkBubbleGtk::BookmarkBubbleGtk(GtkWindow* transient_toplevel, GtkWidget* close_button = gtk_button_new_with_label( l10n_util::GetStringUTF8(IDS_CLOSE).c_str()); - // Our content is arrange in 3 rows. |top| contains a left justified + // 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. @@ -213,8 +213,11 @@ BookmarkBubbleGtk::BookmarkBubbleGtk(GtkWindow* transient_toplevel, // We want the focus to start on the entry, not on the remove button. gtk_container_set_focus_child(GTK_CONTAINER(content), table); - bubble_ = InfoBubbleGtk::Show(transient_toplevel_, - rect, content, theme_provider_, this); + bubble_ = InfoBubbleGtk::Show(toplevel_window_, + rect, + content, + theme_provider_, + this); // delegate if (!bubble_) { NOTREACHED(); return; @@ -347,7 +350,7 @@ void BookmarkBubbleGtk::ShowEditor() { // Closing might delete us, so we'll cache what we want we need on the stack. Profile* profile = profile_; - GtkWidget* toplevel = GTK_WIDGET(transient_toplevel_); + GtkWidget* toplevel = GTK_WIDGET(toplevel_window_); // Close the bubble, deleting the C++ objects, etc. bubble_->Close(); diff --git a/chrome/browser/gtk/bookmark_bubble_gtk.h b/chrome/browser/gtk/bookmark_bubble_gtk.h index a549f18..2011d63 100644 --- a/chrome/browser/gtk/bookmark_bubble_gtk.h +++ b/chrome/browser/gtk/bookmark_bubble_gtk.h @@ -34,7 +34,7 @@ class BookmarkBubbleGtk : public InfoBubbleGtkDelegate, public NotificationObserver { public: // Shows the bookmark bubble, pointing at |rect|. - static void Show(GtkWindow* transient_toplevel, + static void Show(GtkWindow* toplevel_window, const gfx::Rect& rect, Profile* profile, const GURL& url, @@ -52,7 +52,7 @@ class BookmarkBubbleGtk : public InfoBubbleGtkDelegate, const NotificationDetails& details); private: - BookmarkBubbleGtk(GtkWindow* transient_toplevel, + BookmarkBubbleGtk(GtkWindow* toplevel_window, const gfx::Rect& rect, Profile* profile, const GURL& url, @@ -120,7 +120,7 @@ class BookmarkBubbleGtk : public InfoBubbleGtkDelegate, GtkThemeProvider* theme_provider_; // The toplevel window our dialogs should be transient for. - GtkWindow* transient_toplevel_; + GtkWindow* toplevel_window_; // We let the InfoBubble own our content, and then we delete ourself // when the widget is destroyed (when the InfoBubble is destroyed). diff --git a/chrome/browser/gtk/browser_window_gtk.cc b/chrome/browser/gtk/browser_window_gtk.cc index b617829..f6e85f1 100644 --- a/chrome/browser/gtk/browser_window_gtk.cc +++ b/chrome/browser/gtk/browser_window_gtk.cc @@ -1230,14 +1230,7 @@ void BrowserWindowGtk::ActiveWindowChanged(GdkWindow* active_window) { if (!window_) return; - // If we lose focus to an info bubble, we don't want to seem inactive. - // However we can only control this when we are painting a custom - // frame. So if we lose focus BUT it's to one of our info bubbles AND we - // are painting a custom frame, then paint as if we are active. - const GtkWindow* info_bubble_toplevel = - InfoBubbleGtk::GetToplevelForInfoBubble(active_window); - bool is_active = (GTK_WIDGET(window_)->window == active_window || - (window_ == info_bubble_toplevel && UseCustomFrame())); + bool is_active = (GTK_WIDGET(window_)->window == active_window); bool changed = (is_active != is_active_); if (is_active && changed) { diff --git a/chrome/browser/gtk/first_run_bubble.cc b/chrome/browser/gtk/first_run_bubble.cc index 12127bd..1d90194 100644 --- a/chrome/browser/gtk/first_run_bubble.cc +++ b/chrome/browser/gtk/first_run_bubble.cc @@ -137,9 +137,13 @@ FirstRunBubble::FirstRunBubble(Profile* profile, gtk_box_pack_start(GTK_BOX(content_), bottom, FALSE, FALSE, 0); // We want the focus to start on the keep entry, not on the change button. - gtk_container_set_focus_child(GTK_CONTAINER(content_), keep_button); + gtk_widget_grab_focus(keep_button); - bubble_ = InfoBubbleGtk::Show(parent_, rect, content_, theme_provider_, this); + bubble_ = InfoBubbleGtk::Show(parent_, + rect, + content_, + theme_provider_, + this); // delegate if (!bubble_) { NOTREACHED(); return; diff --git a/chrome/browser/gtk/info_bubble_gtk.cc b/chrome/browser/gtk/info_bubble_gtk.cc index f9de638..47733e7 100644 --- a/chrome/browser/gtk/info_bubble_gtk.cc +++ b/chrome/browser/gtk/info_bubble_gtk.cc @@ -37,8 +37,6 @@ const int kRightMargin = kCornerSize + 6; const GdkColor kBackgroundColor = GDK_COLOR_RGB(0xff, 0xff, 0xff); const GdkColor kFrameColor = GDK_COLOR_RGB(0x63, 0x63, 0x63); -const gchar* kInfoBubbleToplevelKey = "__INFO_BUBBLE_TOPLEVEL__"; - enum FrameType { FRAME_MASK, FRAME_STROKE, @@ -123,13 +121,13 @@ gboolean HandleExpose(GtkWidget* widget, } // namespace // static -InfoBubbleGtk* InfoBubbleGtk::Show(GtkWindow* transient_toplevel, +InfoBubbleGtk* InfoBubbleGtk::Show(GtkWindow* toplevel_window, const gfx::Rect& rect, GtkWidget* content, GtkThemeProvider* provider, InfoBubbleGtkDelegate* delegate) { InfoBubbleGtk* bubble = new InfoBubbleGtk(provider); - bubble->Init(transient_toplevel, rect, content); + bubble->Init(toplevel_window, rect, content); bubble->set_delegate(delegate); return bubble; } @@ -140,29 +138,28 @@ InfoBubbleGtk::InfoBubbleGtk(GtkThemeProvider* provider) theme_provider_(provider), accel_group_(gtk_accel_group_new()), screen_x_(0), - screen_y_(0) { + screen_y_(0), + mask_region_(NULL) { } InfoBubbleGtk::~InfoBubbleGtk() { g_object_unref(accel_group_); + if (mask_region_) { + gdk_region_destroy(mask_region_); + mask_region_ = NULL; + } } -void InfoBubbleGtk::Init(GtkWindow* transient_toplevel, +void InfoBubbleGtk::Init(GtkWindow* toplevel_window, const gfx::Rect& rect, GtkWidget* content) { DCHECK(!window_); rect_ = rect; - window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_transient_for(GTK_WINDOW(window_), transient_toplevel); - gtk_window_set_skip_taskbar_hint(GTK_WINDOW(window_), TRUE); - gtk_window_set_decorated(GTK_WINDOW(window_), FALSE); - gtk_window_set_resizable(GTK_WINDOW(window_), FALSE); + window_ = gtk_window_new(GTK_WINDOW_POPUP); gtk_widget_set_app_paintable(window_, TRUE); // Have GTK double buffer around the expose signal. gtk_widget_set_double_buffered(window_, TRUE); - // Make sure that our window can be focused. - GTK_WIDGET_SET_FLAGS(window_, GTK_CAN_FOCUS); // Attach our accelerator group to the window with an escape accelerator. gtk_accel_group_connect(accel_group_, GDK_Escape, @@ -198,35 +195,74 @@ void InfoBubbleGtk::Init(GtkWindow* transient_toplevel, G_CALLBACK(HandleExpose), NULL); g_signal_connect(window_, "size-allocate", G_CALLBACK(HandleSizeAllocateThunk), this); - g_signal_connect(window_, "configure-event", - G_CALLBACK(&HandleConfigureThunk), this); g_signal_connect(window_, "button-press-event", G_CALLBACK(&HandleButtonPressThunk), this); g_signal_connect(window_, "destroy", G_CALLBACK(&HandleDestroyThunk), this); - // Set some data which helps the browser know whether it should appear - // active. - g_object_set_data(G_OBJECT(window_->window), kInfoBubbleToplevelKey, - transient_toplevel); - gtk_widget_show_all(window_); - // Make sure our window has focus, is brought to the top, etc. - gtk_window_present(GTK_WINDOW(window_)); - // We add a GTK (application level) grab. This means we will get all - // keyboard and mouse events for our application, even if they were delivered - // on another window. This allows us to close when the user clicks outside - // of the info bubble. We don't use an X grab since that would steal - // keystrokes from your window manager, prevent you from interacting with - // other applications, etc. + + // Stack our window directly above the toplevel window. Our window is a + // direct child of the root window, so we need to find a similar ancestor + // for the toplevel window (which might have been reparented by a window + // manager). + XID toplevel_window_base = x11_util::GetHighestAncestorWindow( + x11_util::GetX11WindowFromGtkWidget(GTK_WIDGET(toplevel_window)), + x11_util::GetX11RootWindow()); + if (toplevel_window_base) { + XID window_xid = x11_util::GetX11WindowFromGtkWidget(GTK_WIDGET(window_)); + XID window_parent = x11_util::GetParentWindow(window_xid); + if (window_parent == x11_util::GetX11RootWindow()) { + x11_util::RestackWindow(window_xid, toplevel_window_base, true); + } else { + // The window manager shouldn't reparent override-redirect windows. + DLOG(ERROR) << "override-redirect window " << window_xid + << "'s parent is " << window_parent + << ", rather than root window " + << x11_util::GetX11RootWindow(); + } + } + + // We add a GTK (application-level) grab. This means we will get all + // mouse events for our application, even if they were delivered on another + // window. We don't need this to get button presses outside of the bubble's + // window so we'll know to close it (the pointer grab takes care of that), but + // it prevents other widgets from getting highlighted when the pointer moves + // over them. // - // Before adding the grab, we need to ensure that the bubble is added - // to the window group of the top level window. This ensures that the - // grab only affects the current browser window, and not all the open - // browser windows in the application. - gtk_window_group_add_window(gtk_window_get_group(transient_toplevel), + // (Ideally we wouldn't add the window to a group and it would just get all + // the mouse events, but gtk_grab_add() doesn't appear to do anything in that + // case. Adding it to the toplevel window's group first appears to block + // enter/leave events for that window and its subwindows, although other + // browser windows still receive them). + gtk_window_group_add_window(gtk_window_get_group(toplevel_window), GTK_WINDOW(window_)); gtk_grab_add(window_); + + // Do X pointer and keyboard grabs to make sure that we have the focus and get + // all mouse and keyboard events until we're closed. + GdkGrabStatus pointer_grab_status = + gdk_pointer_grab(window_->window, + TRUE, // owner_events + GDK_BUTTON_PRESS_MASK, // event_mask + NULL, // confine_to + NULL, // cursor + GDK_CURRENT_TIME); + if (pointer_grab_status != GDK_GRAB_SUCCESS) { + // This will fail if someone else already has the pointer grabbed, but + // there's not really anything we can do about that. + DLOG(ERROR) << "Unable to grab pointer for info bubble (status=" + << pointer_grab_status << ")"; + } + GdkGrabStatus keyboard_grab_status = + gdk_keyboard_grab(window_->window, + FALSE, // owner_events + GDK_CURRENT_TIME); + if (keyboard_grab_status != GDK_GRAB_SUCCESS) { + DLOG(ERROR) << "Unable to grab keyboard for info bubble (status=" + << keyboard_grab_status << ")"; + } + registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, NotificationService::AllSources()); theme_provider_->InitThemesFor(this); @@ -254,22 +290,15 @@ void InfoBubbleGtk::Observe(NotificationType type, } } -// static -GtkWindow* InfoBubbleGtk::GetToplevelForInfoBubble( - const GdkWindow* bubble_window) { - if (!bubble_window) - return NULL; - - return reinterpret_cast<GtkWindow*>( - g_object_get_data(G_OBJECT(bubble_window), kInfoBubbleToplevelKey)); -} - void InfoBubbleGtk::Close(bool closed_by_escape) { // Notify the delegate that we're about to close. This gives the chance // to save state / etc from the hosted widget before it's destroyed. if (delegate_) delegate_->InfoBubbleClosing(this, closed_by_escape); + // We don't need to ungrab the pointer or keyboard here; the X server will + // automatically do that when we destroy our window. + DCHECK(window_); gtk_widget_destroy(window_); // |this| has been deleted, see HandleDestroy. @@ -289,29 +318,27 @@ void InfoBubbleGtk::HandleSizeAllocate() { } DCHECK(window_->allocation.x == 0 && window_->allocation.y == 0); + if (mask_region_) { + gdk_region_destroy(mask_region_); + mask_region_ = NULL; + } std::vector<GdkPoint> points = MakeFramePolygonPoints( window_->allocation.width, window_->allocation.height, FRAME_MASK); - GdkRegion* mask_region = gdk_region_polygon(&points[0], - points.size(), - GDK_EVEN_ODD_RULE); - gdk_window_shape_combine_region(window_->window, mask_region, 0, 0); - gdk_region_destroy(mask_region); -} - -gboolean InfoBubbleGtk::HandleConfigure(GdkEventConfigure* event) { - // If the window is moved someplace besides where we want it, move it back. - // TODO(deanm): In the end, I will probably remove this code and just let - // the user move around the bubble like a normal dialog. I want to try - // this for now and see if it causes problems when any window managers. - if (event->x != screen_x_ || event->y != screen_y_) - gtk_window_move(GTK_WINDOW(window_), screen_x_, screen_y_); - return FALSE; + mask_region_ = gdk_region_polygon(&points[0], + points.size(), + GDK_EVEN_ODD_RULE); + gdk_window_shape_combine_region(window_->window, mask_region_, 0, 0); } gboolean InfoBubbleGtk::HandleButtonPress(GdkEventButton* event) { - // If we got a click in our own window, that's ok. - if (event->window == window_->window) + // If we got a click in our own window, that's okay (we need to additionally + // check that it falls within our bounds, since we've grabbed the pointer and + // some events that actually occurred in other windows will be reported with + // respect to our window). + if (event->window == window_->window && + (mask_region_ && gdk_region_point_in(mask_region_, event->x, event->y))) { return FALSE; // Propagate. + } // Otherwise we had a click outside of our window, close ourself. Close(); diff --git a/chrome/browser/gtk/info_bubble_gtk.h b/chrome/browser/gtk/info_bubble_gtk.h index 137b395..b5021df 100644 --- a/chrome/browser/gtk/info_bubble_gtk.h +++ b/chrome/browser/gtk/info_bubble_gtk.h @@ -16,6 +16,7 @@ #include <gtk/gtk.h> #include "base/basictypes.h" +#include "base/gfx/point.h" #include "base/gfx/rect.h" #include "chrome/common/notification_registrar.h" @@ -39,10 +40,13 @@ class InfoBubbleGtkDelegate { class InfoBubbleGtk : public NotificationObserver { public: // Show an InfoBubble, pointing at the area |rect| (in screen coordinates). - // An infobubble will try to fit on the screen, so it can point to any edge - // of |rect|. The bubble will host the |content| widget. The |delegate| - // will be notified when things like closing are happening. - static InfoBubbleGtk* Show(GtkWindow* transient_toplevel, + // An info bubble will try to fit on the screen, so it can point to any edge + // of |rect|. The bubble will host the |content| widget. The |delegate| will + // be notified when the bubble is closed. The bubble will perform an X grab + // of the pointer and keyboard, and will close itself if a click is received + // outside of the bubble. + // TODO(derat): This implementation doesn't try to position itself onscreen. + static InfoBubbleGtk* Show(GtkWindow* toplevel_window, const gfx::Rect& rect, GtkWidget* content, GtkThemeProvider* provider, @@ -57,17 +61,12 @@ class InfoBubbleGtk : public NotificationObserver { const NotificationSource& source, const NotificationDetails& details); - // This returns the toplevel GtkWindow that is the transient parent of - // |bubble_window|, or NULL if |bubble_window| isn't the GdkWindow - // for an InfoBubbleGtk. - static GtkWindow* GetToplevelForInfoBubble(const GdkWindow* bubble_window); - private: explicit InfoBubbleGtk(GtkThemeProvider* provider); virtual ~InfoBubbleGtk(); // Creates the InfoBubble. - void Init(GtkWindow* transient_toplevel, + void Init(GtkWindow* toplevel_window, const gfx::Rect& rect, GtkWidget* content); @@ -97,13 +96,6 @@ class InfoBubbleGtk : public NotificationObserver { } void HandleSizeAllocate(); - static gboolean HandleConfigureThunk(GtkWidget* widget, - GdkEventConfigure* event, - gpointer user_data) { - return reinterpret_cast<InfoBubbleGtk*>(user_data)->HandleConfigure(event); - } - gboolean HandleConfigure(GdkEventConfigure* event); - static gboolean HandleButtonPressThunk(GtkWidget* widget, GdkEventButton* event, gpointer userdata) { @@ -147,6 +139,10 @@ class InfoBubbleGtk : public NotificationObserver { int screen_x_; int screen_y_; + // The current shape of |window_| (used to test whether clicks fall in it or + // not). + GdkRegion* mask_region_; + NotificationRegistrar registrar_; DISALLOW_COPY_AND_ASSIGN(InfoBubbleGtk); |