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