diff options
author | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-02 17:08:44 +0000 |
---|---|---|
committer | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-12-02 17:08:44 +0000 |
commit | 9a80065bad1506ac11163d597ace3295ddbfa8cb (patch) | |
tree | d1000277a49442bc02544f47cb18764fbd5d111d /chrome/browser/ui | |
parent | 42503b2e20d59c4444268a500d90f7ef22721437 (diff) | |
download | chromium_src-9a80065bad1506ac11163d597ace3295ddbfa8cb.zip chromium_src-9a80065bad1506ac11163d597ace3295ddbfa8cb.tar.gz chromium_src-9a80065bad1506ac11163d597ace3295ddbfa8cb.tar.bz2 |
Move a bunch more browser stuff around.
toolbar stuff -> ui/toolbar
dock_info -> ui/tabs
window_sizer -> ui
BUG=none
TEST=none
TBR=brettw
Review URL: http://codereview.chromium.org/5544002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@68025 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/ui')
44 files changed, 5432 insertions, 64 deletions
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc index d792d430..b1eb3f0 100644 --- a/chrome/browser/ui/browser.cc +++ b/chrome/browser/ui/browser.cc @@ -37,7 +37,6 @@ #include "chrome/browser/debugger/devtools_manager.h" #include "chrome/browser/debugger/devtools_toggle_action.h" #include "chrome/browser/debugger/devtools_window.h" -#include "chrome/browser/dock_info.h" #include "chrome/browser/dom_ui/filebrowse_ui.h" #include "chrome/browser/dom_ui/options/content_settings_handler.h" #include "chrome/browser/download/download_item.h" @@ -87,9 +86,10 @@ #include "chrome/browser/ui/browser_navigator.h" #include "chrome/browser/ui/find_bar/find_bar.h" #include "chrome/browser/ui/find_bar/find_bar_controller.h" +#include "chrome/browser/ui/tabs/dock_info.h" +#include "chrome/browser/ui/window_sizer.h" #include "chrome/browser/upgrade_detector.h" #include "chrome/browser/web_applications/web_app.h" -#include "chrome/browser/window_sizer.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/content_restriction.h" diff --git a/chrome/browser/ui/browser.h b/chrome/browser/ui/browser.h index b5ff475..77c2146 100644 --- a/chrome/browser/ui/browser.h +++ b/chrome/browser/ui/browser.h @@ -29,7 +29,7 @@ #include "chrome/browser/tabs/tab_strip_model_observer.h" // TODO(beng): remove #include "chrome/browser/tab_contents/page_navigator.h" #include "chrome/browser/tab_contents/tab_contents_delegate.h" -#include "chrome/browser/toolbar_model.h" +#include "chrome/browser/ui/toolbar/toolbar_model.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/notification_registrar.h" #include "chrome/common/page_transition_types.h" diff --git a/chrome/browser/ui/cocoa/back_forward_menu_controller.h b/chrome/browser/ui/cocoa/back_forward_menu_controller.h index 6ec82f6..13bb585 100644 --- a/chrome/browser/ui/cocoa/back_forward_menu_controller.h +++ b/chrome/browser/ui/cocoa/back_forward_menu_controller.h @@ -10,7 +10,7 @@ #include "base/scoped_nsobject.h" #include "base/scoped_ptr.h" -#include "chrome/browser/back_forward_menu_model.h" +#include "chrome/browser/ui/toolbar/back_forward_menu_model.h" @class DelayedMenuButton; diff --git a/chrome/browser/ui/cocoa/back_forward_menu_controller.mm b/chrome/browser/ui/cocoa/back_forward_menu_controller.mm index a3e89b2..9db04cf 100644 --- a/chrome/browser/ui/cocoa/back_forward_menu_controller.mm +++ b/chrome/browser/ui/cocoa/back_forward_menu_controller.mm @@ -7,9 +7,9 @@ #include "base/logging.h" #include "base/scoped_ptr.h" #include "base/sys_string_conversions.h" -#include "chrome/browser/back_forward_menu_model.h" #import "chrome/browser/ui/cocoa/delayedmenu_button.h" #import "chrome/browser/ui/cocoa/event_utils.h" +#include "chrome/browser/ui/toolbar/back_forward_menu_model.h" #include "skia/ext/skia_utils_mac.h" #include "third_party/skia/include/core/SkBitmap.h" diff --git a/chrome/browser/ui/cocoa/browser_window_controller.mm b/chrome/browser/ui/cocoa/browser_window_controller.mm index 82151f0..e96075a 100644 --- a/chrome/browser/ui/cocoa/browser_window_controller.mm +++ b/chrome/browser/ui/cocoa/browser_window_controller.mm @@ -15,8 +15,6 @@ #include "base/sys_string_conversions.h" #include "chrome/app/chrome_command_ids.h" // IDC_* #include "chrome/browser/bookmarks/bookmark_editor.h" -#include "chrome/browser/dock_info.h" -#include "chrome/browser/encoding_menu_controller.h" #include "chrome/browser/google/google_util.h" #include "chrome/browser/location_bar.h" #include "chrome/browser/profile.h" @@ -57,7 +55,9 @@ #import "chrome/browser/ui/cocoa/tab_view.h" #import "chrome/browser/ui/cocoa/tabpose_window.h" #import "chrome/browser/ui/cocoa/toolbar_controller.h" -#include "chrome/browser/window_sizer.h" +#include "chrome/browser/ui/tabs/dock_info.h" +#include "chrome/browser/ui/toolbar/encoding_menu_controller.h" +#include "chrome/browser/ui/window_sizer.h" #include "chrome/common/url_constants.h" #include "grit/generated_resources.h" #include "grit/locale_settings.h" diff --git a/chrome/browser/ui/cocoa/encoding_menu_controller_delegate_mac.mm b/chrome/browser/ui/cocoa/encoding_menu_controller_delegate_mac.mm index 9045540..44d66b1 100644 --- a/chrome/browser/ui/cocoa/encoding_menu_controller_delegate_mac.mm +++ b/chrome/browser/ui/cocoa/encoding_menu_controller_delegate_mac.mm @@ -9,9 +9,9 @@ #include "base/string16.h" #include "base/sys_string_conversions.h" #include "chrome/app/chrome_command_ids.h" -#include "chrome/browser/encoding_menu_controller.h" #include "chrome/browser/profile.h" #include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/toolbar/encoding_menu_controller.h" namespace { diff --git a/chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h b/chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h index 7675165d..d89ec36 100644 --- a/chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h +++ b/chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h @@ -18,7 +18,7 @@ #include "chrome/browser/extensions/image_loading_tracker.h" #include "chrome/browser/first_run/first_run.h" #include "chrome/browser/location_bar.h" -#include "chrome/browser/toolbar_model.h" +#include "chrome/browser/ui/toolbar/toolbar_model.h" #include "chrome/common/content_settings_types.h" @class AutocompleteTextField; diff --git a/chrome/browser/ui/cocoa/toolbar_controller.mm b/chrome/browser/ui/cocoa/toolbar_controller.mm index aa1d521..1383e08 100644 --- a/chrome/browser/ui/cocoa/toolbar_controller.mm +++ b/chrome/browser/ui/cocoa/toolbar_controller.mm @@ -22,9 +22,7 @@ #include "chrome/browser/search_engines/template_url_model.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/themes/browser_theme_provider.h" -#include "chrome/browser/toolbar_model.h" #include "chrome/browser/upgrade_detector.h" -#include "chrome/browser/wrench_menu_model.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_window.h" #import "chrome/browser/ui/cocoa/accelerators_cocoa.h" @@ -43,6 +41,8 @@ #import "chrome/browser/ui/cocoa/toolbar_view.h" #import "chrome/browser/ui/cocoa/view_id_util.h" #import "chrome/browser/ui/cocoa/wrench_menu_controller.h" +#include "chrome/browser/ui/toolbar/toolbar_model.h" +#include "chrome/browser/ui/toolbar/wrench_menu_model.h" #include "chrome/common/notification_details.h" #include "chrome/common/notification_observer.h" #include "chrome/common/notification_service.h" diff --git a/chrome/browser/ui/cocoa/wrench_menu_controller.mm b/chrome/browser/ui/cocoa/wrench_menu_controller.mm index d4a9872..3abd3aa 100644 --- a/chrome/browser/ui/cocoa/wrench_menu_controller.mm +++ b/chrome/browser/ui/cocoa/wrench_menu_controller.mm @@ -12,7 +12,7 @@ #import "chrome/browser/ui/cocoa/toolbar_controller.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_window.h" -#include "chrome/browser/wrench_menu_model.h" +#include "chrome/browser/ui/toolbar/wrench_menu_model.h" #include "chrome/common/notification_observer.h" #include "chrome/common/notification_service.h" #include "chrome/common/notification_source.h" diff --git a/chrome/browser/ui/cocoa/wrench_menu_controller_unittest.mm b/chrome/browser/ui/cocoa/wrench_menu_controller_unittest.mm index 243b2af..42be865 100644 --- a/chrome/browser/ui/cocoa/wrench_menu_controller_unittest.mm +++ b/chrome/browser/ui/cocoa/wrench_menu_controller_unittest.mm @@ -4,12 +4,12 @@ #include "base/scoped_nsobject.h" #include "chrome/app/chrome_command_ids.h" -#include "chrome/browser/wrench_menu_model.h" #include "chrome/browser/ui/cocoa/browser_test_helper.h" #import "chrome/browser/ui/cocoa/cocoa_test_helper.h" #import "chrome/browser/ui/cocoa/toolbar_controller.h" #import "chrome/browser/ui/cocoa/wrench_menu_controller.h" #import "chrome/browser/ui/cocoa/view_resizer_pong.h" +#include "chrome/browser/ui/toolbar/wrench_menu_model.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/platform_test.h" diff --git a/chrome/browser/ui/tabs/dock_info.cc b/chrome/browser/ui/tabs/dock_info.cc new file mode 100644 index 0000000..97fe29d --- /dev/null +++ b/chrome/browser/ui/tabs/dock_info.cc @@ -0,0 +1,268 @@ +// Copyright (c) 2006-2008 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/ui/tabs/dock_info.h" + +#if defined(TOOLKIT_VIEWS) +#include "chrome/browser/views/tabs/tab.h" +#else +#include "chrome/browser/gtk/tabs/tab_gtk.h" +#endif + +namespace { + +// Distance in pixels between the hotspot and when the hint should be shown. +const int kHotSpotDeltaX = 120; +const int kHotSpotDeltaY = 120; + +// Size of the popup window. +const int kPopupWidth = 70; +const int kPopupHeight = 70; + +} // namespace + +// static +DockInfo::Factory* DockInfo::factory_ = NULL; + +// static +bool DockInfo::IsCloseToPoint(const gfx::Point& screen_loc, + int x, + int y, + bool* in_enable_area) { + int delta_x = abs(x - screen_loc.x()); + int delta_y = abs(y - screen_loc.y()); + *in_enable_area = (delta_x < kPopupWidth / 2 && delta_y < kPopupHeight / 2); + return *in_enable_area || (delta_x < kHotSpotDeltaX && + delta_y < kHotSpotDeltaY); +} + +// static +bool DockInfo::IsCloseToMonitorPoint(const gfx::Point& screen_loc, + int x, + int y, + DockInfo::Type type, + bool* in_enable_area) { + // Because the monitor relative positions are aligned with the edge of the + // monitor these need to be handled differently. + int delta_x = abs(x - screen_loc.x()); + int delta_y = abs(y - screen_loc.y()); + + int enable_delta_x = kPopupWidth / 2; + int enable_delta_y = kPopupHeight / 2; + int hot_spot_delta_x = kHotSpotDeltaX; + int hot_spot_delta_y = kHotSpotDeltaY; + + switch (type) { + case DockInfo::LEFT_HALF: + case DockInfo::RIGHT_HALF: + enable_delta_x += enable_delta_x; + hot_spot_delta_x += hot_spot_delta_x; + break; + + + case DockInfo::MAXIMIZE: { + // Make the maximize height smaller than the tab height to avoid showing + // the dock indicator when close to maximized browser. +#if defined(TOOLKIT_VIEWS) + hot_spot_delta_y = Tab::GetMinimumUnselectedSize().height() - 1; +#else + hot_spot_delta_y = TabGtk::GetMinimumUnselectedSize().height() - 1; +#endif + enable_delta_y = hot_spot_delta_y / 2; + break; + } + case DockInfo::BOTTOM_HALF: + enable_delta_y += enable_delta_y; + hot_spot_delta_y += hot_spot_delta_y; + break; + + default: + NOTREACHED(); + return false; + } + *in_enable_area = (delta_x < enable_delta_x && delta_y < enable_delta_y); + bool result = (*in_enable_area || (delta_x < hot_spot_delta_x && + delta_y < hot_spot_delta_y)); + if (type != DockInfo::MAXIMIZE) + return result; + + // Make the hot spot/enable spot for maximized windows the whole top of the + // monitor. + int max_delta_y = abs(screen_loc.y() - y); + *in_enable_area = (*in_enable_area || (max_delta_y < enable_delta_y)); + return *in_enable_area || (max_delta_y < hot_spot_delta_y); +} + +// static +int DockInfo::popup_width() { + return kPopupWidth; +} + +// static +int DockInfo::popup_height() { + return kPopupHeight; +} + +bool DockInfo::IsValidForPoint(const gfx::Point& screen_point) { + if (type_ == NONE) + return false; + + if (window_) { + return IsCloseToPoint(screen_point, hot_spot_.x(), hot_spot_.y(), + &in_enable_area_); + } + + return monitor_bounds_.Contains(screen_point) && + IsCloseToMonitorPoint(screen_point, hot_spot_.x(), + hot_spot_.y(), type_, &in_enable_area_); +} + +bool DockInfo::GetNewWindowBounds(gfx::Rect* new_window_bounds, + bool* maximize_new_window) const { + if (type_ == NONE || !in_enable_area_) + return false; + + gfx::Rect window_bounds; + if (window_ && !GetWindowBounds(&window_bounds)) + return false; + + int half_m_width = (monitor_bounds_.right() - monitor_bounds_.x()) / 2; + int half_m_height = (monitor_bounds_.bottom() - monitor_bounds_.y()) / 2; + + *maximize_new_window = false; + + switch (type_) { + case LEFT_OF_WINDOW: + new_window_bounds->SetRect(monitor_bounds_.x(), window_bounds.y(), + half_m_width, window_bounds.height()); + break; + + case RIGHT_OF_WINDOW: + new_window_bounds->SetRect(monitor_bounds_.x() + half_m_width, + window_bounds.y(), half_m_width, + window_bounds.height()); + break; + + case TOP_OF_WINDOW: + new_window_bounds->SetRect(window_bounds.x(), monitor_bounds_.y(), + window_bounds.width(), half_m_height); + break; + + case BOTTOM_OF_WINDOW: + new_window_bounds->SetRect(window_bounds.x(), + monitor_bounds_.y() + half_m_height, + window_bounds.width(), half_m_height); + break; + + case LEFT_HALF: + new_window_bounds->SetRect(monitor_bounds_.x(), monitor_bounds_.y(), + half_m_width, monitor_bounds_.height()); + break; + + case RIGHT_HALF: + new_window_bounds->SetRect(monitor_bounds_.right() - half_m_width, + monitor_bounds_.y(), half_m_width, monitor_bounds_.height()); + break; + + case BOTTOM_HALF: + new_window_bounds->SetRect(monitor_bounds_.x(), + monitor_bounds_.y() + half_m_height, + monitor_bounds_.width(), half_m_height); + break; + + case MAXIMIZE: + *maximize_new_window = true; + break; + + default: + NOTREACHED(); + } + return true; +} + +void DockInfo::AdjustOtherWindowBounds() const { + if (!in_enable_area_) + return; + + gfx::Rect window_bounds; + if (!window_ || !GetWindowBounds(&window_bounds)) + return; + + gfx::Rect other_window_bounds; + int half_m_width = (monitor_bounds_.right() - monitor_bounds_.x()) / 2; + int half_m_height = (monitor_bounds_.bottom() - monitor_bounds_.y()) / 2; + + switch (type_) { + case LEFT_OF_WINDOW: + other_window_bounds.SetRect(monitor_bounds_.x() + half_m_width, + window_bounds.y(), half_m_width, + window_bounds.height()); + break; + + case RIGHT_OF_WINDOW: + other_window_bounds.SetRect(monitor_bounds_.x(), window_bounds.y(), + half_m_width, window_bounds.height()); + break; + + case TOP_OF_WINDOW: + other_window_bounds.SetRect(window_bounds.x(), + monitor_bounds_.y() + half_m_height, + window_bounds.width(), half_m_height); + break; + + case BOTTOM_OF_WINDOW: + other_window_bounds.SetRect(window_bounds.x(), monitor_bounds_.y(), + window_bounds.width(), half_m_height); + break; + + default: + return; + } + + SizeOtherWindowTo(other_window_bounds); +} + +gfx::Rect DockInfo::GetPopupRect() const { + int x = hot_spot_.x() - popup_width() / 2; + int y = hot_spot_.y() - popup_height() / 2; + switch (type_) { + case LEFT_OF_WINDOW: + case RIGHT_OF_WINDOW: + case TOP_OF_WINDOW: + case BOTTOM_OF_WINDOW: { + // Constrain the popup to the monitor's bounds. + gfx::Rect ideal_bounds(x, y, popup_width(), popup_height()); + ideal_bounds = ideal_bounds.AdjustToFit(monitor_bounds_); + return ideal_bounds; + } + case DockInfo::MAXIMIZE: + y += popup_height() / 2; + break; + case DockInfo::LEFT_HALF: + x += popup_width() / 2; + break; + case DockInfo::RIGHT_HALF: + x -= popup_width() / 2; + break; + case DockInfo::BOTTOM_HALF: + y -= popup_height() / 2; + break; + + default: + NOTREACHED(); + } + return gfx::Rect(x, y, popup_width(), popup_height()); +} + +bool DockInfo::CheckMonitorPoint(const gfx::Point& screen_loc, + int x, + int y, + Type type) { + if (IsCloseToMonitorPoint(screen_loc, x, y, type, &in_enable_area_)) { + hot_spot_.SetPoint(x, y); + type_ = type; + return true; + } + return false; +} diff --git a/chrome/browser/ui/tabs/dock_info.h b/chrome/browser/ui/tabs/dock_info.h new file mode 100644 index 0000000..fa45b1a --- /dev/null +++ b/chrome/browser/ui/tabs/dock_info.h @@ -0,0 +1,190 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_UI_TABS_DOCK_INFO_H_ +#define CHROME_BROWSER_UI_TABS_DOCK_INFO_H_ +#pragma once + +#include <set> + +#include "gfx/native_widget_types.h" +#include "gfx/point.h" +#include "gfx/rect.h" + +// DockInfo is used to do determine possible dock locations for a dragged +// tab. To use DockInfo invoke GetDockInfoAtPoint. This returns a new +// DockInfo whose type indicates the type of dock that should occur based +// on the screen location. As the user drags the mouse around invoke +// IsValidForPoint, this returns true if the DockInfo is still valid for the +// new location. If the DockInfo is not valid, invoke GetDockInfoAtPoint to +// get the new DockInfo. Use GetNewWindowBounds to get the position to place +// the new window at. +// +// DockInfos are cheap and explicitly allow copy and assignment operators. +class DockInfo { + public: + class Factory { + public: + virtual DockInfo GetDockInfoAtPoint( + const gfx::Point& screen_point, + const std::set<gfx::NativeView>& ignore) = 0; + + virtual gfx::NativeWindow GetLocalProcessWindowAtPoint( + const gfx::Point& screen_point, + const std::set<gfx::NativeView>& ignore) = 0; + + protected: + virtual ~Factory() {} + }; + + // Possible dock positions. + enum Type { + // Indicates there is no valid dock position for the current location. + NONE, + + // Indicates the new window should be positioned relative to the window + // identified by window(). + LEFT_OF_WINDOW, + RIGHT_OF_WINDOW, + BOTTOM_OF_WINDOW, + TOP_OF_WINDOW, + + // Indicates the window should be maximized on the monitor at hot_spot. + MAXIMIZE, + + // Indicates the window should be docked to a specific side of the monitor. + LEFT_HALF, + RIGHT_HALF, + BOTTOM_HALF + }; + + DockInfo() : type_(NONE), window_(NULL), in_enable_area_(false) {} + + // Returns true if |screen_loc| is close to the hotspot at |x|, |y|. If the + // point is close enough to the hotspot true is returned and |in_enable_area| + // is set appropriately. + static bool IsCloseToPoint(const gfx::Point& screen_loc, + int x, + int y, + bool* in_enable_area); + + // Variant of IsCloseToPoint used for monitor relative positions. + static bool IsCloseToMonitorPoint(const gfx::Point& screen_loc, + int x, + int y, + DockInfo::Type type, + bool* in_enable_area); + + // Sets the factory. + static void set_factory(Factory* factory) { factory_ = factory; } + + // Size of the popup window shown to indicate a valid dock location. + static int popup_width(); + static int popup_height(); + + // Returns the DockInfo for the specified point |screen_point|. |ignore| + // contains the set of windows to ignore from consideration. This contains the + // dragged window as well as any windows showing possible dock locations. + // + // If there is no docking position for the specified location the returned + // DockInfo has a type of NONE. + // + // If a Factory has been set, the method of the same name is invoked on the + // Factory to determine the DockInfo. + static DockInfo GetDockInfoAtPoint(const gfx::Point& screen_point, + const std::set<gfx::NativeView>& ignore); + + // Returns the top most window from the current process at |screen_point|. + // See GetDockInfoAtPoint for a description of |ignore|. This returns NULL if + // there is no window from the current process at |screen_point|, or another + // window obscures the topmost window from our process at |screen_point|. + // + // If a Factory has been set, the method of the same name is invoked on the + // Factory to determine the DockInfo. + static gfx::NativeWindow GetLocalProcessWindowAtPoint( + const gfx::Point& screen_point, + const std::set<gfx::NativeView>& ignore); + + // Returns true if this DockInfo is valid for the specified point. This + // resets in_enable_area based on the new location. + bool IsValidForPoint(const gfx::Point& screen_point); + + // Returns the bounds for the new window in |new_window_bounds|. If the new + // window is to be maximized, |maximize_new_window| is set to true. + // This returns true if type is other than NONE or the mouse isn't in the + // enable area, false otherwise. + bool GetNewWindowBounds(gfx::Rect* new_window_bounds, + bool* maximize_new_window) const; + + // Adjust the bounds of the other window during docking. Does nothing if type + // is NONE, in_enable_are is false, or the type is not window relative. + void AdjustOtherWindowBounds() const; + + // Type of docking to occur. + void set_type(Type type) { type_ = type; } + Type type() const { return type_; } + + // The window to dock too. Is null for dock types that are relative to the + // monitor. + void set_window(gfx::NativeWindow window) { window_ = window; } + gfx::NativeWindow window() const { return window_; } + + // The location of the hotspot. + void set_hot_spot(const gfx::Point& hot_spot) { hot_spot_ = hot_spot; } + const gfx::Point& hot_spot() const { return hot_spot_; } + + // Bounds of the monitor. + void set_monitor_bounds(const gfx::Rect& monitor_bounds) { + monitor_bounds_ = monitor_bounds; + } + const gfx::Rect& monitor_bounds() const { return monitor_bounds_; } + + // Returns the bounds of the window to show the indicator for. + gfx::Rect GetPopupRect() const; + + // Returns true if the drop should result in docking. DockInfo maintains two + // states (as indicated by this boolean): + // 1. The mouse is close enough to the hot spot such that a visual indicator + // should be shown, but if the user releases the mouse docking shouldn't + // result. This corresponds to a value of false for in_enable_area. + // 2. The mouse is close enough to the hot spot such that releasing the mouse + // should result in docking. This corresponds to a value of true for + // in_enable_area. + void set_in_enable_area(bool in_enable_area) { + in_enable_area_ = in_enable_area; + } + bool in_enable_area() const { return in_enable_area_; } + + // Returns true if |other| is considered equal to this. Two DockInfos are + // considered equal if they have the same type and same window. + bool equals(const DockInfo& other) const { + return type_ == other.type_ && window_ == other.window_ && + monitor_bounds_ == other.monitor_bounds_; + } + + // If screen_loc is close enough to the hot spot given by |x| and |y|, the + // type and hot_spot are set from the supplied parameters. This is used + // internally, there is no need to invoke this otherwise. + bool CheckMonitorPoint(const gfx::Point& screen_loc, + int x, + int y, + Type type); + + private: + // Returns the bounds of the window. + bool GetWindowBounds(gfx::Rect* bounds) const; + void SizeOtherWindowTo(const gfx::Rect& bounds) const; + + Type type_; + gfx::NativeWindow window_; + gfx::Point hot_spot_; + gfx::Rect monitor_bounds_; + bool in_enable_area_; + + // Factory that creates DockInfos. By default this is NULL, which gives the + // default behavior. + static Factory* factory_; +}; + +#endif // CHROME_BROWSER_UI_TABS_DOCK_INFO_H_ diff --git a/chrome/browser/ui/tabs/dock_info_gtk.cc b/chrome/browser/ui/tabs/dock_info_gtk.cc new file mode 100644 index 0000000..3fb2778 --- /dev/null +++ b/chrome/browser/ui/tabs/dock_info_gtk.cc @@ -0,0 +1,219 @@ +// Copyright (c) 2009 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/ui/tabs/dock_info.h" + +#include <gtk/gtk.h> + +#include "base/logging.h" +#include "base/task.h" +#include "chrome/browser/gtk/browser_window_gtk.h" +#include "chrome/browser/gtk/gtk_util.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" +#include "gfx/native_widget_types.h" + +//////////////////////////////////////////////////////////////////////////////// +// BaseWindowFinder +// +// Base class used to locate a window. A subclass need only override +// ShouldStopIterating to determine when iteration should stop. +class BaseWindowFinder : public x11_util::EnumerateWindowsDelegate { + public: + explicit BaseWindowFinder(const std::set<GtkWidget*>& ignore) { + std::set<GtkWidget*>::iterator iter; + for (iter = ignore.begin(); iter != ignore.end(); iter++) { + XID xid = x11_util::GetX11WindowFromGtkWidget(*iter); + ignore_.insert(xid); + } + } + + virtual ~BaseWindowFinder() {} + + protected: + // Returns true if |window| is in the ignore list. + bool ShouldIgnoreWindow(XID window) { + return (ignore_.find(window) != ignore_.end()); + } + + // Returns true if iteration should stop, false otherwise. + virtual bool ShouldStopIterating(XID window) { + return false; + } + + private: + std::set<XID> ignore_; + + DISALLOW_COPY_AND_ASSIGN(BaseWindowFinder); +}; + +//////////////////////////////////////////////////////////////////////////////// +// TopMostFinder +// +// Helper class to determine if a particular point of a window is not obscured +// by another window. +class TopMostFinder : public BaseWindowFinder { + public: + // Returns true if |window| is not obscured by another window at the + // location |screen_loc|, not including the windows in |ignore|. + static bool IsTopMostWindowAtPoint(XID window, + const gfx::Point& screen_loc, + const std::set<GtkWidget*>& ignore) { + TopMostFinder finder(window, screen_loc, ignore); + return finder.is_top_most_; + } + + protected: + virtual bool ShouldStopIterating(XID window) { + if (BaseWindowFinder::ShouldIgnoreWindow(window)) + return false; + + if (window == target_) { + // Window is topmost, stop iterating. + is_top_most_ = true; + return true; + } + + if (!x11_util::IsWindowVisible(window)) { + // The window isn't visible, keep iterating. + return false; + } + + gfx::Rect rect; + if (x11_util::GetWindowRect(window, &rect) && rect.Contains(screen_loc_)) { + // At this point we haven't found our target window, so this window is + // higher in the z-order than the target window. If this window contains + // the point, then we can stop the search now because this window is + // obscuring the target window at this point. + return true; + } + + return false; + } + + private: + TopMostFinder(XID window, + const gfx::Point& screen_loc, + const std::set<GtkWidget*>& ignore) + : BaseWindowFinder(ignore), + target_(window), + screen_loc_(screen_loc), + is_top_most_(false) { + gtk_util::EnumerateTopLevelWindows(this); + } + + // The window we're looking for. + XID target_; + + // Location of window to find. + gfx::Point screen_loc_; + + // Is target_ the top most window? This is initially false but set to true + // in ShouldStopIterating if target_ is passed in. + bool is_top_most_; + + DISALLOW_COPY_AND_ASSIGN(TopMostFinder); +}; + +//////////////////////////////////////////////////////////////////////////////// +// LocalProcessWindowFinder +// +// Helper class to determine if a particular point of a window from our process +// is not obscured by another window. +class LocalProcessWindowFinder : public BaseWindowFinder { + public: + // Returns the XID from our process at screen_loc that is not obscured by + // another window. Returns 0 otherwise. + static XID GetProcessWindowAtPoint(const gfx::Point& screen_loc, + const std::set<GtkWidget*>& ignore) { + LocalProcessWindowFinder finder(screen_loc, ignore); + if (finder.result_ && + TopMostFinder::IsTopMostWindowAtPoint(finder.result_, screen_loc, + ignore)) { + return finder.result_; + } + return 0; + } + + protected: + virtual bool ShouldStopIterating(XID window) { + if (BaseWindowFinder::ShouldIgnoreWindow(window)) + return false; + + // Check if this window is in our process. + if (!BrowserWindowGtk::GetBrowserWindowForXID(window)) + return false; + + if (!x11_util::IsWindowVisible(window)) + return false; + + gfx::Rect rect; + if (x11_util::GetWindowRect(window, &rect) && rect.Contains(screen_loc_)) { + result_ = window; + return true; + } + + return false; + } + + private: + LocalProcessWindowFinder(const gfx::Point& screen_loc, + const std::set<GtkWidget*>& ignore) + : BaseWindowFinder(ignore), + screen_loc_(screen_loc), + result_(0) { + gtk_util::EnumerateTopLevelWindows(this); + } + + // Position of the mouse. + gfx::Point screen_loc_; + + // The resulting window. This is initially null but set to true in + // ShouldStopIterating if an appropriate window is found. + XID result_; + + DISALLOW_COPY_AND_ASSIGN(LocalProcessWindowFinder); +}; + +// static +DockInfo DockInfo::GetDockInfoAtPoint(const gfx::Point& screen_point, + const std::set<GtkWidget*>& ignore) { + if (factory_) + return factory_->GetDockInfoAtPoint(screen_point, ignore); + + NOTIMPLEMENTED(); + return DockInfo(); +} + +// static +GtkWindow* DockInfo::GetLocalProcessWindowAtPoint( + const gfx::Point& screen_point, + const std::set<GtkWidget*>& ignore) { + if (factory_) + return factory_->GetLocalProcessWindowAtPoint(screen_point, ignore); + +#if defined(OS_CHROMEOS) || defined(TOOLKIT_VIEWS) + return NULL; +#else + XID xid = + LocalProcessWindowFinder::GetProcessWindowAtPoint(screen_point, ignore); + return BrowserWindowGtk::GetBrowserWindowForXID(xid); +#endif +} + +bool DockInfo::GetWindowBounds(gfx::Rect* bounds) const { + if (!window()) + return false; + + int x, y, w, h; + gtk_window_get_position(window(), &x, &y); + gtk_window_get_size(window(), &w, &h); + bounds->SetRect(x, y, w, h); + return true; +} + +void DockInfo::SizeOtherWindowTo(const gfx::Rect& bounds) const { + gtk_window_move(window(), bounds.x(), bounds.y()); + gtk_window_resize(window(), bounds.width(), bounds.height()); +} diff --git a/chrome/browser/ui/tabs/dock_info_mac.cc b/chrome/browser/ui/tabs/dock_info_mac.cc new file mode 100644 index 0000000..5c2aeb7 --- /dev/null +++ b/chrome/browser/ui/tabs/dock_info_mac.cc @@ -0,0 +1,15 @@ +// 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/ui/tabs/dock_info.h" + +bool DockInfo::GetNewWindowBounds(gfx::Rect* new_window_bounds, + bool* maximize_new_window) const { + // TODO(pinkerton): Implement DockInfo, http://crbug.com/9274. + return true; +} + +void DockInfo::AdjustOtherWindowBounds() const { + // TODO(pinkerton): Implement DockInfo, http://crbug.com/9274. +} diff --git a/chrome/browser/ui/tabs/dock_info_unittest.cc b/chrome/browser/ui/tabs/dock_info_unittest.cc new file mode 100644 index 0000000..ee4557e --- /dev/null +++ b/chrome/browser/ui/tabs/dock_info_unittest.cc @@ -0,0 +1,191 @@ +// 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 "base/basictypes.h" +#include "chrome/browser/ui/tabs/dock_info.h" +#include "gfx/point.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { +// Distance in pixels between the hotspot and when the hint should be shown. +const int kHotSpotDeltaX = 120; +const int kHotSpotDeltaY = 120; +// Size of the popup window. +const int kPopupWidth = 70; +const int kPopupHeight = 70; +} // namespace + +TEST(DockInfoTest, IsCloseToPoint) { + bool in_enable_area; + gfx::Point screen_loc[] = { + gfx::Point(0, 0), + gfx::Point(kPopupWidth/2 - 1, kPopupHeight/2 - 1), + gfx::Point(kPopupWidth/2, kPopupHeight/2), + gfx::Point(kHotSpotDeltaX - 1, kHotSpotDeltaY - 1), + gfx::Point(kHotSpotDeltaX, kHotSpotDeltaY), + gfx::Point(-kHotSpotDeltaX, -kHotSpotDeltaY) + }; + gfx::Point hotspot[] = { + gfx::Point(0, 0), + gfx::Point(0, 0), + gfx::Point(kPopupWidth, kPopupHeight), + gfx::Point(0, 0), + gfx::Point(2*kHotSpotDeltaX, 2*kHotSpotDeltaY), + gfx::Point(0, 0) + }; + bool expected_results[] = { + true, true, true, true, false, false + }; + bool expected_in_enable_area[] = { + true, true, false, false, false, false + }; + + for (size_t i = 0; i < arraysize(expected_results); ++i) { + bool result = DockInfo::IsCloseToPoint( + screen_loc[i], hotspot[i].x(), hotspot[i].y(), &in_enable_area); + EXPECT_EQ(expected_results[i], result); + EXPECT_EQ(expected_in_enable_area[i], in_enable_area); + } +} + +TEST(DockInfoTest, IsCloseToMonitorPoint) { + bool in_enable_area; + gfx::Point screen_loc[] = { + gfx::Point(0, 0), + gfx::Point(kPopupWidth - 1, kPopupHeight/2 -1), + gfx::Point(kPopupWidth, kPopupHeight/2 - 1), + gfx::Point(kPopupWidth - 1, kPopupHeight), + gfx::Point(2*kHotSpotDeltaX, kHotSpotDeltaY - 1), + gfx::Point(2*kHotSpotDeltaX - 1, kHotSpotDeltaY), + gfx::Point(2*kHotSpotDeltaX - 1, kHotSpotDeltaY), + gfx::Point(0, 0), + gfx::Point(kPopupWidth/2 - 1, kPopupHeight - 1), + gfx::Point(kPopupWidth/2 - 1, kPopupHeight), + gfx::Point(kPopupWidth/2, kPopupHeight - 1), + gfx::Point(kHotSpotDeltaX - 1, 2*kHotSpotDeltaY), + gfx::Point(kHotSpotDeltaX, 2*kHotSpotDeltaY - 1), + }; + gfx::Point hotspot = gfx::Point(0, 0); + DockInfo::Type type[] = { + DockInfo::LEFT_HALF, + DockInfo::LEFT_HALF, + DockInfo::LEFT_HALF, + DockInfo::LEFT_HALF, + DockInfo::LEFT_HALF, + DockInfo::LEFT_HALF, + DockInfo::RIGHT_HALF, + DockInfo::BOTTOM_HALF, + DockInfo::BOTTOM_HALF, + DockInfo::BOTTOM_HALF, + DockInfo::BOTTOM_HALF, + DockInfo::BOTTOM_HALF, + DockInfo::BOTTOM_HALF, + }; + bool expected_results[] = { + true, true, true, true, false, false, false, + true, true, true, true, false, false + }; + bool expected_in_enable_area[] = { + true, true, false, false, false, false, false, + true, true, false, false, false, false + }; + + for (size_t i = 0; i < arraysize(expected_results); ++i) { + bool result = DockInfo::IsCloseToMonitorPoint( + screen_loc[i], hotspot.x(), hotspot.y(), type[i], &in_enable_area); + EXPECT_EQ(expected_results[i], result); + EXPECT_EQ(expected_in_enable_area[i], in_enable_area); + } +} + +TEST(DockInfoTest, IsValidForPoint) { + DockInfo d; + EXPECT_EQ(false, d.IsValidForPoint(gfx::Point(0, 0))); + d.set_monitor_bounds(gfx::Rect(0, 0, kPopupWidth, kPopupHeight)); + d.set_hot_spot(gfx::Point(0, 0)); + d.set_type(DockInfo::LEFT_HALF); + + gfx::Point screen_point[] = { + gfx::Point(0, 0), + gfx::Point(kPopupWidth + 1, kPopupHeight + 1), + gfx::Point(2 * kHotSpotDeltaX, kHotSpotDeltaY), + }; + + bool expected_result[] = { + true, false, false + }; + + for (size_t i = 0; i < arraysize(expected_result); ++i) { + EXPECT_EQ(expected_result[i], d.IsValidForPoint(screen_point[i])); + } +} + +TEST(DockInfoTest, equals) { + DockInfo d; + DockInfo dd; + EXPECT_EQ(true, d.equals(dd)); + d.set_type(DockInfo::MAXIMIZE); + EXPECT_EQ(false, d.equals(dd)); +} + +TEST(DockInfoTest, CheckMonitorPoint) { + DockInfo d; + gfx::Point screen_loc[] = { + gfx::Point(0, 0), + gfx::Point(2 * kHotSpotDeltaX, kHotSpotDeltaY), + gfx::Point(2 * kHotSpotDeltaX, kHotSpotDeltaY), + }; + + DockInfo::Type type[] = { + DockInfo::LEFT_HALF, + DockInfo::RIGHT_HALF, + DockInfo::MAXIMIZE + }; + + bool expected_result[] = { + true, false, false + }; + + for (size_t i = 0; i < arraysize(expected_result); ++i) { + bool result = d.CheckMonitorPoint(screen_loc[i], 0, 0, type[i]); + EXPECT_EQ(result, expected_result[i]); + if (result == true) { + EXPECT_EQ(0, d.hot_spot().x()); + EXPECT_EQ(0, d.hot_spot().y()); + EXPECT_EQ(type[i], d.type()); + } + } +} + +TEST(DockInfoTest, GetPopupRect) { + DockInfo d; + d.set_hot_spot(gfx::Point(kPopupWidth, kPopupHeight)); + DockInfo::Type type[] = { + DockInfo::MAXIMIZE, + DockInfo::LEFT_HALF, + DockInfo::RIGHT_HALF, + DockInfo::BOTTOM_HALF, + }; + int expected_x[] = { + kPopupWidth/2, + kPopupWidth, + 0, + kPopupWidth/2 + }; + int expected_y[] = { + kPopupHeight, + kPopupHeight/2, + kPopupHeight/2, + 0 + }; + + for (size_t i = 0; i < arraysize(type); ++i) { + d.set_type(type[i]); + gfx::Rect result = d.GetPopupRect(); + EXPECT_EQ(expected_x[i], result.x()); + EXPECT_EQ(expected_y[i], result.y()); + EXPECT_EQ(kPopupWidth, result.width()); + EXPECT_EQ(kPopupHeight, result.height()); + } +} diff --git a/chrome/browser/ui/tabs/dock_info_win.cc b/chrome/browser/ui/tabs/dock_info_win.cc new file mode 100644 index 0000000..dee78cf --- /dev/null +++ b/chrome/browser/ui/tabs/dock_info_win.cc @@ -0,0 +1,325 @@ +// 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/ui/tabs/dock_info.h" + +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/views/frame/browser_view.h" +#include "chrome/browser/views/tabs/tab.h" + +namespace { + +// BaseWindowFinder ----------------------------------------------------------- + +// Base class used to locate a window. This is intended to be used with the +// various win32 functions that iterate over windows. +// +// A subclass need only override ShouldStopIterating to determine when +// iteration should stop. +class BaseWindowFinder { + public: + // Creates a BaseWindowFinder with the specified set of HWNDs to ignore. + explicit BaseWindowFinder(const std::set<HWND>& ignore) : ignore_(ignore) {} + virtual ~BaseWindowFinder() {} + + protected: + // Returns true if iteration should stop, false if iteration should continue. + virtual bool ShouldStopIterating(HWND window) = 0; + + static BOOL CALLBACK WindowCallbackProc(HWND hwnd, LPARAM lParam) { + BaseWindowFinder* finder = reinterpret_cast<BaseWindowFinder*>(lParam); + if (finder->ignore_.find(hwnd) != finder->ignore_.end()) + return TRUE; + + return finder->ShouldStopIterating(hwnd) ? FALSE : TRUE; + } + + private: + const std::set<HWND>& ignore_; + + DISALLOW_COPY_AND_ASSIGN(BaseWindowFinder); +}; + +// TopMostFinder -------------------------------------------------------------- + +// Helper class to determine if a particular point of a window is not obscured +// by another window. +class TopMostFinder : public BaseWindowFinder { + public: + // Returns true if |window| is the topmost window at the location + // |screen_loc|, not including the windows in |ignore|. + static bool IsTopMostWindowAtPoint(HWND window, + const gfx::Point& screen_loc, + const std::set<HWND>& ignore) { + TopMostFinder finder(window, screen_loc, ignore); + return finder.is_top_most_; + } + + virtual bool ShouldStopIterating(HWND hwnd) { + if (hwnd == target_) { + // Window is topmost, stop iterating. + is_top_most_ = true; + return true; + } + + if (!IsWindowVisible(hwnd)) { + // The window isn't visible, keep iterating. + return false; + } + + RECT r; + if (!GetWindowRect(hwnd, &r) || !PtInRect(&r, screen_loc_.ToPOINT())) { + // The window doesn't contain the point, keep iterating. + return false; + } + + LONG ex_styles = GetWindowLong(hwnd, GWL_EXSTYLE); + if (ex_styles & WS_EX_TRANSPARENT || ex_styles & WS_EX_LAYERED) { + // Mouse events fall through WS_EX_TRANSPARENT windows, so we ignore them. + // + // WS_EX_LAYERED is trickier. Apps like Switcher create a totally + // transparent WS_EX_LAYERED window that is always on top. If we don't + // ignore WS_EX_LAYERED windows and there are totally transparent + // WS_EX_LAYERED windows then there are effectively holes on the screen + // that the user can't reattach tabs to. So we ignore them. This is a bit + // problematic in so far as WS_EX_LAYERED windows need not be totally + // transparent in which case we treat chrome windows as not being obscured + // when they really are, but this is better than not being able to + // reattach tabs. + return false; + } + + // hwnd is at the point. Make sure the point is within the windows region. + if (GetWindowRgn(hwnd, tmp_region_.Get()) == ERROR) { + // There's no region on the window and the window contains the point. Stop + // iterating. + return true; + } + + // The region is relative to the window's rect. + BOOL is_point_in_region = PtInRegion(tmp_region_.Get(), + screen_loc_.x() - r.left, screen_loc_.y() - r.top); + tmp_region_ = CreateRectRgn(0, 0, 0, 0); + // Stop iterating if the region contains the point. + return !!is_point_in_region; + } + + private: + TopMostFinder(HWND window, + const gfx::Point& screen_loc, + const std::set<HWND>& ignore) + : BaseWindowFinder(ignore), + target_(window), + screen_loc_(screen_loc), + is_top_most_(false), + tmp_region_(CreateRectRgn(0, 0, 0, 0)) { + EnumWindows(WindowCallbackProc, reinterpret_cast<LPARAM>(this)); + } + + // The window we're looking for. + HWND target_; + + // Location of window to find. + gfx::Point screen_loc_; + + // Is target_ the top most window? This is initially false but set to true + // in ShouldStopIterating if target_ is passed in. + bool is_top_most_; + + ScopedRegion tmp_region_; + + DISALLOW_COPY_AND_ASSIGN(TopMostFinder); +}; + +// WindowFinder --------------------------------------------------------------- + +// Helper class to determine if a particular point contains a window from our +// process. +class LocalProcessWindowFinder : public BaseWindowFinder { + public: + // Returns the hwnd from our process at screen_loc that is not obscured by + // another window. Returns NULL otherwise. + static HWND GetProcessWindowAtPoint(const gfx::Point& screen_loc, + const std::set<HWND>& ignore) { + LocalProcessWindowFinder finder(screen_loc, ignore); + if (finder.result_ && + TopMostFinder::IsTopMostWindowAtPoint(finder.result_, screen_loc, + ignore)) { + return finder.result_; + } + return NULL; + } + + protected: + virtual bool ShouldStopIterating(HWND hwnd) { + RECT r; + if (IsWindowVisible(hwnd) && GetWindowRect(hwnd, &r) && + PtInRect(&r, screen_loc_.ToPOINT())) { + result_ = hwnd; + return true; + } + return false; + } + + private: + LocalProcessWindowFinder(const gfx::Point& screen_loc, + const std::set<HWND>& ignore) + : BaseWindowFinder(ignore), + screen_loc_(screen_loc), + result_(NULL) { + EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc, + reinterpret_cast<LPARAM>(this)); + } + + // Position of the mouse. + gfx::Point screen_loc_; + + // The resulting window. This is initially null but set to true in + // ShouldStopIterating if an appropriate window is found. + HWND result_; + + DISALLOW_COPY_AND_ASSIGN(LocalProcessWindowFinder); +}; + +// DockToWindowFinder --------------------------------------------------------- + +// Helper class for creating a DockInfo from a specified point. +class DockToWindowFinder : public BaseWindowFinder { + public: + // Returns the DockInfo for the specified point. If there is no docking + // position for the specified point the returned DockInfo has a type of NONE. + static DockInfo GetDockInfoAtPoint(const gfx::Point& screen_loc, + const std::set<HWND>& ignore) { + DockToWindowFinder finder(screen_loc, ignore); + if (!finder.result_.window() || + !TopMostFinder::IsTopMostWindowAtPoint(finder.result_.window(), + finder.result_.hot_spot(), + ignore)) { + finder.result_.set_type(DockInfo::NONE); + } + return finder.result_; + } + + protected: + virtual bool ShouldStopIterating(HWND hwnd) { + BrowserView* window = BrowserView::GetBrowserViewForNativeWindow(hwnd); + RECT bounds; + if (!window || !IsWindowVisible(hwnd) || + !GetWindowRect(hwnd, &bounds)) { + return false; + } + + // Check the three corners we allow docking to. We don't currently allow + // docking to top of window as it conflicts with docking to the tab strip. + if (CheckPoint(hwnd, bounds.left, (bounds.top + bounds.bottom) / 2, + DockInfo::LEFT_OF_WINDOW) || + CheckPoint(hwnd, bounds.right - 1, (bounds.top + bounds.bottom) / 2, + DockInfo::RIGHT_OF_WINDOW) || + CheckPoint(hwnd, (bounds.left + bounds.right) / 2, bounds.bottom - 1, + DockInfo::BOTTOM_OF_WINDOW)) { + return true; + } + return false; + } + + private: + DockToWindowFinder(const gfx::Point& screen_loc, + const std::set<HWND>& ignore) + : BaseWindowFinder(ignore), + screen_loc_(screen_loc) { + HMONITOR monitor = MonitorFromPoint(screen_loc.ToPOINT(), + MONITOR_DEFAULTTONULL); + MONITORINFO monitor_info = {0}; + monitor_info.cbSize = sizeof(MONITORINFO); + if (monitor && GetMonitorInfo(monitor, &monitor_info)) { + result_.set_monitor_bounds(gfx::Rect(monitor_info.rcWork)); + EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc, + reinterpret_cast<LPARAM>(this)); + } + } + + bool CheckPoint(HWND hwnd, int x, int y, DockInfo::Type type) { + bool in_enable_area; + if (DockInfo::IsCloseToPoint(screen_loc_, x, y, &in_enable_area)) { + result_.set_in_enable_area(in_enable_area); + result_.set_window(hwnd); + result_.set_type(type); + result_.set_hot_spot(gfx::Point(x, y)); + // Only show the hotspot if the monitor contains the bounds of the popup + // window. Otherwise we end with a weird situation where the popup window + // isn't completely visible. + return result_.monitor_bounds().Contains(result_.GetPopupRect()); + } + return false; + } + + // The location to look for. + gfx::Point screen_loc_; + + // The resulting DockInfo. + DockInfo result_; + + DISALLOW_COPY_AND_ASSIGN(DockToWindowFinder); +}; + +} // namespace + +// DockInfo ------------------------------------------------------------------- + +// static +DockInfo DockInfo::GetDockInfoAtPoint(const gfx::Point& screen_point, + const std::set<HWND>& ignore) { + if (factory_) + return factory_->GetDockInfoAtPoint(screen_point, ignore); + + // Try docking to a window first. + DockInfo info = DockToWindowFinder::GetDockInfoAtPoint(screen_point, ignore); + if (info.type() != DockInfo::NONE) + return info; + + // No window relative positions. Try monitor relative positions. + const gfx::Rect& m_bounds = info.monitor_bounds(); + int mid_x = m_bounds.x() + m_bounds.width() / 2; + int mid_y = m_bounds.y() + m_bounds.height() / 2; + + bool result = + info.CheckMonitorPoint(screen_point, mid_x, m_bounds.y(), + DockInfo::MAXIMIZE) || + info.CheckMonitorPoint(screen_point, mid_x, m_bounds.bottom(), + DockInfo::BOTTOM_HALF) || + info.CheckMonitorPoint(screen_point, m_bounds.x(), mid_y, + DockInfo::LEFT_HALF) || + info.CheckMonitorPoint(screen_point, m_bounds.right(), mid_y, + DockInfo::RIGHT_HALF); + + return info; +} + +HWND DockInfo::GetLocalProcessWindowAtPoint(const gfx::Point& screen_point, + const std::set<HWND>& ignore) { + if (factory_) + return factory_->GetLocalProcessWindowAtPoint(screen_point, ignore); + return + LocalProcessWindowFinder::GetProcessWindowAtPoint(screen_point, ignore); +} + +bool DockInfo::GetWindowBounds(gfx::Rect* bounds) const { + RECT window_rect; + if (!window() || !GetWindowRect(window(), &window_rect)) + return false; + *bounds = gfx::Rect(window_rect); + return true; +} + +void DockInfo::SizeOtherWindowTo(const gfx::Rect& bounds) const { + if (IsZoomed(window())) { + // We're docking relative to another window, we need to make sure the + // window we're docking to isn't maximized. + ShowWindow(window(), SW_RESTORE | SW_SHOWNA); + } + SetWindowPos(window(), HWND_TOP, bounds.x(), bounds.y(), bounds.width(), + bounds.height(), SWP_NOACTIVATE | SWP_NOOWNERZORDER); +} diff --git a/chrome/browser/ui/toolbar/back_forward_menu_model.cc b/chrome/browser/ui/toolbar/back_forward_menu_model.cc new file mode 100644 index 0000000..5ea279f --- /dev/null +++ b/chrome/browser/ui/toolbar/back_forward_menu_model.cc @@ -0,0 +1,379 @@ +// 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 "build/build_config.h" + +#include "chrome/browser/ui/toolbar/back_forward_menu_model.h" + +#include "app/l10n_util.h" +#include "app/text_elider.h" +#include "app/resource_bundle.h" +#include "base/string_number_conversions.h" +#include "chrome/browser/metrics/user_metrics.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/common/url_constants.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" +#include "net/base/registry_controlled_domain.h" + +const int BackForwardMenuModel::kMaxHistoryItems = 12; +const int BackForwardMenuModel::kMaxChapterStops = 5; +static const int kMaxWidth = 700; + +BackForwardMenuModel::BackForwardMenuModel(Browser* browser, + ModelType model_type) + : browser_(browser), + test_tab_contents_(NULL), + model_type_(model_type) { +} + +bool BackForwardMenuModel::HasIcons() const { + return true; +} + +int BackForwardMenuModel::GetItemCount() const { + int items = GetHistoryItemCount(); + + if (items > 0) { + int chapter_stops = 0; + + // Next, we count ChapterStops, if any. + if (items == kMaxHistoryItems) + chapter_stops = GetChapterStopCount(items); + + if (chapter_stops) + items += chapter_stops + 1; // Chapter stops also need a separator. + + // If the menu is not empty, add two positions in the end + // for a separator and a "Show Full History" item. + items += 2; + } + + return items; +} + +menus::MenuModel::ItemType BackForwardMenuModel::GetTypeAt(int index) const { + return IsSeparator(index) ? TYPE_SEPARATOR : TYPE_COMMAND; +} + +int BackForwardMenuModel::GetCommandIdAt(int index) const { + return index; +} + +string16 BackForwardMenuModel::GetLabelAt(int index) const { + // Return label "Show Full History" for the last item of the menu. + if (index == GetItemCount() - 1) + return l10n_util::GetStringUTF16(IDS_SHOWFULLHISTORY_LINK); + + // Return an empty string for a separator. + if (IsSeparator(index)) + return string16(); + + // Return the entry title, escaping any '&' characters and eliding it if it's + // super long. + NavigationEntry* entry = GetNavigationEntry(index); + string16 menu_text(entry->GetTitleForDisplay( + &GetTabContents()->controller())); + menu_text = gfx::ElideText(menu_text, gfx::Font(), kMaxWidth, false); + + for (size_t i = menu_text.find('&'); i != string16::npos; + i = menu_text.find('&', i + 2)) { + menu_text.insert(i, 1, '&'); + } + return menu_text; +} + +bool BackForwardMenuModel::IsLabelDynamicAt(int index) const { + // This object is only used for a single showing of a menu. + return false; +} + +bool BackForwardMenuModel::GetAcceleratorAt( + int index, + menus::Accelerator* accelerator) const { + return false; +} + +bool BackForwardMenuModel::IsItemCheckedAt(int index) const { + return false; +} + +int BackForwardMenuModel::GetGroupIdAt(int index) const { + return false; +} + +bool BackForwardMenuModel::GetIconAt(int index, SkBitmap* icon) const { + if (!ItemHasIcon(index)) + return false; + + if (index == GetItemCount() - 1) { + *icon = *ResourceBundle::GetSharedInstance().GetBitmapNamed( + IDR_HISTORY_FAVICON); + } else { + NavigationEntry* entry = GetNavigationEntry(index); + *icon = entry->favicon().bitmap(); + } + + return true; +} + +menus::ButtonMenuItemModel* BackForwardMenuModel::GetButtonMenuItemAt( + int index) const { + return NULL; +} + +bool BackForwardMenuModel::IsEnabledAt(int index) const { + return index < GetItemCount() && !IsSeparator(index); +} + +menus::MenuModel* BackForwardMenuModel::GetSubmenuModelAt(int index) const { + return NULL; +} + +void BackForwardMenuModel::HighlightChangedTo(int index) { +} + +void BackForwardMenuModel::ActivatedAt(int index) { + ActivatedAtWithDisposition(index, CURRENT_TAB); +} + +void BackForwardMenuModel::ActivatedAtWithDisposition( + int index, int disposition) { + Profile* profile = browser_->profile(); + + DCHECK(!IsSeparator(index)); + + // Execute the command for the last item: "Show Full History". + if (index == GetItemCount() - 1) { + UserMetrics::RecordComputedAction(BuildActionName("ShowFullHistory", -1), + profile); + browser_->ShowSingletonTab(GURL(chrome::kChromeUIHistoryURL), false); + return; + } + + // Log whether it was a history or chapter click. + if (index < GetHistoryItemCount()) { + UserMetrics::RecordComputedAction( + BuildActionName("HistoryClick", index), profile); + } else { + UserMetrics::RecordComputedAction( + BuildActionName("ChapterClick", index - GetHistoryItemCount() - 1), + profile); + } + + int controller_index = MenuIndexToNavEntryIndex(index); + if (!browser_->NavigateToIndexWithDisposition( + controller_index, static_cast<WindowOpenDisposition>(disposition))) { + NOTREACHED(); + } +} + +void BackForwardMenuModel::MenuWillShow() { + UserMetrics::RecordComputedAction(BuildActionName("Popup", -1), + browser_->profile()); +} + +bool BackForwardMenuModel::IsSeparator(int index) const { + int history_items = GetHistoryItemCount(); + // If the index is past the number of history items + separator, + // we then consider if it is a chapter-stop entry. + if (index > history_items) { + // We either are in ChapterStop area, or at the end of the list (the "Show + // Full History" link). + int chapter_stops = GetChapterStopCount(history_items); + if (chapter_stops == 0) + return false; // We must have reached the "Show Full History" link. + // Otherwise, look to see if we have reached the separator for the + // chapter-stops. If not, this is a chapter stop. + return (index == history_items + 1 + chapter_stops); + } + + // Look to see if we have reached the separator for the history items. + return index == history_items; +} + +int BackForwardMenuModel::GetHistoryItemCount() const { + TabContents* contents = GetTabContents(); + int items = 0; + + if (model_type_ == FORWARD_MENU) { + // Only count items from n+1 to end (if n is current entry) + items = contents->controller().entry_count() - + contents->controller().GetCurrentEntryIndex() - 1; + } else { + items = contents->controller().GetCurrentEntryIndex(); + } + + if (items > kMaxHistoryItems) + items = kMaxHistoryItems; + else if (items < 0) + items = 0; + + return items; +} + +int BackForwardMenuModel::GetChapterStopCount(int history_items) const { + TabContents* contents = GetTabContents(); + + int chapter_stops = 0; + int current_entry = contents->controller().GetCurrentEntryIndex(); + + if (history_items == kMaxHistoryItems) { + int chapter_id = current_entry; + if (model_type_ == FORWARD_MENU) { + chapter_id += history_items; + } else { + chapter_id -= history_items; + } + + do { + chapter_id = GetIndexOfNextChapterStop(chapter_id, + model_type_ == FORWARD_MENU); + if (chapter_id != -1) + ++chapter_stops; + } while (chapter_id != -1 && chapter_stops < kMaxChapterStops); + } + + return chapter_stops; +} + +int BackForwardMenuModel::GetIndexOfNextChapterStop(int start_from, + bool forward) const { + TabContents* contents = GetTabContents(); + NavigationController& controller = contents->controller(); + + int max_count = controller.entry_count(); + if (start_from < 0 || start_from >= max_count) + return -1; // Out of bounds. + + if (forward) { + if (start_from < max_count - 1) { + // We want to advance over the current chapter stop, so we add one. + // We don't need to do this when direction is backwards. + start_from++; + } else { + return -1; + } + } + + NavigationEntry* start_entry = controller.GetEntryAtIndex(start_from); + const GURL& url = start_entry->url(); + + if (!forward) { + // When going backwards we return the first entry we find that has a + // different domain. + for (int i = start_from - 1; i >= 0; --i) { + if (!net::RegistryControlledDomainService::SameDomainOrHost(url, + controller.GetEntryAtIndex(i)->url())) + return i; + } + // We have reached the beginning without finding a chapter stop. + return -1; + } else { + // When going forwards we return the entry before the entry that has a + // different domain. + for (int i = start_from + 1; i < max_count; ++i) { + if (!net::RegistryControlledDomainService::SameDomainOrHost(url, + controller.GetEntryAtIndex(i)->url())) + return i - 1; + } + // Last entry is always considered a chapter stop. + return max_count - 1; + } +} + +int BackForwardMenuModel::FindChapterStop(int offset, + bool forward, + int skip) const { + if (offset < 0 || skip < 0) + return -1; + + if (!forward) + offset *= -1; + + TabContents* contents = GetTabContents(); + int entry = contents->controller().GetCurrentEntryIndex() + offset; + for (int i = 0; i < skip + 1; i++) + entry = GetIndexOfNextChapterStop(entry, forward); + + return entry; +} + +bool BackForwardMenuModel::ItemHasCommand(int index) const { + return index < GetItemCount() && !IsSeparator(index); +} + +bool BackForwardMenuModel::ItemHasIcon(int index) const { + return index < GetItemCount() && !IsSeparator(index); +} + +string16 BackForwardMenuModel::GetShowFullHistoryLabel() const { + return l10n_util::GetStringUTF16(IDS_SHOWFULLHISTORY_LINK); +} + +TabContents* BackForwardMenuModel::GetTabContents() const { + // We use the test tab contents if the unit test has specified it. + return test_tab_contents_ ? test_tab_contents_ : + browser_->GetSelectedTabContents(); +} + +int BackForwardMenuModel::MenuIndexToNavEntryIndex(int index) const { + TabContents* contents = GetTabContents(); + int history_items = GetHistoryItemCount(); + + DCHECK_GE(index, 0); + + // Convert anything above the History items separator. + if (index < history_items) { + if (model_type_ == FORWARD_MENU) { + index += contents->controller().GetCurrentEntryIndex() + 1; + } else { + // Back menu is reverse. + index = contents->controller().GetCurrentEntryIndex() - (index + 1); + } + return index; + } + if (index == history_items) + return -1; // Don't translate the separator for history items. + + if (index >= history_items + 1 + GetChapterStopCount(history_items)) + return -1; // This is beyond the last chapter stop so we abort. + + // This menu item is a chapter stop located between the two separators. + index = FindChapterStop(history_items, + model_type_ == FORWARD_MENU, + index - history_items - 1); + + return index; +} + +NavigationEntry* BackForwardMenuModel::GetNavigationEntry(int index) const { + int controller_index = MenuIndexToNavEntryIndex(index); + NavigationController& controller = GetTabContents()->controller(); + if (controller_index >= 0 && controller_index < controller.entry_count()) + return controller.GetEntryAtIndex(controller_index); + + NOTREACHED(); + return NULL; +} + +std::string BackForwardMenuModel::BuildActionName( + const std::string& action, int index) const { + DCHECK(!action.empty()); + DCHECK(index >= -1); + std::string metric_string; + if (model_type_ == FORWARD_MENU) + metric_string += "ForwardMenu_"; + else + metric_string += "BackMenu_"; + metric_string += action; + if (index != -1) { + // +1 is for historical reasons (indices used to start at 1). + metric_string += base::IntToString(index + 1); + } + return metric_string; +} diff --git a/chrome/browser/ui/toolbar/back_forward_menu_model.h b/chrome/browser/ui/toolbar/back_forward_menu_model.h new file mode 100644 index 0000000..aba4688 --- /dev/null +++ b/chrome/browser/ui/toolbar/back_forward_menu_model.h @@ -0,0 +1,171 @@ +// 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. + +#ifndef CHROME_BROWSER_UI_TOOLBAR_BACK_FORWARD_MENU_MODEL_H_ +#define CHROME_BROWSER_UI_TOOLBAR_BACK_FORWARD_MENU_MODEL_H_ +#pragma once + +#include <string> + +#include "app/menus/menu_model.h" +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/string16.h" +#include "webkit/glue/window_open_disposition.h" + +class Browser; +class SkBitmap; +class TabContents; +class NavigationEntry; + +/////////////////////////////////////////////////////////////////////////////// +// +// BackForwardMenuModel +// +// Interface for the showing of the dropdown menu for the Back/Forward buttons. +// Actual implementations are platform-specific. +/////////////////////////////////////////////////////////////////////////////// +class BackForwardMenuModel : public menus::MenuModel { + public: + // These are IDs used to identify individual UI elements within the + // browser window using View::GetViewByID. + enum ModelType { + FORWARD_MENU = 1, + BACKWARD_MENU = 2 + }; + + BackForwardMenuModel(Browser* browser, ModelType model_type); + virtual ~BackForwardMenuModel() { } + + // MenuModel implementation. + virtual bool HasIcons() const; + // Returns how many items the menu should show, including history items, + // chapter-stops, separators and the Show Full History link. This function + // uses GetHistoryItemCount() and GetChapterStopCount() internally to figure + // out the total number of items to show. + virtual int GetItemCount() const; + virtual ItemType GetTypeAt(int index) const; + virtual int GetCommandIdAt(int index) const; + virtual string16 GetLabelAt(int index) const; + virtual bool IsLabelDynamicAt(int index) const; + virtual bool GetAcceleratorAt(int index, + menus::Accelerator* accelerator) const; + virtual bool IsItemCheckedAt(int index) const; + virtual int GetGroupIdAt(int index) const; + virtual bool GetIconAt(int index, SkBitmap* icon) const; + virtual menus::ButtonMenuItemModel* GetButtonMenuItemAt(int index) const; + virtual bool IsEnabledAt(int index) const; + virtual MenuModel* GetSubmenuModelAt(int index) const; + virtual void HighlightChangedTo(int index); + virtual void ActivatedAt(int index); + virtual void ActivatedAtWithDisposition(int index, int disposition); + virtual void MenuWillShow(); + + // Is the item at |index| a separator? + bool IsSeparator(int index) const; + + private: + // Allows the unit test to use its own dummy tab contents. + void set_test_tab_contents(TabContents* test_tab_contents) { + test_tab_contents_ = test_tab_contents; + } + + // Returns how many history items the menu should show. For example, if the + // navigation controller of the current tab has a current entry index of 5 and + // forward_direction_ is false (we are the back button delegate) then this + // function will return 5 (representing 0-4). If forward_direction_ is + // true (we are the forward button delegate), then this function will return + // the number of entries after 5. Note, though, that in either case it will + // not report more than kMaxHistoryItems. The number returned also does not + // include the separator line after the history items (nor the separator for + // the "Show Full History" link). + int GetHistoryItemCount() const; + + // Returns how many chapter-stop items the menu should show. For the + // definition of a chapter-stop, see GetIndexOfNextChapterStop(). The number + // returned does not include the separator lines before and after the + // chapter-stops. + int GetChapterStopCount(int history_items) const; + + // Finds the next chapter-stop in the NavigationEntryList starting from + // the index specified in |start_from| and continuing in the direction + // specified (|forward|) until either a chapter-stop is found or we reach the + // end, in which case -1 is returned. If |start_from| is out of bounds, -1 + // will also be returned. A chapter-stop is defined as the last page the user + // browsed to within the same domain. For example, if the user's homepage is + // Google and she navigates to Google pages G1, G2 and G3 before heading over + // to WikiPedia for pages W1 and W2 and then back to Google for pages G4 and + // G5 then G3, W2 and G5 are considered chapter-stops. The return value from + // this function is an index into the NavigationEntryList vector. + int GetIndexOfNextChapterStop(int start_from, bool forward) const; + + // Finds a given chapter-stop starting at the currently active entry in the + // NavigationEntryList vector advancing first forward or backward by |offset| + // (depending on the direction specified in parameter |forward|). It also + // allows you to skip chapter-stops by specifying a positive value for |skip|. + // Example: FindChapterStop(5, false, 3) starts with the currently active + // index, subtracts 5 from it and then finds the fourth chapter-stop before + // that index (skipping the first 3 it finds). + // Example: FindChapterStop(0, true, 0) is functionally equivalent to + // calling GetIndexOfNextChapterStop(GetCurrentEntryIndex(), true). + // + // NOTE: Both |offset| and |skip| must be non-negative. The return value from + // this function is an index into the NavigationEntryList vector. If |offset| + // is out of bounds or if we skip too far (run out of chapter-stops) this + // function returns -1. + int FindChapterStop(int offset, bool forward, int skip) const; + + // How many items (max) to show in the back/forward history menu dropdown. + static const int kMaxHistoryItems; + + // How many chapter-stops (max) to show in the back/forward dropdown list. + static const int kMaxChapterStops; + + // Takes a menu item index as passed in through one of the menu delegate + // functions and converts it into an index into the NavigationEntryList + // vector. |index| can point to a separator, or the + // "Show Full History" link in which case this function returns -1. + int MenuIndexToNavEntryIndex(int index) const; + + // Does the item have a command associated with it? + bool ItemHasCommand(int index) const; + + // Returns true if there is an icon for this menu item. + bool ItemHasIcon(int index) const; + + // Allow the unit test to use the "Show Full History" label. + string16 GetShowFullHistoryLabel() const; + + // Looks up a NavigationEntry by menu id. + NavigationEntry* GetNavigationEntry(int index) const; + + // Retrieves the TabContents pointer to use, which is either the one that + // the unit test sets (using SetTabContentsForUnitTest) or the one from + // the browser window. + TabContents* GetTabContents() const; + + // Build a string version of a user action on this menu, used as an + // identifier for logging user behavior. + // E.g. BuildActionName("Click", 2) returns "BackMenu_Click2". + // An index of -1 means no index. + std::string BuildActionName(const std::string& name, int index) const; + + Browser* browser_; + + // The unit tests will provide their own TabContents to use. + TabContents* test_tab_contents_; + + // Represents whether this is the delegate for the forward button or the + // back button. + ModelType model_type_; + + friend class BackFwdMenuModelTest; + FRIEND_TEST_ALL_PREFIXES(BackFwdMenuModelTest, BasicCase); + FRIEND_TEST_ALL_PREFIXES(BackFwdMenuModelTest, MaxItemsTest); + FRIEND_TEST_ALL_PREFIXES(BackFwdMenuModelTest, ChapterStops); + + DISALLOW_COPY_AND_ASSIGN(BackForwardMenuModel); +}; + +#endif // CHROME_BROWSER_UI_TOOLBAR_BACK_FORWARD_MENU_MODEL_H_ diff --git a/chrome/browser/ui/toolbar/back_forward_menu_model_unittest.cc b/chrome/browser/ui/toolbar/back_forward_menu_model_unittest.cc new file mode 100644 index 0000000..a25260d --- /dev/null +++ b/chrome/browser/ui/toolbar/back_forward_menu_model_unittest.cc @@ -0,0 +1,422 @@ +// 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/ui/toolbar/back_forward_menu_model.h" + +#include "base/path_service.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/profile_manager.h" +#include "chrome/browser/renderer_host/test/test_render_view_host.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/test_tab_contents.h" +#include "chrome/common/url_constants.h" +#include "testing/gtest/include/gtest/gtest.h" + +class BackFwdMenuModelTest : public RenderViewHostTestHarness { + public: + void ValidateModel(BackForwardMenuModel* model, int history_items, + int chapter_stops) { + int h = std::min(BackForwardMenuModel::kMaxHistoryItems, history_items); + int c = std::min(BackForwardMenuModel::kMaxChapterStops, chapter_stops); + EXPECT_EQ(h, model->GetHistoryItemCount()); + EXPECT_EQ(c, model->GetChapterStopCount(h)); + if (h > 0) + h += 2; // Separator and View History link. + if (c > 0) + ++c; + EXPECT_EQ(h + c, model->GetItemCount()); + } + + void LoadURLAndUpdateState(const char* url, const char* title) { + NavigateAndCommit(GURL(url)); + controller().GetLastCommittedEntry()->set_title(UTF8ToUTF16(title)); + } + + // Navigate back or forward the given amount and commits the entry (which + // will be pending after we ask to navigate there). + void NavigateToOffset(int offset) { + controller().GoToOffset(offset); + contents()->CommitPendingNavigation(); + } + + // Same as NavigateToOffset but goes to an absolute index. + void NavigateToIndex(int index) { + controller().GoToIndex(index); + contents()->CommitPendingNavigation(); + } + + // Goes back/forward and commits the load. + void GoBack() { + controller().GoBack(); + contents()->CommitPendingNavigation(); + } + void GoForward() { + controller().GoForward(); + contents()->CommitPendingNavigation(); + } +}; + +TEST_F(BackFwdMenuModelTest, BasicCase) { + scoped_ptr<BackForwardMenuModel> back_model(new BackForwardMenuModel( + NULL, BackForwardMenuModel::BACKWARD_MENU)); + back_model->set_test_tab_contents(contents()); + + scoped_ptr<BackForwardMenuModel> forward_model(new BackForwardMenuModel( + NULL, BackForwardMenuModel::FORWARD_MENU)); + forward_model->set_test_tab_contents(contents()); + + EXPECT_EQ(0, back_model->GetItemCount()); + EXPECT_EQ(0, forward_model->GetItemCount()); + EXPECT_FALSE(back_model->ItemHasCommand(1)); + + // Seed the controller with a few URLs + LoadURLAndUpdateState("http://www.a.com/1", "A1"); + LoadURLAndUpdateState("http://www.a.com/2", "A2"); + LoadURLAndUpdateState("http://www.a.com/3", "A3"); + LoadURLAndUpdateState("http://www.b.com/1", "B1"); + LoadURLAndUpdateState("http://www.b.com/2", "B2"); + LoadURLAndUpdateState("http://www.c.com/1", "C1"); + LoadURLAndUpdateState("http://www.c.com/2", "C2"); + LoadURLAndUpdateState("http://www.c.com/3", "C3"); + + // There're two more items here: a separator and a "Show Full History". + EXPECT_EQ(9, back_model->GetItemCount()); + EXPECT_EQ(0, forward_model->GetItemCount()); + EXPECT_EQ(ASCIIToUTF16("C2"), back_model->GetLabelAt(0)); + EXPECT_EQ(ASCIIToUTF16("A1"), back_model->GetLabelAt(6)); + EXPECT_EQ(back_model->GetShowFullHistoryLabel(), + back_model->GetLabelAt(8)); + + EXPECT_TRUE(back_model->ItemHasCommand(0)); + EXPECT_TRUE(back_model->ItemHasCommand(6)); + EXPECT_TRUE(back_model->IsSeparator(7)); + EXPECT_TRUE(back_model->ItemHasCommand(8)); + EXPECT_FALSE(back_model->ItemHasCommand(9)); + EXPECT_FALSE(back_model->ItemHasCommand(9)); + + NavigateToOffset(-7); + + EXPECT_EQ(0, back_model->GetItemCount()); + EXPECT_EQ(9, forward_model->GetItemCount()); + EXPECT_EQ(ASCIIToUTF16("A2"), forward_model->GetLabelAt(0)); + EXPECT_EQ(ASCIIToUTF16("C3"), forward_model->GetLabelAt(6)); + EXPECT_EQ(forward_model->GetShowFullHistoryLabel(), + forward_model->GetLabelAt(8)); + + EXPECT_TRUE(forward_model->ItemHasCommand(0)); + EXPECT_TRUE(forward_model->ItemHasCommand(6)); + EXPECT_TRUE(forward_model->IsSeparator(7)); + EXPECT_TRUE(forward_model->ItemHasCommand(8)); + EXPECT_FALSE(forward_model->ItemHasCommand(7)); + EXPECT_FALSE(forward_model->ItemHasCommand(9)); + + NavigateToOffset(4); + + EXPECT_EQ(6, back_model->GetItemCount()); + EXPECT_EQ(5, forward_model->GetItemCount()); + EXPECT_EQ(ASCIIToUTF16("B1"), back_model->GetLabelAt(0)); + EXPECT_EQ(ASCIIToUTF16("A1"), back_model->GetLabelAt(3)); + EXPECT_EQ(back_model->GetShowFullHistoryLabel(), + back_model->GetLabelAt(5)); + EXPECT_EQ(ASCIIToUTF16("C1"), forward_model->GetLabelAt(0)); + EXPECT_EQ(ASCIIToUTF16("C3"), forward_model->GetLabelAt(2)); + EXPECT_EQ(forward_model->GetShowFullHistoryLabel(), + forward_model->GetLabelAt(4)); +} + +TEST_F(BackFwdMenuModelTest, MaxItemsTest) { + scoped_ptr<BackForwardMenuModel> back_model(new BackForwardMenuModel( + NULL, BackForwardMenuModel::BACKWARD_MENU)); + back_model->set_test_tab_contents(contents()); + + scoped_ptr<BackForwardMenuModel> forward_model(new BackForwardMenuModel( + NULL, BackForwardMenuModel::FORWARD_MENU)); + forward_model->set_test_tab_contents(contents()); + + // Seed the controller with 32 URLs + LoadURLAndUpdateState("http://www.a.com/1", "A1"); + LoadURLAndUpdateState("http://www.a.com/2", "A2"); + LoadURLAndUpdateState("http://www.a.com/3", "A3"); + LoadURLAndUpdateState("http://www.b.com/1", "B1"); + LoadURLAndUpdateState("http://www.b.com/2", "B2"); + LoadURLAndUpdateState("http://www.b.com/3", "B3"); + LoadURLAndUpdateState("http://www.c.com/1", "C1"); + LoadURLAndUpdateState("http://www.c.com/2", "C2"); + LoadURLAndUpdateState("http://www.c.com/3", "C3"); + LoadURLAndUpdateState("http://www.d.com/1", "D1"); + LoadURLAndUpdateState("http://www.d.com/2", "D2"); + LoadURLAndUpdateState("http://www.d.com/3", "D3"); + LoadURLAndUpdateState("http://www.e.com/1", "E1"); + LoadURLAndUpdateState("http://www.e.com/2", "E2"); + LoadURLAndUpdateState("http://www.e.com/3", "E3"); + LoadURLAndUpdateState("http://www.f.com/1", "F1"); + LoadURLAndUpdateState("http://www.f.com/2", "F2"); + LoadURLAndUpdateState("http://www.f.com/3", "F3"); + LoadURLAndUpdateState("http://www.g.com/1", "G1"); + LoadURLAndUpdateState("http://www.g.com/2", "G2"); + LoadURLAndUpdateState("http://www.g.com/3", "G3"); + LoadURLAndUpdateState("http://www.h.com/1", "H1"); + LoadURLAndUpdateState("http://www.h.com/2", "H2"); + LoadURLAndUpdateState("http://www.h.com/3", "H3"); + LoadURLAndUpdateState("http://www.i.com/1", "I1"); + LoadURLAndUpdateState("http://www.i.com/2", "I2"); + LoadURLAndUpdateState("http://www.i.com/3", "I3"); + LoadURLAndUpdateState("http://www.j.com/1", "J1"); + LoadURLAndUpdateState("http://www.j.com/2", "J2"); + LoadURLAndUpdateState("http://www.j.com/3", "J3"); + LoadURLAndUpdateState("http://www.k.com/1", "K1"); + LoadURLAndUpdateState("http://www.k.com/2", "K2"); + + // Also there're two more for a separator and a "Show Full History". + int chapter_stop_offset = 6; + EXPECT_EQ(BackForwardMenuModel::kMaxHistoryItems + 2 + chapter_stop_offset, + back_model->GetItemCount()); + EXPECT_EQ(0, forward_model->GetItemCount()); + EXPECT_EQ(ASCIIToUTF16("K1"), back_model->GetLabelAt(0)); + EXPECT_EQ(back_model->GetShowFullHistoryLabel(), + back_model->GetLabelAt(BackForwardMenuModel::kMaxHistoryItems + 1 + + chapter_stop_offset)); + + // Test for out of bounds (beyond Show Full History). + EXPECT_FALSE(back_model->ItemHasCommand( + BackForwardMenuModel::kMaxHistoryItems + chapter_stop_offset + 2)); + + EXPECT_TRUE(back_model->ItemHasCommand( + BackForwardMenuModel::kMaxHistoryItems - 1)); + EXPECT_TRUE(back_model->IsSeparator( + BackForwardMenuModel::kMaxHistoryItems)); + + NavigateToIndex(0); + + EXPECT_EQ(BackForwardMenuModel::kMaxHistoryItems + 2 + chapter_stop_offset, + forward_model->GetItemCount()); + EXPECT_EQ(0, back_model->GetItemCount()); + EXPECT_EQ(ASCIIToUTF16("A2"), forward_model->GetLabelAt(0)); + EXPECT_EQ(forward_model->GetShowFullHistoryLabel(), + forward_model->GetLabelAt(BackForwardMenuModel::kMaxHistoryItems + 1 + + chapter_stop_offset)); + + // Out of bounds + EXPECT_FALSE(forward_model->ItemHasCommand( + BackForwardMenuModel::kMaxHistoryItems + 2 + chapter_stop_offset)); + + EXPECT_TRUE(forward_model->ItemHasCommand( + BackForwardMenuModel::kMaxHistoryItems - 1)); + EXPECT_TRUE(forward_model->IsSeparator( + BackForwardMenuModel::kMaxHistoryItems)); +} + +TEST_F(BackFwdMenuModelTest, ChapterStops) { + scoped_ptr<BackForwardMenuModel> back_model(new BackForwardMenuModel( + NULL, BackForwardMenuModel::BACKWARD_MENU)); + back_model->set_test_tab_contents(contents()); + + scoped_ptr<BackForwardMenuModel> forward_model(new BackForwardMenuModel( + NULL, BackForwardMenuModel::FORWARD_MENU)); + forward_model->set_test_tab_contents(contents()); + + // Seed the controller with 32 URLs. + int i = 0; + LoadURLAndUpdateState("http://www.a.com/1", "A1"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.a.com/2", "A2"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.a.com/3", "A3"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.b.com/1", "B1"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.b.com/2", "B2"); + ValidateModel(back_model.get(), i++, 0); + // i = 5 + LoadURLAndUpdateState("http://www.b.com/3", "B3"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.c.com/1", "C1"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.c.com/2", "C2"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.c.com/3", "C3"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.d.com/1", "D1"); + ValidateModel(back_model.get(), i++, 0); + // i = 10 + LoadURLAndUpdateState("http://www.d.com/2", "D2"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.d.com/3", "D3"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.e.com/1", "E1"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.e.com/2", "E2"); + ValidateModel(back_model.get(), i++, 0); + LoadURLAndUpdateState("http://www.e.com/3", "E3"); + ValidateModel(back_model.get(), i++, 0); + // i = 15 + LoadURLAndUpdateState("http://www.f.com/1", "F1"); + ValidateModel(back_model.get(), i++, 1); + LoadURLAndUpdateState("http://www.f.com/2", "F2"); + ValidateModel(back_model.get(), i++, 1); + LoadURLAndUpdateState("http://www.f.com/3", "F3"); + ValidateModel(back_model.get(), i++, 1); + LoadURLAndUpdateState("http://www.g.com/1", "G1"); + ValidateModel(back_model.get(), i++, 2); + LoadURLAndUpdateState("http://www.g.com/2", "G2"); + ValidateModel(back_model.get(), i++, 2); + // i = 20 + LoadURLAndUpdateState("http://www.g.com/3", "G3"); + ValidateModel(back_model.get(), i++, 2); + LoadURLAndUpdateState("http://www.h.com/1", "H1"); + ValidateModel(back_model.get(), i++, 3); + LoadURLAndUpdateState("http://www.h.com/2", "H2"); + ValidateModel(back_model.get(), i++, 3); + LoadURLAndUpdateState("http://www.h.com/3", "H3"); + ValidateModel(back_model.get(), i++, 3); + LoadURLAndUpdateState("http://www.i.com/1", "I1"); + ValidateModel(back_model.get(), i++, 4); + // i = 25 + LoadURLAndUpdateState("http://www.i.com/2", "I2"); + ValidateModel(back_model.get(), i++, 4); + LoadURLAndUpdateState("http://www.i.com/3", "I3"); + ValidateModel(back_model.get(), i++, 4); + LoadURLAndUpdateState("http://www.j.com/1", "J1"); + ValidateModel(back_model.get(), i++, 5); + LoadURLAndUpdateState("http://www.j.com/2", "J2"); + ValidateModel(back_model.get(), i++, 5); + LoadURLAndUpdateState("http://www.j.com/3", "J3"); + ValidateModel(back_model.get(), i++, 5); + // i = 30 + LoadURLAndUpdateState("http://www.k.com/1", "K1"); + ValidateModel(back_model.get(), i++, 6); + LoadURLAndUpdateState("http://www.k.com/2", "K2"); + ValidateModel(back_model.get(), i++, 6); + // i = 32 + LoadURLAndUpdateState("http://www.k.com/3", "K3"); + ValidateModel(back_model.get(), i++, 6); + + // A chapter stop is defined as the last page the user + // browsed to within the same domain. + + // Check to see if the chapter stops have the right labels. + int index = BackForwardMenuModel::kMaxHistoryItems; + // Empty string indicates item is a separator. + EXPECT_EQ(ASCIIToUTF16(""), back_model->GetLabelAt(index++)); + EXPECT_EQ(ASCIIToUTF16("F3"), back_model->GetLabelAt(index++)); + EXPECT_EQ(ASCIIToUTF16("E3"), back_model->GetLabelAt(index++)); + EXPECT_EQ(ASCIIToUTF16("D3"), back_model->GetLabelAt(index++)); + EXPECT_EQ(ASCIIToUTF16("C3"), back_model->GetLabelAt(index++)); + // The menu should only show a maximum of 5 chapter stops. + EXPECT_EQ(ASCIIToUTF16("B3"), back_model->GetLabelAt(index)); + // Empty string indicates item is a separator. + EXPECT_EQ(ASCIIToUTF16(""), back_model->GetLabelAt(index + 1)); + EXPECT_EQ(back_model->GetShowFullHistoryLabel(), + back_model->GetLabelAt(index + 2)); + + // If we go back two we should still see the same chapter stop at the end. + GoBack(); + EXPECT_EQ(ASCIIToUTF16("B3"), back_model->GetLabelAt(index)); + GoBack(); + EXPECT_EQ(ASCIIToUTF16("B3"), back_model->GetLabelAt(index)); + // But if we go back again, it should change. + GoBack(); + EXPECT_EQ(ASCIIToUTF16("A3"), back_model->GetLabelAt(index)); + GoBack(); + EXPECT_EQ(ASCIIToUTF16("A3"), back_model->GetLabelAt(index)); + GoBack(); + EXPECT_EQ(ASCIIToUTF16("A3"), back_model->GetLabelAt(index)); + GoBack(); + // It is now a separator. + EXPECT_EQ(ASCIIToUTF16(""), back_model->GetLabelAt(index)); + // Undo our position change. + NavigateToOffset(6); + + // Go back enough to make sure no chapter stops should appear. + NavigateToOffset(-BackForwardMenuModel::kMaxHistoryItems); + ValidateModel(forward_model.get(), BackForwardMenuModel::kMaxHistoryItems, 0); + // Go forward (still no chapter stop) + GoForward(); + ValidateModel(forward_model.get(), + BackForwardMenuModel::kMaxHistoryItems - 1, 0); + // Go back two (one chapter stop should show up) + GoBack(); + GoBack(); + ValidateModel(forward_model.get(), + BackForwardMenuModel::kMaxHistoryItems, 1); + + // Go to beginning. + NavigateToIndex(0); + + // Check to see if the chapter stops have the right labels. + index = BackForwardMenuModel::kMaxHistoryItems; + // Empty string indicates item is a separator. + EXPECT_EQ(ASCIIToUTF16(""), forward_model->GetLabelAt(index++)); + EXPECT_EQ(ASCIIToUTF16("E3"), forward_model->GetLabelAt(index++)); + EXPECT_EQ(ASCIIToUTF16("F3"), forward_model->GetLabelAt(index++)); + EXPECT_EQ(ASCIIToUTF16("G3"), forward_model->GetLabelAt(index++)); + EXPECT_EQ(ASCIIToUTF16("H3"), forward_model->GetLabelAt(index++)); + // The menu should only show a maximum of 5 chapter stops. + EXPECT_EQ(ASCIIToUTF16("I3"), forward_model->GetLabelAt(index)); + // Empty string indicates item is a separator. + EXPECT_EQ(ASCIIToUTF16(""), forward_model->GetLabelAt(index + 1)); + EXPECT_EQ(forward_model->GetShowFullHistoryLabel(), + forward_model->GetLabelAt(index + 2)); + + // If we advance one we should still see the same chapter stop at the end. + GoForward(); + EXPECT_EQ(ASCIIToUTF16("I3"), forward_model->GetLabelAt(index)); + // But if we advance one again, it should change. + GoForward(); + EXPECT_EQ(ASCIIToUTF16("J3"), forward_model->GetLabelAt(index)); + GoForward(); + EXPECT_EQ(ASCIIToUTF16("J3"), forward_model->GetLabelAt(index)); + GoForward(); + EXPECT_EQ(ASCIIToUTF16("J3"), forward_model->GetLabelAt(index)); + GoForward(); + EXPECT_EQ(ASCIIToUTF16("K3"), forward_model->GetLabelAt(index)); + + // Now test the boundary cases by using the chapter stop function directly. + // Out of bounds, first too far right (incrementing), then too far left. + EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(33, false)); + EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(-1, true)); + // Test being at end and going right, then at beginning going left. + EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(32, true)); + EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(0, false)); + // Test success: beginning going right and end going left. + EXPECT_EQ(2, back_model->GetIndexOfNextChapterStop(0, true)); + EXPECT_EQ(29, back_model->GetIndexOfNextChapterStop(32, false)); + // Now see when the chapter stops begin to show up. + EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(1, false)); + EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(2, false)); + EXPECT_EQ(2, back_model->GetIndexOfNextChapterStop(3, false)); + // Now see when the chapter stops end. + EXPECT_EQ(32, back_model->GetIndexOfNextChapterStop(30, true)); + EXPECT_EQ(32, back_model->GetIndexOfNextChapterStop(31, true)); + EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(32, true)); + + // Bug found during review (two different sites, but first wasn't considered + // a chapter-stop). + // Go to A1; + NavigateToIndex(0); + LoadURLAndUpdateState("http://www.b.com/1", "B1"); + EXPECT_EQ(0, back_model->GetIndexOfNextChapterStop(1, false)); + EXPECT_EQ(1, back_model->GetIndexOfNextChapterStop(0, true)); + + // Now see if it counts 'www.x.com' and 'mail.x.com' as same domain, which + // it should. + // Go to A1. + NavigateToIndex(0); + LoadURLAndUpdateState("http://mail.a.com/2", "A2-mai"); + LoadURLAndUpdateState("http://www.b.com/1", "B1"); + LoadURLAndUpdateState("http://mail.b.com/2", "B2-mai"); + LoadURLAndUpdateState("http://new.site.com", "new"); + EXPECT_EQ(1, back_model->GetIndexOfNextChapterStop(0, true)); + EXPECT_EQ(3, back_model->GetIndexOfNextChapterStop(1, true)); + EXPECT_EQ(3, back_model->GetIndexOfNextChapterStop(2, true)); + EXPECT_EQ(4, back_model->GetIndexOfNextChapterStop(3, true)); + // And try backwards as well. + EXPECT_EQ(3, back_model->GetIndexOfNextChapterStop(4, false)); + EXPECT_EQ(1, back_model->GetIndexOfNextChapterStop(3, false)); + EXPECT_EQ(1, back_model->GetIndexOfNextChapterStop(2, false)); + EXPECT_EQ(-1, back_model->GetIndexOfNextChapterStop(1, false)); +} diff --git a/chrome/browser/ui/toolbar/encoding_menu_controller.cc b/chrome/browser/ui/toolbar/encoding_menu_controller.cc new file mode 100644 index 0000000..96ac58f --- /dev/null +++ b/chrome/browser/ui/toolbar/encoding_menu_controller.cc @@ -0,0 +1,141 @@ +// Copyright (c) 2009 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/ui/toolbar/encoding_menu_controller.h" + +#include "app/l10n_util.h" +#include "base/i18n/rtl.h" +#include "base/utf_string_conversions.h" +#include "chrome/app/chrome_command_ids.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/character_encoding.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profile.h" +#include "chrome/common/pref_names.h" +#include "grit/generated_resources.h" + +const int EncodingMenuController::kValidEncodingIds[] = { + IDC_ENCODING_UTF8, + IDC_ENCODING_UTF16LE, + IDC_ENCODING_ISO88591, + IDC_ENCODING_WINDOWS1252, + IDC_ENCODING_GBK, + IDC_ENCODING_GB18030, + IDC_ENCODING_BIG5, + IDC_ENCODING_BIG5HKSCS, + IDC_ENCODING_KOREAN, + IDC_ENCODING_SHIFTJIS, + IDC_ENCODING_ISO2022JP, + IDC_ENCODING_EUCJP, + IDC_ENCODING_THAI, + IDC_ENCODING_ISO885915, + IDC_ENCODING_MACINTOSH, + IDC_ENCODING_ISO88592, + IDC_ENCODING_WINDOWS1250, + IDC_ENCODING_ISO88595, + IDC_ENCODING_WINDOWS1251, + IDC_ENCODING_KOI8R, + IDC_ENCODING_KOI8U, + IDC_ENCODING_ISO88597, + IDC_ENCODING_WINDOWS1253, + IDC_ENCODING_ISO88594, + IDC_ENCODING_ISO885913, + IDC_ENCODING_WINDOWS1257, + IDC_ENCODING_ISO88593, + IDC_ENCODING_ISO885910, + IDC_ENCODING_ISO885914, + IDC_ENCODING_ISO885916, + IDC_ENCODING_WINDOWS1254, + IDC_ENCODING_ISO88596, + IDC_ENCODING_WINDOWS1256, + IDC_ENCODING_ISO88598, + IDC_ENCODING_WINDOWS1255, + IDC_ENCODING_WINDOWS1258, + IDC_ENCODING_ISO88598I, +}; + +bool EncodingMenuController::DoesCommandBelongToEncodingMenu(int id) { + if (id == IDC_ENCODING_AUTO_DETECT) { + return true; + } + + for (size_t i = 0; i < arraysize(kValidEncodingIds); ++i) { + if (id == kValidEncodingIds[i]) { + return true; + } + } + + return false; +} + +const int* EncodingMenuController::ValidGUIEncodingIDs() { + return kValidEncodingIds; +} + +int EncodingMenuController::NumValidGUIEncodingIDs() { + return arraysize(kValidEncodingIds); +} + +bool EncodingMenuController::IsItemChecked( + Profile* browser_profile, + const std::string& current_tab_encoding, + int item_id) { + if (!DoesCommandBelongToEncodingMenu(item_id)) + return false; + + std::string encoding = current_tab_encoding; + if (encoding.empty()) + encoding = browser_profile->GetPrefs()->GetString(prefs::kDefaultCharset); + + if (item_id == IDC_ENCODING_AUTO_DETECT) { + return browser_profile->GetPrefs()->GetBoolean( + prefs::kWebKitUsesUniversalDetector); + } + + if (!encoding.empty()) { + return encoding == + CharacterEncoding::GetCanonicalEncodingNameByCommandId(item_id); + } + + return false; +} + +void EncodingMenuController::GetEncodingMenuItems(Profile* profile, + EncodingMenuItemList* menuItems) { + + DCHECK(menuItems); + EncodingMenuItem separator(0, string16()); + + menuItems->clear(); + menuItems->push_back( + EncodingMenuItem(IDC_ENCODING_AUTO_DETECT, + l10n_util::GetStringUTF16(IDS_ENCODING_AUTO_DETECT))); + menuItems->push_back(separator); + + // Create current display encoding list. + const std::vector<CharacterEncoding::EncodingInfo>* encodings; + + // Build the list of encoding ids : It is made of the + // locale-dependent short list, the cache of recently selected + // encodings and other encodings. + encodings = CharacterEncoding::GetCurrentDisplayEncodings( + g_browser_process->GetApplicationLocale(), + profile->GetPrefs()->GetString(prefs::kStaticEncodings), + profile->GetPrefs()->GetString(prefs::kRecentlySelectedEncoding)); + DCHECK(encodings); + DCHECK(!encodings->empty()); + + // Build up output list for menu. + std::vector<CharacterEncoding::EncodingInfo>::const_iterator it; + for (it = encodings->begin(); it != encodings->end(); ++it) { + if (it->encoding_id) { + std::wstring encoding = it->encoding_display_name; + base::i18n::AdjustStringForLocaleDirection(&encoding); + menuItems->push_back(EncodingMenuItem(it->encoding_id, + WideToUTF16(encoding))); + } else { + menuItems->push_back(separator); + } + } +} diff --git a/chrome/browser/ui/toolbar/encoding_menu_controller.h b/chrome/browser/ui/toolbar/encoding_menu_controller.h new file mode 100644 index 0000000..bfa8c57 --- /dev/null +++ b/chrome/browser/ui/toolbar/encoding_menu_controller.h @@ -0,0 +1,56 @@ +// 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. + +#ifndef CHROME_BROWSER_UI_TOOLBAR_ENCODING_MENU_CONTROLLER_H_ +#define CHROME_BROWSER_UI_TOOLBAR_ENCODING_MENU_CONTROLLER_H_ +#pragma once + +#include <utility> +#include <string> +#include <vector> + +#include "base/basictypes.h" // For DISALLOW_COPY_AND_ASSIGN +#include "base/gtest_prod_util.h" +#include "base/string16.h" + +class Profile; + +// Cross-platform logic needed for the encoding menu. +// For now, we don't need to track state so all methods are static. +class EncodingMenuController { + FRIEND_TEST_ALL_PREFIXES(EncodingMenuControllerTest, EncodingIDsBelongTest); + FRIEND_TEST_ALL_PREFIXES(EncodingMenuControllerTest, IsItemChecked); + + public: + typedef std::pair<int, string16> EncodingMenuItem; + typedef std::vector<EncodingMenuItem> EncodingMenuItemList; + + public: + EncodingMenuController() {} + + // Given a command ID, does this command belong to the encoding menu? + bool DoesCommandBelongToEncodingMenu(int id); + + // Returns true if the given encoding menu item (specified by item_id) + // is checked. Note that this header is included from objc, where the name + // "id" is reserved. + bool IsItemChecked(Profile* browser_profile, + const std::string& current_tab_encoding, + int item_id); + + // Fills in a list of menu items in the order they should appear in the menu. + // Items whose ids are 0 are separators. + void GetEncodingMenuItems(Profile* profile, + EncodingMenuItemList* menuItems); + + private: + // List of all valid encoding GUI IDs. + static const int kValidEncodingIds[]; + const int* ValidGUIEncodingIDs(); + int NumValidGUIEncodingIDs(); + + DISALLOW_COPY_AND_ASSIGN(EncodingMenuController); +}; + +#endif // CHROME_BROWSER_UI_TOOLBAR_ENCODING_MENU_CONTROLLER_H_ diff --git a/chrome/browser/ui/toolbar/encoding_menu_controller_unittest.cc b/chrome/browser/ui/toolbar/encoding_menu_controller_unittest.cc new file mode 100644 index 0000000..2538547 --- /dev/null +++ b/chrome/browser/ui/toolbar/encoding_menu_controller_unittest.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2009 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/ui/toolbar/encoding_menu_controller.h" + +#include <string> + +#include "base/basictypes.h" +#include "chrome/app/chrome_command_ids.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profile.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/testing_profile.h" +#include "testing/gtest/include/gtest/gtest.h" + + +class EncodingMenuControllerTest : public testing::Test { +}; + +TEST_F(EncodingMenuControllerTest, EncodingIDsBelongTest) { + EncodingMenuController controller; + + // Check some bogus ids to make sure they're never valid. + ASSERT_FALSE(controller.DoesCommandBelongToEncodingMenu(0)); + ASSERT_FALSE(controller.DoesCommandBelongToEncodingMenu(-1)); + + int num_valid_encoding_ids = controller.NumValidGUIEncodingIDs(); + const int* valid_encodings = controller.ValidGUIEncodingIDs(); + ASSERT_TRUE(controller.DoesCommandBelongToEncodingMenu( + IDC_ENCODING_AUTO_DETECT)); + // Check that all valid encodings are accepted. + for (int i = 0; i < num_valid_encoding_ids; ++i) { + ASSERT_TRUE(controller.DoesCommandBelongToEncodingMenu(valid_encodings[i])); + } + + // This test asserts that we haven't added a new valid ID without including it + // in the kValidEncodingIds test list above. + // The premise is that new encodings will be added directly after the current + // ones so we'll catch such cases. + int one_past_largest_id = valid_encodings[num_valid_encoding_ids - 1] + 1; + ASSERT_FALSE(controller.DoesCommandBelongToEncodingMenu(one_past_largest_id)); +} + +TEST_F(EncodingMenuControllerTest, ListEncodingMenuItems) { + typedef EncodingMenuController::EncodingMenuItemList EncodingMenuItemList; + EncodingMenuController controller; + + EncodingMenuItemList english_items; + TestingProfile profile_en; + + controller.GetEncodingMenuItems(&profile_en, &english_items); + + // Make sure there are items in the lists. + ASSERT_TRUE(english_items.size() > 0); + // Make sure that autodetect is the first item on both menus + ASSERT_EQ(english_items[0].first, IDC_ENCODING_AUTO_DETECT); +} + +TEST_F(EncodingMenuControllerTest, IsItemChecked) { + TestingProfile profile_en; + std::string encoding("UTF-8"); + + // Check that enabling and disabling autodetect works. + bool autodetect_enabed[] = {true, false}; + PrefService* prefs = profile_en.GetPrefs(); + EncodingMenuController controller; + for (size_t i = 0; i < arraysize(autodetect_enabed); ++i) { + bool enabled = autodetect_enabed[i]; + prefs->SetBoolean(prefs::kWebKitUsesUniversalDetector, enabled); + ASSERT_TRUE(controller.IsItemChecked(&profile_en, + encoding, + IDC_ENCODING_AUTO_DETECT) == enabled); + } + + // Check all valid encodings, make sure only one is enabled when autodetection + // is turned off. + prefs->SetBoolean(prefs::kWebKitUsesUniversalDetector, false); + bool encoding_is_enabled = false; + size_t num_valid_encoding_ids = controller.NumValidGUIEncodingIDs(); + const int* valid_encodings = controller.ValidGUIEncodingIDs(); + for (size_t i = 0; i < num_valid_encoding_ids; ++i) { + bool on = controller.IsItemChecked(&profile_en, + encoding, + valid_encodings[i]); + // Only one item in the encoding menu can be selected at a time. + ASSERT_FALSE(on && encoding_is_enabled); + encoding_is_enabled |= on; + } + + // Make sure at least one encoding is enabled. + ASSERT_TRUE(encoding_is_enabled); +} diff --git a/chrome/browser/ui/toolbar/toolbar_model.cc b/chrome/browser/ui/toolbar/toolbar_model.cc new file mode 100644 index 0000000..b6533fd --- /dev/null +++ b/chrome/browser/ui/toolbar/toolbar_model.cc @@ -0,0 +1,129 @@ +// 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/ui/toolbar/toolbar_model.h" + +#include "base/utf_string_conversions.h" +#include "chrome/browser/autocomplete/autocomplete.h" +#include "chrome/browser/autocomplete/autocomplete_edit.h" +#include "chrome/browser/cert_store.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/ssl/ssl_error_info.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" +#include "net/base/cert_status_flags.h" +#include "net/base/net_util.h" + +ToolbarModel::ToolbarModel(Browser* browser) + : browser_(browser), + input_in_progress_(false) { +} + +ToolbarModel::~ToolbarModel() { +} + +// ToolbarModel Implementation. +std::wstring ToolbarModel::GetText() const { + GURL url(chrome::kAboutBlankURL); + std::string languages; // Empty if we don't have a |navigation_controller|. + + NavigationController* navigation_controller = GetNavigationController(); + if (navigation_controller) { + languages = navigation_controller->profile()->GetPrefs()->GetString( + prefs::kAcceptLanguages); + NavigationEntry* entry = navigation_controller->GetActiveEntry(); + if (!navigation_controller->tab_contents()->ShouldDisplayURL()) { + // Explicitly hide the URL for this tab. + url = GURL(); + } else if (entry) { + url = entry->virtual_url(); + } + } + if (url.spec().length() > chrome::kMaxURLDisplayChars) + url = url.IsStandard() ? url.GetOrigin() : GURL(url.scheme() + ":"); + // Note that we can't unescape spaces here, because if the user copies this + // and pastes it into another program, that program may think the URL ends at + // the space. + return AutocompleteInput::FormattedStringWithEquivalentMeaning(url, + UTF16ToWideHack(net::FormatUrl(url, languages, net::kFormatUrlOmitAll, + UnescapeRule::NORMAL, NULL, NULL, NULL))); +} + +ToolbarModel::SecurityLevel ToolbarModel::GetSecurityLevel() const { + if (input_in_progress_) // When editing, assume no security style. + return NONE; + + NavigationController* navigation_controller = GetNavigationController(); + if (!navigation_controller) // We might not have a controller on init. + return NONE; + + NavigationEntry* entry = navigation_controller->GetActiveEntry(); + if (!entry) + return NONE; + + const NavigationEntry::SSLStatus& ssl = entry->ssl(); + switch (ssl.security_style()) { + case SECURITY_STYLE_UNKNOWN: + case SECURITY_STYLE_UNAUTHENTICATED: + return NONE; + + case SECURITY_STYLE_AUTHENTICATION_BROKEN: + return SECURITY_ERROR; + + case SECURITY_STYLE_AUTHENTICATED: + if (ssl.displayed_insecure_content()) + return SECURITY_WARNING; + if (net::IsCertStatusError(ssl.cert_status())) { + DCHECK_EQ(ssl.cert_status() & net::CERT_STATUS_ALL_ERRORS, + net::CERT_STATUS_UNABLE_TO_CHECK_REVOCATION); + return SECURITY_WARNING; + } + if ((ssl.cert_status() & net::CERT_STATUS_IS_EV) && + CertStore::GetSharedInstance()->RetrieveCert(ssl.cert_id(), NULL)) + return EV_SECURE; + return SECURE; + + default: + NOTREACHED(); + return NONE; + } +} + +int ToolbarModel::GetIcon() const { + static int icon_ids[NUM_SECURITY_LEVELS] = { + IDR_OMNIBOX_HTTP, + IDR_OMNIBOX_HTTPS_VALID, + IDR_OMNIBOX_HTTPS_VALID, + IDR_OMNIBOX_HTTPS_WARNING, + IDR_OMNIBOX_HTTPS_INVALID, + }; + DCHECK(arraysize(icon_ids) == NUM_SECURITY_LEVELS); + return icon_ids[GetSecurityLevel()]; +} + +std::wstring ToolbarModel::GetEVCertName() const { + DCHECK_EQ(GetSecurityLevel(), EV_SECURE); + scoped_refptr<net::X509Certificate> cert; + // Note: Navigation controller and active entry are guaranteed non-NULL or + // the security level would be NONE. + CertStore::GetSharedInstance()->RetrieveCert( + GetNavigationController()->GetActiveEntry()->ssl().cert_id(), &cert); + return SSLManager::GetEVCertName(*cert); +} + +NavigationController* ToolbarModel::GetNavigationController() const { + // This |current_tab| can be NULL during the initialization of the + // toolbar during window creation (i.e. before any tabs have been added + // to the window). + TabContents* current_tab = browser_->GetSelectedTabContents(); + return current_tab ? ¤t_tab->controller() : NULL; +} diff --git a/chrome/browser/ui/toolbar/toolbar_model.h b/chrome/browser/ui/toolbar/toolbar_model.h new file mode 100644 index 0000000..d9ebec5 --- /dev/null +++ b/chrome/browser/ui/toolbar/toolbar_model.h @@ -0,0 +1,71 @@ +// 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. + +#ifndef CHROME_BROWSER_UI_TOOLBAR_TOOLBAR_MODEL_H_ +#define CHROME_BROWSER_UI_TOOLBAR_TOOLBAR_MODEL_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" + +class Browser; +class NavigationController; + +// This class is the model used by the toolbar, location bar and autocomplete +// edit. It populates its states from the current navigation entry retrieved +// from the navigation controller returned by GetNavigationController(). +class ToolbarModel { + public: + // TODO(wtc): unify ToolbarModel::SecurityLevel with SecurityStyle. We + // don't need two sets of security UI levels. SECURITY_STYLE_AUTHENTICATED + // needs to be refined into three levels: warning, standard, and EV. + enum SecurityLevel { + NONE = 0, // HTTP/no URL/user is editing + EV_SECURE, // HTTPS with valid EV cert + SECURE, // HTTPS (non-EV) + SECURITY_WARNING, // HTTPS, but unable to check certificate revocation + // status or with insecure content on the page + SECURITY_ERROR, // Attempted HTTPS and failed, page not authenticated + NUM_SECURITY_LEVELS, + }; + + explicit ToolbarModel(Browser* browser); + ~ToolbarModel(); + + // Returns the text that should be displayed in the location bar. + std::wstring GetText() const; + + // Returns the security level that the toolbar should display. + SecurityLevel GetSecurityLevel() const; + + // Returns the resource_id of the icon to show to the left of the address, + // based on the current URL. This doesn't cover specialized icons while the + // user is editing; see AutocompleteEditView::GetIcon(). + int GetIcon() const; + + // Returns the name of the EV cert holder. Only call this when the security + // level is EV_SECURE. + std::wstring GetEVCertName() const; + + // Getter/setter of whether the text in location bar is currently being + // edited. + void set_input_in_progress(bool value) { input_in_progress_ = value; } + bool input_in_progress() const { return input_in_progress_; } + + private: + // Returns the navigation controller used to retrieve the navigation entry + // from which the states are retrieved. + // If this returns NULL, default values are used. + NavigationController* GetNavigationController() const; + + Browser* browser_; + + // Whether the text in the location bar is currently being edited. + bool input_in_progress_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(ToolbarModel); +}; + +#endif // CHROME_BROWSER_UI_TOOLBAR_TOOLBAR_MODEL_H_ diff --git a/chrome/browser/ui/toolbar/wrench_menu_model.cc b/chrome/browser/ui/toolbar/wrench_menu_model.cc new file mode 100644 index 0000000..6c83a00 --- /dev/null +++ b/chrome/browser/ui/toolbar/wrench_menu_model.cc @@ -0,0 +1,539 @@ +// 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/ui/toolbar/wrench_menu_model.h" + +#include <algorithm> +#include <cmath> + +#include "app/l10n_util.h" +#include "app/menus/button_menu_item_model.h" +#include "app/resource_bundle.h" +#include "base/command_line.h" +#include "base/i18n/number_formatting.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "chrome/app/chrome_command_ids.h" +#include "chrome/browser/background_page_tracker.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/defaults.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/sync_ui_util.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tabs/tab_strip_model.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/toolbar/encoding_menu_controller.h" +#include "chrome/browser/upgrade_detector.h" +#include "chrome/common/badge_util.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/notification_source.h" +#include "chrome/common/notification_type.h" +#include "chrome/common/pref_names.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" + +#if defined(OS_LINUX) +#include <gtk/gtk.h> +#include "chrome/browser/gtk/gtk_util.h" +#endif + +#if defined(OS_MACOSX) +#include "chrome/browser/ui/browser_window.h" +#endif + +#if defined(OS_CHROMEOS) +#include "chrome/browser/chromeos/cros/cros_library.h" +#include "chrome/browser/chromeos/cros/update_library.h" +#endif + +#if defined(OS_WIN) +#include "chrome/browser/enumerate_modules_model_win.h" +#endif + +// The size of the font used for dynamic text overlays on menu items. +const float kMenuBadgeFontSize = 12.0; + +namespace { +SkBitmap GetBackgroundPageIcon() { + string16 pages = base::FormatNumber( + BackgroundPageTracker::GetSingleton()->GetBackgroundPageCount()); + return badge_util::DrawBadgeIconOverlay( + *ResourceBundle::GetSharedInstance().GetBitmapNamed(IDR_BACKGROUND_MENU), + kMenuBadgeFontSize, + pages, + l10n_util::GetStringUTF16(IDS_BACKGROUND_PAGE_BADGE_OVERFLOW)); +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// EncodingMenuModel + +EncodingMenuModel::EncodingMenuModel(Browser* browser) + : ALLOW_THIS_IN_INITIALIZER_LIST(menus::SimpleMenuModel(this)), + browser_(browser) { + Build(); +} + +EncodingMenuModel::~EncodingMenuModel() { +} + +void EncodingMenuModel::Build() { + EncodingMenuController::EncodingMenuItemList encoding_menu_items; + EncodingMenuController encoding_menu_controller; + encoding_menu_controller.GetEncodingMenuItems(browser_->profile(), + &encoding_menu_items); + + int group_id = 0; + EncodingMenuController::EncodingMenuItemList::iterator it = + encoding_menu_items.begin(); + for (; it != encoding_menu_items.end(); ++it) { + int id = it->first; + string16& label = it->second; + if (id == 0) { + AddSeparator(); + } else { + if (id == IDC_ENCODING_AUTO_DETECT) { + AddCheckItem(id, label); + } else { + // Use the id of the first radio command as the id of the group. + if (group_id <= 0) + group_id = id; + AddRadioItem(id, label, group_id); + } + } + } +} + +bool EncodingMenuModel::IsCommandIdChecked(int command_id) const { + TabContents* current_tab = browser_->GetSelectedTabContents(); + if (!current_tab) + return false; + EncodingMenuController controller; + return controller.IsItemChecked(browser_->profile(), + current_tab->encoding(), command_id); +} + +bool EncodingMenuModel::IsCommandIdEnabled(int command_id) const { + bool enabled = browser_->command_updater()->IsCommandEnabled(command_id); + // Special handling for the contents of the Encoding submenu. On Mac OS, + // instead of enabling/disabling the top-level menu item, the submenu's + // contents get disabled, per Apple's HIG. +#if defined(OS_MACOSX) + enabled &= browser_->command_updater()->IsCommandEnabled(IDC_ENCODING_MENU); +#endif + return enabled; +} + +bool EncodingMenuModel::GetAcceleratorForCommandId( + int command_id, + menus::Accelerator* accelerator) { + return false; +} + +void EncodingMenuModel::ExecuteCommand(int command_id) { + browser_->ExecuteCommand(command_id); +} + +//////////////////////////////////////////////////////////////////////////////// +// ZoomMenuModel + +ZoomMenuModel::ZoomMenuModel(menus::SimpleMenuModel::Delegate* delegate) + : SimpleMenuModel(delegate) { + Build(); +} + +ZoomMenuModel::~ZoomMenuModel() { +} + +void ZoomMenuModel::Build() { + AddItemWithStringId(IDC_ZOOM_PLUS, IDS_ZOOM_PLUS); + AddItemWithStringId(IDC_ZOOM_NORMAL, IDS_ZOOM_NORMAL); + AddItemWithStringId(IDC_ZOOM_MINUS, IDS_ZOOM_MINUS); +} + +//////////////////////////////////////////////////////////////////////////////// +// ToolsMenuModel + +ToolsMenuModel::ToolsMenuModel(menus::SimpleMenuModel::Delegate* delegate, + Browser* browser) + : SimpleMenuModel(delegate) { + Build(browser); +} + +ToolsMenuModel::~ToolsMenuModel() {} + +void ToolsMenuModel::Build(Browser* browser) { + AddCheckItemWithStringId(IDC_SHOW_BOOKMARK_BAR, IDS_SHOW_BOOKMARK_BAR); + + AddSeparator(); + +#if !defined(OS_CHROMEOS) +#if defined(OS_MACOSX) + AddItemWithStringId(IDC_CREATE_SHORTCUTS, IDS_CREATE_APPLICATION_MAC); +#else + AddItemWithStringId(IDC_CREATE_SHORTCUTS, IDS_CREATE_SHORTCUTS); +#endif + AddSeparator(); +#endif + + AddItemWithStringId(IDC_MANAGE_EXTENSIONS, IDS_SHOW_EXTENSIONS); + AddItemWithStringId(IDC_TASK_MANAGER, IDS_TASK_MANAGER); + AddItemWithStringId(IDC_CLEAR_BROWSING_DATA, IDS_CLEAR_BROWSING_DATA); + + AddSeparator(); +#if defined(OS_CHROMEOS) + AddItemWithStringId(IDC_FEEDBACK, IDS_FEEDBACK); + AddSeparator(); +#endif + + encoding_menu_model_.reset(new EncodingMenuModel(browser)); + AddSubMenuWithStringId(IDC_ENCODING_MENU, IDS_ENCODING_MENU, + encoding_menu_model_.get()); + AddItemWithStringId(IDC_VIEW_SOURCE, IDS_VIEW_SOURCE); + if (g_browser_process->have_inspector_files()) { + AddItemWithStringId(IDC_DEV_TOOLS, IDS_DEV_TOOLS); + AddItemWithStringId(IDC_DEV_TOOLS_CONSOLE, IDS_DEV_TOOLS_CONSOLE); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// WrenchMenuModel + +WrenchMenuModel::WrenchMenuModel(menus::AcceleratorProvider* provider, + Browser* browser) + : ALLOW_THIS_IN_INITIALIZER_LIST(menus::SimpleMenuModel(this)), + provider_(provider), + browser_(browser), + tabstrip_model_(browser_->tabstrip_model()) { + Build(); + UpdateZoomControls(); + + tabstrip_model_->AddObserver(this); + + registrar_.Add(this, NotificationType::ZOOM_LEVEL_CHANGED, + Source<Profile>(browser_->profile())); + registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, + NotificationService::AllSources()); + registrar_.Add(this, + NotificationType::BACKGROUND_PAGE_TRACKER_CHANGED, + NotificationService::AllSources()); +} + +WrenchMenuModel::~WrenchMenuModel() { + if (tabstrip_model_) + tabstrip_model_->RemoveObserver(this); +} + +bool WrenchMenuModel::DoesCommandIdDismissMenu(int command_id) const { + return command_id != IDC_ZOOM_MINUS && command_id != IDC_ZOOM_PLUS; +} + +bool WrenchMenuModel::IsLabelForCommandIdDynamic(int command_id) const { + return command_id == IDC_ZOOM_PERCENT_DISPLAY || +#if defined(OS_MACOSX) + command_id == IDC_FULLSCREEN || +#endif + command_id == IDC_SYNC_BOOKMARKS || + command_id == IDC_VIEW_BACKGROUND_PAGES; +} + +string16 WrenchMenuModel::GetLabelForCommandId(int command_id) const { + switch (command_id) { + case IDC_SYNC_BOOKMARKS: + return GetSyncMenuLabel(); + case IDC_ZOOM_PERCENT_DISPLAY: + return zoom_label_; +#if defined(OS_MACOSX) + case IDC_FULLSCREEN: { + int string_id = IDS_ENTER_FULLSCREEN_MAC; // Default to Enter. + // Note: On startup, |window()| may be NULL. + if (browser_->window() && browser_->window()->IsFullscreen()) + string_id = IDS_EXIT_FULLSCREEN_MAC; + return l10n_util::GetStringUTF16(string_id); + } +#endif + case IDC_VIEW_BACKGROUND_PAGES: { + string16 num_background_pages = base::FormatNumber( + BackgroundPageTracker::GetSingleton()->GetBackgroundPageCount()); + return l10n_util::GetStringFUTF16(IDS_VIEW_BACKGROUND_PAGES, + num_background_pages); + } + default: + NOTREACHED(); + return string16(); + } +} + +void WrenchMenuModel::ExecuteCommand(int command_id) { + browser_->ExecuteCommand(command_id); +} + +bool WrenchMenuModel::IsCommandIdChecked(int command_id) const { + if (command_id == IDC_SHOW_BOOKMARK_BAR) { + return browser_->profile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar); + } + + return false; +} + +bool WrenchMenuModel::IsCommandIdEnabled(int command_id) const { +#if defined(OS_CHROMEOS) + // Special case because IDC_NEW_WINDOW item should be disabled in BWSI mode, + // but accelerator should work. + if (command_id == IDC_NEW_WINDOW && + CommandLine::ForCurrentProcess()->HasSwitch(switches::kGuestSession)) + return false; +#endif + + return browser_->command_updater()->IsCommandEnabled(command_id); +} + +bool WrenchMenuModel::IsCommandIdVisible(int command_id) const { + if (command_id == IDC_UPGRADE_DIALOG) { +#if defined(OS_CHROMEOS) + return (chromeos::CrosLibrary::Get()->GetUpdateLibrary()->status().status + == chromeos::UPDATE_STATUS_UPDATED_NEED_REBOOT); +#else + return Singleton<UpgradeDetector>::get()->notify_upgrade(); +#endif + } else if (command_id == IDC_VIEW_INCOMPATIBILITIES) { +#if defined(OS_WIN) + EnumerateModulesModel* loaded_modules = + EnumerateModulesModel::GetSingleton(); + return loaded_modules->confirmed_bad_modules_detected() > 0; +#else + return false; +#endif + } else if (command_id == IDC_VIEW_BACKGROUND_PAGES) { + BackgroundPageTracker* tracker = BackgroundPageTracker::GetSingleton(); + return tracker->GetBackgroundPageCount() > 0; + } + return true; +} + +bool WrenchMenuModel::GetAcceleratorForCommandId( + int command_id, + menus::Accelerator* accelerator) { + return provider_->GetAcceleratorForCommandId(command_id, accelerator); +} + +void WrenchMenuModel::TabSelectedAt(TabContentsWrapper* old_contents, + TabContentsWrapper* new_contents, + int index, + bool user_gesture) { + // The user has switched between tabs and the new tab may have a different + // zoom setting. + UpdateZoomControls(); +} + +void WrenchMenuModel::TabReplacedAt(TabContentsWrapper* old_contents, + TabContentsWrapper* new_contents, + int index) { + UpdateZoomControls(); +} + +void WrenchMenuModel::TabStripModelDeleted() { + // During views shutdown, the tabstrip model/browser is deleted first, while + // it is the opposite in gtk land. + tabstrip_model_->RemoveObserver(this); + tabstrip_model_ = NULL; +} + +void WrenchMenuModel::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type.value) { + case NotificationType::BACKGROUND_PAGE_TRACKER_CHANGED: { + int num_pages = BackgroundPageTracker::GetSingleton()-> + GetUnacknowledgedBackgroundPageCount(); + if (num_pages > 0) { + SetIcon(GetIndexOfCommandId(IDC_VIEW_BACKGROUND_PAGES), + GetBackgroundPageIcon()); + } else { + // Just set a blank icon (no icon). + SetIcon(GetIndexOfCommandId(IDC_VIEW_BACKGROUND_PAGES), SkBitmap()); + } + break; + } + case NotificationType::ZOOM_LEVEL_CHANGED: + case NotificationType::NAV_ENTRY_COMMITTED: + UpdateZoomControls(); + break; + default: + NOTREACHED(); + } +} + +// For testing. +WrenchMenuModel::WrenchMenuModel() + : ALLOW_THIS_IN_INITIALIZER_LIST(menus::SimpleMenuModel(this)), + provider_(NULL), + browser_(NULL), + tabstrip_model_(NULL) { +} + +void WrenchMenuModel::Build() { + AddItemWithStringId(IDC_NEW_TAB, IDS_NEW_TAB); + AddItemWithStringId(IDC_NEW_WINDOW, IDS_NEW_WINDOW); + AddItemWithStringId(IDC_NEW_INCOGNITO_WINDOW, + IDS_NEW_INCOGNITO_WINDOW); + + AddSeparator(); +#if defined(OS_MACOSX) || (defined(OS_LINUX) && !defined(TOOLKIT_VIEWS)) + // WARNING: Mac does not use the ButtonMenuItemModel, but instead defines the + // layout for this menu item in Toolbar.xib. It does, however, use the + // command_id value from AddButtonItem() to identify this special item. + edit_menu_item_model_.reset(new menus::ButtonMenuItemModel(IDS_EDIT, this)); + edit_menu_item_model_->AddGroupItemWithStringId(IDC_CUT, IDS_CUT); + edit_menu_item_model_->AddGroupItemWithStringId(IDC_COPY, IDS_COPY); + edit_menu_item_model_->AddGroupItemWithStringId(IDC_PASTE, IDS_PASTE); + AddButtonItem(IDC_EDIT_MENU, edit_menu_item_model_.get()); +#else + // TODO(port): Move to the above. + CreateCutCopyPaste(); +#endif + + AddSeparator(); +#if defined(OS_MACOSX) || (defined(OS_LINUX) && !defined(TOOLKIT_VIEWS)) + // WARNING: See above comment. + zoom_menu_item_model_.reset( + new menus::ButtonMenuItemModel(IDS_ZOOM_MENU, this)); + zoom_menu_item_model_->AddGroupItemWithStringId( + IDC_ZOOM_MINUS, IDS_ZOOM_MINUS2); + zoom_menu_item_model_->AddButtonLabel(IDC_ZOOM_PERCENT_DISPLAY, + IDS_ZOOM_PLUS2); + zoom_menu_item_model_->AddGroupItemWithStringId( + IDC_ZOOM_PLUS, IDS_ZOOM_PLUS2); + zoom_menu_item_model_->AddSpace(); + zoom_menu_item_model_->AddItemWithImage( + IDC_FULLSCREEN, IDR_FULLSCREEN_MENU_BUTTON); + AddButtonItem(IDC_ZOOM_MENU, zoom_menu_item_model_.get()); +#else + // TODO(port): Move to the above. + CreateZoomFullscreen(); +#endif + + AddSeparator(); + AddItemWithStringId(IDC_SAVE_PAGE, IDS_SAVE_PAGE); + AddItemWithStringId(IDC_FIND, IDS_FIND); + AddItemWithStringId(IDC_PRINT, IDS_PRINT); + + tools_menu_model_.reset(new ToolsMenuModel(this, browser_)); + AddSubMenuWithStringId(IDC_ZOOM_MENU, IDS_TOOLS_MENU, + tools_menu_model_.get()); + + AddSeparator(); +#if defined(ENABLE_REMOTING) + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableRemoting)) { + AddItem(IDC_REMOTING_SETUP, + l10n_util::GetStringUTF16(IDS_REMOTING_SETUP_LABEL)); + } +#endif + AddItemWithStringId(IDC_SHOW_BOOKMARK_MANAGER, IDS_BOOKMARK_MANAGER); + AddItemWithStringId(IDC_SHOW_HISTORY, IDS_SHOW_HISTORY); + AddItemWithStringId(IDC_SHOW_DOWNLOADS, IDS_SHOW_DOWNLOADS); + AddSeparator(); + +#if defined(OS_CHROMEOS) + AddItemWithStringId(IDC_OPTIONS, IDS_SETTINGS); +#elif defined(OS_MACOSX) + AddItemWithStringId(IDC_OPTIONS, IDS_PREFERENCES); +#elif defined(OS_LINUX) + string16 preferences = gtk_util::GetStockPreferencesMenuLabel(); + if (!preferences.empty()) + AddItem(IDC_OPTIONS, preferences); + else + AddItemWithStringId(IDC_OPTIONS, IDS_PREFERENCES); +#else + AddItemWithStringId(IDC_OPTIONS, IDS_OPTIONS); +#endif + +#if defined(OS_CHROMEOS) + const string16 product_name = l10n_util::GetStringUTF16(IDS_PRODUCT_OS_NAME); +#else + const string16 product_name = l10n_util::GetStringUTF16(IDS_PRODUCT_NAME); +#endif + // On Mac, there is no About item. + if (browser_defaults::kShowAboutMenuItem) { + AddItem(IDC_ABOUT, l10n_util::GetStringFUTF16( + IDS_ABOUT, product_name)); + } + string16 num_background_pages = base::FormatNumber( + BackgroundPageTracker::GetSingleton()->GetBackgroundPageCount()); + AddItem(IDC_VIEW_BACKGROUND_PAGES, l10n_util::GetStringFUTF16( + IDS_VIEW_BACKGROUND_PAGES, num_background_pages)); + AddItem(IDC_UPGRADE_DIALOG, l10n_util::GetStringFUTF16( + IDS_UPDATE_NOW, product_name)); + AddItem(IDC_VIEW_INCOMPATIBILITIES, l10n_util::GetStringUTF16( + IDS_VIEW_INCOMPATIBILITIES)); + + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + SetIcon(GetIndexOfCommandId(IDC_UPGRADE_DIALOG), + *rb.GetBitmapNamed(IDR_UPDATE_MENU)); +#if defined(OS_WIN) + SetIcon(GetIndexOfCommandId(IDC_VIEW_INCOMPATIBILITIES), + *rb.GetBitmapNamed(IDR_CONFLICT_MENU)); +#endif + + // Add an icon to the View Background Pages item if there are unacknowledged + // pages. + if (BackgroundPageTracker::GetSingleton()-> + GetUnacknowledgedBackgroundPageCount() > 0) { + SetIcon(GetIndexOfCommandId(IDC_VIEW_BACKGROUND_PAGES), + GetBackgroundPageIcon()); + } + + AddItemWithStringId(IDC_HELP_PAGE, IDS_HELP_PAGE); + if (browser_defaults::kShowExitMenuItem) { + AddSeparator(); +#if defined(OS_CHROMEOS) + if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kGuestSession)) { + AddItemWithStringId(IDC_EXIT, IDS_EXIT_GUEST_MODE); + } else { + AddItemWithStringId(IDC_EXIT, IDS_SIGN_OUT); + } +#else + AddItemWithStringId(IDC_EXIT, IDS_EXIT); +#endif + } +} + +void WrenchMenuModel::CreateCutCopyPaste() { + // WARNING: views/wrench_menu assumes these items are added in this order. If + // you change the order you'll need to update wrench_menu as well. + AddItemWithStringId(IDC_CUT, IDS_CUT); + AddItemWithStringId(IDC_COPY, IDS_COPY); + AddItemWithStringId(IDC_PASTE, IDS_PASTE); +} + +void WrenchMenuModel::CreateZoomFullscreen() { + // WARNING: views/wrench_menu assumes these items are added in this order. If + // you change the order you'll need to update wrench_menu as well. + AddItemWithStringId(IDC_ZOOM_MINUS, IDS_ZOOM_MINUS); + AddItemWithStringId(IDC_ZOOM_PLUS, IDS_ZOOM_PLUS); + AddItemWithStringId(IDC_FULLSCREEN, IDS_FULLSCREEN); +} + +void WrenchMenuModel::UpdateZoomControls() { + bool enable_increment = false; + bool enable_decrement = false; + int zoom_percent = 100; + if (browser_->GetSelectedTabContents()) { + zoom_percent = browser_->GetSelectedTabContents()->GetZoomPercent( + &enable_increment, &enable_decrement); + } + zoom_label_ = l10n_util::GetStringFUTF16( + IDS_ZOOM_PERCENT, base::IntToString16(zoom_percent)); +} + +string16 WrenchMenuModel::GetSyncMenuLabel() const { + return sync_ui_util::GetSyncMenuLabel( + browser_->profile()->GetOriginalProfile()->GetProfileSyncService()); +} diff --git a/chrome/browser/ui/toolbar/wrench_menu_model.h b/chrome/browser/ui/toolbar/wrench_menu_model.h new file mode 100644 index 0000000..e984a4b --- /dev/null +++ b/chrome/browser/ui/toolbar/wrench_menu_model.h @@ -0,0 +1,149 @@ +// 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. + +#ifndef CHROME_BROWSER_UI_TOOLBAR_WRENCH_MENU_MODEL_H_ +#define CHROME_BROWSER_UI_TOOLBAR_WRENCH_MENU_MODEL_H_ +#pragma once + +#include "app/menus/accelerator.h" +#include "app/menus/button_menu_item_model.h" +#include "app/menus/simple_menu_model.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/tabs/tab_strip_model_observer.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" + +class Browser; +class TabStripModel; + +namespace { +class MockWrenchMenuModel; +} // namespace + +// A menu model that builds the contents of an encoding menu. +class EncodingMenuModel : public menus::SimpleMenuModel, + public menus::SimpleMenuModel::Delegate { + public: + explicit EncodingMenuModel(Browser* browser); + virtual ~EncodingMenuModel(); + + // Overridden from menus::SimpleMenuModel::Delegate: + virtual bool IsCommandIdChecked(int command_id) const; + virtual bool IsCommandIdEnabled(int command_id) const; + virtual bool GetAcceleratorForCommandId(int command_id, + menus::Accelerator* accelerator); + virtual void ExecuteCommand(int command_id); + + private: + void Build(); + + Browser* browser_; // weak + + DISALLOW_COPY_AND_ASSIGN(EncodingMenuModel); +}; + +// A menu model that builds the contents of the zoom menu. +class ZoomMenuModel : public menus::SimpleMenuModel { + public: + explicit ZoomMenuModel(menus::SimpleMenuModel::Delegate* delegate); + virtual ~ZoomMenuModel(); + + private: + void Build(); + + DISALLOW_COPY_AND_ASSIGN(ZoomMenuModel); +}; + +class ToolsMenuModel : public menus::SimpleMenuModel { + public: + ToolsMenuModel(menus::SimpleMenuModel::Delegate* delegate, Browser* browser); + virtual ~ToolsMenuModel(); + + private: + void Build(Browser* browser); + + scoped_ptr<EncodingMenuModel> encoding_menu_model_; + + DISALLOW_COPY_AND_ASSIGN(ToolsMenuModel); +}; + +// A menu model that builds the contents of the wrench menu. +class WrenchMenuModel : public menus::SimpleMenuModel, + public menus::SimpleMenuModel::Delegate, + public menus::ButtonMenuItemModel::Delegate, + public TabStripModelObserver, + public NotificationObserver { + public: + WrenchMenuModel(menus::AcceleratorProvider* provider, Browser* browser); + virtual ~WrenchMenuModel(); + + // Overridden for ButtonMenuItemModel::Delegate: + virtual bool DoesCommandIdDismissMenu(int command_id) const; + + // Overridden for both ButtonMenuItemModel::Delegate and SimpleMenuModel: + virtual bool IsLabelForCommandIdDynamic(int command_id) const; + virtual string16 GetLabelForCommandId(int command_id) const; + virtual void ExecuteCommand(int command_id); + virtual bool IsCommandIdChecked(int command_id) const; + virtual bool IsCommandIdEnabled(int command_id) const; + virtual bool IsCommandIdVisible(int command_id) const; + virtual bool GetAcceleratorForCommandId( + int command_id, + menus::Accelerator* accelerator); + + // Overridden from TabStripModelObserver: + virtual void TabSelectedAt(TabContentsWrapper* old_contents, + TabContentsWrapper* new_contents, + int index, + bool user_gesture); + virtual void TabReplacedAt(TabContentsWrapper* old_contents, + TabContentsWrapper* new_contents, int index); + virtual void TabStripModelDeleted(); + + // Overridden from NotificationObserver: + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // Getters. + Browser* browser() const { return browser_; } + + // Calculates |zoom_label_| in response to a zoom change. + void UpdateZoomControls(); + + private: + // Testing constructor used for mocking. + friend class ::MockWrenchMenuModel; + WrenchMenuModel(); + + void Build(); + + // Adds custom items to the menu. Deprecated in favor of a cross platform + // model for button items. + void CreateCutCopyPaste(); + void CreateZoomFullscreen(); + + string16 GetSyncMenuLabel() const; + + // Models for the special menu items with buttons. + scoped_ptr<menus::ButtonMenuItemModel> edit_menu_item_model_; + scoped_ptr<menus::ButtonMenuItemModel> zoom_menu_item_model_; + + // Label of the zoom label in the zoom menu item. + string16 zoom_label_; + + // Tools menu. + scoped_ptr<ToolsMenuModel> tools_menu_model_; + + menus::AcceleratorProvider* provider_; // weak + + Browser* browser_; // weak + TabStripModel* tabstrip_model_; // weak + + NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(WrenchMenuModel); +}; + +#endif // CHROME_BROWSER_UI_TOOLBAR_WRENCH_MENU_MODEL_H_ diff --git a/chrome/browser/ui/toolbar/wrench_menu_model_unittest.cc b/chrome/browser/ui/toolbar/wrench_menu_model_unittest.cc new file mode 100644 index 0000000..92a8125 --- /dev/null +++ b/chrome/browser/ui/toolbar/wrench_menu_model_unittest.cc @@ -0,0 +1,105 @@ +// 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/ui/toolbar/wrench_menu_model.h" + +#include "chrome/app/chrome_command_ids.h" +#include "chrome/test/browser_with_test_window_test.h" +#include "chrome/test/menu_model_test.h" +#include "chrome/test/testing_profile.h" +#include "grit/generated_resources.h" +#include "testing/gtest/include/gtest/gtest.h" + +class WrenchMenuModelTest : public BrowserWithTestWindowTest, + public menus::AcceleratorProvider { + public: + // Don't handle accelerators. + virtual bool GetAcceleratorForCommandId( + int command_id, + menus::Accelerator* accelerator) { return false; } +}; + +// Copies parts of MenuModelTest::Delegate and combines them with the +// WrenchMenuModel since WrenchMenuModel is now a SimpleMenuModel::Delegate and +// not derived from SimpleMenuModel. +class TestWrenchMenuModel : public WrenchMenuModel { + public: + TestWrenchMenuModel(menus::AcceleratorProvider* provider, + Browser* browser) + : WrenchMenuModel(provider, browser), + execute_count_(0), + checked_count_(0), + enable_count_(0) { + } + + // Testing overrides to menus::SimpleMenuModel::Delegate: + virtual bool IsCommandIdChecked(int command_id) const { + bool val = WrenchMenuModel::IsCommandIdChecked(command_id); + if (val) + checked_count_++; + return val; + } + + virtual bool IsCommandIdEnabled(int command_id) const { + ++enable_count_; + return true; + } + + virtual void ExecuteCommand(int command_id) { ++execute_count_; } + + int execute_count_; + mutable int checked_count_; + mutable int enable_count_; +}; + +TEST_F(WrenchMenuModelTest, Basics) { + TestWrenchMenuModel model(this, browser()); + int itemCount = model.GetItemCount(); + + // Verify it has items. The number varies by platform, so we don't check + // the exact number. + EXPECT_GT(itemCount, 10); + + // Execute a couple of the items and make sure it gets back to our delegate. + // We can't use CountEnabledExecutable() here because the encoding menu's + // delegate is internal, it doesn't use the one we pass in. + model.ActivatedAt(0); + EXPECT_TRUE(model.IsEnabledAt(0)); + // Make sure to use the index that is not separator in all configurations. + model.ActivatedAt(2); + EXPECT_TRUE(model.IsEnabledAt(2)); + EXPECT_EQ(model.execute_count_, 2); + EXPECT_EQ(model.enable_count_, 2); + + model.execute_count_ = 0; + model.enable_count_ = 0; + + // Choose something from the tools submenu and make sure it makes it back to + // the delegate as well. Use the first submenu as the tools one. + int toolsModelIndex = -1; + for (int i = 0; i < itemCount; ++i) { + if (model.GetTypeAt(i) == menus::MenuModel::TYPE_SUBMENU) { + toolsModelIndex = i; + break; + } + } + EXPECT_GT(toolsModelIndex, -1); + menus::MenuModel* toolsModel = model.GetSubmenuModelAt(toolsModelIndex); + EXPECT_TRUE(toolsModel); + EXPECT_GT(toolsModel->GetItemCount(), 2); + toolsModel->ActivatedAt(2); + EXPECT_TRUE(toolsModel->IsEnabledAt(2)); + EXPECT_EQ(model.execute_count_, 1); + EXPECT_EQ(model.enable_count_, 1); +} + +class EncodingMenuModelTest : public BrowserWithTestWindowTest, + public MenuModelTest { +}; + +TEST_F(EncodingMenuModelTest, IsCommandIdCheckedWithNoTabs) { + EncodingMenuModel model(browser()); + ASSERT_EQ(NULL, browser()->GetSelectedTabContents()); + EXPECT_FALSE(model.IsCommandIdChecked(IDC_ENCODING_ISO88591)); +} diff --git a/chrome/browser/ui/views/autofill_profiles_view_win.cc b/chrome/browser/ui/views/autofill_profiles_view_win.cc index f7c4b1c..20812b8 100644 --- a/chrome/browser/ui/views/autofill_profiles_view_win.cc +++ b/chrome/browser/ui/views/autofill_profiles_view_win.cc @@ -16,14 +16,14 @@ #include "base/utf_string_conversions.h" #include "chrome/browser/autofill/autofill_manager.h" #include "chrome/browser/autofill/phone_number.h" -#include "chrome/browser/browser_list.h" -#include "chrome/browser/browser_window.h" #include "chrome/browser/metrics/user_metrics.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profile.h" #include "chrome/browser/ui/browser.h" -#include "chrome/browser/views/list_background.h" -#include "chrome/browser/window_sizer.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/views/list_background.h" +#include "chrome/browser/ui/window_sizer.h" #include "chrome/common/notification_details.h" #include "chrome/common/pref_names.h" #include "gfx/canvas.h" diff --git a/chrome/browser/ui/views/chrome_views_delegate.cc b/chrome/browser/ui/views/chrome_views_delegate.cc index d872ace..7ed8ef7 100644 --- a/chrome/browser/ui/views/chrome_views_delegate.cc +++ b/chrome/browser/ui/views/chrome_views_delegate.cc @@ -9,8 +9,8 @@ #include "base/utf_string_conversions.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/prefs/pref_service.h" -#include "chrome/browser/views/accessibility_event_router_views.h" -#include "chrome/browser/window_sizer.h" +#include "chrome/browser/ui/views/accessibility_event_router_views.h" +#include "chrome/browser/ui/window_sizer.h" #include "gfx/rect.h" #if defined(OS_WIN) diff --git a/chrome/browser/ui/views/constrained_window_win.cc b/chrome/browser/ui/views/constrained_window_win.cc index 7fa1dc6..6079e0b 100644 --- a/chrome/browser/ui/views/constrained_window_win.cc +++ b/chrome/browser/ui/views/constrained_window_win.cc @@ -12,9 +12,9 @@ #include "chrome/browser/profile.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/tab_contents/tab_contents_view.h" -#include "chrome/browser/toolbar_model.h" -#include "chrome/browser/views/frame/browser_view.h" -#include "chrome/browser/window_sizer.h" +#include "chrome/browser/ui/toolbar/toolbar_model.h" +#include "chrome/browser/ui/views/frame/browser_view.h" +#include "chrome/browser/ui/window_sizer.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/notification_service.h" #include "chrome/common/pref_names.h" diff --git a/chrome/browser/ui/views/extensions/extension_popup.cc b/chrome/browser/ui/views/extensions/extension_popup.cc index 454bd60..77bd298 100644 --- a/chrome/browser/ui/views/extensions/extension_popup.cc +++ b/chrome/browser/ui/views/extensions/extension_popup.cc @@ -4,8 +4,6 @@ #include "chrome/browser/views/extensions/extension_popup.h" -#include "chrome/browser/browser_list.h" -#include "chrome/browser/browser_window.h" #include "chrome/browser/debugger/devtools_manager.h" #include "chrome/browser/debugger/devtools_toggle_action.h" #include "chrome/browser/extensions/extension_host.h" @@ -14,8 +12,10 @@ #include "chrome/browser/renderer_host/render_widget_host_view.h" #include "chrome/browser/renderer_host/render_view_host.h" #include "chrome/browser/ui/browser.h" -#include "chrome/browser/views/frame/browser_view.h" -#include "chrome/browser/window_sizer.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/views/frame/browser_view.h" +#include "chrome/browser/ui/window_sizer.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/notification_details.h" #include "chrome/common/notification_source.h" diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc index c7d9265..64ef258 100644 --- a/chrome/browser/ui/views/frame/browser_view.cc +++ b/chrome/browser/ui/views/frame/browser_view.cc @@ -20,7 +20,6 @@ #include "chrome/browser/autocomplete/autocomplete_popup_view.h" #include "chrome/browser/automation/ui_controls.h" #include "chrome/browser/bookmarks/bookmark_utils.h" -#include "chrome/browser/browser_list.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/debugger/devtools_window.h" #include "chrome/browser/dom_ui/bug_report_ui.h" @@ -41,25 +40,26 @@ #include "chrome/browser/themes/browser_theme_provider.h" #include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog_queue.h" #include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/toolbar/wrench_menu_model.h" +#include "chrome/browser/ui/views/accessible_view_helper.h" +#include "chrome/browser/ui/views/bookmark_bar_view.h" +#include "chrome/browser/ui/views/browser_dialogs.h" +#include "chrome/browser/ui/views/default_search_view.h" +#include "chrome/browser/ui/views/download_shelf_view.h" +#include "chrome/browser/ui/views/frame/browser_view_layout.h" +#include "chrome/browser/ui/views/frame/contents_container.h" +#include "chrome/browser/ui/views/fullscreen_exit_bubble.h" +#include "chrome/browser/ui/views/status_bubble_views.h" +#include "chrome/browser/ui/views/tab_contents/tab_contents_container.h" +#include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h" +#include "chrome/browser/ui/views/tabs/side_tab_strip.h" +#include "chrome/browser/ui/views/theme_install_bubble_view.h" +#include "chrome/browser/ui/views/toolbar_view.h" +#include "chrome/browser/ui/views/update_recommended_message_box.h" +#include "chrome/browser/ui/views/window.h" +#include "chrome/browser/ui/window_sizer.h" #include "chrome/browser/view_ids.h" -#include "chrome/browser/views/accessible_view_helper.h" -#include "chrome/browser/views/bookmark_bar_view.h" -#include "chrome/browser/views/browser_dialogs.h" -#include "chrome/browser/views/default_search_view.h" -#include "chrome/browser/views/download_shelf_view.h" -#include "chrome/browser/views/frame/browser_view_layout.h" -#include "chrome/browser/views/frame/contents_container.h" -#include "chrome/browser/views/fullscreen_exit_bubble.h" -#include "chrome/browser/views/status_bubble_views.h" -#include "chrome/browser/views/tab_contents/tab_contents_container.h" -#include "chrome/browser/views/tabs/browser_tab_strip_controller.h" -#include "chrome/browser/views/tabs/side_tab_strip.h" -#include "chrome/browser/views/theme_install_bubble_view.h" -#include "chrome/browser/views/toolbar_view.h" -#include "chrome/browser/views/update_recommended_message_box.h" -#include "chrome/browser/views/window.h" -#include "chrome/browser/window_sizer.h" -#include "chrome/browser/wrench_menu_model.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension_resource.h" #include "chrome/common/native_window_notification_source.h" diff --git a/chrome/browser/ui/views/info_bubble.cc b/chrome/browser/ui/views/info_bubble.cc index befb716..d8a3306 100644 --- a/chrome/browser/ui/views/info_bubble.cc +++ b/chrome/browser/ui/views/info_bubble.cc @@ -6,7 +6,7 @@ #include "app/keyboard_codes.h" #include "app/slide_animation.h" -#include "chrome/browser/window_sizer.h" +#include "chrome/browser/ui/window_sizer.h" #include "chrome/common/notification_service.h" #include "gfx/canvas_skia.h" #include "gfx/color_utils.h" diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.h b/chrome/browser/ui/views/location_bar/location_bar_view.h index 5e45e10..d83c7de 100644 --- a/chrome/browser/ui/views/location_bar/location_bar_view.h +++ b/chrome/browser/ui/views/location_bar/location_bar_view.h @@ -16,7 +16,7 @@ #include "chrome/browser/location_bar.h" #include "chrome/browser/search_engines/template_url_model_observer.h" #include "chrome/browser/tab_contents/tab_contents.h" -#include "chrome/browser/toolbar_model.h" +#include "chrome/browser/ui/toolbar/toolbar_model.h" #include "chrome/browser/views/extensions/extension_popup.h" #include "chrome/common/notification_observer.h" #include "chrome/common/notification_registrar.h" diff --git a/chrome/browser/ui/views/options/options_window_view.cc b/chrome/browser/ui/views/options/options_window_view.cc index 8201bd8..e52c8ae 100644 --- a/chrome/browser/ui/views/options/options_window_view.cc +++ b/chrome/browser/ui/views/options/options_window_view.cc @@ -6,15 +6,15 @@ #include "app/l10n_util.h" #include "base/utf_string_conversions.h" -#include "chrome/browser/browser_list.h" #include "chrome/browser/browser_process.h" -#include "chrome/browser/browser_window.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profile.h" -#include "chrome/browser/views/options/advanced_page_view.h" -#include "chrome/browser/views/options/content_page_view.h" -#include "chrome/browser/views/options/general_page_view.h" -#include "chrome/browser/window_sizer.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/views/options/advanced_page_view.h" +#include "chrome/browser/ui/views/options/content_page_view.h" +#include "chrome/browser/ui/views/options/general_page_view.h" +#include "chrome/browser/ui/window_sizer.h" #include "chrome/common/chrome_constants.h" #include "chrome/common/pref_names.h" #include "grit/chromium_strings.h" diff --git a/chrome/browser/ui/views/tabs/dragged_tab_controller.h b/chrome/browser/ui/views/tabs/dragged_tab_controller.h index 2d00ac6..f00a0ef 100644 --- a/chrome/browser/ui/views/tabs/dragged_tab_controller.h +++ b/chrome/browser/ui/views/tabs/dragged_tab_controller.h @@ -9,9 +9,9 @@ #include "base/message_loop.h" #include "base/scoped_ptr.h" #include "base/timer.h" -#include "chrome/browser/dock_info.h" #include "chrome/browser/tab_contents/tab_contents_delegate.h" #include "chrome/browser/tab_contents_wrapper.h" +#include "chrome/browser/ui/tabs/dock_info.h" #include "chrome/common/notification_observer.h" #include "chrome/common/notification_registrar.h" #include "gfx/rect.h" diff --git a/chrome/browser/ui/views/toolbar_view.cc b/chrome/browser/ui/views/toolbar_view.cc index 43806a5..bc1e0a3 100644 --- a/chrome/browser/ui/views/toolbar_view.cc +++ b/chrome/browser/ui/views/toolbar_view.cc @@ -10,17 +10,17 @@ #include "chrome/app/chrome_command_ids.h" #include "chrome/browser/accessibility/browser_accessibility_state.h" #include "chrome/browser/background_page_tracker.h" -#include "chrome/browser/browser_window.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profile.h" #include "chrome/browser/themes/browser_theme_provider.h" #include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/toolbar/wrench_menu_model.h" +#include "chrome/browser/ui/views/browser_actions_container.h" +#include "chrome/browser/ui/views/event_utils.h" +#include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/browser/upgrade_detector.h" #include "chrome/browser/view_ids.h" -#include "chrome/browser/views/browser_actions_container.h" -#include "chrome/browser/views/event_utils.h" -#include "chrome/browser/views/frame/browser_view.h" -#include "chrome/browser/wrench_menu_model.h" #include "chrome/common/badge_util.h" #include "chrome/common/notification_service.h" #include "chrome/common/pref_names.h" diff --git a/chrome/browser/ui/views/toolbar_view.h b/chrome/browser/ui/views/toolbar_view.h index 3553ed2..e8c13af 100644 --- a/chrome/browser/ui/views/toolbar_view.h +++ b/chrome/browser/ui/views/toolbar_view.h @@ -12,12 +12,12 @@ #include "app/slide_animation.h" #include "base/ref_counted.h" #include "base/scoped_ptr.h" -#include "chrome/browser/back_forward_menu_model.h" #include "chrome/browser/command_updater.h" #include "chrome/browser/prefs/pref_member.h" -#include "chrome/browser/views/accessible_pane_view.h" -#include "chrome/browser/views/location_bar/location_bar_view.h" -#include "chrome/browser/views/reload_button.h" +#include "chrome/browser/ui/toolbar/back_forward_menu_model.h" +#include "chrome/browser/ui/views/accessible_pane_view.h" +#include "chrome/browser/ui/views/location_bar/location_bar_view.h" +#include "chrome/browser/ui/views/reload_button.h" #include "views/controls/button/menu_button.h" #include "views/controls/menu/menu.h" #include "views/controls/menu/menu_wrapper.h" diff --git a/chrome/browser/ui/window_sizer.cc b/chrome/browser/ui/window_sizer.cc new file mode 100644 index 0000000..292f5fc --- /dev/null +++ b/chrome/browser/ui/window_sizer.cc @@ -0,0 +1,336 @@ +// 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/ui/window_sizer.h" + +#include "chrome/browser/browser_process.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/common/pref_names.h" + +/////////////////////////////////////////////////////////////////////////////// +// An implementation of WindowSizer::StateProvider that gets the last active +// and persistent state from the browser window and the user's profile. +class DefaultStateProvider : public WindowSizer::StateProvider { + public: + explicit DefaultStateProvider(const std::string& app_name, Browser* browser) + : app_name_(app_name), + browser_(browser) { + } + + // Overridden from WindowSizer::StateProvider: + virtual bool GetPersistentState(gfx::Rect* bounds, + bool* maximized, + gfx::Rect* work_area) const { + DCHECK(bounds && maximized); + + std::string key(prefs::kBrowserWindowPlacement); + if (!app_name_.empty()) { + key.append("_"); + key.append(app_name_); + } + + if (!g_browser_process->local_state()) + return false; + + const DictionaryValue* wp_pref = + g_browser_process->local_state()->GetDictionary(key.c_str()); + int top = 0, left = 0, bottom = 0, right = 0; + bool has_prefs = + wp_pref && + wp_pref->GetInteger("top", &top) && + wp_pref->GetInteger("left", &left) && + wp_pref->GetInteger("bottom", &bottom) && + wp_pref->GetInteger("right", &right) && + wp_pref->GetBoolean("maximized", maximized); + bounds->SetRect(left, top, std::max(0, right - left), + std::max(0, bottom - top)); + + int work_area_top = 0; + int work_area_left = 0; + int work_area_bottom = 0; + int work_area_right = 0; + if (wp_pref) { + wp_pref->GetInteger("work_area_top", &work_area_top); + wp_pref->GetInteger("work_area_left", &work_area_left); + wp_pref->GetInteger("work_area_bottom", &work_area_bottom); + wp_pref->GetInteger("work_area_right", &work_area_right); + } + work_area->SetRect(work_area_left, work_area_top, + std::max(0, work_area_right - work_area_left), + std::max(0, work_area_bottom - work_area_top)); + + return has_prefs; + } + + virtual bool GetLastActiveWindowState(gfx::Rect* bounds) const { + // Applications are always restored with the same position. + if (!app_name_.empty()) + return false; + + // If a reference browser is set, use its window. Otherwise find last + // active. + BrowserWindow* window = NULL; + if (browser_) { + window = browser_->window(); + DCHECK(window); + } else { + BrowserList::const_reverse_iterator it = BrowserList::begin_last_active(); + BrowserList::const_reverse_iterator end = BrowserList::end_last_active(); + for (; (it != end); ++it) { + Browser* last_active = *it; + if (last_active && last_active->type() == Browser::TYPE_NORMAL) { + window = last_active->window(); + DCHECK(window); + break; + } + } + } + + if (window) { + *bounds = window->GetRestoredBounds(); + return true; + } + + return false; + } + + private: + std::string app_name_; + + // If set, is used as the reference browser for GetLastActiveWindowState. + Browser* browser_; + DISALLOW_COPY_AND_ASSIGN(DefaultStateProvider); +}; + +/////////////////////////////////////////////////////////////////////////////// +// MonitorInfoProvider, public: + +WindowSizer::MonitorInfoProvider::MonitorInfoProvider() {} + +WindowSizer::MonitorInfoProvider::~MonitorInfoProvider() {} + +/////////////////////////////////////////////////////////////////////////////// +// WindowSizer, public: + +WindowSizer::WindowSizer( + StateProvider* state_provider, + MonitorInfoProvider* monitor_info_provider) { + Init(state_provider, monitor_info_provider); +} + +WindowSizer::~WindowSizer() { + if (state_provider_) + delete state_provider_; + if (monitor_info_provider_) + delete monitor_info_provider_; +} + +// static +void WindowSizer::GetBrowserWindowBounds(const std::string& app_name, + const gfx::Rect& specified_bounds, + Browser* browser, + gfx::Rect* window_bounds, + bool* maximized) { + const WindowSizer sizer(new DefaultStateProvider(app_name, browser), + CreateDefaultMonitorInfoProvider()); + sizer.DetermineWindowBounds(specified_bounds, window_bounds, maximized); +} + +/////////////////////////////////////////////////////////////////////////////// +// WindowSizer, private: + +WindowSizer::WindowSizer(const std::string& app_name) { + Init(new DefaultStateProvider(app_name, NULL), + CreateDefaultMonitorInfoProvider()); +} + +void WindowSizer::Init(StateProvider* state_provider, + MonitorInfoProvider* monitor_info_provider) { + state_provider_ = state_provider; + monitor_info_provider_ = monitor_info_provider; +} + +void WindowSizer::DetermineWindowBounds(const gfx::Rect& specified_bounds, + gfx::Rect* bounds, + bool* maximized) const { + *bounds = specified_bounds; + if (bounds->IsEmpty()) { + // See if there's saved placement information. + if (!GetLastWindowBounds(bounds)) { + if (!GetSavedWindowBounds(bounds, maximized)) { + // No saved placement, figure out some sensible default size based on + // the user's screen size. + GetDefaultWindowBounds(bounds); + } + } + } +} + +bool WindowSizer::GetLastWindowBounds(gfx::Rect* bounds) const { + DCHECK(bounds); + if (!state_provider_ || !state_provider_->GetLastActiveWindowState(bounds)) + return false; + gfx::Rect last_window_bounds = *bounds; + bounds->Offset(kWindowTilePixels, kWindowTilePixels); + AdjustBoundsToBeVisibleOnMonitorContaining(last_window_bounds, + gfx::Rect(), + bounds); + return true; +} + +bool WindowSizer::GetSavedWindowBounds(gfx::Rect* bounds, + bool* maximized) const { + DCHECK(bounds && maximized); + gfx::Rect saved_work_area; + if (!state_provider_ || + !state_provider_->GetPersistentState(bounds, maximized, &saved_work_area)) + return false; + AdjustBoundsToBeVisibleOnMonitorContaining(*bounds, saved_work_area, bounds); + return true; +} + +void WindowSizer::GetDefaultWindowBounds(gfx::Rect* default_bounds) const { + DCHECK(default_bounds); + DCHECK(monitor_info_provider_); + + gfx::Rect work_area = monitor_info_provider_->GetPrimaryMonitorWorkArea(); + + // The default size is either some reasonably wide width, or if the work + // area is narrower, then the work area width less some aesthetic padding. + int default_width = std::min(work_area.width() - 2 * kWindowTilePixels, 1050); + int default_height = work_area.height() - 2 * kWindowTilePixels; + + // For wider aspect ratio displays at higher resolutions, we might size the + // window narrower to allow two windows to easily be placed side-by-side. + gfx::Rect screen_size = monitor_info_provider_->GetPrimaryMonitorBounds(); + double width_to_height = + static_cast<double>(screen_size.width()) / screen_size.height(); + + // The least wide a screen can be to qualify for the halving described above. + static const int kMinScreenWidthForWindowHalving = 1600; + // We assume 16:9/10 is a fairly standard indicator of a wide aspect ratio + // computer display. + if (((width_to_height * 10) >= 16) && + work_area.width() > kMinScreenWidthForWindowHalving) { + // Halve the work area, subtracting aesthetic padding on either side. + // The padding is set so that two windows, side by side have + // kWindowTilePixels between screen edge and each other. + default_width = static_cast<int>(work_area.width() / 2. - + 1.5 * kWindowTilePixels); + } + default_bounds->SetRect(kWindowTilePixels + work_area.x(), + kWindowTilePixels + work_area.y(), + default_width, default_height); +} + +bool WindowSizer::PositionIsOffscreen(int position, Edge edge) const { + DCHECK(monitor_info_provider_); + size_t monitor_count = monitor_info_provider_->GetMonitorCount(); + for (size_t i = 0; i < monitor_count; ++i) { + gfx::Rect work_area = monitor_info_provider_->GetWorkAreaAt(i); + switch (edge) { + case TOP: + if (position >= work_area.y()) + return false; + break; + case LEFT: + if (position >= work_area.x()) + return false; + break; + case BOTTOM: + if (position <= work_area.bottom()) + return false; + break; + case RIGHT: + if (position <= work_area.right()) + return false; + break; + } + } + return true; +} + +namespace { + // Minimum height of the visible part of a window. + static const int kMinVisibleHeight = 30; + // Minimum width of the visible part of a window. + static const int kMinVisibleWidth = 30; +} + +void WindowSizer::AdjustBoundsToBeVisibleOnMonitorContaining( + const gfx::Rect& other_bounds, + const gfx::Rect& saved_work_area, + gfx::Rect* bounds) const { + DCHECK(bounds); + DCHECK(monitor_info_provider_); + + // Find the size of the work area of the monitor that intersects the bounds + // of the anchor window. + gfx::Rect work_area = + monitor_info_provider_->GetMonitorWorkAreaMatching(other_bounds); + + // If height or width are 0, reset to the default size. + gfx::Rect default_bounds; + GetDefaultWindowBounds(&default_bounds); + if (bounds->height() <= 0) + bounds->set_height(default_bounds.height()); + if (bounds->width() <= 0) + bounds->set_width(default_bounds.width()); + + // Ensure the minimum height and width. + bounds->set_height(std::max(kMinVisibleHeight, bounds->height())); + bounds->set_width(std::max(kMinVisibleWidth, bounds->width())); + + // Ensure that the title bar is not above the work area. + if (bounds->y() < work_area.y()) + bounds->set_y(work_area.y()); + + // Reposition and resize the bounds if the saved_work_area is different from + // the current work area and the current work area doesn't completely contain + // the bounds. + if (!saved_work_area.IsEmpty() && + saved_work_area != work_area && + !work_area.Contains(*bounds)) { + bounds->set_width(std::min(bounds->width(), work_area.width())); + bounds->set_height(std::min(bounds->height(), work_area.height())); + bounds->set_x( + std::max(work_area.x(), + std::min(bounds->x(), work_area.right() - bounds->width()))); + bounds->set_y( + std::max(work_area.y(), + std::min(bounds->y(), work_area.bottom() - bounds->height()))); + } + +#if defined(OS_MACOSX) + // Limit the maximum height. On the Mac the sizer is on the + // bottom-right of the window, and a window cannot be moved "up" + // past the menubar. If the window is too tall you'll never be able + // to shrink it again. Windows does not have this limitation + // (e.g. can be resized from the top). + bounds->set_height(std::min(work_area.height(), bounds->height())); + + // On mac, we want to be aggressive about repositioning windows that are + // partially offscreen. If the window is partially offscreen horizontally, + // move it to be flush with the left edge of the work area. + if (bounds->x() < work_area.x() || bounds->right() > work_area.right()) + bounds->set_x(work_area.x()); + + // If the window is partially offscreen vertically, move it to be flush with + // the top of the work area. + if (bounds->y() < work_area.y() || bounds->bottom() > work_area.bottom()) + bounds->set_y(work_area.y()); +#else + // On non-Mac platforms, we are less aggressive about repositioning. Simply + // ensure that at least kMinVisibleWidth * kMinVisibleHeight is visible. + const int min_y = work_area.y() + kMinVisibleHeight - bounds->height(); + const int min_x = work_area.x() + kMinVisibleWidth - bounds->width(); + const int max_y = work_area.bottom() - kMinVisibleHeight; + const int max_x = work_area.right() - kMinVisibleWidth; + bounds->set_y(std::max(min_y, std::min(max_y, bounds->y()))); + bounds->set_x(std::max(min_x, std::min(max_x, bounds->x()))); +#endif // defined(OS_MACOSX) +} diff --git a/chrome/browser/ui/window_sizer.h b/chrome/browser/ui/window_sizer.h new file mode 100644 index 0000000..7d34399 --- /dev/null +++ b/chrome/browser/ui/window_sizer.h @@ -0,0 +1,184 @@ +// 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. + +#ifndef CHROME_BROWSER_UI_WINDOW_SIZER_H_ +#define CHROME_BROWSER_UI_WINDOW_SIZER_H_ +#pragma once + +#include <vector> + +#include "base/basictypes.h" +#include "gfx/rect.h" + +class Browser; + +/////////////////////////////////////////////////////////////////////////////// +// WindowSizer +// +// A class that determines the best new size and position for a window to be +// shown at based several factors, including the position and size of the last +// window of the same type, the last saved bounds of the window from the +// previous session, and default system metrics if neither of the above two +// conditions exist. The system has built-in providers for monitor metrics +// and persistent storage (using preferences) but can be overrided with mocks +// for testing. +// +class WindowSizer { + public: + class MonitorInfoProvider; + class StateProvider; + + // The WindowSizer assumes ownership of these objects. + WindowSizer(StateProvider* state_provider, + MonitorInfoProvider* monitor_info_provider); + virtual ~WindowSizer(); + + // Static factory methods to create default MonitorInfoProvider + // instances. The returned object is owned by the caller. + static MonitorInfoProvider* CreateDefaultMonitorInfoProvider(); + + // An interface implemented by an object that can retrieve information about + // the monitors on the system. + class MonitorInfoProvider { + public: + MonitorInfoProvider(); + virtual ~MonitorInfoProvider(); + + // Returns the bounds of the work area of the primary monitor. + virtual gfx::Rect GetPrimaryMonitorWorkArea() const = 0; + + // Returns the bounds of the primary monitor. + virtual gfx::Rect GetPrimaryMonitorBounds() const = 0; + + // Returns the bounds of the work area of the monitor that most closely + // intersects the provided bounds. + virtual gfx::Rect GetMonitorWorkAreaMatching( + const gfx::Rect& match_rect) const = 0; + + // Returns the delta between the work area and the monitor bounds for the + // monitor that most closely intersects the provided bounds. + virtual gfx::Point GetBoundsOffsetMatching( + const gfx::Rect& match_rect) const = 0; + + // Ensures number and coordinates of work areas are up-to-date. You must + // call this before calling either of the below functions, as work areas can + // change while the program is running. + virtual void UpdateWorkAreas() = 0; + + // Returns the number of monitors on the system. + size_t GetMonitorCount() const { + return work_areas_.size(); + } + + // Returns the bounds of the work area of the monitor at the specified + // index. + gfx::Rect GetWorkAreaAt(size_t index) const { + return work_areas_[index]; + } + + protected: + std::vector<gfx::Rect> work_areas_; + }; + + // An interface implemented by an object that can retrieve state from either a + // persistent store or an existing window. + class StateProvider { + public: + virtual ~StateProvider() { } + + // Retrieve the persisted bounds of the window. Returns true if there was + // persisted data to retrieve state information, false otherwise. + virtual bool GetPersistentState(gfx::Rect* bounds, + bool* maximized, + gfx::Rect* work_area) const = 0; + + // Retrieve the bounds of the most recent window of the matching type. + // Returns true if there was a last active window to retrieve state + // information from, false otherwise. + virtual bool GetLastActiveWindowState(gfx::Rect* bounds) const = 0; + }; + + // Determines the position, size and maximized state for a window as it is + // created. This function uses several strategies to figure out optimal size + // and placement, first looking for an existing active window, then falling + // back to persisted data from a previous session, finally utilizing a default + // algorithm. If |specified_bounds| are non-empty, this value is returned + // instead. For use only in testing. + // + // NOTE: |maximized| is only set if we're restoring a saved maximized window. + // When creating a new window based on an existing active window, standard + // Windows behavior is to have it always be nonmaximized, even if the existing + // window is maximized. + void DetermineWindowBounds(const gfx::Rect& specified_bounds, + gfx::Rect* bounds, + bool* maximized) const; + + // Determines the size, position and maximized state for the browser window. + // See documentation for DetermineWindowBounds above. Normally, + // |window_bounds| is calculated by calling GetLastActiveWindowState(). To + // explicitly specify a particular window to base the bounds on, pass in a + // non-NULL value for |browser|. + static void GetBrowserWindowBounds(const std::string& app_name, + const gfx::Rect& specified_bounds, + Browser* browser, + gfx::Rect* window_bounds, + bool* maximized); + + // Returns the default origin for popups of the given size. + static gfx::Point GetDefaultPopupOrigin(const gfx::Size& size); + + // How much horizontal and vertical offset there is between newly + // opened windows. This value may be different on each platform. + static const int kWindowTilePixels; + + private: + // The edge of the screen to check for out-of-bounds. + enum Edge { TOP, LEFT, BOTTOM, RIGHT }; + + explicit WindowSizer(const std::string& app_name); + + void Init(StateProvider* state_provider, + MonitorInfoProvider* monitor_info_provider); + + // Gets the size and placement of the last window. Returns true if this data + // is valid, false if there is no last window and the application should + // restore saved state from preferences using RestoreWindowPosition. + bool GetLastWindowBounds(gfx::Rect* bounds) const; + + // Gets the size and placement of the last window in the last session, saved + // in local state preferences. Returns true if local state exists containing + // this information, false if this information does not exist and a default + // size should be used. + bool GetSavedWindowBounds(gfx::Rect* bounds, bool* maximized) const; + + // Gets the default window position and size if there is no last window and + // no saved window placement in prefs. This function determines the default + // size based on monitor size, etc. + void GetDefaultWindowBounds(gfx::Rect* default_bounds) const; + + // Returns true if the specified position is "offscreen" for the given edge, + // meaning that it's outside all work areas in the direction of that edge. + bool PositionIsOffscreen(int position, Edge edge) const; + + // Adjusts |bounds| to be visible onscreen, biased toward the work area of the + // monitor containing |other_bounds|. Despite the name, this doesn't + // guarantee the bounds are fully contained within this monitor's work rect; + // it just tried to ensure the edges are visible on _some_ work rect. + // If |saved_work_area| is non-empty, it is used to determine whether the + // monitor cofiguration has changed. If it has, bounds are repositioned and + // resized if necessary to make them completely contained in the current work + // area. + void AdjustBoundsToBeVisibleOnMonitorContaining( + const gfx::Rect& other_bounds, + const gfx::Rect& saved_work_area, + gfx::Rect* bounds) const; + + // Providers for persistent storage and monitor metrics. + StateProvider* state_provider_; + MonitorInfoProvider* monitor_info_provider_; + + DISALLOW_COPY_AND_ASSIGN(WindowSizer); +}; + +#endif // CHROME_BROWSER_WINDOW_SIZER_H_ diff --git a/chrome/browser/ui/window_sizer_linux.cc b/chrome/browser/ui/window_sizer_linux.cc new file mode 100644 index 0000000..2d2dff6 --- /dev/null +++ b/chrome/browser/ui/window_sizer_linux.cc @@ -0,0 +1,131 @@ +// Copyright (c) 2009 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/ui/window_sizer.h" + +#include <gtk/gtk.h> + +#include "base/logging.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" + +// Used to pad the default new window size. On Windows, this is also used for +// positioning new windows (each window is offset from the previous one). +// Since we don't position windows, it's only used for the default new window +// size. +const int WindowSizer::kWindowTilePixels = 10; + +// An implementation of WindowSizer::MonitorInfoProvider that gets the actual +// monitor information from X via GDK. +class DefaultMonitorInfoProvider : public WindowSizer::MonitorInfoProvider { + public: + DefaultMonitorInfoProvider() { } + + virtual gfx::Rect GetPrimaryMonitorWorkArea() const { + gfx::Rect rect; + if (GetScreenWorkArea(&rect)) + return rect.Intersect(GetPrimaryMonitorBounds()); + + // Return the best we've got. + return GetPrimaryMonitorBounds(); + } + + virtual gfx::Rect GetPrimaryMonitorBounds() const { + GdkScreen* screen = gdk_screen_get_default(); + GdkRectangle rect; + gdk_screen_get_monitor_geometry(screen, 0, &rect); + return gfx::Rect(rect); + } + + virtual gfx::Rect GetMonitorWorkAreaMatching( + const gfx::Rect& match_rect) const { + // TODO(thestig) Implement multi-monitor support. + return GetPrimaryMonitorWorkArea(); + } + + virtual gfx::Point GetBoundsOffsetMatching( + const gfx::Rect& match_rect) const { + // TODO(thestig) Implement multi-monitor support. + return GetPrimaryMonitorWorkArea().origin(); + } + + void UpdateWorkAreas() { + // TODO(thestig) Implement multi-monitor support. + work_areas_.clear(); + work_areas_.push_back(GetPrimaryMonitorBounds()); + } + + private: + // Get the available screen space as a gfx::Rect, or return false if + // if it's unavailable (i.e. the window manager doesn't support + // retrieving this). + // TODO(thestig) Use _NET_CURRENT_DESKTOP here as well? + bool GetScreenWorkArea(gfx::Rect* out_rect) const { + gboolean ok; + guchar* raw_data = NULL; + gint data_len = 0; + ok = gdk_property_get(gdk_get_default_root_window(), // a gdk window + gdk_atom_intern("_NET_WORKAREA", FALSE), // property + gdk_atom_intern("CARDINAL", FALSE), // property type + 0, // byte offset into property + 0xff, // property length to retrieve + false, // delete property after retrieval? + NULL, // returned property type + NULL, // returned data format + &data_len, // returned data len + &raw_data); // returned data + if (!ok) + return false; + + // We expect to get four longs back: x, y, width, height. + if (data_len < static_cast<gint>(4 * sizeof(glong))) { + NOTREACHED(); + g_free(raw_data); + return false; + } + + glong* data = reinterpret_cast<glong*>(raw_data); + gint x = data[0]; + gint y = data[1]; + gint width = data[2]; + gint height = data[3]; + g_free(raw_data); + + out_rect->SetRect(x, y, width, height); + return true; + } + + DISALLOW_COPY_AND_ASSIGN(DefaultMonitorInfoProvider); +}; + +// static +WindowSizer::MonitorInfoProvider* +WindowSizer::CreateDefaultMonitorInfoProvider() { + return new DefaultMonitorInfoProvider(); +} + +// static +gfx::Point WindowSizer::GetDefaultPopupOrigin(const gfx::Size& size) { + scoped_ptr<MonitorInfoProvider> provider(CreateDefaultMonitorInfoProvider()); + gfx::Rect monitor_bounds = provider->GetPrimaryMonitorWorkArea(); + gfx::Point corner(monitor_bounds.x(), monitor_bounds.y()); + if (Browser* browser = BrowserList::GetLastActive()) { + GtkWindow* window = + reinterpret_cast<GtkWindow*>(browser->window()->GetNativeHandle()); + int x = 0, y = 0; + gtk_window_get_position(window, &x, &y); + // Limit to not overflow the work area right and bottom edges. + gfx::Point limit( + std::min(x + kWindowTilePixels, monitor_bounds.right() - size.width()), + std::min(y + kWindowTilePixels, + monitor_bounds.bottom() - size.height())); + // Adjust corner to now overflow the work area left and top edges, so + // that if a popup does not fit the title-bar is remains visible. + corner = gfx::Point( + std::max(corner.x(), limit.x()), + std::max(corner.y(), limit.y())); + } + return corner; +} diff --git a/chrome/browser/ui/window_sizer_mac.mm b/chrome/browser/ui/window_sizer_mac.mm new file mode 100644 index 0000000..5d1bb66 --- /dev/null +++ b/chrome/browser/ui/window_sizer_mac.mm @@ -0,0 +1,144 @@ +// Copyright (c) 2009 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. + +#import <Cocoa/Cocoa.h> +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/window_sizer.h" + + +// How much horizontal and vertical offset there is between newly +// opened windows. +const int WindowSizer::kWindowTilePixels = 22; + +namespace { + +class DefaultMonitorInfoProvider : public WindowSizer::MonitorInfoProvider { + public: + DefaultMonitorInfoProvider() { } + + // Overridden from WindowSizer::MonitorInfoProvider: + virtual gfx::Rect GetPrimaryMonitorWorkArea() const { + // Primary monitor is defined as the monitor with the menubar, + // which is always at index 0. + NSScreen* primary = [[NSScreen screens] objectAtIndex:0]; + NSRect frame = [primary frame]; + NSRect visible_frame = [primary visibleFrame]; + + // Convert coordinate systems. + gfx::Rect rect = gfx::Rect(NSRectToCGRect(visible_frame)); + rect.set_y(frame.size.height - + visible_frame.origin.y - visible_frame.size.height); + return rect; + } + + virtual gfx::Rect GetPrimaryMonitorBounds() const { + // Primary monitor is defined as the monitor with the menubar, + // which is always at index 0. + NSScreen* primary = [[NSScreen screens] objectAtIndex:0]; + return gfx::Rect(NSRectToCGRect([primary frame])); + } + + virtual gfx::Rect GetMonitorWorkAreaMatching( + const gfx::Rect& match_rect) const { + NSScreen* match_screen = GetMatchingScreen(match_rect); + return ConvertCoordinateSystem([match_screen visibleFrame]); + } + + virtual gfx::Point GetBoundsOffsetMatching( + const gfx::Rect& match_rect) const { + NSScreen* match_screen = GetMatchingScreen(match_rect); + gfx::Rect bounds = ConvertCoordinateSystem([match_screen frame]); + gfx::Rect work_area = ConvertCoordinateSystem([match_screen visibleFrame]); + return gfx::Point(work_area.x() - bounds.x(), work_area.y() - bounds.y()); + } + + virtual void UpdateWorkAreas(); + + private: + // Returns a pointer to the screen that most closely matches the + // given |match_rect|. This function currently returns the screen + // that contains the largest amount of |match_rect| by area. + NSScreen* GetMatchingScreen(const gfx::Rect& match_rect) const { + // Default to the monitor with the current keyboard focus, in case + // |match_rect| is not on any screen at all. + NSScreen* max_screen = [NSScreen mainScreen]; + int max_area = 0; + + for (NSScreen* screen in [NSScreen screens]) { + gfx::Rect monitor_area = ConvertCoordinateSystem([screen frame]); + gfx::Rect intersection = monitor_area.Intersect(match_rect); + int area = intersection.width() * intersection.height(); + if (area > max_area) { + max_area = area; + max_screen = screen; + } + } + + return max_screen; + } + + // The lower left corner of screen 0 is always at (0, 0). The + // cross-platform code expects the origin to be in the upper left, + // so we have to translate |ns_rect| to the new coordinate + // system. + gfx::Rect ConvertCoordinateSystem(NSRect ns_rect) const; + + DISALLOW_COPY_AND_ASSIGN(DefaultMonitorInfoProvider); +}; + +void DefaultMonitorInfoProvider::UpdateWorkAreas() { + work_areas_.clear(); + + for (NSScreen* screen in [NSScreen screens]) { + gfx::Rect rect = ConvertCoordinateSystem([screen visibleFrame]); + work_areas_.push_back(rect); + } +} + +gfx::Rect DefaultMonitorInfoProvider::ConvertCoordinateSystem( + NSRect ns_rect) const { + // Primary monitor is defined as the monitor with the menubar, + // which is always at index 0. + NSScreen* primary_screen = [[NSScreen screens] objectAtIndex:0]; + float primary_screen_height = [primary_screen frame].size.height; + gfx::Rect rect(NSRectToCGRect(ns_rect)); + rect.set_y(primary_screen_height - rect.y() - rect.height()); + return rect; +} + +} // namespace + +// static +WindowSizer::MonitorInfoProvider* +WindowSizer::CreateDefaultMonitorInfoProvider() { + return new DefaultMonitorInfoProvider(); +} + +// static +gfx::Point WindowSizer::GetDefaultPopupOrigin(const gfx::Size& size) { + NSRect work_area = [[NSScreen mainScreen] visibleFrame]; + NSRect main_area = [[[NSScreen screens] objectAtIndex:0] frame]; + NSPoint corner = NSMakePoint(NSMinX(work_area), NSMaxY(work_area)); + + if (Browser* b = BrowserList::GetLastActive()) { + NSWindow* window = b->window()->GetNativeHandle(); + NSRect window_frame = [window frame]; + + // Limit to not overflow the work area right and bottom edges. + NSPoint limit = NSMakePoint( + std::min(NSMinX(window_frame) + kWindowTilePixels, + NSMaxX(work_area) - size.width()), + std::max(NSMaxY(window_frame) - kWindowTilePixels, + NSMinY(work_area) + size.height())); + + // Adjust corner to now overflow the work area left and top edges, so + // that if a popup does not fit the title-bar is remains visible. + corner = NSMakePoint(std::max(corner.x, limit.x), + std::min(corner.y, limit.y)); + } + + return gfx::Point(corner.x, NSHeight(main_area) - corner.y); +} diff --git a/chrome/browser/ui/window_sizer_unittest.cc b/chrome/browser/ui/window_sizer_unittest.cc new file mode 100644 index 0000000..f11065f --- /dev/null +++ b/chrome/browser/ui/window_sizer_unittest.cc @@ -0,0 +1,1002 @@ +// Copyright (c) 2006-2008 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 <vector> + +#include "chrome/browser/ui/window_sizer.h" +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Some standard monitor sizes (no task bar). +static const gfx::Rect tentwentyfour(0, 0, 1024, 768); +static const gfx::Rect twelveeighty(0, 0, 1280, 1024); +static const gfx::Rect sixteenhundred(0, 0, 1600, 1200); +static const gfx::Rect sixteeneighty(0, 0, 1680, 1050); +static const gfx::Rect nineteentwenty(0, 0, 1920, 1200); + +// Represents a 1024x768 monitor that is not the primary monitor, arranged to +// the immediate left of the primary 1024x768 monitor. +static const gfx::Rect left_nonprimary(-1024, 0, 1024, 768); + +// Represents a 1024x768 monitor that is not the primary monitor, arranged to +// the immediate right of the primary 1024x768 monitor. +static const gfx::Rect right_nonprimary(1024, 0, 1024, 768); + +// Represents a 1024x768 monitor that is not the primary monitor, arranged to +// the immediate top of the primary 1024x768 monitor. +static const gfx::Rect top_nonprimary(0, -768, 1024, 768); + +// Represents a 1024x768 monitor that is not the primary monitor, arranged to +// the immediate bottom of the primary 1024x768 monitor. +static const gfx::Rect bottom_nonprimary(0, 768, 1024, 768); + +// The work area for 1024x768 monitors with different taskbar orientations. +static const gfx::Rect taskbar_bottom_work_area(0, 0, 1024, 734); +static const gfx::Rect taskbar_top_work_area(0, 34, 1024, 734); +static const gfx::Rect taskbar_left_work_area(107, 0, 917, 768); +static const gfx::Rect taskbar_right_work_area(0, 0, 917, 768); + +static int kWindowTilePixels = WindowSizer::kWindowTilePixels; + +// Testing implementation of WindowSizer::MonitorInfoProvider that we can use +// to fake various monitor layouts and sizes. +class TestMonitorInfoProvider : public WindowSizer::MonitorInfoProvider { + public: + TestMonitorInfoProvider() {} + virtual ~TestMonitorInfoProvider() {} + + void AddMonitor(const gfx::Rect& bounds, const gfx::Rect& work_area) { + DCHECK(bounds.Contains(work_area)); + monitor_bounds_.push_back(bounds); + work_areas_.push_back(work_area); + } + + // Overridden from WindowSizer::MonitorInfoProvider: + virtual gfx::Rect GetPrimaryMonitorWorkArea() const { + return work_areas_[0]; + } + + virtual gfx::Rect GetPrimaryMonitorBounds() const { + return monitor_bounds_[0]; + } + + virtual gfx::Rect GetMonitorWorkAreaMatching( + const gfx::Rect& match_rect) const { + return work_areas_[GetMonitorIndexMatchingBounds(match_rect)]; + } + + virtual gfx::Point GetBoundsOffsetMatching( + const gfx::Rect& match_rect) const { + int monitor_index = GetMonitorIndexMatchingBounds(match_rect); + gfx::Rect bounds = monitor_bounds_[monitor_index]; + const gfx::Rect& work_area = work_areas_[monitor_index]; + return gfx::Point(work_area.x() - bounds.x(), work_area.y() - bounds.y()); + } + + virtual void UpdateWorkAreas() { } + + private: + size_t GetMonitorIndexMatchingBounds(const gfx::Rect& match_rect) const { + int max_area = 0; + size_t max_area_index = 0; + // Loop through all the monitors, finding the one that intersects the + // largest area of the supplied match rect. + for (size_t i = 0; i < work_areas_.size(); ++i) { + gfx::Rect overlap(match_rect.Intersect(work_areas_[i])); + int area = overlap.width() * overlap.height(); + if (area > max_area) { + max_area = area; + max_area_index = i; + } + } + return max_area_index; + } + + std::vector<gfx::Rect> monitor_bounds_; + + DISALLOW_COPY_AND_ASSIGN(TestMonitorInfoProvider); +}; + +// Testing implementation of WindowSizer::StateProvider that we use to fake +// persistent storage and existing windows. +class TestStateProvider : public WindowSizer::StateProvider { + public: + TestStateProvider() + : persistent_maximized_(false), + has_persistent_data_(false), + has_last_active_data_(false) { + } + virtual ~TestStateProvider() {} + + void SetPersistentState(const gfx::Rect& bounds, + bool maximized, + const gfx::Rect& work_area, + bool has_persistent_data) { + persistent_bounds_ = bounds; + persistent_maximized_ = maximized; + persistent_work_area_ = work_area; + has_persistent_data_ = has_persistent_data; + } + + void SetLastActiveState(const gfx::Rect& bounds, bool has_last_active_data) { + last_active_bounds_ = bounds; + has_last_active_data_ = has_last_active_data; + } + + // Overridden from WindowSizer::StateProvider: + virtual bool GetPersistentState(gfx::Rect* bounds, + bool* maximized, + gfx::Rect* saved_work_area) const { + *bounds = persistent_bounds_; + *maximized = persistent_maximized_; + *saved_work_area = persistent_work_area_; + return has_persistent_data_; + } + + virtual bool GetLastActiveWindowState(gfx::Rect* bounds) const { + *bounds = last_active_bounds_; + return has_last_active_data_; + } + + private: + gfx::Rect persistent_bounds_; + bool persistent_maximized_; + gfx::Rect persistent_work_area_; + bool has_persistent_data_; + + gfx::Rect last_active_bounds_; + bool has_last_active_data_; + + DISALLOW_COPY_AND_ASSIGN(TestStateProvider); +}; + +// A convenience function to read the window bounds from the window sizer +// according to the specified configuration. +enum Source { DEFAULT, LAST_ACTIVE, PERSISTED }; +static void GetWindowBounds(const gfx::Rect& monitor1_bounds, + const gfx::Rect& monitor1_work_area, + const gfx::Rect& monitor2_bounds, + const gfx::Rect& state, + bool maximized, + const gfx::Rect& work_area, + Source source, + gfx::Rect* out_bounds, + bool* out_maximized) { + TestMonitorInfoProvider* mip = new TestMonitorInfoProvider; + mip->AddMonitor(monitor1_bounds, monitor1_work_area); + if (!monitor2_bounds.IsEmpty()) + mip->AddMonitor(monitor2_bounds, monitor2_bounds); + TestStateProvider* sp = new TestStateProvider; + if (source == PERSISTED) + sp->SetPersistentState(state, maximized, work_area, true); + else if (source == LAST_ACTIVE) + sp->SetLastActiveState(state, true); + WindowSizer sizer(sp, mip); + sizer.DetermineWindowBounds(gfx::Rect(), out_bounds, out_maximized); +} + +// Test that the window is sized appropriately for the first run experience +// where the default window bounds calculation is invoked. +TEST(WindowSizerTest, DefaultSizeCase) { + { // 4:3 monitor case, 1024x768, no taskbar + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), gfx::Rect(), + false, gfx::Rect(), DEFAULT, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(kWindowTilePixels, kWindowTilePixels, + 1024 - kWindowTilePixels * 2, + 768 - kWindowTilePixels * 2), + window_bounds); + } + + { // 4:3 monitor case, 1024x768, taskbar on bottom + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, taskbar_bottom_work_area, gfx::Rect(), + gfx::Rect(), false, gfx::Rect(), DEFAULT, &window_bounds, + &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(kWindowTilePixels, kWindowTilePixels, + 1024 - kWindowTilePixels * 2, + (taskbar_bottom_work_area.height() - + kWindowTilePixels * 2)), + window_bounds); + } + + { // 4:3 monitor case, 1024x768, taskbar on right + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, taskbar_right_work_area, gfx::Rect(), + gfx::Rect(), false, gfx::Rect(), DEFAULT, &window_bounds, + &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(kWindowTilePixels, kWindowTilePixels, + taskbar_right_work_area.width() - kWindowTilePixels*2, + 768 - kWindowTilePixels * 2), + window_bounds); + } + + { // 4:3 monitor case, 1024x768, taskbar on left + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, taskbar_left_work_area, gfx::Rect(), + gfx::Rect(), false, gfx::Rect(), DEFAULT, &window_bounds, + &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(taskbar_left_work_area.x() + kWindowTilePixels, + kWindowTilePixels, + taskbar_left_work_area.width() - kWindowTilePixels * 2, + (taskbar_left_work_area.height() - + kWindowTilePixels * 2)), + window_bounds); + } + + { // 4:3 monitor case, 1024x768, taskbar on top + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, taskbar_top_work_area, gfx::Rect(), + gfx::Rect(), false, gfx::Rect(), DEFAULT, &window_bounds, + &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(kWindowTilePixels, + taskbar_top_work_area.y() + kWindowTilePixels, + 1024 - kWindowTilePixels * 2, + taskbar_top_work_area.height() - kWindowTilePixels * 2), + window_bounds); + } + + { // 4:3 monitor case, 1280x1024 + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(twelveeighty, twelveeighty, gfx::Rect(), gfx::Rect(), + false, gfx::Rect(), DEFAULT, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(kWindowTilePixels, kWindowTilePixels, + 1050, + 1024 - kWindowTilePixels * 2), + window_bounds); + } + + { // 4:3 monitor case, 1600x1200 + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(sixteenhundred, sixteenhundred, gfx::Rect(), gfx::Rect(), + false, gfx::Rect(), DEFAULT, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(kWindowTilePixels, kWindowTilePixels, + 1050, + 1200 - kWindowTilePixels * 2), + window_bounds); + } + + { // 16:10 monitor case, 1680x1050 + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(sixteeneighty, sixteeneighty, gfx::Rect(), gfx::Rect(), + false, gfx::Rect(), DEFAULT, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(kWindowTilePixels, kWindowTilePixels, + 840 - static_cast<int>(kWindowTilePixels * 1.5), + 1050 - kWindowTilePixels * 2), + window_bounds); + } + + { // 16:10 monitor case, 1920x1200 + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(nineteentwenty, nineteentwenty, gfx::Rect(), gfx::Rect(), + false, gfx::Rect(), DEFAULT, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(kWindowTilePixels, kWindowTilePixels, + 960 - static_cast<int>(kWindowTilePixels * 1.5), + 1200 - kWindowTilePixels * 2), + window_bounds); + } +} + +// Test that the next opened window is positioned appropriately given the +// bounds of an existing window of the same type. +TEST(WindowSizerTest, LastWindowBoundsCase) { + { // normal, in the middle of the screen somewhere. + gfx::Rect window_bounds; + bool maximized = false; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(kWindowTilePixels, kWindowTilePixels, 500, 400), + false, gfx::Rect(), LAST_ACTIVE, + &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(kWindowTilePixels * 2, + kWindowTilePixels * 2, 500, 400), window_bounds); + } + + { // taskbar on top. + gfx::Rect window_bounds; + bool maximized = false; + GetWindowBounds(tentwentyfour, taskbar_top_work_area, gfx::Rect(), + gfx::Rect(kWindowTilePixels, kWindowTilePixels, 500, 400), + false, gfx::Rect(), LAST_ACTIVE, + &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(kWindowTilePixels * 2, + std::max(kWindowTilePixels * 2, + 34 /* toolbar height */), + 500, 400), window_bounds); + } + + { // too small to satisify the minimum visibility condition. + gfx::Rect window_bounds; + bool maximized = false; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(kWindowTilePixels, kWindowTilePixels, 29, 29), + false, gfx::Rect(), LAST_ACTIVE, + &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(kWindowTilePixels * 2, + kWindowTilePixels * 2, + 30 /* not 29 */, + 30 /* not 29 */), + window_bounds); + } + + + { // normal, but maximized + gfx::Rect window_bounds; + bool maximized = false; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(kWindowTilePixels, kWindowTilePixels, 500, 400), + true, gfx::Rect(), LAST_ACTIVE, + &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(kWindowTilePixels * 2, + kWindowTilePixels * 2, 500, 400), window_bounds); + } +} + +// Test that the window opened is sized appropriately given persisted sizes. +TEST(WindowSizerTest, PersistedBoundsCase) { + { // normal, in the middle of the screen somewhere. + gfx::Rect initial_bounds(kWindowTilePixels, kWindowTilePixels, 500, 400); + + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), initial_bounds, + false, gfx::Rect(), PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(initial_bounds, window_bounds); + } + + { // normal, maximized. + gfx::Rect initial_bounds(0, 0, 1024, 768); + + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), initial_bounds, + true, gfx::Rect(), PERSISTED, &window_bounds, &maximized); + EXPECT_TRUE(maximized); + EXPECT_EQ(initial_bounds, window_bounds); + } + + { // normal, on non-primary monitor in negative coords. + gfx::Rect initial_bounds(-600, 10, 500, 400); + + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, left_nonprimary, + initial_bounds, false, gfx::Rect(), PERSISTED, + &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(initial_bounds, window_bounds); + } + + { // normal, on non-primary monitor in negative coords, maximized. + gfx::Rect initial_bounds(-1024, 0, 1024, 768); + + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, left_nonprimary, + initial_bounds, true, gfx::Rect(), PERSISTED, + &window_bounds, &maximized); + EXPECT_TRUE(maximized); + EXPECT_EQ(initial_bounds, window_bounds); + } + + { // Non-primary monitor resoultion has changed, but the monitor still + // completely contains the window. + + gfx::Rect initial_bounds(1074, 50, 600, 500); + + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(1024, 0, 800, 600), + initial_bounds, false, right_nonprimary, + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(initial_bounds, window_bounds); + } + + { // Non-primary monitor resoultion has changed, and the window is partially + // off-screen. + + gfx::Rect initial_bounds(1274, 50, 600, 500); + + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(1024, 0, 800, 600), + initial_bounds, false, right_nonprimary, + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(1224, 50, 600, 500), window_bounds); + } + + { // Non-primary monitor resoultion has changed, and the window is now too + // large for the monitor. + + gfx::Rect initial_bounds(1274, 50, 900, 700); + + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(1024, 0, 800, 600), + initial_bounds, false, right_nonprimary, + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(1024, 0, 800, 600), window_bounds); + } + + { // width and height too small + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(kWindowTilePixels, kWindowTilePixels, 29, 29), + false, gfx::Rect(), PERSISTED, + &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(kWindowTilePixels, kWindowTilePixels, + 30 /* not 29 */, 30 /* not 29 */), + window_bounds); + } + +#if defined(OS_MACOSX) + { // Saved state is too tall to possibly be resized. Mac resizers + // are at the bottom of the window, and no piece of a window can + // be moved higher than the menubar. (Perhaps the user changed + // resolution to something smaller before relaunching Chrome?) + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(kWindowTilePixels, kWindowTilePixels, 30, 5000), + false, gfx::Rect(), PERSISTED, + &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(tentwentyfour.height(), window_bounds.height()); + } +#endif // defined(OS_MACOSX) +} + +////////////////////////////////////////////////////////////////////////////// +// The following unittests have different results on Mac/non-Mac because we +// reposition windows aggressively on Mac. The *WithAggressiveReposition tests +// are run on Mac, and the *WithNonAggressiveRepositioning tests are run on +// other platforms. + +#if defined(OS_MACOSX) +TEST(WindowSizerTest, LastWindowOffscreenWithAggressiveRepositioning) { + { // taskbar on left. The new window overlaps slightly with the taskbar, so + // it is moved to be flush with the left edge of the work area. + gfx::Rect window_bounds; + bool maximized = false; + GetWindowBounds(tentwentyfour, taskbar_left_work_area, gfx::Rect(), + gfx::Rect(kWindowTilePixels, kWindowTilePixels, 500, 400), + false, gfx::Rect(), LAST_ACTIVE, + &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(taskbar_left_work_area.x(), + kWindowTilePixels * 2, 500, 400), window_bounds); + } + + { // offset would put the new window offscreen at the bottom + gfx::Rect window_bounds; + bool maximized = false; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(10, 729, 500, 400), false, gfx::Rect(), + LAST_ACTIVE, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(10 + kWindowTilePixels, + 0 /* not 729 + kWindowTilePixels */, + 500, 400), + window_bounds); + } + + { // offset would put the new window offscreen at the right + gfx::Rect window_bounds; + bool maximized = false; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(985, 10, 500, 400), false, gfx::Rect(), + LAST_ACTIVE, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(0 /* not 985 + kWindowTilePixels*/, + 10 + kWindowTilePixels, + 500, 400), + window_bounds); + } + + { // offset would put the new window offscreen at the bottom right + gfx::Rect window_bounds; + bool maximized = false; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(985, 729, 500, 400), false, gfx::Rect(), + LAST_ACTIVE, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(0 /* not 985 + kWindowTilePixels*/, + 0 /* not 729 + kWindowTilePixels*/, + 500, 400), + window_bounds); + } +} + +TEST(WindowSizerTest, PersistedWindowOffscreenWithAggressiveRepositioning) { + { // off the left + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(-471, 50, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(0 /* not -471 */, 50, 500, 400), window_bounds); + } + + { // off the top + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(50, -370, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(50, 0, 500, 400), window_bounds); + } + + { // off the right + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(995, 50, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(0 /* not 995 */, 50, 500, 400), window_bounds); + } + + { // off the bottom + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(50, 739, 500, 400), false, gfx::Rect(), PERSISTED, + &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(50, 0 /* not 739 */, 500, 400), window_bounds); + } + + { // off the topleft + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(-471, -371, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(0 /* not -471 */, 0 /* not -371 */, 500, 400), + window_bounds); + } + + { // off the topright + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(995, -371, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(0 /* not 995 */, 0 /* not -371 */, 500, 400), + window_bounds); + } + + { // off the bottomleft + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(-471, 739, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(0 /* not -471 */, 0 /* not 739 */, 500, 400), + window_bounds); + } + + { // off the bottomright + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(995, 739, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(0 /* not 995 */, 0 /* not 739 */, 500, 400), + window_bounds); + } + + { // entirely off left + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(-700, 50, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(0 /* not -700 */, 50, 500, 400), window_bounds); + } + + { // entirely off left (monitor was detached since last run) + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(-700, 50, 500, 400), false, left_nonprimary, + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(0, 50, 500, 400), window_bounds); + } + + { // entirely off top + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(50, -500, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(50, 0, 500, 400), window_bounds); + } + + { // entirely off top (monitor was detached since last run) + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(50, -500, 500, 400), false, top_nonprimary, + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(50, 0, 500, 400), window_bounds); + } + + { // entirely off right + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(1200, 50, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(0 /* not 1200 */, 50, 500, 400), window_bounds); + } + + { // entirely off right (monitor was detached since last run) + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(1200, 50, 500, 400), false, right_nonprimary, + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(524 /* not 1200 */, 50, 500, 400), window_bounds); + } + + { // entirely off bottom + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(50, 800, 500, 400), false, gfx::Rect(), PERSISTED, + &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(50, 0 /* not 800 */, 500, 400), window_bounds); + } + + { // entirely off bottom (monitor was detached since last run) + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(50, 800, 500, 400), false, bottom_nonprimary, + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(50, 368 /* not 800 */, 500, 400), window_bounds); + } + + { // wider than the screen. off both the left and right + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(-100, 50, 2000, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(0 /* not -100 */, 50, 2000, 400), window_bounds); + } +} +#else +TEST(WindowSizerTest, LastWindowOffscreenWithNonAggressiveRepositioning) { + { // taskbar on left. + gfx::Rect window_bounds; + bool maximized = false; + GetWindowBounds(tentwentyfour, taskbar_left_work_area, gfx::Rect(), + gfx::Rect(kWindowTilePixels, kWindowTilePixels, 500, 400), + false, gfx::Rect(), LAST_ACTIVE, + &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(kWindowTilePixels * 2, + kWindowTilePixels * 2, 500, 400), window_bounds); + } + + // Linux does not tile windows, so tile adjustment tests don't make sense. +#if !defined(OS_LINUX) + { // offset would put the new window offscreen at the bottom but the minimum + // visibility condition is barely satisfied without relocation. + gfx::Rect window_bounds; + bool maximized = false; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(10, 728, 500, 400), false, gfx::Rect(), + LAST_ACTIVE, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(10 + kWindowTilePixels, 738, + 500, 400), window_bounds); + } + + { // offset would put the new window offscreen at the bottom and the minimum + // visibility condition is satisified by relocation. + gfx::Rect window_bounds; + bool maximized = false; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(10, 729, 500, 400), false, gfx::Rect(), + LAST_ACTIVE, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(10 + kWindowTilePixels, 738 /* not 739 */, 500, 400), + window_bounds); + } + + { // offset would put the new window offscreen at the right but the minimum + // visibility condition is barely satisfied without relocation. + gfx::Rect window_bounds; + bool maximized = false; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(984, 10, 500, 400), false, gfx::Rect(), + LAST_ACTIVE, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(994, 10 + kWindowTilePixels, 500, 400), window_bounds); + } + + { // offset would put the new window offscreen at the right and the minimum + // visibility condition is satisified by relocation. + gfx::Rect window_bounds; + bool maximized = false; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(985, 10, 500, 400), false, gfx::Rect(), + LAST_ACTIVE, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(994 /* not 995 */, 10 + kWindowTilePixels, + 500, 400), window_bounds); + } + + { // offset would put the new window offscreen at the bottom right and the + // minimum visibility condition is satisified by relocation. + gfx::Rect window_bounds; + bool maximized = false; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(985, 729, 500, 400), false, gfx::Rect(), + LAST_ACTIVE, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(994 /* not 995 */, 738 /* not 739 */, 500, 400), + window_bounds); + } +#endif // !defined(OS_LINUX) +} + +TEST(WindowSizerTest, PersistedWindowOffscreenWithNonAggressiveRepositioning) { + { // off the left but the minimum visibility condition is barely satisfied + // without relocaiton. + gfx::Rect initial_bounds(-470, 50, 500, 400); + + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + initial_bounds, false, gfx::Rect(), PERSISTED, + &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(initial_bounds, window_bounds); + } + + { // off the left and the minimum visibility condition is satisfied by + // relocation. + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(-471, 50, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(-470 /* not -471 */, 50, 500, 400), window_bounds); + } + + { // off the top + gfx::Rect initial_bounds(50, -370, 500, 400); + + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(50, -370, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(50, 0, 500, 400), window_bounds); + } + + { // off the right but the minimum visibility condition is barely satisified + // without relocation. + gfx::Rect initial_bounds(994, 50, 500, 400); + + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + initial_bounds, false, gfx::Rect(), PERSISTED, + &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(initial_bounds, window_bounds); + } + + { // off the right and the minimum visibility condition is satisified by + // relocation. + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(995, 50, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(994 /* not 995 */, 50, 500, 400), window_bounds); + } + + { // off the bottom but the minimum visibility condition is barely satisified + // without relocation. + gfx::Rect initial_bounds(50, 738, 500, 400); + + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + initial_bounds, false, gfx::Rect(), PERSISTED, + &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(initial_bounds, window_bounds); + } + + { // off the bottom and the minimum visibility condition is satisified by + // relocation. + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(50, 739, 500, 400), false, gfx::Rect(), PERSISTED, + &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(50, 738 /* not 739 */, 500, 400), window_bounds); + } + + { // off the topleft + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(-471, -371, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(-470 /* not -471 */, 0, 500, 400), + window_bounds); + } + + { // off the topright and the minimum visibility condition is satisified by + // relocation. + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(995, -371, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(994 /* not 995 */, 0, 500, 400), + window_bounds); + } + + { // off the bottomleft and the minimum visibility condition is satisified by + // relocation. + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(-471, 739, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(-470 /* not -471 */, 738 /* not 739 */, 500, 400), + window_bounds); + } + + { // off the bottomright and the minimum visibility condition is satisified by + // relocation. + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(995, 739, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(994 /* not 995 */, 738 /* not 739 */, 500, 400), + window_bounds); + } + + { // entirely off left + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(-700, 50, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(-470 /* not -700 */, 50, 500, 400), window_bounds); + } + + { // entirely off left (monitor was detached since last run) + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(-700, 50, 500, 400), false, left_nonprimary, + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(0, 50, 500, 400), window_bounds); + } + + { // entirely off top + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(50, -500, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(50, 0, 500, 400), window_bounds); + } + + { // entirely off top (monitor was detached since last run) + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(50, -500, 500, 400), false, top_nonprimary, + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(50, 0, 500, 400), window_bounds); + } + + { // entirely off right + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(1200, 50, 500, 400), false, gfx::Rect(), + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(994 /* not 1200 */, 50, 500, 400), window_bounds); + } + + { // entirely off right (monitor was detached since last run) + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(1200, 50, 500, 400), false, right_nonprimary, + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(524, 50, 500, 400), window_bounds); + } + + { // entirely off bottom + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(50, 800, 500, 400), false, gfx::Rect(), PERSISTED, + &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(50, 738 /* not 800 */, 500, 400), window_bounds); + } + + { // entirely off bottom (monitor was detached since last run) + gfx::Rect window_bounds; + bool maximized; + GetWindowBounds(tentwentyfour, tentwentyfour, gfx::Rect(), + gfx::Rect(50, 800, 500, 400), false, bottom_nonprimary, + PERSISTED, &window_bounds, &maximized); + EXPECT_FALSE(maximized); + EXPECT_EQ(gfx::Rect(50, 368, 500, 400), window_bounds); + } +} +#endif //defined(OS_MACOSX) diff --git a/chrome/browser/ui/window_sizer_win.cc b/chrome/browser/ui/window_sizer_win.cc new file mode 100644 index 0000000..a2952a40 --- /dev/null +++ b/chrome/browser/ui/window_sizer_win.cc @@ -0,0 +1,108 @@ +// Copyright (c) 2009 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/ui/window_sizer.h" + +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" + +// How much horizontal and vertical offset there is between newly +// opened windows. +const int WindowSizer::kWindowTilePixels = 10; + +// An implementation of WindowSizer::MonitorInfoProvider that gets the actual +// monitor information from Windows. +class DefaultMonitorInfoProvider : public WindowSizer::MonitorInfoProvider { + public: + DefaultMonitorInfoProvider() { } + + // Overridden from WindowSizer::MonitorInfoProvider: + virtual gfx::Rect GetPrimaryMonitorWorkArea() const { + return gfx::Rect(GetMonitorInfoForMonitor(MonitorFromWindow(NULL, + MONITOR_DEFAULTTOPRIMARY)).rcWork); + } + + virtual gfx::Rect GetPrimaryMonitorBounds() const { + return gfx::Rect(GetMonitorInfoForMonitor(MonitorFromWindow(NULL, + MONITOR_DEFAULTTOPRIMARY)).rcMonitor); + } + + virtual gfx::Rect GetMonitorWorkAreaMatching( + const gfx::Rect& match_rect) const { + RECT other_bounds_rect = match_rect.ToRECT(); + MONITORINFO monitor_info = GetMonitorInfoForMonitor(MonitorFromRect( + &other_bounds_rect, MONITOR_DEFAULTTONEAREST)); + return gfx::Rect(monitor_info.rcWork); + } + + virtual gfx::Point GetBoundsOffsetMatching( + const gfx::Rect& match_rect) const { + RECT other_bounds_rect = match_rect.ToRECT(); + MONITORINFO monitor_info = GetMonitorInfoForMonitor(MonitorFromRect( + &other_bounds_rect, MONITOR_DEFAULTTONEAREST)); + return gfx::Point(monitor_info.rcWork.left - monitor_info.rcMonitor.left, + monitor_info.rcWork.top - monitor_info.rcMonitor.top); + } + + void UpdateWorkAreas() { + work_areas_.clear(); + EnumDisplayMonitors(NULL, NULL, + &DefaultMonitorInfoProvider::MonitorEnumProc, + reinterpret_cast<LPARAM>(&work_areas_)); + } + + private: + // A callback for EnumDisplayMonitors that records the work area of the + // current monitor in the enumeration. + static BOOL CALLBACK MonitorEnumProc(HMONITOR monitor, + HDC monitor_dc, + LPRECT monitor_rect, + LPARAM data) { + reinterpret_cast<std::vector<gfx::Rect>*>(data)->push_back( + gfx::Rect(GetMonitorInfoForMonitor(monitor).rcWork)); + return TRUE; + } + + static MONITORINFO GetMonitorInfoForMonitor(HMONITOR monitor) { + MONITORINFO monitor_info = { 0 }; + monitor_info.cbSize = sizeof(monitor_info); + GetMonitorInfo(monitor, &monitor_info); + return monitor_info; + } + + DISALLOW_COPY_AND_ASSIGN(DefaultMonitorInfoProvider); +}; + +// static +WindowSizer::MonitorInfoProvider* +WindowSizer::CreateDefaultMonitorInfoProvider() { + return new DefaultMonitorInfoProvider(); +} + +// static +gfx::Point WindowSizer::GetDefaultPopupOrigin(const gfx::Size& size) { + RECT area; + SystemParametersInfo(SPI_GETWORKAREA, 0, &area, 0); + gfx::Point corner(area.left, area.top); + + if (Browser* b = BrowserList::GetLastActive()) { + RECT browser; + HWND window = reinterpret_cast<HWND>(b->window()->GetNativeHandle()); + if (GetWindowRect(window, &browser)) { + // Limit to not overflow the work area right and bottom edges. + gfx::Point limit( + std::min(browser.left + kWindowTilePixels, area.right-size.width()), + std::min(browser.top + kWindowTilePixels, area.bottom-size.height()) + ); + // Adjust corner to now overflow the work area left and top edges, so + // that if a popup does not fit the title-bar is remains visible. + corner = gfx::Point( + std::max(corner.x(), limit.x()), + std::max(corner.y(), limit.y()) + ); + } + } + return corner; +} |