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.
4
5#include "chrome/browser/ui/window_sizer/window_sizer.h"
6
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/base/ui_base_switches.h"
20#include "ui/gfx/screen.h"
21
22#if defined(USE_ASH)
23#include "ash/shell.h"
24#include "ash/wm/window_positioner.h"
25#include "chrome/browser/ui/ash/ash_init.h"
26#endif
27
28namespace {
29
30// Minimum height of the visible part of a window.
31const int kMinVisibleHeight = 30;
32// Minimum width of the visible part of a window.
33const int kMinVisibleWidth = 30;
34
35///////////////////////////////////////////////////////////////////////////////
36// An implementation of WindowSizer::StateProvider that gets the last active
37// and persistent state from the browser window and the user's profile.
38class DefaultStateProvider : public WindowSizer::StateProvider {
39 public:
40  DefaultStateProvider(const std::string& app_name, const Browser* browser)
41      : app_name_(app_name), browser_(browser) {
42  }
43
44  // Overridden from WindowSizer::StateProvider:
45  virtual bool GetPersistentState(
46      gfx::Rect* bounds,
47      gfx::Rect* work_area,
48      ui::WindowShowState* show_state) const OVERRIDE {
49    DCHECK(bounds);
50    DCHECK(show_state);
51
52    if (!browser_ || !browser_->profile()->GetPrefs())
53      return false;
54
55    const base::DictionaryValue* wp_pref =
56        chrome::GetWindowPlacementDictionaryReadOnly(
57            chrome::GetWindowName(browser_), browser_->profile()->GetPrefs());
58    int top = 0, left = 0, bottom = 0, right = 0;
59    bool maximized = false;
60    bool has_prefs = wp_pref &&
61                     wp_pref->GetInteger("top", &top) &&
62                     wp_pref->GetInteger("left", &left) &&
63                     wp_pref->GetInteger("bottom", &bottom) &&
64                     wp_pref->GetInteger("right", &right) &&
65                     wp_pref->GetBoolean("maximized", &maximized);
66    bounds->SetRect(left, top, std::max(0, right - left),
67                    std::max(0, bottom - top));
68
69    int work_area_top = 0;
70    int work_area_left = 0;
71    int work_area_bottom = 0;
72    int work_area_right = 0;
73    if (wp_pref) {
74      wp_pref->GetInteger("work_area_top", &work_area_top);
75      wp_pref->GetInteger("work_area_left", &work_area_left);
76      wp_pref->GetInteger("work_area_bottom", &work_area_bottom);
77      wp_pref->GetInteger("work_area_right", &work_area_right);
78      if (*show_state == ui::SHOW_STATE_DEFAULT && maximized)
79        *show_state = ui::SHOW_STATE_MAXIMIZED;
80    }
81    work_area->SetRect(work_area_left, work_area_top,
82                      std::max(0, work_area_right - work_area_left),
83                      std::max(0, work_area_bottom - work_area_top));
84
85    return has_prefs;
86  }
87
88  virtual bool GetLastActiveWindowState(
89      gfx::Rect* bounds,
90      ui::WindowShowState* show_state) const OVERRIDE {
91    DCHECK(show_state);
92    // Applications are always restored with the same position.
93    if (!app_name_.empty())
94      return false;
95
96    // If a reference browser is set, use its window. Otherwise find last
97    // active. Panels are never used as reference browsers as panels are
98    // specially positioned.
99    BrowserWindow* window = NULL;
100    // Window may be null if browser is just starting up.
101    if (browser_ && browser_->window()) {
102      window = browser_->window();
103    } else {
104      // This code is only ran on the native desktop (on the ash
105      // desktop, GetTabbedBrowserBoundsAsh should take over below
106      // before this is reached).  TODO(gab): This code should go in a
107      // native desktop specific window sizer as part of fixing
108      // crbug.com/175812.
109      const BrowserList* native_browser_list =
110          BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_NATIVE);
111      for (BrowserList::const_reverse_iterator it =
112               native_browser_list->begin_last_active();
113           it != native_browser_list->end_last_active(); ++it) {
114        Browser* last_active = *it;
115        if (last_active && last_active->is_type_tabbed()) {
116          window = last_active->window();
117          DCHECK(window);
118          break;
119        }
120      }
121    }
122
123    if (window) {
124      *bounds = window->GetRestoredBounds();
125      if (*show_state == ui::SHOW_STATE_DEFAULT && window->IsMaximized())
126        *show_state = ui::SHOW_STATE_MAXIMIZED;
127      return true;
128    }
129
130    return false;
131  }
132
133 private:
134  std::string app_name_;
135
136  // If set, is used as the reference browser for GetLastActiveWindowState.
137  const Browser* browser_;
138  DISALLOW_COPY_AND_ASSIGN(DefaultStateProvider);
139};
140
141class DefaultTargetDisplayProvider : public WindowSizer::TargetDisplayProvider {
142 public:
143  DefaultTargetDisplayProvider() {}
144  virtual ~DefaultTargetDisplayProvider() {}
145
146  virtual gfx::Display GetTargetDisplay(
147      const gfx::Screen* screen,
148      const gfx::Rect& bounds) const OVERRIDE {
149#if defined(USE_ASH)
150    bool force_ash = false;
151    // On Windows check if the browser is launched to serve ASH. If yes then
152    // we should get the display for the corresponding root window created for
153    // ASH. This ensures that the display gets the correct workarea, etc.
154    // If the ASH shell does not exist then the current behavior is to open
155    // browser windows if any on the desktop. Preserve that for now.
156    // TODO(ananta).
157    // This effectively means that the running browser process is in a split
158    // personality mode, part of it running in ASH and the other running in
159    // desktop. This may cause apps and other widgets to not work correctly.
160    // Revisit and address.
161#if defined(OS_WIN)
162    force_ash = ash::Shell::HasInstance() &&
163        CommandLine::ForCurrentProcess()->HasSwitch(switches::kViewerConnect);
164#endif
165    // Use the target display on ash.
166    if (chrome::ShouldOpenAshOnStartup() || force_ash) {
167      aura::Window* target = ash::Shell::GetTargetRootWindow();
168      return screen->GetDisplayNearestWindow(target);
169    }
170#endif
171    // Find the size of the work area of the monitor that intersects the bounds
172    // of the anchor window.
173    return screen->GetDisplayMatching(bounds);
174  }
175
176 private:
177  DISALLOW_COPY_AND_ASSIGN(DefaultTargetDisplayProvider);
178};
179
180}  // namespace
181
182///////////////////////////////////////////////////////////////////////////////
183// WindowSizer, public:
184
185WindowSizer::WindowSizer(
186    scoped_ptr<StateProvider> state_provider,
187    scoped_ptr<TargetDisplayProvider> target_display_provider,
188    const Browser* browser)
189    : state_provider_(state_provider.Pass()),
190      target_display_provider_(target_display_provider.Pass()),
191      // TODO(scottmg): NativeScreen is wrong. http://crbug.com/133312
192      screen_(gfx::Screen::GetNativeScreen()),
193      browser_(browser) {
194}
195
196WindowSizer::WindowSizer(
197    scoped_ptr<StateProvider> state_provider,
198    scoped_ptr<TargetDisplayProvider> target_display_provider,
199    gfx::Screen* screen,
200    const Browser* browser)
201    : state_provider_(state_provider.Pass()),
202      target_display_provider_(target_display_provider.Pass()),
203      screen_(screen),
204      browser_(browser) {
205  DCHECK(screen_);
206}
207
208WindowSizer::~WindowSizer() {
209}
210
211// static
212void WindowSizer::GetBrowserWindowBoundsAndShowState(
213    const std::string& app_name,
214    const gfx::Rect& specified_bounds,
215    const Browser* browser,
216    gfx::Rect* window_bounds,
217    ui::WindowShowState* show_state) {
218  scoped_ptr<StateProvider> state_provider(
219      new DefaultStateProvider(app_name, browser));
220  scoped_ptr<TargetDisplayProvider> target_display_provider(
221      new DefaultTargetDisplayProvider);
222  const WindowSizer sizer(state_provider.Pass(),
223                          target_display_provider.Pass(),
224                          browser);
225  sizer.DetermineWindowBoundsAndShowState(specified_bounds,
226                                          window_bounds,
227                                          show_state);
228}
229
230///////////////////////////////////////////////////////////////////////////////
231// WindowSizer, private:
232
233void WindowSizer::DetermineWindowBoundsAndShowState(
234    const gfx::Rect& specified_bounds,
235    gfx::Rect* bounds,
236    ui::WindowShowState* show_state) const {
237  DCHECK(bounds);
238  DCHECK(show_state);
239  // Pre-populate the window state with our default.
240  *show_state = GetWindowDefaultShowState();
241  *bounds = specified_bounds;
242
243#if defined(USE_ASH)
244  // See if ash should decide the window placement.
245  if (GetBrowserBoundsAsh(bounds, show_state))
246    return;
247#endif
248
249  if (bounds->IsEmpty()) {
250    // See if there's last active window's placement information.
251    if (GetLastActiveWindowBounds(bounds, show_state))
252      return;
253    // See if there's saved placement information.
254    if (GetSavedWindowBounds(bounds, show_state))
255      return;
256
257    // No saved placement, figure out some sensible default size based on
258    // the user's screen size.
259    GetDefaultWindowBounds(GetTargetDisplay(gfx::Rect()), bounds);
260    return;
261  }
262
263  // In case that there was a bound given we need to make sure that it is
264  // visible and fits on the screen.
265  // Find the size of the work area of the monitor that intersects the bounds
266  // of the anchor window. Note: AdjustBoundsToBeVisibleOnMonitorContaining
267  // does not exactly what we want: It makes only sure that "a minimal part"
268  // is visible on the screen.
269  gfx::Rect work_area = screen_->GetDisplayMatching(*bounds).work_area();
270  // Resize so that it fits.
271  bounds->AdjustToFit(work_area);
272}
273
274bool WindowSizer::GetLastActiveWindowBounds(
275    gfx::Rect* bounds,
276    ui::WindowShowState* show_state) const {
277  DCHECK(bounds);
278  DCHECK(show_state);
279  if (!state_provider_.get() ||
280      !state_provider_->GetLastActiveWindowState(bounds, show_state))
281    return false;
282  gfx::Rect last_window_bounds = *bounds;
283  bounds->Offset(kWindowTilePixels, kWindowTilePixels);
284  AdjustBoundsToBeVisibleOnDisplay(screen_->GetDisplayMatching(*bounds),
285                                   gfx::Rect(),
286                                   bounds);
287  return true;
288}
289
290bool WindowSizer::GetSavedWindowBounds(gfx::Rect* bounds,
291                                       ui::WindowShowState* show_state) const {
292  DCHECK(bounds);
293  DCHECK(show_state);
294  gfx::Rect saved_work_area;
295  if (!state_provider_.get() ||
296      !state_provider_->GetPersistentState(bounds,
297                                           &saved_work_area,
298                                           show_state))
299    return false;
300  AdjustBoundsToBeVisibleOnDisplay(GetTargetDisplay(*bounds),
301                                   saved_work_area,
302                                   bounds);
303  return true;
304}
305
306void WindowSizer::GetDefaultWindowBounds(const gfx::Display& display,
307                                         gfx::Rect* default_bounds) const {
308  DCHECK(default_bounds);
309#if defined(USE_ASH)
310  // TODO(beng): insufficient but currently necessary. http://crbug.com/133312
311  if (chrome::ShouldOpenAshOnStartup()) {
312    *default_bounds = ash::WindowPositioner::GetDefaultWindowBounds(display);
313    return;
314  }
315#endif
316  gfx::Rect work_area = display.work_area();
317
318  // The default size is either some reasonably wide width, or if the work
319  // area is narrower, then the work area width less some aesthetic padding.
320  int default_width = std::min(work_area.width() - 2 * kWindowTilePixels, 1050);
321  int default_height = work_area.height() - 2 * kWindowTilePixels;
322
323  // For wider aspect ratio displays at higher resolutions, we might size the
324  // window narrower to allow two windows to easily be placed side-by-side.
325  gfx::Rect screen_size = screen_->GetPrimaryDisplay().bounds();
326  double width_to_height =
327    static_cast<double>(screen_size.width()) / screen_size.height();
328
329  // The least wide a screen can be to qualify for the halving described above.
330  static const int kMinScreenWidthForWindowHalving = 1600;
331  // We assume 16:9/10 is a fairly standard indicator of a wide aspect ratio
332  // computer display.
333  if (((width_to_height * 10) >= 16) &&
334      work_area.width() > kMinScreenWidthForWindowHalving) {
335    // Halve the work area, subtracting aesthetic padding on either side.
336    // The padding is set so that two windows, side by side have
337    // kWindowTilePixels between screen edge and each other.
338    default_width = static_cast<int>(work_area.width() / 2. -
339        1.5 * kWindowTilePixels);
340  }
341  default_bounds->SetRect(kWindowTilePixels + work_area.x(),
342                          kWindowTilePixels + work_area.y(),
343                          default_width, default_height);
344}
345
346void WindowSizer::AdjustBoundsToBeVisibleOnDisplay(
347    const gfx::Display& display,
348    const gfx::Rect& saved_work_area,
349    gfx::Rect* bounds) const {
350  DCHECK(bounds);
351
352  // If |bounds| is empty, reset to the default size.
353  if (bounds->IsEmpty()) {
354    gfx::Rect default_bounds;
355    GetDefaultWindowBounds(display, &default_bounds);
356    if (bounds->height() <= 0)
357      bounds->set_height(default_bounds.height());
358    if (bounds->width() <= 0)
359      bounds->set_width(default_bounds.width());
360  }
361
362  // Ensure the minimum height and width.
363  bounds->set_height(std::max(kMinVisibleHeight, bounds->height()));
364  bounds->set_width(std::max(kMinVisibleWidth, bounds->width()));
365
366  gfx::Rect work_area = display.work_area();
367  // Ensure that the title bar is not above the work area.
368  if (bounds->y() < work_area.y())
369    bounds->set_y(work_area.y());
370
371  // Reposition and resize the bounds if the saved_work_area is different from
372  // the current work area and the current work area doesn't completely contain
373  // the bounds.
374  if (!saved_work_area.IsEmpty() &&
375      saved_work_area != work_area &&
376      !work_area.Contains(*bounds)) {
377    bounds->set_width(std::min(bounds->width(), work_area.width()));
378    bounds->set_height(std::min(bounds->height(), work_area.height()));
379    bounds->set_x(
380        std::max(work_area.x(),
381                 std::min(bounds->x(), work_area.right() - bounds->width())));
382    bounds->set_y(
383        std::max(work_area.y(),
384                 std::min(bounds->y(), work_area.bottom() - bounds->height())));
385  }
386
387#if defined(OS_MACOSX)
388  // Limit the maximum height.  On the Mac the sizer is on the
389  // bottom-right of the window, and a window cannot be moved "up"
390  // past the menubar.  If the window is too tall you'll never be able
391  // to shrink it again.  Windows does not have this limitation
392  // (e.g. can be resized from the top).
393  bounds->set_height(std::min(work_area.height(), bounds->height()));
394
395  // On mac, we want to be aggressive about repositioning windows that are
396  // partially offscreen.  If the window is partially offscreen horizontally,
397  // move it to be flush with the left edge of the work area.
398  if (bounds->x() < work_area.x() || bounds->right() > work_area.right())
399    bounds->set_x(work_area.x());
400
401  // If the window is partially offscreen vertically, move it to be flush with
402  // the top of the work area.
403  if (bounds->y() < work_area.y() || bounds->bottom() > work_area.bottom())
404    bounds->set_y(work_area.y());
405#else
406  // On non-Mac platforms, we are less aggressive about repositioning.  Simply
407  // ensure that at least kMinVisibleWidth * kMinVisibleHeight is visible.
408  const int min_y = work_area.y() + kMinVisibleHeight - bounds->height();
409  const int min_x = work_area.x() + kMinVisibleWidth - bounds->width();
410  const int max_y = work_area.bottom() - kMinVisibleHeight;
411  const int max_x = work_area.right() - kMinVisibleWidth;
412  bounds->set_y(std::max(min_y, std::min(max_y, bounds->y())));
413  bounds->set_x(std::max(min_x, std::min(max_x, bounds->x())));
414#endif  // defined(OS_MACOSX)
415}
416
417gfx::Display WindowSizer::GetTargetDisplay(const gfx::Rect& bounds) const {
418  return target_display_provider_->GetTargetDisplay(screen_, bounds);
419}
420
421ui::WindowShowState WindowSizer::GetWindowDefaultShowState() const {
422  if (!browser_)
423    return ui::SHOW_STATE_DEFAULT;
424
425  // Only tabbed browsers use the command line or preference state, with the
426  // exception of devtools.
427  bool show_state = !browser_->is_type_tabbed() && !browser_->is_devtools();
428
429#if defined(USE_AURA)
430  // We use the apps save state on aura.
431  show_state &= !browser_->is_app();
432#endif
433
434  if (show_state)
435    return browser_->initial_show_state();
436
437  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kStartMaximized))
438    return ui::SHOW_STATE_MAXIMIZED;
439
440  if (browser_->initial_show_state() != ui::SHOW_STATE_DEFAULT)
441    return browser_->initial_show_state();
442
443  // Otherwise we use the default which can be overridden later on.
444  return ui::SHOW_STATE_DEFAULT;
445}
446