1 // Copyright (c) 2011 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 // This is the GTK implementation of InfoBubbles. InfoBubbles are like 6 // dialogs, but they point to a given element on the screen. You should call 7 // InfoBubbleGtk::Show, which will create and display a bubble. The object is 8 // self deleting, when the bubble is closed, you will be notified via 9 // InfoBubbleGtkDelegate::InfoBubbleClosing(). Then the widgets and the 10 // underlying object will be destroyed. You can also close and destroy the 11 // bubble by calling Close(). 12 13 #ifndef CHROME_BROWSER_UI_GTK_INFO_BUBBLE_GTK_H_ 14 #define CHROME_BROWSER_UI_GTK_INFO_BUBBLE_GTK_H_ 15 #pragma once 16 17 #include <gtk/gtk.h> 18 #include <vector> 19 20 #include "base/basictypes.h" 21 #include "content/common/notification_observer.h" 22 #include "content/common/notification_registrar.h" 23 #include "ui/base/gtk/gtk_signal.h" 24 #include "ui/base/gtk/gtk_signal_registrar.h" 25 #include "ui/gfx/point.h" 26 #include "ui/gfx/rect.h" 27 28 class GtkThemeService; 29 class InfoBubbleGtk; 30 namespace gfx { 31 class Rect; 32 } 33 34 class InfoBubbleGtkDelegate { 35 public: 36 // Called when the InfoBubble is closing and is about to be deleted. 37 // |closed_by_escape| is true if the close is the result of pressing escape. 38 virtual void InfoBubbleClosing(InfoBubbleGtk* info_bubble, 39 bool closed_by_escape) = 0; 40 41 // NOTE: The Views interface has CloseOnEscape, except I can't find a place 42 // where it ever returns false, so we always allow you to close via escape. 43 44 protected: 45 virtual ~InfoBubbleGtkDelegate() {} 46 }; 47 48 class InfoBubbleGtk : public NotificationObserver { 49 public: 50 // Where should the arrow be placed relative to the bubble? 51 enum ArrowLocationGtk { 52 // TODO(derat): Support placing arrows on the bottoms of the bubbles. 53 ARROW_LOCATION_TOP_LEFT, 54 ARROW_LOCATION_TOP_RIGHT, 55 }; 56 57 // Show an InfoBubble, pointing at the area |rect| (in coordinates relative to 58 // |anchor_widget|'s origin). An info bubble will try to fit on the screen, 59 // so it can point to any edge of |rect|. If |rect| is NULL, the widget's 60 // entire area will be used. The bubble will host the |content| 61 // widget. Its arrow will be drawn at |arrow_location| if possible. The 62 // |delegate| will be notified when the bubble is closed. The bubble will 63 // perform an X grab of the pointer and keyboard, and will close itself if a 64 // click is received outside of the bubble. 65 static InfoBubbleGtk* Show(GtkWidget* anchor_widget, 66 const gfx::Rect* rect, 67 GtkWidget* content, 68 ArrowLocationGtk arrow_location, 69 bool match_system_theme, 70 bool grab_input, 71 GtkThemeService* provider, 72 InfoBubbleGtkDelegate* delegate); 73 74 // Close the bubble if it's open. This will delete the widgets and object, 75 // so you shouldn't hold a InfoBubbleGtk pointer after calling Close(). 76 void Close(); 77 78 // NotificationObserver implementation. 79 virtual void Observe(NotificationType type, 80 const NotificationSource& source, 81 const NotificationDetails& details); 82 83 // If the content contains widgets that can steal our pointer and keyboard 84 // grabs (e.g. GtkComboBox), this method should be called after a widget 85 // releases the grabs so we can reacquire them. Note that this causes a race 86 // condition; another client could grab them before we do (ideally, GDK would 87 // transfer the grabs back to us when the widget releases them). The window 88 // is small, though, and the worst-case scenario for this seems to just be 89 // that the content's widgets will appear inactive even after the user clicks 90 // in them. 91 void HandlePointerAndKeyboardUngrabbedByContent(); 92 93 private: 94 enum FrameType { 95 FRAME_MASK, 96 FRAME_STROKE, 97 }; 98 99 explicit InfoBubbleGtk(GtkThemeService* provider, bool match_system_theme); 100 virtual ~InfoBubbleGtk(); 101 102 // Creates the InfoBubble. 103 void Init(GtkWidget* anchor_widget, 104 const gfx::Rect* rect, 105 GtkWidget* content, 106 ArrowLocationGtk arrow_location, 107 bool grab_input); 108 109 // Make the points for our polygon frame, either for fill (the mask), or for 110 // when we stroke the border. 111 static std::vector<GdkPoint> MakeFramePolygonPoints( 112 ArrowLocationGtk arrow_location, 113 int width, 114 int height, 115 FrameType type); 116 117 // Get the location where the arrow should be placed (which is a function of 118 // the preferred location and of the direction that the bubble should be 119 // facing to fit onscreen). |arrow_x| is the X component in screen 120 // coordinates of the point at which the bubble's arrow should be aimed, and 121 // |width| is the bubble's width. 122 static ArrowLocationGtk GetArrowLocation( 123 ArrowLocationGtk preferred_location, int arrow_x, int width); 124 125 // Updates |arrow_location_| based on the toplevel window's current position 126 // and the bubble's size. If the |force_move_and_reshape| is true or the 127 // location changes, moves and reshapes the window and returns true. 128 bool UpdateArrowLocation(bool force_move_and_reshape); 129 130 // Reshapes the window and updates |mask_region_|. 131 void UpdateWindowShape(); 132 133 // Calculate the current screen position for the bubble's window (per 134 // |toplevel_window_|'s position as of its most-recent ConfigureNotify event 135 // and |rect_|) and move it there. 136 void MoveWindow(); 137 138 // Restack the bubble's window directly above |toplevel_window_|. 139 void StackWindow(); 140 141 // Sets the delegate. 142 void set_delegate(InfoBubbleGtkDelegate* delegate) { delegate_ = delegate; } 143 144 // Grab (in the X sense) the pointer and keyboard. This is needed to make 145 // sure that we have the input focus. 146 void GrabPointerAndKeyboard(); 147 148 CHROMEG_CALLBACK_3(InfoBubbleGtk, gboolean, OnGtkAccelerator, GtkAccelGroup*, 149 GObject*, guint, GdkModifierType); 150 151 CHROMEGTK_CALLBACK_1(InfoBubbleGtk, gboolean, OnExpose, GdkEventExpose*); 152 CHROMEGTK_CALLBACK_1(InfoBubbleGtk, void, OnSizeAllocate, GtkAllocation*); 153 CHROMEGTK_CALLBACK_1(InfoBubbleGtk, gboolean, OnButtonPress, GdkEventButton*); 154 CHROMEGTK_CALLBACK_0(InfoBubbleGtk, gboolean, OnDestroy); 155 CHROMEGTK_CALLBACK_0(InfoBubbleGtk, void, OnHide); 156 CHROMEGTK_CALLBACK_1(InfoBubbleGtk, gboolean, OnToplevelConfigure, 157 GdkEventConfigure*); 158 CHROMEGTK_CALLBACK_1(InfoBubbleGtk, gboolean, OnToplevelUnmap, GdkEvent*); 159 CHROMEGTK_CALLBACK_1(InfoBubbleGtk, void, OnAnchorAllocate, GtkAllocation*); 160 161 // The caller supplied delegate, can be NULL. 162 InfoBubbleGtkDelegate* delegate_; 163 164 // Our GtkWindow popup window, we don't technically "own" the widget, since 165 // it deletes us when it is destroyed. 166 GtkWidget* window_; 167 168 // Provides colors and stuff. 169 GtkThemeService* theme_service_; 170 171 // The accel group attached to |window_|, to handle closing with escape. 172 GtkAccelGroup* accel_group_; 173 174 // The window for which we're being shown (and to which |rect_| is relative). 175 // Note that it's possible for |toplevel_window_| to be NULL if the 176 // window is destroyed before this object is destroyed, so it's important 177 // to check for that case. 178 GtkWindow* toplevel_window_; 179 180 // The widget that we use to relatively position the popup window. 181 GtkWidget* anchor_widget_; 182 183 // Provides an offset from |anchor_widget_|'s origin for MoveWindow() to 184 // use. 185 gfx::Rect rect_; 186 187 // The current shape of |window_| (used to test whether clicks fall in it or 188 // not). 189 GdkRegion* mask_region_; 190 191 // Where would we prefer for the arrow be drawn relative to the bubble, and 192 // where is it currently drawn? 193 ArrowLocationGtk preferred_arrow_location_; 194 ArrowLocationGtk current_arrow_location_; 195 196 // Whether the background should match the system theme, when the system theme 197 // is being used. For example, the bookmark bubble does, but extension popups 198 // do not. 199 bool match_system_theme_; 200 201 // If true, the popup owns all X input for the duration of its existence. 202 // This will usually be true, the exception being when inspecting extension 203 // popups with dev tools. 204 bool grab_input_; 205 206 bool closed_by_escape_; 207 208 NotificationRegistrar registrar_; 209 210 ui::GtkSignalRegistrar signals_; 211 212 DISALLOW_COPY_AND_ASSIGN(InfoBubbleGtk); 213 }; 214 215 #endif // CHROME_BROWSER_UI_GTK_INFO_BUBBLE_GTK_H_ 216