| OLD | NEW |
| (Empty) |
| 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.h" | |
| 6 | |
| 7 #include "base/compiler_specific.h" | |
| 8 #include "chrome/browser/browser_process.h" | |
| 9 #include "chrome/browser/prefs/pref_service.h" | |
| 10 #include "chrome/browser/profiles/profile.h" | |
| 11 #include "chrome/browser/ui/ash/ash_init.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/common/pref_names.h" | |
| 17 #include "ui/gfx/screen.h" | |
| 18 | |
| 19 // Minimum height of the visible part of a window. | |
| 20 const int kMinVisibleHeight = 30; | |
| 21 // Minimum width of the visible part of a window. | |
| 22 const int kMinVisibleWidth = 30; | |
| 23 | |
| 24 class DefaultMonitorInfoProvider : public MonitorInfoProvider { | |
| 25 public: | |
| 26 // Overridden from MonitorInfoProvider: | |
| 27 virtual gfx::Rect GetPrimaryDisplayWorkArea() const OVERRIDE { | |
| 28 return gfx::Screen::GetPrimaryDisplay().work_area(); | |
| 29 } | |
| 30 virtual gfx::Rect GetPrimaryDisplayBounds() const OVERRIDE { | |
| 31 return gfx::Screen::GetPrimaryDisplay().bounds(); | |
| 32 } | |
| 33 virtual gfx::Rect GetMonitorWorkAreaMatching( | |
| 34 const gfx::Rect& match_rect) const OVERRIDE { | |
| 35 return gfx::Screen::GetDisplayMatching(match_rect).work_area(); | |
| 36 } | |
| 37 }; | |
| 38 | |
| 39 /////////////////////////////////////////////////////////////////////////////// | |
| 40 // An implementation of WindowSizer::StateProvider that gets the last active | |
| 41 // and persistent state from the browser window and the user's profile. | |
| 42 class DefaultStateProvider : public WindowSizer::StateProvider { | |
| 43 public: | |
| 44 DefaultStateProvider(const std::string& app_name, const Browser* browser) | |
| 45 : app_name_(app_name), browser_(browser) { | |
| 46 } | |
| 47 | |
| 48 // Overridden from WindowSizer::StateProvider: | |
| 49 virtual bool GetPersistentState(gfx::Rect* bounds, | |
| 50 gfx::Rect* work_area) const { | |
| 51 DCHECK(bounds); | |
| 52 | |
| 53 if (!browser_ || !browser_->profile()->GetPrefs()) | |
| 54 return false; | |
| 55 | |
| 56 std::string window_name(chrome::GetWindowPlacementKey(browser_)); | |
| 57 const DictionaryValue* wp_pref = | |
| 58 browser_->profile()->GetPrefs()->GetDictionary(window_name.c_str()); | |
| 59 int top = 0, left = 0, bottom = 0, right = 0; | |
| 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 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 } | |
| 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(gfx::Rect* bounds) const { | |
| 86 // Applications are always restored with the same position. | |
| 87 if (!app_name_.empty()) | |
| 88 return false; | |
| 89 | |
| 90 // If a reference browser is set, use its window. Otherwise find last | |
| 91 // active. Panels are never used as reference browsers as panels are | |
| 92 // specially positioned. | |
| 93 BrowserWindow* window = NULL; | |
| 94 // Window may be null if browser is just starting up. | |
| 95 if (browser_ && browser_->window() && !browser_->window()->IsPanel()) { | |
| 96 window = browser_->window(); | |
| 97 } else { | |
| 98 BrowserList::const_reverse_iterator it = BrowserList::begin_last_active(); | |
| 99 BrowserList::const_reverse_iterator end = BrowserList::end_last_active(); | |
| 100 for (; (it != end); ++it) { | |
| 101 Browser* last_active = *it; | |
| 102 if (last_active && last_active->is_type_tabbed()) { | |
| 103 window = last_active->window(); | |
| 104 DCHECK(window); | |
| 105 break; | |
| 106 } | |
| 107 } | |
| 108 } | |
| 109 | |
| 110 if (window) { | |
| 111 *bounds = window->GetRestoredBounds(); | |
| 112 return true; | |
| 113 } | |
| 114 | |
| 115 return false; | |
| 116 } | |
| 117 | |
| 118 private: | |
| 119 std::string app_name_; | |
| 120 | |
| 121 // If set, is used as the reference browser for GetLastActiveWindowState. | |
| 122 const Browser* browser_; | |
| 123 DISALLOW_COPY_AND_ASSIGN(DefaultStateProvider); | |
| 124 }; | |
| 125 | |
| 126 /////////////////////////////////////////////////////////////////////////////// | |
| 127 // WindowSizer, public: | |
| 128 | |
| 129 // The number of pixels which are kept free top, left and right when a window | |
| 130 // gets positioned to its default location. | |
| 131 const int WindowSizer::kDesktopBorderSize = 16; | |
| 132 | |
| 133 WindowSizer::WindowSizer(StateProvider* state_provider, const Browser* browser) | |
| 134 : state_provider_(state_provider), | |
| 135 monitor_info_provider_(new DefaultMonitorInfoProvider), | |
| 136 browser_(browser) { | |
| 137 } | |
| 138 | |
| 139 WindowSizer::WindowSizer(StateProvider* state_provider, | |
| 140 MonitorInfoProvider* monitor_info_provider, | |
| 141 const Browser* browser) | |
| 142 : state_provider_(state_provider), | |
| 143 monitor_info_provider_(monitor_info_provider), | |
| 144 browser_(browser) { | |
| 145 } | |
| 146 | |
| 147 WindowSizer::~WindowSizer() { | |
| 148 } | |
| 149 | |
| 150 // static | |
| 151 void WindowSizer::GetBrowserWindowBounds(const std::string& app_name, | |
| 152 const gfx::Rect& specified_bounds, | |
| 153 const Browser* browser, | |
| 154 gfx::Rect* window_bounds) { | |
| 155 const WindowSizer sizer(new DefaultStateProvider(app_name, browser), browser); | |
| 156 sizer.DetermineWindowBounds(specified_bounds, window_bounds); | |
| 157 } | |
| 158 | |
| 159 /////////////////////////////////////////////////////////////////////////////// | |
| 160 // WindowSizer, private: | |
| 161 | |
| 162 void WindowSizer::DetermineWindowBounds(const gfx::Rect& specified_bounds, | |
| 163 gfx::Rect* bounds) const { | |
| 164 *bounds = specified_bounds; | |
| 165 if (bounds->IsEmpty()) { | |
| 166 if (GetBoundsIgnoringPreviousState(specified_bounds, bounds)) | |
| 167 return; | |
| 168 // See if there's saved placement information. | |
| 169 if (!GetLastWindowBounds(bounds)) { | |
| 170 if (!GetSavedWindowBounds(bounds)) { | |
| 171 // No saved placement, figure out some sensible default size based on | |
| 172 // the user's screen size. | |
| 173 GetDefaultWindowBounds(bounds); | |
| 174 } | |
| 175 } | |
| 176 } else { | |
| 177 // In case that there was a bound given we need to make sure that it is | |
| 178 // visible and fits on the screen. | |
| 179 // Find the size of the work area of the monitor that intersects the bounds | |
| 180 // of the anchor window. Note: AdjustBoundsToBeVisibleOnMonitorContaining | |
| 181 // does not exactly what we want: It makes only sure that "a minimal part" | |
| 182 // is visible on the screen. | |
| 183 gfx::Rect work_area = | |
| 184 monitor_info_provider_->GetMonitorWorkAreaMatching(*bounds); | |
| 185 // Resize so that it fits. | |
| 186 *bounds = bounds->AdjustToFit(work_area); | |
| 187 } | |
| 188 } | |
| 189 | |
| 190 bool WindowSizer::GetLastWindowBounds(gfx::Rect* bounds) const { | |
| 191 DCHECK(bounds); | |
| 192 if (!state_provider_.get() || | |
| 193 !state_provider_->GetLastActiveWindowState(bounds)) | |
| 194 return false; | |
| 195 gfx::Rect last_window_bounds = *bounds; | |
| 196 bounds->Offset(kWindowTilePixels, kWindowTilePixels); | |
| 197 AdjustBoundsToBeVisibleOnMonitorContaining(last_window_bounds, | |
| 198 gfx::Rect(), | |
| 199 bounds); | |
| 200 return true; | |
| 201 } | |
| 202 | |
| 203 bool WindowSizer::GetSavedWindowBounds(gfx::Rect* bounds) const { | |
| 204 DCHECK(bounds); | |
| 205 gfx::Rect saved_work_area; | |
| 206 if (!state_provider_.get() || | |
| 207 !state_provider_->GetPersistentState(bounds, &saved_work_area)) | |
| 208 return false; | |
| 209 AdjustBoundsToBeVisibleOnMonitorContaining(*bounds, saved_work_area, bounds); | |
| 210 return true; | |
| 211 } | |
| 212 | |
| 213 void WindowSizer::GetDefaultWindowBounds(gfx::Rect* default_bounds) const { | |
| 214 #if defined(USE_ASH) | |
| 215 // TODO(beng): insufficient but currently necessary. http://crbug.com/133312 | |
| 216 if (chrome::ShouldOpenAshOnStartup()) { | |
| 217 GetDefaultWindowBoundsAsh(default_bounds); | |
| 218 return; | |
| 219 } | |
| 220 #endif | |
| 221 DCHECK(default_bounds); | |
| 222 DCHECK(monitor_info_provider_.get()); | |
| 223 | |
| 224 gfx::Rect work_area = monitor_info_provider_->GetPrimaryDisplayWorkArea(); | |
| 225 | |
| 226 // The default size is either some reasonably wide width, or if the work | |
| 227 // area is narrower, then the work area width less some aesthetic padding. | |
| 228 int default_width = std::min(work_area.width() - 2 * kWindowTilePixels, 1050); | |
| 229 int default_height = work_area.height() - 2 * kWindowTilePixels; | |
| 230 | |
| 231 // For wider aspect ratio displays at higher resolutions, we might size the | |
| 232 // window narrower to allow two windows to easily be placed side-by-side. | |
| 233 gfx::Rect screen_size = monitor_info_provider_->GetPrimaryDisplayBounds(); | |
| 234 double width_to_height = | |
| 235 static_cast<double>(screen_size.width()) / screen_size.height(); | |
| 236 | |
| 237 // The least wide a screen can be to qualify for the halving described above. | |
| 238 static const int kMinScreenWidthForWindowHalving = 1600; | |
| 239 // We assume 16:9/10 is a fairly standard indicator of a wide aspect ratio | |
| 240 // computer display. | |
| 241 if (((width_to_height * 10) >= 16) && | |
| 242 work_area.width() > kMinScreenWidthForWindowHalving) { | |
| 243 // Halve the work area, subtracting aesthetic padding on either side. | |
| 244 // The padding is set so that two windows, side by side have | |
| 245 // kWindowTilePixels between screen edge and each other. | |
| 246 default_width = static_cast<int>(work_area.width() / 2. - | |
| 247 1.5 * kWindowTilePixels); | |
| 248 } | |
| 249 default_bounds->SetRect(kWindowTilePixels + work_area.x(), | |
| 250 kWindowTilePixels + work_area.y(), | |
| 251 default_width, default_height); | |
| 252 } | |
| 253 | |
| 254 void WindowSizer::AdjustBoundsToBeVisibleOnMonitorContaining( | |
| 255 const gfx::Rect& other_bounds, | |
| 256 const gfx::Rect& saved_work_area, | |
| 257 gfx::Rect* bounds) const { | |
| 258 DCHECK(bounds); | |
| 259 DCHECK(monitor_info_provider_.get()); | |
| 260 | |
| 261 // Find the size of the work area of the monitor that intersects the bounds | |
| 262 // of the anchor window. | |
| 263 gfx::Rect work_area = | |
| 264 monitor_info_provider_->GetMonitorWorkAreaMatching(other_bounds); | |
| 265 | |
| 266 // If height or width are 0, reset to the default size. | |
| 267 gfx::Rect default_bounds; | |
| 268 GetDefaultWindowBounds(&default_bounds); | |
| 269 if (bounds->height() <= 0) | |
| 270 bounds->set_height(default_bounds.height()); | |
| 271 if (bounds->width() <= 0) | |
| 272 bounds->set_width(default_bounds.width()); | |
| 273 | |
| 274 // Ensure the minimum height and width. | |
| 275 bounds->set_height(std::max(kMinVisibleHeight, bounds->height())); | |
| 276 bounds->set_width(std::max(kMinVisibleWidth, bounds->width())); | |
| 277 | |
| 278 // Ensure that the title bar is not above the work area. | |
| 279 if (bounds->y() < work_area.y()) | |
| 280 bounds->set_y(work_area.y()); | |
| 281 | |
| 282 // Reposition and resize the bounds if the saved_work_area is different from | |
| 283 // the current work area and the current work area doesn't completely contain | |
| 284 // the bounds. | |
| 285 if (!saved_work_area.IsEmpty() && | |
| 286 saved_work_area != work_area && | |
| 287 !work_area.Contains(*bounds)) { | |
| 288 bounds->set_width(std::min(bounds->width(), work_area.width())); | |
| 289 bounds->set_height(std::min(bounds->height(), work_area.height())); | |
| 290 bounds->set_x( | |
| 291 std::max(work_area.x(), | |
| 292 std::min(bounds->x(), work_area.right() - bounds->width()))); | |
| 293 bounds->set_y( | |
| 294 std::max(work_area.y(), | |
| 295 std::min(bounds->y(), work_area.bottom() - bounds->height()))); | |
| 296 } | |
| 297 | |
| 298 #if defined(OS_MACOSX) | |
| 299 // Limit the maximum height. On the Mac the sizer is on the | |
| 300 // bottom-right of the window, and a window cannot be moved "up" | |
| 301 // past the menubar. If the window is too tall you'll never be able | |
| 302 // to shrink it again. Windows does not have this limitation | |
| 303 // (e.g. can be resized from the top). | |
| 304 bounds->set_height(std::min(work_area.height(), bounds->height())); | |
| 305 | |
| 306 // On mac, we want to be aggressive about repositioning windows that are | |
| 307 // partially offscreen. If the window is partially offscreen horizontally, | |
| 308 // move it to be flush with the left edge of the work area. | |
| 309 if (bounds->x() < work_area.x() || bounds->right() > work_area.right()) | |
| 310 bounds->set_x(work_area.x()); | |
| 311 | |
| 312 // If the window is partially offscreen vertically, move it to be flush with | |
| 313 // the top of the work area. | |
| 314 if (bounds->y() < work_area.y() || bounds->bottom() > work_area.bottom()) | |
| 315 bounds->set_y(work_area.y()); | |
| 316 #else | |
| 317 // On non-Mac platforms, we are less aggressive about repositioning. Simply | |
| 318 // ensure that at least kMinVisibleWidth * kMinVisibleHeight is visible. | |
| 319 const int min_y = work_area.y() + kMinVisibleHeight - bounds->height(); | |
| 320 const int min_x = work_area.x() + kMinVisibleWidth - bounds->width(); | |
| 321 const int max_y = work_area.bottom() - kMinVisibleHeight; | |
| 322 const int max_x = work_area.right() - kMinVisibleWidth; | |
| 323 bounds->set_y(std::max(min_y, std::min(max_y, bounds->y()))); | |
| 324 bounds->set_x(std::max(min_x, std::min(max_x, bounds->x()))); | |
| 325 #endif // defined(OS_MACOSX) | |
| 326 } | |
| 327 | |
| 328 bool WindowSizer::GetBoundsIgnoringPreviousState( | |
| 329 const gfx::Rect& specified_bounds, | |
| 330 gfx::Rect* bounds) const { | |
| 331 #if defined(USE_ASH) | |
| 332 // TODO(beng): insufficient but currently necessary. http://crbug.com/133312 | |
| 333 if (chrome::ShouldOpenAshOnStartup()) | |
| 334 return GetBoundsIgnoringPreviousStateAsh(specified_bounds, bounds); | |
| 335 #endif | |
| 336 return false; | |
| 337 } | |
| OLD | NEW |