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
BrowserViewLayout()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
~BrowserViewLayout()55 BrowserViewLayout::~BrowserViewLayout() {
56 }
57
GetMinimumSize()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
GetFindBarBoundingBox() const88 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
IsPositionInWindowCaption(const gfx::Point & point)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
NonClientHitTest(const gfx::Point & point)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
Installed(views::View * host)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
Uninstalled(views::View * host)210 void BrowserViewLayout::Uninstalled(views::View* host) {}
211
ViewAdded(views::View * host,views::View * view)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
ViewRemoved(views::View * host,views::View * view)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
Layout(views::View * host)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.
GetPreferredSize(views::View * host)275 gfx::Size BrowserViewLayout::GetPreferredSize(views::View* host) {
276 return gfx::Size();
277 }
278
279 //////////////////////////////////////////////////////////////////////////////
280 // BrowserViewLayout, private:
281
browser()282 Browser* BrowserViewLayout::browser() {
283 return browser_view_->browser();
284 }
285
browser() const286 const Browser* BrowserViewLayout::browser() const {
287 return browser_view_->browser();
288 }
289
LayoutTabStrip()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
LayoutToolbar(int top)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
LayoutBookmarkAndInfoBars(int top)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
LayoutBookmarkBar(int top)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
LayoutInfoBar(int top)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.
UpdateReservedContentsRect(const gfx::Rect & browser_reserved_rect,TabContentsContainer * source,const gfx::Rect & future_source_bounds,const gfx::Point & future_parent_offset)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
LayoutTabContents(int top,int bottom)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
GetTopMarginForActiveContent()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
LayoutDownloadShelf(int bottom)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
InfobarVisible() const525 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