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