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