diff options
-rw-r--r-- | chrome/browser/ui/gtk/bubble/bubble_gtk.cc | 151 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/bubble/bubble_gtk.h | 25 | ||||
-rw-r--r-- | chrome/browser/ui/gtk/bubble/bubble_gtk_browsertest.cc | 135 | ||||
-rw-r--r-- | chrome/chrome_tests.gypi | 1 |
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', |