summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui
diff options
context:
space:
mode:
authorben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-12-02 17:08:44 +0000
committerben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-12-02 17:08:44 +0000
commit9a80065bad1506ac11163d597ace3295ddbfa8cb (patch)
treed1000277a49442bc02544f47cb18764fbd5d111d /chrome/browser/ui
parent42503b2e20d59c4444268a500d90f7ef22721437 (diff)
downloadchromium_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')
-rw-r--r--chrome/browser/ui/browser.cc4
-rw-r--r--chrome/browser/ui/browser.h2
-rw-r--r--chrome/browser/ui/cocoa/back_forward_menu_controller.h2
-rw-r--r--chrome/browser/ui/cocoa/back_forward_menu_controller.mm2
-rw-r--r--chrome/browser/ui/cocoa/browser_window_controller.mm6
-rw-r--r--chrome/browser/ui/cocoa/encoding_menu_controller_delegate_mac.mm2
-rw-r--r--chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h2
-rw-r--r--chrome/browser/ui/cocoa/toolbar_controller.mm4
-rw-r--r--chrome/browser/ui/cocoa/wrench_menu_controller.mm2
-rw-r--r--chrome/browser/ui/cocoa/wrench_menu_controller_unittest.mm2
-rw-r--r--chrome/browser/ui/tabs/dock_info.cc268
-rw-r--r--chrome/browser/ui/tabs/dock_info.h190
-rw-r--r--chrome/browser/ui/tabs/dock_info_gtk.cc219
-rw-r--r--chrome/browser/ui/tabs/dock_info_mac.cc15
-rw-r--r--chrome/browser/ui/tabs/dock_info_unittest.cc191
-rw-r--r--chrome/browser/ui/tabs/dock_info_win.cc325
-rw-r--r--chrome/browser/ui/toolbar/back_forward_menu_model.cc379
-rw-r--r--chrome/browser/ui/toolbar/back_forward_menu_model.h171
-rw-r--r--chrome/browser/ui/toolbar/back_forward_menu_model_unittest.cc422
-rw-r--r--chrome/browser/ui/toolbar/encoding_menu_controller.cc141
-rw-r--r--chrome/browser/ui/toolbar/encoding_menu_controller.h56
-rw-r--r--chrome/browser/ui/toolbar/encoding_menu_controller_unittest.cc93
-rw-r--r--chrome/browser/ui/toolbar/toolbar_model.cc129
-rw-r--r--chrome/browser/ui/toolbar/toolbar_model.h71
-rw-r--r--chrome/browser/ui/toolbar/wrench_menu_model.cc539
-rw-r--r--chrome/browser/ui/toolbar/wrench_menu_model.h149
-rw-r--r--chrome/browser/ui/toolbar/wrench_menu_model_unittest.cc105
-rw-r--r--chrome/browser/ui/views/autofill_profiles_view_win.cc8
-rw-r--r--chrome/browser/ui/views/chrome_views_delegate.cc4
-rw-r--r--chrome/browser/ui/views/constrained_window_win.cc6
-rw-r--r--chrome/browser/ui/views/extensions/extension_popup.cc8
-rw-r--r--chrome/browser/ui/views/frame/browser_view.cc38
-rw-r--r--chrome/browser/ui/views/info_bubble.cc2
-rw-r--r--chrome/browser/ui/views/location_bar/location_bar_view.h2
-rw-r--r--chrome/browser/ui/views/options/options_window_view.cc12
-rw-r--r--chrome/browser/ui/views/tabs/dragged_tab_controller.h2
-rw-r--r--chrome/browser/ui/views/toolbar_view.cc10
-rw-r--r--chrome/browser/ui/views/toolbar_view.h8
-rw-r--r--chrome/browser/ui/window_sizer.cc336
-rw-r--r--chrome/browser/ui/window_sizer.h184
-rw-r--r--chrome/browser/ui/window_sizer_linux.cc131
-rw-r--r--chrome/browser/ui/window_sizer_mac.mm144
-rw-r--r--chrome/browser/ui/window_sizer_unittest.cc1002
-rw-r--r--chrome/browser/ui/window_sizer_win.cc108
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 ? &current_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;
+}