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: 38 explicit NativeControlContainer(NativeControl* parent) 39 : parent_(parent), 40 control_(NULL), 41 original_handler_(NULL) { 42 } 43 44 void Init() { 45 Create(parent_->GetWidget()->GetNativeView()); 46 ::ShowWindow(m_hWnd, SW_SHOW); 47 } 48 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 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. 74 void ResetParent() { 75 parent_ = NULL; 76 } 77 78 void OnFinalMessage(HWND hwnd) { 79 if (parent_) 80 parent_->NativeControlDestroyed(); 81 delete this; 82 } 83 84 private: 85 friend class NativeControl; 86 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 99 LRESULT OnEraseBkgnd(HDC dc) { 100 return 1; 101 } 102 103 void OnPaint(HDC ignore) { 104 PAINTSTRUCT ps; 105 HDC dc = ::BeginPaint(*this, &ps); 106 ::EndPaint(*this, &ps); 107 } 108 109 void OnSize(int type, const CSize& sz) { 110 ::MoveWindow(control_, 0, 0, sz.cx, sz.cy, TRUE); 111 } 112 113 LRESULT OnCommand(UINT code, int id, HWND source) { 114 return parent_ ? parent_->OnCommand(code, id, source) : 0; 115 } 116 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 124 void OnDestroy() { 125 if (parent_) 126 parent_->OnDestroy(); 127 } 128 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. 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 155 LRESULT OnCtlColorBtn(HDC dc, HWND control) { 156 return OnCtlColor(WM_CTLCOLORBTN, dc, control); 157 } 158 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 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 183 NativeControl::~NativeControl() { 184 if (container_) { 185 container_->ResetParent(); 186 ::DestroyWindow(*container_); 187 } 188 } 189 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 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 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 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 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 288 HWND NativeControl::GetNativeControlHWND() { 289 if (container_) 290 return container_->GetControl(); 291 else 292 return NULL; 293 } 294 295 void NativeControl::NativeControlDestroyed() { 296 if (hwnd_view_) 297 hwnd_view_->Detach(); 298 container_ = NULL; 299 } 300 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 311 void NativeControl::OnEnabledChanged() { 312 View::OnEnabledChanged(); 313 if (GetNativeControlHWND()) 314 EnableWindow(GetNativeControlHWND(), enabled()); 315 } 316 317 void NativeControl::OnPaint(gfx::Canvas* canvas) { 318 } 319 320 void NativeControl::VisibilityChanged(View* starting_from, bool is_visible) { 321 SetVisible(is_visible); 322 } 323 324 void NativeControl::SetFixedWidth(int width, Alignment alignment) { 325 DCHECK_GT(width, 0); 326 fixed_width_ = width; 327 horizontal_alignment_ = alignment; 328 } 329 330 void NativeControl::SetFixedHeight(int height, Alignment alignment) { 331 DCHECK_GT(height, 0); 332 fixed_height_ = height; 333 vertical_alignment_ = alignment; 334 } 335 336 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 347 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 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