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