Home | History | Annotate | Download | only in window
      1 // Copyright (c) 2012 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/window/non_client_view.h"
      6 
      7 #include "ui/accessibility/ax_view_state.h"
      8 #include "ui/base/hit_test.h"
      9 #include "ui/gfx/rect_conversions.h"
     10 #include "ui/views/rect_based_targeting_utils.h"
     11 #include "ui/views/view_targeter.h"
     12 #include "ui/views/widget/root_view.h"
     13 #include "ui/views/widget/widget.h"
     14 #include "ui/views/window/client_view.h"
     15 
     16 namespace views {
     17 
     18 // static
     19 const char NonClientFrameView::kViewClassName[] =
     20     "ui/views/window/NonClientFrameView";
     21 
     22 const char NonClientView::kViewClassName[] =
     23     "ui/views/window/NonClientView";
     24 
     25 // The frame view and the client view are always at these specific indices,
     26 // because the RootView message dispatch sends messages to items higher in the
     27 // z-order first and we always want the client view to have first crack at
     28 // handling mouse messages.
     29 static const int kFrameViewIndex = 0;
     30 static const int kClientViewIndex = 1;
     31 // The overlay view is always on top (index == child_count() - 1).
     32 
     33 ////////////////////////////////////////////////////////////////////////////////
     34 // NonClientView, public:
     35 
     36 NonClientView::NonClientView()
     37     : client_view_(NULL),
     38       overlay_view_(NULL) {
     39   SetEventTargeter(
     40       scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
     41 }
     42 
     43 NonClientView::~NonClientView() {
     44   // This value may have been reset before the window hierarchy shuts down,
     45   // so we need to manually remove it.
     46   RemoveChildView(frame_view_.get());
     47 }
     48 
     49 void NonClientView::SetFrameView(NonClientFrameView* frame_view) {
     50   // See comment in header about ownership.
     51   frame_view->set_owned_by_client();
     52   if (frame_view_.get())
     53     RemoveChildView(frame_view_.get());
     54   frame_view_.reset(frame_view);
     55   if (parent())
     56     AddChildViewAt(frame_view_.get(), kFrameViewIndex);
     57 }
     58 
     59 void NonClientView::SetOverlayView(View* view) {
     60   if (overlay_view_)
     61     RemoveChildView(overlay_view_);
     62 
     63   if (!view)
     64     return;
     65 
     66   overlay_view_ = view;
     67   if (parent())
     68     AddChildView(overlay_view_);
     69 }
     70 
     71 bool NonClientView::CanClose() {
     72   return client_view_->CanClose();
     73 }
     74 
     75 void NonClientView::WindowClosing() {
     76   client_view_->WidgetClosing();
     77 }
     78 
     79 void NonClientView::UpdateFrame() {
     80   Widget* widget = GetWidget();
     81   SetFrameView(widget->CreateNonClientFrameView());
     82   widget->ThemeChanged();
     83   Layout();
     84   SchedulePaint();
     85 }
     86 
     87 void NonClientView::SetInactiveRenderingDisabled(bool disable) {
     88   frame_view_->SetInactiveRenderingDisabled(disable);
     89 }
     90 
     91 gfx::Rect NonClientView::GetWindowBoundsForClientBounds(
     92     const gfx::Rect client_bounds) const {
     93   return frame_view_->GetWindowBoundsForClientBounds(client_bounds);
     94 }
     95 
     96 int NonClientView::NonClientHitTest(const gfx::Point& point) {
     97   // The NonClientFrameView is responsible for also asking the ClientView.
     98   return frame_view_->NonClientHitTest(point);
     99 }
    100 
    101 void NonClientView::GetWindowMask(const gfx::Size& size,
    102                                   gfx::Path* window_mask) {
    103   frame_view_->GetWindowMask(size, window_mask);
    104 }
    105 
    106 void NonClientView::ResetWindowControls() {
    107   frame_view_->ResetWindowControls();
    108 }
    109 
    110 void NonClientView::UpdateWindowIcon() {
    111   frame_view_->UpdateWindowIcon();
    112 }
    113 
    114 void NonClientView::UpdateWindowTitle() {
    115   frame_view_->UpdateWindowTitle();
    116 }
    117 
    118 void NonClientView::SizeConstraintsChanged() {
    119   frame_view_->SizeConstraintsChanged();
    120 }
    121 
    122 void NonClientView::LayoutFrameView() {
    123   // First layout the NonClientFrameView, which determines the size of the
    124   // ClientView...
    125   frame_view_->SetBounds(0, 0, width(), height());
    126 
    127   // We need to manually call Layout here because layout for the frame view can
    128   // change independently of the bounds changing - e.g. after the initial
    129   // display of the window the metrics of the native window controls can change,
    130   // which does not change the bounds of the window but requires a re-layout to
    131   // trigger a repaint. We override OnBoundsChanged() for the NonClientFrameView
    132   // to do nothing so that SetBounds above doesn't cause Layout to be called
    133   // twice.
    134   frame_view_->Layout();
    135 }
    136 
    137 void NonClientView::SetAccessibleName(const base::string16& name) {
    138   accessible_name_ = name;
    139 }
    140 
    141 ////////////////////////////////////////////////////////////////////////////////
    142 // NonClientView, View overrides:
    143 
    144 gfx::Size NonClientView::GetPreferredSize() const {
    145   // TODO(pkasting): This should probably be made to look similar to
    146   // GetMinimumSize() below.  This will require implementing GetPreferredSize()
    147   // better in the various frame views.
    148   gfx::Rect client_bounds(gfx::Point(), client_view_->GetPreferredSize());
    149   return GetWindowBoundsForClientBounds(client_bounds).size();
    150 }
    151 
    152 gfx::Size NonClientView::GetMinimumSize() const {
    153   return frame_view_->GetMinimumSize();
    154 }
    155 
    156 gfx::Size NonClientView::GetMaximumSize() const {
    157   return frame_view_->GetMaximumSize();
    158 }
    159 
    160 void NonClientView::Layout() {
    161   LayoutFrameView();
    162 
    163   // Then layout the ClientView, using those bounds.
    164   client_view_->SetBoundsRect(frame_view_->GetBoundsForClientView());
    165 
    166   // We need to manually call Layout on the ClientView as well for the same
    167   // reason as above.
    168   client_view_->Layout();
    169 
    170   if (overlay_view_ && overlay_view_->visible())
    171     overlay_view_->SetBoundsRect(GetLocalBounds());
    172 }
    173 
    174 void NonClientView::ViewHierarchyChanged(
    175     const ViewHierarchyChangedDetails& details) {
    176   // Add our two child views here as we are added to the Widget so that if we
    177   // are subsequently resized all the parent-child relationships are
    178   // established.
    179   if (details.is_add && GetWidget() && details.child == this) {
    180     AddChildViewAt(frame_view_.get(), kFrameViewIndex);
    181     AddChildViewAt(client_view_, kClientViewIndex);
    182     if (overlay_view_)
    183       AddChildView(overlay_view_);
    184   }
    185 }
    186 
    187 void NonClientView::GetAccessibleState(ui::AXViewState* state) {
    188   state->role = ui::AX_ROLE_CLIENT;
    189   state->name = accessible_name_;
    190 }
    191 
    192 const char* NonClientView::GetClassName() const {
    193   return kViewClassName;
    194 }
    195 
    196 View* NonClientView::GetTooltipHandlerForPoint(const gfx::Point& point) {
    197   // The same logic as for |TargetForRect()| applies here.
    198   if (frame_view_->parent() == this) {
    199     // During the reset of the frame_view_ it's possible to be in this code
    200     // after it's been removed from the view hierarchy but before it's been
    201     // removed from the NonClientView.
    202     gfx::Point point_in_child_coords(point);
    203     View::ConvertPointToTarget(this, frame_view_.get(), &point_in_child_coords);
    204     View* handler =
    205         frame_view_->GetTooltipHandlerForPoint(point_in_child_coords);
    206     if (handler)
    207       return handler;
    208   }
    209 
    210   return View::GetTooltipHandlerForPoint(point);
    211 }
    212 
    213 View* NonClientView::TargetForRect(View* root, const gfx::Rect& rect) {
    214   CHECK_EQ(root, this);
    215 
    216   if (!UsePointBasedTargeting(rect))
    217     return ViewTargeterDelegate::TargetForRect(root, rect);
    218 
    219   // Because of the z-ordering of our child views (the client view is positioned
    220   // over the non-client frame view, if the client view ever overlaps the frame
    221   // view visually (as it does for the browser window), then it will eat
    222   // events for the window controls. We override this method here so that we can
    223   // detect this condition and re-route the events to the non-client frame view.
    224   // The assumption is that the frame view's implementation of HitTest will only
    225   // return true for area not occupied by the client view.
    226   if (frame_view_->parent() == this) {
    227     // During the reset of the frame_view_ it's possible to be in this code
    228     // after it's been removed from the view hierarchy but before it's been
    229     // removed from the NonClientView.
    230     gfx::RectF rect_in_child_coords_f(rect);
    231     View::ConvertRectToTarget(this, frame_view_.get(), &rect_in_child_coords_f);
    232     gfx::Rect rect_in_child_coords = gfx::ToEnclosingRect(
    233         rect_in_child_coords_f);
    234     if (frame_view_->HitTestRect(rect_in_child_coords))
    235       return frame_view_->GetEventHandlerForRect(rect_in_child_coords);
    236   }
    237 
    238   return ViewTargeterDelegate::TargetForRect(root, rect);
    239 }
    240 
    241 ////////////////////////////////////////////////////////////////////////////////
    242 // NonClientFrameView, public:
    243 
    244 NonClientFrameView::~NonClientFrameView() {
    245 }
    246 
    247 void NonClientFrameView::SetInactiveRenderingDisabled(bool disable) {
    248   if (inactive_rendering_disabled_ == disable)
    249     return;
    250 
    251   bool should_paint_as_active_old = ShouldPaintAsActive();
    252   inactive_rendering_disabled_ = disable;
    253 
    254   // The widget schedules a paint when the activation changes.
    255   if (should_paint_as_active_old != ShouldPaintAsActive())
    256     SchedulePaint();
    257 }
    258 
    259 bool NonClientFrameView::ShouldPaintAsActive() const {
    260   return inactive_rendering_disabled_ || GetWidget()->IsActive();
    261 }
    262 
    263 int NonClientFrameView::GetHTComponentForFrame(const gfx::Point& point,
    264                                                int top_resize_border_height,
    265                                                int resize_border_thickness,
    266                                                int top_resize_corner_height,
    267                                                int resize_corner_width,
    268                                                bool can_resize) {
    269   // Tricky: In XP, native behavior is to return HTTOPLEFT and HTTOPRIGHT for
    270   // a |resize_corner_size|-length strip of both the side and top borders, but
    271   // only to return HTBOTTOMLEFT/HTBOTTOMRIGHT along the bottom border + corner
    272   // (not the side border).  Vista goes further and doesn't return these on any
    273   // of the side borders.  We allow callers to match either behavior.
    274   int component;
    275   if (point.x() < resize_border_thickness) {
    276     if (point.y() < top_resize_corner_height)
    277       component = HTTOPLEFT;
    278     else if (point.y() >= (height() - resize_border_thickness))
    279       component = HTBOTTOMLEFT;
    280     else
    281       component = HTLEFT;
    282   } else if (point.x() >= (width() - resize_border_thickness)) {
    283     if (point.y() < top_resize_corner_height)
    284       component = HTTOPRIGHT;
    285     else if (point.y() >= (height() - resize_border_thickness))
    286       component = HTBOTTOMRIGHT;
    287     else
    288       component = HTRIGHT;
    289   } else if (point.y() < top_resize_border_height) {
    290     if (point.x() < resize_corner_width)
    291       component = HTTOPLEFT;
    292     else if (point.x() >= (width() - resize_corner_width))
    293       component = HTTOPRIGHT;
    294     else
    295       component = HTTOP;
    296   } else if (point.y() >= (height() - resize_border_thickness)) {
    297     if (point.x() < resize_corner_width)
    298       component = HTBOTTOMLEFT;
    299     else if (point.x() >= (width() - resize_corner_width))
    300       component = HTBOTTOMRIGHT;
    301     else
    302       component = HTBOTTOM;
    303   } else {
    304     return HTNOWHERE;
    305   }
    306 
    307   // If the window can't be resized, there are no resize boundaries, just
    308   // window borders.
    309   return can_resize ? component : HTBORDER;
    310 }
    311 
    312 ////////////////////////////////////////////////////////////////////////////////
    313 // NonClientFrameView, protected:
    314 
    315 void NonClientFrameView::GetAccessibleState(ui::AXViewState* state) {
    316   state->role = ui::AX_ROLE_CLIENT;
    317 }
    318 
    319 const char* NonClientFrameView::GetClassName() const {
    320   return kViewClassName;
    321 }
    322 
    323 void NonClientFrameView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
    324   // Overridden to do nothing. The NonClientView manually calls Layout on the
    325   // FrameView when it is itself laid out, see comment in NonClientView::Layout.
    326 }
    327 
    328 NonClientFrameView::NonClientFrameView() : inactive_rendering_disabled_(false) {
    329   SetEventTargeter(
    330       scoped_ptr<views::ViewTargeter>(new views::ViewTargeter(this)));
    331 }
    332 
    333 // ViewTargeterDelegate:
    334 bool NonClientFrameView::DoesIntersectRect(const View* target,
    335                                            const gfx::Rect& rect) const {
    336   CHECK_EQ(target, this);
    337 
    338   // For the default case, we assume the non-client frame view never overlaps
    339   // the client view.
    340   return !GetWidget()->client_view()->bounds().Intersects(rect);
    341 }
    342 
    343 }  // namespace views
    344