- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / window_sizer / window_sizer.cc
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 // 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;
31
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 {
36  public:
37   DefaultStateProvider(const std::string& app_name, const Browser* browser)
38       : app_name_(app_name), browser_(browser) {
39   }
40
41   // Overridden from WindowSizer::StateProvider:
42   virtual bool GetPersistentState(
43       gfx::Rect* bounds,
44       gfx::Rect* work_area,
45       ui::WindowShowState* show_state) const OVERRIDE {
46     DCHECK(bounds);
47     DCHECK(show_state);
48
49     if (!browser_ || !browser_->profile()->GetPrefs())
50       return false;
51
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));
65
66     int work_area_top = 0;
67     int work_area_left = 0;
68     int work_area_bottom = 0;
69     int work_area_right = 0;
70     if (wp_pref) {
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;
77     }
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));
81
82     return has_prefs;
83   }
84
85   virtual bool GetLastActiveWindowState(
86       gfx::Rect* bounds,
87       ui::WindowShowState* show_state) const OVERRIDE {
88     DCHECK(show_state);
89     // Applications are always restored with the same position.
90     if (!app_name_.empty())
91       return false;
92
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();
100     } else {
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
105       // crbug.com/175812.
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();
114           DCHECK(window);
115           break;
116         }
117       }
118     }
119
120     if (window) {
121       *bounds = window->GetRestoredBounds();
122       if (*show_state == ui::SHOW_STATE_DEFAULT && window->IsMaximized())
123         *show_state = ui::SHOW_STATE_MAXIMIZED;
124       return true;
125     }
126
127     return false;
128   }
129
130  private:
131   std::string app_name_;
132
133   // If set, is used as the reference browser for GetLastActiveWindowState.
134   const Browser* browser_;
135   DISALLOW_COPY_AND_ASSIGN(DefaultStateProvider);
136 };
137
138 ///////////////////////////////////////////////////////////////////////////////
139 // WindowSizer, public:
140
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()),
145       browser_(browser) {
146 }
147
148 WindowSizer::WindowSizer(StateProvider* state_provider,
149                          gfx::Screen* screen,
150                          const Browser* browser)
151     : state_provider_(state_provider),
152       screen_(screen),
153       browser_(browser) {
154   DCHECK(screen_);
155 }
156
157 WindowSizer::~WindowSizer() {
158 }
159
160 // static
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,
169                                           window_bounds,
170                                           show_state);
171 }
172
173 ///////////////////////////////////////////////////////////////////////////////
174 // WindowSizer, private:
175
176 void WindowSizer::DetermineWindowBoundsAndShowState(
177     const gfx::Rect& specified_bounds,
178     gfx::Rect* bounds,
179     ui::WindowShowState* show_state) const {
180   DCHECK(bounds);
181   DCHECK(show_state);
182   // Pre-populate the window state with our default.
183   *show_state = GetWindowDefaultShowState();
184   *bounds = specified_bounds;
185   if (bounds->IsEmpty()) {
186 #if defined(USE_ASH)
187     // See if ash should decide the window placement.
188     if (IsTabbedBrowserInAsh()) {
189       GetTabbedBrowserBoundsAsh(bounds, show_state);
190       return;
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);
197     }
198 #endif
199     // See if there's last active window's placement information.
200     if (GetLastWindowBounds(bounds, show_state))
201       return;
202     // See if there's saved placement information.
203     if (GetSavedWindowBounds(bounds, show_state))
204       return;
205     // No saved placement, figure out some sensible default size based on
206     // the user's screen size.
207     GetDefaultWindowBounds(screen_->GetPrimaryDisplay(), bounds);
208   } else {
209 #if defined(USE_ASH)
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);
216       return;
217     }
218 #endif
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);
228   }
229 }
230
231 bool WindowSizer::GetLastWindowBounds(gfx::Rect* bounds,
232                                       ui::WindowShowState* show_state) const {
233   DCHECK(bounds);
234   DCHECK(show_state);
235   if (!state_provider_.get() ||
236       !state_provider_->GetLastActiveWindowState(bounds, show_state))
237     return false;
238   gfx::Rect last_window_bounds = *bounds;
239   bounds->Offset(kWindowTilePixels, kWindowTilePixels);
240   AdjustBoundsToBeVisibleOnMonitorContaining(last_window_bounds,
241                                              gfx::Rect(),
242                                              bounds);
243   return true;
244 }
245
246 bool WindowSizer::GetSavedWindowBounds(gfx::Rect* bounds,
247                                        ui::WindowShowState* show_state) const {
248   DCHECK(bounds);
249   DCHECK(show_state);
250   gfx::Rect saved_work_area;
251   if (!state_provider_.get() ||
252       !state_provider_->GetPersistentState(bounds,
253                                            &saved_work_area,
254                                            show_state))
255     return false;
256   AdjustBoundsToBeVisibleOnMonitorContaining(*bounds, saved_work_area, bounds);
257   return true;
258 }
259
260 void WindowSizer::GetDefaultWindowBounds(const gfx::Display& display,
261                                          gfx::Rect* default_bounds) const {
262   DCHECK(default_bounds);
263 #if defined(USE_ASH)
264   // TODO(beng): insufficient but currently necessary. http://crbug.com/133312
265   if (chrome::ShouldOpenAshOnStartup()) {
266     *default_bounds = ash::WindowPositioner::GetDefaultWindowBounds(
267         display);
268     return;
269   }
270 #endif
271   gfx::Rect work_area = display.work_area();
272
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;
277
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();
283
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
287   // computer display.
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);
295   }
296   default_bounds->SetRect(kWindowTilePixels + work_area.x(),
297                           kWindowTilePixels + work_area.y(),
298                           default_width, default_height);
299 }
300
301 void WindowSizer::AdjustBoundsToBeVisibleOnMonitorContaining(
302     const gfx::Rect& other_bounds,
303     const gfx::Rect& saved_work_area,
304     gfx::Rect* bounds) const {
305   DCHECK(bounds);
306
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);
310
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());
318
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()));
322
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());
327
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
330   // the bounds.
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()));
336     bounds->set_x(
337         std::max(work_area.x(),
338                  std::min(bounds->x(), work_area.right() - bounds->width())));
339     bounds->set_y(
340         std::max(work_area.y(),
341                  std::min(bounds->y(), work_area.bottom() - bounds->height())));
342   }
343
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()));
351
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());
357
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());
362 #else
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)
372 }
373
374 ui::WindowShowState WindowSizer::GetWindowDefaultShowState() const {
375   if (!browser_)
376     return ui::SHOW_STATE_DEFAULT;
377
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();
381
382 #if defined(USE_AURA)
383   // We use the apps save state on aura.
384   show_state &= !browser_->is_app();
385 #endif
386
387   if (show_state)
388     return browser_->initial_show_state();
389
390   if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kStartMaximized))
391     return ui::SHOW_STATE_MAXIMIZED;
392
393   if (browser_->initial_show_state() != ui::SHOW_STATE_DEFAULT)
394     return browser_->initial_show_state();
395
396   // Otherwise we use the default which can be overridden later on.
397   return ui::SHOW_STATE_DEFAULT;
398 }
399
400 #if defined(USE_ASH)
401 bool WindowSizer::IsTabbedBrowserInAsh() const {
402   // TODO(beng): insufficient but currently necessary. http://crbug.com/133312
403   return chrome::ShouldOpenAshOnStartup() &&
404       browser_ &&
405       browser_->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_ASH &&
406       browser_->is_type_tabbed();
407 }
408
409 bool WindowSizer::IsPopupBrowserInAsh() const {
410   // TODO(beng): insufficient but currently necessary. http://crbug.com/133312
411   return chrome::ShouldOpenAshOnStartup() &&
412       browser_ &&
413       browser_->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_ASH &&
414       browser_->is_type_popup();
415 }
416 #endif