// Copyright (c) 2009 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "views/controls/native_control.h" #include #include #include #include #include #include "app/gfx/native_theme_win.h" #include "app/l10n_util_win.h" #include "base/keyboard_codes.h" #include "base/logging.h" #include "base/win_util.h" #include "views/background.h" #include "views/border.h" #include "views/controls/native/native_view_host.h" #include "views/focus/focus_manager.h" #include "views/widget/widget.h" namespace views { // Maps to the original WNDPROC for the controller window before we subclassed // it. static const wchar_t* const kHandlerKey = L"__CONTROL_ORIGINAL_MESSAGE_HANDLER__"; // Maps to the NativeControl. static const wchar_t* const kNativeControlKey = L"__NATIVE_CONTROL__"; class NativeControlContainer : public CWindowImpl> { public: explicit NativeControlContainer(NativeControl* parent) : parent_(parent), control_(NULL) { Create(parent->GetWidget()->GetNativeView()); ::ShowWindow(m_hWnd, SW_SHOW); } virtual ~NativeControlContainer() { } // NOTE: If you add a new message, be sure and verify parent_ is valid before // calling into parent_. DECLARE_FRAME_WND_CLASS(L"ChromeViewsNativeControlContainer", NULL); BEGIN_MSG_MAP(NativeControlContainer); MSG_WM_CREATE(OnCreate); MSG_WM_ERASEBKGND(OnEraseBkgnd); MSG_WM_PAINT(OnPaint); MSG_WM_SIZE(OnSize); MSG_WM_NOTIFY(OnNotify); MSG_WM_COMMAND(OnCommand); MSG_WM_DESTROY(OnDestroy); MSG_WM_CONTEXTMENU(OnContextMenu); MSG_WM_CTLCOLORBTN(OnCtlColorBtn); MSG_WM_CTLCOLORSTATIC(OnCtlColorStatic) END_MSG_MAP(); HWND GetControl() { return control_; } // Called when the parent is getting deleted. This control stays around until // it gets the OnFinalMessage call. void ResetParent() { parent_ = NULL; } void OnFinalMessage(HWND hwnd) { if (parent_) parent_->NativeControlDestroyed(); delete this; } private: LRESULT OnCreate(LPCREATESTRUCT create_struct) { control_ = parent_->CreateNativeControl(m_hWnd); // We subclass the control hwnd so we get the WM_KEYDOWN messages. WNDPROC original_handler = win_util::SetWindowProc(control_, &NativeControl::NativeControlWndProc); SetProp(control_, kHandlerKey, original_handler); SetProp(control_, kNativeControlKey , parent_); ::ShowWindow(control_, SW_SHOW); return 1; } LRESULT OnEraseBkgnd(HDC dc) { return 1; } void OnPaint(HDC ignore) { PAINTSTRUCT ps; HDC dc = ::BeginPaint(*this, &ps); ::EndPaint(*this, &ps); } void OnSize(int type, const CSize& sz) { ::MoveWindow(control_, 0, 0, sz.cx, sz.cy, TRUE); } LRESULT OnCommand(UINT code, int id, HWND source) { return parent_ ? parent_->OnCommand(code, id, source) : 0; } LRESULT OnNotify(int w_param, LPNMHDR l_param) { if (parent_) return parent_->OnNotify(w_param, l_param); else return 0; } void OnDestroy() { if (parent_) parent_->OnDestroy(); } void OnContextMenu(HWND window, const POINT& location) { if (parent_) parent_->OnContextMenu(location); } // We need to find an ancestor with a non-null background, and // ask it for a (solid color) brush that approximates // the background. The caller will use this when drawing // the native control as a background color, particularly // for radiobuttons and XP style pushbuttons. LRESULT OnCtlColor(UINT msg, HDC dc, HWND control) { const View *ancestor = parent_; while (ancestor) { const Background *background = ancestor->background(); if (background) { HBRUSH brush = background->GetNativeControlBrush(); if (brush) return reinterpret_cast(brush); } ancestor = ancestor->GetParent(); } // COLOR_BTNFACE is the default for dialog box backgrounds. return reinterpret_cast(GetSysColorBrush(COLOR_BTNFACE)); } LRESULT OnCtlColorBtn(HDC dc, HWND control) { return OnCtlColor(WM_CTLCOLORBTN, dc, control); } LRESULT OnCtlColorStatic(HDC dc, HWND control) { return OnCtlColor(WM_CTLCOLORSTATIC, dc, control); } NativeControl* parent_; HWND control_; DISALLOW_EVIL_CONSTRUCTORS(NativeControlContainer); }; NativeControl::NativeControl() : hwnd_view_(NULL), container_(NULL), fixed_width_(-1), horizontal_alignment_(CENTER), fixed_height_(-1), vertical_alignment_(CENTER) { enabled_ = true; focusable_ = true; } NativeControl::~NativeControl() { if (container_) { container_->ResetParent(); ::DestroyWindow(*container_); } } void NativeControl::ValidateNativeControl() { if (hwnd_view_ == NULL) { hwnd_view_ = new NativeViewHost; AddChildView(hwnd_view_); } if (!container_ && IsVisible()) { container_ = new NativeControlContainer(this); hwnd_view_->Attach(*container_); if (!enabled_) EnableWindow(GetNativeControlHWND(), enabled_); // This message ensures that the focus border is shown. ::SendMessage(container_->GetControl(), WM_CHANGEUISTATE, MAKELPARAM(UIS_CLEAR, UISF_HIDEFOCUS), 0); } } void NativeControl::ViewHierarchyChanged(bool is_add, View *parent, View *child) { if (is_add && child == this && GetWidget()) { ValidateNativeControl(); Layout(); } } void NativeControl::Layout() { if (!container_ && GetWidget()) ValidateNativeControl(); if (hwnd_view_) { gfx::Rect lb = GetLocalBounds(false); int x = lb.x(); int y = lb.y(); int width = lb.width(); int height = lb.height(); if (fixed_width_ > 0) { width = std::min(fixed_width_, width); switch (horizontal_alignment_) { case LEADING: // Nothing to do. break; case CENTER: x += (lb.width() - width) / 2; break; case TRAILING: x = x + lb.width() - width; break; default: NOTREACHED(); } } if (fixed_height_ > 0) { height = std::min(fixed_height_, height); switch (vertical_alignment_) { case LEADING: // Nothing to do. break; case CENTER: y += (lb.height() - height) / 2; break; case TRAILING: y = y + lb.height() - height; break; default: NOTREACHED(); } } hwnd_view_->SetBounds(x, y, width, height); } } void NativeControl::OnContextMenu(const POINT& location) { if (!GetContextMenuController()) return; int x = location.x; int y = location.y; bool is_mouse = true; if (x == -1 && y == -1) { gfx::Point point = GetKeyboardContextMenuLocation(); x = point.x(); y = point.y(); is_mouse = false; } ShowContextMenu(x, y, is_mouse); } void NativeControl::Focus() { if (container_) { DCHECK(container_->GetControl()); ::SetFocus(container_->GetControl()); } } HWND NativeControl::GetNativeControlHWND() { if (container_) return container_->GetControl(); else return NULL; } void NativeControl::NativeControlDestroyed() { if (hwnd_view_) hwnd_view_->Detach(); container_ = NULL; } void NativeControl::SetVisible(bool f) { if (f != IsVisible()) { View::SetVisible(f); if (!f && container_) { ::DestroyWindow(*container_); } else if (f && !container_) { ValidateNativeControl(); } } } void NativeControl::SetEnabled(bool enabled) { if (enabled_ != enabled) { View::SetEnabled(enabled); if (GetNativeControlHWND()) { EnableWindow(GetNativeControlHWND(), enabled_); } } } void NativeControl::Paint(gfx::Canvas* canvas) { } void NativeControl::VisibilityChanged(View* starting_from, bool is_visible) { SetVisible(is_visible); } void NativeControl::SetFixedWidth(int width, Alignment alignment) { DCHECK(width > 0); fixed_width_ = width; horizontal_alignment_ = alignment; } void NativeControl::SetFixedHeight(int height, Alignment alignment) { DCHECK(height > 0); fixed_height_ = height; vertical_alignment_ = alignment; } DWORD NativeControl::GetAdditionalExStyle() const { // If the UI for the view is mirrored, we should make sure we add the // extended window style for a right-to-left layout so the subclass creates // a mirrored HWND for the underlying control. DWORD ex_style = 0; if (UILayoutIsRightToLeft()) ex_style |= l10n_util::GetExtendedStyles(); return ex_style; } DWORD NativeControl::GetAdditionalRTLStyle() const { // If the UI for the view is mirrored, we should make sure we add the // extended window style for a right-to-left layout so the subclass creates // a mirrored HWND for the underlying control. DWORD ex_style = 0; if (UILayoutIsRightToLeft()) ex_style |= l10n_util::GetExtendedTooltipStyles(); return ex_style; } // static LRESULT CALLBACK NativeControl::NativeControlWndProc(HWND window, UINT message, WPARAM w_param, LPARAM l_param) { HANDLE original_handler = GetProp(window, kHandlerKey); DCHECK(original_handler); NativeControl* native_control = static_cast(GetProp(window, kNativeControlKey)); DCHECK(native_control); if (message == WM_KEYDOWN && native_control->OnKeyDown(win_util::WinToKeyboardCode(w_param))) { return 0; } else if (message == WM_SETFOCUS) { // Let the focus manager know that the focus changed. FocusManager* focus_manager = native_control->GetFocusManager(); if (focus_manager) { focus_manager->SetFocusedView(native_control); } else { NOTREACHED(); } } else if (message == WM_DESTROY) { win_util::SetWindowProc(window, reinterpret_cast(original_handler)); RemoveProp(window, kHandlerKey); RemoveProp(window, kNativeControlKey); } return CallWindowProc(reinterpret_cast(original_handler), window, message, w_param, l_param); } } // namespace views