1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/window_sizer/window_sizer.h"
7 #include "base/command_line.h"
8 #include "base/compiler_specific.h"
9 #include "base/prefs/pref_service.h"
10 #include "chrome/browser/browser_process.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/ui/browser.h"
13 #include "chrome/browser/ui/browser_list.h"
14 #include "chrome/browser/ui/browser_window.h"
15 #include "chrome/browser/ui/browser_window_state.h"
16 #include "chrome/browser/ui/host_desktop.h"
17 #include "chrome/common/chrome_switches.h"
18 #include "chrome/common/pref_names.h"
19 #include "ui/gfx/screen.h"
22 #include "ash/shell.h"
23 #include "ash/wm/window_positioner.h"
24 #include "chrome/browser/ui/ash/ash_init.h"
27 // Minimum height of the visible part of a window.
28 const int kMinVisibleHeight = 30;
29 // Minimum width of the visible part of a window.
30 const int kMinVisibleWidth = 30;
32 ///////////////////////////////////////////////////////////////////////////////
33 // An implementation of WindowSizer::StateProvider that gets the last active
34 // and persistent state from the browser window and the user's profile.
35 class DefaultStateProvider : public WindowSizer::StateProvider {
37 DefaultStateProvider(const std::string& app_name, const Browser* browser)
38 : app_name_(app_name), browser_(browser) {
41 // Overridden from WindowSizer::StateProvider:
42 virtual bool GetPersistentState(
45 ui::WindowShowState* show_state) const OVERRIDE {
49 if (!browser_ || !browser_->profile()->GetPrefs())
52 std::string window_name(chrome::GetWindowPlacementKey(browser_));
53 const DictionaryValue* wp_pref =
54 browser_->profile()->GetPrefs()->GetDictionary(window_name.c_str());
55 int top = 0, left = 0, bottom = 0, right = 0;
56 bool maximized = false;
57 bool has_prefs = wp_pref &&
58 wp_pref->GetInteger("top", &top) &&
59 wp_pref->GetInteger("left", &left) &&
60 wp_pref->GetInteger("bottom", &bottom) &&
61 wp_pref->GetInteger("right", &right) &&
62 wp_pref->GetBoolean("maximized", &maximized);
63 bounds->SetRect(left, top, std::max(0, right - left),
64 std::max(0, bottom - top));
66 int work_area_top = 0;
67 int work_area_left = 0;
68 int work_area_bottom = 0;
69 int work_area_right = 0;
71 wp_pref->GetInteger("work_area_top", &work_area_top);
72 wp_pref->GetInteger("work_area_left", &work_area_left);
73 wp_pref->GetInteger("work_area_bottom", &work_area_bottom);
74 wp_pref->GetInteger("work_area_right", &work_area_right);
75 if (*show_state == ui::SHOW_STATE_DEFAULT && maximized)
76 *show_state = ui::SHOW_STATE_MAXIMIZED;
78 work_area->SetRect(work_area_left, work_area_top,
79 std::max(0, work_area_right - work_area_left),
80 std::max(0, work_area_bottom - work_area_top));
85 virtual bool GetLastActiveWindowState(
87 ui::WindowShowState* show_state) const OVERRIDE {
89 // Applications are always restored with the same position.
90 if (!app_name_.empty())
93 // If a reference browser is set, use its window. Otherwise find last
94 // active. Panels are never used as reference browsers as panels are
95 // specially positioned.
96 BrowserWindow* window = NULL;
97 // Window may be null if browser is just starting up.
98 if (browser_ && browser_->window()) {
99 window = browser_->window();
101 // This code is only ran on the native desktop (on the ash
102 // desktop, GetTabbedBrowserBoundsAsh should take over below
103 // before this is reached). TODO(gab): This code should go in a
104 // native desktop specific window sizer as part of fixing
106 const BrowserList* native_browser_list =
107 BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE);
108 for (BrowserList::const_reverse_iterator it =
109 native_browser_list->begin_last_active();
110 it != native_browser_list->end_last_active(); ++it) {
111 Browser* last_active = *it;
112 if (last_active && last_active->is_type_tabbed()) {
113 window = last_active->window();
121 *bounds = window->GetRestoredBounds();
122 if (*show_state == ui::SHOW_STATE_DEFAULT && window->IsMaximized())
123 *show_state = ui::SHOW_STATE_MAXIMIZED;
131 std::string app_name_;
133 // If set, is used as the reference browser for GetLastActiveWindowState.
134 const Browser* browser_;
135 DISALLOW_COPY_AND_ASSIGN(DefaultStateProvider);
138 ///////////////////////////////////////////////////////////////////////////////
139 // WindowSizer, public:
141 WindowSizer::WindowSizer(StateProvider* state_provider, const Browser* browser)
142 : state_provider_(state_provider),
143 // TODO(scottmg): NativeScreen is wrong. http://crbug.com/133312
144 screen_(gfx::Screen::GetNativeScreen()),
148 WindowSizer::WindowSizer(StateProvider* state_provider,
150 const Browser* browser)
151 : state_provider_(state_provider),
157 WindowSizer::~WindowSizer() {
161 void WindowSizer::GetBrowserWindowBoundsAndShowState(
162 const std::string& app_name,
163 const gfx::Rect& specified_bounds,
164 const Browser* browser,
165 gfx::Rect* window_bounds,
166 ui::WindowShowState* show_state) {
167 const WindowSizer sizer(new DefaultStateProvider(app_name, browser), browser);
168 sizer.DetermineWindowBoundsAndShowState(specified_bounds,
173 ///////////////////////////////////////////////////////////////////////////////
174 // WindowSizer, private:
176 void WindowSizer::DetermineWindowBoundsAndShowState(
177 const gfx::Rect& specified_bounds,
179 ui::WindowShowState* show_state) const {
182 // Pre-populate the window state with our default.
183 *show_state = GetWindowDefaultShowState();
184 *bounds = specified_bounds;
185 if (bounds->IsEmpty()) {
187 // See if ash should decide the window placement.
188 if (IsTabbedBrowserInAsh()) {
189 GetTabbedBrowserBoundsAsh(bounds, show_state);
191 } else if (chrome::ShouldOpenAshOnStartup() &&
192 browser_ && browser_->host_desktop_type() ==
193 chrome::HOST_DESKTOP_TYPE_ASH) {
194 // In ash, saved show state takes precidence. If you have a
195 // question or an issue, please contact oshima@chromium.org.
196 GetSavedWindowBounds(bounds, show_state);
199 // See if there's last active window's placement information.
200 if (GetLastWindowBounds(bounds, show_state))
202 // See if there's saved placement information.
203 if (GetSavedWindowBounds(bounds, show_state))
205 // No saved placement, figure out some sensible default size based on
206 // the user's screen size.
207 GetDefaultWindowBounds(screen_->GetPrimaryDisplay(), bounds);
210 // In case of a popup with an 'unspecified' location in ash, we are
211 // looking for a good screen location. We are interpreting (0,0) as an
212 // unspecified location.
213 if (IsPopupBrowserInAsh() && bounds->origin().IsOrigin()) {
214 *bounds = ash::Shell::GetInstance()->window_positioner()->
215 GetPopupPosition(*bounds);
219 // In case that there was a bound given we need to make sure that it is
220 // visible and fits on the screen.
221 // Find the size of the work area of the monitor that intersects the bounds
222 // of the anchor window. Note: AdjustBoundsToBeVisibleOnMonitorContaining
223 // does not exactly what we want: It makes only sure that "a minimal part"
224 // is visible on the screen.
225 gfx::Rect work_area = screen_->GetDisplayMatching(*bounds).work_area();
226 // Resize so that it fits.
227 bounds->AdjustToFit(work_area);
231 bool WindowSizer::GetLastWindowBounds(gfx::Rect* bounds,
232 ui::WindowShowState* show_state) const {
235 if (!state_provider_.get() ||
236 !state_provider_->GetLastActiveWindowState(bounds, show_state))
238 gfx::Rect last_window_bounds = *bounds;
239 bounds->Offset(kWindowTilePixels, kWindowTilePixels);
240 AdjustBoundsToBeVisibleOnMonitorContaining(last_window_bounds,
246 bool WindowSizer::GetSavedWindowBounds(gfx::Rect* bounds,
247 ui::WindowShowState* show_state) const {
250 gfx::Rect saved_work_area;
251 if (!state_provider_.get() ||
252 !state_provider_->GetPersistentState(bounds,
256 AdjustBoundsToBeVisibleOnMonitorContaining(*bounds, saved_work_area, bounds);
260 void WindowSizer::GetDefaultWindowBounds(const gfx::Display& display,
261 gfx::Rect* default_bounds) const {
262 DCHECK(default_bounds);
264 // TODO(beng): insufficient but currently necessary. http://crbug.com/133312
265 if (chrome::ShouldOpenAshOnStartup()) {
266 *default_bounds = ash::WindowPositioner::GetDefaultWindowBounds(
271 gfx::Rect work_area = display.work_area();
273 // The default size is either some reasonably wide width, or if the work
274 // area is narrower, then the work area width less some aesthetic padding.
275 int default_width = std::min(work_area.width() - 2 * kWindowTilePixels, 1050);
276 int default_height = work_area.height() - 2 * kWindowTilePixels;
278 // For wider aspect ratio displays at higher resolutions, we might size the
279 // window narrower to allow two windows to easily be placed side-by-side.
280 gfx::Rect screen_size = screen_->GetPrimaryDisplay().bounds();
281 double width_to_height =
282 static_cast<double>(screen_size.width()) / screen_size.height();
284 // The least wide a screen can be to qualify for the halving described above.
285 static const int kMinScreenWidthForWindowHalving = 1600;
286 // We assume 16:9/10 is a fairly standard indicator of a wide aspect ratio
288 if (((width_to_height * 10) >= 16) &&
289 work_area.width() > kMinScreenWidthForWindowHalving) {
290 // Halve the work area, subtracting aesthetic padding on either side.
291 // The padding is set so that two windows, side by side have
292 // kWindowTilePixels between screen edge and each other.
293 default_width = static_cast<int>(work_area.width() / 2. -
294 1.5 * kWindowTilePixels);
296 default_bounds->SetRect(kWindowTilePixels + work_area.x(),
297 kWindowTilePixels + work_area.y(),
298 default_width, default_height);
301 void WindowSizer::AdjustBoundsToBeVisibleOnMonitorContaining(
302 const gfx::Rect& other_bounds,
303 const gfx::Rect& saved_work_area,
304 gfx::Rect* bounds) const {
307 // Find the size of the work area of the monitor that intersects the bounds
308 // of the anchor window.
309 gfx::Display display = screen_->GetDisplayMatching(other_bounds);
311 // If height or width are 0, reset to the default size.
312 gfx::Rect default_bounds;
313 GetDefaultWindowBounds(display, &default_bounds);
314 if (bounds->height() <= 0)
315 bounds->set_height(default_bounds.height());
316 if (bounds->width() <= 0)
317 bounds->set_width(default_bounds.width());
319 // Ensure the minimum height and width.
320 bounds->set_height(std::max(kMinVisibleHeight, bounds->height()));
321 bounds->set_width(std::max(kMinVisibleWidth, bounds->width()));
323 gfx::Rect work_area = display.work_area();
324 // Ensure that the title bar is not above the work area.
325 if (bounds->y() < work_area.y())
326 bounds->set_y(work_area.y());
328 // Reposition and resize the bounds if the saved_work_area is different from
329 // the current work area and the current work area doesn't completely contain
331 if (!saved_work_area.IsEmpty() &&
332 saved_work_area != work_area &&
333 !work_area.Contains(*bounds)) {
334 bounds->set_width(std::min(bounds->width(), work_area.width()));
335 bounds->set_height(std::min(bounds->height(), work_area.height()));
337 std::max(work_area.x(),
338 std::min(bounds->x(), work_area.right() - bounds->width())));
340 std::max(work_area.y(),
341 std::min(bounds->y(), work_area.bottom() - bounds->height())));
344 #if defined(OS_MACOSX)
345 // Limit the maximum height. On the Mac the sizer is on the
346 // bottom-right of the window, and a window cannot be moved "up"
347 // past the menubar. If the window is too tall you'll never be able
348 // to shrink it again. Windows does not have this limitation
349 // (e.g. can be resized from the top).
350 bounds->set_height(std::min(work_area.height(), bounds->height()));
352 // On mac, we want to be aggressive about repositioning windows that are
353 // partially offscreen. If the window is partially offscreen horizontally,
354 // move it to be flush with the left edge of the work area.
355 if (bounds->x() < work_area.x() || bounds->right() > work_area.right())
356 bounds->set_x(work_area.x());
358 // If the window is partially offscreen vertically, move it to be flush with
359 // the top of the work area.
360 if (bounds->y() < work_area.y() || bounds->bottom() > work_area.bottom())
361 bounds->set_y(work_area.y());
363 // On non-Mac platforms, we are less aggressive about repositioning. Simply
364 // ensure that at least kMinVisibleWidth * kMinVisibleHeight is visible.
365 const int min_y = work_area.y() + kMinVisibleHeight - bounds->height();
366 const int min_x = work_area.x() + kMinVisibleWidth - bounds->width();
367 const int max_y = work_area.bottom() - kMinVisibleHeight;
368 const int max_x = work_area.right() - kMinVisibleWidth;
369 bounds->set_y(std::max(min_y, std::min(max_y, bounds->y())));
370 bounds->set_x(std::max(min_x, std::min(max_x, bounds->x())));
371 #endif // defined(OS_MACOSX)
374 ui::WindowShowState WindowSizer::GetWindowDefaultShowState() const {
376 return ui::SHOW_STATE_DEFAULT;
378 // Only tabbed browsers use the command line or preference state, with the
379 // exception of devtools.
380 bool show_state = !browser_->is_type_tabbed() && !browser_->is_devtools();
382 #if defined(USE_AURA)
383 // We use the apps save state on aura.
384 show_state &= !browser_->is_app();
388 return browser_->initial_show_state();
390 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kStartMaximized))
391 return ui::SHOW_STATE_MAXIMIZED;
393 if (browser_->initial_show_state() != ui::SHOW_STATE_DEFAULT)
394 return browser_->initial_show_state();
396 // Otherwise we use the default which can be overridden later on.
397 return ui::SHOW_STATE_DEFAULT;
401 bool WindowSizer::IsTabbedBrowserInAsh() const {
402 // TODO(beng): insufficient but currently necessary. http://crbug.com/133312
403 return chrome::ShouldOpenAshOnStartup() &&
405 browser_->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_ASH &&
406 browser_->is_type_tabbed();
409 bool WindowSizer::IsPopupBrowserInAsh() const {
410 // TODO(beng): insufficient but currently necessary. http://crbug.com/133312
411 return chrome::ShouldOpenAshOnStartup() &&
413 browser_->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_ASH &&
414 browser_->is_type_popup();