summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authorderat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-10-24 03:19:13 +0000
committerderat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-10-24 03:19:13 +0000
commit73142769aac422267b84568000a5cc2a4b024f3d (patch)
tree27f651de1d3656bf505814acfbc7abdd08d2f547 /chrome/browser
parent92416a93858d7d21044296ec6b7b2440be3ac1bf (diff)
downloadchromium_src-73142769aac422267b84568000a5cc2a4b024f3d.zip
chromium_src-73142769aac422267b84568000a5cc2a4b024f3d.tar.gz
chromium_src-73142769aac422267b84568000a5cc2a4b024f3d.tar.bz2
GTK: Try to position info bubbles onscreen.
I didn't go so far as to add support for arrows-on-the-bottom in this change; I'm just making the bubble extend left or right as needed. I'll add a parameter to specify the default arrow location (needed for browser action popups) in another CL. Tested by: - dragging a window to the right edge of the screen and confirming that bookmark bubbles open extended to the left - opening a bookmark bubble, using a little X program to move the Chrome window to the right side of the screen (can't drag it there since the pointer is grabbed), and confirming that the bubble gets updated to extend to the left - running in Arabic and confirming that bubbles extend to the left by default but get switched to extend to the right as needed BUG=23373 TEST=see above Review URL: http://codereview.chromium.org/316006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@29992 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/gtk/info_bubble_gtk.cc249
-rw-r--r--chrome/browser/gtk/info_bubble_gtk.h45
-rw-r--r--chrome/browser/gtk/location_bar_view_gtk.cc2
3 files changed, 193 insertions, 103 deletions
diff --git a/chrome/browser/gtk/info_bubble_gtk.cc b/chrome/browser/gtk/info_bubble_gtk.cc
index 1072fb2..68fbc2d 100644
--- a/chrome/browser/gtk/info_bubble_gtk.cc
+++ b/chrome/browser/gtk/info_bubble_gtk.cc
@@ -21,13 +21,17 @@ namespace {
// The height of the arrow, and the width will be about twice the height.
const int kArrowSize = 5;
+
// Number of pixels to the start of the arrow from the edge of the window.
const int kArrowX = 13;
+
// Number of pixels between the tip of the arrow and the region we're
// pointing to.
const int kArrowToContentPadding = -6;
+
// We draw flat diagonal corners, each corner is an NxN square.
const int kCornerSize = 3;
+
// Margins around the content.
const int kTopMargin = kArrowSize + kCornerSize + 6;
const int kBottomMargin = kCornerSize + 6;
@@ -37,87 +41,6 @@ const int kRightMargin = kCornerSize + 6;
const GdkColor kBackgroundColor = GDK_COLOR_RGB(0xff, 0xff, 0xff);
const GdkColor kFrameColor = GDK_COLOR_RGB(0x63, 0x63, 0x63);
-enum FrameType {
- FRAME_MASK,
- FRAME_STROKE,
-};
-
-// Make the points for our polygon frame, either for fill (the mask), or for
-// when we stroke the border. 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.
-std::vector<GdkPoint> MakeFramePolygonPoints(int width,
- int height,
- FrameType type) {
- using gtk_util::MakeBidiGdkPoint;
- std::vector<GdkPoint> points;
-
- bool ltr = l10n_util::GetTextDirection() == l10n_util::LEFT_TO_RIGHT;
- // If we have a stroke, we have to offset some of our points by 1 pixel.
- // We have to inset by 1 pixel when we draw horizontal lines that are on the
- // bottom or when we draw vertical lines that are closer to the end (end is
- // right for ltr).
- int y_off = (type == FRAME_MASK) ? 0 : -1;
- // We use this one for LTR.
- int x_off_l = ltr ? y_off : 0;
- // We use this one for RTL.
- int x_off_r = !ltr ? -y_off : 0;
-
- // Top left corner.
- points.push_back(MakeBidiGdkPoint(
- x_off_r, kArrowSize + kCornerSize - 1, width, ltr));
- points.push_back(MakeBidiGdkPoint(
- kCornerSize + x_off_r - 1, kArrowSize, width, ltr));
-
- // The arrow.
- points.push_back(MakeBidiGdkPoint(
- kArrowX - kArrowSize + x_off_r, kArrowSize, width, ltr));
- points.push_back(MakeBidiGdkPoint(
- kArrowX + x_off_r, 0, width, ltr));
- points.push_back(MakeBidiGdkPoint(
- kArrowX + 1 + x_off_l, 0, width, ltr));
- points.push_back(MakeBidiGdkPoint(
- kArrowX + kArrowSize + 1 + x_off_l, kArrowSize, width, ltr));
-
- // Top right corner.
- points.push_back(MakeBidiGdkPoint(
- width - kCornerSize + 1 + x_off_l, kArrowSize, width, ltr));
- points.push_back(MakeBidiGdkPoint(
- width + x_off_l, kArrowSize + kCornerSize - 1, width, ltr));
-
- // Bottom right corner.
- points.push_back(MakeBidiGdkPoint(
- width + x_off_l, height - kCornerSize, width, ltr));
- points.push_back(MakeBidiGdkPoint(
- width - kCornerSize + x_off_r, height + y_off, width, ltr));
-
- // Bottom left corner.
- points.push_back(MakeBidiGdkPoint(
- kCornerSize + x_off_l, height + y_off, width, ltr));
- points.push_back(MakeBidiGdkPoint(
- x_off_r, height - kCornerSize, width, ltr));
-
- return points;
-}
-
-gboolean HandleExpose(GtkWidget* widget,
- GdkEventExpose* event,
- gpointer unused) {
- GdkDrawable* drawable = GDK_DRAWABLE(event->window);
- GdkGC* gc = gdk_gc_new(drawable);
- gdk_gc_set_rgb_fg_color(gc, &kFrameColor);
-
- // Stroke the frame border.
- std::vector<GdkPoint> points = MakeFramePolygonPoints(
- widget->allocation.width, widget->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.
-}
-
} // namespace
// static
@@ -138,7 +61,8 @@ InfoBubbleGtk::InfoBubbleGtk(GtkThemeProvider* provider)
theme_provider_(provider),
accel_group_(gtk_accel_group_new()),
toplevel_window_(NULL),
- mask_region_(NULL) {
+ mask_region_(NULL),
+ arrow_location_(ARROW_LOCATION_TOP_LEFT) {
}
InfoBubbleGtk::~InfoBubbleGtk() {
@@ -168,8 +92,6 @@ void InfoBubbleGtk::Init(GtkWindow* toplevel_window,
window_ = gtk_window_new(GTK_WINDOW_POPUP);
gtk_widget_set_app_paintable(window_, TRUE);
- // Have GTK double buffer around the expose signal.
- gtk_widget_set_double_buffered(window_, TRUE);
// Attach our accelerator group to the window with an escape accelerator.
gtk_accel_group_connect(accel_group_, GDK_Escape,
@@ -202,7 +124,7 @@ void InfoBubbleGtk::Init(GtkWindow* toplevel_window,
GDK_BUTTON_RELEASE_MASK);
g_signal_connect(window_, "expose-event",
- G_CALLBACK(HandleExpose), NULL);
+ G_CALLBACK(HandleExposeThunk), this);
g_signal_connect(window_, "size-allocate",
G_CALLBACK(HandleSizeAllocateThunk), this);
g_signal_connect(window_, "button-press-event",
@@ -240,17 +162,135 @@ void InfoBubbleGtk::Init(GtkWindow* toplevel_window,
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;
+
+ // This name isn't completely accurate; the arrow location can differ from its
+ // expected location for LTR/RTL if needed for the bubble to fit onscreen.
+ bool ltr = (arrow_location == ARROW_LOCATION_TOP_LEFT);
+
+ // If we have a stroke, we have to offset some of our points by 1 pixel.
+ // We have to inset by 1 pixel when we draw horizontal lines that are on the
+ // bottom or when we draw vertical lines that are closer to the end (end is
+ // right for ltr).
+ int y_off = (type == FRAME_MASK) ? 0 : -1;
+ // We use this one for LTR.
+ int x_off_l = ltr ? y_off : 0;
+ // We use this one for RTL.
+ int x_off_r = !ltr ? -y_off : 0;
+
+ // Top left corner.
+ points.push_back(MakeBidiGdkPoint(
+ x_off_r, kArrowSize + kCornerSize - 1, width, ltr));
+ points.push_back(MakeBidiGdkPoint(
+ kCornerSize + x_off_r - 1, kArrowSize, width, ltr));
+
+ // The arrow.
+ points.push_back(MakeBidiGdkPoint(
+ kArrowX - kArrowSize + x_off_r, kArrowSize, width, ltr));
+ points.push_back(MakeBidiGdkPoint(
+ kArrowX + x_off_r, 0, width, ltr));
+ points.push_back(MakeBidiGdkPoint(
+ kArrowX + 1 + x_off_l, 0, width, ltr));
+ points.push_back(MakeBidiGdkPoint(
+ kArrowX + kArrowSize + 1 + x_off_l, kArrowSize, width, ltr));
+
+ // Top right corner.
+ points.push_back(MakeBidiGdkPoint(
+ width - kCornerSize + 1 + x_off_l, kArrowSize, width, ltr));
+ points.push_back(MakeBidiGdkPoint(
+ width + x_off_l, kArrowSize + kCornerSize - 1, width, ltr));
+
+ // Bottom right corner.
+ points.push_back(MakeBidiGdkPoint(
+ width + x_off_l, height - kCornerSize, width, ltr));
+ points.push_back(MakeBidiGdkPoint(
+ width - kCornerSize + x_off_r, height + y_off, width, ltr));
+
+ // Bottom left corner.
+ points.push_back(MakeBidiGdkPoint(
+ kCornerSize + x_off_l, height + y_off, width, ltr));
+ points.push_back(MakeBidiGdkPoint(
+ x_off_r, height - kCornerSize, width, ltr));
+
+ return points;
+}
+
+InfoBubbleGtk::ArrowLocationGtk InfoBubbleGtk::GetArrowLocation(
+ int arrow_x, int width) {
+ bool ltr = (l10n_util::GetTextDirection() == l10n_util::LEFT_TO_RIGHT);
+ 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 expected location for our LTR/RTL-ness if it fits onscreen, use
+ // whatever fits otherwise, and use the expected location if neither fits.
+ if (left_is_onscreen && (ltr || !right_is_onscreen))
+ return ARROW_LOCATION_TOP_LEFT;
+ if (right_is_onscreen && (!ltr || !left_is_onscreen))
+ return ARROW_LOCATION_TOP_RIGHT;
+ return (ltr ? ARROW_LOCATION_TOP_LEFT : ARROW_LOCATION_TOP_RIGHT);
+}
+
+bool InfoBubbleGtk::UpdateArrowLocation() {
+ gint toplevel_x = 0, toplevel_y = 0;
+ gdk_window_get_position(
+ GTK_WIDGET(toplevel_window_)->window, &toplevel_x, &toplevel_y);
+
+ ArrowLocationGtk old_location = arrow_location_;
+ arrow_location_ = GetArrowLocation(
+ toplevel_x + rect_.x() + (rect_.width() / 2), // arrow_x
+ window_->allocation.width);
+
+ if (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(
+ 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, mask_region_, 0, 0);
+}
+
void InfoBubbleGtk::MoveWindow() {
gint toplevel_x = 0, toplevel_y = 0;
gdk_window_get_position(
GTK_WIDGET(toplevel_window_)->window, &toplevel_x, &toplevel_y);
gint screen_x = 0;
- if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) {
+ if (arrow_location_ == ARROW_LOCATION_TOP_LEFT) {
+ screen_x = toplevel_x + rect_.x() + (rect_.width() / 2) - kArrowX;
+ } else if (arrow_location_ == ARROW_LOCATION_TOP_RIGHT) {
screen_x = toplevel_x + rect_.x() + (rect_.width() / 2) -
window_->allocation.width + kArrowX;
} else {
- screen_x = toplevel_x + rect_.x() + (rect_.width() / 2) - kArrowX;
+ NOTREACHED();
}
gint screen_y = toplevel_y + rect_.y() + rect_.height() +
@@ -325,23 +365,29 @@ gboolean InfoBubbleGtk::HandleEscape() {
return TRUE;
}
+gboolean InfoBubbleGtk::HandleExpose() {
+ 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(
+ 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::HandleSizeAllocate() {
- if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT)
- MoveWindow();
-
- DCHECK(window_->allocation.x == 0 && window_->allocation.y == 0);
- if (mask_region_) {
- gdk_region_destroy(mask_region_);
- mask_region_ = NULL;
+ if (!UpdateArrowLocation()) {
+ UpdateWindowShape();
+ if (arrow_location_ == ARROW_LOCATION_TOP_RIGHT)
+ MoveWindow();
}
- std::vector<GdkPoint> points = MakeFramePolygonPoints(
- 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, mask_region_, 0, 0);
}
gboolean InfoBubbleGtk::HandleButtonPress(GdkEventButton* event) {
@@ -374,7 +420,8 @@ gboolean InfoBubbleGtk::HandleDestroy() {
}
gboolean InfoBubbleGtk::HandleToplevelConfigure(GdkEventConfigure* event) {
- MoveWindow();
+ if (!UpdateArrowLocation())
+ MoveWindow();
StackWindow();
return FALSE;
}
diff --git a/chrome/browser/gtk/info_bubble_gtk.h b/chrome/browser/gtk/info_bubble_gtk.h
index a92cd3b..876cb9c 100644
--- a/chrome/browser/gtk/info_bubble_gtk.h
+++ b/chrome/browser/gtk/info_bubble_gtk.h
@@ -72,6 +72,18 @@ class InfoBubbleGtk : public NotificationObserver {
void HandlePointerAndKeyboardUngrabbedByContent();
private:
+ // Where should the arrow be placed relative to the bubble?
+ enum ArrowLocationGtk {
+ // TODO(derat): Support placing arrows on the bottoms of the bubbles.
+ ARROW_LOCATION_TOP_LEFT,
+ ARROW_LOCATION_TOP_RIGHT,
+ };
+
+ enum FrameType {
+ FRAME_MASK,
+ FRAME_STROKE,
+ };
+
explicit InfoBubbleGtk(GtkThemeProvider* provider);
virtual ~InfoBubbleGtk();
@@ -80,6 +92,29 @@ class InfoBubbleGtk : public NotificationObserver {
const gfx::Rect& rect,
GtkWidget* content);
+ // Make the points for our polygon frame, either for fill (the mask), or for
+ // when we stroke the border.
+ static std::vector<GdkPoint> MakeFramePolygonPoints(
+ ArrowLocationGtk arrow_location,
+ int width,
+ int height,
+ FrameType type);
+
+ // Get the location where the arrow should be placed (which is a function of
+ // whether the user's language is LTR/RTL and of the direction that the bubble
+ // should be facing to fit onscreen). |arrow_x| is the X component in screen
+ // coordinates of the point at which the bubble's arrow should be aimed, and
+ // |width| is the bubble's width.
+ static ArrowLocationGtk GetArrowLocation(int arrow_x, int width);
+
+ // Updates |arrow_location_| based on the toplevel window's current position
+ // and the bubble's size. If the location changes, moves and reshapes the
+ // window and returns true.
+ bool UpdateArrowLocation();
+
+ // Reshapes the window and updates |mask_region_|.
+ void UpdateWindowShape();
+
// Calculate the current screen position for the bubble's window (per
// |toplevel_window_|'s position as of its most-recent ConfigureNotify event
// and |rect_|) and move it there.
@@ -108,6 +143,13 @@ class InfoBubbleGtk : public NotificationObserver {
}
gboolean HandleEscape();
+ static gboolean HandleExposeThunk(GtkWidget* widget,
+ GdkEventExpose* event,
+ gpointer user_data) {
+ return reinterpret_cast<InfoBubbleGtk*>(user_data)->HandleExpose();
+ }
+ gboolean HandleExpose();
+
static void HandleSizeAllocateThunk(GtkWidget* widget,
GtkAllocation* allocation,
gpointer user_data) {
@@ -176,6 +218,9 @@ class InfoBubbleGtk : public NotificationObserver {
// not).
GdkRegion* mask_region_;
+ // Where should the arrow be drawn relative to the bubble?
+ ArrowLocationGtk arrow_location_;
+
NotificationRegistrar registrar_;
DISALLOW_COPY_AND_ASSIGN(InfoBubbleGtk);
diff --git a/chrome/browser/gtk/location_bar_view_gtk.cc b/chrome/browser/gtk/location_bar_view_gtk.cc
index 086cf97..4027d4e 100644
--- a/chrome/browser/gtk/location_bar_view_gtk.cc
+++ b/chrome/browser/gtk/location_bar_view_gtk.cc
@@ -151,8 +151,6 @@ void LocationBarViewGtk::Init(bool popup_window_mode) {
gtk_container_set_border_width(GTK_CONTAINER(hbox_.get()), kHboxBorder);
// We will paint for the alignment, to paint the background and border.
gtk_widget_set_app_paintable(hbox_.get(), TRUE);
- // Have GTK double buffer around the expose signal.
- gtk_widget_set_double_buffered(hbox_.get(), TRUE);
// Redraw the whole location bar when it changes size (e.g., when toggling
// the home button on/off.
gtk_widget_set_redraw_on_allocate(hbox_.get(), TRUE);