Index: chrome/browser/gtk/info_bubble_gtk.cc |
=================================================================== |
--- chrome/browser/gtk/info_bubble_gtk.cc (revision 71352) |
+++ chrome/browser/gtk/info_bubble_gtk.cc (working copy) |
@@ -1,535 +0,0 @@ |
-// Copyright (c) 2010 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-#include "chrome/browser/gtk/info_bubble_gtk.h" |
- |
-#include <gdk/gdkkeysyms.h> |
-#include <vector> |
- |
-#include "base/basictypes.h" |
-#include "base/logging.h" |
-#include "chrome/browser/gtk/gtk_theme_provider.h" |
-#include "chrome/browser/gtk/gtk_util.h" |
-#include "chrome/browser/gtk/info_bubble_accelerators_gtk.h" |
-#include "chrome/common/notification_service.h" |
-#include "gfx/gtk_util.h" |
-#include "gfx/path.h" |
-#include "gfx/rect.h" |
- |
-namespace { |
- |
-// The height of the arrow, and the width will be about twice the height. |
-const int kArrowSize = 8; |
- |
-// Number of pixels to the middle of the arrow from the close edge of the |
-// window. |
-const int kArrowX = 18; |
- |
-// Number of pixels between the tip of the arrow and the region we're |
-// pointing to. |
-const int kArrowToContentPadding = -4; |
- |
-// We draw flat diagonal corners, each corner is an NxN square. |
-const int kCornerSize = 3; |
- |
-// Margins around the content. |
-const int kTopMargin = kArrowSize + kCornerSize - 1; |
-const int kBottomMargin = kCornerSize - 1; |
-const int kLeftMargin = kCornerSize - 1; |
-const int kRightMargin = kCornerSize - 1; |
- |
-const GdkColor kBackgroundColor = GDK_COLOR_RGB(0xff, 0xff, 0xff); |
-const GdkColor kFrameColor = GDK_COLOR_RGB(0x63, 0x63, 0x63); |
- |
-} // namespace |
- |
-// static |
-InfoBubbleGtk* InfoBubbleGtk::Show(GtkWidget* anchor_widget, |
- const gfx::Rect* rect, |
- GtkWidget* content, |
- ArrowLocationGtk arrow_location, |
- bool match_system_theme, |
- bool grab_input, |
- GtkThemeProvider* provider, |
- InfoBubbleGtkDelegate* delegate) { |
- InfoBubbleGtk* bubble = new InfoBubbleGtk(provider, match_system_theme); |
- bubble->Init(anchor_widget, rect, content, arrow_location, grab_input); |
- bubble->set_delegate(delegate); |
- return bubble; |
-} |
- |
-InfoBubbleGtk::InfoBubbleGtk(GtkThemeProvider* provider, |
- bool match_system_theme) |
- : delegate_(NULL), |
- window_(NULL), |
- theme_provider_(provider), |
- accel_group_(gtk_accel_group_new()), |
- toplevel_window_(NULL), |
- anchor_widget_(NULL), |
- mask_region_(NULL), |
- preferred_arrow_location_(ARROW_LOCATION_TOP_LEFT), |
- current_arrow_location_(ARROW_LOCATION_TOP_LEFT), |
- match_system_theme_(match_system_theme), |
- grab_input_(true), |
- closed_by_escape_(false) { |
-} |
- |
-InfoBubbleGtk::~InfoBubbleGtk() { |
- // Notify the delegate that we're about to close. This gives the chance |
- // to save state / etc from the hosted widget before it's destroyed. |
- if (delegate_) |
- delegate_->InfoBubbleClosing(this, closed_by_escape_); |
- |
- g_object_unref(accel_group_); |
- if (mask_region_) |
- gdk_region_destroy(mask_region_); |
-} |
- |
-void InfoBubbleGtk::Init(GtkWidget* anchor_widget, |
- const gfx::Rect* rect, |
- GtkWidget* content, |
- ArrowLocationGtk arrow_location, |
- bool grab_input) { |
- // If there is a current grab widget (menu, other info bubble, etc.), hide it. |
- GtkWidget* current_grab_widget = gtk_grab_get_current(); |
- if (current_grab_widget) |
- gtk_widget_hide(current_grab_widget); |
- |
- DCHECK(!window_); |
- anchor_widget_ = anchor_widget; |
- toplevel_window_ = GTK_WINDOW(gtk_widget_get_toplevel(anchor_widget_)); |
- DCHECK(GTK_WIDGET_TOPLEVEL(toplevel_window_)); |
- rect_ = rect ? *rect : gtk_util::WidgetBounds(anchor_widget); |
- preferred_arrow_location_ = arrow_location; |
- |
- grab_input_ = grab_input; |
- // Using a TOPLEVEL window may cause placement issues with certain WMs but it |
- // is necessary to be able to focus the window. |
- window_ = gtk_window_new(grab_input ? GTK_WINDOW_POPUP : GTK_WINDOW_TOPLEVEL); |
- |
- gtk_widget_set_app_paintable(window_, TRUE); |
- // Resizing is handled by the program, not user. |
- gtk_window_set_resizable(GTK_WINDOW(window_), FALSE); |
- |
- // Attach all of the accelerators to the bubble. |
- InfoBubbleAcceleratorGtkList acceleratorList = |
- InfoBubbleAcceleratorsGtk::GetList(); |
- for (InfoBubbleAcceleratorGtkList::const_iterator iter = |
- acceleratorList.begin(); |
- iter != acceleratorList.end(); |
- ++iter) { |
- gtk_accel_group_connect(accel_group_, |
- iter->keyval, |
- iter->modifier_type, |
- GtkAccelFlags(0), |
- g_cclosure_new(G_CALLBACK(&OnGtkAcceleratorThunk), |
- this, |
- NULL)); |
- } |
- |
- gtk_window_add_accel_group(GTK_WINDOW(window_), accel_group_); |
- |
- GtkWidget* alignment = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); |
- gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), |
- kTopMargin, kBottomMargin, |
- kLeftMargin, kRightMargin); |
- |
- gtk_container_add(GTK_CONTAINER(alignment), content); |
- gtk_container_add(GTK_CONTAINER(window_), alignment); |
- |
- // GtkWidget only exposes the bitmap mask interface. Use GDK to more |
- // efficently mask a GdkRegion. Make sure the window is realized during |
- // OnSizeAllocate, so the mask can be applied to the GdkWindow. |
- gtk_widget_realize(window_); |
- |
- UpdateArrowLocation(true); // Force move and reshape. |
- StackWindow(); |
- |
- gtk_widget_add_events(window_, GDK_BUTTON_PRESS_MASK); |
- |
- signals_.Connect(window_, "expose-event", G_CALLBACK(OnExposeThunk), this); |
- signals_.Connect(window_, "size-allocate", G_CALLBACK(OnSizeAllocateThunk), |
- this); |
- signals_.Connect(window_, "button-press-event", |
- G_CALLBACK(OnButtonPressThunk), this); |
- signals_.Connect(window_, "destroy", G_CALLBACK(OnDestroyThunk), this); |
- signals_.Connect(window_, "hide", G_CALLBACK(OnHideThunk), this); |
- |
- // If the toplevel window is being used as the anchor, then the signals below |
- // are enough to keep us positioned correctly. |
- if (anchor_widget_ != GTK_WIDGET(toplevel_window_)) { |
- signals_.Connect(anchor_widget_, "size-allocate", |
- G_CALLBACK(OnAnchorAllocateThunk), this); |
- signals_.Connect(anchor_widget_, "destroy", |
- G_CALLBACK(gtk_widget_destroyed), &anchor_widget_); |
- } |
- |
- signals_.Connect(toplevel_window_, "configure-event", |
- G_CALLBACK(OnToplevelConfigureThunk), this); |
- signals_.Connect(toplevel_window_, "unmap-event", |
- G_CALLBACK(OnToplevelUnmapThunk), this); |
- // Set |toplevel_window_| to NULL if it gets destroyed. |
- signals_.Connect(toplevel_window_, "destroy", |
- G_CALLBACK(gtk_widget_destroyed), &toplevel_window_); |
- |
- gtk_widget_show_all(window_); |
- |
- if (grab_input_) { |
- gtk_grab_add(window_); |
- GrabPointerAndKeyboard(); |
- } |
- |
- registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED, |
- NotificationService::AllSources()); |
- theme_provider_->InitThemesFor(this); |
-} |
- |
-// NOTE: This seems a bit overcomplicated, but it requires a bunch of careful |
-// fudging to get the pixels rasterized exactly where we want them, the arrow to |
-// have a 1 pixel point, etc. |
-// TODO(deanm): Windows draws with Skia and uses some PNG images for the |
-// corners. This is a lot more work, but they get anti-aliasing. |
-// static |
-std::vector<GdkPoint> InfoBubbleGtk::MakeFramePolygonPoints( |
- ArrowLocationGtk arrow_location, |
- int width, |
- int height, |
- FrameType type) { |
- using gtk_util::MakeBidiGdkPoint; |
- std::vector<GdkPoint> points; |
- |
- bool on_left = (arrow_location == ARROW_LOCATION_TOP_LEFT); |
- |
- // If we're stroking the frame, we need to offset some of our points by 1 |
- // pixel. We do this when we draw horizontal lines that are on the bottom or |
- // when we draw vertical lines that are closer to the end (where "end" is the |
- // right side for ARROW_LOCATION_TOP_LEFT). |
- int y_off = (type == FRAME_MASK) ? 0 : -1; |
- // We use this one for arrows located on the left. |
- int x_off_l = on_left ? y_off : 0; |
- // We use this one for RTL. |
- int x_off_r = !on_left ? -y_off : 0; |
- |
- // Top left corner. |
- points.push_back(MakeBidiGdkPoint( |
- x_off_r, kArrowSize + kCornerSize - 1, width, on_left)); |
- points.push_back(MakeBidiGdkPoint( |
- kCornerSize + x_off_r - 1, kArrowSize, width, on_left)); |
- |
- // The arrow. |
- points.push_back(MakeBidiGdkPoint( |
- kArrowX - kArrowSize + x_off_r, kArrowSize, width, on_left)); |
- points.push_back(MakeBidiGdkPoint( |
- kArrowX + x_off_r, 0, width, on_left)); |
- points.push_back(MakeBidiGdkPoint( |
- kArrowX + 1 + x_off_l, 0, width, on_left)); |
- points.push_back(MakeBidiGdkPoint( |
- kArrowX + kArrowSize + 1 + x_off_l, kArrowSize, width, on_left)); |
- |
- // Top right corner. |
- points.push_back(MakeBidiGdkPoint( |
- width - kCornerSize + 1 + x_off_l, kArrowSize, width, on_left)); |
- points.push_back(MakeBidiGdkPoint( |
- width + x_off_l, kArrowSize + kCornerSize - 1, width, on_left)); |
- |
- // Bottom right corner. |
- points.push_back(MakeBidiGdkPoint( |
- width + x_off_l, height - kCornerSize, width, on_left)); |
- points.push_back(MakeBidiGdkPoint( |
- width - kCornerSize + x_off_r, height + y_off, width, on_left)); |
- |
- // Bottom left corner. |
- points.push_back(MakeBidiGdkPoint( |
- kCornerSize + x_off_l, height + y_off, width, on_left)); |
- points.push_back(MakeBidiGdkPoint( |
- x_off_r, height - kCornerSize, width, on_left)); |
- |
- return points; |
-} |
- |
-InfoBubbleGtk::ArrowLocationGtk InfoBubbleGtk::GetArrowLocation( |
- ArrowLocationGtk preferred_location, int arrow_x, int width) { |
- bool wants_left = (preferred_location == ARROW_LOCATION_TOP_LEFT); |
- int screen_width = gdk_screen_get_width(gdk_screen_get_default()); |
- |
- bool left_is_onscreen = (arrow_x - kArrowX + width < screen_width); |
- bool right_is_onscreen = (arrow_x + kArrowX - width >= 0); |
- |
- // Use the requested location if it fits onscreen, use whatever fits |
- // otherwise, and use the requested location if neither fits. |
- if (left_is_onscreen && (wants_left || !right_is_onscreen)) |
- return ARROW_LOCATION_TOP_LEFT; |
- if (right_is_onscreen && (!wants_left || !left_is_onscreen)) |
- return ARROW_LOCATION_TOP_RIGHT; |
- return (wants_left ? ARROW_LOCATION_TOP_LEFT : ARROW_LOCATION_TOP_RIGHT); |
-} |
- |
-bool InfoBubbleGtk::UpdateArrowLocation(bool force_move_and_reshape) { |
- if (!toplevel_window_ || !anchor_widget_) |
- return false; |
- |
- gint toplevel_x = 0, toplevel_y = 0; |
- gdk_window_get_position( |
- GTK_WIDGET(toplevel_window_)->window, &toplevel_x, &toplevel_y); |
- int offset_x, offset_y; |
- gtk_widget_translate_coordinates(anchor_widget_, GTK_WIDGET(toplevel_window_), |
- rect_.x(), rect_.y(), &offset_x, &offset_y); |
- |
- ArrowLocationGtk old_location = current_arrow_location_; |
- current_arrow_location_ = GetArrowLocation( |
- preferred_arrow_location_, |
- toplevel_x + offset_x + (rect_.width() / 2), // arrow_x |
- window_->allocation.width); |
- |
- if (force_move_and_reshape || current_arrow_location_ != old_location) { |
- UpdateWindowShape(); |
- MoveWindow(); |
- // We need to redraw the entire window to repaint its border. |
- gtk_widget_queue_draw(window_); |
- return true; |
- } |
- return false; |
-} |
- |
-void InfoBubbleGtk::UpdateWindowShape() { |
- if (mask_region_) { |
- gdk_region_destroy(mask_region_); |
- mask_region_ = NULL; |
- } |
- std::vector<GdkPoint> points = MakeFramePolygonPoints( |
- current_arrow_location_, |
- window_->allocation.width, window_->allocation.height, |
- FRAME_MASK); |
- mask_region_ = gdk_region_polygon(&points[0], |
- points.size(), |
- GDK_EVEN_ODD_RULE); |
- gdk_window_shape_combine_region(window_->window, NULL, 0, 0); |
- gdk_window_shape_combine_region(window_->window, mask_region_, 0, 0); |
-} |
- |
-void InfoBubbleGtk::MoveWindow() { |
- if (!toplevel_window_ || !anchor_widget_) |
- return; |
- |
- gint toplevel_x = 0, toplevel_y = 0; |
- gdk_window_get_position( |
- GTK_WIDGET(toplevel_window_)->window, &toplevel_x, &toplevel_y); |
- |
- int offset_x, offset_y; |
- gtk_widget_translate_coordinates(anchor_widget_, GTK_WIDGET(toplevel_window_), |
- rect_.x(), rect_.y(), &offset_x, &offset_y); |
- |
- gint screen_x = 0; |
- if (current_arrow_location_ == ARROW_LOCATION_TOP_LEFT) { |
- screen_x = toplevel_x + offset_x + (rect_.width() / 2) - kArrowX; |
- } else if (current_arrow_location_ == ARROW_LOCATION_TOP_RIGHT) { |
- screen_x = toplevel_x + offset_x + (rect_.width() / 2) - |
- window_->allocation.width + kArrowX; |
- } else { |
- NOTREACHED(); |
- } |
- |
- gint screen_y = toplevel_y + offset_y + rect_.height() + |
- kArrowToContentPadding; |
- |
- gtk_window_move(GTK_WINDOW(window_), screen_x, screen_y); |
-} |
- |
-void InfoBubbleGtk::StackWindow() { |
- // Stack our window directly above the toplevel window. |
- if (toplevel_window_) |
- gtk_util::StackPopupWindow(window_, GTK_WIDGET(toplevel_window_)); |
-} |
- |
-void InfoBubbleGtk::Observe(NotificationType type, |
- const NotificationSource& source, |
- const NotificationDetails& details) { |
- DCHECK_EQ(type.value, NotificationType::BROWSER_THEME_CHANGED); |
- if (theme_provider_->UseGtkTheme() && match_system_theme_) { |
- gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, NULL); |
- } else { |
- // Set the background color, so we don't need to paint it manually. |
- gtk_widget_modify_bg(window_, GTK_STATE_NORMAL, &kBackgroundColor); |
- } |
-} |
- |
-void InfoBubbleGtk::HandlePointerAndKeyboardUngrabbedByContent() { |
- if (grab_input_) |
- GrabPointerAndKeyboard(); |
-} |
- |
-void InfoBubbleGtk::Close() { |
- // We don't need to ungrab the pointer or keyboard here; the X server will |
- // automatically do that when we destroy our window. |
- DCHECK(window_); |
- gtk_widget_destroy(window_); |
- // |this| has been deleted, see OnDestroy. |
-} |
- |
-void InfoBubbleGtk::GrabPointerAndKeyboard() { |
- // Install X pointer and keyboard grabs to make sure that we have the focus |
- // and get all mouse and keyboard events until we're closed. |
- GdkGrabStatus pointer_grab_status = |
- gdk_pointer_grab(window_->window, |
- TRUE, // owner_events |
- GDK_BUTTON_PRESS_MASK, // event_mask |
- NULL, // confine_to |
- NULL, // cursor |
- GDK_CURRENT_TIME); |
- if (pointer_grab_status != GDK_GRAB_SUCCESS) { |
- // This will fail if someone else already has the pointer grabbed, but |
- // there's not really anything we can do about that. |
- DLOG(ERROR) << "Unable to grab pointer (status=" |
- << pointer_grab_status << ")"; |
- } |
- GdkGrabStatus keyboard_grab_status = |
- gdk_keyboard_grab(window_->window, |
- FALSE, // owner_events |
- GDK_CURRENT_TIME); |
- if (keyboard_grab_status != GDK_GRAB_SUCCESS) { |
- DLOG(ERROR) << "Unable to grab keyboard (status=" |
- << keyboard_grab_status << ")"; |
- } |
-} |
- |
-gboolean InfoBubbleGtk::OnGtkAccelerator(GtkAccelGroup* group, |
- GObject* acceleratable, |
- guint keyval, |
- GdkModifierType modifier) { |
- GdkEventKey msg; |
- GdkKeymapKey* keys; |
- gint n_keys; |
- |
- switch (keyval) { |
- case GDK_Escape: |
- // Close on Esc and trap the accelerator |
- closed_by_escape_ = true; |
- Close(); |
- return TRUE; |
- case GDK_w: |
- // Close on C-w and forward the accelerator |
- if (modifier & GDK_CONTROL_MASK) { |
- Close(); |
- } |
- break; |
- default: |
- return FALSE; |
- } |
- |
- gdk_keymap_get_entries_for_keyval(NULL, |
- keyval, |
- &keys, |
- &n_keys); |
- if (n_keys) { |
- // Forward the accelerator to root window the bubble is anchored |
- // to for further processing |
- msg.type = GDK_KEY_PRESS; |
- msg.window = GTK_WIDGET(toplevel_window_)->window; |
- msg.send_event = TRUE; |
- msg.time = GDK_CURRENT_TIME; |
- msg.state = modifier | GDK_MOD2_MASK; |
- msg.keyval = keyval; |
- // length and string are deprecated and thus zeroed out |
- msg.length = 0; |
- msg.string = NULL; |
- msg.hardware_keycode = keys[0].keycode; |
- msg.group = keys[0].group; |
- msg.is_modifier = 0; |
- |
- g_free(keys); |
- |
- gtk_main_do_event(reinterpret_cast<GdkEvent*>(&msg)); |
- } else { |
- // This means that there isn't a h/w code for the keyval in the |
- // current keymap, which is weird but possible if the keymap just |
- // changed. This isn't a critical error, but might be indicative |
- // of something off if it happens regularly. |
- DLOG(WARNING) << "Found no keys for value " << keyval; |
- } |
- return TRUE; |
-} |
- |
-gboolean InfoBubbleGtk::OnExpose(GtkWidget* widget, GdkEventExpose* expose) { |
- GdkDrawable* drawable = GDK_DRAWABLE(window_->window); |
- GdkGC* gc = gdk_gc_new(drawable); |
- gdk_gc_set_rgb_fg_color(gc, &kFrameColor); |
- |
- // Stroke the frame border. |
- std::vector<GdkPoint> points = MakeFramePolygonPoints( |
- current_arrow_location_, |
- window_->allocation.width, window_->allocation.height, |
- FRAME_STROKE); |
- gdk_draw_polygon(drawable, gc, FALSE, &points[0], points.size()); |
- |
- g_object_unref(gc); |
- return FALSE; // Propagate so our children paint, etc. |
-} |
- |
-// When our size is initially allocated or changed, we need to recompute |
-// and apply our shape mask region. |
-void InfoBubbleGtk::OnSizeAllocate(GtkWidget* widget, |
- GtkAllocation* allocation) { |
- if (!UpdateArrowLocation(false)) { |
- UpdateWindowShape(); |
- if (current_arrow_location_ == ARROW_LOCATION_TOP_RIGHT) |
- MoveWindow(); |
- } |
-} |
- |
-gboolean InfoBubbleGtk::OnButtonPress(GtkWidget* widget, |
- GdkEventButton* event) { |
- // If we got a click in our own window, that's okay (we need to additionally |
- // check that it falls within our bounds, since we've grabbed the pointer and |
- // some events that actually occurred in other windows will be reported with |
- // respect to our window). |
- if (event->window == window_->window && |
- (mask_region_ && gdk_region_point_in(mask_region_, event->x, event->y))) { |
- return FALSE; // Propagate. |
- } |
- |
- // Our content widget got a click. |
- if (event->window != window_->window && |
- gdk_window_get_toplevel(event->window) == window_->window) { |
- return FALSE; |
- } |
- |
- if (grab_input_) { |
- // Otherwise we had a click outside of our window, close ourself. |
- Close(); |
- return TRUE; |
- } |
- |
- return FALSE; |
-} |
- |
-gboolean InfoBubbleGtk::OnDestroy(GtkWidget* widget) { |
- // We are self deleting, we have a destroy signal setup to catch when we |
- // destroy the widget manually, or the window was closed via X. This will |
- // delete the InfoBubbleGtk object. |
- delete this; |
- return FALSE; // Propagate. |
-} |
- |
-void InfoBubbleGtk::OnHide(GtkWidget* widget) { |
- gtk_widget_destroy(widget); |
-} |
- |
-gboolean InfoBubbleGtk::OnToplevelConfigure(GtkWidget* widget, |
- GdkEventConfigure* event) { |
- if (!UpdateArrowLocation(false)) |
- MoveWindow(); |
- StackWindow(); |
- return FALSE; |
-} |
- |
-gboolean InfoBubbleGtk::OnToplevelUnmap(GtkWidget* widget, GdkEvent* event) { |
- Close(); |
- return FALSE; |
-} |
- |
-void InfoBubbleGtk::OnAnchorAllocate(GtkWidget* widget, |
- GtkAllocation* allocation) { |
- if (!UpdateArrowLocation(false)) |
- MoveWindow(); |
-} |