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/bind.h"
     10 #include "base/i18n/rtl.h"
     11 #include "base/message_loop/message_loop.h"
     12 #include "chrome/browser/chrome_notification_types.h"
     13 #include "chrome/browser/ui/gtk/bubble/bubble_accelerators_gtk.h"
     14 #include "chrome/browser/ui/gtk/gtk_theme_service.h"
     15 #include "chrome/browser/ui/gtk/gtk_util.h"
     16 #include "content/public/browser/notification_source.h"
     17 #include "ui/base/gtk/gtk_hig_constants.h"
     18 #include "ui/base/gtk/gtk_windowing.h"
     19 #include "ui/gfx/gtk_compat.h"
     20 #include "ui/gfx/gtk_util.h"
     21 #include "ui/gfx/path.h"
     22 #include "ui/gfx/rect.h"
     23 
     24 namespace {
     25 
     26 // The height of the arrow, and the width will be about twice the height.
     27 const int kArrowSize = 8;
     28 
     29 // Number of pixels to the middle of the arrow from the close edge of the
     30 // window.
     31 const int kArrowX = 18;
     32 
     33 // Number of pixels between the tip of the arrow and the region we're
     34 // pointing to.
     35 const int kArrowToContentPadding = -4;
     36 
     37 // We draw flat diagonal corners, each corner is an NxN square.
     38 const int kCornerSize = 3;
     39 
     40 // The amount of padding (in pixels) from the top of |toplevel_window_| to the
     41 // top of |window_| when fixed positioning is used.
     42 const int kFixedPositionPaddingEnd = 10;
     43 const int kFixedPositionPaddingTop = 5;
     44 
     45 const GdkColor kBackgroundColor = GDK_COLOR_RGB(0xff, 0xff, 0xff);
     46 const GdkColor kFrameColor = GDK_COLOR_RGB(0x63, 0x63, 0x63);
     47 
     48 // Helper functions that encapsulate arrow locations.
     49 bool HasArrow(BubbleGtk::FrameStyle frame_style) {
     50   return frame_style != BubbleGtk::FLOAT_BELOW_RECT &&
     51          frame_style != BubbleGtk::CENTER_OVER_RECT &&
     52          frame_style != BubbleGtk::FIXED_TOP_LEFT &&
     53          frame_style != BubbleGtk::FIXED_TOP_RIGHT;
     54 }
     55 
     56 bool IsArrowLeft(BubbleGtk::FrameStyle frame_style) {
     57   return frame_style == BubbleGtk::ANCHOR_TOP_LEFT ||
     58          frame_style == BubbleGtk::ANCHOR_BOTTOM_LEFT;
     59 }
     60 
     61 bool IsArrowMiddle(BubbleGtk::FrameStyle frame_style) {
     62   return frame_style == BubbleGtk::ANCHOR_TOP_MIDDLE ||
     63          frame_style == BubbleGtk::ANCHOR_BOTTOM_MIDDLE;
     64 }
     65 
     66 bool IsArrowRight(BubbleGtk::FrameStyle frame_style) {
     67   return frame_style == BubbleGtk::ANCHOR_TOP_RIGHT ||
     68          frame_style == BubbleGtk::ANCHOR_BOTTOM_RIGHT;
     69 }
     70 
     71 bool IsArrowTop(BubbleGtk::FrameStyle frame_style) {
     72   return frame_style == BubbleGtk::ANCHOR_TOP_LEFT ||
     73          frame_style == BubbleGtk::ANCHOR_TOP_MIDDLE ||
     74          frame_style == BubbleGtk::ANCHOR_TOP_RIGHT;
     75 }
     76 
     77 bool IsArrowBottom(BubbleGtk::FrameStyle frame_style) {
     78   return frame_style == BubbleGtk::ANCHOR_BOTTOM_LEFT ||
     79          frame_style == BubbleGtk::ANCHOR_BOTTOM_MIDDLE ||
     80          frame_style == BubbleGtk::ANCHOR_BOTTOM_RIGHT;
     81 }
     82 
     83 bool IsFixed(BubbleGtk::FrameStyle frame_style) {
     84   return frame_style == BubbleGtk::FIXED_TOP_LEFT ||
     85          frame_style == BubbleGtk::FIXED_TOP_RIGHT;
     86 }
     87 
     88 BubbleGtk::FrameStyle AdjustFrameStyleForLocale(
     89     BubbleGtk::FrameStyle frame_style) {
     90   // Only RTL requires more work.
     91   if (!base::i18n::IsRTL())
     92     return frame_style;
     93 
     94   switch (frame_style) {
     95     // These don't flip.
     96     case BubbleGtk::ANCHOR_TOP_MIDDLE:
     97     case BubbleGtk::ANCHOR_BOTTOM_MIDDLE:
     98     case BubbleGtk::FLOAT_BELOW_RECT:
     99     case BubbleGtk::CENTER_OVER_RECT:
    100       return frame_style;
    101 
    102     // These do flip.
    103     case BubbleGtk::ANCHOR_TOP_LEFT:
    104       return BubbleGtk::ANCHOR_TOP_RIGHT;
    105 
    106     case BubbleGtk::ANCHOR_TOP_RIGHT:
    107       return BubbleGtk::ANCHOR_TOP_LEFT;
    108 
    109     case BubbleGtk::ANCHOR_BOTTOM_LEFT:
    110       return BubbleGtk::ANCHOR_BOTTOM_RIGHT;
    111 
    112     case BubbleGtk::ANCHOR_BOTTOM_RIGHT:
    113       return BubbleGtk::ANCHOR_BOTTOM_LEFT;
    114 
    115     case BubbleGtk::FIXED_TOP_LEFT:
    116       return BubbleGtk::FIXED_TOP_RIGHT;
    117 
    118     case BubbleGtk::FIXED_TOP_RIGHT:
    119       return BubbleGtk::FIXED_TOP_LEFT;
    120   }
    121 
    122   NOTREACHED();
    123   return BubbleGtk::ANCHOR_TOP_LEFT;
    124 }
    125 
    126 }  // namespace
    127 
    128 // static
    129 BubbleGtk* BubbleGtk::Show(GtkWidget* anchor_widget,
    130                            const gfx::Rect* rect,
    131                            GtkWidget* content,
    132                            FrameStyle frame_style,
    133                            int attribute_flags,
    134                            GtkThemeService* provider,
    135                            BubbleDelegateGtk* delegate) {
    136   BubbleGtk* bubble = new BubbleGtk(provider,
    137                                     AdjustFrameStyleForLocale(frame_style),
    138                                     attribute_flags);
    139   bubble->Init(anchor_widget, rect, content, attribute_flags);
    140   bubble->set_delegate(delegate);
    141   return bubble;
    142 }
    143 
    144 BubbleGtk::BubbleGtk(GtkThemeService* provider,
    145                      FrameStyle frame_style,
    146                      int attribute_flags)
    147     : delegate_(NULL),
    148       window_(NULL),
    149       theme_service_(provider),
    150       accel_group_(gtk_accel_group_new()),
    151       toplevel_window_(NULL),
    152       anchor_widget_(NULL),
    153       mask_region_(NULL),
    154       requested_frame_style_(frame_style),
    155       actual_frame_style_(ANCHOR_TOP_LEFT),
    156       match_system_theme_(attribute_flags & MATCH_SYSTEM_THEME),
    157       grab_input_(attribute_flags & GRAB_INPUT),
    158       closed_by_escape_(false),
    159       weak_ptr_factory_(this) {}
    160 
    161 BubbleGtk::~BubbleGtk() {
    162   // Notify the delegate that we're about to close.  This gives the chance
    163   // to save state / etc from the hosted widget before it's destroyed.
    164   if (delegate_)
    165     delegate_->BubbleClosing(this, closed_by_escape_);
    166 
    167   g_object_unref(accel_group_);
    168   if (mask_region_)
    169     gdk_region_destroy(mask_region_);
    170 }
    171 
    172 void BubbleGtk::Init(GtkWidget* anchor_widget,
    173                      const gfx::Rect* rect,
    174                      GtkWidget* content,
    175                      int attribute_flags) {
    176   // If there is a current grab widget (menu, other bubble, etc.), hide it.
    177   GtkWidget* current_grab_widget = gtk_grab_get_current();
    178   if (current_grab_widget)
    179     gtk_widget_hide(current_grab_widget);
    180 
    181   DCHECK(!window_);
    182   anchor_widget_ = anchor_widget;
    183   toplevel_window_ = gtk_widget_get_toplevel(anchor_widget_);
    184   DCHECK(gtk_widget_is_toplevel(toplevel_window_));
    185   rect_ = rect ? *rect : gtk_util::WidgetBounds(anchor_widget);
    186 
    187   // Using a TOPLEVEL window may cause placement issues with certain WMs but it
    188   // is necessary to be able to focus the window.
    189   window_ = gtk_window_new(attribute_flags & POPUP_WINDOW ?
    190                            GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL);
    191 
    192   gtk_widget_set_app_paintable(window_, TRUE);
    193   // Resizing is handled by the program, not user.
    194   gtk_window_set_resizable(GTK_WINDOW(window_), FALSE);
    195 
    196   if (!(attribute_flags & NO_ACCELERATORS)) {
    197     // Attach all of the accelerators to the bubble.
    198     for (BubbleAcceleratorsGtk::const_iterator
    199              i(BubbleAcceleratorsGtk::begin());
    200          i != BubbleAcceleratorsGtk::end();
    201          ++i) {
    202       gtk_accel_group_connect(accel_group_,
    203                               i->keyval,
    204                               i->modifier_type,
    205                               GtkAccelFlags(0),
    206                               g_cclosure_new(G_CALLBACK(&OnGtkAcceleratorThunk),
    207                                              this,
    208                                              NULL));
    209     }
    210     gtk_window_add_accel_group(GTK_WINDOW(window_), accel_group_);
    211   }
    212 
    213   // |requested_frame_style_| is used instead of |actual_frame_style_| here
    214   // because |actual_frame_style_| is only correct after calling
    215   // |UpdateFrameStyle()|. Unfortunately, |UpdateFrameStyle()| requires knowing
    216   // the size of |window_| (which happens later on).
    217   int arrow_padding = HasArrow(requested_frame_style_) ? kArrowSize : 0;
    218   GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
    219   gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), arrow_padding, 0, 0, 0);
    220 
    221   gtk_container_add(GTK_CONTAINER(alignment), content);
    222   gtk_container_add(GTK_CONTAINER(window_), alignment);
    223 
    224   // GtkWidget only exposes the bitmap mask interface.  Use GDK to more
    225   // efficently mask a GdkRegion.  Make sure the window is realized during
    226   // OnSizeAllocate, so the mask can be applied to the GdkWindow.
    227   gtk_widget_realize(window_);
    228 
    229   UpdateFrameStyle(true);  // Force move and reshape.
    230   StackWindow();
    231 
    232   gtk_widget_add_events(window_, GDK_BUTTON_PRESS_MASK);
    233 
    234   // Connect during the bubbling phase so the border is always on top.
    235   signals_.ConnectAfter(window_, "expose-event",
    236                         G_CALLBACK(OnExposeThunk), this);
    237   signals_.Connect(window_, "size-allocate", G_CALLBACK(OnSizeAllocateThunk),
    238                    this);
    239   signals_.Connect(window_, "button-press-event",
    240                    G_CALLBACK(OnButtonPressThunk), this);
    241   signals_.Connect(window_, "destroy", G_CALLBACK(OnDestroyThunk), this);
    242   signals_.Connect(window_, "hide", G_CALLBACK(OnHideThunk), this);
    243   if (grab_input_) {
    244     signals_.Connect(window_, "grab-broken-event",
    245                      G_CALLBACK(OnGrabBrokenThunk), this);
    246   }
    247 
    248   signals_.Connect(anchor_widget_, "destroy",
    249                    G_CALLBACK(OnAnchorDestroyThunk), this);
    250   // If the toplevel window is being used as the anchor, then the signals below
    251   // are enough to keep us positioned correctly.
    252   if (anchor_widget_ != toplevel_window_) {
    253     signals_.Connect(anchor_widget_, "size-allocate",
    254                      G_CALLBACK(OnAnchorAllocateThunk), this);
    255   }
    256 
    257   signals_.Connect(toplevel_window_, "configure-event",
    258                    G_CALLBACK(OnToplevelConfigureThunk), this);
    259   signals_.Connect(toplevel_window_, "unmap-event",
    260                    G_CALLBACK(OnToplevelUnmapThunk), 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 
    790   // Ctrl-W will first destroy the anchor, then call |this| back via
    791   // |accel_group_|. So that the callback |this| registered via |accel_group_|
    792   // doesn't run after |this| is destroyed, we delay this close (which, unlike
    793   // the accelerator callback, will be cancelled if |this| is destroyed).
    794   // http://crbug.com/286621
    795   base::MessageLoop::current()->PostTask(
    796       FROM_HERE,
    797       base::Bind(&BubbleGtk::Close, weak_ptr_factory_.GetWeakPtr()));
    798 }
    799