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