| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2010 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/gtk/info_bubble_gtk.h" | |
| 6 | |
| 7 #include <gdk/gdkkeysyms.h> | |
| 8 #include <vector> | |
| 9 | |
| 10 #include "base/basictypes.h" | |
| 11 #include "base/logging.h" | |
| 12 #include "chrome/browser/gtk/gtk_theme_provider.h" | |
| 13 #include "chrome/browser/gtk/gtk_util.h" | |
| 14 #include "chrome/browser/gtk/info_bubble_accelerators_gtk.h" | |
| 15 #include "chrome/common/notification_service.h" | |
| 16 #include "gfx/gtk_util.h" | |
| 17 #include "gfx/path.h" | |
| 18 #include "gfx/rect.h" | |
| 19 | |
| 20 namespace { | |
| 21 | |
| 22 // The height of the arrow, and the width will be about twice the height. | |
| 23 const int kArrowSize = 8; | |
| 24 | |
| 25 // Number of pixels to the middle of the arrow from the close edge of the | |
| 26 // window. | |
| 27 const int kArrowX = 18; | |
| 28 | |
| 29 // Number of pixels between the tip of the arrow and the region we're | |
| 30 // pointing to. | |
| 31 const int kArrowToContentPadding = -4; | |
| 32 | |
| 33 // We draw flat diagonal corners, each corner is an NxN square. | |
| 34 const int kCornerSize = 3; | |
| 35 | |
| 36 // Margins around the content. | |
| 37 const int kTopMargin = kArrowSize + kCornerSize - 1; | |
| 38 const int kBottomMargin = kCornerSize - 1; | |
| 39 const int kLeftMargin = kCornerSize - 1; | |
| 40 const int kRightMargin = kCornerSize - 1; | |
| 41 | |
| 42 const GdkColor kBackgroundColor = GDK_COLOR_RGB(0xff, 0xff, 0xff); | |
| 43 const GdkColor kFrameColor = GDK_COLOR_RGB(0x63, 0x63, 0x63); | |
| 44 | |
| 45 } // namespace | |
| 46 | |
| 47 // static | |
| 48 InfoBubbleGtk* InfoBubbleGtk::Show(GtkWidget* anchor_widget, | |
| 49 const gfx::Rect* rect, | |
| 50 GtkWidget* content, | |
| 51 ArrowLocationGtk arrow_location, | |
| 52 bool match_system_theme, | |
| 53 bool grab_input, | |
| 54 GtkThemeProvider* provider, | |
| 55 InfoBubbleGtkDelegate* delegate) { | |
| 56 InfoBubbleGtk* bubble = new InfoBubbleGtk(provider, match_system_theme); | |
| 57 bubble->Init(anchor_widget, rect, content, arrow_location, grab_input); | |
| 58 bubble->set_delegate(delegate); | |
| 59 return bubble; | |
| 60 } | |
| 61 | |
| 62 InfoBubbleGtk::InfoBubbleGtk(GtkThemeProvider* provider, | |
| 63 bool match_system_theme) | |
| 64 : delegate_(NULL), | |
| 65 window_(NULL), | |
| 66 theme_provider_(provider), | |
| 67 accel_group_(gtk_accel_group_new()), | |
| 68 toplevel_window_(NULL), | |
| 69 anchor_widget_(NULL), | |
| 70 mask_region_(NULL), | |
| 71 preferred_arrow_location_(ARROW_LOCATION_TOP_LEFT), | |
| 72 current_arrow_location_(ARROW_LOCATION_TOP_LEFT), | |
| 73 match_system_theme_(match_system_theme), | |
| 74 grab_input_(true), | |
| 75 closed_by_escape_(false) { | |
| 76 } | |
| 77 | |
| 78 InfoBubbleGtk::~InfoBubbleGtk() { | |
| 79 // Notify the delegate that we're about to close. This gives the chance | |
| 80 // to save state / etc from the hosted widget before it's destroyed. | |
| 81 if (delegate_) | |
| 82 delegate_->InfoBubbleClosing(this, closed_by_escape_); | |
| 83 | |
| 84 g_object_unref(accel_group_); | |
| 85 if (mask_region_) | |
| 86 gdk_region_destroy(mask_region_); | |
| 87 } | |
| 88 | |
| 89 void InfoBubbleGtk::Init(GtkWidget* anchor_widget, | |
| 90 const gfx::Rect* rect, | |
| 91 GtkWidget* content, | |
| 92 ArrowLocationGtk arrow_location, | |
| 93 bool grab_input) { | |
| 94 // If there is a current grab widget (menu, other info bubble, etc.), hide it. | |
| 95 GtkWidget* current_grab_widget = gtk_grab_get_current(); | |
| 96 if (current_grab_widget) | |
| 97 gtk_widget_hide(current_grab_widget); | |
| 98 | |
| 99 DCHECK(!window_); | |
| 100 anchor_widget_ = anchor_widget; | |
| 101 toplevel_window_ = GTK_WINDOW(gtk_widget_get_toplevel(anchor_widget_)); | |
| 102 DCHECK(GTK_WIDGET_TOPLEVEL(toplevel_window_)); | |
| 103 rect_ = rect ? *rect : gtk_util::WidgetBounds(anchor_widget); | |
| 104 preferred_arrow_location_ = arrow_location; | |
| 105 | |
| 106 grab_input_ = grab_input; | |
| 107 // Using a TOPLEVEL window may cause placement issues with certain WMs but it | |
| 108 // is necessary to be able to focus the window. | |
| 109 window_ = gtk_window_new(grab_input ? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL); | |
| 110 | |
| 111 gtk_widget_set_app_paintable(window_, TRUE); | |
| 112 // Resizing is handled by the program, not user. | |
| 113 gtk_window_set_resizable(GTK_WINDOW(window_), FALSE); | |
| 114 | |
| 115 // Attach all of the accelerators to the bubble. | |
| 116 InfoBubbleAcceleratorGtkList acceleratorList = | |
| 117 InfoBubbleAcceleratorsGtk::GetList(); | |
| 118 for (InfoBubbleAcceleratorGtkList::const_iterator iter = | |
| 119 acceleratorList.begin(); | |
| 120 iter != acceleratorList.end(); | |
| 121 ++iter) { | |
| 122 gtk_accel_group_connect(accel_group_, | |
| 123 iter->keyval, | |
| 124 iter->modifier_type, | |
| 125 GtkAccelFlags(0), | |
| 126 g_cclosure_new(G_CALLBACK(&OnGtkAcceleratorThunk), | |
| 127 this, | |
| 128 NULL)); | |
| 129 } | |
| 130 | |
| 131 gtk_window_add_accel_group(GTK_WINDOW(window_), accel_group_); | |
| 132 | |
| 133 GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); | |
| 134 gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), | |
| 135 kTopMargin, kBottomMargin, | |
| 136 kLeftMargin, kRightMargin); | |
| 137 | |
| 138 gtk_container_add(GTK_CONTAINER(alignment), content); | |
| 139 gtk_container_add(GTK_CONTAINER(window_), alignment); | |
| 140 | |
| 141 // GtkWidget only exposes the bitmap mask interface. Use GDK to more | |
| 142 // efficently mask a GdkRegion. Make sure the window is realized during | |
| 143 // OnSizeAllocate, so the mask can be applied to the GdkWindow. | |
| 144 gtk_widget_realize(window_); | |
| 145 | |
| 146 UpdateArrowLocation(true); // Force move and reshape. | |
| 147 StackWindow(); | |
| 148 | |
| 149 gtk_widget_add_events(window_, GDK_BUTTON_PRESS_MASK); | |
| 150 | |
| 151 signals_.Connect(window_, "expose-event", G_CALLBACK(OnExposeThunk), this); | |
| 152 signals_.Connect(window_, "size-allocate", G_CALLBACK(OnSizeAllocateThunk), | |
| 153 this); | |
| 154 signals_.Connect(window_, "button-press-event", | |
| 155 G_CALLBACK(OnButtonPressThunk), this); | |
| 156 signals_.Connect(window_, "destroy", G_CALLBACK(OnDestroyThunk), this); | |
| 157 signals_.Connect(window_, "hide", G_CALLBACK(OnHideThunk), this); | |
| 158 | |
| 159 // If the toplevel window is being used as the anchor, then the signals below | |
| 160 // are enough to keep us positioned correctly. | |
| 161 if (anchor_widget_ != GTK_WIDGET(toplevel_window_)) { | |
| 162 signals_.Connect(anchor_widget_, "size-allocate", | |
| 163 G_CALLBACK(OnAnchorAllocateThunk), this); | |
| 164 signals_.Connect(anchor_widget_, "destroy", | |
| 165 G_CALLBACK(gtk_widget_destroyed), &anchor_widget_); | |
| 166 } | |
| 167 | |
| 168 signals_.Connect(toplevel_window_, "configure-event", | |
| 169 G_CALLBACK(OnToplevelConfigureThunk), this); | |
| 170 signals_.Connect(toplevel_window_, "unmap-event", | |
| 171 G_CALLBACK(OnToplevelUnmapThunk), this); | |
| 172 // Set |toplevel_window_| to NULL if it gets destroyed. | |
| 173 signals_.Connect(toplevel_window_, "destroy", | |
| 174 G_CALLBACK(gtk_widget_destroyed), &toplevel_window_); | |
| 175 | |
| 176 gtk_widget_show_all(window_); | |
| 177 | |
| 178 if (grab_input_) { | |
| 179 gtk_grab_add(window_); | |
| 180 GrabPointerAndKeyboard(); | |
| 181 } | |
| 182 | |
| 183 registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, | |
| 184 NotificationService::AllSources()); | |
| 185 theme_provider_->InitThemesFor(this); | |
| 186 } | |
| 187 | |
| 188 // NOTE: This seems a bit overcomplicated, but it requires a bunch of careful | |
| 189 // fudging to get the pixels rasterized exactly where we want them, the arrow to | |
| 190 // have a 1 pixel point, etc. | |
| 191 // TODO(deanm): Windows draws with Skia and uses some PNG images for the | |
| 192 // corners. This is a lot more work, but they get anti-aliasing. | |
| 193 // static | |
| 194 std::vector<GdkPoint> InfoBubbleGtk::MakeFramePolygonPoints( | |
| 195 ArrowLocationGtk arrow_location, | |
| 196 int width, | |
| 197 int height, | |
| 198 FrameType type) { | |
| 199 using gtk_util::MakeBidiGdkPoint; | |
| 200 std::vector<GdkPoint> points; | |
| 201 | |
| 202 bool on_left = (arrow_location == ARROW_LOCATION_TOP_LEFT); | |
| 203 | |
| 204 // If we're stroking the frame, we need to offset some of our points by 1 | |
| 205 // pixel. We do this when we draw horizontal lines that are on the bottom or | |
| 206 // when we draw vertical lines that are closer to the end (where "end" is the | |
| 207 // right side for ARROW_LOCATION_TOP_LEFT). | |
| 208 int y_off = (type == FRAME_MASK) ? 0 : -1; | |
| 209 // We use this one for arrows located on the left. | |
| 210 int x_off_l = on_left ? y_off : 0; | |
| 211 // We use this one for RTL. | |
| 212 int x_off_r = !on_left ? -y_off : 0; | |
| 213 | |
| 214 // Top left corner. | |
| 215 points.push_back(MakeBidiGdkPoint( | |
| 216 x_off_r, kArrowSize + kCornerSize - 1, width, on_left)); | |
| 217 points.push_back(MakeBidiGdkPoint( | |
| 218 kCornerSize + x_off_r - 1, kArrowSize, width, on_left)); | |
| 219 | |
| 220 // The arrow. | |
| 221 points.push_back(MakeBidiGdkPoint( | |
| 222 kArrowX - kArrowSize + x_off_r, kArrowSize, width, on_left)); | |
| 223 points.push_back(MakeBidiGdkPoint( | |
| 224 kArrowX + x_off_r, 0, width, on_left)); | |
| 225 points.push_back(MakeBidiGdkPoint( | |
| 226 kArrowX + 1 + x_off_l, 0, width, on_left)); | |
| 227 points.push_back(MakeBidiGdkPoint( | |
| 228 kArrowX + kArrowSize + 1 + x_off_l, kArrowSize, width, on_left)); | |
| 229 | |
| 230 // Top right corner. | |
| 231 points.push_back(MakeBidiGdkPoint( | |
| 232 width - kCornerSize + 1 + x_off_l, kArrowSize, width, on_left)); | |
| 233 points.push_back(MakeBidiGdkPoint( | |
| 234 width + x_off_l, kArrowSize + kCornerSize - 1, width, on_left)); | |
| 235 | |
| 236 // Bottom right corner. | |
| 237 points.push_back(MakeBidiGdkPoint( | |
| 238 width + x_off_l, height - kCornerSize, width, on_left)); | |
| 239 points.push_back(MakeBidiGdkPoint( | |
| 240 width - kCornerSize + x_off_r, height + y_off, width, on_left)); | |
| 241 | |
| 242 // Bottom left corner. | |
| 243 points.push_back(MakeBidiGdkPoint( | |
| 244 kCornerSize + x_off_l, height + y_off, width, on_left)); | |
| 245 points.push_back(MakeBidiGdkPoint( | |
| 246 x_off_r, height - kCornerSize, width, on_left)); | |
| 247 | |
| 248 return points; | |
| 249 } | |
| 250 | |
| 251 InfoBubbleGtk::ArrowLocationGtk InfoBubbleGtk::GetArrowLocation( | |
| 252 ArrowLocationGtk preferred_location, int arrow_x, int width) { | |
| 253 bool wants_left = (preferred_location == ARROW_LOCATION_TOP_LEFT); | |
| 254 int screen_width = gdk_screen_get_width(gdk_screen_get_default()); | |
| 255 | |
| 256 bool left_is_onscreen = (arrow_x - kArrowX + width < screen_width); | |
| 257 bool right_is_onscreen = (arrow_x + kArrowX - width >= 0); | |
| 258 | |
| 259 // Use the requested location if it fits onscreen, use whatever fits | |
| 260 // otherwise, and use the requested location if neither fits. | |
| 261 if (left_is_onscreen && (wants_left || !right_is_onscreen)) | |
| 262 return ARROW_LOCATION_TOP_LEFT; | |
| 263 if (right_is_onscreen && (!wants_left || !left_is_onscreen)) | |
| 264 return ARROW_LOCATION_TOP_RIGHT; | |
| 265 return (wants_left ? ARROW_LOCATION_TOP_LEFT : ARROW_LOCATION_TOP_RIGHT); | |
| 266 } | |
| 267 | |
| 268 bool InfoBubbleGtk::UpdateArrowLocation(bool force_move_and_reshape) { | |
| 269 if (!toplevel_window_ || !anchor_widget_) | |
| 270 return false; | |
| 271 | |
| 272 gint toplevel_x = 0, toplevel_y = 0; | |
| 273 gdk_window_get_position( | |
| 274 GTK_WIDGET(toplevel_window_)->window, &toplevel_x, &toplevel_y); | |
| 275 int offset_x, offset_y; | |
| 276 gtk_widget_translate_coordinates(anchor_widget_, GTK_WIDGET(toplevel_window_), | |
| 277 rect_.x(), rect_.y(), &offset_x, &offset_y); | |
| 278 | |
| 279 ArrowLocationGtk old_location = current_arrow_location_; | |
| 280 current_arrow_location_ = GetArrowLocation( | |
| 281 preferred_arrow_location_, | |
| 282 toplevel_x + offset_x + (rect_.width() / 2), // arrow_x | |
| 283 window_->allocation.width); | |
| 284 | |
| 285 if (force_move_and_reshape || current_arrow_location_ != old_location) { | |
| 286 UpdateWindowShape(); | |
| 287 MoveWindow(); | |
| 288 // We need to redraw the entire window to repaint its border. | |
| 289 gtk_widget_queue_draw(window_); | |
| 290 return true; | |
| 291 } | |
| 292 return false; | |
| 293 } | |
| 294 | |
| 295 void InfoBubbleGtk::UpdateWindowShape() { | |
| 296 if (mask_region_) { | |
| 297 gdk_region_destroy(mask_region_); | |
| 298 mask_region_ = NULL; | |
| 299 } | |
| 300 std::vector<GdkPoint> points = MakeFramePolygonPoints( | |
| 301 current_arrow_location_, | |
| 302 window_->allocation.width, window_->allocation.height, | |
| 303 FRAME_MASK); | |
| 304 mask_region_ = gdk_region_polygon(&points[0], | |
| 305 points.size(), | |
| 306 GDK_EVEN_ODD_RULE); | |
| 307 gdk_window_shape_combine_region(window_->window, NULL, 0, 0); | |
| 308 gdk_window_shape_combine_region(window_->window, mask_region_, 0, 0); | |
| 309 } | |
| 310 | |
| 311 void InfoBubbleGtk::MoveWindow() { | |
| 312 if (!toplevel_window_ || !anchor_widget_) | |
| 313 return; | |
| 314 | |
| 315 gint toplevel_x = 0, toplevel_y = 0; | |
| 316 gdk_window_get_position( | |
| 317 GTK_WIDGET(toplevel_window_)->window, &toplevel_x, &toplevel_y); | |
| 318 | |
| 319 int offset_x, offset_y; | |
| 320 gtk_widget_translate_coordinates(anchor_widget_, GTK_WIDGET(toplevel_window_), | |
| 321 rect_.x(), rect_.y(), &offset_x, &offset_y); | |
| 322 | |
| 323 gint screen_x = 0; | |
| 324 if (current_arrow_location_ == ARROW_LOCATION_TOP_LEFT) { | |
| 325 screen_x = toplevel_x + offset_x + (rect_.width() / 2) - kArrowX; | |
| 326 } else if (current_arrow_location_ == ARROW_LOCATION_TOP_RIGHT) { | |
| 327 screen_x = toplevel_x + offset_x + (rect_.width() / 2) - | |
| 328 window_->allocation.width + kArrowX; | |
| 329 } else { | |
| 330 NOTREACHED(); | |
| 331 } | |
| 332 | |
| 333 gint screen_y = toplevel_y + offset_y + rect_.height() + | |
| 334 kArrowToContentPadding; | |
| 335 | |
| 336 gtk_window_move(GTK_WINDOW(window_), screen_x, screen_y); | |
| 337 } | |
| 338 | |
| 339 void InfoBubbleGtk::StackWindow() { | |
| 340 // Stack our window directly above the toplevel window. | |
| 341 if (toplevel_window_) | |
| 342 gtk_util::StackPopupWindow(window_, GTK_WIDGET(toplevel_window_)); | |
| 343 } | |
| 344 | |
| 345 void InfoBubbleGtk::Observe(NotificationType type, | |
| 346 const NotificationSource& source, | |
| 347 const NotificationDetails& details) { | |
| 348 DCHECK_EQ(type.value, NotificationType::BROWSER_THEME_CHANGED); | |
| 349 if (theme_provider_->UseGtkTheme() && match_system_theme_) { | |
| 350 gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, NULL); | |
| 351 } else { | |
| 352 // Set the background color, so we don't need to paint it manually. | |
| 353 gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, &kBackgroundColor); | |
| 354 } | |
| 355 } | |
| 356 | |
| 357 void InfoBubbleGtk::HandlePointerAndKeyboardUngrabbedByContent() { | |
| 358 if (grab_input_) | |
| 359 GrabPointerAndKeyboard(); | |
| 360 } | |
| 361 | |
| 362 void InfoBubbleGtk::Close() { | |
| 363 // We don't need to ungrab the pointer or keyboard here; the X server will | |
| 364 // automatically do that when we destroy our window. | |
| 365 DCHECK(window_); | |
| 366 gtk_widget_destroy(window_); | |
| 367 // |this| has been deleted, see OnDestroy. | |
| 368 } | |
| 369 | |
| 370 void InfoBubbleGtk::GrabPointerAndKeyboard() { | |
| 371 // Install X pointer and keyboard grabs to make sure that we have the focus | |
| 372 // and get all mouse and keyboard events until we're closed. | |
| 373 GdkGrabStatus pointer_grab_status = | |
| 374 gdk_pointer_grab(window_->window, | |
| 375 TRUE, // owner_events | |
| 376 GDK_BUTTON_PRESS_MASK, // event_mask | |
| 377 NULL, // confine_to | |
| 378 NULL, // cursor | |
| 379 GDK_CURRENT_TIME); | |
| 380 if (pointer_grab_status != GDK_GRAB_SUCCESS) { | |
| 381 // This will fail if someone else already has the pointer grabbed, but | |
| 382 // there's not really anything we can do about that. | |
| 383 DLOG(ERROR) << "Unable to grab pointer (status=" | |
| 384 << pointer_grab_status << ")"; | |
| 385 } | |
| 386 GdkGrabStatus keyboard_grab_status = | |
| 387 gdk_keyboard_grab(window_->window, | |
| 388 FALSE, // owner_events | |
| 389 GDK_CURRENT_TIME); | |
| 390 if (keyboard_grab_status != GDK_GRAB_SUCCESS) { | |
| 391 DLOG(ERROR) << "Unable to grab keyboard (status=" | |
| 392 << keyboard_grab_status << ")"; | |
| 393 } | |
| 394 } | |
| 395 | |
| 396 gboolean InfoBubbleGtk::OnGtkAccelerator(GtkAccelGroup* group, | |
| 397 GObject* acceleratable, | |
| 398 guint keyval, | |
| 399 GdkModifierType modifier) { | |
| 400 GdkEventKey msg; | |
| 401 GdkKeymapKey* keys; | |
| 402 gint n_keys; | |
| 403 | |
| 404 switch (keyval) { | |
| 405 case GDK_Escape: | |
| 406 // Close on Esc and trap the accelerator | |
| 407 closed_by_escape_ = true; | |
| 408 Close(); | |
| 409 return TRUE; | |
| 410 case GDK_w: | |
| 411 // Close on C-w and forward the accelerator | |
| 412 if (modifier & GDK_CONTROL_MASK) { | |
| 413 Close(); | |
| 414 } | |
| 415 break; | |
| 416 default: | |
| 417 return FALSE; | |
| 418 } | |
| 419 | |
| 420 gdk_keymap_get_entries_for_keyval(NULL, | |
| 421 keyval, | |
| 422 &keys, | |
| 423 &n_keys); | |
| 424 if (n_keys) { | |
| 425 // Forward the accelerator to root window the bubble is anchored | |
| 426 // to for further processing | |
| 427 msg.type = GDK_KEY_PRESS; | |
| 428 msg.window = GTK_WIDGET(toplevel_window_)->window; | |
| 429 msg.send_event = TRUE; | |
| 430 msg.time = GDK_CURRENT_TIME; | |
| 431 msg.state = modifier | GDK_MOD2_MASK; | |
| 432 msg.keyval = keyval; | |
| 433 // length and string are deprecated and thus zeroed out | |
| 434 msg.length = 0; | |
| 435 msg.string = NULL; | |
| 436 msg.hardware_keycode = keys[0].keycode; | |
| 437 msg.group = keys[0].group; | |
| 438 msg.is_modifier = 0; | |
| 439 | |
| 440 g_free(keys); | |
| 441 | |
| 442 gtk_main_do_event(reinterpret_cast<GdkEvent*>(&msg)); | |
| 443 } else { | |
| 444 // This means that there isn't a h/w code for the keyval in the | |
| 445 // current keymap, which is weird but possible if the keymap just | |
| 446 // changed. This isn't a critical error, but might be indicative | |
| 447 // of something off if it happens regularly. | |
| 448 DLOG(WARNING) << "Found no keys for value " << keyval; | |
| 449 } | |
| 450 return TRUE; | |
| 451 } | |
| 452 | |
| 453 gboolean InfoBubbleGtk::OnExpose(GtkWidget* widget, GdkEventExpose* expose) { | |
| 454 GdkDrawable* drawable = GDK_DRAWABLE(window_->window); | |
| 455 GdkGC* gc = gdk_gc_new(drawable); | |
| 456 gdk_gc_set_rgb_fg_color(gc, &kFrameColor); | |
| 457 | |
| 458 // Stroke the frame border. | |
| 459 std::vector<GdkPoint> points = MakeFramePolygonPoints( | |
| 460 current_arrow_location_, | |
| 461 window_->allocation.width, window_->allocation.height, | |
| 462 FRAME_STROKE); | |
| 463 gdk_draw_polygon(drawable, gc, FALSE, &points[0], points.size()); | |
| 464 | |
| 465 g_object_unref(gc); | |
| 466 return FALSE; // Propagate so our children paint, etc. | |
| 467 } | |
| 468 | |
| 469 // When our size is initially allocated or changed, we need to recompute | |
| 470 // and apply our shape mask region. | |
| 471 void InfoBubbleGtk::OnSizeAllocate(GtkWidget* widget, | |
| 472 GtkAllocation* allocation) { | |
| 473 if (!UpdateArrowLocation(false)) { | |
| 474 UpdateWindowShape(); | |
| 475 if (current_arrow_location_ == ARROW_LOCATION_TOP_RIGHT) | |
| 476 MoveWindow(); | |
| 477 } | |
| 478 } | |
| 479 | |
| 480 gboolean InfoBubbleGtk::OnButtonPress(GtkWidget* widget, | |
| 481 GdkEventButton* event) { | |
| 482 // If we got a click in our own window, that's okay (we need to additionally | |
| 483 // check that it falls within our bounds, since we've grabbed the pointer and | |
| 484 // some events that actually occurred in other windows will be reported with | |
| 485 // respect to our window). | |
| 486 if (event->window == window_->window && | |
| 487 (mask_region_ && gdk_region_point_in(mask_region_, event->x, event->y))) { | |
| 488 return FALSE; // Propagate. | |
| 489 } | |
| 490 | |
| 491 // Our content widget got a click. | |
| 492 if (event->window != window_->window && | |
| 493 gdk_window_get_toplevel(event->window) == window_->window) { | |
| 494 return FALSE; | |
| 495 } | |
| 496 | |
| 497 if (grab_input_) { | |
| 498 // Otherwise we had a click outside of our window, close ourself. | |
| 499 Close(); | |
| 500 return TRUE; | |
| 501 } | |
| 502 | |
| 503 return FALSE; | |
| 504 } | |
| 505 | |
| 506 gboolean InfoBubbleGtk::OnDestroy(GtkWidget* widget) { | |
| 507 // We are self deleting, we have a destroy signal setup to catch when we | |
| 508 // destroy the widget manually, or the window was closed via X. This will | |
| 509 // delete the InfoBubbleGtk object. | |
| 510 delete this; | |
| 511 return FALSE; // Propagate. | |
| 512 } | |
| 513 | |
| 514 void InfoBubbleGtk::OnHide(GtkWidget* widget) { | |
| 515 gtk_widget_destroy(widget); | |
| 516 } | |
| 517 | |
| 518 gboolean InfoBubbleGtk::OnToplevelConfigure(GtkWidget* widget, | |
| 519 GdkEventConfigure* event) { | |
| 520 if (!UpdateArrowLocation(false)) | |
| 521 MoveWindow(); | |
| 522 StackWindow(); | |
| 523 return FALSE; | |
| 524 } | |
| 525 | |
| 526 gboolean InfoBubbleGtk::OnToplevelUnmap(GtkWidget* widget, GdkEvent* event) { | |
| 527 Close(); | |
| 528 return FALSE; | |
| 529 } | |
| 530 | |
| 531 void InfoBubbleGtk::OnAnchorAllocate(GtkWidget* widget, | |
| 532 GtkAllocation* allocation) { | |
| 533 if (!UpdateArrowLocation(false)) | |
| 534 MoveWindow(); | |
| 535 } | |
| OLD | NEW |