blob: 3a1fe581016372aef09b0063060dc6199cb0ea18 [file] [log] [blame]
// Copyright (c) 2011 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 "ui/views/controls/native_control.h"
#include <atlbase.h>
#include <atlapp.h>
#include <atlcrack.h>
#include <atlframe.h>
#include <atlmisc.h>
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "ui/base/accessibility/accessibility_types.h"
#include "ui/base/l10n/l10n_util_win.h"
#include "ui/base/view_prop.h"
#include "ui/events/keycodes/keyboard_code_conversion_win.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/win/hwnd_util.h"
#include "ui/views/background.h"
#include "ui/views/controls/native/native_view_host.h"
#include "ui/views/focus/focus_manager.h"
#include "ui/views/widget/widget.h"
using ui::ViewProp;
namespace views {
// Maps to the NativeControl.
static const char* const kNativeControlKey = "__NATIVE_CONTROL__";
class NativeControlContainer : public CWindowImpl<NativeControlContainer,
CWindow,
CWinTraits<WS_CHILD | WS_CLIPSIBLINGS |
WS_CLIPCHILDREN>> {
public:
explicit NativeControlContainer(NativeControl* parent)
: parent_(parent),
control_(NULL),
original_handler_(NULL) {
}
void Init() {
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:
friend class NativeControl;
LRESULT OnCreate(LPCREATESTRUCT create_struct) {
control_ = parent_->CreateNativeControl(m_hWnd);
// We subclass the control hwnd so we get the WM_KEYDOWN messages.
original_handler_ = gfx::SetWindowProc(
control_, &NativeControl::NativeControlWndProc);
prop_.reset(new ViewProp(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<LRESULT>(brush);
}
ancestor = ancestor->parent();
}
// COLOR_BTNFACE is the default for dialog box backgrounds.
return reinterpret_cast<LRESULT>(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_;
// Message handler that was set before we reset it.
WNDPROC original_handler_;
scoped_ptr<ViewProp> prop_;
DISALLOW_COPY_AND_ASSIGN(NativeControlContainer);
};
NativeControl::NativeControl() : hwnd_view_(NULL),
container_(NULL),
fixed_width_(-1),
horizontal_alignment_(CENTER),
fixed_height_(-1),
vertical_alignment_(CENTER) {
set_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_ && visible()) {
container_ = new NativeControlContainer(this);
container_->Init();
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(
const ViewHierarchyChangedDetails& details) {
if (details.is_add && details.parent != this && !container_ && GetWidget()) {
ValidateNativeControl();
Layout();
}
}
void NativeControl::Layout() {
if (!container_ && GetWidget())
ValidateNativeControl();
if (hwnd_view_) {
gfx::Rect lb = GetLocalBounds();
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 (!context_menu_controller())
return;
if (location.x == -1 && location.y == -1) {
ShowContextMenu(GetKeyboardContextMenuLocation(),
ui::MENU_SOURCE_KEYBOARD);
} else {
ShowContextMenu(gfx::Point(location), ui::MENU_SOURCE_MOUSE);
}
}
void NativeControl::OnFocus() {
if (container_) {
DCHECK(container_->GetControl());
::SetFocus(container_->GetControl());
NotifyAccessibilityEvent(ui::AccessibilityTypes::EVENT_FOCUS, false);
}
}
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 is_visible) {
if (is_visible != visible()) {
View::SetVisible(is_visible);
if (!is_visible && container_)
::DestroyWindow(*container_);
else if (is_visible && !container_)
ValidateNativeControl();
}
}
void NativeControl::OnEnabledChanged() {
View::OnEnabledChanged();
if (GetNativeControlHWND())
EnableWindow(GetNativeControlHWND(), enabled());
}
void NativeControl::OnPaint(gfx::Canvas* canvas) {
}
void NativeControl::VisibilityChanged(View* starting_from, bool is_visible) {
SetVisible(is_visible);
}
void NativeControl::SetFixedWidth(int width, Alignment alignment) {
DCHECK_GT(width, 0);
fixed_width_ = width;
horizontal_alignment_ = alignment;
}
void NativeControl::SetFixedHeight(int height, Alignment alignment) {
DCHECK_GT(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 (base::i18n::IsRTL())
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 (base::i18n::IsRTL())
ex_style |= l10n_util::GetExtendedTooltipStyles();
return ex_style;
}
// static
LRESULT CALLBACK NativeControl::NativeControlWndProc(HWND window,
UINT message,
WPARAM w_param,
LPARAM l_param) {
NativeControl* native_control = static_cast<NativeControl*>(
ViewProp::GetValue(window, kNativeControlKey));
DCHECK(native_control);
WNDPROC original_handler = native_control->container_->original_handler_;
DCHECK(original_handler);
if (message == WM_KEYDOWN &&
native_control->OnKeyDown(ui::KeyboardCodeForWindowsKeyCode(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) {
gfx::SetWindowProc(window, reinterpret_cast<WNDPROC>(original_handler));
native_control->container_->prop_.reset();
}
return CallWindowProc(reinterpret_cast<WNDPROC>(original_handler), window,
message, w_param, l_param);
}
} // namespace views