| OLD | NEW |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/gtk/info_bubble_gtk.h" | 5 #include "chrome/browser/gtk/info_bubble_gtk.h" |
| 6 | 6 |
| 7 #include <gdk/gdkkeysyms.h> | 7 #include <gdk/gdkkeysyms.h> |
| 8 #include <gtk/gtk.h> | 8 #include <gtk/gtk.h> |
| 9 | 9 |
| 10 #include "app/l10n_util.h" | 10 #include "app/l10n_util.h" |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 42 const GdkColor kFrameColor = GDK_COLOR_RGB(0x63, 0x63, 0x63); | 42 const GdkColor kFrameColor = GDK_COLOR_RGB(0x63, 0x63, 0x63); |
| 43 | 43 |
| 44 } // namespace | 44 } // namespace |
| 45 | 45 |
| 46 // static | 46 // static |
| 47 InfoBubbleGtk* InfoBubbleGtk::Show(GtkWindow* toplevel_window, | 47 InfoBubbleGtk* InfoBubbleGtk::Show(GtkWindow* toplevel_window, |
| 48 const gfx::Rect& rect, | 48 const gfx::Rect& rect, |
| 49 GtkWidget* content, | 49 GtkWidget* content, |
| 50 ArrowLocationGtk arrow_location, | 50 ArrowLocationGtk arrow_location, |
| 51 bool match_system_theme, | 51 bool match_system_theme, |
| 52 bool grab_input, |
| 52 GtkThemeProvider* provider, | 53 GtkThemeProvider* provider, |
| 53 InfoBubbleGtkDelegate* delegate) { | 54 InfoBubbleGtkDelegate* delegate) { |
| 54 InfoBubbleGtk* bubble = new InfoBubbleGtk(provider, match_system_theme); | 55 InfoBubbleGtk* bubble = new InfoBubbleGtk(provider, match_system_theme); |
| 55 bubble->Init(toplevel_window, rect, content, arrow_location); | 56 bubble->Init(toplevel_window, rect, content, arrow_location, grab_input); |
| 56 bubble->set_delegate(delegate); | 57 bubble->set_delegate(delegate); |
| 57 return bubble; | 58 return bubble; |
| 58 } | 59 } |
| 59 | 60 |
| 60 InfoBubbleGtk::InfoBubbleGtk(GtkThemeProvider* provider, | 61 InfoBubbleGtk::InfoBubbleGtk(GtkThemeProvider* provider, |
| 61 bool match_system_theme) | 62 bool match_system_theme) |
| 62 : delegate_(NULL), | 63 : delegate_(NULL), |
| 63 window_(NULL), | 64 window_(NULL), |
| 64 theme_provider_(provider), | 65 theme_provider_(provider), |
| 65 accel_group_(gtk_accel_group_new()), | 66 accel_group_(gtk_accel_group_new()), |
| 66 toplevel_window_(NULL), | 67 toplevel_window_(NULL), |
| 67 mask_region_(NULL), | 68 mask_region_(NULL), |
| 68 preferred_arrow_location_(ARROW_LOCATION_TOP_LEFT), | 69 preferred_arrow_location_(ARROW_LOCATION_TOP_LEFT), |
| 69 current_arrow_location_(ARROW_LOCATION_TOP_LEFT), | 70 current_arrow_location_(ARROW_LOCATION_TOP_LEFT), |
| 70 match_system_theme_(match_system_theme) { | 71 match_system_theme_(match_system_theme), |
| 72 grab_input_(true), |
| 73 closed_by_escape_(false) { |
| 71 } | 74 } |
| 72 | 75 |
| 73 InfoBubbleGtk::~InfoBubbleGtk() { | 76 InfoBubbleGtk::~InfoBubbleGtk() { |
| 77 // Notify the delegate that we're about to close. This gives the chance |
| 78 // to save state / etc from the hosted widget before it's destroyed. |
| 79 if (delegate_) |
| 80 delegate_->InfoBubbleClosing(this, closed_by_escape_); |
| 81 |
| 74 g_object_unref(accel_group_); | 82 g_object_unref(accel_group_); |
| 75 if (mask_region_) { | 83 if (mask_region_) { |
| 76 gdk_region_destroy(mask_region_); | 84 gdk_region_destroy(mask_region_); |
| 77 mask_region_ = NULL; | 85 mask_region_ = NULL; |
| 78 } | 86 } |
| 79 | 87 |
| 80 g_signal_handlers_disconnect_by_func( | 88 if (toplevel_window_) { |
| 81 toplevel_window_, | 89 g_signal_handlers_disconnect_by_func( |
| 82 reinterpret_cast<gpointer>(HandleToplevelConfigureThunk), | 90 toplevel_window_, |
| 83 this); | 91 reinterpret_cast<gpointer>(HandleToplevelConfigureThunk), |
| 84 g_signal_handlers_disconnect_by_func( | 92 this); |
| 85 toplevel_window_, | 93 g_signal_handlers_disconnect_by_func( |
| 86 reinterpret_cast<gpointer>(HandleToplevelUnmapThunk), | 94 toplevel_window_, |
| 87 this); | 95 reinterpret_cast<gpointer>(HandleToplevelUnmapThunk), |
| 96 this); |
| 97 } |
| 88 toplevel_window_ = NULL; | 98 toplevel_window_ = NULL; |
| 89 } | 99 } |
| 90 | 100 |
| 91 void InfoBubbleGtk::Init(GtkWindow* toplevel_window, | 101 void InfoBubbleGtk::Init(GtkWindow* toplevel_window, |
| 92 const gfx::Rect& rect, | 102 const gfx::Rect& rect, |
| 93 GtkWidget* content, | 103 GtkWidget* content, |
| 94 ArrowLocationGtk arrow_location) { | 104 ArrowLocationGtk arrow_location, |
| 105 bool grab_input) { |
| 95 DCHECK(!window_); | 106 DCHECK(!window_); |
| 96 toplevel_window_ = toplevel_window; | 107 toplevel_window_ = toplevel_window; |
| 97 rect_ = rect; | 108 rect_ = rect; |
| 98 preferred_arrow_location_ = arrow_location; | 109 preferred_arrow_location_ = arrow_location; |
| 99 | 110 |
| 100 window_ = gtk_window_new(GTK_WINDOW_POPUP); | 111 grab_input_ = grab_input; |
| 112 // Using a TOPLEVEL window may cause placement issues with certain WMs but it |
| 113 // is necessary to be able to focus the window. |
| 114 window_ = gtk_window_new(grab_input ? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL); |
| 115 |
| 101 gtk_widget_set_app_paintable(window_, TRUE); | 116 gtk_widget_set_app_paintable(window_, TRUE); |
| 102 // Resizing is handled by the program, not user. | 117 // Resizing is handled by the program, not user. |
| 103 gtk_window_set_resizable(GTK_WINDOW(window_), FALSE); | 118 gtk_window_set_resizable(GTK_WINDOW(window_), FALSE); |
| 104 | 119 |
| 105 // Attach our accelerator group to the window with an escape accelerator. | 120 // Attach our accelerator group to the window with an escape accelerator. |
| 106 gtk_accel_group_connect(accel_group_, GDK_Escape, | 121 gtk_accel_group_connect(accel_group_, GDK_Escape, |
| 107 static_cast<GdkModifierType>(0), static_cast<GtkAccelFlags>(0), | 122 static_cast<GdkModifierType>(0), static_cast<GtkAccelFlags>(0), |
| 108 g_cclosure_new(G_CALLBACK(&HandleEscapeThunk), this, NULL)); | 123 g_cclosure_new(G_CALLBACK(&HandleEscapeThunk), this, NULL)); |
| 109 gtk_window_add_accel_group(GTK_WINDOW(window_), accel_group_); | 124 gtk_window_add_accel_group(GTK_WINDOW(window_), accel_group_); |
| 110 | 125 |
| (...skipping 22 matching lines...) Expand all Loading... |
| 133 G_CALLBACK(HandleSizeAllocateThunk), this); | 148 G_CALLBACK(HandleSizeAllocateThunk), this); |
| 134 g_signal_connect(window_, "button-press-event", | 149 g_signal_connect(window_, "button-press-event", |
| 135 G_CALLBACK(&HandleButtonPressThunk), this); | 150 G_CALLBACK(&HandleButtonPressThunk), this); |
| 136 g_signal_connect(window_, "destroy", | 151 g_signal_connect(window_, "destroy", |
| 137 G_CALLBACK(&HandleDestroyThunk), this); | 152 G_CALLBACK(&HandleDestroyThunk), this); |
| 138 | 153 |
| 139 g_signal_connect(toplevel_window, "configure-event", | 154 g_signal_connect(toplevel_window, "configure-event", |
| 140 G_CALLBACK(&HandleToplevelConfigureThunk), this); | 155 G_CALLBACK(&HandleToplevelConfigureThunk), this); |
| 141 g_signal_connect(toplevel_window, "unmap-event", | 156 g_signal_connect(toplevel_window, "unmap-event", |
| 142 G_CALLBACK(&HandleToplevelUnmapThunk), this); | 157 G_CALLBACK(&HandleToplevelUnmapThunk), this); |
| 158 // Set |toplevel_window_| to NULL if it gets destroyed. |
| 159 g_signal_connect(toplevel_window, "destroy", |
| 160 G_CALLBACK(gtk_widget_destroyed), &toplevel_window_); |
| 143 | 161 |
| 144 gtk_widget_show_all(window_); | 162 gtk_widget_show_all(window_); |
| 145 | 163 |
| 146 // We add a GTK (application-level) grab. This means we will get all | 164 if (grab_input_) { |
| 147 // mouse events for our application, even if they were delivered on another | 165 // We add a GTK (application-level) grab. This means we will get all |
| 148 // window. We don't need this to get button presses outside of the bubble's | 166 // mouse events for our application, even if they were delivered on another |
| 149 // window so we'll know to close it (the pointer grab takes care of that), but | 167 // window. We don't need this to get button presses outside of the bubble's |
| 150 // it prevents other widgets from getting highlighted when the pointer moves | 168 // window so we'll know to close it (the pointer grab takes care of that), |
| 151 // over them. | 169 // but it prevents other widgets from getting highlighted when the pointer |
| 152 // | 170 // moves over them. |
| 153 // (Ideally we wouldn't add the window to a group and it would just get all | 171 // |
| 154 // the mouse events, but gtk_grab_add() doesn't appear to do anything in that | 172 // (Ideally we wouldn't add the window to a group and it would just get all |
| 155 // case. Adding it to the toplevel window's group first appears to block | 173 // the mouse events, but gtk_grab_add() doesn't appear to do anything in |
| 156 // enter/leave events for that window and its subwindows, although other | 174 // that case. Adding it to the toplevel window's group first appears to |
| 157 // browser windows still receive them). | 175 // block enter/leave events for that window and its subwindows, although |
| 158 gtk_window_group_add_window(gtk_window_get_group(toplevel_window), | 176 // other browser windows still receive them). |
| 159 GTK_WINDOW(window_)); | 177 gtk_window_group_add_window(gtk_window_get_group(toplevel_window), |
| 160 gtk_grab_add(window_); | 178 GTK_WINDOW(window_)); |
| 179 gtk_grab_add(window_); |
| 161 | 180 |
| 162 GrabPointerAndKeyboard(); | 181 GrabPointerAndKeyboard(); |
| 182 } |
| 163 | 183 |
| 164 registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, | 184 registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, |
| 165 NotificationService::AllSources()); | 185 NotificationService::AllSources()); |
| 166 theme_provider_->InitThemesFor(this); | 186 theme_provider_->InitThemesFor(this); |
| 167 } | 187 } |
| 168 | 188 |
| 169 // NOTE: This seems a bit overcomplicated, but it requires a bunch of careful | 189 // NOTE: This seems a bit overcomplicated, but it requires a bunch of careful |
| 170 // fudging to get the pixels rasterized exactly where we want them, the arrow to | 190 // fudging to get the pixels rasterized exactly where we want them, the arrow to |
| 171 // have a 1 pixel point, etc. | 191 // have a 1 pixel point, etc. |
| 172 // TODO(deanm): Windows draws with Skia and uses some PNG images for the | 192 // TODO(deanm): Windows draws with Skia and uses some PNG images for the |
| (...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 314 DCHECK_EQ(type.value, NotificationType::BROWSER_THEME_CHANGED); | 334 DCHECK_EQ(type.value, NotificationType::BROWSER_THEME_CHANGED); |
| 315 if (theme_provider_->UseGtkTheme() && match_system_theme_) { | 335 if (theme_provider_->UseGtkTheme() && match_system_theme_) { |
| 316 gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, NULL); | 336 gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, NULL); |
| 317 } else { | 337 } else { |
| 318 // Set the background color, so we don't need to paint it manually. | 338 // Set the background color, so we don't need to paint it manually. |
| 319 gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, &kBackgroundColor); | 339 gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, &kBackgroundColor); |
| 320 } | 340 } |
| 321 } | 341 } |
| 322 | 342 |
| 323 void InfoBubbleGtk::HandlePointerAndKeyboardUngrabbedByContent() { | 343 void InfoBubbleGtk::HandlePointerAndKeyboardUngrabbedByContent() { |
| 324 GrabPointerAndKeyboard(); | 344 if (grab_input_) |
| 345 GrabPointerAndKeyboard(); |
| 325 } | 346 } |
| 326 | 347 |
| 327 void InfoBubbleGtk::CloseInternal(bool closed_by_escape) { | 348 void InfoBubbleGtk::Close() { |
| 328 // Notify the delegate that we're about to close. This gives the chance | |
| 329 // to save state / etc from the hosted widget before it's destroyed. | |
| 330 if (delegate_) | |
| 331 delegate_->InfoBubbleClosing(this, closed_by_escape); | |
| 332 | |
| 333 // We don't need to ungrab the pointer or keyboard here; the X server will | 349 // We don't need to ungrab the pointer or keyboard here; the X server will |
| 334 // automatically do that when we destroy our window. | 350 // automatically do that when we destroy our window. |
| 335 | |
| 336 DCHECK(window_); | 351 DCHECK(window_); |
| 337 gtk_widget_destroy(window_); | 352 gtk_widget_destroy(window_); |
| 338 // |this| has been deleted, see HandleDestroy. | 353 // |this| has been deleted, see HandleDestroy. |
| 339 } | 354 } |
| 340 | 355 |
| 341 void InfoBubbleGtk::GrabPointerAndKeyboard() { | 356 void InfoBubbleGtk::GrabPointerAndKeyboard() { |
| 342 // Install X pointer and keyboard grabs to make sure that we have the focus | 357 // Install X pointer and keyboard grabs to make sure that we have the focus |
| 343 // and get all mouse and keyboard events until we're closed. | 358 // and get all mouse and keyboard events until we're closed. |
| 344 GdkGrabStatus pointer_grab_status = | 359 GdkGrabStatus pointer_grab_status = |
| 345 gdk_pointer_grab(window_->window, | 360 gdk_pointer_grab(window_->window, |
| (...skipping 12 matching lines...) Expand all Loading... |
| 358 gdk_keyboard_grab(window_->window, | 373 gdk_keyboard_grab(window_->window, |
| 359 FALSE, // owner_events | 374 FALSE, // owner_events |
| 360 GDK_CURRENT_TIME); | 375 GDK_CURRENT_TIME); |
| 361 if (keyboard_grab_status != GDK_GRAB_SUCCESS) { | 376 if (keyboard_grab_status != GDK_GRAB_SUCCESS) { |
| 362 DLOG(ERROR) << "Unable to grab keyboard (status=" | 377 DLOG(ERROR) << "Unable to grab keyboard (status=" |
| 363 << keyboard_grab_status << ")"; | 378 << keyboard_grab_status << ")"; |
| 364 } | 379 } |
| 365 } | 380 } |
| 366 | 381 |
| 367 gboolean InfoBubbleGtk::HandleEscape() { | 382 gboolean InfoBubbleGtk::HandleEscape() { |
| 368 CloseInternal(true); // Close by escape. | 383 closed_by_escape_ = true; |
| 384 Close(); |
| 369 return TRUE; | 385 return TRUE; |
| 370 } | 386 } |
| 371 | 387 |
| 372 gboolean InfoBubbleGtk::HandleExpose() { | 388 gboolean InfoBubbleGtk::HandleExpose() { |
| 373 GdkDrawable* drawable = GDK_DRAWABLE(window_->window); | 389 GdkDrawable* drawable = GDK_DRAWABLE(window_->window); |
| 374 GdkGC* gc = gdk_gc_new(drawable); | 390 GdkGC* gc = gdk_gc_new(drawable); |
| 375 gdk_gc_set_rgb_fg_color(gc, &kFrameColor); | 391 gdk_gc_set_rgb_fg_color(gc, &kFrameColor); |
| 376 | 392 |
| 377 // Stroke the frame border. | 393 // Stroke the frame border. |
| 378 std::vector<GdkPoint> points = MakeFramePolygonPoints( | 394 std::vector<GdkPoint> points = MakeFramePolygonPoints( |
| (...skipping 25 matching lines...) Expand all Loading... |
| 404 (mask_region_ && gdk_region_point_in(mask_region_, event->x, event->y))) { | 420 (mask_region_ && gdk_region_point_in(mask_region_, event->x, event->y))) { |
| 405 return FALSE; // Propagate. | 421 return FALSE; // Propagate. |
| 406 } | 422 } |
| 407 | 423 |
| 408 // Our content widget got a click. | 424 // Our content widget got a click. |
| 409 if (event->window != window_->window && | 425 if (event->window != window_->window && |
| 410 gdk_window_get_toplevel(event->window) == window_->window) { | 426 gdk_window_get_toplevel(event->window) == window_->window) { |
| 411 return FALSE; | 427 return FALSE; |
| 412 } | 428 } |
| 413 | 429 |
| 414 // Otherwise we had a click outside of our window, close ourself. | 430 if (grab_input_) { |
| 415 Close(); | 431 // Otherwise we had a click outside of our window, close ourself. |
| 416 return TRUE; | 432 Close(); |
| 433 return TRUE; |
| 434 } |
| 435 |
| 436 return FALSE; |
| 417 } | 437 } |
| 418 | 438 |
| 419 gboolean InfoBubbleGtk::HandleDestroy() { | 439 gboolean InfoBubbleGtk::HandleDestroy() { |
| 420 // We are self deleting, we have a destroy signal setup to catch when we | 440 // We are self deleting, we have a destroy signal setup to catch when we |
| 421 // destroy the widget manually, or the window was closed via X. This will | 441 // destroy the widget manually, or the window was closed via X. This will |
| 422 // delete the InfoBubbleGtk object. | 442 // delete the InfoBubbleGtk object. |
| 423 delete this; | 443 delete this; |
| 424 return FALSE; // Propagate. | 444 return FALSE; // Propagate. |
| 425 } | 445 } |
| 426 | 446 |
| 427 gboolean InfoBubbleGtk::HandleToplevelConfigure(GdkEventConfigure* event) { | 447 gboolean InfoBubbleGtk::HandleToplevelConfigure(GdkEventConfigure* event) { |
| 428 if (!UpdateArrowLocation(false)) | 448 if (!UpdateArrowLocation(false)) |
| 429 MoveWindow(); | 449 MoveWindow(); |
| 430 StackWindow(); | 450 StackWindow(); |
| 431 return FALSE; | 451 return FALSE; |
| 432 } | 452 } |
| 433 | 453 |
| 434 gboolean InfoBubbleGtk::HandleToplevelUnmap() { | 454 gboolean InfoBubbleGtk::HandleToplevelUnmap() { |
| 435 Close(); | 455 Close(); |
| 436 return FALSE; | 456 return FALSE; |
| 437 } | 457 } |
| OLD | NEW |