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 "ui/base/ui_base_switches.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_util.h"
29 // Minimum height of the visible part of a window.
30 const int kMinVisibleHeight
= 30;
31 // Minimum width of the visible part of a window.
32 const int kMinVisibleWidth
= 30;
34 ///////////////////////////////////////////////////////////////////////////////
35 // An implementation of WindowSizer::StateProvider that gets the last active
36 // and persistent state from the browser window and the user's profile.
37 class DefaultStateProvider
: public WindowSizer::StateProvider
{
39 DefaultStateProvider(const std::string
& app_name
, const Browser
* browser
)
40 : app_name_(app_name
), browser_(browser
) {
43 // Overridden from WindowSizer::StateProvider:
44 bool GetPersistentState(gfx::Rect
* bounds
,
46 ui::WindowShowState
* show_state
) const override
{
50 if (!browser_
|| !browser_
->profile()->GetPrefs())
53 const base::DictionaryValue
* wp_pref
=
54 chrome::GetWindowPlacementDictionaryReadOnly(
55 chrome::GetWindowName(browser_
), browser_
->profile()->GetPrefs());
56 int top
= 0, left
= 0, bottom
= 0, right
= 0;
57 bool maximized
= false;
58 bool has_prefs
= wp_pref
&&
59 wp_pref
->GetInteger("top", &top
) &&
60 wp_pref
->GetInteger("left", &left
) &&
61 wp_pref
->GetInteger("bottom", &bottom
) &&
62 wp_pref
->GetInteger("right", &right
) &&
63 wp_pref
->GetBoolean("maximized", &maximized
);
64 bounds
->SetRect(left
, top
, std::max(0, right
- left
),
65 std::max(0, bottom
- top
));
67 int work_area_top
= 0;
68 int work_area_left
= 0;
69 int work_area_bottom
= 0;
70 int work_area_right
= 0;
72 wp_pref
->GetInteger("work_area_top", &work_area_top
);
73 wp_pref
->GetInteger("work_area_left", &work_area_left
);
74 wp_pref
->GetInteger("work_area_bottom", &work_area_bottom
);
75 wp_pref
->GetInteger("work_area_right", &work_area_right
);
76 if (*show_state
== ui::SHOW_STATE_DEFAULT
&& maximized
)
77 *show_state
= ui::SHOW_STATE_MAXIMIZED
;
79 wp_pref
->GetBoolean("docked", &docked
);
80 if (*show_state
== ui::SHOW_STATE_DEFAULT
&& docked
&&
81 !browser_
->is_type_tabbed() &&
82 browser_
->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_ASH
) {
83 *show_state
= ui::SHOW_STATE_DOCKED
;
86 work_area
->SetRect(work_area_left
, work_area_top
,
87 std::max(0, work_area_right
- work_area_left
),
88 std::max(0, work_area_bottom
- work_area_top
));
93 bool GetLastActiveWindowState(
95 ui::WindowShowState
* show_state
) const override
{
97 // Applications are always restored with the same position.
98 if (!app_name_
.empty())
101 // If a reference browser is set, use its window. Otherwise find last
102 // active. Panels are never used as reference browsers as panels are
103 // specially positioned.
104 BrowserWindow
* window
= NULL
;
105 // Window may be null if browser is just starting up.
106 if (browser_
&& browser_
->window()) {
107 window
= browser_
->window();
109 // This code is only ran on the native desktop (on the ash
110 // desktop, GetTabbedBrowserBoundsAsh should take over below
111 // before this is reached). TODO(gab): This code should go in a
112 // native desktop specific window sizer as part of fixing
114 const BrowserList
* native_browser_list
=
115 BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE
);
116 for (BrowserList::const_reverse_iterator it
=
117 native_browser_list
->begin_last_active();
118 it
!= native_browser_list
->end_last_active(); ++it
) {
119 Browser
* last_active
= *it
;
120 if (last_active
&& last_active
->is_type_tabbed()) {
121 window
= last_active
->window();
129 *bounds
= window
->GetRestoredBounds();
130 if (*show_state
== ui::SHOW_STATE_DEFAULT
&& window
->IsMaximized())
131 *show_state
= ui::SHOW_STATE_MAXIMIZED
;
139 std::string app_name_
;
141 // If set, is used as the reference browser for GetLastActiveWindowState.
142 const Browser
* browser_
;
143 DISALLOW_COPY_AND_ASSIGN(DefaultStateProvider
);
146 class DefaultTargetDisplayProvider
: public WindowSizer::TargetDisplayProvider
{
148 DefaultTargetDisplayProvider() {}
149 ~DefaultTargetDisplayProvider() override
{}
151 gfx::Display
GetTargetDisplay(const gfx::Screen
* screen
,
152 const gfx::Rect
& bounds
) const override
{
154 bool force_ash
= false;
155 // On Windows check if the browser is launched to serve ASH. If yes then
156 // we should get the display for the corresponding root window created for
157 // ASH. This ensures that the display gets the correct workarea, etc.
158 // If the ASH shell does not exist then the current behavior is to open
159 // browser windows if any on the desktop. Preserve that for now.
161 // This effectively means that the running browser process is in a split
162 // personality mode, part of it running in ASH and the other running in
163 // desktop. This may cause apps and other widgets to not work correctly.
164 // Revisit and address.
166 force_ash
= ash::Shell::HasInstance() &&
167 base::CommandLine::ForCurrentProcess()->HasSwitch(
168 switches::kViewerConnect
);
170 // Use the target display on ash.
171 if (chrome::ShouldOpenAshOnStartup() || force_ash
) {
172 aura::Window
* target
= ash::Shell::GetTargetRootWindow();
173 return screen
->GetDisplayNearestWindow(target
);
176 // Find the size of the work area of the monitor that intersects the bounds
177 // of the anchor window.
178 return screen
->GetDisplayMatching(bounds
);
182 DISALLOW_COPY_AND_ASSIGN(DefaultTargetDisplayProvider
);
187 ///////////////////////////////////////////////////////////////////////////////
188 // WindowSizer, public:
190 WindowSizer::WindowSizer(
191 scoped_ptr
<StateProvider
> state_provider
,
192 scoped_ptr
<TargetDisplayProvider
> target_display_provider
,
193 const Browser
* browser
)
194 : state_provider_(state_provider
.Pass()),
195 target_display_provider_(target_display_provider
.Pass()),
196 // TODO(scottmg): NativeScreen is wrong. http://crbug.com/133312
197 screen_(gfx::Screen::GetNativeScreen()),
201 WindowSizer::WindowSizer(
202 scoped_ptr
<StateProvider
> state_provider
,
203 scoped_ptr
<TargetDisplayProvider
> target_display_provider
,
205 const Browser
* browser
)
206 : state_provider_(state_provider
.Pass()),
207 target_display_provider_(target_display_provider
.Pass()),
213 WindowSizer::~WindowSizer() {
217 void WindowSizer::GetBrowserWindowBoundsAndShowState(
218 const std::string
& app_name
,
219 const gfx::Rect
& specified_bounds
,
220 const Browser
* browser
,
221 gfx::Rect
* window_bounds
,
222 ui::WindowShowState
* show_state
) {
223 scoped_ptr
<StateProvider
> state_provider(
224 new DefaultStateProvider(app_name
, browser
));
225 scoped_ptr
<TargetDisplayProvider
> target_display_provider(
226 new DefaultTargetDisplayProvider
);
227 const WindowSizer
sizer(state_provider
.Pass(),
228 target_display_provider
.Pass(),
230 sizer
.DetermineWindowBoundsAndShowState(specified_bounds
,
235 ///////////////////////////////////////////////////////////////////////////////
236 // WindowSizer, private:
238 void WindowSizer::DetermineWindowBoundsAndShowState(
239 const gfx::Rect
& specified_bounds
,
241 ui::WindowShowState
* show_state
) const {
244 // Pre-populate the window state with our default.
245 *show_state
= GetWindowDefaultShowState();
246 *bounds
= specified_bounds
;
249 // See if ash should decide the window placement.
250 if (GetBrowserBoundsAsh(bounds
, show_state
))
254 if (bounds
->IsEmpty()) {
255 // See if there's last active window's placement information.
256 if (GetLastActiveWindowBounds(bounds
, show_state
))
258 // See if there's saved placement information.
259 if (GetSavedWindowBounds(bounds
, show_state
))
262 // No saved placement, figure out some sensible default size based on
263 // the user's screen size.
264 GetDefaultWindowBounds(GetTargetDisplay(gfx::Rect()), bounds
);
268 // In case that there was a bound given we need to make sure that it is
269 // visible and fits on the screen.
270 // Find the size of the work area of the monitor that intersects the bounds
271 // of the anchor window. Note: AdjustBoundsToBeVisibleOnMonitorContaining
272 // does not exactly what we want: It makes only sure that "a minimal part"
273 // is visible on the screen.
274 gfx::Rect work_area
= screen_
->GetDisplayMatching(*bounds
).work_area();
275 // Resize so that it fits.
276 bounds
->AdjustToFit(work_area
);
279 bool WindowSizer::GetLastActiveWindowBounds(
281 ui::WindowShowState
* show_state
) const {
284 if (!state_provider_
.get() ||
285 !state_provider_
->GetLastActiveWindowState(bounds
, show_state
))
287 gfx::Rect last_window_bounds
= *bounds
;
288 bounds
->Offset(kWindowTilePixels
, kWindowTilePixels
);
289 AdjustBoundsToBeVisibleOnDisplay(screen_
->GetDisplayMatching(*bounds
),
295 bool WindowSizer::GetSavedWindowBounds(gfx::Rect
* bounds
,
296 ui::WindowShowState
* show_state
) const {
299 gfx::Rect saved_work_area
;
300 if (!state_provider_
.get() ||
301 !state_provider_
->GetPersistentState(bounds
,
305 AdjustBoundsToBeVisibleOnDisplay(GetTargetDisplay(*bounds
),
311 void WindowSizer::GetDefaultWindowBounds(const gfx::Display
& display
,
312 gfx::Rect
* default_bounds
) const {
313 DCHECK(default_bounds
);
315 // TODO(beng): insufficient but currently necessary. http://crbug.com/133312
316 if (chrome::ShouldOpenAshOnStartup()) {
317 *default_bounds
= ash::WindowPositioner::GetDefaultWindowBounds(display
);
321 gfx::Rect work_area
= display
.work_area();
323 // The default size is either some reasonably wide width, or if the work
324 // area is narrower, then the work area width less some aesthetic padding.
325 int default_width
= std::min(work_area
.width() - 2 * kWindowTilePixels
, 1050);
326 int default_height
= work_area
.height() - 2 * kWindowTilePixels
;
328 // For wider aspect ratio displays at higher resolutions, we might size the
329 // window narrower to allow two windows to easily be placed side-by-side.
330 gfx::Rect screen_size
= screen_
->GetPrimaryDisplay().bounds();
331 double width_to_height
=
332 static_cast<double>(screen_size
.width()) / screen_size
.height();
334 // The least wide a screen can be to qualify for the halving described above.
335 static const int kMinScreenWidthForWindowHalving
= 1600;
336 // We assume 16:9/10 is a fairly standard indicator of a wide aspect ratio
338 if (((width_to_height
* 10) >= 16) &&
339 work_area
.width() > kMinScreenWidthForWindowHalving
) {
340 // Halve the work area, subtracting aesthetic padding on either side.
341 // The padding is set so that two windows, side by side have
342 // kWindowTilePixels between screen edge and each other.
343 default_width
= static_cast<int>(work_area
.width() / 2. -
344 1.5 * kWindowTilePixels
);
346 default_bounds
->SetRect(kWindowTilePixels
+ work_area
.x(),
347 kWindowTilePixels
+ work_area
.y(),
348 default_width
, default_height
);
351 void WindowSizer::AdjustBoundsToBeVisibleOnDisplay(
352 const gfx::Display
& display
,
353 const gfx::Rect
& saved_work_area
,
354 gfx::Rect
* bounds
) const {
357 // If |bounds| is empty, reset to the default size.
358 if (bounds
->IsEmpty()) {
359 gfx::Rect default_bounds
;
360 GetDefaultWindowBounds(display
, &default_bounds
);
361 if (bounds
->height() <= 0)
362 bounds
->set_height(default_bounds
.height());
363 if (bounds
->width() <= 0)
364 bounds
->set_width(default_bounds
.width());
367 // Ensure the minimum height and width.
368 bounds
->set_height(std::max(kMinVisibleHeight
, bounds
->height()));
369 bounds
->set_width(std::max(kMinVisibleWidth
, bounds
->width()));
371 gfx::Rect work_area
= display
.work_area();
372 // Ensure that the title bar is not above the work area.
373 if (bounds
->y() < work_area
.y())
374 bounds
->set_y(work_area
.y());
376 // Reposition and resize the bounds if the saved_work_area is different from
377 // the current work area and the current work area doesn't completely contain
379 if (!saved_work_area
.IsEmpty() &&
380 saved_work_area
!= work_area
&&
381 !work_area
.Contains(*bounds
)) {
382 bounds
->set_width(std::min(bounds
->width(), work_area
.width()));
383 bounds
->set_height(std::min(bounds
->height(), work_area
.height()));
385 std::max(work_area
.x(),
386 std::min(bounds
->x(), work_area
.right() - bounds
->width())));
388 std::max(work_area
.y(),
389 std::min(bounds
->y(), work_area
.bottom() - bounds
->height())));
392 #if defined(OS_MACOSX)
393 // Limit the maximum height. On the Mac the sizer is on the
394 // bottom-right of the window, and a window cannot be moved "up"
395 // past the menubar. If the window is too tall you'll never be able
396 // to shrink it again. Windows does not have this limitation
397 // (e.g. can be resized from the top).
398 bounds
->set_height(std::min(work_area
.height(), bounds
->height()));
400 // On mac, we want to be aggressive about repositioning windows that are
401 // partially offscreen. If the window is partially offscreen horizontally,
402 // move it to be flush with the left edge of the work area.
403 if (bounds
->x() < work_area
.x() || bounds
->right() > work_area
.right())
404 bounds
->set_x(work_area
.x());
406 // If the window is partially offscreen vertically, move it to be flush with
407 // the top of the work area.
408 if (bounds
->y() < work_area
.y() || bounds
->bottom() > work_area
.bottom())
409 bounds
->set_y(work_area
.y());
411 // On non-Mac platforms, we are less aggressive about repositioning. Simply
412 // ensure that at least kMinVisibleWidth * kMinVisibleHeight is visible.
413 const int min_y
= work_area
.y() + kMinVisibleHeight
- bounds
->height();
414 const int min_x
= work_area
.x() + kMinVisibleWidth
- bounds
->width();
415 const int max_y
= work_area
.bottom() - kMinVisibleHeight
;
416 const int max_x
= work_area
.right() - kMinVisibleWidth
;
417 bounds
->set_y(std::max(min_y
, std::min(max_y
, bounds
->y())));
418 bounds
->set_x(std::max(min_x
, std::min(max_x
, bounds
->x())));
419 #endif // defined(OS_MACOSX)
422 gfx::Display
WindowSizer::GetTargetDisplay(const gfx::Rect
& bounds
) const {
423 return target_display_provider_
->GetTargetDisplay(screen_
, bounds
);
426 ui::WindowShowState
WindowSizer::GetWindowDefaultShowState() const {
428 return ui::SHOW_STATE_DEFAULT
;
430 // Only tabbed browsers use the command line or preference state, with the
431 // exception of devtools.
432 bool show_state
= !browser_
->is_type_tabbed() && !browser_
->is_devtools();
434 #if defined(USE_AURA)
435 // We use the apps save state on aura.
436 show_state
&= !browser_
->is_app();
440 return browser_
->initial_show_state();
442 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
443 switches::kStartMaximized
))
444 return ui::SHOW_STATE_MAXIMIZED
;
446 if (browser_
->initial_show_state() != ui::SHOW_STATE_DEFAULT
)
447 return browser_
->initial_show_state();
449 // Otherwise we use the default which can be overridden later on.
450 return ui::SHOW_STATE_DEFAULT
;