Home | History | Annotate | Download | only in gtk
      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