1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/views/controls/native_control.h"
6
7 #include <atlbase.h>
8 #include <atlapp.h>
9 #include <atlcrack.h>
10 #include <atlframe.h>
11 #include <atlmisc.h>
12
13 #include "base/logging.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "ui/base/accessibility/accessibility_types.h"
16 #include "ui/base/l10n/l10n_util_win.h"
17 #include "ui/base/view_prop.h"
18 #include "ui/events/keycodes/keyboard_code_conversion_win.h"
19 #include "ui/events/keycodes/keyboard_codes.h"
20 #include "ui/gfx/win/hwnd_util.h"
21 #include "ui/views/background.h"
22 #include "ui/views/controls/native/native_view_host.h"
23 #include "ui/views/focus/focus_manager.h"
24 #include "ui/views/widget/widget.h"
25
26 using ui::ViewProp;
27
28 namespace views {
29
30 // Maps to the NativeControl.
31 static const char* const kNativeControlKey = "__NATIVE_CONTROL__";
32
33 class NativeControlContainer : public CWindowImpl<NativeControlContainer,
34 CWindow,
35 CWinTraits<WS_CHILD | WS_CLIPSIBLINGS |
36 WS_CLIPCHILDREN>> {
37 public:
NativeControlContainer(NativeControl * parent)38 explicit NativeControlContainer(NativeControl* parent)
39 : parent_(parent),
40 control_(NULL),
41 original_handler_(NULL) {
42 }
43
Init()44 void Init() {
45 Create(parent_->GetWidget()->GetNativeView());
46 ::ShowWindow(m_hWnd, SW_SHOW);
47 }
48
~NativeControlContainer()49 virtual ~NativeControlContainer() {
50 }
51
52 // NOTE: If you add a new message, be sure and verify parent_ is valid before
53 // calling into parent_.
54 DECLARE_FRAME_WND_CLASS(L"ChromeViewsNativeControlContainer", NULL);
55 BEGIN_MSG_MAP(NativeControlContainer);
56 MSG_WM_CREATE(OnCreate);
57 MSG_WM_ERASEBKGND(OnEraseBkgnd);
58 MSG_WM_PAINT(OnPaint);
59 MSG_WM_SIZE(OnSize);
60 MSG_WM_NOTIFY(OnNotify);
61 MSG_WM_COMMAND(OnCommand);
62 MSG_WM_DESTROY(OnDestroy);
63 MSG_WM_CONTEXTMENU(OnContextMenu);
64 MSG_WM_CTLCOLORBTN(OnCtlColorBtn);
65 MSG_WM_CTLCOLORSTATIC(OnCtlColorStatic)
66 END_MSG_MAP();
67
GetControl()68 HWND GetControl() {
69 return control_;
70 }
71
72 // Called when the parent is getting deleted. This control stays around until
73 // it gets the OnFinalMessage call.
ResetParent()74 void ResetParent() {
75 parent_ = NULL;
76 }
77
OnFinalMessage(HWND hwnd)78 void OnFinalMessage(HWND hwnd) {
79 if (parent_)
80 parent_->NativeControlDestroyed();
81 delete this;
82 }
83
84 private:
85 friend class NativeControl;
86
OnCreate(LPCREATESTRUCT create_struct)87 LRESULT OnCreate(LPCREATESTRUCT create_struct) {
88 control_ = parent_->CreateNativeControl(m_hWnd);
89
90 // We subclass the control hwnd so we get the WM_KEYDOWN messages.
91 original_handler_ = gfx::SetWindowProc(
92 control_, &NativeControl::NativeControlWndProc);
93 prop_.reset(new ViewProp(control_, kNativeControlKey , parent_));
94
95 ::ShowWindow(control_, SW_SHOW);
96 return 1;
97 }
98
OnEraseBkgnd(HDC dc)99 LRESULT OnEraseBkgnd(HDC dc) {
100 return 1;
101 }
102
OnPaint(HDC ignore)103 void OnPaint(HDC ignore) {
104 PAINTSTRUCT ps;
105 HDC dc = ::BeginPaint(*this, &ps);
106 ::EndPaint(*this, &ps);
107 }
108
OnSize(int type,const CSize & sz)109 void OnSize(int type, const CSize& sz) {
110 ::MoveWindow(control_, 0, 0, sz.cx, sz.cy, TRUE);
111 }
112
OnCommand(UINT code,int id,HWND source)113 LRESULT OnCommand(UINT code, int id, HWND source) {
114 return parent_ ? parent_->OnCommand(code, id, source) : 0;
115 }
116
OnNotify(int w_param,LPNMHDR l_param)117 LRESULT OnNotify(int w_param, LPNMHDR l_param) {
118 if (parent_)
119 return parent_->OnNotify(w_param, l_param);
120 else
121 return 0;
122 }
123
OnDestroy()124 void OnDestroy() {
125 if (parent_)
126 parent_->OnDestroy();
127 }
128
OnContextMenu(HWND window,const POINT & location)129 void OnContextMenu(HWND window, const POINT& location) {
130 if (parent_)
131 parent_->OnContextMenu(location);
132 }
133
134 // We need to find an ancestor with a non-null background, and
135 // ask it for a (solid color) brush that approximates
136 // the background. The caller will use this when drawing
137 // the native control as a background color, particularly
138 // for radiobuttons and XP style pushbuttons.
OnCtlColor(UINT msg,HDC dc,HWND control)139 LRESULT OnCtlColor(UINT msg, HDC dc, HWND control) {
140 const View *ancestor = parent_;
141 while (ancestor) {
142 const Background *background = ancestor->background();
143 if (background) {
144 HBRUSH brush = background->GetNativeControlBrush();
145 if (brush)
146 return reinterpret_cast<LRESULT>(brush);
147 }
148 ancestor = ancestor->parent();
149 }
150
151 // COLOR_BTNFACE is the default for dialog box backgrounds.
152 return reinterpret_cast<LRESULT>(GetSysColorBrush(COLOR_BTNFACE));
153 }
154
OnCtlColorBtn(HDC dc,HWND control)155 LRESULT OnCtlColorBtn(HDC dc, HWND control) {
156 return OnCtlColor(WM_CTLCOLORBTN, dc, control);
157 }
158
OnCtlColorStatic(HDC dc,HWND control)159 LRESULT OnCtlColorStatic(HDC dc, HWND control) {
160 return OnCtlColor(WM_CTLCOLORSTATIC, dc, control);
161 }
162
163 NativeControl* parent_;
164 HWND control_;
165
166 // Message handler that was set before we reset it.
167 WNDPROC original_handler_;
168
169 scoped_ptr<ViewProp> prop_;
170
171 DISALLOW_COPY_AND_ASSIGN(NativeControlContainer);
172 };
173
NativeControl()174 NativeControl::NativeControl() : hwnd_view_(NULL),
175 container_(NULL),
176 fixed_width_(-1),
177 horizontal_alignment_(CENTER),
178 fixed_height_(-1),
179 vertical_alignment_(CENTER) {
180 SetFocusable(true);
181 }
182
~NativeControl()183 NativeControl::~NativeControl() {
184 if (container_) {
185 container_->ResetParent();
186 ::DestroyWindow(*container_);
187 }
188 }
189
ValidateNativeControl()190 void NativeControl::ValidateNativeControl() {
191 if (hwnd_view_ == NULL) {
192 hwnd_view_ = new NativeViewHost;
193 AddChildView(hwnd_view_);
194 }
195
196 if (!container_ && visible()) {
197 container_ = new NativeControlContainer(this);
198 container_->Init();
199 hwnd_view_->Attach(*container_);
200 if (!enabled())
201 EnableWindow(GetNativeControlHWND(), enabled());
202
203 // This message ensures that the focus border is shown.
204 ::SendMessage(container_->GetControl(),
205 WM_CHANGEUISTATE,
206 MAKELPARAM(UIS_CLEAR, UISF_HIDEFOCUS),
207 0);
208 }
209 }
210
ViewHierarchyChanged(const ViewHierarchyChangedDetails & details)211 void NativeControl::ViewHierarchyChanged(
212 const ViewHierarchyChangedDetails& details) {
213 if (details.is_add && details.parent != this && !container_ && GetWidget()) {
214 ValidateNativeControl();
215 Layout();
216 }
217 }
218
Layout()219 void NativeControl::Layout() {
220 if (!container_ && GetWidget())
221 ValidateNativeControl();
222
223 if (hwnd_view_) {
224 gfx::Rect lb = GetLocalBounds();
225
226 int x = lb.x();
227 int y = lb.y();
228 int width = lb.width();
229 int height = lb.height();
230 if (fixed_width_ > 0) {
231 width = std::min(fixed_width_, width);
232 switch (horizontal_alignment_) {
233 case LEADING:
234 // Nothing to do.
235 break;
236 case CENTER:
237 x += (lb.width() - width) / 2;
238 break;
239 case TRAILING:
240 x = x + lb.width() - width;
241 break;
242 default:
243 NOTREACHED();
244 }
245 }
246
247 if (fixed_height_ > 0) {
248 height = std::min(fixed_height_, height);
249 switch (vertical_alignment_) {
250 case LEADING:
251 // Nothing to do.
252 break;
253 case CENTER:
254 y += (lb.height() - height) / 2;
255 break;
256 case TRAILING:
257 y = y + lb.height() - height;
258 break;
259 default:
260 NOTREACHED();
261 }
262 }
263
264 hwnd_view_->SetBounds(x, y, width, height);
265 }
266 }
267
OnContextMenu(const POINT & location)268 void NativeControl::OnContextMenu(const POINT& location) {
269 if (!context_menu_controller())
270 return;
271
272 if (location.x == -1 && location.y == -1) {
273 ShowContextMenu(GetKeyboardContextMenuLocation(),
274 ui::MENU_SOURCE_KEYBOARD);
275 } else {
276 ShowContextMenu(gfx::Point(location), ui::MENU_SOURCE_MOUSE);
277 }
278 }
279
OnFocus()280 void NativeControl::OnFocus() {
281 if (container_) {
282 DCHECK(container_->GetControl());
283 ::SetFocus(container_->GetControl());
284 NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_FOCUS, false);
285 }
286 }
287
GetNativeControlHWND()288 HWND NativeControl::GetNativeControlHWND() {
289 if (container_)
290 return container_->GetControl();
291 else
292 return NULL;
293 }
294
NativeControlDestroyed()295 void NativeControl::NativeControlDestroyed() {
296 if (hwnd_view_)
297 hwnd_view_->Detach();
298 container_ = NULL;
299 }
300
SetVisible(bool is_visible)301 void NativeControl::SetVisible(bool is_visible) {
302 if (is_visible != visible()) {
303 View::SetVisible(is_visible);
304 if (!is_visible && container_)
305 ::DestroyWindow(*container_);
306 else if (is_visible && !container_)
307 ValidateNativeControl();
308 }
309 }
310
OnEnabledChanged()311 void NativeControl::OnEnabledChanged() {
312 View::OnEnabledChanged();
313 if (GetNativeControlHWND())
314 EnableWindow(GetNativeControlHWND(), enabled());
315 }
316
OnPaint(gfx::Canvas * canvas)317 void NativeControl::OnPaint(gfx::Canvas* canvas) {
318 }
319
VisibilityChanged(View * starting_from,bool is_visible)320 void NativeControl::VisibilityChanged(View* starting_from, bool is_visible) {
321 SetVisible(is_visible);
322 }
323
SetFixedWidth(int width,Alignment alignment)324 void NativeControl::SetFixedWidth(int width, Alignment alignment) {
325 DCHECK_GT(width, 0);
326 fixed_width_ = width;
327 horizontal_alignment_ = alignment;
328 }
329
SetFixedHeight(int height,Alignment alignment)330 void NativeControl::SetFixedHeight(int height, Alignment alignment) {
331 DCHECK_GT(height, 0);
332 fixed_height_ = height;
333 vertical_alignment_ = alignment;
334 }
335
GetAdditionalExStyle() const336 DWORD NativeControl::GetAdditionalExStyle() const {
337 // If the UI for the view is mirrored, we should make sure we add the
338 // extended window style for a right-to-left layout so the subclass creates
339 // a mirrored HWND for the underlying control.
340 DWORD ex_style = 0;
341 if (base::i18n::IsRTL())
342 ex_style |= l10n_util::GetExtendedStyles();
343
344 return ex_style;
345 }
346
GetAdditionalRTLStyle() const347 DWORD NativeControl::GetAdditionalRTLStyle() const {
348 // If the UI for the view is mirrored, we should make sure we add the
349 // extended window style for a right-to-left layout so the subclass creates
350 // a mirrored HWND for the underlying control.
351 DWORD ex_style = 0;
352 if (base::i18n::IsRTL())
353 ex_style |= l10n_util::GetExtendedTooltipStyles();
354
355 return ex_style;
356 }
357
358 // static
NativeControlWndProc(HWND window,UINT message,WPARAM w_param,LPARAM l_param)359 LRESULT CALLBACK NativeControl::NativeControlWndProc(HWND window,
360 UINT message,
361 WPARAM w_param,
362 LPARAM l_param) {
363 NativeControl* native_control = static_cast<NativeControl*>(
364 ViewProp::GetValue(window, kNativeControlKey));
365 DCHECK(native_control);
366 WNDPROC original_handler = native_control->container_->original_handler_;
367 DCHECK(original_handler);
368
369 if (message == WM_KEYDOWN &&
370 native_control->OnKeyDown(ui::KeyboardCodeForWindowsKeyCode(w_param))) {
371 return 0;
372 } else if (message == WM_SETFOCUS) {
373 // Let the focus manager know that the focus changed.
374 FocusManager* focus_manager = native_control->GetFocusManager();
375 if (focus_manager) {
376 focus_manager->SetFocusedView(native_control);
377 } else {
378 NOTREACHED();
379 }
380 } else if (message == WM_DESTROY) {
381 gfx::SetWindowProc(window, reinterpret_cast<WNDPROC>(original_handler));
382 native_control->container_->prop_.reset();
383 }
384
385 return CallWindowProc(reinterpret_cast<WNDPROC>(original_handler), window,
386 message, w_param, l_param);
387 }
388
389 } // namespace views
390