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/browser_desktop_window_tree_host_win.h"
6
7#include <dwmapi.h>
8
9#include "chrome/browser/lifetime/application_lifetime.h"
10#include "chrome/browser/themes/theme_service.h"
11#include "chrome/browser/themes/theme_service_factory.h"
12#include "chrome/browser/ui/views/frame/browser_frame.h"
13#include "chrome/browser/ui/views/frame/browser_frame_common_win.h"
14#include "chrome/browser/ui/views/frame/browser_view.h"
15#include "chrome/browser/ui/views/frame/browser_window_property_manager_win.h"
16#include "chrome/browser/ui/views/frame/system_menu_insertion_delegate_win.h"
17#include "chrome/browser/ui/views/tabs/tab_strip.h"
18#include "chrome/browser/ui/views/theme_image_mapper.h"
19#include "ui/base/theme_provider.h"
20#include "ui/gfx/win/dpi.h"
21#include "ui/views/controls/menu/native_menu_win.h"
22
23#pragma comment(lib, "dwmapi.lib")
24
25namespace {
26
27const int kClientEdgeThickness = 3;
28// We need to offset the DWMFrame into the toolbar so that the blackness
29// doesn't show up on our rounded corners.
30const int kDWMFrameTopOffset = 3;
31
32// DesktopThemeProvider maps resource ids using MapThemeImage(). This is
33// necessary for BrowserDesktopWindowTreeHostWin so that it uses the windows
34// theme images rather than the ash theme images.
35class DesktopThemeProvider : public ui::ThemeProvider {
36 public:
37  explicit DesktopThemeProvider(ui::ThemeProvider* delegate)
38      : delegate_(delegate) {
39  }
40
41  virtual bool UsingSystemTheme() const OVERRIDE {
42    return delegate_->UsingSystemTheme();
43  }
44  virtual gfx::ImageSkia* GetImageSkiaNamed(int id) const OVERRIDE {
45    return delegate_->GetImageSkiaNamed(
46        chrome::MapThemeImage(chrome::HOST_DESKTOP_TYPE_NATIVE, id));
47  }
48  virtual SkColor GetColor(int id) const OVERRIDE {
49    return delegate_->GetColor(id);
50  }
51  virtual int GetDisplayProperty(int id) const OVERRIDE {
52    return delegate_->GetDisplayProperty(id);
53  }
54  virtual bool ShouldUseNativeFrame() const OVERRIDE {
55    return delegate_->ShouldUseNativeFrame();
56  }
57  virtual bool HasCustomImage(int id) const OVERRIDE {
58    return delegate_->HasCustomImage(
59        chrome::MapThemeImage(chrome::HOST_DESKTOP_TYPE_NATIVE, id));
60  }
61  virtual base::RefCountedMemory* GetRawData(
62      int id,
63      ui::ScaleFactor scale_factor) const OVERRIDE {
64    return delegate_->GetRawData(id, scale_factor);
65  }
66
67 private:
68  ui::ThemeProvider* delegate_;
69
70  DISALLOW_COPY_AND_ASSIGN(DesktopThemeProvider);
71};
72
73}  // namespace
74
75////////////////////////////////////////////////////////////////////////////////
76// BrowserDesktopWindowTreeHostWin, public:
77
78BrowserDesktopWindowTreeHostWin::BrowserDesktopWindowTreeHostWin(
79    views::internal::NativeWidgetDelegate* native_widget_delegate,
80    views::DesktopNativeWidgetAura* desktop_native_widget_aura,
81    BrowserView* browser_view,
82    BrowserFrame* browser_frame)
83    : DesktopWindowTreeHostWin(native_widget_delegate,
84                               desktop_native_widget_aura),
85      browser_view_(browser_view),
86      browser_frame_(browser_frame),
87      did_gdi_clear_(false) {
88  scoped_ptr<ui::ThemeProvider> theme_provider(
89      new DesktopThemeProvider(ThemeServiceFactory::GetForProfile(
90                                   browser_view->browser()->profile())));
91  browser_frame->SetThemeProvider(theme_provider.Pass());
92}
93
94BrowserDesktopWindowTreeHostWin::~BrowserDesktopWindowTreeHostWin() {
95}
96
97views::NativeMenuWin* BrowserDesktopWindowTreeHostWin::GetSystemMenu() {
98  if (!system_menu_.get()) {
99    SystemMenuInsertionDelegateWin insertion_delegate;
100    system_menu_.reset(
101        new views::NativeMenuWin(browser_frame_->GetSystemMenuModel(),
102                                 GetHWND()));
103    system_menu_->Rebuild(&insertion_delegate);
104  }
105  return system_menu_.get();
106}
107
108////////////////////////////////////////////////////////////////////////////////
109// BrowserDesktopWindowTreeHostWin, BrowserDesktopWindowTreeHost implementation:
110
111views::DesktopWindowTreeHost*
112    BrowserDesktopWindowTreeHostWin::AsDesktopWindowTreeHost() {
113  return this;
114}
115
116int BrowserDesktopWindowTreeHostWin::GetMinimizeButtonOffset() const {
117  return minimize_button_metrics_.GetMinimizeButtonOffsetX();
118}
119
120bool BrowserDesktopWindowTreeHostWin::UsesNativeSystemMenu() const {
121  return true;
122}
123
124////////////////////////////////////////////////////////////////////////////////
125// BrowserDesktopWindowTreeHostWin, views::DesktopWindowTreeHostWin overrides:
126
127int BrowserDesktopWindowTreeHostWin::GetInitialShowState() const {
128  STARTUPINFO si = {0};
129  si.cb = sizeof(si);
130  si.dwFlags = STARTF_USESHOWWINDOW;
131  GetStartupInfo(&si);
132  return si.wShowWindow;
133}
134
135bool BrowserDesktopWindowTreeHostWin::GetClientAreaInsets(
136    gfx::Insets* insets) const {
137  // Use the default client insets for an opaque frame or a glass popup/app
138  // frame.
139  if (!GetWidget()->ShouldUseNativeFrame() ||
140      !browser_view_->IsBrowserTypeNormal()) {
141    return false;
142  }
143
144  int border_thickness = GetSystemMetrics(SM_CXSIZEFRAME);
145  // In fullscreen mode, we have no frame. In restored mode, we draw our own
146  // client edge over part of the default frame.
147  if (GetWidget()->IsFullscreen())
148    border_thickness = 0;
149  else if (!IsMaximized())
150    border_thickness -= kClientEdgeThickness;
151  insets->Set(0, border_thickness, border_thickness, border_thickness);
152  return true;
153}
154
155void BrowserDesktopWindowTreeHostWin::HandleCreate() {
156  DesktopWindowTreeHostWin::HandleCreate();
157  browser_window_property_manager_ =
158      BrowserWindowPropertyManager::CreateBrowserWindowPropertyManager(
159          browser_view_);
160  if (browser_window_property_manager_)
161    browser_window_property_manager_->UpdateWindowProperties(GetHWND());
162}
163
164void BrowserDesktopWindowTreeHostWin::HandleFrameChanged() {
165  // Reinitialize the status bubble, since it needs to be initialized
166  // differently depending on whether or not DWM composition is enabled
167  browser_view_->InitStatusBubble();
168
169  // We need to update the glass region on or off before the base class adjusts
170  // the window region.
171  UpdateDWMFrame();
172  DesktopWindowTreeHostWin::HandleFrameChanged();
173}
174
175bool BrowserDesktopWindowTreeHostWin::PreHandleMSG(UINT message,
176                                                   WPARAM w_param,
177                                                   LPARAM l_param,
178                                                   LRESULT* result) {
179  switch (message) {
180    case WM_ACTIVATE:
181      if (LOWORD(w_param) != WA_INACTIVE)
182        minimize_button_metrics_.OnHWNDActivated();
183      return false;
184    case WM_ENDSESSION:
185      chrome::SessionEnding();
186      return true;
187    case WM_INITMENUPOPUP:
188      GetSystemMenu()->UpdateStates();
189      return true;
190  }
191  return DesktopWindowTreeHostWin::PreHandleMSG(
192      message, w_param, l_param, result);
193}
194
195void BrowserDesktopWindowTreeHostWin::PostHandleMSG(UINT message,
196                                                    WPARAM w_param,
197                                                    LPARAM l_param) {
198  switch (message) {
199  case WM_CREATE:
200    minimize_button_metrics_.Init(GetHWND());
201    break;
202  case WM_WINDOWPOSCHANGED: {
203    UpdateDWMFrame();
204
205    // Windows lies to us about the position of the minimize button before a
206    // window is visible.  We use this position to place the OTR avatar in RTL
207    // mode, so when the window is shown, we need to re-layout and schedule a
208    // paint for the non-client frame view so that the icon top has the correct
209    // position when the window becomes visible.  This fixes bugs where the icon
210    // appears to overlay the minimize button.
211    // Note that we will call Layout every time SetWindowPos is called with
212    // SWP_SHOWWINDOW, however callers typically are careful about not
213    // specifying this flag unless necessary to avoid flicker.
214    // This may be invoked during creation on XP and before the non_client_view
215    // has been created.
216    WINDOWPOS* window_pos = reinterpret_cast<WINDOWPOS*>(l_param);
217    if (window_pos->flags & SWP_SHOWWINDOW && GetWidget()->non_client_view()) {
218      GetWidget()->non_client_view()->Layout();
219      GetWidget()->non_client_view()->SchedulePaint();
220    }
221    break;
222  }
223  case WM_ERASEBKGND:
224    if (!did_gdi_clear_ && DesktopWindowTreeHostWin::ShouldUseNativeFrame()) {
225      // This is necessary to avoid white flashing in the titlebar area around
226      // the minimize/maximize/close buttons.
227      HDC dc = GetDC(GetHWND());
228      MARGINS margins = GetDWMFrameMargins();
229      RECT client_rect;
230      GetClientRect(GetHWND(), &client_rect);
231      HBRUSH brush = CreateSolidBrush(0);
232      RECT rect = { 0, 0, client_rect.right, margins.cyTopHeight };
233      FillRect(dc, &rect, brush);
234      DeleteObject(brush);
235      ReleaseDC(GetHWND(), dc);
236      did_gdi_clear_ = true;
237    }
238    break;
239  }
240}
241
242
243bool BrowserDesktopWindowTreeHostWin::IsUsingCustomFrame() const {
244  // We don't theme popup or app windows, so regardless of whether or not a
245  // theme is active for normal browser windows, we don't want to use the custom
246  // frame for popups/apps.
247  if (!browser_view_->IsBrowserTypeNormal() &&
248      !DesktopWindowTreeHostWin::IsUsingCustomFrame()) {
249    return false;
250  }
251
252  // Otherwise, we use the native frame when we're told we should by the theme
253  // provider (e.g. no custom theme is active).
254  return !GetWidget()->GetThemeProvider()->ShouldUseNativeFrame();
255}
256
257bool BrowserDesktopWindowTreeHostWin::ShouldUseNativeFrame() const {
258  if (!views::DesktopWindowTreeHostWin::ShouldUseNativeFrame())
259    return false;
260  // This function can get called when the Browser window is closed i.e. in the
261  // context of the BrowserView destructor.
262  if (!browser_view_->browser())
263    return false;
264  return chrome::ShouldUseNativeFrame(browser_view_,
265                                      GetWidget()->GetThemeProvider());
266}
267
268void BrowserDesktopWindowTreeHostWin::FrameTypeChanged() {
269  views::DesktopWindowTreeHostWin::FrameTypeChanged();
270  did_gdi_clear_ = false;
271}
272
273////////////////////////////////////////////////////////////////////////////////
274// BrowserDesktopWindowTreeHostWin, private:
275
276void BrowserDesktopWindowTreeHostWin::UpdateDWMFrame() {
277  // For "normal" windows on Aero, we always need to reset the glass area
278  // correctly, even if we're not currently showing the native frame (e.g.
279  // because a theme is showing), so we explicitly check for that case rather
280  // than checking browser_frame_->ShouldUseNativeFrame() here.  Using that here
281  // would mean we wouldn't reset the glass area to zero when moving from the
282  // native frame to an opaque frame, leading to graphical glitches behind the
283  // opaque frame.  Instead, we use that function below to tell us whether the
284  // frame is currently native or opaque.
285  if (!GetWidget()->client_view() || !browser_view_->IsBrowserTypeNormal() ||
286      !DesktopWindowTreeHostWin::ShouldUseNativeFrame())
287    return;
288
289  MARGINS margins = GetDWMFrameMargins();
290
291  DwmExtendFrameIntoClientArea(GetHWND(), &margins);
292}
293
294MARGINS BrowserDesktopWindowTreeHostWin::GetDWMFrameMargins() const {
295  MARGINS margins = { 0 };
296
297  // If the opaque frame is visible, we use the default (zero) margins.
298  // Otherwise, we need to figure out how to extend the glass in.
299  if (GetWidget()->ShouldUseNativeFrame()) {
300    // In fullscreen mode, we don't extend glass into the client area at all,
301    // because the GDI-drawn text in the web content composited over it will
302    // become semi-transparent over any glass area.
303    if (!IsMaximized() && !GetWidget()->IsFullscreen()) {
304      margins.cxLeftWidth = kClientEdgeThickness + 1;
305      margins.cxRightWidth = kClientEdgeThickness + 1;
306      margins.cyBottomHeight = kClientEdgeThickness + 1;
307      margins.cyTopHeight = kClientEdgeThickness + 1;
308    }
309    // In maximized mode, we only have a titlebar strip of glass, no side/bottom
310    // borders.
311    if (!browser_view_->IsFullscreen()) {
312      gfx::Rect tabstrip_bounds(
313          browser_frame_->GetBoundsForTabStrip(browser_view_->tabstrip()));
314      tabstrip_bounds = gfx::win::DIPToScreenRect(tabstrip_bounds);
315      margins.cyTopHeight = tabstrip_bounds.bottom() + kDWMFrameTopOffset;
316    }
317  }
318  return margins;
319}
320
321////////////////////////////////////////////////////////////////////////////////
322// BrowserDesktopWindowTreeHost, public:
323
324// static
325BrowserDesktopWindowTreeHost*
326    BrowserDesktopWindowTreeHost::CreateBrowserDesktopWindowTreeHost(
327        views::internal::NativeWidgetDelegate* native_widget_delegate,
328        views::DesktopNativeWidgetAura* desktop_native_widget_aura,
329        BrowserView* browser_view,
330        BrowserFrame* browser_frame) {
331  return new BrowserDesktopWindowTreeHostWin(native_widget_delegate,
332                                             desktop_native_widget_aura,
333                                             browser_view,
334                                             browser_frame);
335}
336