1// Copyright (c) 2011 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.h"
6
7#include "chrome/browser/browser_process.h"
8#include "chrome/browser/prefs/pref_service.h"
9#include "chrome/browser/profiles/profile.h"
10#include "chrome/browser/ui/browser.h"
11#include "chrome/browser/ui/browser_list.h"
12#include "chrome/browser/ui/browser_window.h"
13#include "chrome/common/pref_names.h"
14
15///////////////////////////////////////////////////////////////////////////////
16// An implementation of WindowSizer::StateProvider that gets the last active
17// and persistent state from the browser window and the user's profile.
18class DefaultStateProvider : public WindowSizer::StateProvider {
19 public:
20  explicit DefaultStateProvider(const std::string& app_name,
21      const Browser* browser) : app_name_(app_name),
22                                browser_(browser) {
23  }
24
25  // Overridden from WindowSizer::StateProvider:
26  virtual bool GetPersistentState(gfx::Rect* bounds,
27                                  bool* maximized,
28                                  gfx::Rect* work_area) const {
29    DCHECK(bounds && maximized);
30
31    std::string key(prefs::kBrowserWindowPlacement);
32    if (!app_name_.empty()) {
33      key.append("_");
34      key.append(app_name_);
35    }
36
37    if (!browser_->profile()->GetPrefs())
38      return false;
39
40    const DictionaryValue* wp_pref =
41        browser_->profile()->GetPrefs()->GetDictionary(key.c_str());
42    int top = 0, left = 0, bottom = 0, right = 0;
43    bool has_prefs =
44        wp_pref &&
45        wp_pref->GetInteger("top", &top) &&
46        wp_pref->GetInteger("left", &left) &&
47        wp_pref->GetInteger("bottom", &bottom) &&
48        wp_pref->GetInteger("right", &right) &&
49        wp_pref->GetBoolean("maximized", maximized);
50    bounds->SetRect(left, top, std::max(0, right - left),
51                    std::max(0, bottom - top));
52
53    int work_area_top = 0;
54    int work_area_left = 0;
55    int work_area_bottom = 0;
56    int work_area_right = 0;
57    if (wp_pref) {
58      wp_pref->GetInteger("work_area_top", &work_area_top);
59      wp_pref->GetInteger("work_area_left", &work_area_left);
60      wp_pref->GetInteger("work_area_bottom", &work_area_bottom);
61      wp_pref->GetInteger("work_area_right", &work_area_right);
62    }
63    work_area->SetRect(work_area_left, work_area_top,
64                      std::max(0, work_area_right - work_area_left),
65                      std::max(0, work_area_bottom - work_area_top));
66
67    return has_prefs;
68  }
69
70  virtual bool GetLastActiveWindowState(gfx::Rect* bounds) const {
71    // Applications are always restored with the same position.
72    if (!app_name_.empty())
73      return false;
74
75    // If a reference browser is set, use its window. Otherwise find last
76    // active.
77    BrowserWindow* window = NULL;
78    // Window may be null if browser is just starting up.
79    if (browser_ && browser_->window()) {
80      window = browser_->window();
81    } else {
82      BrowserList::const_reverse_iterator it = BrowserList::begin_last_active();
83      BrowserList::const_reverse_iterator end = BrowserList::end_last_active();
84      for (; (it != end); ++it) {
85        Browser* last_active = *it;
86        if (last_active && last_active->type() == Browser::TYPE_NORMAL) {
87          window = last_active->window();
88          DCHECK(window);
89          break;
90        }
91      }
92    }
93
94    if (window) {
95      *bounds = window->GetRestoredBounds();
96      return true;
97    }
98
99    return false;
100  }
101
102 private:
103  std::string app_name_;
104
105  // If set, is used as the reference browser for GetLastActiveWindowState.
106  const Browser* browser_;
107  DISALLOW_COPY_AND_ASSIGN(DefaultStateProvider);
108};
109
110///////////////////////////////////////////////////////////////////////////////
111// MonitorInfoProvider, public:
112
113WindowSizer::MonitorInfoProvider::MonitorInfoProvider() {}
114
115WindowSizer::MonitorInfoProvider::~MonitorInfoProvider() {}
116
117///////////////////////////////////////////////////////////////////////////////
118// WindowSizer, public:
119
120WindowSizer::WindowSizer(
121    StateProvider* state_provider,
122    MonitorInfoProvider* monitor_info_provider) {
123  Init(state_provider, monitor_info_provider);
124}
125
126WindowSizer::~WindowSizer() {
127  if (state_provider_)
128    delete state_provider_;
129  if (monitor_info_provider_)
130    delete monitor_info_provider_;
131}
132
133// static
134void WindowSizer::GetBrowserWindowBounds(const std::string& app_name,
135                                         const gfx::Rect& specified_bounds,
136                                         const Browser* browser,
137                                         gfx::Rect* window_bounds,
138                                         bool* maximized) {
139  const WindowSizer sizer(new DefaultStateProvider(app_name, browser),
140                          CreateDefaultMonitorInfoProvider());
141  sizer.DetermineWindowBounds(specified_bounds, window_bounds, maximized);
142}
143
144///////////////////////////////////////////////////////////////////////////////
145// WindowSizer, private:
146
147WindowSizer::WindowSizer(const std::string& app_name) {
148  Init(new DefaultStateProvider(app_name, NULL),
149       CreateDefaultMonitorInfoProvider());
150}
151
152void WindowSizer::Init(StateProvider* state_provider,
153                       MonitorInfoProvider* monitor_info_provider) {
154  state_provider_ = state_provider;
155  monitor_info_provider_ = monitor_info_provider;
156}
157
158void WindowSizer::DetermineWindowBounds(const gfx::Rect& specified_bounds,
159                                        gfx::Rect* bounds,
160                                        bool* maximized) const {
161  *bounds = specified_bounds;
162  if (bounds->IsEmpty()) {
163    // See if there's saved placement information.
164    if (!GetLastWindowBounds(bounds)) {
165      if (!GetSavedWindowBounds(bounds, maximized)) {
166        // No saved placement, figure out some sensible default size based on
167        // the user's screen size.
168        GetDefaultWindowBounds(bounds);
169      }
170    }
171  }
172}
173
174bool WindowSizer::GetLastWindowBounds(gfx::Rect* bounds) const {
175  DCHECK(bounds);
176  if (!state_provider_ || !state_provider_->GetLastActiveWindowState(bounds))
177    return false;
178  gfx::Rect last_window_bounds = *bounds;
179  bounds->Offset(kWindowTilePixels, kWindowTilePixels);
180  AdjustBoundsToBeVisibleOnMonitorContaining(last_window_bounds,
181                                             gfx::Rect(),
182                                             bounds);
183  return true;
184}
185
186bool WindowSizer::GetSavedWindowBounds(gfx::Rect* bounds,
187                                       bool* maximized) const {
188  DCHECK(bounds && maximized);
189  gfx::Rect saved_work_area;
190  if (!state_provider_ ||
191      !state_provider_->GetPersistentState(bounds, maximized, &saved_work_area))
192    return false;
193  AdjustBoundsToBeVisibleOnMonitorContaining(*bounds, saved_work_area, bounds);
194  return true;
195}
196
197void WindowSizer::GetDefaultWindowBounds(gfx::Rect* default_bounds) const {
198  DCHECK(default_bounds);
199  DCHECK(monitor_info_provider_);
200
201  gfx::Rect work_area = monitor_info_provider_->GetPrimaryMonitorWorkArea();
202
203  // The default size is either some reasonably wide width, or if the work
204  // area is narrower, then the work area width less some aesthetic padding.
205  int default_width = std::min(work_area.width() - 2 * kWindowTilePixels, 1050);
206  int default_height = work_area.height() - 2 * kWindowTilePixels;
207
208  // For wider aspect ratio displays at higher resolutions, we might size the
209  // window narrower to allow two windows to easily be placed side-by-side.
210  gfx::Rect screen_size = monitor_info_provider_->GetPrimaryMonitorBounds();
211  double width_to_height =
212    static_cast<double>(screen_size.width()) / screen_size.height();
213
214  // The least wide a screen can be to qualify for the halving described above.
215  static const int kMinScreenWidthForWindowHalving = 1600;
216  // We assume 16:9/10 is a fairly standard indicator of a wide aspect ratio
217  // computer display.
218  if (((width_to_height * 10) >= 16) &&
219      work_area.width() > kMinScreenWidthForWindowHalving) {
220    // Halve the work area, subtracting aesthetic padding on either side.
221    // The padding is set so that two windows, side by side have
222    // kWindowTilePixels between screen edge and each other.
223    default_width = static_cast<int>(work_area.width() / 2. -
224        1.5 * kWindowTilePixels);
225  }
226  default_bounds->SetRect(kWindowTilePixels + work_area.x(),
227                          kWindowTilePixels + work_area.y(),
228                          default_width, default_height);
229}
230
231bool WindowSizer::PositionIsOffscreen(int position, Edge edge) const {
232  DCHECK(monitor_info_provider_);
233  size_t monitor_count = monitor_info_provider_->GetMonitorCount();
234  for (size_t i = 0; i < monitor_count; ++i) {
235    gfx::Rect work_area = monitor_info_provider_->GetWorkAreaAt(i);
236    switch (edge) {
237      case TOP:
238        if (position >= work_area.y())
239          return false;
240        break;
241      case LEFT:
242        if (position >= work_area.x())
243          return false;
244        break;
245      case BOTTOM:
246        if (position <= work_area.bottom())
247          return false;
248        break;
249      case RIGHT:
250        if (position <= work_area.right())
251          return false;
252        break;
253    }
254  }
255  return true;
256}
257
258namespace {
259  // Minimum height of the visible part of a window.
260  static const int kMinVisibleHeight = 30;
261  // Minimum width of the visible part of a window.
262  static const int kMinVisibleWidth = 30;
263}
264
265void WindowSizer::AdjustBoundsToBeVisibleOnMonitorContaining(
266    const gfx::Rect& other_bounds,
267    const gfx::Rect& saved_work_area,
268    gfx::Rect* bounds) const {
269  DCHECK(bounds);
270  DCHECK(monitor_info_provider_);
271
272  // Find the size of the work area of the monitor that intersects the bounds
273  // of the anchor window.
274  gfx::Rect work_area =
275      monitor_info_provider_->GetMonitorWorkAreaMatching(other_bounds);
276
277  // If height or width are 0, reset to the default size.
278  gfx::Rect default_bounds;
279  GetDefaultWindowBounds(&default_bounds);
280  if (bounds->height() <= 0)
281    bounds->set_height(default_bounds.height());
282  if (bounds->width() <= 0)
283    bounds->set_width(default_bounds.width());
284
285  // Ensure the minimum height and width.
286  bounds->set_height(std::max(kMinVisibleHeight, bounds->height()));
287  bounds->set_width(std::max(kMinVisibleWidth, bounds->width()));
288
289  // Ensure that the title bar is not above the work area.
290  if (bounds->y() < work_area.y())
291    bounds->set_y(work_area.y());
292
293  // Reposition and resize the bounds if the saved_work_area is different from
294  // the current work area and the current work area doesn't completely contain
295  // the bounds.
296  if (!saved_work_area.IsEmpty() &&
297      saved_work_area != work_area &&
298      !work_area.Contains(*bounds)) {
299    bounds->set_width(std::min(bounds->width(), work_area.width()));
300    bounds->set_height(std::min(bounds->height(), work_area.height()));
301    bounds->set_x(
302        std::max(work_area.x(),
303                 std::min(bounds->x(), work_area.right() - bounds->width())));
304    bounds->set_y(
305        std::max(work_area.y(),
306                 std::min(bounds->y(), work_area.bottom() - bounds->height())));
307  }
308
309#if defined(OS_MACOSX)
310  // Limit the maximum height.  On the Mac the sizer is on the
311  // bottom-right of the window, and a window cannot be moved "up"
312  // past the menubar.  If the window is too tall you'll never be able
313  // to shrink it again.  Windows does not have this limitation
314  // (e.g. can be resized from the top).
315  bounds->set_height(std::min(work_area.height(), bounds->height()));
316
317  // On mac, we want to be aggressive about repositioning windows that are
318  // partially offscreen.  If the window is partially offscreen horizontally,
319  // move it to be flush with the left edge of the work area.
320  if (bounds->x() < work_area.x() || bounds->right() > work_area.right())
321    bounds->set_x(work_area.x());
322
323  // If the window is partially offscreen vertically, move it to be flush with
324  // the top of the work area.
325  if (bounds->y() < work_area.y() || bounds->bottom() > work_area.bottom())
326    bounds->set_y(work_area.y());
327#else
328  // On non-Mac platforms, we are less aggressive about repositioning.  Simply
329  // ensure that at least kMinVisibleWidth * kMinVisibleHeight is visible.
330  const int min_y = work_area.y() + kMinVisibleHeight - bounds->height();
331  const int min_x = work_area.x() + kMinVisibleWidth - bounds->width();
332  const int max_y = work_area.bottom() - kMinVisibleHeight;
333  const int max_x = work_area.right() - kMinVisibleWidth;
334  bounds->set_y(std::max(min_y, std::min(max_y, bounds->y())));
335  bounds->set_x(std::max(min_x, std::min(max_x, bounds->x())));
336#endif  // defined(OS_MACOSX)
337}
338