summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/ui/gtk/bubble/bubble_gtk.cc151
-rw-r--r--chrome/browser/ui/gtk/bubble/bubble_gtk.h25
-rw-r--r--chrome/browser/ui/gtk/bubble/bubble_gtk_browsertest.cc135
-rw-r--r--chrome/chrome_tests.gypi1
4 files changed, 275 insertions, 37 deletions
diff --git a/chrome/browser/ui/gtk/bubble/bubble_gtk.cc b/chrome/browser/ui/gtk/bubble/bubble_gtk.cc
index c4c35a6..dc0f3c5 100644
--- a/chrome/browser/ui/gtk/bubble/bubble_gtk.cc
+++ b/chrome/browser/ui/gtk/bubble/bubble_gtk.cc
@@ -44,6 +44,32 @@ const int kRightMargin = kCornerSize - 1;
const GdkColor kBackgroundColor = GDK_COLOR_RGB(0xff, 0xff, 0xff);
const GdkColor kFrameColor = GDK_COLOR_RGB(0x63, 0x63, 0x63);
+// Helper functions that encapsulate arrow locations.
+bool HasArrow(BubbleGtk::ArrowLocationGtk location) {
+ return location != BubbleGtk::ARROW_LOCATION_NONE &&
+ location != BubbleGtk::ARROW_LOCATION_FLOAT;
+}
+
+bool IsArrowLeft(BubbleGtk::ArrowLocationGtk location) {
+ return location == BubbleGtk::ARROW_LOCATION_TOP_LEFT ||
+ location == BubbleGtk::ARROW_LOCATION_BOTTOM_LEFT;
+}
+
+bool IsArrowRight(BubbleGtk::ArrowLocationGtk location) {
+ return location == BubbleGtk::ARROW_LOCATION_TOP_RIGHT ||
+ location == BubbleGtk::ARROW_LOCATION_BOTTOM_RIGHT;
+}
+
+bool IsArrowTop(BubbleGtk::ArrowLocationGtk location) {
+ return location == BubbleGtk::ARROW_LOCATION_TOP_LEFT ||
+ location == BubbleGtk::ARROW_LOCATION_TOP_RIGHT;
+}
+
+bool IsArrowBottom(BubbleGtk::ArrowLocationGtk location) {
+ return location == BubbleGtk::ARROW_LOCATION_BOTTOM_LEFT ||
+ location == BubbleGtk::ARROW_LOCATION_BOTTOM_RIGHT;
+}
+
} // namespace
// static
@@ -197,7 +223,9 @@ std::vector<GdkPoint> BubbleGtk::MakeFramePolygonPoints(
using gtk_util::MakeBidiGdkPoint;
std::vector<GdkPoint> points;
- bool on_left = (arrow_location == ARROW_LOCATION_TOP_LEFT);
+ int top_arrow_size = IsArrowTop(arrow_location) ? kArrowSize : 0;
+ int bottom_arrow_size = IsArrowBottom(arrow_location) ? kArrowSize : 0;
+ bool on_left = IsArrowLeft(arrow_location);
// 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
@@ -211,37 +239,61 @@ std::vector<GdkPoint> BubbleGtk::MakeFramePolygonPoints(
// 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));
+ x_off_r, top_arrow_size + kCornerSize - 1, 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));
+ kCornerSize + x_off_r - 1, top_arrow_size, width, on_left));
+
+ // The top arrow.
+ if (top_arrow_size) {
+ points.push_back(MakeBidiGdkPoint(
+ kArrowX - top_arrow_size + x_off_r, top_arrow_size, 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 + top_arrow_size + 1 + x_off_l, top_arrow_size,
+ width, on_left));
+ }
// Top right corner.
points.push_back(MakeBidiGdkPoint(
- width - kCornerSize + 1 + x_off_l, kArrowSize, width, on_left));
+ width - kCornerSize + 1 + x_off_l, top_arrow_size, width, on_left));
points.push_back(MakeBidiGdkPoint(
- width + x_off_l, kArrowSize + kCornerSize - 1, width, on_left));
+ width + x_off_l, top_arrow_size + kCornerSize - 1, width, on_left));
// Bottom right corner.
points.push_back(MakeBidiGdkPoint(
- width + x_off_l, height - kCornerSize, width, on_left));
+ width + x_off_l, height - bottom_arrow_size - kCornerSize,
+ width, on_left));
points.push_back(MakeBidiGdkPoint(
- width - kCornerSize + x_off_r, height + y_off, width, on_left));
+ width - kCornerSize + x_off_r, height - bottom_arrow_size + y_off,
+ width, on_left));
+
+ // The bottom arrow.
+ if (bottom_arrow_size) {
+ points.push_back(MakeBidiGdkPoint(
+ kArrowX + bottom_arrow_size + 1 + x_off_l,
+ height - bottom_arrow_size + y_off,
+ width,
+ on_left));
+ points.push_back(MakeBidiGdkPoint(
+ kArrowX + 1 + x_off_l, height + y_off, width, on_left));
+ points.push_back(MakeBidiGdkPoint(
+ kArrowX + x_off_r, height + y_off, width, on_left));
+ points.push_back(MakeBidiGdkPoint(
+ kArrowX - bottom_arrow_size + x_off_r,
+ height - bottom_arrow_size + y_off,
+ width,
+ on_left));
+ }
// Bottom left corner.
points.push_back(MakeBidiGdkPoint(
- kCornerSize + x_off_l, height + y_off, width, on_left));
+ kCornerSize + x_off_l, height -bottom_arrow_size + y_off,
+ width, on_left));
points.push_back(MakeBidiGdkPoint(
- x_off_r, height - kCornerSize, width, on_left));
+ x_off_r, height - bottom_arrow_size - kCornerSize, width, on_left));
return points;
}
@@ -249,20 +301,46 @@ std::vector<GdkPoint> BubbleGtk::MakeFramePolygonPoints(
BubbleGtk::ArrowLocationGtk BubbleGtk::GetArrowLocation(
ArrowLocationGtk preferred_location,
int arrow_x,
- int width) {
- bool wants_left = (preferred_location == ARROW_LOCATION_TOP_LEFT);
+ int arrow_y,
+ int width,
+ int height) {
int screen_width = gdk_screen_get_width(gdk_screen_get_default());
+ int screen_height = gdk_screen_get_height(gdk_screen_get_default());
+
+ // Choose whether we should show this bubble above the specified location or
+ // below it.
+ bool wants_top = IsArrowTop(preferred_location) ||
+ preferred_location == ARROW_LOCATION_NONE;
+ bool top_is_onscreen = (arrow_y + height < screen_height);
+ bool bottom_is_onscreen = (arrow_y - height >= 0);
+
+ ArrowLocationGtk arrow_location_none;
+ ArrowLocationGtk arrow_location_left;
+ ArrowLocationGtk arrow_location_right;
+ if (top_is_onscreen && (wants_top || !bottom_is_onscreen)) {
+ arrow_location_none = ARROW_LOCATION_NONE;
+ arrow_location_left = ARROW_LOCATION_TOP_LEFT;
+ arrow_location_right =ARROW_LOCATION_TOP_RIGHT;
+ } else {
+ arrow_location_none = ARROW_LOCATION_FLOAT;
+ arrow_location_left = ARROW_LOCATION_BOTTOM_LEFT;
+ arrow_location_right =ARROW_LOCATION_BOTTOM_RIGHT;
+ }
+ if (!HasArrow(preferred_location))
+ return arrow_location_none;
+
+ bool wants_left = IsArrowLeft(preferred_location);
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;
+ return arrow_location_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);
+ return arrow_location_right;
+ return (wants_left ? arrow_location_left : arrow_location_right);
}
bool BubbleGtk::UpdateArrowLocation(bool force_move_and_reshape) {
@@ -282,7 +360,9 @@ bool BubbleGtk::UpdateArrowLocation(bool force_move_and_reshape) {
current_arrow_location_ = GetArrowLocation(
preferred_arrow_location_,
toplevel_x + offset_x + (rect_.width() / 2), // arrow_x
- allocation.width);
+ toplevel_y + offset_y,
+ allocation.width,
+ allocation.height);
if (force_move_and_reshape || current_arrow_location_ != old_location) {
UpdateWindowShape();
@@ -325,10 +405,16 @@ void BubbleGtk::MoveWindow() {
gtk_widget_translate_coordinates(anchor_widget_, toplevel_window_,
rect_.x(), rect_.y(), &offset_x, &offset_y);
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(window_, &allocation);
+
gint screen_x = 0;
- if (current_arrow_location_ == ARROW_LOCATION_TOP_LEFT) {
+ if (!HasArrow(current_arrow_location_)) {
+ screen_x =
+ toplevel_x + offset_x + (rect_.width() / 2) - allocation.width / 2;
+ } else if (IsArrowLeft(current_arrow_location_)) {
screen_x = toplevel_x + offset_x + (rect_.width() / 2) - kArrowX;
- } else if (current_arrow_location_ == ARROW_LOCATION_TOP_RIGHT) {
+ } else if (IsArrowRight(current_arrow_location_)) {
GtkAllocation allocation;
gtk_widget_get_allocation(window_, &allocation);
screen_x = toplevel_x + offset_x + (rect_.width() / 2) -
@@ -337,8 +423,13 @@ void BubbleGtk::MoveWindow() {
NOTREACHED();
}
- gint screen_y = toplevel_y + offset_y + rect_.height() +
- kArrowToContentPadding;
+ gint screen_y = toplevel_y + offset_y + rect_.height();
+ if (IsArrowTop(current_arrow_location_) ||
+ current_arrow_location_ == ARROW_LOCATION_NONE) {
+ screen_y += kArrowToContentPadding;
+ } else {
+ screen_y -= allocation.height + kArrowToContentPadding;
+ }
gtk_window_move(GTK_WINDOW(window_), screen_x, screen_y);
}
@@ -483,7 +574,7 @@ void BubbleGtk::OnSizeAllocate(GtkWidget* widget,
GtkAllocation* allocation) {
if (!UpdateArrowLocation(false)) {
UpdateWindowShape();
- if (current_arrow_location_ == ARROW_LOCATION_TOP_RIGHT)
+ if (current_arrow_location_ != ARROW_LOCATION_TOP_LEFT)
MoveWindow();
}
}
diff --git a/chrome/browser/ui/gtk/bubble/bubble_gtk.h b/chrome/browser/ui/gtk/bubble/bubble_gtk.h
index 804b6180..e5464be 100644
--- a/chrome/browser/ui/gtk/bubble/bubble_gtk.h
+++ b/chrome/browser/ui/gtk/bubble/bubble_gtk.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Copyright (c) 2012 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.
@@ -12,6 +12,7 @@
#include "base/basictypes.h"
#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "ui/base/gtk/gtk_signal.h"
@@ -50,9 +51,12 @@ class BubbleGtk : public content::NotificationObserver {
public:
// 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,
+ ARROW_LOCATION_BOTTOM_LEFT,
+ ARROW_LOCATION_BOTTOM_RIGHT,
+ ARROW_LOCATION_NONE, // No arrow. Positioned under the supplied rect.
+ ARROW_LOCATION_FLOAT, // No arrow. Centered over the supplied rect.
};
// Show a bubble, pointing at the area |rect| (in coordinates relative to
@@ -92,6 +96,9 @@ class BubbleGtk : public content::NotificationObserver {
void HandlePointerAndKeyboardUngrabbedByContent();
private:
+ FRIEND_TEST_ALL_PREFIXES(BubbleGtkTest, ArrowLocation);
+ FRIEND_TEST_ALL_PREFIXES(BubbleGtkTest, NoArrow);
+
enum FrameType {
FRAME_MASK,
FRAME_STROKE,
@@ -117,11 +124,15 @@ class BubbleGtk : public content::NotificationObserver {
// Get the location where the arrow should be placed (which is a function of
// the preferred location 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(
- ArrowLocationGtk preferred_location, int arrow_x, int width);
+ // facing to fit onscreen). |arrow_x| (or |arrow_y|) is the X component (or
+ // Y component) in screen coordinates of the point at which the bubble's arrow
+ // should be aimed, respectively. |width| (or |height|) is the bubble's width
+ // (or height).
+ static ArrowLocationGtk GetArrowLocation(ArrowLocationGtk preferred_location,
+ int arrow_x,
+ int arrow_y,
+ int width,
+ int height);
// Updates |arrow_location_| based on the toplevel window's current position
// and the bubble's size. If the |force_move_and_reshape| is true or the
diff --git a/chrome/browser/ui/gtk/bubble/bubble_gtk_browsertest.cc b/chrome/browser/ui/gtk/bubble/bubble_gtk_browsertest.cc
new file mode 100644
index 0000000..ecdb541
--- /dev/null
+++ b/chrome/browser/ui/gtk/bubble/bubble_gtk_browsertest.cc
@@ -0,0 +1,135 @@
+// Copyright (c) 2012 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 <gtk/gtk.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/gtk/browser_window_gtk.h"
+#include "chrome/browser/ui/gtk/bubble/bubble_gtk.h"
+#include "chrome/browser/ui/gtk/gtk_theme_service.h"
+#include "chrome/browser/ui/gtk/gtk_util.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "ui/base/gtk/gtk_hig_constants.h"
+
+class BubbleGtkTest : public InProcessBrowserTest,
+ public BubbleDelegateGtk {
+ public:
+ BubbleGtkTest() : browser_window_(NULL) {
+ }
+
+ virtual ~BubbleGtkTest() {
+ }
+
+ // BubbleDelegateGtk implementation.
+ virtual void BubbleClosing(BubbleGtk* bubble,
+ bool closed_by_escape) OVERRIDE {
+ }
+
+ Profile* GetProfile() {
+ return browser()->profile();
+ }
+
+ GtkWidget* GetNativeBrowserWindow() {
+ if (!browser_window_)
+ browser_window_ = GTK_WIDGET(browser()->window()->GetNativeHandle());
+ return browser_window_;
+ }
+
+ private:
+ GtkWidget* browser_window_;
+};
+
+// Tests that we can adjust a bubble arrow so we can show a bubble without being
+// clipped. This test verifies the following four issues:
+// 1. Shows a bubble to the top-left corner and see its arrow location always
+// becomes ARROW_LOCATION_TOP_LEFT.
+// 2. Shows a bubble to the top-right corner and see its arrow location always
+// becomes ARROW_LOCATION_TOP_RIGHT.
+// 3. Shows a bubble to the bottom-left corner and see its arrow location always
+// becomes ARROW_LOCATION_BOTTOM_LEFT.
+// 4. Shows a bubble to the top-left corner and see its arrow location always
+// becomes ARROW_LOCATION_BOTTOM_RIGHT.
+IN_PROC_BROWSER_TEST_F(BubbleGtkTest, ArrowLocation) {
+ int width = gdk_screen_get_width(gdk_screen_get_default());
+ int height = gdk_screen_get_height(gdk_screen_get_default());
+ struct {
+ int x, y;
+ BubbleGtk::ArrowLocationGtk expected;
+ } points[] = {
+ {0, 0, BubbleGtk::ARROW_LOCATION_TOP_LEFT},
+ {width - 1, 0, BubbleGtk::ARROW_LOCATION_TOP_RIGHT},
+ {0, height - 1, BubbleGtk::ARROW_LOCATION_BOTTOM_LEFT},
+ {width - 1, height - 1, BubbleGtk::ARROW_LOCATION_BOTTOM_RIGHT},
+ };
+ static const BubbleGtk::ArrowLocationGtk kPreferredLocations[] = {
+ BubbleGtk::ARROW_LOCATION_TOP_LEFT,
+ BubbleGtk::ARROW_LOCATION_TOP_RIGHT,
+ BubbleGtk::ARROW_LOCATION_BOTTOM_LEFT,
+ BubbleGtk::ARROW_LOCATION_BOTTOM_RIGHT,
+ };
+
+ GtkWidget* anchor = GetNativeBrowserWindow();
+ GtkThemeService* theme_service = GtkThemeService::GetFrom(GetProfile());
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(points); ++i) {
+ for (size_t j = 0; j < arraysize(kPreferredLocations); ++j) {
+ static const char kText[] =
+ "Google's mission is to organize the world's information and make it"
+ " universally accessible and useful.";
+ GtkWidget* label = theme_service->BuildLabel(kText, ui::kGdkBlack);
+ gfx::Rect rect(points[i].x, points[i].y, 0, 0);
+ BubbleGtk* bubble = BubbleGtk::Show(anchor,
+ &rect,
+ label,
+ kPreferredLocations[j],
+ true,
+ true,
+ theme_service,
+ this);
+ EXPECT_EQ(points[i].expected, bubble->current_arrow_location_);
+ bubble->Close();
+ }
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(BubbleGtkTest, NoArrow) {
+ int width = gdk_screen_get_width(gdk_screen_get_default());
+ int height = gdk_screen_get_height(gdk_screen_get_default());
+ struct {
+ int x, y;
+ BubbleGtk::ArrowLocationGtk expected;
+ } points[] = {
+ {0, 0, BubbleGtk::ARROW_LOCATION_NONE},
+ {width - 1, 0, BubbleGtk::ARROW_LOCATION_NONE},
+ {0, height - 1, BubbleGtk::ARROW_LOCATION_FLOAT},
+ {width - 1, height - 1, BubbleGtk::ARROW_LOCATION_FLOAT},
+ };
+ static const BubbleGtk::ArrowLocationGtk kPreferredLocations[] = {
+ BubbleGtk::ARROW_LOCATION_NONE,
+ BubbleGtk::ARROW_LOCATION_FLOAT,
+ };
+
+ GtkWidget* anchor = GetNativeBrowserWindow();
+ GtkThemeService* theme_service = GtkThemeService::GetFrom(GetProfile());
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(points); ++i) {
+ for (size_t j = 0; j < arraysize(kPreferredLocations); ++j) {
+ static const char kText[] =
+ "Google's mission is to organize the world's information and make it"
+ " universally accessible and useful.";
+ GtkWidget* label = theme_service->BuildLabel(kText, ui::kGdkBlack);
+ gfx::Rect rect(points[i].x, points[i].y, 0, 0);
+ BubbleGtk* bubble = BubbleGtk::Show(anchor,
+ &rect,
+ label,
+ kPreferredLocations[j],
+ true,
+ true,
+ theme_service,
+ this);
+ EXPECT_EQ(points[i].expected, bubble->current_arrow_location_);
+ bubble->Close();
+ }
+ }
+}
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 2ff5381..8808bc0 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -2691,6 +2691,7 @@
'browser/ui/cocoa/applescript/window_applescript_test.mm',
'browser/ui/find_bar/find_bar_host_browsertest.cc',
'browser/ui/global_error_service_browsertest.cc',
+ 'browser/ui/gtk/bubble/bubble_gtk_browsertest.cc',
'browser/ui/gtk/view_id_util_browsertest.cc',
'browser/ui/intents/web_intent_picker_controller_browsertest.cc',
'browser/ui/login/login_prompt_browsertest.cc',