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
28class GtkThemeService;
29class InfoBubbleGtk;
30namespace gfx {
31class Rect;
32}
33
34class 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
48class 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