Home | History | Annotate | Download | only in bubble
      1 // Copyright (c) 2012 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 #include "chrome/browser/ui/gtk/bubble/bubble_gtk.h"
      6 
      7 #include <gdk/gdkkeysyms.h>
      8 
      9 #include "base/i18n/rtl.h"
     10 #include "chrome/browser/chrome_notification_types.h"
     11 #include "chrome/browser/ui/gtk/bubble/bubble_accelerators_gtk.h"
     12 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
     13 #include "chrome/browser/ui/gtk/gtk_util.h"
     14 #include "content/public/browser/notification_source.h"
     15 #include "ui/base/gtk/gtk_compat.h"
     16 #include "ui/base/gtk/gtk_hig_constants.h"
     17 #include "ui/base/gtk/gtk_windowing.h"
     18 #include "ui/gfx/gtk_util.h"
     19 #include "ui/gfx/path.h"
     20 #include "ui/gfx/rect.h"
     21 
     22 namespace {
     23 
     24 // The height of the arrow, and the width will be about twice the height.
     25 const int kArrowSize = 8;
     26 
     27 // Number of pixels to the middle of the arrow from the close edge of the
     28 // window.
     29 const int kArrowX = 18;
     30 
     31 // Number of pixels between the tip of the arrow and the region we're
     32 // pointing to.
     33 const int kArrowToContentPadding = -4;
     34 
     35 // We draw flat diagonal corners, each corner is an NxN square.
     36 const int kCornerSize = 3;
     37 
     38 // The amount of padding (in pixels) from the top of |toplevel_window_| to the
     39 // top of |window_| when fixed positioning is used.
     40 const int kFixedPositionPaddingEnd = 10;
     41 const int kFixedPositionPaddingTop = 5;
     42 
     43 const GdkColor kBackgroundColor = GDK_COLOR_RGB(0xff, 0xff, 0xff);
     44 const GdkColor kFrameColor = GDK_COLOR_RGB(0x63, 0x63, 0x63);
     45 
     46 // Helper functions that encapsulate arrow locations.
     47 bool HasArrow(BubbleGtk::FrameStyle frame_style) {
     48   return frame_style != BubbleGtk::FLOAT_BELOW_RECT &&
     49          frame_style != BubbleGtk::CENTER_OVER_RECT &&
     50          frame_style != BubbleGtk::FIXED_TOP_LEFT &&
     51          frame_style != BubbleGtk::FIXED_TOP_RIGHT;
     52 }
     53 
     54 bool IsArrowLeft(BubbleGtk::FrameStyle frame_style) {
     55   return frame_style == BubbleGtk::ANCHOR_TOP_LEFT ||
     56          frame_style == BubbleGtk::ANCHOR_BOTTOM_LEFT;
     57 }
     58 
     59 bool IsArrowMiddle(BubbleGtk::FrameStyle frame_style) {
     60   return frame_style == BubbleGtk::ANCHOR_TOP_MIDDLE ||
     61          frame_style == BubbleGtk::ANCHOR_BOTTOM_MIDDLE;
     62 }
     63 
     64 bool IsArrowRight(BubbleGtk::FrameStyle frame_style) {
     65   return frame_style == BubbleGtk::ANCHOR_TOP_RIGHT ||
     66          frame_style == BubbleGtk::ANCHOR_BOTTOM_RIGHT;
     67 }
     68 
     69 bool IsArrowTop(BubbleGtk::FrameStyle frame_style) {
     70   return frame_style == BubbleGtk::ANCHOR_TOP_LEFT ||
     71          frame_style == BubbleGtk::ANCHOR_TOP_MIDDLE ||
     72          frame_style == BubbleGtk::ANCHOR_TOP_RIGHT;
     73 }
     74 
     75 bool IsArrowBottom(BubbleGtk::FrameStyle frame_style) {
     76   return frame_style == BubbleGtk::ANCHOR_BOTTOM_LEFT ||
     77          frame_style == BubbleGtk::ANCHOR_BOTTOM_MIDDLE ||
     78          frame_style == BubbleGtk::ANCHOR_BOTTOM_RIGHT;
     79 }
     80 
     81 bool IsFixed(BubbleGtk::FrameStyle frame_style) {
     82   return frame_style == BubbleGtk::FIXED_TOP_LEFT ||
     83          frame_style == BubbleGtk::FIXED_TOP_RIGHT;
     84 }
     85 
     86 BubbleGtk::FrameStyle AdjustFrameStyleForLocale(
     87     BubbleGtk::FrameStyle frame_style) {
     88   // Only RTL requires more work.
     89   if (!base::i18n::IsRTL())
     90     return frame_style;
     91 
     92   switch (frame_style) {
     93     // These don't flip.
     94     case BubbleGtk::ANCHOR_TOP_MIDDLE:
     95     case BubbleGtk::ANCHOR_BOTTOM_MIDDLE:
     96     case BubbleGtk::FLOAT_BELOW_RECT:
     97     case BubbleGtk::CENTER_OVER_RECT:
     98       return frame_style;
     99 
    100     // These do flip.
    101     case BubbleGtk::ANCHOR_TOP_LEFT:
    102       return BubbleGtk::ANCHOR_TOP_RIGHT;
    103 
    104     case BubbleGtk::ANCHOR_TOP_RIGHT:
    105       return BubbleGtk::ANCHOR_TOP_LEFT;
    106 
    107     case BubbleGtk::ANCHOR_BOTTOM_LEFT:
    108       return BubbleGtk::ANCHOR_BOTTOM_RIGHT;
    109 
    110     case BubbleGtk::ANCHOR_BOTTOM_RIGHT:
    111       return BubbleGtk::ANCHOR_BOTTOM_LEFT;
    112 
    113     case BubbleGtk::FIXED_TOP_LEFT:
    114       return BubbleGtk::FIXED_TOP_RIGHT;
    115 
    116     case BubbleGtk::FIXED_TOP_RIGHT:
    117       return BubbleGtk::FIXED_TOP_LEFT;
    118   }
    119 
    120   NOTREACHED();
    121   return BubbleGtk::ANCHOR_TOP_LEFT;
    122 }
    123 
    124 }  // namespace
    125 
    126 // static
    127 BubbleGtk* BubbleGtk::Show(GtkWidget* anchor_widget,
    128                            const gfx::Rect* rect,
    129                            GtkWidget* content,
    130                            FrameStyle frame_style,
    131                            int attribute_flags,
    132                            GtkThemeService* provider,
    133                            BubbleDelegateGtk* delegate) {
    134   BubbleGtk* bubble = new BubbleGtk(provider,
    135                                     AdjustFrameStyleForLocale(frame_style),
    136                                     attribute_flags);
    137   bubble->Init(anchor_widget, rect, content, attribute_flags);
    138   bubble->set_delegate(delegate);
    139   return bubble;
    140 }
    141 
    142 BubbleGtk::BubbleGtk(GtkThemeService* provider,
    143                      FrameStyle frame_style,
    144                      int attribute_flags)
    145     : delegate_(NULL),
    146       window_(NULL),
    147       theme_service_(provider),
    148       accel_group_(gtk_accel_group_new()),
    149       toplevel_window_(NULL),
    150       anchor_widget_(NULL),
    151       mask_region_(NULL),
    152       requested_frame_style_(frame_style),
    153       actual_frame_style_(ANCHOR_TOP_LEFT),
    154       match_system_theme_(attribute_flags & MATCH_SYSTEM_THEME),
    155       grab_input_(attribute_flags & GRAB_INPUT),
    156       closed_by_escape_(false) {
    157 }
    158 
    159 BubbleGtk::~BubbleGtk() {
    160   // Notify the delegate that we're about to close.  This gives the chance
    161   // to save state / etc from the hosted widget before it's destroyed.
    162   if (delegate_)
    163     delegate_->BubbleClosing(this, closed_by_escape_);
    164 
    165   g_object_unref(accel_group_);
    166   if (mask_region_)
    167     gdk_region_destroy(mask_region_);
    168 }
    169 
    170 void BubbleGtk::Init(GtkWidget* anchor_widget,
    171                      const gfx::Rect* rect,
    172                      GtkWidget* content,
    173                      int attribute_flags) {
    174   // If there is a current grab widget (menu, other bubble, etc.), hide it.
    175   GtkWidget* current_grab_widget = gtk_grab_get_current();
    176   if (current_grab_widget)
    177     gtk_widget_hide(current_grab_widget);
    178 
    179   DCHECK(!window_);
    180   anchor_widget_ = anchor_widget;
    181   toplevel_window_ = gtk_widget_get_toplevel(anchor_widget_);
    182   DCHECK(gtk_widget_is_toplevel(toplevel_window_));
    183   rect_ = rect ? *rect : gtk_util::WidgetBounds(anchor_widget);
    184 
    185   // Using a TOPLEVEL window may cause placement issues with certain WMs but it
    186   // is necessary to be able to focus the window.
    187   window_ = gtk_window_new(attribute_flags & POPUP_WINDOW ?
    188                            GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL);
    189 
    190   gtk_widget_set_app_paintable(window_, TRUE);
    191   // Resizing is handled by the program, not user.
    192   gtk_window_set_resizable(GTK_WINDOW(window_), FALSE);
    193 
    194   if (!(attribute_flags & NO_ACCELERATORS)) {
    195     // Attach all of the accelerators to the bubble.
    196     for (BubbleAcceleratorsGtk::const_iterator
    197              i(BubbleAcceleratorsGtk::begin());
    198          i != BubbleAcceleratorsGtk::end();
    199          ++i) {
    200       gtk_accel_group_connect(accel_group_,
    201                               i->keyval,
    202                               i->modifier_type,
    203                               GtkAccelFlags(0),
    204                               g_cclosure_new(G_CALLBACK(&OnGtkAcceleratorThunk),
    205                                              this,
    206                                              NULL));
    207     }
    208     gtk_window_add_accel_group(GTK_WINDOW(window_), accel_group_);
    209   }
    210 
    211   // |requested_frame_style_| is used instead of |actual_frame_style_| here
    212   // because |actual_frame_style_| is only correct after calling
    213   // |UpdateFrameStyle()|. Unfortunately, |UpdateFrameStyle()| requires knowing
    214   // the size of |window_| (which happens later on).
    215   int arrow_padding = HasArrow(requested_frame_style_) ? kArrowSize : 0;
    216   GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
    217   gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), arrow_padding, 0, 0, 0);
    218 
    219   gtk_container_add(GTK_CONTAINER(alignment), content);
    220   gtk_container_add(GTK_CONTAINER(window_), alignment);
    221 
    222   // GtkWidget only exposes the bitmap mask interface.  Use GDK to more
    223   // efficently mask a GdkRegion.  Make sure the window is realized during
    224   // OnSizeAllocate, so the mask can be applied to the GdkWindow.
    225   gtk_widget_realize(window_);
    226 
    227   UpdateFrameStyle(true);  // Force move and reshape.
    228   StackWindow();
    229 
    230   gtk_widget_add_events(window_, GDK_BUTTON_PRESS_MASK);
    231 
    232   // Connect during the bubbling phase so the border is always on top.
    233   signals_.ConnectAfter(window_, "expose-event",
    234                         G_CALLBACK(OnExposeThunk), this);
    235   signals_.Connect(window_, "size-allocate", G_CALLBACK(OnSizeAllocateThunk),
    236                    this);
    237   signals_.Connect(window_, "button-press-event",
    238                    G_CALLBACK(OnButtonPressThunk), this);
    239   signals_.Connect(window_, "destroy", G_CALLBACK(OnDestroyThunk), this);
    240   signals_.Connect(window_, "hide", G_CALLBACK(OnHideThunk), this);
    241   if (grab_input_) {
    242     signals_.Connect(window_, "grab-broken-event",
    243                      G_CALLBACK(OnGrabBrokenThunk), this);
    244   }
    245 
    246   // If the toplevel window is being used as the anchor, then the signals below
    247   // are enough to keep us positioned correctly.
    248   if (anchor_widget_ != toplevel_window_) {
    249     signals_.Connect(anchor_widget_, "size-allocate",
    250                      G_CALLBACK(OnAnchorAllocateThunk), this);
    251     signals_.Connect(anchor_widget_, "destroy",
    252                      G_CALLBACK(OnAnchorDestroyThunk), this);
    253   }
    254 
    255   signals_.Connect(toplevel_window_, "configure-event",
    256                    G_CALLBACK(OnToplevelConfigureThunk), this);
    257   signals_.Connect(toplevel_window_, "unmap-event",
    258                    G_CALLBACK(OnToplevelUnmapThunk), this);
    259   signals_.Connect(window_, "destroy",
    260                    G_CALLBACK(OnToplevelDestroyThunk), this);
    261 
    262   gtk_widget_show_all(window_);
    263 
    264   if (grab_input_)
    265     gtk_grab_add(window_);
    266   GrabPointerAndKeyboard();
    267 
    268   registrar_.Add(this, chrome::NOTIFICATION_BROWSER_THEME_CHANGED,
    269                  content::Source<ThemeService>(theme_service_));
    270   theme_service_->InitThemesFor(this);
    271 }
    272 
    273 // NOTE: This seems a bit overcomplicated, but it requires a bunch of careful
    274 // fudging to get the pixels rasterized exactly where we want them, the arrow to
    275 // have a 1 pixel point, etc.
    276 // TODO(deanm): Windows draws with Skia and uses some PNG images for the
    277 // corners.  This is a lot more work, but they get anti-aliasing.
    278 // static
    279 std::vector<GdkPoint> BubbleGtk::MakeFramePolygonPoints(
    280     FrameStyle frame_style,
    281     int width,
    282     int height,
    283     FrameType type) {
    284   using gtk_util::MakeBidiGdkPoint;
    285   std::vector<GdkPoint> points;
    286 
    287   int top_arrow_size = IsArrowTop(frame_style) ? kArrowSize : 0;
    288   int bottom_arrow_size = IsArrowBottom(frame_style) ? kArrowSize : 0;
    289   bool on_left = IsArrowLeft(frame_style);
    290 
    291   // If we're stroking the frame, we need to offset some of our points by 1
    292   // pixel.  We do this when we draw horizontal lines that are on the bottom or
    293   // when we draw vertical lines that are closer to the end (where "end" is the
    294   // right side for ANCHOR_TOP_LEFT).
    295   int y_off = type == FRAME_MASK ? 0 : -1;
    296   // We use this one for arrows located on the left.
    297   int x_off_l = on_left ? y_off : 0;
    298   // We use this one for RTL.
    299   int x_off_r = !on_left ? -y_off : 0;
    300 
    301   // Top left corner.
    302   points.push_back(MakeBidiGdkPoint(
    303       x_off_r, top_arrow_size + kCornerSize - 1, width, on_left));
    304   points.push_back(MakeBidiGdkPoint(
    305       kCornerSize + x_off_r - 1, top_arrow_size, width, on_left));
    306 
    307   // The top arrow.
    308   if (top_arrow_size) {
    309     int arrow_x = frame_style == ANCHOR_TOP_MIDDLE ? width / 2 : kArrowX;
    310     points.push_back(MakeBidiGdkPoint(
    311         arrow_x - top_arrow_size + x_off_r, top_arrow_size, width, on_left));
    312     points.push_back(MakeBidiGdkPoint(
    313         arrow_x + x_off_r, 0, width, on_left));
    314     points.push_back(MakeBidiGdkPoint(
    315         arrow_x + 1 + x_off_l, 0, width, on_left));
    316     points.push_back(MakeBidiGdkPoint(
    317         arrow_x + top_arrow_size + 1 + x_off_l, top_arrow_size,
    318         width, on_left));
    319   }
    320 
    321   // Top right corner.
    322   points.push_back(MakeBidiGdkPoint(
    323       width - kCornerSize + 1 + x_off_l, top_arrow_size, width, on_left));
    324   points.push_back(MakeBidiGdkPoint(
    325       width + x_off_l, top_arrow_size + kCornerSize - 1, width, on_left));
    326 
    327   // Bottom right corner.
    328   points.push_back(MakeBidiGdkPoint(
    329       width + x_off_l, height - bottom_arrow_size - kCornerSize,
    330       width, on_left));
    331   points.push_back(MakeBidiGdkPoint(
    332       width - kCornerSize + x_off_r, height - bottom_arrow_size + y_off,
    333       width, on_left));
    334 
    335   // The bottom arrow.
    336   if (bottom_arrow_size) {
    337     int arrow_x = frame_style == ANCHOR_BOTTOM_MIDDLE ?
    338         width / 2 : kArrowX;
    339     points.push_back(MakeBidiGdkPoint(
    340         arrow_x + bottom_arrow_size + 1 + x_off_l,
    341         height - bottom_arrow_size + y_off,
    342         width,
    343         on_left));
    344     points.push_back(MakeBidiGdkPoint(
    345         arrow_x + 1 + x_off_l, height + y_off, width, on_left));
    346     points.push_back(MakeBidiGdkPoint(
    347         arrow_x + x_off_r, height + y_off, width, on_left));
    348     points.push_back(MakeBidiGdkPoint(
    349         arrow_x - bottom_arrow_size + x_off_r,
    350         height - bottom_arrow_size + y_off,
    351         width,
    352         on_left));
    353   }
    354 
    355   // Bottom left corner.
    356   points.push_back(MakeBidiGdkPoint(
    357       kCornerSize + x_off_l, height -bottom_arrow_size + y_off,
    358       width, on_left));
    359   points.push_back(MakeBidiGdkPoint(
    360       x_off_r, height - bottom_arrow_size - kCornerSize, width, on_left));
    361 
    362   return points;
    363 }
    364 
    365 BubbleGtk::FrameStyle BubbleGtk::GetAllowedFrameStyle(
    366     FrameStyle preferred_style,
    367     int arrow_x,
    368     int arrow_y,
    369     int width,
    370     int height) {
    371   if (IsFixed(preferred_style))
    372     return preferred_style;
    373 
    374   const int screen_width = gdk_screen_get_width(gdk_screen_get_default());
    375   const int screen_height = gdk_screen_get_height(gdk_screen_get_default());
    376 
    377   // Choose whether to show the bubble above or below the specified location.
    378   const bool prefer_top_arrow = IsArrowTop(preferred_style) ||
    379              preferred_style == FLOAT_BELOW_RECT;
    380   // The bleed measures the amount of bubble that would be shown offscreen.
    381   const int top_arrow_bleed =
    382       std::max(height + kArrowSize + arrow_y - screen_height, 0);
    383   const int bottom_arrow_bleed = std::max(height + kArrowSize - arrow_y, 0);
    384 
    385   FrameStyle frame_style_none = FLOAT_BELOW_RECT;
    386   FrameStyle frame_style_left = ANCHOR_TOP_LEFT;
    387   FrameStyle frame_style_middle = ANCHOR_TOP_MIDDLE;
    388   FrameStyle frame_style_right = ANCHOR_TOP_RIGHT;
    389   if ((prefer_top_arrow && (top_arrow_bleed > bottom_arrow_bleed)) ||
    390       (!prefer_top_arrow && (top_arrow_bleed >= bottom_arrow_bleed))) {
    391     frame_style_none = CENTER_OVER_RECT;
    392     frame_style_left = ANCHOR_BOTTOM_LEFT;
    393     frame_style_middle = ANCHOR_BOTTOM_MIDDLE;
    394     frame_style_right = ANCHOR_BOTTOM_RIGHT;
    395   }
    396 
    397   if (!HasArrow(preferred_style))
    398     return frame_style_none;
    399 
    400   if (IsArrowMiddle(preferred_style))
    401     return frame_style_middle;
    402 
    403   // Choose whether to show the bubble left or right of the specified location.
    404   const bool prefer_left_arrow = IsArrowLeft(preferred_style);
    405   // The bleed measures the amount of bubble that would be shown offscreen.
    406   const int left_arrow_bleed =
    407       std::max(width + arrow_x - kArrowX - screen_width, 0);
    408   const int right_arrow_bleed = std::max(width - arrow_x - kArrowX, 0);
    409 
    410   // Use the preferred location if it doesn't bleed more than the opposite side.
    411   return ((prefer_left_arrow && (left_arrow_bleed <= right_arrow_bleed)) ||
    412           (!prefer_left_arrow && (left_arrow_bleed < right_arrow_bleed))) ?
    413       frame_style_left : frame_style_right;
    414 }
    415 
    416 bool BubbleGtk::UpdateFrameStyle(bool force_move_and_reshape) {
    417   if (!toplevel_window_ || !anchor_widget_)
    418     return false;
    419 
    420   gint toplevel_x = 0, toplevel_y = 0;
    421   gdk_window_get_position(gtk_widget_get_window(toplevel_window_),
    422                           &toplevel_x, &toplevel_y);
    423   int offset_x, offset_y;
    424   gtk_widget_translate_coordinates(anchor_widget_, toplevel_window_,
    425                                    rect_.x(), rect_.y(), &offset_x, &offset_y);
    426 
    427   FrameStyle old_frame_style = actual_frame_style_;
    428   GtkAllocation allocation;
    429   gtk_widget_get_allocation(window_, &allocation);
    430   actual_frame_style_ = GetAllowedFrameStyle(
    431       requested_frame_style_,
    432       toplevel_x + offset_x + (rect_.width() / 2),  // arrow_x
    433       toplevel_y + offset_y,
    434       allocation.width,
    435       allocation.height);
    436 
    437   if (force_move_and_reshape || actual_frame_style_ != old_frame_style) {
    438     UpdateWindowShape();
    439     MoveWindow();
    440     // We need to redraw the entire window to repaint its border.
    441     gtk_widget_queue_draw(window_);
    442     return true;
    443   }
    444   return false;
    445 }
    446 
    447 void BubbleGtk::UpdateWindowShape() {
    448   if (mask_region_) {
    449     gdk_region_destroy(mask_region_);
    450     mask_region_ = NULL;
    451   }
    452   GtkAllocation allocation;
    453   gtk_widget_get_allocation(window_, &allocation);
    454   std::vector<GdkPoint> points = MakeFramePolygonPoints(
    455       actual_frame_style_, allocation.width, allocation.height,
    456       FRAME_MASK);
    457   mask_region_ = gdk_region_polygon(&points[0],
    458                                     points.size(),
    459                                     GDK_EVEN_ODD_RULE);
    460 
    461   GdkWindow* gdk_window = gtk_widget_get_window(window_);
    462   gdk_window_shape_combine_region(gdk_window, NULL, 0, 0);
    463   gdk_window_shape_combine_region(gdk_window, mask_region_, 0, 0);
    464 }
    465 
    466 void BubbleGtk::MoveWindow() {
    467   if (!toplevel_window_ || !anchor_widget_)
    468     return;
    469 
    470   gint toplevel_x = 0, toplevel_y = 0;
    471   gdk_window_get_position(gtk_widget_get_window(toplevel_window_),
    472                           &toplevel_x, &toplevel_y);
    473 
    474   int offset_x, offset_y;
    475   gtk_widget_translate_coordinates(anchor_widget_, toplevel_window_,
    476                                    rect_.x(), rect_.y(), &offset_x, &offset_y);
    477 
    478   gint screen_x = 0;
    479   if (IsFixed(actual_frame_style_)) {
    480     GtkAllocation toplevel_allocation;
    481     gtk_widget_get_allocation(toplevel_window_, &toplevel_allocation);
    482 
    483     GtkAllocation bubble_allocation;
    484     gtk_widget_get_allocation(window_, &bubble_allocation);
    485 
    486     int x_offset = actual_frame_style_ == FIXED_TOP_LEFT ?
    487         kFixedPositionPaddingEnd :
    488         toplevel_allocation.width - bubble_allocation.width -
    489             kFixedPositionPaddingEnd;
    490     screen_x = toplevel_x + x_offset;
    491   } else if (!HasArrow(actual_frame_style_) ||
    492              IsArrowMiddle(actual_frame_style_)) {
    493     GtkAllocation allocation;
    494     gtk_widget_get_allocation(window_, &allocation);
    495     screen_x =
    496         toplevel_x + offset_x + (rect_.width() / 2) - allocation.width / 2;
    497   } else if (IsArrowLeft(actual_frame_style_)) {
    498     screen_x = toplevel_x + offset_x + (rect_.width() / 2) - kArrowX;
    499   } else if (IsArrowRight(actual_frame_style_)) {
    500     GtkAllocation allocation;
    501     gtk_widget_get_allocation(window_, &allocation);
    502     screen_x = toplevel_x + offset_x + (rect_.width() / 2) -
    503                allocation.width + kArrowX;
    504   } else {
    505     NOTREACHED();
    506   }
    507 
    508   gint screen_y = toplevel_y + offset_y + rect_.height();
    509   if (IsFixed(actual_frame_style_)) {
    510     screen_y = toplevel_y + kFixedPositionPaddingTop;
    511   } else if (IsArrowTop(actual_frame_style_) ||
    512              actual_frame_style_ == FLOAT_BELOW_RECT) {
    513     screen_y += kArrowToContentPadding;
    514   } else {
    515     GtkAllocation allocation;
    516     gtk_widget_get_allocation(window_, &allocation);
    517     screen_y -= allocation.height + kArrowToContentPadding;
    518   }
    519 
    520   gtk_window_move(GTK_WINDOW(window_), screen_x, screen_y);
    521 }
    522 
    523 void BubbleGtk::StackWindow() {
    524   // Stack our window directly above the toplevel window.
    525   if (toplevel_window_)
    526     ui::StackPopupWindow(window_, toplevel_window_);
    527 }
    528 
    529 void BubbleGtk::Observe(int type,
    530                         const content::NotificationSource& source,
    531                         const content::NotificationDetails& details) {
    532   DCHECK_EQ(type, chrome::NOTIFICATION_BROWSER_THEME_CHANGED);
    533   if (theme_service_->UsingNativeTheme() && match_system_theme_) {
    534     gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, NULL);
    535   } else {
    536     // Set the background color, so we don't need to paint it manually.
    537     gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, &kBackgroundColor);
    538   }
    539 }
    540 
    541 void BubbleGtk::StopGrabbingInput() {
    542   UngrabPointerAndKeyboard();
    543   if (!grab_input_)
    544     return;
    545   grab_input_ = false;
    546   gtk_grab_remove(window_);
    547 }
    548 
    549 GtkWindow* BubbleGtk::GetNativeWindow() {
    550   return GTK_WINDOW(window_);
    551 }
    552 
    553 void BubbleGtk::Close() {
    554   // We don't need to ungrab the pointer or keyboard here; the X server will
    555   // automatically do that when we destroy our window.
    556   DCHECK(window_);
    557   gtk_widget_destroy(window_);
    558   // |this| has been deleted, see OnDestroy.
    559 }
    560 
    561 void BubbleGtk::SetPositionRelativeToAnchor(const gfx::Rect* rect) {
    562   rect_ = rect ? *rect : gtk_util::WidgetBounds(anchor_widget_);
    563   if (!UpdateFrameStyle(false))
    564     MoveWindow();
    565 }
    566 
    567 void BubbleGtk::GrabPointerAndKeyboard() {
    568   GdkWindow* gdk_window = gtk_widget_get_window(window_);
    569 
    570   // Install X pointer and keyboard grabs to make sure that we have the focus
    571   // and get all mouse and keyboard events until we're closed. As a hack, grab
    572   // the pointer even if |grab_input_| is false to prevent a weird error
    573   // rendering the bubble's frame. See
    574   // https://code.google.com/p/chromium/issues/detail?id=130820.
    575   GdkGrabStatus pointer_grab_status =
    576       gdk_pointer_grab(gdk_window,
    577                        TRUE,                   // owner_events
    578                        GDK_BUTTON_PRESS_MASK,  // event_mask
    579                        NULL,                   // confine_to
    580                        NULL,                   // cursor
    581                        GDK_CURRENT_TIME);
    582   if (pointer_grab_status != GDK_GRAB_SUCCESS) {
    583     // This will fail if someone else already has the pointer grabbed, but
    584     // there's not really anything we can do about that.
    585     DLOG(ERROR) << "Unable to grab pointer (status="
    586                 << pointer_grab_status << ")";
    587   }
    588 
    589   // Only grab the keyboard input if |grab_input_| is true.
    590   if (grab_input_) {
    591     GdkGrabStatus keyboard_grab_status =
    592         gdk_keyboard_grab(gdk_window,
    593                           FALSE,  // owner_events
    594                           GDK_CURRENT_TIME);
    595     if (keyboard_grab_status != GDK_GRAB_SUCCESS) {
    596       DLOG(ERROR) << "Unable to grab keyboard (status="
    597                   << keyboard_grab_status << ")";
    598     }
    599   }
    600 }
    601 
    602 void BubbleGtk::UngrabPointerAndKeyboard() {
    603   gdk_pointer_ungrab(GDK_CURRENT_TIME);
    604   if (grab_input_)
    605     gdk_keyboard_ungrab(GDK_CURRENT_TIME);
    606 }
    607 
    608 gboolean BubbleGtk::OnGtkAccelerator(GtkAccelGroup* group,
    609                                      GObject* acceleratable,
    610                                      guint keyval,
    611                                      GdkModifierType modifier) {
    612   GdkEventKey msg;
    613   GdkKeymapKey* keys;
    614   gint n_keys;
    615 
    616   switch (keyval) {
    617     case GDK_Escape:
    618       // Close on Esc and trap the accelerator
    619       closed_by_escape_ = true;
    620       Close();
    621       return TRUE;
    622     case GDK_w:
    623       // Close on C-w and forward the accelerator
    624       if (modifier & GDK_CONTROL_MASK) {
    625         gdk_keymap_get_entries_for_keyval(NULL,
    626                                           keyval,
    627                                           &keys,
    628                                           &n_keys);
    629         if (n_keys) {
    630           // Forward the accelerator to root window the bubble is anchored
    631           // to for further processing
    632           msg.type = GDK_KEY_PRESS;
    633           msg.window = gtk_widget_get_window(toplevel_window_);
    634           msg.send_event = TRUE;
    635           msg.time = GDK_CURRENT_TIME;
    636           msg.state = modifier | GDK_MOD2_MASK;
    637           msg.keyval = keyval;
    638           // length and string are deprecated and thus zeroed out
    639           msg.length = 0;
    640           msg.string = NULL;
    641           msg.hardware_keycode = keys[0].keycode;
    642           msg.group = keys[0].group;
    643           msg.is_modifier = 0;
    644 
    645           g_free(keys);
    646 
    647           gtk_main_do_event(reinterpret_cast<GdkEvent*>(&msg));
    648         } else {
    649           // This means that there isn't a h/w code for the keyval in the
    650           // current keymap, which is weird but possible if the keymap just
    651           // changed. This isn't a critical error, but might be indicative
    652           // of something off if it happens regularly.
    653           DLOG(WARNING) << "Found no keys for value " << keyval;
    654         }
    655         Close();
    656       }
    657       break;
    658     default:
    659       return FALSE;
    660   }
    661 
    662   return TRUE;
    663 }
    664 
    665 gboolean BubbleGtk::OnExpose(GtkWidget* widget, GdkEventExpose* expose) {
    666   // TODO(erg): This whole method will need to be rewritten in cairo.
    667   GdkDrawable* drawable = GDK_DRAWABLE(gtk_widget_get_window(window_));
    668   GdkGC* gc = gdk_gc_new(drawable);
    669   gdk_gc_set_rgb_fg_color(gc, &kFrameColor);
    670 
    671   // Stroke the frame border.
    672   GtkAllocation allocation;
    673   gtk_widget_get_allocation(window_, &allocation);
    674   std::vector<GdkPoint> points = MakeFramePolygonPoints(
    675       actual_frame_style_, allocation.width, allocation.height,
    676       FRAME_STROKE);
    677   gdk_draw_polygon(drawable, gc, FALSE, &points[0], points.size());
    678 
    679   // If |grab_input_| is false, pointer input has been grabbed as a hack in
    680   // |GrabPointerAndKeyboard()| to ensure that the polygon frame is drawn
    681   // correctly. Since the intention is not actually to grab the pointer, release
    682   // it now that the frame is drawn to prevent clicks from being missed. See
    683   // https://code.google.com/p/chromium/issues/detail?id=130820.
    684   if (!grab_input_)
    685     gdk_pointer_ungrab(GDK_CURRENT_TIME);
    686 
    687   g_object_unref(gc);
    688   return FALSE;  // Propagate so our children paint, etc.
    689 }
    690 
    691 // When our size is initially allocated or changed, we need to recompute
    692 // and apply our shape mask region.
    693 void BubbleGtk::OnSizeAllocate(GtkWidget* widget,
    694                                GtkAllocation* allocation) {
    695   if (!UpdateFrameStyle(false)) {
    696     UpdateWindowShape();
    697     MoveWindow();
    698   }
    699 }
    700 
    701 gboolean BubbleGtk::OnButtonPress(GtkWidget* widget,
    702                                   GdkEventButton* event) {
    703   // If we got a click in our own window, that's okay (we need to additionally
    704   // check that it falls within our bounds, since we've grabbed the pointer and
    705   // some events that actually occurred in other windows will be reported with
    706   // respect to our window).
    707   GdkWindow* gdk_window = gtk_widget_get_window(window_);
    708   if (event->window == gdk_window &&
    709       (mask_region_ && gdk_region_point_in(mask_region_, event->x, event->y))) {
    710     return FALSE;  // Propagate.
    711   }
    712 
    713   // Our content widget got a click.
    714   if (event->window != gdk_window &&
    715       gdk_window_get_toplevel(event->window) == gdk_window) {
    716     return FALSE;
    717   }
    718 
    719   if (grab_input_) {
    720     // Otherwise we had a click outside of our window, close ourself.
    721     Close();
    722     return TRUE;
    723   }
    724 
    725   return FALSE;
    726 }
    727 
    728 gboolean BubbleGtk::OnDestroy(GtkWidget* widget) {
    729   // We are self deleting, we have a destroy signal setup to catch when we
    730   // destroy the widget manually, or the window was closed via X.  This will
    731   // delete the BubbleGtk object.
    732   delete this;
    733   return FALSE;  // Propagate.
    734 }
    735 
    736 void BubbleGtk::OnHide(GtkWidget* widget) {
    737   gtk_widget_destroy(widget);
    738 }
    739 
    740 gboolean BubbleGtk::OnGrabBroken(GtkWidget* widget,
    741                                  GdkEventGrabBroken* grab_broken) {
    742   // |grab_input_| may have been changed to false.
    743   if (!grab_input_)
    744     return FALSE;
    745 
    746   // |grab_window| can be NULL.
    747   if (!grab_broken->grab_window)
    748     return FALSE;
    749 
    750   gpointer user_data;
    751   gdk_window_get_user_data(grab_broken->grab_window, &user_data);
    752 
    753   if (GTK_IS_WIDGET(user_data)) {
    754     signals_.Connect(GTK_WIDGET(user_data), "hide",
    755                      G_CALLBACK(OnForeshadowWidgetHideThunk), this);
    756   }
    757 
    758   return FALSE;
    759 }
    760 
    761 void BubbleGtk::OnForeshadowWidgetHide(GtkWidget* widget) {
    762   if (grab_input_)
    763     GrabPointerAndKeyboard();
    764 
    765   signals_.DisconnectAll(widget);
    766 }
    767 
    768 gboolean BubbleGtk::OnToplevelConfigure(GtkWidget* widget,
    769                                         GdkEventConfigure* event) {
    770   if (!UpdateFrameStyle(false))
    771     MoveWindow();
    772   StackWindow();
    773   return FALSE;
    774 }
    775 
    776 gboolean BubbleGtk::OnToplevelUnmap(GtkWidget* widget, GdkEvent* event) {
    777   Close();
    778   return FALSE;
    779 }
    780 
    781 void BubbleGtk::OnAnchorAllocate(GtkWidget* widget,
    782                                  GtkAllocation* allocation) {
    783   if (!UpdateFrameStyle(false))
    784     MoveWindow();
    785 }
    786 
    787 void BubbleGtk::OnAnchorDestroy(GtkWidget* widget) {
    788   anchor_widget_ = NULL;
    789   Close();
    790   // |this| is deleted.
    791 }
    792 
    793 void BubbleGtk::OnToplevelDestroy(GtkWidget* widget) {
    794   toplevel_window_ = NULL;
    795   Close();
    796   // |this| is deleted.
    797 }
    798