// 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/view.h" #include #include #include "ui/base/dragdrop/drag_drop_types.h" #include "ui/gfx/canvas.h" #include "ui/gfx/point.h" #include "ui/gfx/size.h" #include "ui/views/events/context_menu_controller.h" #include "ui/views/events/drag_controller.h" #include "ui/views/layout/layout_manager.h" #include "ui/views/rendering/border.h" #include "ui/views/widget/widget.h" namespace ui { namespace { // Saves gfx::Canvas state upon construction and automatically restores it when // it goes out of scope. class ScopedCanvasState { public: explicit ScopedCanvasState(gfx::Canvas* canvas) : canvas_(canvas) { canvas_->Save(); } ~ScopedCanvasState() { canvas_->Restore(); } private: gfx::Canvas* canvas_; DISALLOW_COPY_AND_ASSIGN(ScopedCanvasState); }; bool ExceededDragThreshold(const gfx::Point& press_point, const gfx::Point& event_point) { // TODO(beng): implement return true; } } // namespace //////////////////////////////////////////////////////////////////////////////// // View, public: View::View() : parent_owned_(true), visible_(true), enabled_(true), id_(-1), group_(-1), parent_(NULL), focusable_(false), next_focusable_view_(NULL), prev_focusable_view_(NULL), context_menu_controller_(NULL), drag_controller_(NULL) { } View::~View() { if (parent_) parent_->RemoveChildView(this, false); for (Views::const_iterator i(children_begin()); i != children_.end(); ++i) { (*i)->parent_ = NULL; if ((*i)->parent_owned()) delete *i; } } // Size and disposition -------------------------------------------------------- gfx::Rect View::GetVisibleBounds() const { // TODO(beng): return bounds(); } gfx::Rect View::GetContentsBounds() const { gfx::Rect rect(gfx::Point(), size()); if (border_.get()) rect.Inset(border_->insets()); return rect; } void View::SetBounds(const gfx::Rect& bounds) { gfx::Rect old_bounds = bounds_; bounds_ = bounds; // TODO(beng): investigate usage of needs_layout_ in old View code. if (old_bounds != bounds_) { OnBoundsChanged(); Layout(); } } void View::SetOrigin(const gfx::Point& origin) { SetBounds(gfx::Rect(origin, size())); } void View::SetSize(const gfx::Size& size) { SetBounds(gfx::Rect(origin(), size)); } void View::SetBorder(Border* border) { border_.reset(border); } void View::OnBoundsChanged() { } gfx::Size View::GetPreferredSize() const { return gfx::Size(); } gfx::Size View::GetMinimumSize() const { return GetPreferredSize(); } void View::SetLayoutManager(LayoutManager* layout_manager) { layout_manager_.reset(layout_manager); } void View::Layout() { // The layout manager handles all child layout if present. if (layout_manager_.get()) { layout_manager_->Layout(this); } else { std::for_each(children_begin(), children_end(), std::mem_fun(&View::Layout)); } // TODO(beng): needs_layout_? SchedulePaint()? } void View::SetVisible(bool visible) { if (visible != visible_) { visible_ = visible; // InvaldateRect() checks for view visibility before proceeding, so we need // to ask the parent to invalidate our bounds. if (parent_) parent_->InvalidateRect(bounds_); } } void View::SetEnabled(bool enabled) { if (enabled != enabled_) { enabled_ = enabled; Invalidate(); } } // Attributes ------------------------------------------------------------------ View* View::GetViewByID(int id) { if (id_ == id) return this; for (Views::const_iterator i(children_begin()); i != children_end(); ++i) { View* view = (*i)->GetViewByID(id); if (view) return view; } return NULL; } void View::GetViewsInGroup(int group, Views* vec) { if (group_ == group) vec->push_back(const_cast(this)); for (Views::const_iterator i(children_begin()); i != children_end(); ++i) (*i)->GetViewsInGroup(group, vec); } View* View::GetSelectedViewForGroup(int group_id) { // TODO(beng): implementme return NULL; } // Coordinate conversion ------------------------------------------------------- // static void View::ConvertPointToView(const View& source, const View& target, gfx::Point* point) { const View* inner = NULL; const View* outer = NULL; if (source.Contains(target)) { inner = ⌖ outer = &source; } else if (target.Contains(source)) { inner = &source; outer = ⌖ } // Note that we cannot do a plain "else" here since |source| and |target| // may be in different hierarchies with no relation. if (!inner) return; gfx::Point offset; for (const View* v = inner; v != outer; v = v->parent()) offset.Offset(v->x(), v->y()); // When target is contained by source, we need to subtract the offset. // When source is contained by target, we need to add the offset. int multiplier = (inner == &target) ? -1 : 1; point->Offset(multiplier * offset.x(), multiplier * offset.y()); } // static void View::ConvertPointToScreen(const View& source, gfx::Point* point) { const Widget* widget = source.GetWidget(); if (widget) { ConvertPointToWidget(source, point); gfx::Point client_origin(widget->GetClientAreaScreenBounds().origin()); point->Offset(client_origin.x(), client_origin.y()); } } // static void View::ConvertPointToWidget(const View& source, gfx::Point* point) { for (const View* v = &source; v; v = v->parent()) point->Offset(v->x(), v->y()); } // Tree operations ------------------------------------------------------------- const Widget* View::GetWidget() const { return parent() ? parent()->GetWidget() : NULL; } void View::AddChildView(View* view) { AddChildViewAt(view, children_size()); } void View::AddChildViewAt(View* view, size_t index) { CHECK_NE(this, view) << "A view cannot be its own child."; // Remove the child from its current parent if any. if (view->parent()) view->parent()->RemoveChildView(view, false); // TODO(beng): Move focus initialization to FocusManager. InitFocusSiblings(view, index); children_.insert(children_.begin() + index, view); view->parent_ = this; // Notify the hierarchy. NotifyHierarchyChanged(view, true); // TODO(beng): Notify other objects like tooltip, layout manager, etc. // Figure out RegisterChildrenForVisibleBoundsNotification. } void View::RemoveChildView(View* view, bool delete_child) { Views::const_iterator i(std::find(children_begin(), children_end(), view)); DCHECK(i != children_end()); view->parent_ = NULL; children_.erase(i); NotifyHierarchyChanged(view, false); // TODO(beng): Notify other objects like tooltip, layout manager, etc. if (delete_child) delete view; } void View::RemoveAllChildViews(bool delete_children) { while (!children_.empty()) { RemoveChildView(children_.front(), delete_children); // TODO(beng): view deletion is actually more complicated in the old view.cc // figure out why. (it uses a ScopedVector to accumulate a list // of views to delete). } } bool View::Contains(const View& child) const { for (const View* v = &child; v; v = v->parent()) { if (v == this) return true; } return false; } // Painting -------------------------------------------------------------------- void View::Invalidate() { InvalidateRect(gfx::Rect(gfx::Point(), size())); } void View::InvalidateRect(const gfx::Rect& invalid_rect) { if (visible_ && parent_) { gfx::Rect r(invalid_rect); r.Offset(bounds_.origin()); parent_->InvalidateRect(r); } } // Input ----------------------------------------------------------------------- bool View::HitTest(const gfx::Point& point) const { // TODO(beng): Hit test mask support. return gfx::Rect(gfx::Point(), size()).Contains(point); } // Accelerators ---------------------------------------------------------------- void View::AddAccelerator(const Accelerator& accelerator) { } void View::RemoveAccelerator(const Accelerator& accelerator) { } void View::RemoveAllAccelerators() { } // Focus ----------------------------------------------------------------------- FocusManager* View::GetFocusManager() { return const_cast(static_cast(this)-> GetFocusManager()); } const FocusManager* View::GetFocusManager() const { const Widget* widget = GetWidget(); return widget ? widget->GetFocusManager() : NULL; } FocusTraversable* View::GetFocusTraversable() { return NULL; } View* View::GetNextFocusableView() { return NULL; } View* View::GetPreviousFocusableView() { return NULL; } bool View::IsFocusable() const { return focusable_ && enabled_ && visible_; } bool View::HasFocus() const { const FocusManager* focus_manager = GetFocusManager(); return focus_manager ? (focus_manager->focused_view() == this) : false; } void View::RequestFocus() { FocusManager* focus_manager = GetFocusManager(); if (focus_manager && (focus_manager->focused_view() != this)) focus_manager->SetFocusedView(this); } // Resources ------------------------------------------------------------------- ThemeProvider* View::GetThemeProvider() { Widget* widget = GetWidget(); return widget ? widget->GetThemeProvider() : NULL; } //////////////////////////////////////////////////////////////////////////////// // View, protected: // Tree operations ------------------------------------------------------------- void View::OnViewAdded(const View& parent, const View& child) { } void View::OnViewRemoved(const View& parent, const View& child) { } void View::OnViewAddedToWidget() { } void View::OnViewRemovedFromWidget() { } // Painting -------------------------------------------------------------------- void View::PaintChildren(gfx::Canvas* canvas) { std::for_each(children_begin(), children_end(), std::bind2nd(std::mem_fun(&View::Paint), canvas)); } void View::OnPaint(gfx::Canvas* canvas) { // TODO(beng): investigate moving these function calls to Paint(). OnPaintBackground(canvas); OnPaintFocusBorder(canvas); OnPaintBorder(canvas); } void View::OnPaintBackground(gfx::Canvas* canvas) { } void View::OnPaintBorder(gfx::Canvas* canvas) { if (border_.get()) border_->Paint(this, canvas); } void View::OnPaintFocusBorder(gfx::Canvas* canvas) { } // Input ----------------------------------------------------------------------- View* View::GetEventHandlerForPoint(const gfx::Point& point) { for (Views::const_reverse_iterator i(children_rbegin()); i != children_rend(); ++i) { View* child = *i; if (child->visible()) { gfx::Point point_in_child_coords(point); View::ConvertPointToView(*this, *child, &point_in_child_coords); if (child->HitTest(point_in_child_coords)) return child->GetEventHandlerForPoint(point_in_child_coords); } } return this; } gfx::NativeCursor View::GetCursorForPoint(const gfx::Point& point) const { return NULL; } bool View::OnKeyPressed(const KeyEvent& event) { return true; } bool View::OnKeyReleased(const KeyEvent& event) { return true; } bool View::OnMouseWheel(const MouseWheelEvent& event) { return true; } bool View::OnMousePressed(const MouseEvent& event) { return true; } bool View::OnMouseDragged(const MouseEvent& event) { return true; } void View::OnMouseReleased(const MouseEvent& event) { } void View::OnMouseCaptureLost() { } void View::OnMouseMoved(const MouseEvent& event) { } void View::OnMouseEntered(const MouseEvent& event) { } void View::OnMouseExited(const MouseEvent& event) { } // Accelerators ---------------------------------------------------------------- bool View::OnAcceleratorPressed(const Accelerator& accelerator) { return false; } // Focus ----------------------------------------------------------------------- bool View::SkipDefaultKeyEventProcessing(const KeyEvent& event) const { return false; } bool View::IsGroupFocusTraversable() const { return true; } bool View::IsFocusableInRootView() const { // TODO(beng): kill this, replace with direct check in focus manager. return IsFocusable(); } bool View::IsAccessibilityFocusableInRootView() const { // TODO(beng): kill this, replace with direct check in focus manager. return false; } FocusTraversable* View::GetPaneFocusTraversable() { // TODO(beng): figure out what to do about this. return NULL; } void View::OnFocus(const FocusEvent& event) { } void View::OnBlur(const FocusEvent& event) { } //////////////////////////////////////////////////////////////////////////////// // View, private: void View::DragInfo::Reset() { possible_drag = false; press_point = gfx::Point(); } void View::DragInfo::PossibleDrag(const gfx::Point& point) { possible_drag = true; press_point = point; } // Tree operations ------------------------------------------------------------- void View::NotifyHierarchyChanged(View* child, bool is_add) { // Notify the hierarchy up. for (View* v = parent(); v; v = v->parent()) CallViewNotification(v, *child, is_add, false); // Notify the hierarchy down. bool has_widget = GetWidget() != NULL; if (!is_add) { // Because |child| has already been removed from |parent|'s child list, we // need to notify its hierarchy manually. child->NotifyHierarchyChangedDown(*child, is_add, has_widget); } NotifyHierarchyChangedDown(*child, is_add, has_widget); } void View::NotifyHierarchyChangedDown(const View& child, bool is_add, bool has_widget) { CallViewNotification(this, child, is_add, has_widget); for (Views::const_iterator i(children_begin()); i != children_end(); ++i) (*i)->NotifyHierarchyChangedDown(child, is_add, has_widget); } void View::CallViewNotification(View* target, const View& child, bool is_add, bool has_widget) { if (is_add) { target->OnViewAdded(*this, child); if (has_widget) target->OnViewAddedToWidget(); } else { target->OnViewRemoved(*this, child); if (has_widget) target->OnViewRemovedFromWidget(); } } // Painting -------------------------------------------------------------------- void View::Paint(gfx::Canvas* canvas) { // Invisible views are not painted. if (!visible_) return; ScopedCanvasState canvas_state(canvas); if (canvas->ClipRectInt(x(), y(), width(), height())) { canvas->TranslateInt(x(), y()); // TODO(beng): RTL ScopedCanvasState canvas_state(canvas); OnPaint(canvas); PaintChildren(canvas); } } // Input ----------------------------------------------------------------------- bool View::MousePressed(const MouseEvent& event, DragInfo* drag_info) { bool handled = OnMousePressed(event); // TODO(beng): deal with view deletion, see ProcessMousePressed() in old code. if (!enabled_) return handled; if (!event.IsOnlyLeftMouseButton() || !HitTest(event.location()) || (GetDragOperations(event.location()) == DragDropTypes::DRAG_NONE)) return handled || (event.IsRightMouseButton() && context_menu_controller_); drag_info->PossibleDrag(event.location()); return true; } bool View::MouseDragged(const MouseEvent& event, DragInfo* drag_info) { if (drag_info->possible_drag && ExceededDragThreshold(drag_info->press_point, event.location())) { if (!drag_controller_ || drag_controller_->CanStartDrag(this, drag_info->press_point, event.location())) StartShellDrag(event, drag_info->press_point); } else if (OnMouseDragged(event)) { return true; } // TODO(beng): Handle view deletion from OnMouseDragged(). return context_menu_controller_ || drag_info->possible_drag; } void View::MouseReleased(const MouseEvent& event) { OnMouseReleased(event); // TODO(beng): Handle view deletion from OnMouseReleased(). if (context_menu_controller_ && event.IsOnlyRightMouseButton()) { gfx::Point location(event.location()); if (HitTest(location)) { ConvertPointToScreen(*this, &location); context_menu_controller_->ShowContextMenu(this, location, true); } } } // Focus ----------------------------------------------------------------------- // TODO(beng): Move to FocusManager. void View::InitFocusSiblings(View* view, size_t index) { if (children_empty()) { view->next_focusable_view_ = NULL; view->prev_focusable_view_ = NULL; return; } if (index != children_size()) { View* prev = children_[index]->GetPreviousFocusableView(); view->prev_focusable_view_ = prev; view->next_focusable_view_ = children_[index]; if (prev) prev->next_focusable_view_ = view; children_[index]->prev_focusable_view_ = view; return; } // We are inserting at the end, but the end of the child list may not be // the last focusable element. Let's try to find an element with no next // focusable element to link to. for (Views::const_iterator i(children_begin()); i != children_end(); ++i) { if (!(*i)->next_focusable_view_) { (*i)->next_focusable_view_ = view; view->next_focusable_view_ = NULL; view->prev_focusable_view_ = *i; return; } } // Hum... there is a cycle in the focus list. Let's just insert ourself // after the last child. View* prev = children_[index - 1]; view->prev_focusable_view_ = prev; view->next_focusable_view_ = prev->next_focusable_view_; prev->next_focusable_view_->prev_focusable_view_ = view; prev->next_focusable_view_ = view; } // Drag & Drop ----------------------------------------------------------------- int View::GetDragOperations(const gfx::Point& point) { return drag_controller_ ? drag_controller_->GetDragOperations(this, point) : DragDropTypes::DRAG_NONE; } void View::WriteDragData(const gfx::Point& point, OSExchangeData* data) { drag_controller_->WriteDragData(this, point, data); } void View::StartShellDrag(const MouseEvent& event, const gfx::Point& press_point) { // TODO(beng): system stuff. } } // namespace ui