Home | History | Annotate | Download | only in window_sizer
      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 
     28 namespace {
     29 
     30 // Minimum height of the visible part of a window.
     31 const int kMinVisibleHeight = 30;
     32 // Minimum width of the visible part of a window.
     33 const 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.
     38 class 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 
    141 class 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 
    185 WindowSizer::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 
    196 WindowSizer::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 
    208 WindowSizer::~WindowSizer() {
    209 }
    210 
    211 // static
    212 void 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 
    233 void 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 
    274 bool 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 
    290 bool 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 
    306 void 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 
    346 void 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 
    417 gfx::Display WindowSizer::GetTargetDisplay(const gfx::Rect& bounds) const {
    418   return target_display_provider_->GetTargetDisplay(screen_, bounds);
    419 }
    420 
    421 ui::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