opaque_browser_frame_view.cc revision d0247b1b59f9c528cb6df88b4f2b9afaf80d181e
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/views/frame/opaque_browser_frame_view.h"
6
7#include <algorithm>
8#include <string>
9
10#include "base/command_line.h"
11#include "base/compiler_specific.h"
12#include "base/prefs/pref_service.h"
13#include "base/strings/utf_string_conversions.h"
14#include "chrome/browser/chrome_notification_types.h"
15#include "chrome/browser/themes/theme_properties.h"
16#include "chrome/browser/ui/views/avatar_label.h"
17#include "chrome/browser/ui/views/avatar_menu_button.h"
18#include "chrome/browser/ui/views/frame/browser_frame.h"
19#include "chrome/browser/ui/views/frame/browser_view.h"
20#include "chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h"
21#include "chrome/browser/ui/views/frame/opaque_browser_frame_view_platform_specific.h"
22#include "chrome/browser/ui/views/tab_icon_view.h"
23#include "chrome/browser/ui/views/tabs/tab_strip.h"
24#include "chrome/browser/ui/views/toolbar_view.h"
25#include "chrome/common/chrome_switches.h"
26#include "chrome/common/pref_names.h"
27#include "content/public/browser/notification_service.h"
28#include "content/public/browser/web_contents.h"
29#include "grit/chromium_strings.h"
30#include "grit/generated_resources.h"
31#include "grit/theme_resources.h"
32#include "grit/ui_resources.h"
33#include "ui/base/accessibility/accessible_view_state.h"
34#include "ui/base/hit_test.h"
35#include "ui/base/l10n/l10n_util.h"
36#include "ui/base/resource/resource_bundle.h"
37#include "ui/base/theme_provider.h"
38#include "ui/gfx/canvas.h"
39#include "ui/gfx/font.h"
40#include "ui/gfx/image/image.h"
41#include "ui/gfx/image/image_skia.h"
42#include "ui/gfx/path.h"
43#include "ui/views/controls/button/image_button.h"
44#include "ui/views/controls/image_view.h"
45#include "ui/views/controls/label.h"
46#include "ui/views/layout/layout_constants.h"
47#include "ui/views/widget/root_view.h"
48#include "ui/views/window/frame_background.h"
49#include "ui/views/window/window_shape.h"
50
51using content::WebContents;
52
53namespace {
54
55// While resize areas on Windows are normally the same size as the window
56// borders, our top area is shrunk by 1 px to make it easier to move the window
57// around with our thinner top grabbable strip.  (Incidentally, our side and
58// bottom resize areas don't match the frame border thickness either -- they
59// span the whole nonclient area, so there's no "dead zone" for the mouse.)
60const int kTopResizeAdjust = 1;
61
62// In the window corners, the resize areas don't actually expand bigger, but the
63// 16 px at the end of each edge triggers diagonal resizing.
64const int kResizeAreaCornerSize = 16;
65
66// The content left/right images have a shadow built into them.
67const int kContentEdgeShadowThickness = 2;
68
69// The icon never shrinks below 16 px on a side.
70const int kIconMinimumSize = 16;
71
72// The top 3 px of the tabstrip is shadow; in maximized mode we push this off
73// the top of the screen so the tabs appear flush against the screen edge.
74const int kTabstripTopShadowThickness = 3;
75
76// Converts |bounds| from |src|'s coordinate system to |dst|, and checks if
77// |pt| is contained within.
78bool ConvertedContainsCheck(gfx::Rect bounds, const views::View* src,
79                            const views::View* dst, const gfx::Point& pt) {
80  DCHECK(src);
81  DCHECK(dst);
82  gfx::Point origin(bounds.origin());
83  views::View::ConvertPointToTarget(src, dst, &origin);
84  bounds.set_origin(origin);
85  return bounds.Contains(pt);
86}
87
88}  // namespace
89
90///////////////////////////////////////////////////////////////////////////////
91// OpaqueBrowserFrameView, public:
92
93OpaqueBrowserFrameView::OpaqueBrowserFrameView(BrowserFrame* frame,
94                                               BrowserView* browser_view)
95    : BrowserNonClientFrameView(frame, browser_view),
96      layout_(new OpaqueBrowserFrameViewLayout(this)),
97      minimize_button_(NULL),
98      maximize_button_(NULL),
99      restore_button_(NULL),
100      close_button_(NULL),
101      window_icon_(NULL),
102      window_title_(NULL),
103      frame_background_(new views::FrameBackground()) {
104  SetLayoutManager(layout_);
105
106  if (OpaqueBrowserFrameViewLayout::ShouldAddDefaultCaptionButtons()) {
107    minimize_button_ = InitWindowCaptionButton(IDR_MINIMIZE,
108                                               IDR_MINIMIZE_H,
109                                               IDR_MINIMIZE_P,
110                                               IDR_MINIMIZE_BUTTON_MASK,
111                                               IDS_ACCNAME_MINIMIZE,
112                                               VIEW_ID_MINIMIZE_BUTTON);
113    maximize_button_ = InitWindowCaptionButton(IDR_MAXIMIZE,
114                                               IDR_MAXIMIZE_H,
115                                               IDR_MAXIMIZE_P,
116                                               IDR_MAXIMIZE_BUTTON_MASK,
117                                               IDS_ACCNAME_MAXIMIZE,
118                                               VIEW_ID_MAXIMIZE_BUTTON);
119    restore_button_ = InitWindowCaptionButton(IDR_RESTORE,
120                                              IDR_RESTORE_H,
121                                              IDR_RESTORE_P,
122                                              IDR_RESTORE_BUTTON_MASK,
123                                              IDS_ACCNAME_RESTORE,
124                                              VIEW_ID_RESTORE_BUTTON);
125    close_button_ = InitWindowCaptionButton(IDR_CLOSE,
126                                            IDR_CLOSE_H,
127                                            IDR_CLOSE_P,
128                                            IDR_CLOSE_BUTTON_MASK,
129                                            IDS_ACCNAME_CLOSE,
130                                            VIEW_ID_CLOSE_BUTTON);
131  }
132
133  // Initializing the TabIconView is expensive, so only do it if we need to.
134  if (browser_view->ShouldShowWindowIcon()) {
135    window_icon_ = new TabIconView(this);
136    window_icon_->set_is_light(true);
137    window_icon_->set_id(VIEW_ID_WINDOW_ICON);
138    AddChildView(window_icon_);
139    window_icon_->Update();
140  }
141
142  window_title_ = new views::Label(browser_view->GetWindowTitle(),
143                                   BrowserFrame::GetTitleFont());
144  window_title_->SetVisible(browser_view->ShouldShowWindowTitle());
145  window_title_->SetEnabledColor(SK_ColorWHITE);
146  // TODO(msw): Use a transparent background color as a workaround to use the
147  // gfx::Canvas::NO_SUBPIXEL_RENDERING flag and avoid some visual artifacts.
148  window_title_->SetBackgroundColor(0x00000000);
149  window_title_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
150  window_title_->set_id(VIEW_ID_WINDOW_TITLE);
151  AddChildView(window_title_);
152
153  UpdateAvatarInfo();
154  if (!browser_view->IsOffTheRecord()) {
155    registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED,
156                   content::NotificationService::AllSources());
157  }
158
159  platform_observer_.reset(
160      OpaqueBrowserFrameViewPlatformSpecific::Create(this, layout_));
161}
162
163OpaqueBrowserFrameView::~OpaqueBrowserFrameView() {
164}
165
166///////////////////////////////////////////////////////////////////////////////
167// OpaqueBrowserFrameView, BrowserNonClientFrameView implementation:
168
169gfx::Rect OpaqueBrowserFrameView::GetBoundsForTabStrip(
170    views::View* tabstrip) const {
171  if (!tabstrip)
172    return gfx::Rect();
173
174  return layout_->GetBoundsForTabStrip(tabstrip->GetPreferredSize(), width());
175}
176
177BrowserNonClientFrameView::TabStripInsets
178OpaqueBrowserFrameView::GetTabStripInsets(bool restored) const {
179  // TODO: include OTR and caption.
180  return TabStripInsets(layout_->GetTabStripInsetsTop(restored), 0, 0);
181}
182
183int OpaqueBrowserFrameView::GetThemeBackgroundXInset() const {
184  return 0;
185}
186
187void OpaqueBrowserFrameView::UpdateThrobber(bool running) {
188  if (window_icon_)
189    window_icon_->Update();
190}
191
192gfx::Size OpaqueBrowserFrameView::GetMinimumSize() {
193  return layout_->GetMinimumSize(width());
194}
195
196///////////////////////////////////////////////////////////////////////////////
197// OpaqueBrowserFrameView, views::NonClientFrameView implementation:
198
199gfx::Rect OpaqueBrowserFrameView::GetBoundsForClientView() const {
200  return layout_->client_view_bounds();
201}
202
203gfx::Rect OpaqueBrowserFrameView::GetWindowBoundsForClientBounds(
204    const gfx::Rect& client_bounds) const {
205  return layout_->GetWindowBoundsForClientBounds(client_bounds);
206}
207
208int OpaqueBrowserFrameView::NonClientHitTest(const gfx::Point& point) {
209  if (!bounds().Contains(point))
210    return HTNOWHERE;
211
212  // See if the point is within the avatar menu button or within the avatar
213  // label.
214  if ((avatar_button() &&
215       avatar_button()->GetMirroredBounds().Contains(point)) ||
216      (avatar_label() && avatar_label()->GetMirroredBounds().Contains(point)))
217    return HTCLIENT;
218
219  int frame_component = frame()->client_view()->NonClientHitTest(point);
220
221  // See if we're in the sysmenu region.  We still have to check the tabstrip
222  // first so that clicks in a tab don't get treated as sysmenu clicks.
223  gfx::Rect sysmenu_rect(IconBounds());
224  // In maximized mode we extend the rect to the screen corner to take advantage
225  // of Fitts' Law.
226  if (frame()->IsMaximized())
227    sysmenu_rect.SetRect(0, 0, sysmenu_rect.right(), sysmenu_rect.bottom());
228  sysmenu_rect.set_x(GetMirroredXForRect(sysmenu_rect));
229  if (sysmenu_rect.Contains(point))
230    return (frame_component == HTCLIENT) ? HTCLIENT : HTSYSMENU;
231
232  if (frame_component != HTNOWHERE)
233    return frame_component;
234
235  // Then see if the point is within any of the window controls.
236  if (close_button_ && close_button_->visible() &&
237      close_button_->GetMirroredBounds().Contains(point))
238    return HTCLOSE;
239  if (restore_button_ && restore_button_->visible() &&
240      restore_button_->GetMirroredBounds().Contains(point))
241    return HTMAXBUTTON;
242  if (maximize_button_ && maximize_button_->visible() &&
243      maximize_button_->GetMirroredBounds().Contains(point))
244    return HTMAXBUTTON;
245  if (minimize_button_ && minimize_button_->visible() &&
246      minimize_button_->GetMirroredBounds().Contains(point))
247    return HTMINBUTTON;
248
249  views::WidgetDelegate* delegate = frame()->widget_delegate();
250  if (!delegate) {
251    LOG(WARNING) << "delegate is NULL, returning safe default.";
252    return HTCAPTION;
253  }
254  int window_component = GetHTComponentForFrame(point, TopResizeHeight(),
255      NonClientBorderThickness(), kResizeAreaCornerSize, kResizeAreaCornerSize,
256      delegate->CanResize());
257  // Fall back to the caption if no other component matches.
258  return (window_component == HTNOWHERE) ? HTCAPTION : window_component;
259}
260
261void OpaqueBrowserFrameView::GetWindowMask(const gfx::Size& size,
262                                           gfx::Path* window_mask) {
263  DCHECK(window_mask);
264
265  if (frame()->IsMaximized() || frame()->IsFullscreen())
266    return;
267
268  views::GetDefaultWindowMask(size, window_mask);
269}
270
271void OpaqueBrowserFrameView::ResetWindowControls() {
272  if (!OpaqueBrowserFrameViewLayout::ShouldAddDefaultCaptionButtons())
273    return;
274  restore_button_->SetState(views::CustomButton::STATE_NORMAL);
275  minimize_button_->SetState(views::CustomButton::STATE_NORMAL);
276  maximize_button_->SetState(views::CustomButton::STATE_NORMAL);
277  // The close button isn't affected by this constraint.
278}
279
280void OpaqueBrowserFrameView::UpdateWindowIcon() {
281  window_icon_->SchedulePaint();
282}
283
284void OpaqueBrowserFrameView::UpdateWindowTitle() {
285  if (!frame()->IsFullscreen())
286    window_title_->SchedulePaint();
287}
288
289///////////////////////////////////////////////////////////////////////////////
290// OpaqueBrowserFrameView, views::View overrides:
291
292void OpaqueBrowserFrameView::OnPaint(gfx::Canvas* canvas) {
293  if (frame()->IsFullscreen())
294    return;  // Nothing is visible, so don't bother to paint.
295
296  if (frame()->IsMaximized())
297    PaintMaximizedFrameBorder(canvas);
298  else
299    PaintRestoredFrameBorder(canvas);
300
301  // The window icon and title are painted by their respective views.
302  /* TODO(pkasting):  If this window is active, we should also draw a drop
303   * shadow on the title.  This is tricky, because we don't want to hardcode a
304   * shadow color (since we want to work with various themes), but we can't
305   * alpha-blend either (since the Windows text APIs don't really do this).
306   * So we'd need to sample the background color at the right location and
307   * synthesize a good shadow color. */
308
309  if (browser_view()->IsToolbarVisible())
310    PaintToolbarBackground(canvas);
311  if (!frame()->IsMaximized())
312    PaintRestoredClientEdge(canvas);
313}
314
315bool OpaqueBrowserFrameView::HitTestRect(const gfx::Rect& rect) const {
316  if (!views::View::HitTestRect(rect)) {
317    // |rect| is outside OpaqueBrowserFrameView's bounds.
318    return false;
319  }
320
321  // If the rect is outside the bounds of the client area, claim it.
322  // TODO(tdanderson): Implement View::ConvertRectToTarget().
323  gfx::Point rect_in_client_view_coords_origin(rect.origin());
324  View::ConvertPointToTarget(this, frame()->client_view(),
325      &rect_in_client_view_coords_origin);
326  gfx::Rect rect_in_client_view_coords(
327      rect_in_client_view_coords_origin, rect.size());
328  if (!frame()->client_view()->HitTestRect(rect_in_client_view_coords))
329    return true;
330
331  // Otherwise, claim |rect| only if it is above the bottom of the tabstrip in
332  // a non-tab portion.
333  TabStrip* tabstrip = browser_view()->tabstrip();
334  if (!tabstrip || !browser_view()->IsTabStripVisible())
335    return false;
336
337  gfx::Point rect_in_tabstrip_coords_origin(rect.origin());
338  View::ConvertPointToTarget(this, tabstrip,
339      &rect_in_tabstrip_coords_origin);
340  gfx::Rect rect_in_tabstrip_coords(
341      rect_in_tabstrip_coords_origin, rect.size());
342
343  if (rect_in_tabstrip_coords.bottom() > tabstrip->GetLocalBounds().bottom()) {
344    // |rect| is below the tabstrip.
345    return false;
346  }
347
348  if (tabstrip->HitTestRect(rect_in_tabstrip_coords)) {
349    // Claim |rect| if it is in a non-tab portion of the tabstrip.
350    // TODO(tdanderson): Pass |rect_in_tabstrip_coords| instead of its center
351    // point to TabStrip::IsPositionInWindowCaption() once
352    // GetEventHandlerForRect() is implemented.
353    return tabstrip->IsPositionInWindowCaption(
354        rect_in_tabstrip_coords.CenterPoint());
355  }
356
357  // The window switcher button is to the right of the tabstrip but is
358  // part of the client view.
359  views::View* window_switcher_button =
360      browser_view()->window_switcher_button();
361  if (window_switcher_button && window_switcher_button->visible()) {
362    gfx::Point rect_in_window_switcher_coords_origin(rect.origin());
363    View::ConvertPointToTarget(this, window_switcher_button,
364        &rect_in_window_switcher_coords_origin);
365    gfx::Rect rect_in_window_switcher_coords(
366        rect_in_window_switcher_coords_origin, rect.size());
367
368    if (window_switcher_button->HitTestRect(rect_in_window_switcher_coords))
369      return false;
370  }
371
372  // We claim |rect| because it is above the bottom of the tabstrip, but
373  // neither in the tabstrip nor in the window switcher button. In particular,
374  // the avatar label/button is left of the tabstrip and the window controls
375  // are right of the tabstrip.
376  return true;
377}
378
379void OpaqueBrowserFrameView::GetAccessibleState(
380    ui::AccessibleViewState* state) {
381  state->role = ui::AccessibilityTypes::ROLE_TITLEBAR;
382}
383
384///////////////////////////////////////////////////////////////////////////////
385// OpaqueBrowserFrameView, views::ButtonListener implementation:
386
387void OpaqueBrowserFrameView::ButtonPressed(views::Button* sender,
388                                           const ui::Event& event) {
389  if (sender == minimize_button_)
390    frame()->Minimize();
391  else if (sender == maximize_button_)
392    frame()->Maximize();
393  else if (sender == restore_button_)
394    frame()->Restore();
395  else if (sender == close_button_)
396    frame()->Close();
397}
398
399///////////////////////////////////////////////////////////////////////////////
400// OpaqueBrowserFrameView, TabIconView::TabContentsProvider implementation:
401
402bool OpaqueBrowserFrameView::ShouldTabIconViewAnimate() const {
403  // This function is queried during the creation of the window as the
404  // TabIconView we host is initialized, so we need to NULL check the selected
405  // WebContents because in this condition there is not yet a selected tab.
406  WebContents* current_tab = browser_view()->GetActiveWebContents();
407  return current_tab ? current_tab->IsLoading() : false;
408}
409
410gfx::ImageSkia OpaqueBrowserFrameView::GetFaviconForTabIconView() {
411  views::WidgetDelegate* delegate = frame()->widget_delegate();
412  if (!delegate) {
413    LOG(WARNING) << "delegate is NULL, returning safe default.";
414    return gfx::ImageSkia();
415  }
416  return delegate->GetWindowIcon();
417}
418
419///////////////////////////////////////////////////////////////////////////////
420// OpaqueBrowserFrameView, protected:
421
422void OpaqueBrowserFrameView::Observe(
423    int type,
424    const content::NotificationSource& source,
425    const content::NotificationDetails& details) {
426  switch (type) {
427    case chrome::NOTIFICATION_PROFILE_CACHED_INFO_CHANGED:
428      UpdateAvatarInfo();
429      break;
430    default:
431      NOTREACHED() << "Got a notification we didn't register for!";
432      break;
433  }
434}
435
436///////////////////////////////////////////////////////////////////////////////
437// OpaqueBrowserFrameView, OpaqueBrowserFrameViewLayoutDelegate implementation:
438
439bool OpaqueBrowserFrameView::ShouldShowWindowIcon() const {
440  views::WidgetDelegate* delegate = frame()->widget_delegate();
441  return delegate && delegate->ShouldShowWindowIcon();
442}
443
444bool OpaqueBrowserFrameView::ShouldShowWindowTitle() const {
445  // |delegate| may be NULL if called from callback of InputMethodChanged while
446  // a window is being destroyed.
447  // See more discussion at http://crosbug.com/8958
448  views::WidgetDelegate* delegate = frame()->widget_delegate();
449  return delegate && delegate->ShouldShowWindowTitle();
450}
451
452string16 OpaqueBrowserFrameView::GetWindowTitle() const {
453  return frame()->widget_delegate()->GetWindowTitle();
454}
455
456int OpaqueBrowserFrameView::GetIconSize() const {
457#if defined(OS_WIN)
458  // This metric scales up if either the titlebar height or the titlebar font
459  // size are increased.
460  return GetSystemMetrics(SM_CYSMICON);
461#else
462  return std::max(BrowserFrame::GetTitleFont().GetHeight(), kIconMinimumSize);
463#endif
464}
465
466bool OpaqueBrowserFrameView::ShouldLeaveOffsetNearTopBorder() const {
467  return frame()->ShouldLeaveOffsetNearTopBorder();
468}
469
470gfx::Size OpaqueBrowserFrameView::GetBrowserViewMinimumSize() const {
471  return browser_view()->GetMinimumSize();
472}
473
474bool OpaqueBrowserFrameView::ShouldShowAvatar() const {
475  return browser_view()->ShouldShowAvatar();
476}
477
478gfx::ImageSkia OpaqueBrowserFrameView::GetOTRAvatarIcon() const {
479  return browser_view()->GetOTRAvatarIcon();
480}
481
482bool OpaqueBrowserFrameView::IsMaximized() const {
483  return frame()->IsMaximized();
484}
485
486bool OpaqueBrowserFrameView::IsMinimized() const {
487  return frame()->IsMinimized();
488}
489
490bool OpaqueBrowserFrameView::IsFullscreen() const {
491  return frame()->IsFullscreen();
492}
493
494bool OpaqueBrowserFrameView::IsTabStripVisible() const {
495  return browser_view()->IsTabStripVisible();
496}
497
498int OpaqueBrowserFrameView::GetTabStripHeight() const {
499  return browser_view()->GetTabStripHeight();
500}
501
502int OpaqueBrowserFrameView::GetAdditionalReservedSpaceInTabStrip() const {
503  // We don't have the sysmenu buttons in Windows 8 metro mode. However there
504  // are buttons like the window switcher which are drawn in the non client
505  // are in the BrowserView. We need to ensure that the tab strip does not
506  // draw on the window switcher button.
507  views::View* button = browser_view()->window_switcher_button();
508  return button ? button->width() : 0;
509}
510
511gfx::Size OpaqueBrowserFrameView::GetTabstripPreferredSize() const {
512  gfx::Size s = browser_view()->tabstrip()->GetPreferredSize();
513  return s;
514}
515
516///////////////////////////////////////////////////////////////////////////////
517// OpaqueBrowserFrameView, private:
518
519views::ImageButton* OpaqueBrowserFrameView::InitWindowCaptionButton(
520    int normal_image_id,
521    int hot_image_id,
522    int pushed_image_id,
523    int mask_image_id,
524    int accessibility_string_id,
525    ViewID view_id) {
526  views::ImageButton* button = new views::ImageButton(this);
527  ui::ThemeProvider* tp = frame()->GetThemeProvider();
528  button->SetImage(views::CustomButton::STATE_NORMAL,
529                   tp->GetImageSkiaNamed(normal_image_id));
530  button->SetImage(views::CustomButton::STATE_HOVERED,
531                   tp->GetImageSkiaNamed(hot_image_id));
532  button->SetImage(views::CustomButton::STATE_PRESSED,
533                   tp->GetImageSkiaNamed(pushed_image_id));
534  if (browser_view()->IsBrowserTypeNormal()) {
535    button->SetBackground(
536        tp->GetColor(ThemeProperties::COLOR_BUTTON_BACKGROUND),
537        tp->GetImageSkiaNamed(IDR_THEME_WINDOW_CONTROL_BACKGROUND),
538        tp->GetImageSkiaNamed(mask_image_id));
539  }
540  button->SetAccessibleName(
541      l10n_util::GetStringUTF16(accessibility_string_id));
542  button->set_id(view_id);
543  AddChildView(button);
544  return button;
545}
546
547int OpaqueBrowserFrameView::FrameBorderThickness(bool restored) const {
548  return layout_->FrameBorderThickness(restored);
549}
550
551int OpaqueBrowserFrameView::TopResizeHeight() const {
552  return FrameBorderThickness(false) - kTopResizeAdjust;
553}
554
555int OpaqueBrowserFrameView::NonClientBorderThickness() const {
556  return layout_->NonClientBorderThickness();
557}
558
559gfx::Rect OpaqueBrowserFrameView::IconBounds() const {
560  return layout_->IconBounds();
561}
562
563void OpaqueBrowserFrameView::PaintRestoredFrameBorder(gfx::Canvas* canvas) {
564  frame_background_->set_frame_color(GetFrameColor());
565  frame_background_->set_theme_image(GetFrameImage());
566  frame_background_->set_theme_overlay_image(GetFrameOverlayImage());
567  frame_background_->set_top_area_height(GetTopAreaHeight());
568
569  ui::ThemeProvider* tp = GetThemeProvider();
570  frame_background_->SetSideImages(
571      tp->GetImageSkiaNamed(IDR_WINDOW_LEFT_SIDE),
572      tp->GetImageSkiaNamed(IDR_WINDOW_TOP_CENTER),
573      tp->GetImageSkiaNamed(IDR_WINDOW_RIGHT_SIDE),
574      tp->GetImageSkiaNamed(IDR_WINDOW_BOTTOM_CENTER));
575  frame_background_->SetCornerImages(
576      tp->GetImageSkiaNamed(IDR_WINDOW_TOP_LEFT_CORNER),
577      tp->GetImageSkiaNamed(IDR_WINDOW_TOP_RIGHT_CORNER),
578      tp->GetImageSkiaNamed(IDR_WINDOW_BOTTOM_LEFT_CORNER),
579      tp->GetImageSkiaNamed(IDR_WINDOW_BOTTOM_RIGHT_CORNER));
580  frame_background_->PaintRestored(canvas, this);
581
582  // Note: When we don't have a toolbar, we need to draw some kind of bottom
583  // edge here.  Because the App Window graphics we use for this have an
584  // attached client edge and their sizing algorithm is a little involved, we do
585  // all this in PaintRestoredClientEdge().
586}
587
588void OpaqueBrowserFrameView::PaintMaximizedFrameBorder(gfx::Canvas* canvas) {
589  frame_background_->set_frame_color(GetFrameColor());
590  frame_background_->set_theme_image(GetFrameImage());
591  frame_background_->set_theme_overlay_image(GetFrameOverlayImage());
592  frame_background_->set_top_area_height(GetTopAreaHeight());
593
594  // Theme frame must be aligned with the tabstrip as if we were
595  // in restored mode.  Note that the top of the tabstrip is
596  // kTabstripTopShadowThickness px off the top of the screen.
597  int theme_background_y = -(GetTabStripInsets(true).top +
598      kTabstripTopShadowThickness);
599  frame_background_->set_theme_background_y(theme_background_y);
600
601  frame_background_->PaintMaximized(canvas, this);
602
603  // TODO(jamescook): Migrate this into FrameBackground.
604  if (!browser_view()->IsToolbarVisible()) {
605    // There's no toolbar to edge the frame border, so we need to draw a bottom
606    // edge.  The graphic we use for this has a built in client edge, so we clip
607    // it off the bottom.
608    gfx::ImageSkia* top_center =
609        GetThemeProvider()->GetImageSkiaNamed(IDR_APP_TOP_CENTER);
610    int edge_height = top_center->height() - kClientEdgeThickness;
611    canvas->TileImageInt(*top_center, 0,
612        frame()->client_view()->y() - edge_height, width(), edge_height);
613  }
614}
615
616void OpaqueBrowserFrameView::PaintToolbarBackground(gfx::Canvas* canvas) {
617  gfx::Rect toolbar_bounds(browser_view()->GetToolbarBounds());
618  if (toolbar_bounds.IsEmpty())
619    return;
620  gfx::Point toolbar_origin(toolbar_bounds.origin());
621  ConvertPointToTarget(browser_view(), this, &toolbar_origin);
622  toolbar_bounds.set_origin(toolbar_origin);
623
624  int x = toolbar_bounds.x();
625  int w = toolbar_bounds.width();
626  int y = toolbar_bounds.y();
627  int h = toolbar_bounds.height();
628
629  // Gross hack: We split the toolbar images into two pieces, since sometimes
630  // (popup mode) the toolbar isn't tall enough to show the whole image.  The
631  // split happens between the top shadow section and the bottom gradient
632  // section so that we never break the gradient.
633  int split_point = kFrameShadowThickness * 2;
634  int bottom_y = y + split_point;
635  ui::ThemeProvider* tp = GetThemeProvider();
636  gfx::ImageSkia* toolbar_left = tp->GetImageSkiaNamed(
637      IDR_CONTENT_TOP_LEFT_CORNER);
638  int bottom_edge_height = std::min(toolbar_left->height(), h) - split_point;
639
640  // Split our canvas out so we can mask out the corners of the toolbar
641  // without masking out the frame.
642  canvas->SaveLayerAlpha(
643      255, gfx::Rect(x - kClientEdgeThickness, y, w + kClientEdgeThickness * 3,
644                     h));
645
646  // Paint the bottom rect.
647  canvas->FillRect(gfx::Rect(x, bottom_y, w, bottom_edge_height),
648                   tp->GetColor(ThemeProperties::COLOR_TOOLBAR));
649
650  // Tile the toolbar image starting at the frame edge on the left and where the
651  // horizontal tabstrip is (or would be) on the top.
652  gfx::ImageSkia* theme_toolbar = tp->GetImageSkiaNamed(IDR_THEME_TOOLBAR);
653  canvas->TileImageInt(*theme_toolbar,
654                       x + GetThemeBackgroundXInset(),
655                       bottom_y - GetTabStripInsets(false).top,
656                       x, bottom_y, w, theme_toolbar->height());
657
658  // Draw rounded corners for the tab.
659  gfx::ImageSkia* toolbar_left_mask =
660      tp->GetImageSkiaNamed(IDR_CONTENT_TOP_LEFT_CORNER_MASK);
661  gfx::ImageSkia* toolbar_right_mask =
662      tp->GetImageSkiaNamed(IDR_CONTENT_TOP_RIGHT_CORNER_MASK);
663
664  // We mask out the corners by using the DestinationIn transfer mode,
665  // which keeps the RGB pixels from the destination and the alpha from
666  // the source.
667  SkPaint paint;
668  paint.setXfermodeMode(SkXfermode::kDstIn_Mode);
669
670  // Mask the left edge.
671  int left_x = x - kContentEdgeShadowThickness;
672  canvas->DrawImageInt(*toolbar_left_mask, 0, 0, toolbar_left_mask->width(),
673                       split_point, left_x, y, toolbar_left_mask->width(),
674                       split_point, false, paint);
675  canvas->DrawImageInt(*toolbar_left_mask, 0,
676      toolbar_left_mask->height() - bottom_edge_height,
677      toolbar_left_mask->width(), bottom_edge_height, left_x, bottom_y,
678      toolbar_left_mask->width(), bottom_edge_height, false, paint);
679
680  // Mask the right edge.
681  int right_x =
682      x + w - toolbar_right_mask->width() + kContentEdgeShadowThickness;
683  canvas->DrawImageInt(*toolbar_right_mask, 0, 0, toolbar_right_mask->width(),
684                       split_point, right_x, y, toolbar_right_mask->width(),
685                       split_point, false, paint);
686  canvas->DrawImageInt(*toolbar_right_mask, 0,
687      toolbar_right_mask->height() - bottom_edge_height,
688      toolbar_right_mask->width(), bottom_edge_height, right_x, bottom_y,
689      toolbar_right_mask->width(), bottom_edge_height, false, paint);
690  canvas->Restore();
691
692  canvas->DrawImageInt(*toolbar_left, 0, 0, toolbar_left->width(), split_point,
693                       left_x, y, toolbar_left->width(), split_point, false);
694  canvas->DrawImageInt(*toolbar_left, 0,
695      toolbar_left->height() - bottom_edge_height, toolbar_left->width(),
696      bottom_edge_height, left_x, bottom_y, toolbar_left->width(),
697      bottom_edge_height, false);
698
699  gfx::ImageSkia* toolbar_center =
700      tp->GetImageSkiaNamed(IDR_CONTENT_TOP_CENTER);
701  canvas->TileImageInt(*toolbar_center, 0, 0, left_x + toolbar_left->width(),
702      y, right_x - (left_x + toolbar_left->width()),
703      split_point);
704
705  gfx::ImageSkia* toolbar_right = tp->GetImageSkiaNamed(
706      IDR_CONTENT_TOP_RIGHT_CORNER);
707  canvas->DrawImageInt(*toolbar_right, 0, 0, toolbar_right->width(),
708      split_point, right_x, y, toolbar_right->width(), split_point, false);
709  canvas->DrawImageInt(*toolbar_right, 0,
710      toolbar_right->height() - bottom_edge_height, toolbar_right->width(),
711      bottom_edge_height, right_x, bottom_y, toolbar_right->width(),
712      bottom_edge_height, false);
713
714  // Draw the content/toolbar separator.
715  canvas->FillRect(
716      gfx::Rect(x + kClientEdgeThickness,
717                toolbar_bounds.bottom() - kClientEdgeThickness,
718                w - (2 * kClientEdgeThickness),
719                kClientEdgeThickness),
720      ThemeProperties::GetDefaultColor(
721          ThemeProperties::COLOR_TOOLBAR_SEPARATOR));
722}
723
724void OpaqueBrowserFrameView::PaintRestoredClientEdge(gfx::Canvas* canvas) {
725  ui::ThemeProvider* tp = GetThemeProvider();
726  int client_area_top = frame()->client_view()->y();
727  int image_top = client_area_top;
728
729  gfx::Rect client_area_bounds =
730      layout_->CalculateClientAreaBounds(width(), height());
731  SkColor toolbar_color = tp->GetColor(ThemeProperties::COLOR_TOOLBAR);
732
733  if (browser_view()->IsToolbarVisible()) {
734    // The client edge images always start below the toolbar corner images.  The
735    // client edge filled rects start there or at the bottom of the toolbar,
736    // whichever is shorter.
737    gfx::Rect toolbar_bounds(browser_view()->GetToolbarBounds());
738    image_top += toolbar_bounds.y() +
739        tp->GetImageSkiaNamed(IDR_CONTENT_TOP_LEFT_CORNER)->height();
740    client_area_top = std::min(image_top,
741        client_area_top + toolbar_bounds.bottom() - kClientEdgeThickness);
742  } else if (!browser_view()->IsTabStripVisible()) {
743    // The toolbar isn't going to draw a client edge for us, so draw one
744    // ourselves.
745    gfx::ImageSkia* top_left = tp->GetImageSkiaNamed(IDR_APP_TOP_LEFT);
746    gfx::ImageSkia* top_center = tp->GetImageSkiaNamed(IDR_APP_TOP_CENTER);
747    gfx::ImageSkia* top_right = tp->GetImageSkiaNamed(IDR_APP_TOP_RIGHT);
748    int top_edge_y = client_area_top - top_center->height();
749    int height = client_area_top - top_edge_y;
750
751    canvas->DrawImageInt(*top_left, 0, 0, top_left->width(), height,
752        client_area_bounds.x() - top_left->width(), top_edge_y,
753        top_left->width(), height, false);
754    canvas->TileImageInt(*top_center, 0, 0, client_area_bounds.x(), top_edge_y,
755      client_area_bounds.width(), std::min(height, top_center->height()));
756    canvas->DrawImageInt(*top_right, 0, 0, top_right->width(), height,
757        client_area_bounds.right(), top_edge_y,
758        top_right->width(), height, false);
759
760    // Draw the toolbar color across the top edge.
761    canvas->FillRect(gfx::Rect(client_area_bounds.x() - kClientEdgeThickness,
762        client_area_top - kClientEdgeThickness,
763        client_area_bounds.width() + (2 * kClientEdgeThickness),
764        kClientEdgeThickness), toolbar_color);
765  }
766
767  int client_area_bottom =
768      std::max(client_area_top, height() - NonClientBorderThickness());
769  int image_height = client_area_bottom - image_top;
770
771  // Draw the client edge images.
772  gfx::ImageSkia* right = tp->GetImageSkiaNamed(IDR_CONTENT_RIGHT_SIDE);
773  canvas->TileImageInt(*right, client_area_bounds.right(), image_top,
774                       right->width(), image_height);
775  canvas->DrawImageInt(
776      *tp->GetImageSkiaNamed(IDR_CONTENT_BOTTOM_RIGHT_CORNER),
777      client_area_bounds.right(), client_area_bottom);
778  gfx::ImageSkia* bottom = tp->GetImageSkiaNamed(IDR_CONTENT_BOTTOM_CENTER);
779  canvas->TileImageInt(*bottom, client_area_bounds.x(),
780      client_area_bottom, client_area_bounds.width(),
781      bottom->height());
782  gfx::ImageSkia* bottom_left =
783      tp->GetImageSkiaNamed(IDR_CONTENT_BOTTOM_LEFT_CORNER);
784  canvas->DrawImageInt(*bottom_left,
785      client_area_bounds.x() - bottom_left->width(), client_area_bottom);
786  gfx::ImageSkia* left = tp->GetImageSkiaNamed(IDR_CONTENT_LEFT_SIDE);
787  canvas->TileImageInt(*left, client_area_bounds.x() - left->width(),
788                       image_top, left->width(), image_height);
789
790  // Draw the toolbar color so that the client edges show the right color even
791  // where not covered by the toolbar image.  NOTE: We do this after drawing the
792  // images because the images are meant to alpha-blend atop the frame whereas
793  // these rects are meant to be fully opaque, without anything overlaid.
794  canvas->FillRect(gfx::Rect(client_area_bounds.x() - kClientEdgeThickness,
795      client_area_top, kClientEdgeThickness,
796      client_area_bottom + kClientEdgeThickness - client_area_top),
797       toolbar_color);
798  canvas->FillRect(gfx::Rect(client_area_bounds.x(), client_area_bottom,
799                             client_area_bounds.width(), kClientEdgeThickness),
800                   toolbar_color);
801  canvas->FillRect(gfx::Rect(client_area_bounds.right(), client_area_top,
802      kClientEdgeThickness,
803      client_area_bottom + kClientEdgeThickness - client_area_top),
804      toolbar_color);
805}
806
807SkColor OpaqueBrowserFrameView::GetFrameColor() const {
808  bool is_incognito = browser_view()->IsOffTheRecord();
809  if (browser_view()->IsBrowserTypeNormal()) {
810    if (ShouldPaintAsActive()) {
811      return GetThemeProvider()->GetColor(is_incognito ?
812          ThemeProperties::COLOR_FRAME_INCOGNITO :
813          ThemeProperties::COLOR_FRAME);
814    }
815    return GetThemeProvider()->GetColor(is_incognito ?
816        ThemeProperties::COLOR_FRAME_INCOGNITO_INACTIVE :
817        ThemeProperties::COLOR_FRAME_INACTIVE);
818  }
819  // Never theme app and popup windows.
820  if (ShouldPaintAsActive()) {
821    return ThemeProperties::GetDefaultColor(is_incognito ?
822        ThemeProperties::COLOR_FRAME_INCOGNITO : ThemeProperties::COLOR_FRAME);
823  }
824  return ThemeProperties::GetDefaultColor(is_incognito ?
825      ThemeProperties::COLOR_FRAME_INCOGNITO_INACTIVE :
826      ThemeProperties::COLOR_FRAME_INACTIVE);
827}
828
829gfx::ImageSkia* OpaqueBrowserFrameView::GetFrameImage() const {
830  bool is_incognito = browser_view()->IsOffTheRecord();
831  int resource_id;
832  if (browser_view()->IsBrowserTypeNormal()) {
833    if (ShouldPaintAsActive()) {
834      resource_id = is_incognito ?
835          IDR_THEME_FRAME_INCOGNITO : IDR_THEME_FRAME;
836    } else {
837      resource_id = is_incognito ?
838          IDR_THEME_FRAME_INCOGNITO_INACTIVE : IDR_THEME_FRAME_INACTIVE;
839    }
840    return GetThemeProvider()->GetImageSkiaNamed(resource_id);
841  }
842  // Never theme app and popup windows.
843  ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
844  if (ShouldPaintAsActive()) {
845    resource_id = is_incognito ?
846        IDR_THEME_FRAME_INCOGNITO : IDR_FRAME;
847  } else {
848    resource_id = is_incognito ?
849        IDR_THEME_FRAME_INCOGNITO_INACTIVE : IDR_THEME_FRAME_INACTIVE;
850  }
851  return rb.GetImageSkiaNamed(resource_id);
852}
853
854gfx::ImageSkia* OpaqueBrowserFrameView::GetFrameOverlayImage() const {
855  ui::ThemeProvider* tp = GetThemeProvider();
856  if (tp->HasCustomImage(IDR_THEME_FRAME_OVERLAY) &&
857      browser_view()->IsBrowserTypeNormal() &&
858      !browser_view()->IsOffTheRecord()) {
859    return tp->GetImageSkiaNamed(ShouldPaintAsActive() ?
860        IDR_THEME_FRAME_OVERLAY : IDR_THEME_FRAME_OVERLAY_INACTIVE);
861  }
862  return NULL;
863}
864
865int OpaqueBrowserFrameView::GetTopAreaHeight() const {
866  gfx::ImageSkia* frame_image = GetFrameImage();
867  int top_area_height = frame_image->height();
868  if (browser_view()->IsTabStripVisible()) {
869    top_area_height = std::max(top_area_height,
870      GetBoundsForTabStrip(browser_view()->tabstrip()).bottom());
871  }
872  return top_area_height;
873}
874