Home | History | Annotate | Download | only in controls
      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/keycodes/keyboard_code_conversion_win.h"
     17 #include "ui/base/keycodes/keyboard_codes.h"
     18 #include "ui/base/l10n/l10n_util_win.h"
     19 #include "ui/base/view_prop.h"
     20 #include "ui/base/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_ = ui::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   set_focusable(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     ui::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