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/views/frame/browser_view_layout.h" 6 7 #include "chrome/browser/sidebar/sidebar_manager.h" 8 #include "chrome/browser/ui/find_bar/find_bar.h" 9 #include "chrome/browser/ui/find_bar/find_bar_controller.h" 10 #include "chrome/browser/ui/view_ids.h" 11 #include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h" 12 #include "chrome/browser/ui/views/download/download_shelf_view.h" 13 #include "chrome/browser/ui/views/frame/browser_frame.h" 14 #include "chrome/browser/ui/views/frame/browser_view.h" 15 #include "chrome/browser/ui/views/frame/contents_container.h" 16 #include "chrome/browser/ui/views/infobars/infobar_container_view.h" 17 #include "chrome/browser/ui/views/tab_contents/tab_contents_container.h" 18 #include "chrome/browser/ui/views/tabs/abstract_tab_strip_view.h" 19 #include "chrome/browser/ui/views/toolbar_view.h" 20 #include "ui/gfx/point.h" 21 #include "ui/gfx/scrollbar_size.h" 22 #include "ui/gfx/size.h" 23 #include "views/controls/single_split_view.h" 24 #include "views/window/window.h" 25 26 #if defined(OS_LINUX) 27 #include "views/window/hit_test.h" 28 #endif 29 30 namespace { 31 32 // The visible height of the shadow above the tabs. Clicks in this area are 33 // treated as clicks to the frame, rather than clicks to the tab. 34 const int kTabShadowSize = 2; 35 // The vertical overlap between the TabStrip and the Toolbar. 36 const int kToolbarTabStripVerticalOverlap = 3; 37 38 } // namespace 39 40 //////////////////////////////////////////////////////////////////////////////// 41 // BrowserViewLayout, public: 42 43 BrowserViewLayout::BrowserViewLayout() 44 : tabstrip_(NULL), 45 toolbar_(NULL), 46 contents_split_(NULL), 47 contents_container_(NULL), 48 infobar_container_(NULL), 49 download_shelf_(NULL), 50 active_bookmark_bar_(NULL), 51 browser_view_(NULL), 52 find_bar_y_(0) { 53 } 54 55 BrowserViewLayout::~BrowserViewLayout() { 56 } 57 58 gfx::Size BrowserViewLayout::GetMinimumSize() { 59 // TODO(noname): In theory the tabstrip width should probably be 60 // (OTR + tabstrip + caption buttons) width. 61 gfx::Size tabstrip_size( 62 browser()->SupportsWindowFeature(Browser::FEATURE_TABSTRIP) ? 63 tabstrip_->GetMinimumSize() : gfx::Size()); 64 gfx::Size toolbar_size( 65 (browser()->SupportsWindowFeature(Browser::FEATURE_TOOLBAR) || 66 browser()->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR)) ? 67 toolbar_->GetMinimumSize() : gfx::Size()); 68 if (tabstrip_size.height() && toolbar_size.height()) 69 toolbar_size.Enlarge(0, -kToolbarTabStripVerticalOverlap); 70 gfx::Size bookmark_bar_size; 71 if (active_bookmark_bar_ && 72 browser()->SupportsWindowFeature(Browser::FEATURE_BOOKMARKBAR)) { 73 bookmark_bar_size = active_bookmark_bar_->GetMinimumSize(); 74 bookmark_bar_size.Enlarge(0, 75 -(views::NonClientFrameView::kClientEdgeThickness + 76 active_bookmark_bar_->GetToolbarOverlap(true))); 77 } 78 gfx::Size contents_size(contents_split_->GetMinimumSize()); 79 80 int min_height = tabstrip_size.height() + toolbar_size.height() + 81 bookmark_bar_size.height() + contents_size.height(); 82 int widths[] = { tabstrip_size.width(), toolbar_size.width(), 83 bookmark_bar_size.width(), contents_size.width() }; 84 int min_width = *std::max_element(&widths[0], &widths[arraysize(widths)]); 85 return gfx::Size(min_width, min_height); 86 } 87 88 gfx::Rect BrowserViewLayout::GetFindBarBoundingBox() const { 89 // This function returns the area the Find Bar can be laid out 90 // within. This basically implies the "user-perceived content 91 // area" of the browser window excluding the vertical 92 // scrollbar. This is not quite so straightforward as positioning 93 // based on the TabContentsContainer since the BookmarkBarView may 94 // be visible but not persistent (in the New Tab case) and we 95 // position the Find Bar over the top of it in that case since the 96 // BookmarkBarView is not _visually_ connected to the Toolbar. 97 98 // First determine the bounding box of the content area in Widget 99 // coordinates. 100 gfx::Rect bounding_box(contents_container_->bounds()); 101 102 gfx::Point topleft; 103 views::View::ConvertPointToWidget(contents_container_, &topleft); 104 bounding_box.set_origin(topleft); 105 106 // Adjust the position and size of the bounding box by the find bar offset 107 // calculated during the last Layout. 108 int height_delta = find_bar_y_ - bounding_box.y(); 109 bounding_box.set_y(find_bar_y_); 110 bounding_box.set_height(std::max(0, bounding_box.height() + height_delta)); 111 112 // Finally decrease the width of the bounding box by the width of 113 // the vertical scroll bar. 114 int scrollbar_width = gfx::scrollbar_size(); 115 bounding_box.set_width(std::max(0, bounding_box.width() - scrollbar_width)); 116 if (base::i18n::IsRTL()) 117 bounding_box.set_x(bounding_box.x() + scrollbar_width); 118 119 return bounding_box; 120 } 121 122 bool BrowserViewLayout::IsPositionInWindowCaption( 123 const gfx::Point& point) { 124 gfx::Point tabstrip_point(point); 125 views::View::ConvertPointToView(browser_view_, tabstrip_, &tabstrip_point); 126 return tabstrip_->IsPositionInWindowCaption(tabstrip_point); 127 } 128 129 int BrowserViewLayout::NonClientHitTest( 130 const gfx::Point& point) { 131 // Since the TabStrip only renders in some parts of the top of the window, 132 // the un-obscured area is considered to be part of the non-client caption 133 // area of the window. So we need to treat hit-tests in these regions as 134 // hit-tests of the titlebar. 135 136 views::View* parent = browser_view_->parent(); 137 138 gfx::Point point_in_browser_view_coords(point); 139 views::View::ConvertPointToView( 140 parent, browser_view_, &point_in_browser_view_coords); 141 142 // Determine if the TabStrip exists and is capable of being clicked on. We 143 // might be a popup window without a TabStrip. 144 if (browser_view_->IsTabStripVisible()) { 145 // See if the mouse pointer is within the bounds of the TabStrip. 146 gfx::Point point_in_tabstrip_coords(point); 147 views::View::ConvertPointToView(parent, tabstrip_, 148 &point_in_tabstrip_coords); 149 if (tabstrip_->HitTest(point_in_tabstrip_coords)) { 150 if (tabstrip_->IsPositionInWindowCaption(point_in_tabstrip_coords)) 151 return HTCAPTION; 152 return HTCLIENT; 153 } 154 155 // The top few pixels of the TabStrip are a drop-shadow - as we're pretty 156 // starved of dragable area, let's give it to window dragging (this also 157 // makes sense visually). 158 if (!browser_view_->IsMaximized() && 159 (point_in_browser_view_coords.y() < 160 (tabstrip_->y() + kTabShadowSize))) { 161 // We return HTNOWHERE as this is a signal to our containing 162 // NonClientView that it should figure out what the correct hit-test 163 // code is given the mouse position... 164 return HTNOWHERE; 165 } 166 } 167 168 // If the point's y coordinate is below the top of the toolbar and otherwise 169 // within the bounds of this view, the point is considered to be within the 170 // client area. 171 gfx::Rect bv_bounds = browser_view_->bounds(); 172 bv_bounds.Offset(0, toolbar_->y()); 173 bv_bounds.set_height(bv_bounds.height() - toolbar_->y()); 174 if (bv_bounds.Contains(point)) 175 return HTCLIENT; 176 177 // If the point's y coordinate is above the top of the toolbar, but not in 178 // the tabstrip (per previous checking in this function), then we consider it 179 // in the window caption (e.g. the area to the right of the tabstrip 180 // underneath the window controls). However, note that we DO NOT return 181 // HTCAPTION here, because when the window is maximized the window controls 182 // will fall into this space (since the BrowserView is sized to entire size 183 // of the window at that point), and the HTCAPTION value will cause the 184 // window controls not to work. So we return HTNOWHERE so that the caller 185 // will hit-test the window controls before finally falling back to 186 // HTCAPTION. 187 bv_bounds = browser_view_->bounds(); 188 bv_bounds.set_height(toolbar_->y()); 189 if (bv_bounds.Contains(point)) 190 return HTNOWHERE; 191 192 // If the point is somewhere else, delegate to the default implementation. 193 return browser_view_->views::ClientView::NonClientHitTest(point); 194 } 195 196 ////////////////////////////////////////////////////////////////////////////// 197 // BrowserViewLayout, views::LayoutManager implementation: 198 199 void BrowserViewLayout::Installed(views::View* host) { 200 toolbar_ = NULL; 201 contents_split_ = NULL; 202 contents_container_ = NULL; 203 infobar_container_ = NULL; 204 download_shelf_ = NULL; 205 active_bookmark_bar_ = NULL; 206 tabstrip_ = NULL; 207 browser_view_ = static_cast<BrowserView*>(host); 208 } 209 210 void BrowserViewLayout::Uninstalled(views::View* host) {} 211 212 void BrowserViewLayout::ViewAdded(views::View* host, views::View* view) { 213 switch (view->GetID()) { 214 case VIEW_ID_CONTENTS_SPLIT: { 215 contents_split_ = static_cast<views::SingleSplitView*>(view); 216 // We're installed as the LayoutManager before BrowserView creates the 217 // contents, so we have to set contents_container_ here rather than in 218 // Installed. 219 contents_container_ = browser_view_->contents_; 220 break; 221 } 222 case VIEW_ID_INFO_BAR_CONTAINER: 223 infobar_container_ = view; 224 break; 225 case VIEW_ID_DOWNLOAD_SHELF: 226 download_shelf_ = static_cast<DownloadShelfView*>(view); 227 break; 228 case VIEW_ID_BOOKMARK_BAR: 229 active_bookmark_bar_ = static_cast<BookmarkBarView*>(view); 230 break; 231 case VIEW_ID_TOOLBAR: 232 toolbar_ = static_cast<ToolbarView*>(view); 233 break; 234 case VIEW_ID_TAB_STRIP: 235 tabstrip_ = static_cast<AbstractTabStripView*>(view); 236 break; 237 } 238 } 239 240 void BrowserViewLayout::ViewRemoved(views::View* host, views::View* view) { 241 switch (view->GetID()) { 242 case VIEW_ID_BOOKMARK_BAR: 243 active_bookmark_bar_ = NULL; 244 break; 245 } 246 } 247 248 void BrowserViewLayout::Layout(views::View* host) { 249 vertical_layout_rect_ = browser_view_->GetLocalBounds(); 250 int top = LayoutTabStrip(); 251 if (browser_view_->IsTabStripVisible() && !browser_view_->UseVerticalTabs()) { 252 tabstrip_->SetBackgroundOffset(gfx::Point( 253 tabstrip_->GetMirroredX() + browser_view_->GetMirroredX(), 254 browser_view_->frame()->GetHorizontalTabStripVerticalOffset(false))); 255 } 256 top = LayoutToolbar(top); 257 top = LayoutBookmarkAndInfoBars(top); 258 int bottom = LayoutDownloadShelf(browser_view_->height()); 259 int active_top_margin = GetTopMarginForActiveContent(); 260 top -= active_top_margin; 261 contents_container_->SetActiveTopMargin(active_top_margin); 262 LayoutTabContents(top, bottom); 263 // This must be done _after_ we lay out the TabContents since this 264 // code calls back into us to find the bounding box the find bar 265 // must be laid out within, and that code depends on the 266 // TabContentsContainer's bounds being up to date. 267 if (browser()->HasFindBarController()) { 268 browser()->GetFindBarController()->find_bar()->MoveWindowIfNecessary( 269 gfx::Rect(), true); 270 } 271 } 272 273 // Return the preferred size which is the size required to give each 274 // children their respective preferred size. 275 gfx::Size BrowserViewLayout::GetPreferredSize(views::View* host) { 276 return gfx::Size(); 277 } 278 279 ////////////////////////////////////////////////////////////////////////////// 280 // BrowserViewLayout, private: 281 282 Browser* BrowserViewLayout::browser() { 283 return browser_view_->browser(); 284 } 285 286 const Browser* BrowserViewLayout::browser() const { 287 return browser_view_->browser(); 288 } 289 290 int BrowserViewLayout::LayoutTabStrip() { 291 if (!browser_view_->IsTabStripVisible()) { 292 tabstrip_->SetVisible(false); 293 tabstrip_->SetBounds(0, 0, 0, 0); 294 return 0; 295 } 296 297 gfx::Rect tabstrip_bounds( 298 browser_view_->frame()->GetBoundsForTabStrip(tabstrip_)); 299 gfx::Point tabstrip_origin(tabstrip_bounds.origin()); 300 views::View::ConvertPointToView(browser_view_->parent(), browser_view_, 301 &tabstrip_origin); 302 tabstrip_bounds.set_origin(tabstrip_origin); 303 304 if (browser_view_->UseVerticalTabs()) 305 vertical_layout_rect_.Inset(tabstrip_bounds.width(), 0, 0, 0); 306 307 tabstrip_->SetVisible(true); 308 tabstrip_->SetBoundsRect(tabstrip_bounds); 309 return browser_view_->UseVerticalTabs() ? 310 tabstrip_bounds.y() : tabstrip_bounds.bottom(); 311 } 312 313 int BrowserViewLayout::LayoutToolbar(int top) { 314 int browser_view_width = vertical_layout_rect_.width(); 315 bool visible = browser_view_->IsToolbarVisible(); 316 toolbar_->location_bar()->SetFocusable(visible); 317 int y = top; 318 if (!browser_view_->UseVerticalTabs()) { 319 y -= ((visible && browser_view_->IsTabStripVisible()) ? 320 kToolbarTabStripVerticalOverlap : 0); 321 } 322 int height = visible ? toolbar_->GetPreferredSize().height() : 0; 323 toolbar_->SetVisible(visible); 324 toolbar_->SetBounds(vertical_layout_rect_.x(), y, browser_view_width, height); 325 return y + height; 326 } 327 328 int BrowserViewLayout::LayoutBookmarkAndInfoBars(int top) { 329 find_bar_y_ = top + browser_view_->y() - 1; 330 if (active_bookmark_bar_) { 331 // If we're showing the Bookmark bar in detached style, then we 332 // need to show any Info bar _above_ the Bookmark bar, since the 333 // Bookmark bar is styled to look like it's part of the page. 334 if (active_bookmark_bar_->IsDetached()) 335 return LayoutBookmarkBar(LayoutInfoBar(top)); 336 // Otherwise, Bookmark bar first, Info bar second. 337 top = std::max(toolbar_->bounds().bottom(), LayoutBookmarkBar(top)); 338 } 339 find_bar_y_ = top + browser_view_->y() - 1; 340 return LayoutInfoBar(top); 341 } 342 343 int BrowserViewLayout::LayoutBookmarkBar(int top) { 344 DCHECK(active_bookmark_bar_); 345 int y = top; 346 if (!browser_view_->IsBookmarkBarVisible()) { 347 active_bookmark_bar_->SetVisible(false); 348 active_bookmark_bar_->SetBounds(0, y, browser_view_->width(), 0); 349 return y; 350 } 351 352 active_bookmark_bar_->set_infobar_visible(InfobarVisible()); 353 int bookmark_bar_height = active_bookmark_bar_->GetPreferredSize().height(); 354 y -= views::NonClientFrameView::kClientEdgeThickness + 355 active_bookmark_bar_->GetToolbarOverlap(false); 356 active_bookmark_bar_->SetVisible(true); 357 active_bookmark_bar_->SetBounds(vertical_layout_rect_.x(), y, 358 vertical_layout_rect_.width(), 359 bookmark_bar_height); 360 return y + bookmark_bar_height; 361 } 362 363 int BrowserViewLayout::LayoutInfoBar(int top) { 364 // Raise the |infobar_container_| by its vertical overlap. 365 infobar_container_->SetVisible(InfobarVisible()); 366 int height; 367 int overlapped_top = top - 368 static_cast<InfoBarContainerView*>(infobar_container_)-> 369 GetVerticalOverlap(&height); 370 infobar_container_->SetBounds(vertical_layout_rect_.x(), 371 overlapped_top, 372 vertical_layout_rect_.width(), 373 height); 374 return overlapped_top + height; 375 } 376 377 // |browser_reserved_rect| is in browser_view_ coordinates. 378 // |future_source_bounds| is in |source|'s parent coordinates. 379 // |future_parent_offset| is required, since parent view is not moved yet. 380 // Note that |future_parent_offset| is relative to browser_view_, not to 381 // the parent view. 382 void BrowserViewLayout::UpdateReservedContentsRect( 383 const gfx::Rect& browser_reserved_rect, 384 TabContentsContainer* source, 385 const gfx::Rect& future_source_bounds, 386 const gfx::Point& future_parent_offset) { 387 gfx::Point resize_corner_origin(browser_reserved_rect.origin()); 388 // Convert |resize_corner_origin| from browser_view_ to source's parent 389 // coordinates. 390 views::View::ConvertPointToView(browser_view_, source->parent(), 391 &resize_corner_origin); 392 // Create |reserved_rect| in source's parent coordinates. 393 gfx::Rect reserved_rect(resize_corner_origin, browser_reserved_rect.size()); 394 // Apply source's parent future offset to it. 395 reserved_rect.Offset(-future_parent_offset.x(), -future_parent_offset.y()); 396 if (future_source_bounds.Intersects(reserved_rect)) { 397 // |source| is not properly positioned yet to use ConvertPointToView, 398 // so convert it into |source|'s coordinates manually. 399 reserved_rect.Offset(-future_source_bounds.x(), -future_source_bounds.y()); 400 } else { 401 reserved_rect = gfx::Rect(); 402 } 403 404 source->SetReservedContentsRect(reserved_rect); 405 } 406 407 void BrowserViewLayout::LayoutTabContents(int top, int bottom) { 408 // The ultimate idea is to calcualte bounds and reserved areas for all 409 // contents views first and then resize them all, so every view 410 // (and its contents) is resized and laid out only once. 411 412 // The views hierarcy (see browser_view.h for more details): 413 // 1) Sidebar is not allowed: 414 // contents_split_ -> [contents_container_ | devtools] 415 // 2) Sidebar is allowed: 416 // contents_split_ -> 417 // [sidebar_split -> [contents_container_ | sidebar]] | devtools 418 419 gfx::Rect sidebar_split_bounds; 420 gfx::Rect contents_bounds; 421 gfx::Rect sidebar_bounds; 422 gfx::Rect devtools_bounds; 423 424 gfx::Rect contents_split_bounds(vertical_layout_rect_.x(), top, 425 vertical_layout_rect_.width(), 426 std::max(0, bottom - top)); 427 contents_split_->CalculateChildrenBounds( 428 contents_split_bounds, &sidebar_split_bounds, &devtools_bounds); 429 gfx::Point contents_split_offset( 430 contents_split_bounds.x() - contents_split_->bounds().x(), 431 contents_split_bounds.y() - contents_split_->bounds().y()); 432 gfx::Point sidebar_split_offset(contents_split_offset); 433 sidebar_split_offset.Offset(sidebar_split_bounds.x(), 434 sidebar_split_bounds.y()); 435 436 views::SingleSplitView* sidebar_split = browser_view_->sidebar_split_; 437 if (sidebar_split) { 438 DCHECK(sidebar_split == contents_split_->GetChildViewAt(0)); 439 sidebar_split->CalculateChildrenBounds( 440 sidebar_split_bounds, &contents_bounds, &sidebar_bounds); 441 } else { 442 contents_bounds = sidebar_split_bounds; 443 } 444 445 // Layout resize corner, sidebar mini tabs and calculate reserved contents 446 // rects here as all contents view bounds are already determined, but not yet 447 // set at this point, so contents will be laid out once at most. 448 // TODO(alekseys): layout sidebar minitabs and adjust reserved rect 449 // accordingly. 450 gfx::Rect browser_reserved_rect; 451 if (!browser_view_->frame_->GetWindow()->IsMaximized() && 452 !browser_view_->frame_->GetWindow()->IsFullscreen()) { 453 gfx::Size resize_corner_size = browser_view_->GetResizeCornerSize(); 454 if (!resize_corner_size.IsEmpty()) { 455 gfx::Rect bounds = browser_view_->GetContentsBounds(); 456 gfx::Point resize_corner_origin( 457 bounds.right() - resize_corner_size.width(), 458 bounds.bottom() - resize_corner_size.height()); 459 browser_reserved_rect = 460 gfx::Rect(resize_corner_origin, resize_corner_size); 461 } 462 } 463 464 UpdateReservedContentsRect(browser_reserved_rect, 465 browser_view_->contents_container_, 466 contents_bounds, 467 sidebar_split_offset); 468 if (sidebar_split) { 469 UpdateReservedContentsRect(browser_reserved_rect, 470 browser_view_->sidebar_container_, 471 sidebar_bounds, 472 sidebar_split_offset); 473 } 474 UpdateReservedContentsRect(browser_reserved_rect, 475 browser_view_->devtools_container_, 476 devtools_bounds, 477 contents_split_offset); 478 479 // Now it's safe to actually resize all contents views in the hierarchy. 480 contents_split_->SetBoundsRect(contents_split_bounds); 481 if (sidebar_split) 482 sidebar_split->SetBoundsRect(sidebar_split_bounds); 483 } 484 485 int BrowserViewLayout::GetTopMarginForActiveContent() { 486 if (!active_bookmark_bar_ || !browser_view_->IsBookmarkBarVisible() || 487 !active_bookmark_bar_->IsDetached()) { 488 return 0; 489 } 490 491 if (contents_split_->GetChildViewAt(1) && 492 contents_split_->GetChildViewAt(1)->IsVisible()) 493 return 0; 494 495 if (SidebarManager::IsSidebarAllowed()) { 496 views::View* sidebar_split = contents_split_->GetChildViewAt(0); 497 if (sidebar_split->GetChildViewAt(1) && 498 sidebar_split->GetChildViewAt(1)->IsVisible()) 499 return 0; 500 } 501 502 // Adjust for separator. 503 return active_bookmark_bar_->height() - 504 views::NonClientFrameView::kClientEdgeThickness; 505 } 506 507 int BrowserViewLayout::LayoutDownloadShelf(int bottom) { 508 // Re-layout the shelf either if it is visible or if it's close animation 509 // is currently running. 510 if (browser_view_->IsDownloadShelfVisible() || 511 (download_shelf_ && download_shelf_->IsClosing())) { 512 bool visible = browser()->SupportsWindowFeature( 513 Browser::FEATURE_DOWNLOADSHELF); 514 DCHECK(download_shelf_); 515 int height = visible ? download_shelf_->GetPreferredSize().height() : 0; 516 download_shelf_->SetVisible(visible); 517 download_shelf_->SetBounds(vertical_layout_rect_.x(), bottom - height, 518 vertical_layout_rect_.width(), height); 519 download_shelf_->Layout(); 520 bottom -= height; 521 } 522 return bottom; 523 } 524 525 bool BrowserViewLayout::InfobarVisible() const { 526 // NOTE: Can't check if the size IsEmpty() since it's always 0-width. 527 return browser()->SupportsWindowFeature(Browser::FEATURE_INFOBAR) && 528 (infobar_container_->GetPreferredSize().height() != 0); 529 } 530