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/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:
     39   DefaultStateProvider(const std::string& app_name, const Browser* browser)
     40       : app_name_(app_name), browser_(browser) {
     41   }
     42 
     43   // Overridden from WindowSizer::StateProvider:
     44   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 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 
     87   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:
    142   DefaultTargetDisplayProvider() {}
    143   virtual ~DefaultTargetDisplayProvider() {}
    144 
    145   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 
    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 
    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 
    192 WindowSizer::~WindowSizer() {
    193 }
    194 
    195 // static
    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 
    217 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   if (bounds->IsEmpty()) {
    227 #if defined(USE_ASH)
    228     // See if ash should decide the window placement.
    229     if (IsTabbedBrowserInAsh()) {
    230       GetTabbedBrowserBoundsAsh(bounds, show_state);
    231       return;
    232     } else if (browser_ && browser_->host_desktop_type() ==
    233                chrome::HOST_DESKTOP_TYPE_ASH) {
    234       // In ash, saved show state takes precidence.  If you have a
    235       // question or an issue, please contact oshima (at) chromium.org.
    236       GetSavedWindowBounds(bounds, show_state);
    237     }
    238 #endif
    239     // See if there's last active window's placement information.
    240     if (GetLastActiveWindowBounds(bounds, show_state))
    241       return;
    242     // See if there's saved placement information.
    243     if (GetSavedWindowBounds(bounds, show_state))
    244       return;
    245     // No saved placement, figure out some sensible default size based on
    246     // the user's screen size.
    247     GetDefaultWindowBounds(GetTargetDisplay(gfx::Rect()), bounds);
    248   } else {
    249 #if defined(USE_ASH)
    250     // In case of a popup with an 'unspecified' location in ash, we are
    251     // looking for a good screen location. We are interpreting (0,0) as an
    252     // unspecified location.
    253     if (IsPopupBrowserInAsh() && bounds->origin().IsOrigin()) {
    254       *bounds = ash::Shell::GetInstance()->window_positioner()->
    255           GetPopupPosition(*bounds);
    256       return;
    257     }
    258 #endif
    259     // In case that there was a bound given we need to make sure that it is
    260     // visible and fits on the screen.
    261     // Find the size of the work area of the monitor that intersects the bounds
    262     // of the anchor window. Note: AdjustBoundsToBeVisibleOnMonitorContaining
    263     // does not exactly what we want: It makes only sure that "a minimal part"
    264     // is visible on the screen.
    265     gfx::Rect work_area = screen_->GetDisplayMatching(*bounds).work_area();
    266     // Resize so that it fits.
    267     bounds->AdjustToFit(work_area);
    268   }
    269 }
    270 
    271 bool WindowSizer::GetLastActiveWindowBounds(
    272     gfx::Rect* bounds,
    273     ui::WindowShowState* show_state) const {
    274   DCHECK(bounds);
    275   DCHECK(show_state);
    276   if (!state_provider_.get() ||
    277       !state_provider_->GetLastActiveWindowState(bounds, show_state))
    278     return false;
    279   gfx::Rect last_window_bounds = *bounds;
    280   bounds->Offset(kWindowTilePixels, kWindowTilePixels);
    281   AdjustBoundsToBeVisibleOnDisplay(screen_->GetDisplayMatching(*bounds),
    282                                    gfx::Rect(),
    283                                    bounds);
    284   return true;
    285 }
    286 
    287 bool WindowSizer::GetSavedWindowBounds(gfx::Rect* bounds,
    288                                        ui::WindowShowState* show_state) const {
    289   DCHECK(bounds);
    290   DCHECK(show_state);
    291   gfx::Rect saved_work_area;
    292   if (!state_provider_.get() ||
    293       !state_provider_->GetPersistentState(bounds,
    294                                            &saved_work_area,
    295                                            show_state))
    296     return false;
    297   AdjustBoundsToBeVisibleOnDisplay(GetTargetDisplay(*bounds),
    298                                    saved_work_area,
    299                                    bounds);
    300   return true;
    301 }
    302 
    303 void WindowSizer::GetDefaultWindowBounds(const gfx::Display& display,
    304                                          gfx::Rect* default_bounds) const {
    305   DCHECK(default_bounds);
    306 #if defined(USE_ASH)
    307   // TODO(beng): insufficient but currently necessary. http://crbug.com/133312
    308   if (chrome::ShouldOpenAshOnStartup()) {
    309     *default_bounds = ash::WindowPositioner::GetDefaultWindowBounds(display);
    310     return;
    311   }
    312 #endif
    313   gfx::Rect work_area = display.work_area();
    314 
    315   // The default size is either some reasonably wide width, or if the work
    316   // area is narrower, then the work area width less some aesthetic padding.
    317   int default_width = std::min(work_area.width() - 2 * kWindowTilePixels, 1050);
    318   int default_height = work_area.height() - 2 * kWindowTilePixels;
    319 
    320   // For wider aspect ratio displays at higher resolutions, we might size the
    321   // window narrower to allow two windows to easily be placed side-by-side.
    322   gfx::Rect screen_size = screen_->GetPrimaryDisplay().bounds();
    323   double width_to_height =
    324     static_cast<double>(screen_size.width()) / screen_size.height();
    325 
    326   // The least wide a screen can be to qualify for the halving described above.
    327   static const int kMinScreenWidthForWindowHalving = 1600;
    328   // We assume 16:9/10 is a fairly standard indicator of a wide aspect ratio
    329   // computer display.
    330   if (((width_to_height * 10) >= 16) &&
    331       work_area.width() > kMinScreenWidthForWindowHalving) {
    332     // Halve the work area, subtracting aesthetic padding on either side.
    333     // The padding is set so that two windows, side by side have
    334     // kWindowTilePixels between screen edge and each other.
    335     default_width = static_cast<int>(work_area.width() / 2. -
    336         1.5 * kWindowTilePixels);
    337   }
    338   default_bounds->SetRect(kWindowTilePixels + work_area.x(),
    339                           kWindowTilePixels + work_area.y(),
    340                           default_width, default_height);
    341 }
    342 
    343 void WindowSizer::AdjustBoundsToBeVisibleOnDisplay(
    344     const gfx::Display& display,
    345     const gfx::Rect& saved_work_area,
    346     gfx::Rect* bounds) const {
    347   DCHECK(bounds);
    348 
    349   // If |bounds| is empty, reset to the default size.
    350   if (bounds->IsEmpty()) {
    351     gfx::Rect default_bounds;
    352     GetDefaultWindowBounds(display, &default_bounds);
    353     if (bounds->height() <= 0)
    354       bounds->set_height(default_bounds.height());
    355     if (bounds->width() <= 0)
    356       bounds->set_width(default_bounds.width());
    357   }
    358 
    359   // Ensure the minimum height and width.
    360   bounds->set_height(std::max(kMinVisibleHeight, bounds->height()));
    361   bounds->set_width(std::max(kMinVisibleWidth, bounds->width()));
    362 
    363   gfx::Rect work_area = display.work_area();
    364   // Ensure that the title bar is not above the work area.
    365   if (bounds->y() < work_area.y())
    366     bounds->set_y(work_area.y());
    367 
    368   // Reposition and resize the bounds if the saved_work_area is different from
    369   // the current work area and the current work area doesn't completely contain
    370   // the bounds.
    371   if (!saved_work_area.IsEmpty() &&
    372       saved_work_area != work_area &&
    373       !work_area.Contains(*bounds)) {
    374     bounds->set_width(std::min(bounds->width(), work_area.width()));
    375     bounds->set_height(std::min(bounds->height(), work_area.height()));
    376     bounds->set_x(
    377         std::max(work_area.x(),
    378                  std::min(bounds->x(), work_area.right() - bounds->width())));
    379     bounds->set_y(
    380         std::max(work_area.y(),
    381                  std::min(bounds->y(), work_area.bottom() - bounds->height())));
    382   }
    383 
    384 #if defined(OS_MACOSX)
    385   // Limit the maximum height.  On the Mac the sizer is on the
    386   // bottom-right of the window, and a window cannot be moved "up"
    387   // past the menubar.  If the window is too tall you'll never be able
    388   // to shrink it again.  Windows does not have this limitation
    389   // (e.g. can be resized from the top).
    390   bounds->set_height(std::min(work_area.height(), bounds->height()));
    391 
    392   // On mac, we want to be aggressive about repositioning windows that are
    393   // partially offscreen.  If the window is partially offscreen horizontally,
    394   // move it to be flush with the left edge of the work area.
    395   if (bounds->x() < work_area.x() || bounds->right() > work_area.right())
    396     bounds->set_x(work_area.x());
    397 
    398   // If the window is partially offscreen vertically, move it to be flush with
    399   // the top of the work area.
    400   if (bounds->y() < work_area.y() || bounds->bottom() > work_area.bottom())
    401     bounds->set_y(work_area.y());
    402 #else
    403   // On non-Mac platforms, we are less aggressive about repositioning.  Simply
    404   // ensure that at least kMinVisibleWidth * kMinVisibleHeight is visible.
    405   const int min_y = work_area.y() + kMinVisibleHeight - bounds->height();
    406   const int min_x = work_area.x() + kMinVisibleWidth - bounds->width();
    407   const int max_y = work_area.bottom() - kMinVisibleHeight;
    408   const int max_x = work_area.right() - kMinVisibleWidth;
    409   bounds->set_y(std::max(min_y, std::min(max_y, bounds->y())));
    410   bounds->set_x(std::max(min_x, std::min(max_x, bounds->x())));
    411 #endif  // defined(OS_MACOSX)
    412 }
    413 
    414 gfx::Display WindowSizer::GetTargetDisplay(const gfx::Rect& bounds) const {
    415   return target_display_provider_->GetTargetDisplay(screen_, bounds);
    416 }
    417 
    418 ui::WindowShowState WindowSizer::GetWindowDefaultShowState() const {
    419   if (!browser_)
    420     return ui::SHOW_STATE_DEFAULT;
    421 
    422   // Only tabbed browsers use the command line or preference state, with the
    423   // exception of devtools.
    424   bool show_state = !browser_->is_type_tabbed() && !browser_->is_devtools();
    425 
    426 #if defined(USE_AURA)
    427   // We use the apps save state on aura.
    428   show_state &= !browser_->is_app();
    429 #endif
    430 
    431   if (show_state)
    432     return browser_->initial_show_state();
    433 
    434   if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kStartMaximized))
    435     return ui::SHOW_STATE_MAXIMIZED;
    436 
    437   if (browser_->initial_show_state() != ui::SHOW_STATE_DEFAULT)
    438     return browser_->initial_show_state();
    439 
    440   // Otherwise we use the default which can be overridden later on.
    441   return ui::SHOW_STATE_DEFAULT;
    442 }
    443 
    444 #if defined(USE_ASH)
    445 bool WindowSizer::IsTabbedBrowserInAsh() const {
    446   return browser_ &&
    447       browser_->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_ASH &&
    448       browser_->is_type_tabbed();
    449 }
    450 
    451 bool WindowSizer::IsPopupBrowserInAsh() const {
    452   return browser_ &&
    453       browser_->host_desktop_type() == chrome::HOST_DESKTOP_TYPE_ASH &&
    454       browser_->is_type_popup();
    455 }
    456 #endif
    457