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 |