blob: a56ec998c27f637299297b20e202105cabf76910 [file] [log] [blame]
// Copyright (c) 2010 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/widget/root_view.h"
#include <algorithm>
#include "app/drag_drop_types.h"
#include "base/keyboard_codes.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "gfx/canvas_skia.h"
#include "views/fill_layout.h"
#include "views/focus/view_storage.h"
#include "views/widget/widget.h"
#include "views/window/window.h"
#if defined(OS_LINUX)
#include "views/widget/widget_gtk.h"
#endif // defined(OS_LINUX)
namespace views {
/////////////////////////////////////////////////////////////////////////////
//
// A Task to trigger non urgent painting.
//
/////////////////////////////////////////////////////////////////////////////
class PaintTask : public Task {
public:
explicit PaintTask(RootView* target) : root_view_(target) {
}
~PaintTask() {}
void Cancel() {
root_view_ = NULL;
}
void Run() {
if (root_view_)
root_view_->PaintNow();
}
private:
// The target root view.
RootView* root_view_;
DISALLOW_COPY_AND_ASSIGN(PaintTask);
};
const char RootView::kViewClassName[] = "views/RootView";
/////////////////////////////////////////////////////////////////////////////
//
// RootView - constructors, destructors, initialization
//
/////////////////////////////////////////////////////////////////////////////
RootView::RootView(Widget* widget)
: mouse_pressed_handler_(NULL),
mouse_move_handler_(NULL),
last_click_handler_(NULL),
widget_(widget),
ALLOW_THIS_IN_INITIALIZER_LIST(focus_search_(this, false, false)),
invalid_rect_urgent_(false),
pending_paint_task_(NULL),
paint_task_needed_(false),
explicit_mouse_handler_(false),
#if defined(OS_WIN)
previous_cursor_(NULL),
#endif
default_keyboard_handler_(NULL),
focus_on_mouse_pressed_(false),
ignore_set_focus_calls_(false),
focus_traversable_parent_(NULL),
focus_traversable_parent_view_(NULL),
drag_view_(NULL)
#ifndef NDEBUG
,
is_processing_paint_(false)
#endif
{
}
RootView::~RootView() {
// If we have children remove them explicitly so to make sure a remove
// notification is sent for each one of them.
if (!child_views_.empty())
RemoveAllChildViews(true);
if (pending_paint_task_)
pending_paint_task_->Cancel(); // Ensure we're not called any more.
}
void RootView::SetContentsView(View* contents_view) {
DCHECK(contents_view && GetWidget()->GetNativeView()) <<
"Can't be called until after the native view is created!";
// The ContentsView must be set up _after_ the window is created so that its
// Widget pointer is valid.
SetLayoutManager(new FillLayout);
if (GetChildViewCount() != 0)
RemoveAllChildViews(true);
AddChildView(contents_view);
// Force a layout now, since the attached hierarchy won't be ready for the
// containing window's bounds. Note that we call Layout directly rather than
// calling the widget's size changed handler, since the RootView's bounds may
// not have changed, which will cause the Layout not to be done otherwise.
Layout();
}
/////////////////////////////////////////////////////////////////////////////
//
// RootView - layout, painting
//
/////////////////////////////////////////////////////////////////////////////
void RootView::SchedulePaint(const gfx::Rect& r, bool urgent) {
// If there is an existing invalid rect, add the union of the scheduled
// rect with the invalid rect. This could be optimized further if
// necessary.
if (invalid_rect_.IsEmpty())
invalid_rect_ = r;
else
invalid_rect_ = invalid_rect_.Union(r);
if (urgent || invalid_rect_urgent_) {
invalid_rect_urgent_ = true;
} else {
if (!pending_paint_task_) {
pending_paint_task_ = new PaintTask(this);
MessageLoop::current()->PostTask(FROM_HERE, pending_paint_task_);
}
paint_task_needed_ = true;
}
}
void RootView::SchedulePaint() {
View::SchedulePaint();
}
#ifndef NDEBUG
// Sets the value of RootView's |is_processing_paint_| member to true as long
// as ProcessPaint is being called. Sets it to |false| when it returns.
class ScopedProcessingPaint {
public:
explicit ScopedProcessingPaint(bool* is_processing_paint)
: is_processing_paint_(is_processing_paint) {
*is_processing_paint_ = true;
}
~ScopedProcessingPaint() {
*is_processing_paint_ = false;
}
private:
bool* is_processing_paint_;
DISALLOW_COPY_AND_ASSIGN(ScopedProcessingPaint);
};
#endif
void RootView::ProcessPaint(gfx::Canvas* canvas) {
#ifndef NDEBUG
ScopedProcessingPaint processing_paint(&is_processing_paint_);
#endif
// Clip the invalid rect to our bounds. If a view is in a scrollview
// it could be a lot larger
invalid_rect_ = GetScheduledPaintRectConstrainedToSize();
if (invalid_rect_.IsEmpty())
return;
// Clear the background.
canvas->AsCanvasSkia()->drawColor(SK_ColorBLACK, SkXfermode::kClear_Mode);
// Save the current transforms.
canvas->Save();
// Set the clip rect according to the invalid rect.
int clip_x = invalid_rect_.x() + x();
int clip_y = invalid_rect_.y() + y();
canvas->ClipRectInt(clip_x, clip_y, invalid_rect_.width(),
invalid_rect_.height());
// Paint the tree
View::ProcessPaint(canvas);
// Restore the previous transform
canvas->Restore();
ClearPaintRect();
}
void RootView::PaintNow() {
if (pending_paint_task_) {
pending_paint_task_->Cancel();
pending_paint_task_ = NULL;
}
if (!paint_task_needed_)
return;
Widget* widget = GetWidget();
if (widget)
widget->PaintNow(invalid_rect_);
}
bool RootView::NeedsPainting(bool urgent) {
bool has_invalid_rect = !invalid_rect_.IsEmpty();
if (urgent) {
if (invalid_rect_urgent_)
return has_invalid_rect;
else
return false;
} else {
return has_invalid_rect;
}
}
const gfx::Rect& RootView::GetScheduledPaintRect() {
return invalid_rect_;
}
gfx::Rect RootView::GetScheduledPaintRectConstrainedToSize() {
if (invalid_rect_.IsEmpty())
return invalid_rect_;
return invalid_rect_.Intersect(GetLocalBounds(true));
}
/////////////////////////////////////////////////////////////////////////////
//
// RootView - tree
//
/////////////////////////////////////////////////////////////////////////////
Widget* RootView::GetWidget() const {
return widget_;
}
void RootView::NotifyThemeChanged() {
View::PropagateThemeChanged();
}
void RootView::NotifyLocaleChanged() {
View::PropagateLocaleChanged();
}
/////////////////////////////////////////////////////////////////////////////
//
// RootView - event dispatch and propagation
//
/////////////////////////////////////////////////////////////////////////////
void RootView::ViewHierarchyChanged(bool is_add, View* parent, View* child) {
if (!is_add) {
if (!explicit_mouse_handler_ && mouse_pressed_handler_ == child) {
mouse_pressed_handler_ = NULL;
}
if (widget_)
widget_->ViewHierarchyChanged(is_add, parent, child);
if (mouse_move_handler_ == child) {
mouse_move_handler_ = NULL;
}
if (GetFocusedView() == child) {
FocusView(NULL);
}
if (child == drag_view_)
drag_view_ = NULL;
if (default_keyboard_handler_ == child) {
default_keyboard_handler_ = NULL;
}
FocusManager* focus_manager = widget_->GetFocusManager();
// An unparanted RootView does not have a FocusManager.
if (focus_manager)
focus_manager->ViewRemoved(parent, child);
ViewStorage::GetSharedInstance()->ViewRemoved(parent, child);
}
}
void RootView::SetFocusOnMousePressed(bool f) {
focus_on_mouse_pressed_ = f;
}
bool RootView::OnMousePressed(const MouseEvent& e) {
// This function does not normally handle non-client messages except for
// non-client double-clicks. Actually, all double-clicks are special as the
// are formed from a single-click followed by a double-click event. When the
// double-click event lands on a different view than its single-click part,
// we transform it into a single-click which prevents odd things.
if ((e.GetFlags() & MouseEvent::EF_IS_NON_CLIENT) &&
!(e.GetFlags() & MouseEvent::EF_IS_DOUBLE_CLICK)) {
last_click_handler_ = NULL;
return false;
}
UpdateCursor(e);
SetMouseLocationAndFlags(e);
// If mouse_pressed_handler_ is non null, we are currently processing
// a pressed -> drag -> released session. In that case we send the
// event to mouse_pressed_handler_
if (mouse_pressed_handler_) {
MouseEvent mouse_pressed_event(e, this, mouse_pressed_handler_);
drag_info.Reset();
mouse_pressed_handler_->ProcessMousePressed(mouse_pressed_event,
&drag_info);
return true;
}
DCHECK(!explicit_mouse_handler_);
bool hit_disabled_view = false;
// Walk up the tree until we find a view that wants the mouse event.
for (mouse_pressed_handler_ = GetViewForPoint(e.location());
mouse_pressed_handler_ && (mouse_pressed_handler_ != this);
mouse_pressed_handler_ = mouse_pressed_handler_->GetParent()) {
if (!mouse_pressed_handler_->IsEnabled()) {
// Disabled views should eat events instead of propagating them upwards.
hit_disabled_view = true;
break;
}
// See if this view wants to handle the mouse press.
MouseEvent mouse_pressed_event(e, this, mouse_pressed_handler_);
// Remove the double-click flag if the handler is different than the
// one which got the first click part of the double-click.
if (mouse_pressed_handler_ != last_click_handler_)
mouse_pressed_event.set_flags(e.GetFlags() &
~MouseEvent::EF_IS_DOUBLE_CLICK);
drag_info.Reset();
bool handled = mouse_pressed_handler_->ProcessMousePressed(
mouse_pressed_event, &drag_info);
// The view could have removed itself from the tree when handling
// OnMousePressed(). In this case, the removal notification will have
// reset mouse_pressed_handler_ to NULL out from under us. Detect this
// case and stop. (See comments in view.h.)
//
// NOTE: Don't return true here, because we don't want the frame to
// forward future events to us when there's no handler.
if (!mouse_pressed_handler_)
break;
// If the view handled the event, leave mouse_pressed_handler_ set and
// return true, which will cause subsequent drag/release events to get
// forwarded to that view.
if (handled) {
last_click_handler_ = mouse_pressed_handler_;
return true;
}
}
// Reset mouse_pressed_handler_ to indicate that no processing is occurring.
mouse_pressed_handler_ = NULL;
if (focus_on_mouse_pressed_) {
#if defined(OS_WIN)
HWND hwnd = GetWidget()->GetNativeView();
if (::GetFocus() != hwnd)
::SetFocus(hwnd);
#else
GtkWidget* widget = GetWidget()->GetNativeView();
if (!gtk_widget_is_focus(widget))
gtk_widget_grab_focus(widget);
#endif
}
// In the event that a double-click is not handled after traversing the
// entire hierarchy (even as a single-click when sent to a different view),
// it must be marked as handled to avoid anything happening from default
// processing if it the first click-part was handled by us.
if (last_click_handler_ && e.GetFlags() & MouseEvent::EF_IS_DOUBLE_CLICK)
hit_disabled_view = true;
last_click_handler_ = NULL;
return hit_disabled_view;
}
bool RootView::ConvertPointToMouseHandler(const gfx::Point& l,
gfx::Point* p) {
//
// If the mouse_handler was set explicitly, we need to keep
// sending events even if it was reparented in a different
// window. (a non explicit mouse handler is automatically
// cleared when the control is removed from the hierarchy)
if (explicit_mouse_handler_) {
if (mouse_pressed_handler_->GetWidget()) {
*p = l;
ConvertPointToScreen(this, p);
ConvertPointToView(NULL, mouse_pressed_handler_, p);
} else {
// If the mouse_pressed_handler_ is not connected, we send the
// event in screen coordinate system
*p = l;
ConvertPointToScreen(this, p);
return true;
}
} else {
*p = l;
ConvertPointToView(this, mouse_pressed_handler_, p);
}
return true;
}
void RootView::UpdateCursor(const MouseEvent& e) {
gfx::NativeCursor cursor = NULL;
View* v = GetViewForPoint(e.location());
if (v && v != this) {
gfx::Point l(e.location());
View::ConvertPointToView(this, v, &l);
cursor = v->GetCursorForPoint(e.GetType(), l);
}
SetActiveCursor(cursor);
}
bool RootView::OnMouseDragged(const MouseEvent& e) {
UpdateCursor(e);
if (mouse_pressed_handler_) {
SetMouseLocationAndFlags(e);
gfx::Point p;
ConvertPointToMouseHandler(e.location(), &p);
MouseEvent mouse_event(e.GetType(), p.x(), p.y(), e.GetFlags());
return mouse_pressed_handler_->ProcessMouseDragged(mouse_event, &drag_info);
}
return false;
}
void RootView::OnMouseReleased(const MouseEvent& e, bool canceled) {
UpdateCursor(e);
if (mouse_pressed_handler_) {
gfx::Point p;
ConvertPointToMouseHandler(e.location(), &p);
MouseEvent mouse_released(e.GetType(), p.x(), p.y(), e.GetFlags());
// We allow the view to delete us from ProcessMouseReleased. As such,
// configure state such that we're done first, then call View.
View* mouse_pressed_handler = mouse_pressed_handler_;
mouse_pressed_handler_ = NULL;
explicit_mouse_handler_ = false;
mouse_pressed_handler->ProcessMouseReleased(mouse_released, canceled);
// WARNING: we may have been deleted.
}
}
void RootView::OnMouseMoved(const MouseEvent& e) {
View* v = GetViewForPoint(e.location());
// Find the first enabled view, or the existing move handler, whichever comes
// first. The check for the existing handler is because if a view becomes
// disabled while handling moves, it's wrong to suddenly send ET_MOUSE_EXITED
// and ET_MOUSE_ENTERED events, because the mouse hasn't actually exited yet.
while (v && !v->IsEnabled() && (v != mouse_move_handler_))
v = v->GetParent();
if (v && v != this) {
if (v != mouse_move_handler_) {
if (mouse_move_handler_ != NULL) {
MouseEvent exited_event(Event::ET_MOUSE_EXITED, 0, 0, 0);
mouse_move_handler_->OnMouseExited(exited_event);
}
mouse_move_handler_ = v;
MouseEvent entered_event(Event::ET_MOUSE_ENTERED,
this,
mouse_move_handler_,
e.location(),
0);
mouse_move_handler_->OnMouseEntered(entered_event);
}
MouseEvent moved_event(Event::ET_MOUSE_MOVED,
this,
mouse_move_handler_,
e.location(),
0);
mouse_move_handler_->OnMouseMoved(moved_event);
gfx::NativeCursor cursor = mouse_move_handler_->GetCursorForPoint(
moved_event.GetType(), moved_event.location());
SetActiveCursor(cursor);
} else if (mouse_move_handler_ != NULL) {
MouseEvent exited_event(Event::ET_MOUSE_EXITED, 0, 0, 0);
mouse_move_handler_->OnMouseExited(exited_event);
SetActiveCursor(NULL);
}
}
void RootView::ProcessOnMouseExited() {
if (mouse_move_handler_ != NULL) {
MouseEvent exited_event(Event::ET_MOUSE_EXITED, 0, 0, 0);
mouse_move_handler_->OnMouseExited(exited_event);
mouse_move_handler_ = NULL;
}
}
void RootView::SetMouseHandler(View *new_mh) {
// If we're clearing the mouse handler, clear explicit_mouse_handler as well.
explicit_mouse_handler_ = (new_mh != NULL);
mouse_pressed_handler_ = new_mh;
}
void RootView::ProcessMouseDragCanceled() {
if (mouse_pressed_handler_) {
// Synthesize a release event.
MouseEvent release_event(Event::ET_MOUSE_RELEASED, last_mouse_event_x_,
last_mouse_event_y_, last_mouse_event_flags_);
OnMouseReleased(release_event, true);
}
}
void RootView::FocusView(View* view) {
if (view != GetFocusedView()) {
FocusManager* focus_manager = GetFocusManager();
// TODO(jcampan): This fails under WidgetGtk with TYPE_CHILD.
// (see http://crbug.com/21335) Reenable DCHECK and
// verify GetFocusManager works as expecte.
#if defined(OS_WIN)
DCHECK(focus_manager) << "No Focus Manager for Window " <<
(GetWidget() ? GetWidget()->GetNativeView() : 0);
#endif
if (!focus_manager)
return;
focus_manager->SetFocusedView(view);
}
}
View* RootView::GetFocusedView() {
FocusManager* focus_manager = GetFocusManager();
if (!focus_manager) {
// We may not have a FocusManager when the window that contains us is being
// deleted. Sadly we cannot wait for the window to be destroyed before we
// remove the FocusManager (see xp_frame.cc for more info).
return NULL;
}
// Make sure the focused view belongs to this RootView's view hierarchy.
View* view = focus_manager->GetFocusedView();
if (view && (view->GetRootView() == this))
return view;
return NULL;
}
FocusSearch* RootView::GetFocusSearch() {
return &focus_search_;
}
FocusTraversable* RootView::GetFocusTraversableParent() {
return focus_traversable_parent_;
}
void RootView::SetFocusTraversableParent(FocusTraversable* focus_traversable) {
DCHECK(focus_traversable != this);
focus_traversable_parent_ = focus_traversable;
}
View* RootView::GetFocusTraversableParentView() {
return focus_traversable_parent_view_;
}
void RootView::SetFocusTraversableParentView(View* view) {
focus_traversable_parent_view_ = view;
}
void RootView::NotifyNativeViewHierarchyChanged(bool attached,
gfx::NativeView native_view) {
PropagateNativeViewHierarchyChanged(attached, native_view, this);
}
bool RootView::ProcessKeyEvent(const KeyEvent& event) {
bool consumed = false;
View* v = GetFocusedView();
// Special case to handle right-click context menus triggered by the
// keyboard.
if (v && v->IsEnabled() && ((event.GetKeyCode() == base::VKEY_APPS) ||
(event.GetKeyCode() == base::VKEY_F10 && event.IsShiftDown()))) {
v->ShowContextMenu(v->GetKeyboardContextMenuLocation(), false);
return true;
}
for (; v && v != this && !consumed; v = v->GetParent()) {
consumed = (event.GetType() == Event::ET_KEY_PRESSED) ?
v->OnKeyPressed(event) : v->OnKeyReleased(event);
}
if (!consumed && default_keyboard_handler_) {
consumed = (event.GetType() == Event::ET_KEY_PRESSED) ?
default_keyboard_handler_->OnKeyPressed(event) :
default_keyboard_handler_->OnKeyReleased(event);
}
return consumed;
}
bool RootView::ProcessMouseWheelEvent(const MouseWheelEvent& e) {
View* v;
bool consumed = false;
if (GetFocusedView()) {
for (v = GetFocusedView();
v && v != this && !consumed; v = v->GetParent()) {
consumed = v->OnMouseWheel(e);
}
}
if (!consumed && default_keyboard_handler_) {
consumed = default_keyboard_handler_->OnMouseWheel(e);
}
return consumed;
}
void RootView::SetDefaultKeyboardHandler(View* v) {
default_keyboard_handler_ = v;
}
bool RootView::IsVisibleInRootView() const {
return IsVisible();
}
void RootView::ViewBoundsChanged(View* view, bool size_changed,
bool position_changed) {
DCHECK(view && (size_changed || position_changed));
if (!view->descendants_to_notify_.get())
return;
for (std::vector<View*>::iterator i = view->descendants_to_notify_->begin();
i != view->descendants_to_notify_->end(); ++i) {
(*i)->VisibleBoundsInRootChanged();
}
}
void RootView::RegisterViewForVisibleBoundsNotification(View* view) {
DCHECK(view);
if (view->registered_for_visible_bounds_notification_)
return;
view->registered_for_visible_bounds_notification_ = true;
View* ancestor = view->GetParent();
while (ancestor) {
ancestor->AddDescendantToNotify(view);
ancestor = ancestor->GetParent();
}
}
void RootView::UnregisterViewForVisibleBoundsNotification(View* view) {
DCHECK(view);
if (!view->registered_for_visible_bounds_notification_)
return;
view->registered_for_visible_bounds_notification_ = false;
View* ancestor = view->GetParent();
while (ancestor) {
ancestor->RemoveDescendantToNotify(view);
ancestor = ancestor->GetParent();
}
}
void RootView::SetMouseLocationAndFlags(const MouseEvent& e) {
last_mouse_event_flags_ = e.GetFlags();
last_mouse_event_x_ = e.x();
last_mouse_event_y_ = e.y();
}
std::string RootView::GetClassName() const {
return kViewClassName;
}
void RootView::ClearPaintRect() {
invalid_rect_.SetRect(0, 0, 0, 0);
// This painting has been done. Reset the urgent flag.
invalid_rect_urgent_ = false;
// If a pending_paint_task_ does Run(), we don't need to do anything.
paint_task_needed_ = false;
}
/////////////////////////////////////////////////////////////////////////////
//
// RootView - accessibility
//
/////////////////////////////////////////////////////////////////////////////
bool RootView::GetAccessibleRole(AccessibilityTypes::Role* role) {
DCHECK(role);
*role = AccessibilityTypes::ROLE_APPLICATION;
return true;
}
View* RootView::GetDragView() {
return drag_view_;
}
void RootView::SetActiveCursor(gfx::NativeCursor cursor) {
#if defined(OS_WIN)
if (cursor) {
previous_cursor_ = ::SetCursor(cursor);
} else if (previous_cursor_) {
::SetCursor(previous_cursor_);
previous_cursor_ = NULL;
}
#elif defined(OS_LINUX)
gfx::NativeView native_view =
static_cast<WidgetGtk*>(GetWidget())->window_contents();
if (!native_view)
return;
gdk_window_set_cursor(native_view->window, cursor);
if (cursor)
gdk_cursor_destroy(cursor);
#endif
}
} // namespace views