| OLD | NEW |
| (Empty) |
| 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 } | |
| OLD | NEW |