Home | History | Annotate | Download | only in ui
      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.
     18 class 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 
    113 WindowSizer::MonitorInfoProvider::MonitorInfoProvider() {}
    114 
    115 WindowSizer::MonitorInfoProvider::~MonitorInfoProvider() {}
    116 
    117 ///////////////////////////////////////////////////////////////////////////////
    118 // WindowSizer, public:
    119 
    120 WindowSizer::WindowSizer(
    121     StateProvider* state_provider,
    122     MonitorInfoProvider* monitor_info_provider) {
    123   Init(state_provider, monitor_info_provider);
    124 }
    125 
    126 WindowSizer::~WindowSizer() {
    127   if (state_provider_)
    128     delete state_provider_;
    129   if (monitor_info_provider_)
    130     delete monitor_info_provider_;
    131 }
    132 
    133 // static
    134 void 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 
    147 WindowSizer::WindowSizer(const std::string& app_name) {
    148   Init(new DefaultStateProvider(app_name, NULL),
    149        CreateDefaultMonitorInfoProvider());
    150 }
    151 
    152 void WindowSizer::Init(StateProvider* state_provider,
    153                        MonitorInfoProvider* monitor_info_provider) {
    154   state_provider_ = state_provider;
    155   monitor_info_provider_ = monitor_info_provider;
    156 }
    157 
    158 void 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 
    174 bool 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 
    186 bool 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 
    197 void 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 
    231 bool 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 
    258 namespace {
    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 
    265 void 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