diff options
Diffstat (limited to 'views/widget')
-rw-r--r-- | views/widget/accelerator_handler.cc | 46 | ||||
-rw-r--r-- | views/widget/accelerator_handler.h | 30 | ||||
-rw-r--r-- | views/widget/aero_tooltip_manager.cc | 128 | ||||
-rw-r--r-- | views/widget/aero_tooltip_manager.h | 57 | ||||
-rw-r--r-- | views/widget/root_view.cc | 1001 | ||||
-rw-r--r-- | views/widget/root_view.h | 363 | ||||
-rw-r--r-- | views/widget/root_view_drop_target.cc | 118 | ||||
-rw-r--r-- | views/widget/root_view_drop_target.h | 75 | ||||
-rw-r--r-- | views/widget/root_view_gtk.cc | 28 | ||||
-rw-r--r-- | views/widget/root_view_win.cc | 70 | ||||
-rw-r--r-- | views/widget/tooltip_manager.cc | 447 | ||||
-rw-r--r-- | views/widget/tooltip_manager.h | 168 | ||||
-rw-r--r-- | views/widget/widget.h | 81 | ||||
-rw-r--r-- | views/widget/widget_gtk.cc | 386 | ||||
-rw-r--r-- | views/widget/widget_gtk.h | 125 | ||||
-rw-r--r-- | views/widget/widget_win.cc | 999 | ||||
-rw-r--r-- | views/widget/widget_win.h | 636 |
17 files changed, 4758 insertions, 0 deletions
diff --git a/views/widget/accelerator_handler.cc b/views/widget/accelerator_handler.cc new file mode 100644 index 0000000..386357f --- /dev/null +++ b/views/widget/accelerator_handler.cc @@ -0,0 +1,46 @@ +// Copyright (c) 2006-2008 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/accelerator_handler.h" + +#include "views/focus/focus_manager.h" + +namespace views { + +AcceleratorHandler::AcceleratorHandler() { +} + +bool AcceleratorHandler::Dispatch(const MSG& msg) { + bool process_message = true; + + if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST) { + FocusManager* focus_manager = FocusManager::GetFocusManager(msg.hwnd); + if (focus_manager) { + // FocusManager::OnKeyDown and OnKeyUp return false if this message has + // been consumed and should not be propagated further. + switch (msg.message) { + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + process_message = focus_manager->OnKeyDown(msg.hwnd, msg.message, + msg.wParam, msg.lParam); + break; + + case WM_KEYUP: + case WM_SYSKEYUP: + process_message = focus_manager->OnKeyUp(msg.hwnd, msg.message, + msg.wParam, msg.lParam); + break; + } + } + } + + if (process_message) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return true; +} + +} // namespace views diff --git a/views/widget/accelerator_handler.h b/views/widget/accelerator_handler.h new file mode 100644 index 0000000..5ee896c --- /dev/null +++ b/views/widget/accelerator_handler.h @@ -0,0 +1,30 @@ +// Copyright (c) 2006-2008 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. + +#ifndef VIEWS_WIDGET_ACCELERATOR_HANDLER_H_ +#define VIEWS_WIDGET_ACCELERATOR_HANDLER_H_ + +#include "base/message_loop.h" + +namespace views { + +// This class delegates WM_KEYDOWN and WM_SYSKEYDOWN messages to +// the associated FocusManager class for the window that is receiving +// these messages for accelerator processing. The BrowserProcess object +// holds a singleton instance of this class which can be used by other +// custom message loop dispatcher objects to implement default accelerator +// handling. +class AcceleratorHandler : public MessageLoopForUI::Dispatcher { + public: + AcceleratorHandler(); + // Dispatcher method. This returns true if an accelerator was + // processed by the focus manager + virtual bool Dispatch(const MSG& msg); + private: + DISALLOW_EVIL_CONSTRUCTORS(AcceleratorHandler); +}; + +} // namespace views + +#endif // VIEWS_WIDGET_ACCELERATOR_HANDLER_H_ diff --git a/views/widget/aero_tooltip_manager.cc b/views/widget/aero_tooltip_manager.cc new file mode 100644 index 0000000..ca58c24 --- /dev/null +++ b/views/widget/aero_tooltip_manager.cc @@ -0,0 +1,128 @@ +// Copyright (c) 2006-2008 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/aero_tooltip_manager.h" + +#include <windows.h> +#include <atlbase.h> +#include <atlapp.h> // for GET_X/Y_LPARAM +#include <commctrl.h> +#include <shlobj.h> + +#include "app/l10n_util_win.h" +#include "base/message_loop.h" + +namespace views { + +/////////////////////////////////////////////////////////////////////////////// +// AeroTooltipManager, public: + +AeroTooltipManager::AeroTooltipManager(Widget* widget, HWND parent) + : TooltipManager(widget, parent), + initial_delay_(0) { +} + +AeroTooltipManager::~AeroTooltipManager() { + if (initial_timer_) + initial_timer_->Disown(); +} + +void AeroTooltipManager::OnMouse(UINT u_msg, WPARAM w_param, LPARAM l_param) { + if (initial_timer_) + initial_timer_->Disown(); + + if (u_msg == WM_MOUSEMOVE || u_msg == WM_NCMOUSEMOVE) { + int x = GET_X_LPARAM(l_param); + int y = GET_Y_LPARAM(l_param); + if (last_mouse_x_ != x || last_mouse_y_ != y) { + last_mouse_x_ = x; + last_mouse_y_ = y; + HideKeyboardTooltip(); + UpdateTooltip(x, y); + } + + // Delay opening of the tooltip just in case the user moves their + // mouse to another control. We defer this from Init because we get + // zero if we query it too soon. + if (!initial_delay_) { + initial_delay_ = static_cast<int>( + ::SendMessage(tooltip_hwnd_, TTM_GETDELAYTIME, TTDT_INITIAL, 0)); + } + initial_timer_ = new InitialTimer(this, initial_delay_); + } else { + // Hide the tooltip and cancel any timers. + ::SendMessage(tooltip_hwnd_, TTM_POP, 0, 0); + ::SendMessage(tooltip_hwnd_, TTM_TRACKACTIVATE, false, (LPARAM)&toolinfo_); + return; + } +} + +void AeroTooltipManager::OnMouseLeave() { + last_mouse_x_ = last_mouse_y_ = -1; + UpdateTooltip(); +} + +/////////////////////////////////////////////////////////////////////////////// +// AeroTooltipManager, private: + +void AeroTooltipManager::Init() { + // Create the tooltip control. + tooltip_hwnd_ = CreateWindowEx( + WS_EX_TRANSPARENT | l10n_util::GetExtendedTooltipStyles(), + TOOLTIPS_CLASS, NULL, TTS_NOPREFIX, 0, 0, 0, 0, + parent_, NULL, NULL, NULL); + + l10n_util::AdjustUIFontForWindow(tooltip_hwnd_); + + // Add one tool that is used for all tooltips. + toolinfo_.cbSize = sizeof(toolinfo_); + + // We use tracking tooltips on Vista to allow us to manually control the + // visibility of the tooltip. + toolinfo_.uFlags = TTF_TRANSPARENT | TTF_IDISHWND | TTF_TRACK | TTF_ABSOLUTE; + toolinfo_.hwnd = parent_; + toolinfo_.uId = (UINT_PTR)parent_; + + // Setting this tells windows to call parent_ back (using a WM_NOTIFY + // message) for the actual tooltip contents. + toolinfo_.lpszText = LPSTR_TEXTCALLBACK; + SetRectEmpty(&toolinfo_.rect); + ::SendMessage(tooltip_hwnd_, TTM_ADDTOOL, 0, (LPARAM)&toolinfo_); +} + +void AeroTooltipManager::OnTimer() { + initial_timer_ = NULL; + + POINT pt; + pt.x = last_mouse_x_; + pt.y = last_mouse_y_; + ::ClientToScreen(parent_, &pt); + + // Set the position and visibility. + if (!tooltip_showing_) { + ::SendMessage(tooltip_hwnd_, TTM_POPUP, 0, 0); + ::SendMessage(tooltip_hwnd_, TTM_TRACKPOSITION, 0, MAKELPARAM(pt.x, pt.y)); + ::SendMessage(tooltip_hwnd_, TTM_TRACKACTIVATE, true, (LPARAM)&toolinfo_); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// AeroTooltipManager::InitialTimer + +AeroTooltipManager::InitialTimer::InitialTimer(AeroTooltipManager* manager, + int time) : manager_(manager) { + MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableMethod( + this, &InitialTimer::Execute), time); +} + +void AeroTooltipManager::InitialTimer::Disown() { + manager_ = NULL; +} + +void AeroTooltipManager::InitialTimer::Execute() { + if (manager_) + manager_->OnTimer(); +} + +} // namespace views diff --git a/views/widget/aero_tooltip_manager.h b/views/widget/aero_tooltip_manager.h new file mode 100644 index 0000000..fe5856d --- /dev/null +++ b/views/widget/aero_tooltip_manager.h @@ -0,0 +1,57 @@ +// Copyright (c) 2006-2008 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. + +#ifndef VIEWS_WIDGET_AERO_TOOLTIP_MANAGER_H_ +#define VIEWS_WIDGET_AERO_TOOLTIP_MANAGER_H_ + +#include "base/ref_counted.h" +#include "base/task.h" +#include "views/widget/tooltip_manager.h" + +namespace views { + +/////////////////////////////////////////////////////////////////////////////// +// AeroTooltipManager +// +// Default Windows tooltips are broken when using our custom window frame +// - as soon as the tooltip receives a WM_MOUSEMOVE event, it starts spewing +// NCHITTEST messages at its parent window (us). These messages have random +// x/y coordinates and can't be ignored, as the DwmDefWindowProc uses +// NCHITTEST messages to determine how to highlight the caption buttons +// (the buttons then flicker as the hit tests sent by the user's mouse +// trigger different effects to those sent by the tooltip). +// +// So instead, we have to partially implement tooltips ourselves using +// TTF_TRACKed tooltips. +// +// TODO(glen): Resolve this with Microsoft. +class AeroTooltipManager : public TooltipManager { + public: + AeroTooltipManager(Widget* widget, HWND parent); + virtual ~AeroTooltipManager(); + + virtual void OnMouse(UINT u_msg, WPARAM w_param, LPARAM l_param); + virtual void OnMouseLeave(); + + private: + void Init(); + void OnTimer(); + + class InitialTimer : public base::RefCounted<InitialTimer> { + public: + InitialTimer(AeroTooltipManager* manager, int time); + void Disown(); + void Execute(); + + private: + AeroTooltipManager* manager_; + }; + + int initial_delay_; + scoped_refptr<InitialTimer> initial_timer_; +}; + +} // namespace views + +#endif // #ifndef VIEWS_WIDGET_AERO_TOOLTIP_MANAGER_H_ diff --git a/views/widget/root_view.cc b/views/widget/root_view.cc new file mode 100644 index 0000000..fd9f1c0 --- /dev/null +++ b/views/widget/root_view.cc @@ -0,0 +1,1001 @@ +// Copyright (c) 2006-2008 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 "app/gfx/chrome_canvas.h" +#if defined(OS_WIN) +#include "base/base_drag_source.h" +#endif +#include "base/logging.h" +#include "base/message_loop.h" +#if defined(OS_WIN) +#include "views/focus/view_storage.h" +#include "views/widget/root_view_drop_target.h" +#endif +#include "views/widget/widget.h" +#include "views/window/window.h" + +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_EVIL_CONSTRUCTORS(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), + 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_listener_(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. +} + +///////////////////////////////////////////////////////////////////////////// +// +// 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(); +} + +void RootView::SchedulePaint(int x, int y, int w, int h) { + 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_EVIL_CONSTRUCTORS(ScopedProcessingPaint); +}; +#endif + +void RootView::ProcessPaint(ChromeCanvas* 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->drawColor(SK_ColorBLACK, SkPorterDuff::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::ThemeChanged() { + View::ThemeChanged(); +} + +///////////////////////////////////////////////////////////////////////////// +// +// 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 defined(OS_WIN) + if (drop_target_.get()) + drop_target_->ResetTargetViewIfEquals(child); +#else + NOTIMPLEMENTED(); +#endif + + 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; + } + + // For a given widget hierarchy, focus is tracked by a FocusManager attached + // to our nearest enclosing Window. <-- Important Assumption! + // We may not have access to our window if this function is called as a + // result of teardown during the deletion of the RootView and its hierarchy, + // so we don't bother notifying the FocusManager in that case because it + // will have already been destroyed (the Widget that contains us is + // NCDESTROY'ed which in turn destroys the focus manager _before_ the + // RootView is deleted.) +#if defined(OS_WIN) + Window* window = GetWindow(); + if (window) { + FocusManager* focus_manager = + FocusManager::GetFocusManager(window->GetNativeWindow()); + focus_manager->ViewRemoved(parent, child); + } + ViewStorage::GetSharedInstance()->ViewRemoved(parent, child); +#endif + } +} + +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 + NOTIMPLEMENTED(); +#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; +} + +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. + while (v && !v->IsEnabled()) + 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); + +#if defined(OS_WIN) + HCURSOR cursor = mouse_move_handler_->GetCursorForPoint( + moved_event.GetType(), moved_event.x(), moved_event.y()); + if (cursor) { + previous_cursor_ = ::SetCursor(cursor); + } else if (previous_cursor_) { + ::SetCursor(previous_cursor_); + previous_cursor_ = NULL; + } +#else + NOTIMPLEMENTED(); +#endif + } else if (mouse_move_handler_ != NULL) { + MouseEvent exited_event(Event::ET_MOUSE_EXITED, 0, 0, 0); + mouse_move_handler_->OnMouseExited(exited_event); +#if defined(OS_WIN) + if (previous_cursor_) { + ::SetCursor(previous_cursor_); + previous_cursor_ = NULL; + } +#else + NOTIMPLEMENTED(); +#endif + } +} + +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::OnWidgetCreated() { +#if defined(OS_WIN) + DCHECK(!drop_target_.get()); + drop_target_ = new RootViewDropTarget(this); +#else + // TODO(port): Port RootViewDropTarget and this goes away. + NOTIMPLEMENTED(); +#endif +} + +void RootView::OnWidgetDestroyed() { +#if defined(OS_WIN) + if (drop_target_.get()) { + RevokeDragDrop(GetWidget()->GetNativeView()); + drop_target_ = NULL; + } +#else + // TODO(port): Port RootViewDropTarget and this goes away. + NOTIMPLEMENTED(); +#endif + widget_ = NULL; +} + +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::SetFocusListener(FocusListener* listener) { + focus_listener_ = listener; +} + +void RootView::FocusView(View* view) { + if (view != GetFocusedView()) { +#if defined(OS_WIN) + FocusManager* focus_manager = GetFocusManager(); + DCHECK(focus_manager) << "No Focus Manager for Window " << + (GetWidget() ? GetWidget()->GetNativeView() : 0); + if (!focus_manager) + return; + + View* prev_focused_view = focus_manager->GetFocusedView(); + focus_manager->SetFocusedView(view); + + if (focus_listener_) + focus_listener_->FocusChanged(prev_focused_view, view); +#else + // TODO(port): Port the focus manager and this goes away. + NOTIMPLEMENTED(); +#endif + } +} + +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; +} + +View* RootView::FindNextFocusableView(View* starting_view, + bool reverse, + Direction direction, + bool dont_loop, + FocusTraversable** focus_traversable, + View** focus_traversable_view) { + *focus_traversable = NULL; + *focus_traversable_view = NULL; + + if (GetChildViewCount() == 0) { + NOTREACHED(); + // Nothing to focus on here. + return NULL; + } + + bool skip_starting_view = true; + if (!starting_view) { + // Default to the first/last child + starting_view = reverse ? GetChildViewAt(GetChildViewCount() - 1) : + GetChildViewAt(0) ; + // If there was no starting view, then the one we select is a potential + // focus candidate. + skip_starting_view = false; + } else { + // The starting view should be part of this RootView. + DCHECK(IsParentOf(starting_view)); + } + + View* v = NULL; + if (!reverse) { + v = FindNextFocusableViewImpl(starting_view, skip_starting_view, + true, + (direction == DOWN) ? true : false, + starting_view->GetGroup()); + } else { + // If the starting view is focusable, we don't want to go down, as we are + // traversing the view hierarchy tree bottom-up. + bool can_go_down = (direction == DOWN) && !starting_view->IsFocusable(); + v = FindPreviousFocusableViewImpl(starting_view, true, + true, + can_go_down, + starting_view->GetGroup()); + } + if (v) { + if (v->IsFocusable()) + return v; + *focus_traversable = v->GetFocusTraversable(); + DCHECK(*focus_traversable); + *focus_traversable_view = v; + return NULL; + } + // Nothing found. + return NULL; +} + +// Strategy for finding the next focusable view: +// - keep going down the first child, stop when you find a focusable view or +// a focus traversable view (in that case return it) or when you reach a view +// with no children. +// - go to the right sibling and start the search from there (by invoking +// FindNextFocusableViewImpl on that view). +// - if the view has no right sibling, go up the parents until you find a parent +// with a right sibling and start the search from there. +View* RootView::FindNextFocusableViewImpl(View* starting_view, + bool skip_starting_view, + bool can_go_up, + bool can_go_down, + int skip_group_id) { + if (!skip_starting_view) { + if (IsViewFocusableCandidate(starting_view, skip_group_id)) + return FindSelectedViewForGroup(starting_view); + if (starting_view->GetFocusTraversable()) + return starting_view; + } + + // First let's try the left child. + if (can_go_down) { + View* v = NULL; + if (starting_view->GetChildViewCount() > 0) { + // We are only interested in non floating-views, as attached floating + // views order is variable (depending on mouse moves). + for (int i = 0; i < starting_view->GetChildViewCount(); i++) { + View* child = starting_view->GetChildViewAt(i); + if (!child->IsFloatingView()) { + v = FindNextFocusableViewImpl(child, false, false, true, + skip_group_id); + break; + } + } + } + if (v == NULL) { + // Try the floating views. + int id = 0; + if (starting_view->EnumerateFloatingViews(View::FIRST, 0, &id)) { + View* child = starting_view->RetrieveFloatingViewForID(id); + DCHECK(child); + v = FindNextFocusableViewImpl(child, false, false, true, skip_group_id); + } + } + if (v) + return v; + } + + // Then try the right sibling. + View* sibling = NULL; + if (starting_view->IsFloatingView()) { + int id = 0; + if (starting_view->GetParent()->EnumerateFloatingViews( + View::NEXT, starting_view->GetFloatingViewID(), &id)) { + sibling = starting_view->GetParent()->RetrieveFloatingViewForID(id); + DCHECK(sibling); + } + } else { + sibling = starting_view->GetNextFocusableView(); + if (!sibling) { + // Let's try floating views. + int id = 0; + if (starting_view->GetParent()->EnumerateFloatingViews(View::FIRST, + 0, &id)) { + sibling = starting_view->GetParent()->RetrieveFloatingViewForID(id); + DCHECK(sibling); + } + } + } + if (sibling) { + View* v = + FindNextFocusableViewImpl(sibling, false, false, true, skip_group_id); + if (v) + return v; + } + + // Then go up to the parent sibling. + if (can_go_up) { + View* parent = starting_view->GetParent(); + while (parent) { + int id = 0; + if (parent->IsFloatingView() && + parent->GetParent()->EnumerateFloatingViews( + View::NEXT, parent->GetFloatingViewID(), &id)) { + sibling = parent->GetParent()->RetrieveFloatingViewForID(id); + DCHECK(sibling); + } else { + sibling = parent->GetNextFocusableView(); + } + if (sibling) { + return FindNextFocusableViewImpl(sibling, + false, true, true, + skip_group_id); + } + parent = parent->GetParent(); + } + } + + // We found nothing. + return NULL; +} + +// Strategy for finding the previous focusable view: +// - keep going down on the right until you reach a view with no children, if it +// it is a good candidate return it. +// - start the search on the left sibling. +// - if there are no left sibling, start the search on the parent (without going +// down). +View* RootView::FindPreviousFocusableViewImpl(View* starting_view, + bool skip_starting_view, + bool can_go_up, + bool can_go_down, + int skip_group_id) { + // Let's go down and right as much as we can. + if (can_go_down) { + View* v = NULL; + if (starting_view->GetChildViewCount() - + starting_view->GetFloatingViewCount() > 0) { + View* view = + starting_view->GetChildViewAt(starting_view->GetChildViewCount() - 1); + v = FindPreviousFocusableViewImpl(view, false, false, true, + skip_group_id); + } else { + // Let's try floating views. + int id = 0; + if (starting_view->EnumerateFloatingViews(View::LAST, 0, &id)) { + View* child = starting_view->RetrieveFloatingViewForID(id); + DCHECK(child); + v = FindNextFocusableViewImpl(child, false, false, true, skip_group_id); + } + } + if (v) + return v; + } + + if (!skip_starting_view) { + if (IsViewFocusableCandidate(starting_view, skip_group_id)) + return FindSelectedViewForGroup(starting_view); + if (starting_view->GetFocusTraversable()) + return starting_view; + } + + // Then try the left sibling. + View* sibling = NULL; + if (starting_view->IsFloatingView()) { + int id = 0; + if (starting_view->GetParent()->EnumerateFloatingViews( + View::PREVIOUS, starting_view->GetFloatingViewID(), &id)) { + sibling = starting_view->GetParent()->RetrieveFloatingViewForID(id); + DCHECK(sibling); + } + if (!sibling) { + // No more floating views, try regular views, starting at the last one. + View* parent = starting_view->GetParent(); + for (int i = parent->GetChildViewCount() - 1; i >= 0; i--) { + View* v = parent->GetChildViewAt(i); + if (!v->IsFloatingView()) { + sibling = v; + break; + } + } + } + } else { + sibling = starting_view->GetPreviousFocusableView(); + } + if (sibling) { + return FindPreviousFocusableViewImpl(sibling, + false, true, true, + skip_group_id); + } + + // Then go up the parent. + if (can_go_up) { + View* parent = starting_view->GetParent(); + if (parent) + return FindPreviousFocusableViewImpl(parent, + false, true, false, + skip_group_id); + } + + // We found nothing. + return NULL; +} + +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; +} + +// static +View* RootView::FindSelectedViewForGroup(View* view) { + if (view->IsGroupFocusTraversable() || + view->GetGroup() == -1) // No group for that view. + return view; + + View* selected_view = view->GetSelectedViewForGroup(view->GetGroup()); + if (selected_view) + return selected_view; + + // No view selected for that group, default to the specified view. + return view; +} + +// static +bool RootView::IsViewFocusableCandidate(View* v, int skip_group_id) { + return v->IsFocusable() && + (v->IsGroupFocusTraversable() || skip_group_id == -1 || + v->GetGroup() != skip_group_id); +} + +bool RootView::ProcessKeyEvent(const KeyEvent& event) { + bool consumed = false; + + View* v = GetFocusedView(); +#if defined(OS_WIN) + // Special case to handle right-click context menus triggered by the + // keyboard. + if (v && v->IsEnabled() && ((event.GetCharacter() == VK_APPS) || + (event.GetCharacter() == VK_F10 && event.IsShiftDown()))) { + gfx::Point screen_loc = v->GetKeyboardContextMenuLocation(); + v->ShowContextMenu(screen_loc.x(), screen_loc.y(), false); + return true; + } +#else + // TODO(port): The above block needs the VK_* refactored out. + NOTIMPLEMENTED(); +#endif + + 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; +} + +bool RootView::GetAccessibleName(std::wstring* name) { + if (!accessible_name_.empty()) { + *name = accessible_name_; + return true; + } + return false; +} + +void RootView::SetAccessibleName(const std::wstring& name) { + accessible_name_.assign(name); +} + +View* RootView::GetDragView() { + return drag_view_; +} + +} // namespace views diff --git a/views/widget/root_view.h b/views/widget/root_view.h new file mode 100644 index 0000000..d775ac1 --- /dev/null +++ b/views/widget/root_view.h @@ -0,0 +1,363 @@ +// Copyright (c) 2009 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. + +#ifndef VIEWS_WIDGET_ROOT_VIEW_H_ +#define VIEWS_WIDGET_ROOT_VIEW_H_ + +#include "build/build_config.h" + +#if defined(OS_LINUX) +#include <gtk/gtk.h> +#endif + +#if defined(OS_WIN) +#include "base/ref_counted.h" +#endif + +#include "views/focus/focus_manager.h" +#include "views/view.h" + +namespace views { + +class PaintTask; +class RootViewDropTarget; +class Widget; + +//////////////////////////////////////////////////////////////////////////////// +// +// FocusListener Interface +// +//////////////////////////////////////////////////////////////////////////////// +class FocusListener { + public: + virtual void FocusChanged(View* lost_focus, View* got_focus) = 0; +}; + + +///////////////////////////////////////////////////////////////////////////// +// +// RootView class +// +// The RootView is the root of a View hierarchy. A RootView is always the +// first and only child of a Widget. +// +// The RootView manages the View hierarchy's interface with the Widget +// and also maintains the current invalid rect - the region that needs +// repainting. +// +///////////////////////////////////////////////////////////////////////////// +class RootView : public View, + public FocusTraversable { + public: + static const char kViewClassName[]; + + explicit RootView(Widget* widget); + + virtual ~RootView(); + + // Layout and Painting functions + + // Overridden from View to implement paint scheduling. + virtual void SchedulePaint(const gfx::Rect& r, bool urgent); + + // Convenience to schedule the whole view + virtual void SchedulePaint(); + + // Convenience to schedule a paint given some ints + virtual void SchedulePaint(int x, int y, int w, int h); + + // Paint this RootView and its child Views. + virtual void ProcessPaint(ChromeCanvas* canvas); + + // If the invalid rect is non-empty and there is a pending paint the RootView + // is painted immediately. This is internally invoked as the result of + // invoking SchedulePaint. + virtual void PaintNow(); + + // Whether or not this View needs repainting. If |urgent| is true, this method + // returns whether this root view needs to paint as soon as possible. + virtual bool NeedsPainting(bool urgent); + + // Invoked by the Widget to discover what rectangle should be painted. + const gfx::Rect& GetScheduledPaintRect(); + + // Returns the region scheduled to paint clipped to the RootViews bounds. + gfx::Rect GetScheduledPaintRectConstrainedToSize(); + + // Tree functions + + // Get the Widget that hosts this View. + virtual Widget* GetWidget() const; + + // Public API for broadcasting theme change notifications to this View + // hierarchy. + virtual void ThemeChanged(); + + // The following event methods are overridden to propagate event to the + // control tree + virtual bool OnMousePressed(const MouseEvent& e); + virtual bool OnMouseDragged(const MouseEvent& e); + virtual void OnMouseReleased(const MouseEvent& e, bool canceled); + virtual void OnMouseMoved(const MouseEvent& e); + virtual void SetMouseHandler(View* new_mouse_handler); + + // Invoked when the Widget has been fully initialized. + // At the time the constructor is invoked the Widget may not be completely + // initialized, when this method is invoked, it is. + void OnWidgetCreated(); + + // Invoked prior to the Widget being destroyed. + void OnWidgetDestroyed(); + + // Invoked By the Widget if the mouse drag is interrupted by + // the system. Invokes OnMouseReleased with a value of true for canceled. + void ProcessMouseDragCanceled(); + + // Invoked by the Widget instance when the mouse moves outside of the Widget + // bounds. + virtual void ProcessOnMouseExited(); + + // Make the provided view focused. Also make sure that our Widget is focused. + void FocusView(View* view); + + // Check whether the provided view is in the focus path. The focus path is the + // path between the focused view (included) to the root view. + bool IsInFocusPath(View* view); + + // Returns the View in this RootView hierarchy that has the focus, or NULL if + // no View currently has the focus. + View* GetFocusedView(); + + // Process a key event. Send the event to the focused view and up the focus + // path, and finally to the default keyboard handler, until someone consumes + // it. Returns whether anyone consumed the event. + bool ProcessKeyEvent(const KeyEvent& event); + + // Set the default keyboard handler. The default keyboard handler is + // a view that will get an opportunity to process key events when all + // views in the focus path did not process an event. + // + // Note: this is a single view at this point. We may want to make + // this a list if needed. + void SetDefaultKeyboardHandler(View* v); + + // Set whether this root view should focus the corresponding hwnd + // when an unprocessed mouse event occurs. + void SetFocusOnMousePressed(bool f); + + // Process a mousewheel event. Return true if the event was processed + // and false otherwise. + // MouseWheel events are sent on the focus path. + virtual bool ProcessMouseWheelEvent(const MouseWheelEvent& e); + + // Overridden to handle special root view case. + virtual bool IsVisibleInRootView() const; + + // Sets a listener that receives focus changes events. + void SetFocusListener(FocusListener* listener); + + // FocusTraversable implementation. + virtual View* FindNextFocusableView(View* starting_view, + bool reverse, + Direction direction, + bool dont_loop, + FocusTraversable** focus_traversable, + View** focus_traversable_view); + virtual FocusTraversable* GetFocusTraversableParent(); + virtual View* GetFocusTraversableParentView(); + + // Used to set the FocusTraversable parent after the view has been created + // (typically when the hierarchy changes and this RootView is added/removed). + virtual void SetFocusTraversableParent(FocusTraversable* focus_traversable); + + // Used to set the View parent after the view has been created. + virtual void SetFocusTraversableParentView(View* view); + + // Returns the name of this class: views/RootView + virtual std::string GetClassName() const; + + // Clears the region that is schedule to be painted. You nearly never need + // to invoke this. This is primarily intended for Widgets. + void ClearPaintRect(); + +#if defined(OS_WIN) + // Invoked from the Widget to service a WM_PAINT call. + void OnPaint(HWND hwnd); +#elif defined(OS_LINUX) + void OnPaint(GdkEventExpose* event); +#endif + + // Accessibility accessors/mutators, overridden from View. + virtual bool GetAccessibleRole(AccessibilityTypes::Role* role); + virtual bool GetAccessibleName(std::wstring* name); + virtual void SetAccessibleName(const std::wstring& name); + + protected: + + // Overridden to properly reset our event propagation member + // variables when a child is removed + virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + +#ifndef NDEBUG + virtual bool IsProcessingPaint() const { return is_processing_paint_; } +#endif + + private: + friend class View; + friend class PaintTask; + + RootView(); + DISALLOW_EVIL_CONSTRUCTORS(RootView); + + // Convert a point to our current mouse handler. Returns false if the + // mouse handler is not connected to a Widget. In that case, the + // conversion cannot take place and *p is unchanged + bool ConvertPointToMouseHandler(const gfx::Point& l, gfx::Point *p); + + // Update the cursor given a mouse event. This is called by non mouse_move + // event handlers to honor the cursor desired by views located under the + // cursor during drag operations. + void UpdateCursor(const MouseEvent& e); + + // Notification that size and/or position of a view has changed. This + // notifies the appropriate views. + void ViewBoundsChanged(View* view, bool size_changed, bool position_changed); + + // Registers a view for notification when the visible bounds relative to the + // root of a view changes. + void RegisterViewForVisibleBoundsNotification(View* view); + void UnregisterViewForVisibleBoundsNotification(View* view); + + // Returns the next focusable view or view containing a FocusTraversable (NULL + // if none was found), starting at the starting_view. + // skip_starting_view, can_go_up and can_go_down controls the traversal of + // the views hierarchy. + // skip_group_id specifies a group_id, -1 means no group. All views from a + // group are traversed in one pass. + View* FindNextFocusableViewImpl(View* starting_view, + bool skip_starting_view, + bool can_go_up, + bool can_go_down, + int skip_group_id); + + // Same as FindNextFocusableViewImpl but returns the previous focusable view. + View* FindPreviousFocusableViewImpl(View* starting_view, + bool skip_starting_view, + bool can_go_up, + bool can_go_down, + int skip_group_id); + + // Convenience method that returns true if a view is focusable and does not + // belong to the specified group. + bool IsViewFocusableCandidate(View* v, int skip_group_id); + + // Returns the view selected for the group of the selected view. If the view + // does not belong to a group or if no view is selected in the group, the + // specified view is returned. + static View* FindSelectedViewForGroup(View* view); + + // Updates the last_mouse_* fields from e. + void SetMouseLocationAndFlags(const MouseEvent& e); + +#if defined(OS_WIN) + // Starts a drag operation for the specified view. This blocks until done. + // If the view has not been deleted during the drag, OnDragDone is invoked + // on the view. + void StartDragForViewFromMouseEvent(View* view, + IDataObject* data, + int operation); +#endif + + // If a view is dragging, this returns it. Otherwise returns NULL. + View* GetDragView(); + + // The view currently handing down - drag - up + View* mouse_pressed_handler_; + + // The view currently handling enter / exit + View* mouse_move_handler_; + + // The last view to handle a mouse click, so that we can determine if + // a double-click lands on the same view as its single-click part. + View* last_click_handler_; + + // The host Widget + Widget* widget_; + + // The rectangle that should be painted + gfx::Rect invalid_rect_; + + // Whether the current invalid rect should be painted urgently. + bool invalid_rect_urgent_; + + // The task that we are using to trigger some non urgent painting or NULL + // if no painting has been scheduled yet. + PaintTask* pending_paint_task_; + + // Indicate if, when the pending_paint_task_ is run, actual painting is still + // required. + bool paint_task_needed_; + + // true if mouse_handler_ has been explicitly set + bool explicit_mouse_handler_; + +#if defined(OS_WIN) + // Previous cursor + HCURSOR previous_cursor_; +#endif + + // Default keyboard handler + View* default_keyboard_handler_; + + // The listener that gets focus change notifications. + FocusListener* focus_listener_; + + // Whether this root view should make our hwnd focused + // when an unprocessed mouse press event occurs + bool focus_on_mouse_pressed_; + + // Flag used to ignore focus events when we focus the native window associated + // with a view. + bool ignore_set_focus_calls_; + + // Whether this root view belongs to the current active window. + // bool activated_; + + // Last position/flag of a mouse press/drag. Used if capture stops and we need + // to synthesize a release. + int last_mouse_event_flags_; + int last_mouse_event_x_; + int last_mouse_event_y_; + + // The parent FocusTraversable, used for focus traversal. + FocusTraversable* focus_traversable_parent_; + + // The View that contains this RootView. This is used when we have RootView + // wrapped inside native components, and is used for the focus traversal. + View* focus_traversable_parent_view_; + +#if defined(OS_WIN) + // Handles dnd for us. + scoped_refptr<RootViewDropTarget> drop_target_; +#endif + + // Storage of strings needed for accessibility. + std::wstring accessible_name_; + + // Tracks drag state for a view. + View::DragInfo drag_info; + + // Valid for the lifetime of StartDragForViewFromMouseEvent, indicates the + // view the drag started from. + View* drag_view_; + +#ifndef NDEBUG + // True if we're currently processing paint. + bool is_processing_paint_; +#endif +}; + +} // namespace views + +#endif // VIEWS_WIDGET_ROOT_VIEW_H_ diff --git a/views/widget/root_view_drop_target.cc b/views/widget/root_view_drop_target.cc new file mode 100644 index 0000000..9c1f087 --- /dev/null +++ b/views/widget/root_view_drop_target.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2006-2008 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_drop_target.h" + +#include "app/drag_drop_types.h" +#include "base/gfx/point.h" +#include "views/widget/root_view.h" +#include "views/widget/widget.h" + +namespace views { + +RootViewDropTarget::RootViewDropTarget(RootView* root_view) + : BaseDropTarget(root_view->GetWidget()->GetNativeView()), + root_view_(root_view), + target_view_(NULL), + deepest_view_(NULL) { +} + +RootViewDropTarget::~RootViewDropTarget() { +} + +void RootViewDropTarget::ResetTargetViewIfEquals(View* view) { + if (target_view_ == view) + target_view_ = NULL; + if (deepest_view_ == view) + deepest_view_ = NULL; +} + +DWORD RootViewDropTarget::OnDragOver(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect) { + const OSExchangeData data(data_object); + gfx::Point root_view_location(cursor_position.x, cursor_position.y); + View::ConvertPointToView(NULL, root_view_, &root_view_location); + View* view = CalculateTargetView(root_view_location, data); + + if (view != target_view_) { + // Target changed notify old drag exited, then new drag entered. + if (target_view_) + target_view_->OnDragExited(); + target_view_ = view; + if (target_view_) { + gfx::Point target_view_location(root_view_location); + View::ConvertPointToView(root_view_, target_view_, &target_view_location); + DropTargetEvent enter_event(data, + target_view_location.x(), + target_view_location.y(), + DragDropTypes::DropEffectToDragOperation(effect)); + target_view_->OnDragEntered(enter_event); + } + } + + if (target_view_) { + gfx::Point target_view_location(root_view_location); + View::ConvertPointToView(root_view_, target_view_, &target_view_location); + DropTargetEvent enter_event(data, + target_view_location.x(), + target_view_location.y(), + DragDropTypes::DropEffectToDragOperation(effect)); + int result_operation = target_view_->OnDragUpdated(enter_event); + return DragDropTypes::DragOperationToDropEffect(result_operation); + } else { + return DROPEFFECT_NONE; + } +} + +void RootViewDropTarget::OnDragLeave(IDataObject* data_object) { + if (target_view_) + target_view_->OnDragExited(); + deepest_view_ = target_view_ = NULL; +} + +DWORD RootViewDropTarget::OnDrop(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect) { + const OSExchangeData data(data_object); + DWORD drop_effect = OnDragOver(data_object, key_state, cursor_position, + effect); + View* drop_view = target_view_; + deepest_view_ = target_view_ = NULL; + if (drop_effect != DROPEFFECT_NONE) { + gfx::Point view_location(cursor_position.x, cursor_position.y); + View::ConvertPointToView(NULL, drop_view, &view_location); + DropTargetEvent drop_event(data, view_location.x(), view_location.y(), + DragDropTypes::DropEffectToDragOperation(effect)); + return DragDropTypes::DragOperationToDropEffect( + drop_view->OnPerformDrop(drop_event)); + } else { + if (drop_view) + drop_view->OnDragExited(); + return DROPEFFECT_NONE; + } +} + +View* RootViewDropTarget::CalculateTargetView( + const gfx::Point& root_view_location, + const OSExchangeData& data) { + View* view = root_view_->GetViewForPoint(root_view_location); + if (view == deepest_view_) { + // The view the mouse is over hasn't changed; reuse the target. + return target_view_; + } + // View under mouse changed, which means a new view may want the drop. + // Walk the tree, stopping at target_view_ as we know it'll accept the + // drop. + deepest_view_ = view; + while (view && view != target_view_ && + (!view->IsEnabled() || !view->CanDrop(data))) { + view = view->GetParent(); + } + return view; +} + +} // namespace views diff --git a/views/widget/root_view_drop_target.h b/views/widget/root_view_drop_target.h new file mode 100644 index 0000000..a3c3afd --- /dev/null +++ b/views/widget/root_view_drop_target.h @@ -0,0 +1,75 @@ +// Copyright (c) 2006-2008 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. + +#ifndef VIEWS_WIDGET_ROOT_VIEW_DROP_TARGET_H_ +#define VIEWS_WIDGET_ROOT_VIEW_DROP_TARGET_H_ + +#include <atlbase.h> +#include <atlapp.h> +#include <atlmisc.h> + +#include "app/os_exchange_data.h" +#include "base/base_drop_target.h" + +namespace gfx { +class Point; +} + +namespace views { + +class RootView; +class View; + +// RootViewDropTarget takes care of managing drag and drop for the RootView and +// converts Windows OLE drop messages into Views drop messages. +// +// RootViewDropTarget is responsible for determining the appropriate View to +// use during a drag and drop session, and forwarding events to it. +class RootViewDropTarget : public BaseDropTarget { + public: + explicit RootViewDropTarget(RootView* root_view); + virtual ~RootViewDropTarget(); + + // If a drag and drop is underway and view is the current drop target, the + // drop target is set to null. + // This is invoked when a View is removed from the RootView to make sure + // we don't target a view that was removed during dnd. + void ResetTargetViewIfEquals(View* view); + + protected: + virtual DWORD OnDragOver(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect); + + virtual void OnDragLeave(IDataObject* data_object); + + virtual DWORD OnDrop(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect); + + private: + // Calculates the target view for a drop given the specified location in + // the coordinate system of the rootview. This tries to avoid continually + // querying CanDrop by returning target_view_ if the mouse is still over + // target_view_. + View* CalculateTargetView(const gfx::Point& root_view_location, + const OSExchangeData& data); + + // RootView we were created for. + RootView* root_view_; + + // View we're targeting events at. + View* target_view_; + + // The deepest view under the current drop coordinate. + View* deepest_view_; + + DISALLOW_EVIL_CONSTRUCTORS(RootViewDropTarget); +}; + +} // namespace views + +#endif // VIEWS_WIDGET_ROOT_VIEW_DROP_TARGET_H_ diff --git a/views/widget/root_view_gtk.cc b/views/widget/root_view_gtk.cc new file mode 100644 index 0000000..428c695 --- /dev/null +++ b/views/widget/root_view_gtk.cc @@ -0,0 +1,28 @@ +// Copyright (c) 2006-2008 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 "app/gfx/chrome_canvas.h" +#include "base/logging.h" +#include "skia/include/SkColor.h" + +namespace views { + +void RootView::UpdateCursor(const MouseEvent& e) { + NOTIMPLEMENTED(); +} + +void RootView::OnPaint(GdkEventExpose* event) { + ChromeCanvasPaint canvas(event); + + if (!canvas.isEmpty()) { + SchedulePaint(gfx::Rect(canvas.rectangle()), false); + if (NeedsPainting(false)) { + ProcessPaint(&canvas); + } + } +} + +} diff --git a/views/widget/root_view_win.cc b/views/widget/root_view_win.cc new file mode 100644 index 0000000..ac4a50d --- /dev/null +++ b/views/widget/root_view_win.cc @@ -0,0 +1,70 @@ +// Copyright (c) 2006-2008 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 "app/drag_drop_types.h" +#include "app/gfx/chrome_canvas.h" +#include "base/base_drag_source.h" +#include "base/logging.h" +#include "views/widget/root_view_drop_target.h" + +namespace views { + +void RootView::UpdateCursor(const MouseEvent& e) { + View *v = GetViewForPoint(e.location()); + + if (v && v != this) { + gfx::Point l(e.location()); + View::ConvertPointToView(this, v, &l); + HCURSOR cursor = v->GetCursorForPoint(e.GetType(), l.x(), l.y()); + if (cursor) { + ::SetCursor(cursor); + return; + } + } + if (previous_cursor_) { + SetCursor(previous_cursor_); + } +} + +void RootView::OnPaint(HWND hwnd) { + gfx::Rect original_dirty_region = GetScheduledPaintRectConstrainedToSize(); + if (!original_dirty_region.IsEmpty()) { + // Invoke InvalidateRect so that the dirty region of the window includes the + // region we need to paint. If we didn't do this and the region didn't + // include the dirty region, ProcessPaint would incorrectly mark everything + // as clean. This can happen if a WM_PAINT is generated by the system before + // the InvokeLater schedule by RootView is processed. + RECT win_version = original_dirty_region.ToRECT(); + InvalidateRect(hwnd, &win_version, FALSE); + } + ChromeCanvasPaint canvas(hwnd); + if (!canvas.isEmpty()) { + const PAINTSTRUCT& ps = canvas.paintStruct(); + SchedulePaint(gfx::Rect(ps.rcPaint), false); + if (NeedsPainting(false)) + ProcessPaint(&canvas); + } +} + +void RootView::StartDragForViewFromMouseEvent( + View* view, + IDataObject* data, + int operation) { + drag_view_ = view; + scoped_refptr<BaseDragSource> drag_source(new BaseDragSource); + DWORD effects; + DoDragDrop(data, drag_source, + DragDropTypes::DragOperationToDropEffect(operation), &effects); + // If the view is removed during the drag operation, drag_view_ is set to + // NULL. + if (drag_view_ == view) { + View* drag_view = drag_view_; + drag_view_ = NULL; + drag_view->OnDragDone(); + } +} + +} diff --git a/views/widget/tooltip_manager.cc b/views/widget/tooltip_manager.cc new file mode 100644 index 0000000..18faf4a --- /dev/null +++ b/views/widget/tooltip_manager.cc @@ -0,0 +1,447 @@ +// Copyright (c) 2006-2008 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/tooltip_manager.h" + +#include <limits> + +#include "app/l10n_util.h" +#include "app/l10n_util_win.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "chrome/common/gfx/text_elider.h" +#include "chrome/common/win_util.h" +#include "views/view.h" +#include "views/widget/root_view.h" +#include "views/widget/widget.h" + +namespace views { + +//static +int TooltipManager::tooltip_height_ = 0; + +// Default timeout for the tooltip displayed using keyboard. +// Timeout is mentioned in milliseconds. +static const int kDefaultTimeout = 4000; + +// Maximum number of lines we allow in the tooltip. +static const int kMaxLines = 6; + +// Maximum number of characters we allow in a tooltip. +static const int kMaxTooltipLength = 1024; + +// Breaks |text| along line boundaries, placing each line of text into lines. +static void SplitTooltipString(const std::wstring& text, + std::vector<std::wstring>* lines) { + size_t index = 0; + size_t next_index; + while ((next_index = text.find(TooltipManager::GetLineSeparator(), index)) + != std::wstring::npos && lines->size() < kMaxLines) { + lines->push_back(text.substr(index, next_index - index)); + index = next_index + TooltipManager::GetLineSeparator().size(); + } + if (next_index != text.size() && lines->size() < kMaxLines) + lines->push_back(text.substr(index, text.size() - index)); +} + +// static +int TooltipManager::GetTooltipHeight() { + DCHECK(tooltip_height_ > 0); + return tooltip_height_; +} + +static ChromeFont DetermineDefaultFont() { + HWND window = CreateWindowEx( + WS_EX_TRANSPARENT | l10n_util::GetExtendedTooltipStyles(), + TOOLTIPS_CLASS, NULL, 0 , 0, 0, 0, 0, NULL, NULL, NULL, NULL); + HFONT hfont = reinterpret_cast<HFONT>(SendMessage(window, WM_GETFONT, 0, 0)); + ChromeFont font = hfont ? ChromeFont::CreateFont(hfont) : ChromeFont(); + DestroyWindow(window); + return font; +} + +// static +ChromeFont TooltipManager::GetDefaultFont() { + static ChromeFont* font = NULL; + if (!font) + font = new ChromeFont(DetermineDefaultFont()); + return *font; +} + +// static +const std::wstring& TooltipManager::GetLineSeparator() { + static const std::wstring* separator = NULL; + if (!separator) + separator = new std::wstring(L"\r\n"); + return *separator; +} + +TooltipManager::TooltipManager(Widget* widget, HWND parent) + : widget_(widget), + parent_(parent), + last_mouse_x_(-1), + last_mouse_y_(-1), + tooltip_showing_(false), + last_tooltip_view_(NULL), + last_view_out_of_sync_(false), + tooltip_width_(0), + keyboard_tooltip_hwnd_(NULL), +#pragma warning(suppress: 4355) + keyboard_tooltip_factory_(this) { + DCHECK(widget && parent); + Init(); +} + +TooltipManager::~TooltipManager() { + if (tooltip_hwnd_) + DestroyWindow(tooltip_hwnd_); + if (keyboard_tooltip_hwnd_) + DestroyWindow(keyboard_tooltip_hwnd_); +} + +void TooltipManager::Init() { + // Create the tooltip control. + tooltip_hwnd_ = CreateWindowEx( + WS_EX_TRANSPARENT | l10n_util::GetExtendedTooltipStyles(), + TOOLTIPS_CLASS, NULL, TTS_NOPREFIX, 0, 0, 0, 0, + parent_, NULL, NULL, NULL); + + l10n_util::AdjustUIFontForWindow(tooltip_hwnd_); + + // This effectively turns off clipping of tooltips. We need this otherwise + // multi-line text (\r\n) won't work right. The size doesn't really matter + // (just as long as its bigger than the monitor's width) as we clip to the + // screen size before rendering. + SendMessage(tooltip_hwnd_, TTM_SETMAXTIPWIDTH, 0, + std::numeric_limits<short>::max()); + + // Add one tool that is used for all tooltips. + toolinfo_.cbSize = sizeof(toolinfo_); + toolinfo_.uFlags = TTF_TRANSPARENT | TTF_IDISHWND; + toolinfo_.hwnd = parent_; + toolinfo_.uId = reinterpret_cast<UINT_PTR>(parent_); + // Setting this tells windows to call parent_ back (using a WM_NOTIFY + // message) for the actual tooltip contents. + toolinfo_.lpszText = LPSTR_TEXTCALLBACK; + SetRectEmpty(&toolinfo_.rect); + SendMessage(tooltip_hwnd_, TTM_ADDTOOL, 0, (LPARAM)&toolinfo_); +} + +void TooltipManager::UpdateTooltip() { + // Set last_view_out_of_sync_ to indicate the view is currently out of sync. + // This doesn't update the view under the mouse immediately as it may cause + // timing problems. + last_view_out_of_sync_ = true; + last_tooltip_view_ = NULL; + // Hide the tooltip. + SendMessage(tooltip_hwnd_, TTM_POP, 0, 0); +} + +void TooltipManager::TooltipTextChanged(View* view) { + if (view == last_tooltip_view_) + UpdateTooltip(last_mouse_x_, last_mouse_y_); +} + +LRESULT TooltipManager::OnNotify(int w_param, NMHDR* l_param, bool* handled) { + *handled = false; + if (l_param->hwndFrom == tooltip_hwnd_ && keyboard_tooltip_hwnd_ == NULL) { + switch (l_param->code) { + case TTN_GETDISPINFO: { + if (last_view_out_of_sync_) { + // View under the mouse is out of sync, determine it now. + RootView* root_view = widget_->GetRootView(); + last_tooltip_view_ = root_view->GetViewForPoint( + gfx::Point(last_mouse_x_, last_mouse_y_)); + last_view_out_of_sync_ = false; + } + // Tooltip control is asking for the tooltip to display. + NMTTDISPINFOW* tooltip_info = + reinterpret_cast<NMTTDISPINFOW*>(l_param); + // Initialize the string, if we have a valid tooltip the string will + // get reset below. + tooltip_info->szText[0] = TEXT('\0'); + tooltip_text_.clear(); + tooltip_info->lpszText = NULL; + clipped_text_.clear(); + if (last_tooltip_view_ != NULL) { + tooltip_text_.clear(); + // Mouse is over a View, ask the View for it's tooltip. + gfx::Point view_loc(last_mouse_x_, last_mouse_y_); + View::ConvertPointToView(widget_->GetRootView(), + last_tooltip_view_, &view_loc); + if (last_tooltip_view_->GetTooltipText(view_loc.x(), view_loc.y(), + &tooltip_text_) && + !tooltip_text_.empty()) { + // View has a valid tip, copy it into TOOLTIPINFO. + clipped_text_ = tooltip_text_; + TrimTooltipToFit(&clipped_text_, &tooltip_width_, &line_count_, + last_mouse_x_, last_mouse_y_, tooltip_hwnd_); + // Adjust the clipped tooltip text for locale direction. + l10n_util::AdjustStringForLocaleDirection(clipped_text_, + &clipped_text_); + tooltip_info->lpszText = const_cast<WCHAR*>(clipped_text_.c_str()); + } else { + tooltip_text_.clear(); + } + } + *handled = true; + return 0; + } + case TTN_POP: + tooltip_showing_ = false; + *handled = true; + return 0; + case TTN_SHOW: { + *handled = true; + tooltip_showing_ = true; + // The tooltip is about to show, allow the view to position it + gfx::Point text_origin; + if (tooltip_height_ == 0) + tooltip_height_ = CalcTooltipHeight(); + gfx::Point view_loc(last_mouse_x_, last_mouse_y_); + View::ConvertPointToView(widget_->GetRootView(), + last_tooltip_view_, &view_loc); + if (last_tooltip_view_->GetTooltipTextOrigin( + view_loc.x(), view_loc.y(), &text_origin) && + SetTooltipPosition(text_origin.x(), text_origin.y())) { + // Return true, otherwise the rectangle we specified is ignored. + return TRUE; + } + return 0; + } + default: + // Fall through. + break; + } + } + return 0; +} + +bool TooltipManager::SetTooltipPosition(int text_x, int text_y) { + // NOTE: this really only tests that the y location fits on screen, but that + // is good enough for our usage. + + // Calculate the bounds the tooltip will get. + gfx::Point view_loc; + View::ConvertPointToScreen(last_tooltip_view_, &view_loc); + RECT bounds = { view_loc.x() + text_x, + view_loc.y() + text_y, + view_loc.x() + text_x + tooltip_width_, + view_loc.y() + line_count_ * GetTooltipHeight() }; + SendMessage(tooltip_hwnd_, TTM_ADJUSTRECT, TRUE, (LPARAM)&bounds); + + // Make sure the rectangle completely fits on the current monitor. If it + // doesn't, return false so that windows positions the tooltip at the + // default location. + gfx::Rect monitor_bounds = + win_util::GetMonitorBoundsForRect(gfx::Rect(bounds.left,bounds.right, + 0, 0)); + if (!monitor_bounds.Contains(gfx::Rect(bounds))) { + return false; + } + + ::SetWindowPos(tooltip_hwnd_, NULL, bounds.left, bounds.top, 0, 0, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE); + return true; +} + +int TooltipManager::CalcTooltipHeight() { + // Ask the tooltip for it's font. + int height; + HFONT hfont = reinterpret_cast<HFONT>( + SendMessage(tooltip_hwnd_, WM_GETFONT, 0, 0)); + if (hfont != NULL) { + HDC dc = GetDC(tooltip_hwnd_); + HFONT previous_font = static_cast<HFONT>(SelectObject(dc, hfont)); + int last_map_mode = SetMapMode(dc, MM_TEXT); + TEXTMETRIC font_metrics; + GetTextMetrics(dc, &font_metrics); + height = font_metrics.tmHeight; + // To avoid the DC referencing font_handle_, select the previous font. + SelectObject(dc, previous_font); + SetMapMode(dc, last_map_mode); + ReleaseDC(NULL, dc); + } else { + // Tooltip is using the system font. Use ChromeFont, which should pick + // up the system font. + height = ChromeFont().height(); + } + // Get the margins from the tooltip + RECT tooltip_margin; + SendMessage(tooltip_hwnd_, TTM_GETMARGIN, 0, (LPARAM)&tooltip_margin); + return height + tooltip_margin.top + tooltip_margin.bottom; +} + +void TooltipManager::TrimTooltipToFit(std::wstring* text, + int* max_width, + int* line_count, + int position_x, + int position_y, + HWND window) { + *max_width = 0; + *line_count = 0; + + // Clamp the tooltip length to kMaxTooltipLength so that we don't + // accidentally DOS the user with a mega tooltip (since Windows doesn't seem + // to do this itself). + if (text->length() > kMaxTooltipLength) + *text = text->substr(0, kMaxTooltipLength); + + // Determine the available width for the tooltip. + gfx::Point screen_loc(position_x, position_y); + View::ConvertPointToScreen(widget_->GetRootView(), &screen_loc); + gfx::Rect monitor_bounds = + win_util::GetMonitorBoundsForRect(gfx::Rect(screen_loc.x(), + screen_loc.y(), + 0, 0)); + RECT tooltip_margin; + SendMessage(window, TTM_GETMARGIN, 0, (LPARAM)&tooltip_margin); + const int available_width = monitor_bounds.width() - tooltip_margin.left - + tooltip_margin.right; + if (available_width <= 0) + return; + + // Split the string. + std::vector<std::wstring> lines; + SplitTooltipString(*text, &lines); + *line_count = static_cast<int>(lines.size()); + + // Format each line to fit. + ChromeFont font = GetDefaultFont(); + std::wstring result; + for (std::vector<std::wstring>::iterator i = lines.begin(); i != lines.end(); + ++i) { + std::wstring elided_text = gfx::ElideText(*i, font, available_width); + *max_width = std::max(*max_width, font.GetStringWidth(elided_text)); + if (i == lines.begin() && i + 1 == lines.end()) { + *text = elided_text; + return; + } + if (!result.empty()) + result.append(GetLineSeparator()); + result.append(elided_text); + } + *text = result; +} + +void TooltipManager::UpdateTooltip(int x, int y) { + RootView* root_view = widget_->GetRootView(); + View* view = root_view->GetViewForPoint(gfx::Point(x, y)); + if (view != last_tooltip_view_) { + // NOTE: This *must* be sent regardless of the visibility of the tooltip. + // It triggers Windows to ask for the tooltip again. + SendMessage(tooltip_hwnd_, TTM_POP, 0, 0); + last_tooltip_view_ = view; + } else if (last_tooltip_view_ != NULL) { + // Tooltip is showing, and mouse is over the same view. See if the tooltip + // text has changed. + gfx::Point view_point(x, y); + View::ConvertPointToView(root_view, last_tooltip_view_, &view_point); + std::wstring new_tooltip_text; + if (last_tooltip_view_->GetTooltipText(view_point.x(), view_point.y(), + &new_tooltip_text) && + new_tooltip_text != tooltip_text_) { + // The text has changed, hide the popup. + SendMessage(tooltip_hwnd_, TTM_POP, 0, 0); + if (!new_tooltip_text.empty() && tooltip_showing_) { + // New text is valid, show the popup. + SendMessage(tooltip_hwnd_, TTM_POPUP, 0, 0); + } + } + } +} + +void TooltipManager::OnMouse(UINT u_msg, WPARAM w_param, LPARAM l_param) { + int x = GET_X_LPARAM(l_param); + int y = GET_Y_LPARAM(l_param); + + if (u_msg >= WM_NCMOUSEMOVE && u_msg <= WM_NCXBUTTONDBLCLK) { + // NC message coordinates are in screen coordinates. + gfx::Rect frame_bounds; + widget_->GetBounds(&frame_bounds, true); + x -= frame_bounds.x(); + y -= frame_bounds.y(); + } + + if (u_msg != WM_MOUSEMOVE || last_mouse_x_ != x || last_mouse_y_ != y) { + last_mouse_x_ = x; + last_mouse_y_ = y; + HideKeyboardTooltip(); + UpdateTooltip(x, y); + } + // Forward the message onto the tooltip. + MSG msg; + msg.hwnd = parent_; + msg.message = u_msg; + msg.wParam = w_param; + msg.lParam = l_param; + SendMessage(tooltip_hwnd_, TTM_RELAYEVENT, 0, (LPARAM)&msg); +} + +void TooltipManager::ShowKeyboardTooltip(View* focused_view) { + if (tooltip_showing_) { + SendMessage(tooltip_hwnd_, TTM_POP, 0, 0); + tooltip_text_.clear(); + } + HideKeyboardTooltip(); + std::wstring tooltip_text; + if (!focused_view->GetTooltipText(0, 0, &tooltip_text)) + return; + gfx::Rect focused_bounds = focused_view->bounds(); + gfx::Point screen_point; + focused_view->ConvertPointToScreen(focused_view, &screen_point); + gfx::Point relative_point_coordinates; + focused_view->ConvertPointToWidget(focused_view, &relative_point_coordinates); + keyboard_tooltip_hwnd_ = CreateWindowEx( + WS_EX_TRANSPARENT | l10n_util::GetExtendedTooltipStyles(), + TOOLTIPS_CLASS, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL); + SendMessage(keyboard_tooltip_hwnd_, TTM_SETMAXTIPWIDTH, 0, + std::numeric_limits<short>::max()); + int tooltip_width; + int line_count; + TrimTooltipToFit(&tooltip_text, &tooltip_width, &line_count, + relative_point_coordinates.x(), + relative_point_coordinates.y(), keyboard_tooltip_hwnd_); + TOOLINFO keyboard_toolinfo; + memset(&keyboard_toolinfo, 0, sizeof(keyboard_toolinfo)); + keyboard_toolinfo.cbSize = sizeof(keyboard_toolinfo); + keyboard_toolinfo.hwnd = parent_; + keyboard_toolinfo.uFlags = TTF_TRACK | TTF_TRANSPARENT | TTF_IDISHWND ; + keyboard_toolinfo.lpszText = const_cast<WCHAR*>(tooltip_text.c_str()); + SendMessage(keyboard_tooltip_hwnd_, TTM_ADDTOOL, 0, + reinterpret_cast<LPARAM>(&keyboard_toolinfo)); + SendMessage(keyboard_tooltip_hwnd_, TTM_TRACKACTIVATE, TRUE, + reinterpret_cast<LPARAM>(&keyboard_toolinfo)); + if (!tooltip_height_) + tooltip_height_ = CalcTooltipHeight(); + RECT rect_bounds = {screen_point.x(), + screen_point.y() + focused_bounds.height(), + screen_point.x() + tooltip_width, + screen_point.y() + focused_bounds.height() + + line_count * tooltip_height_ }; + gfx::Rect monitor_bounds = + win_util::GetMonitorBoundsForRect(gfx::Rect(rect_bounds)); + rect_bounds = gfx::Rect(rect_bounds).AdjustToFit(monitor_bounds).ToRECT(); + ::SetWindowPos(keyboard_tooltip_hwnd_, NULL, rect_bounds.left, + rect_bounds.top, 0, 0, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE); + MessageLoop::current()->PostDelayedTask(FROM_HERE, + keyboard_tooltip_factory_.NewRunnableMethod( + &TooltipManager::DestroyKeyboardTooltipWindow, keyboard_tooltip_hwnd_), + kDefaultTimeout); +} + +void TooltipManager::HideKeyboardTooltip() { + if (keyboard_tooltip_hwnd_ != NULL) { + SendMessage(keyboard_tooltip_hwnd_, WM_CLOSE, 0, 0); + keyboard_tooltip_hwnd_ = NULL; + } +} + +void TooltipManager::DestroyKeyboardTooltipWindow(HWND window_to_destroy) { + if (keyboard_tooltip_hwnd_ == window_to_destroy) + HideKeyboardTooltip(); +} + +} // namespace views diff --git a/views/widget/tooltip_manager.h b/views/widget/tooltip_manager.h new file mode 100644 index 0000000..3d5620d --- /dev/null +++ b/views/widget/tooltip_manager.h @@ -0,0 +1,168 @@ +// Copyright (c) 2006-2008 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. + +#ifndef VIEWS_WIDGET_TOOLTIP_MANAGER_H_ +#define VIEWS_WIDGET_TOOLTIP_MANAGER_H_ + +#include <windows.h> +#include <commctrl.h> + +#include <string> +#include "base/basictypes.h" +#include "base/task.h" + +class ChromeFont; + +namespace views { + +class View; +class Widget; + +// TooltipManager takes care of the wiring to support tooltips for Views. +// This class is intended to be used by Widgets. To use this, you must +// do the following: +// Add the following to your MSG_MAP: +// +// MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseRange) +// MESSAGE_RANGE_HANDLER(WM_NCMOUSEMOVE, WM_NCMOUSEMOVE, OnMouseRange) +// MSG_WM_NOTIFY(OnNotify) +// +// With the following implementations: +// LRESULT XXX::OnMouseRange(UINT u_msg, WPARAM w_param, LPARAM l_param, +// BOOL& handled) { +// tooltip_manager_->OnMouse(u_msg, w_param, l_param); +// handled = FALSE; +// return 0; +// } +// +// LRESULT XXX::OnNotify(int w_param, NMHDR* l_param) { +// bool handled; +// LRESULT result = tooltip_manager_->OnNotify(w_param, l_param, &handled); +// SetMsgHandled(handled); +// return result; +// } +// +// And of course you'll need to create the TooltipManager! +// +// Lastly, you'll need to override GetTooltipManager. +// +// See XPFrame for an example of this in action. +class TooltipManager { + public: + // Returns the height of tooltips. This should only be invoked from within + // GetTooltipTextOrigin. + static int GetTooltipHeight(); + + // Returns the default font used by tooltips. + static ChromeFont GetDefaultFont(); + + // Returns the separator for lines of text in a tooltip. + static const std::wstring& GetLineSeparator(); + + // Creates a TooltipManager for the specified Widget and parent window. + TooltipManager(Widget* widget, HWND parent); + virtual ~TooltipManager(); + + // Notification that the view hierarchy has changed in some way. + void UpdateTooltip(); + + // Invoked when the tooltip text changes for the specified views. + void TooltipTextChanged(View* view); + + // Invoked when toolbar icon gets focus. + void ShowKeyboardTooltip(View* view); + + // Invoked when toolbar loses focus. + void HideKeyboardTooltip(); + + // Message handlers. These forward to the tooltip control. + virtual void OnMouse(UINT u_msg, WPARAM w_param, LPARAM l_param); + LRESULT OnNotify(int w_param, NMHDR* l_param, bool* handled); + // Not used directly by TooltipManager, but provided for AeroTooltipManager. + virtual void OnMouseLeave() {} + + protected: + virtual void Init(); + + // Updates the tooltip for the specified location. + void UpdateTooltip(int x, int y); + + // Parent window the tooltip is added to. + HWND parent_; + + // Tooltip control window. + HWND tooltip_hwnd_; + + // Tooltip information. + TOOLINFO toolinfo_; + + // Last location of the mouse. This is in the coordinates of the rootview. + int last_mouse_x_; + int last_mouse_y_; + + // Whether or not the tooltip is showing. + bool tooltip_showing_; + + private: + // Sets the tooltip position based on the x/y position of the text. If the + // tooltip fits, true is returned. + bool SetTooltipPosition(int text_x, int text_y); + + // Calculates the preferred height for tooltips. This always returns a + // positive value. + int CalcTooltipHeight(); + + // Trims the tooltip to fit, setting text to the clipped result, width to the + // width (in pixels) of the clipped text and line_count to the number of lines + // of text in the tooltip. + void TrimTooltipToFit(std::wstring* text, + int* width, + int* line_count, + int position_x, + int position_y, + HWND window); + + // Invoked when the timer elapses and tooltip has to be destroyed. + void DestroyKeyboardTooltipWindow(HWND window_to_destroy); + + // Hosting Widget. + Widget* widget_; + + // The View the mouse is under. This is null if the mouse isn't under a + // View. + View* last_tooltip_view_; + + // Whether or not the view under the mouse needs to be refreshed. If this + // is true, when the tooltip is asked for the view under the mouse is + // refreshed. + bool last_view_out_of_sync_; + + // Text for tooltip from the view. + std::wstring tooltip_text_; + + // The clipped tooltip. + std::wstring clipped_text_; + + // Number of lines in the tooltip. + int line_count_; + + // Width of the last tooltip. + int tooltip_width_; + + // Height for a tooltip; lazily calculated. + static int tooltip_height_; + + // control window for tooltip displayed using keyboard. + HWND keyboard_tooltip_hwnd_; + + // Used to register DestroyTooltipWindow function with PostDelayedTask + // function. + ScopedRunnableMethodFactory<TooltipManager> keyboard_tooltip_factory_; + + DISALLOW_EVIL_CONSTRUCTORS(TooltipManager); +}; + +} // namespace views + +#endif // VIEWS_WIDGET_TOOLTIP_MANAGER_H_ diff --git a/views/widget/widget.h b/views/widget/widget.h new file mode 100644 index 0000000..8dd3f2e --- /dev/null +++ b/views/widget/widget.h @@ -0,0 +1,81 @@ +// Copyright (c) 2006-2008 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. + +#ifndef VIEWS_WIDGET_WIDGET_H_ +#define VIEWS_WIDGET_WIDGET_H_ + +#include "base/gfx/native_widget_types.h" + +namespace gfx { +class Rect; +} + +namespace views { + +class Accelerator; +class RootView; +class TooltipManager; +class Window; + +//////////////////////////////////////////////////////////////////////////////// +// +// Widget interface +// +// Widget is an abstract class that defines the API that should be implemented +// by a native window in order to host a view hierarchy. +// +// Widget wraps a hierarchy of View objects (see view.h) that implement +// painting and flexible layout within the bounds of the Widget's window. +// +// The Widget is responsible for handling various system events and forwarding +// them to the appropriate view. +// +///////////////////////////////////////////////////////////////////////////// + +class Widget { + public: + virtual ~Widget() { } + + // Returns the bounds of this Widget in the screen coordinate system. + // If the receiving Widget is a frame which is larger than its client area, + // this method returns the client area if including_frame is false and the + // frame bounds otherwise. If the receiving Widget is not a frame, + // including_frame is ignored. + virtual void GetBounds(gfx::Rect* out, bool including_frame) const = 0; + + // Returns the gfx::NativeView associated with this Widget. + virtual gfx::NativeView GetNativeView() const = 0; + + // Forces a paint of a specified rectangle immediately. + virtual void PaintNow(const gfx::Rect& update_rect) = 0; + + // Returns the RootView contained by this Widget. + virtual RootView* GetRootView() = 0; + + // Returns whether the Widget is visible to the user. + virtual bool IsVisible() const = 0; + + // Returns whether the Widget is the currently active window. + virtual bool IsActive() const = 0; + + // Returns the TooltipManager for this Widget. If this Widget does not support + // tooltips, NULL is returned. + virtual TooltipManager* GetTooltipManager() { + return NULL; + } + + // Returns the accelerator given a command id. Returns false if there is + // no accelerator associated with a given id, which is a common condition. + virtual bool GetAccelerator(int cmd_id, + Accelerator* accelerator) = 0; + + // Returns the Window containing this Widget, or NULL if not contained in a + // window. + virtual Window* GetWindow() { return NULL; } + virtual const Window* GetWindow() const { return NULL; } +}; + +} // namespace views + +#endif // VIEWS_WIDGET_WIDGET_H_ diff --git a/views/widget/widget_gtk.cc b/views/widget/widget_gtk.cc new file mode 100644 index 0000000..25869f6 --- /dev/null +++ b/views/widget/widget_gtk.cc @@ -0,0 +1,386 @@ +// Copyright (c) 2009 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/widget_gtk.h" + +#include "views/fill_layout.h" +#include "views/widget/root_view.h" + +namespace views { + +WidgetGtk::WidgetGtk() + : widget_(NULL), + is_mouse_down_(false), + last_mouse_event_was_move_(false) { +} + +WidgetGtk::~WidgetGtk() { + gtk_widget_unref(widget_); + + // MessageLoopForUI::current()->RemoveObserver(this); +} + +void WidgetGtk::Init(const gfx::Rect& bounds, + bool has_own_focus_manager) { + + // Force creation of the RootView if it hasn't been created yet. + GetRootView(); + + // Make container here. + widget_ = gtk_drawing_area_new(); + gtk_drawing_area_size(GTK_DRAWING_AREA(widget_), 100, 100); + gtk_widget_show(widget_); + + // Make sure we receive our motion events. + gtk_widget_set_events(widget_, + gtk_widget_get_events(widget_) | + GDK_ENTER_NOTIFY_MASK | + GDK_LEAVE_NOTIFY_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK); + + root_view_->OnWidgetCreated(); + + // TODO(port): if(has_own_focus_manager) block + + SetViewForNative(widget_, this); + SetRootViewForWidget(widget_, root_view_.get()); + + // MessageLoopForUI::current()->AddObserver(this); + + g_signal_connect_after(G_OBJECT(widget_), "size_allocate", + G_CALLBACK(CallSizeAllocate), NULL); + g_signal_connect(G_OBJECT(widget_), "expose_event", + G_CALLBACK(CallPaint), NULL); + g_signal_connect(G_OBJECT(widget_), "enter_notify_event", + G_CALLBACK(CallEnterNotify), NULL); + g_signal_connect(G_OBJECT(widget_), "leave_notify_event", + G_CALLBACK(CallLeaveNotify), NULL); + g_signal_connect(G_OBJECT(widget_), "motion_notify_event", + G_CALLBACK(CallMotionNotify), NULL); + g_signal_connect(G_OBJECT(widget_), "button_press_event", + G_CALLBACK(CallButtonPress), NULL); + g_signal_connect(G_OBJECT(widget_), "button_release_event", + G_CALLBACK(CallButtonRelease), NULL); + g_signal_connect(G_OBJECT(widget_), "focus_in_event", + G_CALLBACK(CallFocusIn), NULL); + g_signal_connect(G_OBJECT(widget_), "focus_out_event", + G_CALLBACK(CallFocusOut), NULL); + g_signal_connect(G_OBJECT(widget_), "key_press_event", + G_CALLBACK(CallKeyPress), NULL); + g_signal_connect(G_OBJECT(widget_), "key_release_event", + G_CALLBACK(CallKeyRelease), NULL); + g_signal_connect(G_OBJECT(widget_), "scroll_event", + G_CALLBACK(CallScroll), NULL); + g_signal_connect(G_OBJECT(widget_), "visibility_notify_event", + G_CALLBACK(CallVisibilityNotify), NULL); + + // TODO(erg): Ignore these signals for now because they're such a drag. + // + // g_signal_connect(G_OBJECT(widget_), "drag_motion", + // G_CALLBACK(drag_motion_event_cb), NULL); + // g_signal_connect(G_OBJECT(widget_), "drag_leave", + // G_CALLBACK(drag_leave_event_cb), NULL); + // g_signal_connect(G_OBJECT(widget_), "drag_drop", + // G_CALLBACK(drag_drop_event_cb), NULL); + // g_signal_connect(G_OBJECT(widget_), "drag_data_received", + // G_CALLBACK(drag_data_received_event_cb), NULL); +} + +void WidgetGtk::SetContentsView(View* view) { + DCHECK(view && widget_) << "Can't be called until after the HWND is created!"; + // The ContentsView must be set up _after_ the window is created so that its + // Widget pointer is valid. + root_view_->SetLayoutManager(new FillLayout); + if (root_view_->GetChildViewCount() != 0) + root_view_->RemoveAllChildViews(true); + root_view_->AddChildView(view); + + // TODO(erg): Terrible hack to work around lack of real sizing mechanics for + // now. + root_view_->SetBounds(0, 0, 100, 100); + root_view_->Layout(); + root_view_->SchedulePaint(); + NOTIMPLEMENTED(); +} + +void WidgetGtk::GetBounds(gfx::Rect* out, bool including_frame) const { + if (including_frame) { + NOTIMPLEMENTED(); + *out = gfx::Rect(); + return; + } + + // TODO(erg): Not sure how to implement this. gtk_widget_size_request() + // returns a widget's requested size--not it's actual size. The system of + // containers and such do auto sizing tricks to make everything work within + // the constraints and requested sizes... + NOTIMPLEMENTED(); +} + +gfx::NativeView WidgetGtk::GetNativeView() const { + return widget_; +} + +void WidgetGtk::PaintNow(const gfx::Rect& update_rect) { + // TODO(erg): This is woefully incomplete and is a straw man implementation. + gtk_widget_queue_draw_area(widget_, update_rect.x(), update_rect.y(), + update_rect.width(), update_rect.height()); +} + +RootView* WidgetGtk::GetRootView() { + if (!root_view_.get()) { + // First time the root view is being asked for, create it now. + root_view_.reset(CreateRootView()); + } + return root_view_.get(); +} + +bool WidgetGtk::IsVisible() const { + return GTK_WIDGET_VISIBLE(widget_); +} + +bool WidgetGtk::IsActive() const { + NOTIMPLEMENTED(); + return false; +} + +TooltipManager* WidgetGtk::GetTooltipManager() { + NOTIMPLEMENTED(); + return NULL; +} + +bool WidgetGtk::GetAccelerator(int cmd_id, Accelerator* accelerator) { + NOTIMPLEMENTED(); + return false; +} + +gboolean WidgetGtk::OnMotionNotify(GtkWidget* widget, GdkEventMotion* event) { + gfx::Point screen_loc(event->x_root, event->y_root); + if (last_mouse_event_was_move_ && last_mouse_move_x_ == screen_loc.x() && + last_mouse_move_y_ == screen_loc.y()) { + // Don't generate a mouse event for the same location as the last. + return false; + } + last_mouse_move_x_ = screen_loc.x(); + last_mouse_move_y_ = screen_loc.y(); + last_mouse_event_was_move_ = true; + MouseEvent mouse_move(Event::ET_MOUSE_MOVED, + event->x, + event->y, + Event::GetFlagsFromGdkState(event->state)); + root_view_->OnMouseMoved(mouse_move); + return true; +} + +gboolean WidgetGtk::OnButtonPress(GtkWidget* widget, GdkEventButton* event) { + return ProcessMousePressed(event); +} + +gboolean WidgetGtk::OnButtonRelease(GtkWidget* widget, GdkEventButton* event) { + ProcessMouseReleased(event); + return true; +} + +gboolean WidgetGtk::OnPaint(GtkWidget* widget, GdkEventExpose* event) { + root_view_->OnPaint(event); + return true; +} + +gboolean WidgetGtk::OnEnterNotify(GtkWidget* widget, GdkEventCrossing* event) { + // TODO(port): We may not actually need this message; it looks like + // OnNotificationNotify() takes care of this case... + return false; +} + +gboolean WidgetGtk::OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event) { + last_mouse_event_was_move_ = false; + root_view_->ProcessOnMouseExited(); + return true; +} + +gboolean WidgetGtk::OnKeyPress(GtkWidget* widget, GdkEventKey* event) { + KeyEvent key_event(event); + return root_view_->ProcessKeyEvent(key_event); +} + +gboolean WidgetGtk::OnKeyRelease(GtkWidget* widget, GdkEventKey* event) { + KeyEvent key_event(event); + return root_view_->ProcessKeyEvent(key_event); +} + +RootView* WidgetGtk::CreateRootView() { + return new RootView(this); +} + +bool WidgetGtk::ProcessMousePressed(GdkEventButton* event) { + last_mouse_event_was_move_ = false; + MouseEvent mouse_pressed(Event::ET_MOUSE_PRESSED, + event->x, event->y, +// (dbl_click ? MouseEvent::EF_IS_DOUBLE_CLICK : 0) | + Event::GetFlagsFromGdkState(event->state)); + if (root_view_->OnMousePressed(mouse_pressed)) { + is_mouse_down_ = true; + // TODO(port): Enable this once I figure out what capture is. + // if (!has_capture_) { + // SetCapture(); + // has_capture_ = true; + // current_action_ = FA_FORWARDING; + // } + return true; + } + + return false; +} + +void WidgetGtk::ProcessMouseReleased(GdkEventButton* event) { + last_mouse_event_was_move_ = false; + MouseEvent mouse_up(Event::ET_MOUSE_RELEASED, + event->x, event->y, + Event::GetFlagsFromGdkState(event->state)); + // Release the capture first, that way we don't get confused if + // OnMouseReleased blocks. + // + // TODO(port): Enable this once I figure out what capture is. + // if (has_capture_ && ReleaseCaptureOnMouseReleased()) { + // has_capture_ = false; + // current_action_ = FA_NONE; + // ReleaseCapture(); + // } + is_mouse_down_ = false; + root_view_->OnMouseReleased(mouse_up, false); +} + +// static +WidgetGtk* WidgetGtk::GetViewForNative(GtkWidget* widget) { + gpointer user_data = g_object_get_data(G_OBJECT(widget), "chrome-views"); + return static_cast<WidgetGtk*>(user_data); +} + +// static +void WidgetGtk::SetViewForNative(GtkWidget* widget, WidgetGtk* view) { + g_object_set_data(G_OBJECT(widget), "chrome-views", view); +} + +// static +RootView* WidgetGtk::GetRootViewForWidget(GtkWidget* widget) { + gpointer user_data = g_object_get_data(G_OBJECT(widget), "root-view"); + return static_cast<RootView*>(user_data); +} + +// static +void WidgetGtk::SetRootViewForWidget(GtkWidget* widget, RootView* root_view) { + g_object_set_data(G_OBJECT(widget), "root-view", root_view); +} + +// static +void WidgetGtk::CallSizeAllocate(GtkWidget* widget, GtkAllocation* allocation) { + WidgetGtk* widget_gtk = GetViewForNative(widget); + if (!widget_gtk) + return; + + widget_gtk->OnSizeAllocate(widget, allocation); +} + +gboolean WidgetGtk::CallPaint(GtkWidget* widget, GdkEventExpose* event) { + WidgetGtk* widget_gtk = GetViewForNative(widget); + if (!widget_gtk) + return false; + + return widget_gtk->OnPaint(widget, event); +} + +gboolean WidgetGtk::CallEnterNotify(GtkWidget* widget, GdkEventCrossing* event) { + WidgetGtk* widget_gtk = GetViewForNative(widget); + if (!widget_gtk) + return false; + + return widget_gtk->OnEnterNotify(widget, event); +} + +gboolean WidgetGtk::CallLeaveNotify(GtkWidget* widget, GdkEventCrossing* event) { + WidgetGtk* widget_gtk = GetViewForNative(widget); + if (!widget_gtk) + return false; + + return widget_gtk->OnLeaveNotify(widget, event); +} + +gboolean WidgetGtk::CallMotionNotify(GtkWidget* widget, GdkEventMotion* event) { + WidgetGtk* widget_gtk = GetViewForNative(widget); + if (!widget_gtk) + return false; + + return widget_gtk->OnMotionNotify(widget, event); +} + +gboolean WidgetGtk::CallButtonPress(GtkWidget* widget, GdkEventButton* event) { + WidgetGtk* widget_gtk = GetViewForNative(widget); + if (!widget_gtk) + return false; + + return widget_gtk->OnButtonPress(widget, event); +} + +gboolean WidgetGtk::CallButtonRelease(GtkWidget* widget, GdkEventButton* event) { + WidgetGtk* widget_gtk = GetViewForNative(widget); + if (!widget_gtk) + return false; + + return widget_gtk->OnButtonRelease(widget, event); +} + +gboolean WidgetGtk::CallFocusIn(GtkWidget* widget, GdkEventFocus* event) { + WidgetGtk* widget_gtk = GetViewForNative(widget); + if (!widget_gtk) + return false; + + return widget_gtk->OnFocusIn(widget, event); +} + +gboolean WidgetGtk::CallFocusOut(GtkWidget* widget, GdkEventFocus* event) { + WidgetGtk* widget_gtk = GetViewForNative(widget); + if (!widget_gtk) + return false; + + return widget_gtk->OnFocusOut(widget, event); +} + +gboolean WidgetGtk::CallKeyPress(GtkWidget* widget, GdkEventKey* event) { + WidgetGtk* widget_gtk = GetViewForNative(widget); + if (!widget_gtk) + return false; + + return widget_gtk->OnKeyPress(widget, event); +} + +gboolean WidgetGtk::CallKeyRelease(GtkWidget* widget, GdkEventKey* event) { + WidgetGtk* widget_gtk = GetViewForNative(widget); + if (!widget_gtk) + return false; + + return widget_gtk->OnKeyRelease(widget, event); +} + +gboolean WidgetGtk::CallScroll(GtkWidget* widget, GdkEventScroll* event) { + WidgetGtk* widget_gtk = GetViewForNative(widget); + if (!widget_gtk) + return false; + + return widget_gtk->OnScroll(widget, event); +} + +gboolean WidgetGtk::CallVisibilityNotify(GtkWidget* widget, + GdkEventVisibility* event) { + WidgetGtk* widget_gtk = GetViewForNative(widget); + if (!widget_gtk) + return false; + + return widget_gtk->OnVisibilityNotify(widget, event); +} + +} diff --git a/views/widget/widget_gtk.h b/views/widget/widget_gtk.h new file mode 100644 index 0000000..4bbc0dd --- /dev/null +++ b/views/widget/widget_gtk.h @@ -0,0 +1,125 @@ +// Copyright (c) 2009 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. + +#ifndef VIEWS_WIDGET_WIDGET_GTK_H_ +#define VIEWS_WIDGET_WIDGET_GTK_H_ + +#include <gtk/gtk.h> + +#include "base/message_loop.h" +#include "views/widget/widget.h" + +namespace gfx { +class Rect; +} + +namespace views { + +class View; + +class WidgetGtk : public Widget { + public: + static WidgetGtk* Construct() { + // This isn't used, but exists to force WidgetGtk to be instantiable. + return new WidgetGtk; + } + + WidgetGtk(); + virtual ~WidgetGtk(); + + // Initializes this widget and returns the gtk drawing area for the caller to + // add to its hierarchy. (We can't pass in the parent to this method because + // there are no standard adding semantics in gtk...) + void Init(const gfx::Rect& bounds, bool has_own_focus_manager); + + virtual void SetContentsView(View* view); + + // Overridden from Widget: + virtual void GetBounds(gfx::Rect* out, bool including_frame) const; + virtual gfx::NativeView GetNativeView() const; + virtual void PaintNow(const gfx::Rect& update_rect); + virtual RootView* GetRootView(); + virtual bool IsVisible() const; + virtual bool IsActive() const; + virtual TooltipManager* GetTooltipManager(); + virtual bool GetAccelerator(int cmd_id, Accelerator* accelerator); + + protected: + virtual void OnSizeAllocate(GtkWidget* widget, GtkAllocation* allocation) {} + virtual gboolean OnPaint(GtkWidget* widget, GdkEventExpose* event); + virtual gboolean OnEnterNotify(GtkWidget* widget, GdkEventCrossing* event); + virtual gboolean OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event); + virtual gboolean OnMotionNotify(GtkWidget* widget, GdkEventMotion* event); + virtual gboolean OnButtonPress(GtkWidget* widget, GdkEventButton* event); + virtual gboolean OnButtonRelease(GtkWidget* widget, GdkEventButton* event); + virtual gboolean OnFocusIn(GtkWidget* widget, GdkEventFocus* event) { + return false; + } + virtual gboolean OnFocusOut(GtkWidget* widget, GdkEventFocus* event) { + return false; + } + virtual gboolean OnKeyPress(GtkWidget* widget, GdkEventKey* event); + virtual gboolean OnKeyRelease(GtkWidget* widget, GdkEventKey* event); + virtual gboolean OnScroll(GtkWidget* widget, GdkEventScroll* event) { + return false; + } + virtual gboolean OnVisibilityNotify(GtkWidget* widget, + GdkEventVisibility* event) { + return false; + } + + private: + virtual RootView* CreateRootView(); + + // Process a mouse click + bool ProcessMousePressed(GdkEventButton* event); + void ProcessMouseReleased(GdkEventButton* event); + + // Sets and retrieves the WidgetGtk in the userdata section of the widget. + static WidgetGtk* GetViewForNative(GtkWidget* widget); + static void SetViewForNative(GtkWidget* widget, WidgetGtk* view); + + static RootView* GetRootViewForWidget(GtkWidget* widget); + static void SetRootViewForWidget(GtkWidget* widget, RootView* root_view); + + // A set of static signal handlers that bridge + static void CallSizeAllocate(GtkWidget* widget, GtkAllocation* allocation); + static gboolean CallPaint(GtkWidget* widget, GdkEventExpose* event); + static gboolean CallEnterNotify(GtkWidget* widget, GdkEventCrossing* event); + static gboolean CallLeaveNotify(GtkWidget* widget, GdkEventCrossing* event); + static gboolean CallMotionNotify(GtkWidget* widget, GdkEventMotion* event); + static gboolean CallButtonPress(GtkWidget* widget, GdkEventButton* event); + static gboolean CallButtonRelease(GtkWidget* widget, GdkEventButton* event); + static gboolean CallFocusIn(GtkWidget* widget, GdkEventFocus* event); + static gboolean CallFocusOut(GtkWidget* widget, GdkEventFocus* event); + static gboolean CallKeyPress(GtkWidget* widget, GdkEventKey* event); + static gboolean CallKeyRelease(GtkWidget* widget, GdkEventKey* event); + static gboolean CallScroll(GtkWidget* widget, GdkEventScroll* event); + static gboolean CallVisibilityNotify(GtkWidget* widget, + GdkEventVisibility* event); + + // Our native view. + GtkWidget* widget_; + + // The root of the View hierarchy attached to this window. + scoped_ptr<RootView> root_view_; + + // If true, the mouse is currently down. + bool is_mouse_down_; + + // The following are used to detect duplicate mouse move events and not + // deliver them. Displaying a window may result in the system generating + // duplicate move events even though the mouse hasn't moved. + + // If true, the last event was a mouse move event. + bool last_mouse_event_was_move_; + + // Coordinates of the last mouse move event, in screen coordinates. + int last_mouse_move_x_; + int last_mouse_move_y_; +}; + +} // namespace views + +#endif // VIEWS_WIDGET_WIDGET_GTK_H_ diff --git a/views/widget/widget_win.cc b/views/widget/widget_win.cc new file mode 100644 index 0000000..2bc9b0c --- /dev/null +++ b/views/widget/widget_win.cc @@ -0,0 +1,999 @@ +// Copyright (c) 2006-2008 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/widget_win.h" + +#include "app/gfx/chrome_canvas.h" +#include "base/gfx/native_theme.h" +#include "base/string_util.h" +#include "base/win_util.h" +#include "chrome/app/chrome_dll_resource.h" +#include "chrome/common/win_util.h" +#include "views/accessibility/view_accessibility.h" +#include "views/controls/native_control_win.h" +#include "views/fill_layout.h" +#include "views/focus/focus_util_win.h" +#include "views/widget/aero_tooltip_manager.h" +#include "views/widget/root_view.h" +#include "views/window/window_win.h" + +namespace views { + +static const DWORD kWindowDefaultChildStyle = + WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; +static const DWORD kWindowDefaultStyle = WS_OVERLAPPEDWINDOW; +static const DWORD kWindowDefaultExStyle = 0; + +// Property used to link the HWND to its RootView. +static const wchar_t* const kRootViewWindowProperty = L"__ROOT_VIEW__"; + +bool SetRootViewForHWND(HWND hwnd, RootView* root_view) { + return ::SetProp(hwnd, kRootViewWindowProperty, root_view) ? true : false; +} + +RootView* GetRootViewForHWND(HWND hwnd) { + return reinterpret_cast<RootView*>(::GetProp(hwnd, kRootViewWindowProperty)); +} + +NativeControlWin* GetNativeControlWinForHWND(HWND hwnd) { + return reinterpret_cast<NativeControlWin*>( + ::GetProp(hwnd, NativeControlWin::kNativeControlWinKey)); +} + +/////////////////////////////////////////////////////////////////////////////// +// Window class tracking. + +// static +const wchar_t* const WidgetWin::kBaseClassName = + L"Chrome_WidgetWin_"; + +// Window class information used for registering unique windows. +struct ClassInfo { + UINT style; + HBRUSH background; + + explicit ClassInfo(int style) + : style(style), + background(NULL) {} + + // Compares two ClassInfos. Returns true if all members match. + bool Equals(const ClassInfo& other) const { + return (other.style == style && other.background == background); + } +}; + +class ClassRegistrar { + public: + ~ClassRegistrar() { + for (RegisteredClasses::iterator i = registered_classes_.begin(); + i != registered_classes_.end(); ++i) { + UnregisterClass(i->name.c_str(), NULL); + } + } + + // Puts the name for the class matching |class_info| in |class_name|, creating + // a new name if the class is not yet known. + // Returns true if this class was already known, false otherwise. + bool RetrieveClassName(const ClassInfo& class_info, std::wstring* name) { + for (RegisteredClasses::const_iterator i = registered_classes_.begin(); + i != registered_classes_.end(); ++i) { + if (class_info.Equals(i->info)) { + name->assign(i->name); + return true; + } + } + + name->assign(std::wstring(WidgetWin::kBaseClassName) + + IntToWString(registered_count_++)); + return false; + } + + void RegisterClass(const ClassInfo& class_info, + const std::wstring& name, + ATOM atom) { + registered_classes_.push_back(RegisteredClass(class_info, name, atom)); + } + + private: + // Represents a registered window class. + struct RegisteredClass { + RegisteredClass(const ClassInfo& info, + const std::wstring& name, + ATOM atom) + : info(info), + name(name), + atom(atom) { + } + + // Info used to create the class. + ClassInfo info; + + // The name given to the window. + std::wstring name; + + // The ATOM returned from creating the window. + ATOM atom; + }; + + ClassRegistrar() : registered_count_(0) { } + friend struct DefaultSingletonTraits<ClassRegistrar>; + + typedef std::list<RegisteredClass> RegisteredClasses; + RegisteredClasses registered_classes_; + + // Counter of how many classes have ben registered so far. + int registered_count_; + + DISALLOW_COPY_AND_ASSIGN(ClassRegistrar); +}; + +/////////////////////////////////////////////////////////////////////////////// +// WidgetWin, public + +WidgetWin::WidgetWin() + : close_widget_factory_(this), + active_mouse_tracking_flags_(0), + has_capture_(false), + current_action_(FA_NONE), + window_style_(0), + window_ex_style_(kWindowDefaultExStyle), + use_layered_buffer_(true), + layered_alpha_(255), + delete_on_destroy_(true), + can_update_layered_window_(true), + last_mouse_event_was_move_(false), + is_mouse_down_(false), + is_window_(false), + class_style_(CS_DBLCLKS), + hwnd_(NULL) { +} + +WidgetWin::~WidgetWin() { + MessageLoopForUI::current()->RemoveObserver(this); +} + +void WidgetWin::Init(HWND parent, const gfx::Rect& bounds, + bool has_own_focus_manager) { + if (window_style_ == 0) + window_style_ = parent ? kWindowDefaultChildStyle : kWindowDefaultStyle; + + // See if the style has been overridden. + opaque_ = !(window_ex_style_ & WS_EX_TRANSPARENT); + use_layered_buffer_ = (use_layered_buffer_ && + !!(window_ex_style_ & WS_EX_LAYERED)); + + // Force creation of the RootView if it hasn't been created yet. + GetRootView(); + + // Ensures the parent we have been passed is valid, otherwise CreateWindowEx + // will fail. + if (parent && !::IsWindow(parent)) { + NOTREACHED() << "invalid parent window specified."; + parent = NULL; + } + + hwnd_ = CreateWindowEx(window_ex_style_, GetWindowClassName().c_str(), L"", + window_style_, bounds.x(), bounds.y(), bounds.width(), + bounds.height(), parent, NULL, NULL, this); + DCHECK(hwnd_); + TRACK_HWND_CREATION(hwnd_); + SetWindowSupportsRerouteMouseWheel(hwnd_); + + // The window procedure should have set the data for us. + DCHECK(win_util::GetWindowUserData(hwnd_) == this); + + root_view_->OnWidgetCreated(); + + if (has_own_focus_manager) { + FocusManager::CreateFocusManager(hwnd_, GetRootView()); + } else { + // Subclass the window so we get the tab key messages when a view with no + // associated native window is focused. + FocusManager::InstallFocusSubclass(hwnd_, NULL); + } + + // Sets the RootView as a property, so the automation can introspect windows. + SetRootViewForHWND(hwnd_, root_view_.get()); + + MessageLoopForUI::current()->AddObserver(this); + + // Windows special DWM window frame requires a special tooltip manager so + // that window controls in Chrome windows don't flicker when you move your + // mouse over them. See comment in aero_tooltip_manager.h. + if (win_util::ShouldUseVistaFrame()) { + tooltip_manager_.reset(new AeroTooltipManager(this, GetNativeView())); + } else { + tooltip_manager_.reset(new TooltipManager(this, GetNativeView())); + } + + // This message initializes the window so that focus border are shown for + // windows. + ::SendMessage(GetNativeView(), + WM_CHANGEUISTATE, + MAKELPARAM(UIS_CLEAR, UISF_HIDEFOCUS), + 0); + + // Bug 964884: detach the IME attached to this window. + // We should attach IMEs only when we need to input CJK strings. + ::ImmAssociateContextEx(GetNativeView(), NULL, 0); +} + +void WidgetWin::SetContentsView(View* view) { + DCHECK(view && hwnd_) << "Can't be called until after the HWND is created!"; + // The ContentsView must be set up _after_ the window is created so that its + // Widget pointer is valid. + root_view_->SetLayoutManager(new FillLayout); + if (root_view_->GetChildViewCount() != 0) + root_view_->RemoveAllChildViews(true); + root_view_->AddChildView(view); + + // Manually size the window here to ensure the root view is laid out. + RECT wr; + GetWindowRect(&wr); + ChangeSize(0, CSize(wr.right - wr.left, wr.bottom - wr.top)); +} + +/////////////////////////////////////////////////////////////////////////////// +// Widget implementation: + +void WidgetWin::GetBounds(gfx::Rect* out, bool including_frame) const { + CRect crect; + if (including_frame) { + GetWindowRect(&crect); + *out = gfx::Rect(crect); + return; + } + + GetClientRect(&crect); + POINT p = {0, 0}; + ::ClientToScreen(hwnd_, &p); + out->SetRect(crect.left + p.x, crect.top + p.y, + crect.Width(), crect.Height()); +} + +gfx::NativeView WidgetWin::GetNativeView() const { + return hwnd_; +} + +void WidgetWin::PaintNow(const gfx::Rect& update_rect) { + if (use_layered_buffer_) { + PaintLayeredWindow(); + } else if (root_view_->NeedsPainting(false) && IsWindow()) { + if (!opaque_ && GetParent()) { + // We're transparent. Need to force painting to occur from our parent. + CRect parent_update_rect = update_rect.ToRECT(); + POINT location_in_parent = { 0, 0 }; + ClientToScreen(hwnd_, &location_in_parent); + ::ScreenToClient(GetParent(), &location_in_parent); + parent_update_rect.OffsetRect(location_in_parent); + ::RedrawWindow(GetParent(), parent_update_rect, NULL, + RDW_UPDATENOW | RDW_INVALIDATE | RDW_ALLCHILDREN); + } else { + RECT native_update_rect = update_rect.ToRECT(); + RedrawWindow(hwnd_, &native_update_rect, NULL, + RDW_UPDATENOW | RDW_INVALIDATE | RDW_ALLCHILDREN); + } + // As we were created with a style of WS_CLIPCHILDREN redraw requests may + // result in an empty paint rect in WM_PAINT (this'll happen if a + // child HWND completely contains the update _rect). In such a scenario + // RootView would never get a ProcessPaint and always think it needs to + // be painted (leading to a steady stream of RedrawWindow requests on every + // event). For this reason we tell RootView it doesn't need to paint + // here. + root_view_->ClearPaintRect(); + } +} + +RootView* WidgetWin::GetRootView() { + if (!root_view_.get()) { + // First time the root view is being asked for, create it now. + root_view_.reset(CreateRootView()); + } + return root_view_.get(); +} + +bool WidgetWin::IsVisible() const { + return !!::IsWindowVisible(GetNativeView()); +} + +bool WidgetWin::IsActive() const { + return win_util::IsWindowActive(GetNativeView()); +} + +TooltipManager* WidgetWin::GetTooltipManager() { + return tooltip_manager_.get(); +} + +Window* WidgetWin::GetWindow() { + return GetWindowImpl(hwnd_); +} + +const Window* WidgetWin::GetWindow() const { + return GetWindowImpl(hwnd_); +} + +void WidgetWin::SetLayeredAlpha(BYTE layered_alpha) { + layered_alpha_ = layered_alpha; + +// if (hwnd_) +// UpdateWindowFromContents(contents_->getTopPlatformDevice().getBitmapDC()); +} + +void WidgetWin::SetUseLayeredBuffer(bool use_layered_buffer) { + if (use_layered_buffer_ == use_layered_buffer) + return; + + use_layered_buffer_ = use_layered_buffer; + if (!hwnd_) + return; + + if (use_layered_buffer_) { + // Force creation of the buffer at the right size. + RECT wr; + GetWindowRect(&wr); + ChangeSize(0, CSize(wr.right - wr.left, wr.bottom - wr.top)); + } else { + contents_.reset(NULL); + } +} + +static BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM l_param) { + RootView* root_view = + reinterpret_cast<RootView*>(GetProp(hwnd, kRootViewWindowProperty)); + if (root_view) { + *reinterpret_cast<RootView**>(l_param) = root_view; + return FALSE; // Stop enumerating. + } + return TRUE; // Keep enumerating. +} + +// static +RootView* WidgetWin::FindRootView(HWND hwnd) { + RootView* root_view = + reinterpret_cast<RootView*>(GetProp(hwnd, kRootViewWindowProperty)); + if (root_view) + return root_view; + + // Enumerate all children and check if they have a RootView. + EnumChildWindows(hwnd, EnumChildProc, reinterpret_cast<LPARAM>(&root_view)); + + return root_view; +} + +void WidgetWin::Close() { + if (!IsWindow()) + return; // No need to do anything. + + // Let's hide ourselves right away. + Hide(); + if (close_widget_factory_.empty()) { + // And we delay the close so that if we are called from an ATL callback, + // we don't destroy the window before the callback returned (as the caller + // may delete ourselves on destroy and the ATL callback would still + // dereference us when the callback returns). + MessageLoop::current()->PostTask(FROM_HERE, + close_widget_factory_.NewRunnableMethod( + &WidgetWin::CloseNow)); + } +} + +void WidgetWin::Hide() { + if (IsWindow()) { + // NOTE: Be careful not to activate any windows here (for example, calling + // ShowWindow(SW_HIDE) will automatically activate another window). This + // code can be called while a window is being deactivated, and activating + // another window will screw up the activation that is already in progress. + SetWindowPos(NULL, 0, 0, 0, 0, + SWP_HIDEWINDOW | SWP_NOACTIVATE | SWP_NOMOVE | + SWP_NOREPOSITION | SWP_NOSIZE | SWP_NOZORDER); + } +} + +void WidgetWin::Show() { + if (IsWindow()) + ShowWindow(SW_SHOWNOACTIVATE); +} + +void WidgetWin::CloseNow() { + // We may already have been destroyed if the selection resulted in a tab + // switch which will have reactivated the browser window and closed us, so + // we need to check to see if we're still a window before trying to destroy + // ourself. + if (IsWindow()) + DestroyWindow(); +} + +/////////////////////////////////////////////////////////////////////////////// +// MessageLoop::Observer + +void WidgetWin::WillProcessMessage(const MSG& msg) { +} + +void WidgetWin::DidProcessMessage(const MSG& msg) { + if (root_view_->NeedsPainting(true)) { + PaintNow(root_view_->GetScheduledPaintRect()); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// FocusTraversable + +View* WidgetWin::FindNextFocusableView( + View* starting_view, bool reverse, Direction direction, bool dont_loop, + FocusTraversable** focus_traversable, View** focus_traversable_view) { + return root_view_->FindNextFocusableView(starting_view, + reverse, + direction, + dont_loop, + focus_traversable, + focus_traversable_view); +} + +FocusTraversable* WidgetWin::GetFocusTraversableParent() { + // We are a proxy to the root view, so we should be bypassed when traversing + // up and as a result this should not be called. + NOTREACHED(); + return NULL; +} + +void WidgetWin::SetFocusTraversableParent(FocusTraversable* parent) { + root_view_->SetFocusTraversableParent(parent); +} + +View* WidgetWin::GetFocusTraversableParentView() { + // We are a proxy to the root view, so we should be bypassed when traversing + // up and as a result this should not be called. + NOTREACHED(); + return NULL; +} + +void WidgetWin::SetFocusTraversableParentView(View* parent_view) { + root_view_->SetFocusTraversableParentView(parent_view); +} + +/////////////////////////////////////////////////////////////////////////////// +// Message handlers + +void WidgetWin::OnCaptureChanged(HWND hwnd) { + if (has_capture_) { + if (is_mouse_down_) + root_view_->ProcessMouseDragCanceled(); + is_mouse_down_ = false; + has_capture_ = false; + } +} + +void WidgetWin::OnClose() { + Close(); +} + +void WidgetWin::OnDestroy() { + root_view_->OnWidgetDestroyed(); + + RemoveProp(hwnd_, kRootViewWindowProperty); +} + +LRESULT WidgetWin::OnEraseBkgnd(HDC dc) { + // This is needed for magical win32 flicker ju-ju + return 1; +} + +LRESULT WidgetWin::OnGetObject(UINT uMsg, WPARAM w_param, LPARAM l_param) { + LRESULT reference_result = static_cast<LRESULT>(0L); + + // Accessibility readers will send an OBJID_CLIENT message + if (OBJID_CLIENT == l_param) { + // If our MSAA root is already created, reuse that pointer. Otherwise, + // create a new one. + if (!accessibility_root_) { + CComObject<ViewAccessibility>* instance = NULL; + + HRESULT hr = CComObject<ViewAccessibility>::CreateInstance(&instance); + DCHECK(SUCCEEDED(hr)); + + if (!instance) { + // Return with failure. + return static_cast<LRESULT>(0L); + } + + CComPtr<IAccessible> accessibility_instance(instance); + + if (!SUCCEEDED(instance->Initialize(root_view_.get()))) { + // Return with failure. + return static_cast<LRESULT>(0L); + } + + // All is well, assign the temp instance to the class smart pointer + accessibility_root_.Attach(accessibility_instance.Detach()); + + if (!accessibility_root_) { + // Return with failure. + return static_cast<LRESULT>(0L); + } + + // Notify that an instance of IAccessible was allocated for m_hWnd + ::NotifyWinEvent(EVENT_OBJECT_CREATE, GetNativeView(), OBJID_CLIENT, + CHILDID_SELF); + } + + // Create a reference to ViewAccessibility that MSAA will marshall + // to the client. + reference_result = LresultFromObject(IID_IAccessible, w_param, + static_cast<IAccessible*>(accessibility_root_)); + } + return reference_result; +} + +void WidgetWin::OnKeyDown(TCHAR c, UINT rep_cnt, UINT flags) { + KeyEvent event(Event::ET_KEY_PRESSED, c, rep_cnt, flags); + SetMsgHandled(root_view_->ProcessKeyEvent(event)); +} + +void WidgetWin::OnKeyUp(TCHAR c, UINT rep_cnt, UINT flags) { + KeyEvent event(Event::ET_KEY_RELEASED, c, rep_cnt, flags); + SetMsgHandled(root_view_->ProcessKeyEvent(event)); +} + +void WidgetWin::OnLButtonDown(UINT flags, const CPoint& point) { + ProcessMousePressed(point, flags | MK_LBUTTON, false, false); +} + +void WidgetWin::OnLButtonUp(UINT flags, const CPoint& point) { + ProcessMouseReleased(point, flags | MK_LBUTTON); +} + +void WidgetWin::OnLButtonDblClk(UINT flags, const CPoint& point) { + ProcessMousePressed(point, flags | MK_LBUTTON, true, false); +} + +void WidgetWin::OnMButtonDown(UINT flags, const CPoint& point) { + ProcessMousePressed(point, flags | MK_MBUTTON, false, false); +} + +void WidgetWin::OnMButtonUp(UINT flags, const CPoint& point) { + ProcessMouseReleased(point, flags | MK_MBUTTON); +} + +void WidgetWin::OnMButtonDblClk(UINT flags, const CPoint& point) { + ProcessMousePressed(point, flags | MK_MBUTTON, true, false); +} + +LRESULT WidgetWin::OnMouseActivate(HWND window, UINT hittest_code, + UINT message) { + SetMsgHandled(FALSE); + return MA_ACTIVATE; +} + +void WidgetWin::OnMouseMove(UINT flags, const CPoint& point) { + ProcessMouseMoved(point, flags, false); +} + +LRESULT WidgetWin::OnMouseLeave(UINT message, WPARAM w_param, LPARAM l_param) { + tooltip_manager_->OnMouseLeave(); + ProcessMouseExited(); + return 0; +} + +LRESULT WidgetWin::OnMouseWheel(UINT message, WPARAM w_param, LPARAM l_param) { + // Reroute the mouse-wheel to the window under the mouse pointer if + // applicable. + if (message == WM_MOUSEWHEEL && + views::RerouteMouseWheel(hwnd_, w_param, l_param)) { + return 0; + } + + int flags = GET_KEYSTATE_WPARAM(w_param); + short distance = GET_WHEEL_DELTA_WPARAM(w_param); + int x = GET_X_LPARAM(l_param); + int y = GET_Y_LPARAM(l_param); + MouseWheelEvent e(distance, x, y, Event::ConvertWindowsFlags(flags)); + return root_view_->ProcessMouseWheelEvent(e) ? 0 : 1; +} + +LRESULT WidgetWin::OnMouseRange(UINT msg, WPARAM w_param, LPARAM l_param) { + tooltip_manager_->OnMouse(msg, w_param, l_param); + SetMsgHandled(FALSE); + return 0; +} + +void WidgetWin::OnNCLButtonDblClk(UINT flags, const CPoint& point) { + SetMsgHandled(ProcessMousePressed(point, flags | MK_LBUTTON, true, true)); +} + +void WidgetWin::OnNCLButtonDown(UINT flags, const CPoint& point) { + SetMsgHandled(ProcessMousePressed(point, flags | MK_LBUTTON, false, true)); +} + +void WidgetWin::OnNCLButtonUp(UINT flags, const CPoint& point) { + SetMsgHandled(FALSE); +} + +void WidgetWin::OnNCMButtonDblClk(UINT flags, const CPoint& point) { + SetMsgHandled(ProcessMousePressed(point, flags | MK_MBUTTON, true, true)); +} + +void WidgetWin::OnNCMButtonDown(UINT flags, const CPoint& point) { + SetMsgHandled(ProcessMousePressed(point, flags | MK_MBUTTON, false, true)); +} + +void WidgetWin::OnNCMButtonUp(UINT flags, const CPoint& point) { + SetMsgHandled(FALSE); +} + +LRESULT WidgetWin::OnNCMouseLeave(UINT uMsg, WPARAM w_param, LPARAM l_param) { + ProcessMouseExited(); + return 0; +} + +LRESULT WidgetWin::OnNCMouseMove(UINT flags, const CPoint& point) { + // NC points are in screen coordinates. + CPoint temp = point; + MapWindowPoints(HWND_DESKTOP, GetNativeView(), &temp, 1); + ProcessMouseMoved(temp, 0, true); + + // We need to process this message to stop Windows from drawing the window + // controls as the mouse moves over the title bar area when the window is + // maximized. + return 0; +} + +void WidgetWin::OnNCRButtonDblClk(UINT flags, const CPoint& point) { + SetMsgHandled(ProcessMousePressed(point, flags | MK_RBUTTON, true, true)); +} + +void WidgetWin::OnNCRButtonDown(UINT flags, const CPoint& point) { + SetMsgHandled(ProcessMousePressed(point, flags | MK_RBUTTON, false, true)); +} + +void WidgetWin::OnNCRButtonUp(UINT flags, const CPoint& point) { + SetMsgHandled(FALSE); +} + +LRESULT WidgetWin::OnNotify(int w_param, NMHDR* l_param) { + // We can be sent this message before the tooltip manager is created, if a + // subclass overrides OnCreate and creates some kind of Windows control there + // that sends WM_NOTIFY messages. + if (tooltip_manager_.get()) { + bool handled; + LRESULT result = tooltip_manager_->OnNotify(w_param, l_param, &handled); + SetMsgHandled(handled); + return result; + } + SetMsgHandled(FALSE); + return 0; +} + +void WidgetWin::OnPaint(HDC dc) { + root_view_->OnPaint(GetNativeView()); +} + +void WidgetWin::OnRButtonDown(UINT flags, const CPoint& point) { + ProcessMousePressed(point, flags | MK_RBUTTON, false, false); +} + +void WidgetWin::OnRButtonUp(UINT flags, const CPoint& point) { + ProcessMouseReleased(point, flags | MK_RBUTTON); +} + +void WidgetWin::OnRButtonDblClk(UINT flags, const CPoint& point) { + ProcessMousePressed(point, flags | MK_RBUTTON, true, false); +} + +void WidgetWin::OnSize(UINT param, const CSize& size) { + ChangeSize(param, size); +} + +void WidgetWin::OnThemeChanged() { + // Notify NativeTheme. + gfx::NativeTheme::instance()->CloseHandles(); +} + +void WidgetWin::OnFinalMessage(HWND window) { + if (delete_on_destroy_) + delete this; +} + +/////////////////////////////////////////////////////////////////////////////// +// WidgetWin, protected: + +void WidgetWin::TrackMouseEvents(DWORD mouse_tracking_flags) { + // Begin tracking mouse events for this HWND so that we get WM_MOUSELEAVE + // when the user moves the mouse outside this HWND's bounds. + if (active_mouse_tracking_flags_ == 0 || mouse_tracking_flags & TME_CANCEL) { + if (mouse_tracking_flags & TME_CANCEL) { + // We're about to cancel active mouse tracking, so empty out the stored + // state. + active_mouse_tracking_flags_ = 0; + } else { + active_mouse_tracking_flags_ = mouse_tracking_flags; + } + + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(tme); + tme.dwFlags = mouse_tracking_flags; + tme.hwndTrack = GetNativeView(); + tme.dwHoverTime = 0; + TrackMouseEvent(&tme); + } else if (mouse_tracking_flags != active_mouse_tracking_flags_) { + TrackMouseEvents(active_mouse_tracking_flags_ | TME_CANCEL); + TrackMouseEvents(mouse_tracking_flags); + } +} + +bool WidgetWin::ProcessMousePressed(const CPoint& point, + UINT flags, + bool dbl_click, + bool non_client) { + last_mouse_event_was_move_ = false; + // Windows gives screen coordinates for nonclient events, while the RootView + // expects window coordinates; convert if necessary. + gfx::Point converted_point(point); + if (non_client) + View::ConvertPointToView(NULL, root_view_.get(), &converted_point); + MouseEvent mouse_pressed(Event::ET_MOUSE_PRESSED, + converted_point.x(), + converted_point.y(), + (dbl_click ? MouseEvent::EF_IS_DOUBLE_CLICK : 0) | + (non_client ? MouseEvent::EF_IS_NON_CLIENT : 0) | + Event::ConvertWindowsFlags(flags)); + if (root_view_->OnMousePressed(mouse_pressed)) { + is_mouse_down_ = true; + if (!has_capture_) { + SetCapture(); + has_capture_ = true; + current_action_ = FA_FORWARDING; + } + return true; + } + return false; +} + +void WidgetWin::ProcessMouseDragged(const CPoint& point, UINT flags) { + last_mouse_event_was_move_ = false; + MouseEvent mouse_drag(Event::ET_MOUSE_DRAGGED, + point.x, + point.y, + Event::ConvertWindowsFlags(flags)); + root_view_->OnMouseDragged(mouse_drag); +} + +void WidgetWin::ProcessMouseReleased(const CPoint& point, UINT flags) { + last_mouse_event_was_move_ = false; + MouseEvent mouse_up(Event::ET_MOUSE_RELEASED, + point.x, + point.y, + Event::ConvertWindowsFlags(flags)); + // Release the capture first, that way we don't get confused if + // OnMouseReleased blocks. + if (has_capture_ && ReleaseCaptureOnMouseReleased()) { + has_capture_ = false; + current_action_ = FA_NONE; + ReleaseCapture(); + } + is_mouse_down_ = false; + root_view_->OnMouseReleased(mouse_up, false); +} + +void WidgetWin::ProcessMouseMoved(const CPoint &point, UINT flags, + bool is_nonclient) { + // Windows only fires WM_MOUSELEAVE events if the application begins + // "tracking" mouse events for a given HWND during WM_MOUSEMOVE events. + // We need to call |TrackMouseEvents| to listen for WM_MOUSELEAVE. + if (!has_capture_) + TrackMouseEvents(is_nonclient ? TME_NONCLIENT | TME_LEAVE : TME_LEAVE); + if (has_capture_ && is_mouse_down_) { + ProcessMouseDragged(point, flags); + } else { + gfx::Point screen_loc(point); + View::ConvertPointToScreen(root_view_.get(), &screen_loc); + if (last_mouse_event_was_move_ && last_mouse_move_x_ == screen_loc.x() && + last_mouse_move_y_ == screen_loc.y()) { + // Don't generate a mouse event for the same location as the last. + return; + } + last_mouse_move_x_ = screen_loc.x(); + last_mouse_move_y_ = screen_loc.y(); + last_mouse_event_was_move_ = true; + MouseEvent mouse_move(Event::ET_MOUSE_MOVED, + point.x, + point.y, + Event::ConvertWindowsFlags(flags)); + root_view_->OnMouseMoved(mouse_move); + } +} + +void WidgetWin::ProcessMouseExited() { + last_mouse_event_was_move_ = false; + root_view_->ProcessOnMouseExited(); + // Reset our tracking flag so that future mouse movement over this WidgetWin + // results in a new tracking session. + active_mouse_tracking_flags_ = 0; +} + +void WidgetWin::ChangeSize(UINT size_param, const CSize& size) { + CRect rect; + if (use_layered_buffer_) { + GetWindowRect(&rect); + SizeContents(rect); + } else { + GetClientRect(&rect); + } + + // Resizing changes the size of the view hierarchy and thus forces a + // complete relayout. + root_view_->SetBounds(0, 0, rect.Width(), rect.Height()); + root_view_->Layout(); + root_view_->SchedulePaint(); + + if (use_layered_buffer_) + PaintNow(gfx::Rect(rect)); +} + +RootView* WidgetWin::CreateRootView() { + return new RootView(this); +} + +/////////////////////////////////////////////////////////////////////////////// +// WidgetWin, private: + +// static +Window* WidgetWin::GetWindowImpl(HWND hwnd) { + // NOTE: we can't use GetAncestor here as constrained windows are a Window, + // but not a top level window. + HWND parent = hwnd; + while (parent) { + WidgetWin* widget = + reinterpret_cast<WidgetWin*>(win_util::GetWindowUserData(parent)); + if (widget && widget->is_window_) + return static_cast<WindowWin*>(widget); + parent = ::GetParent(parent); + } + return NULL; +} + +void WidgetWin::SizeContents(const CRect& window_rect) { + contents_.reset(new ChromeCanvas(window_rect.Width(), + window_rect.Height(), + false)); +} + +void WidgetWin::PaintLayeredWindow() { + // Painting monkeys with our cliprect, so we need to save it so that the + // call to UpdateLayeredWindow updates the entire window, not just the + // cliprect. + contents_->save(SkCanvas::kClip_SaveFlag); + gfx::Rect dirty_rect = root_view_->GetScheduledPaintRect(); + contents_->ClipRectInt(dirty_rect.x(), dirty_rect.y(), dirty_rect.width(), + dirty_rect.height()); + root_view_->ProcessPaint(contents_.get()); + contents_->restore(); + + UpdateWindowFromContents(contents_->getTopPlatformDevice().getBitmapDC()); +} + +void WidgetWin::UpdateWindowFromContents(HDC dib_dc) { + DCHECK(use_layered_buffer_); + if (can_update_layered_window_) { + CRect wr; + GetWindowRect(&wr); + CSize size(wr.right - wr.left, wr.bottom - wr.top); + CPoint zero_origin(0, 0); + CPoint window_position = wr.TopLeft(); + + BLENDFUNCTION blend = {AC_SRC_OVER, 0, layered_alpha_, AC_SRC_ALPHA}; + ::UpdateLayeredWindow( + hwnd_, NULL, &window_position, &size, dib_dc, &zero_origin, + RGB(0xFF, 0xFF, 0xFF), &blend, ULW_ALPHA); + } +} + +std::wstring WidgetWin::GetWindowClassName() { + ClassInfo class_info(initial_class_style()); + std::wstring name; + if (Singleton<ClassRegistrar>()->RetrieveClassName(class_info, &name)) + return name; + + // No class found, need to register one. + WNDCLASSEX class_ex; + class_ex.cbSize = sizeof(WNDCLASSEX); + class_ex.style = class_info.style; + class_ex.lpfnWndProc = &WidgetWin::WndProc; + class_ex.cbClsExtra = 0; + class_ex.cbWndExtra = 0; + class_ex.hInstance = NULL; + class_ex.hIcon = LoadIcon(GetModuleHandle(L"chrome.dll"), + MAKEINTRESOURCE(IDR_MAINFRAME)); + class_ex.hCursor = LoadCursor(NULL, IDC_ARROW); + class_ex.hbrBackground = reinterpret_cast<HBRUSH>(class_info.background + 1); + class_ex.lpszMenuName = NULL; + class_ex.lpszClassName = name.c_str(); + class_ex.hIconSm = class_ex.hIcon; + ATOM atom = RegisterClassEx(&class_ex); + DCHECK(atom); + + Singleton<ClassRegistrar>()->RegisterClass(class_info, name, atom); + + return name; +} + +// Get the source HWND of the specified message. Depending on the message, the +// source HWND is encoded in either the WPARAM or the LPARAM value. +HWND GetControlHWNDForMessage(UINT message, WPARAM w_param, LPARAM l_param) { + // Each of the following messages can be sent by a child HWND and must be + // forwarded to its associated NativeControlWin for handling. + switch (message) { + case WM_NOTIFY: + return reinterpret_cast<NMHDR*>(l_param)->hwndFrom; + case WM_COMMAND: + return reinterpret_cast<HWND>(l_param); + case WM_CONTEXTMENU: + return reinterpret_cast<HWND>(w_param); + case WM_CTLCOLORBTN: + case WM_CTLCOLORSTATIC: + return reinterpret_cast<HWND>(l_param); + } + return NULL; +} + +// Some messages may be sent to us by a child HWND managed by +// NativeControlWin. If this is the case, this function will forward those +// messages on to the object associated with the source HWND and return true, +// in which case the window procedure must not do any further processing of +// the message. If there is no associated NativeControlWin, the return value +// will be false and the WndProc can continue processing the message normally. +// |l_result| contains the result of the message processing by the control and +// must be returned by the WndProc if the return value is true. +bool ProcessNativeControlMessage(UINT message, + WPARAM w_param, + LPARAM l_param, + LRESULT* l_result) { + *l_result = 0; + + HWND control_hwnd = GetControlHWNDForMessage(message, w_param, l_param); + if (IsWindow(control_hwnd)) { + NativeControlWin* wrapper = GetNativeControlWinForHWND(control_hwnd); + if (wrapper) + return wrapper->ProcessMessage(message, w_param, l_param, l_result); + } + + return false; +} + +// static +LRESULT CALLBACK WidgetWin::WndProc(HWND window, UINT message, + WPARAM w_param, LPARAM l_param) { + if (message == WM_NCCREATE) { + CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(l_param); + WidgetWin* widget = reinterpret_cast<WidgetWin*>(cs->lpCreateParams); + DCHECK(widget); + win_util::SetWindowUserData(window, widget); + widget->hwnd_ = window; + return TRUE; + } + + WidgetWin* widget = reinterpret_cast<WidgetWin*>( + win_util::GetWindowUserData(window)); + if (!widget) + return 0; + + LRESULT result = 0; + + // First allow messages sent by child controls to be processed directly by + // their associated views. If such a view is present, it will handle the + // message *instead of* this WidgetWin. + if (ProcessNativeControlMessage(message, w_param, l_param, &result)) + return result; + + // Otherwise we handle everything else. + if (!widget->ProcessWindowMessage(window, message, w_param, l_param, result)) + result = DefWindowProc(window, message, w_param, l_param); + if (message == WM_NCDESTROY) { + TRACK_HWND_DESTRUCTION(window); + widget->hwnd_ = NULL; + widget->OnFinalMessage(window); + } + return result; +} + +} // namespace views diff --git a/views/widget/widget_win.h b/views/widget/widget_win.h new file mode 100644 index 0000000..09efa21 --- /dev/null +++ b/views/widget/widget_win.h @@ -0,0 +1,636 @@ +// Copyright (c) 2006-2008 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. + +#ifndef VIEWS_WIDGET_WIDGET_WIN_H_ +#define VIEWS_WIDGET_WIDGET_WIN_H_ + +#include <atlbase.h> +#include <atlcrack.h> + +#include "base/message_loop.h" +#include "base/system_monitor.h" +#include "views/focus/focus_manager.h" +#include "views/layout_manager.h" +#include "views/widget/widget.h" + +class ChromeCanvas; + +namespace gfx { +class Rect; +} + +namespace views { + +class RootView; +class TooltipManager; +class Window; + +bool SetRootViewForHWND(HWND hwnd, RootView* root_view); +RootView* GetRootViewForHWND(HWND hwnd); + +// A Windows message reflected from other windows. This message is sent +// with the following arguments: +// hWnd - Target window +// uMsg - kReflectedMessage +// wParam - Should be 0 +// lParam - Pointer to MSG struct containing the original message. +static const int kReflectedMessage = WM_APP + 3; + +// These two messages aren't defined in winuser.h, but they are sent to windows +// with captions. They appear to paint the window caption and frame. +// Unfortunately if you override the standard non-client rendering as we do +// with CustomFrameWindow, sometimes Windows (not deterministically +// reproducibly but definitely frequently) will send these messages to the +// window and paint the standard caption/title over the top of the custom one. +// So we need to handle these messages in CustomFrameWindow to prevent this +// from happening. +static const int WM_NCUAHDRAWCAPTION = 0xAE; +static const int WM_NCUAHDRAWFRAME = 0xAF; + +/////////////////////////////////////////////////////////////////////////////// +// +// WidgetWin +// A Widget for a views hierarchy used to represent anything that can be +// contained within an HWND, e.g. a control, a window, etc. Specializations +// suitable for specific tasks, e.g. top level window, are derived from this. +// +// This Widget contains a RootView which owns the hierarchy of views within it. +// As long as views are part of this tree, they will be deleted automatically +// when the RootView is destroyed. If you remove a view from the tree, you are +// then responsible for cleaning up after it. +// +/////////////////////////////////////////////////////////////////////////////// +class WidgetWin : public Widget, + public MessageLoopForUI::Observer, + public FocusTraversable, + public AcceleratorTarget { + public: + WidgetWin(); + virtual ~WidgetWin(); + + // Initialize the Widget with a parent and an initial desired size. + // |contents_view| is the view that will be the single child of RootView + // within this Widget. As contents_view is inserted into RootView's tree, + // RootView assumes ownership of this view and cleaning it up. If you remove + // this view, you are responsible for its destruction. If this value is NULL, + // the caller is responsible for populating the RootView, and sizing its + // contents as the window is sized. + // If |has_own_focus_manager| is true, the focus traversal stay confined to + // the window. + void Init(HWND parent, + const gfx::Rect& bounds, + bool has_own_focus_manager); + + // Sets the specified view as the contents of this Widget. There can only + // be one contnets view child of this Widget's RootView. This view is sized to + // fit the entire size of the RootView. The RootView takes ownership of this + // View, unless it is set as not being parent-owned. + virtual void SetContentsView(View* view); + + // Sets the window styles. This is ONLY used when the window is created. + // In other words, if you invoke this after invoking Init, nothing happens. + void set_window_style(DWORD style) { window_style_ = style; } + DWORD window_style() const { return window_style_; } + + // Sets the extended window styles. See comment about |set_window_style|. + void set_window_ex_style(DWORD style) { window_ex_style_ = style; } + DWORD window_ex_style() const { return window_ex_style_; }; + + // Sets the class style to use. The default is CS_DBLCLKS. + void set_initial_class_style(UINT class_style) { + // We dynamically generate the class name, so don't register it globally! + DCHECK((class_style & CS_GLOBALCLASS) == 0); + class_style_ = class_style; + } + UINT initial_class_style() { return class_style_; } + + void set_delete_on_destroy(bool delete_on_destroy) { + delete_on_destroy_ = delete_on_destroy; + } + + // Sets the initial opacity of a layered window, or updates the window's + // opacity if it is on the screen. + void SetLayeredAlpha(BYTE layered_alpha); + + // See description of use_layered_buffer_ for details. + void SetUseLayeredBuffer(bool use_layered_buffer); + + // Disable Layered Window updates by setting to false. + void set_can_update_layered_window(bool can_update_layered_window) { + can_update_layered_window_ = can_update_layered_window; + } + + // Returns the RootView associated with the specified HWND (if any). + static RootView* FindRootView(HWND hwnd); + + // Closes the window asynchronously by scheduling a task for it. The window + // is destroyed as a result. + // This invokes Hide to hide the window, and schedules a task that + // invokes CloseNow. + virtual void Close(); + + // Hides the window. This does NOT delete the window, it just hides it. + virtual void Hide(); + + // Shows the window without changing size/position/activation state. + virtual void Show(); + + // Closes the window synchronously. Note that this should not be called from + // an ATL message callback as it deletes the WidgetWin and ATL will + // dereference it after the callback is processed. + void CloseNow(); + + // All classes registered by WidgetWin start with this name. + static const wchar_t* const kBaseClassName; + + BEGIN_MSG_MAP_EX(0) + // Range handlers must go first! + MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseRange) + MESSAGE_RANGE_HANDLER_EX(WM_NCMOUSEMOVE, WM_NCMOUSEMOVE, OnMouseRange) + + // Reflected message handler + MESSAGE_HANDLER_EX(kReflectedMessage, OnReflectedMessage) + + // CustomFrameWindow hacks + MESSAGE_HANDLER_EX(WM_NCUAHDRAWCAPTION, OnNCUAHDrawCaption) + MESSAGE_HANDLER_EX(WM_NCUAHDRAWFRAME, OnNCUAHDrawFrame) + + // Vista and newer + MESSAGE_HANDLER_EX(WM_DWMCOMPOSITIONCHANGED, OnDwmCompositionChanged) + + // Non-atlcrack.h handlers + MESSAGE_HANDLER_EX(WM_GETOBJECT, OnGetObject) + MESSAGE_HANDLER_EX(WM_NCMOUSELEAVE, OnNCMouseLeave) + MESSAGE_HANDLER_EX(WM_MOUSELEAVE, OnMouseLeave) + MESSAGE_HANDLER_EX(WM_MOUSEWHEEL, OnMouseWheel) + + // This list is in _ALPHABETICAL_ order! OR I WILL HURT YOU. + MSG_WM_ACTIVATE(OnActivate) + MSG_WM_ACTIVATEAPP(OnActivateApp) + MSG_WM_APPCOMMAND(OnAppCommand) + MSG_WM_CANCELMODE(OnCancelMode) + MSG_WM_CAPTURECHANGED(OnCaptureChanged) + MSG_WM_CLOSE(OnClose) + MSG_WM_COMMAND(OnCommand) + MSG_WM_CREATE(OnCreate) + MSG_WM_DESTROY(OnDestroy) + MSG_WM_ERASEBKGND(OnEraseBkgnd) + MSG_WM_ENDSESSION(OnEndSession) + MSG_WM_ENTERSIZEMOVE(OnEnterSizeMove) + MSG_WM_EXITMENULOOP(OnExitMenuLoop) + MSG_WM_GETMINMAXINFO(OnGetMinMaxInfo) + MSG_WM_HSCROLL(OnHScroll) + MSG_WM_INITMENU(OnInitMenu) + MSG_WM_INITMENUPOPUP(OnInitMenuPopup) + MSG_WM_KEYDOWN(OnKeyDown) + MSG_WM_KEYUP(OnKeyUp) + MSG_WM_SYSKEYDOWN(OnKeyDown) + MSG_WM_SYSKEYUP(OnKeyUp) + MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk) + MSG_WM_LBUTTONDOWN(OnLButtonDown) + MSG_WM_LBUTTONUP(OnLButtonUp) + MSG_WM_MBUTTONDOWN(OnMButtonDown) + MSG_WM_MBUTTONUP(OnMButtonUp) + MSG_WM_MBUTTONDBLCLK(OnMButtonDblClk) + MSG_WM_MOUSEACTIVATE(OnMouseActivate) + MSG_WM_MOUSEMOVE(OnMouseMove) + MSG_WM_MOVE(OnMove) + MSG_WM_MOVING(OnMoving) + MSG_WM_NCACTIVATE(OnNCActivate) + MSG_WM_NCCALCSIZE(OnNCCalcSize) + MSG_WM_NCHITTEST(OnNCHitTest) + MSG_WM_NCMOUSEMOVE(OnNCMouseMove) + MSG_WM_NCLBUTTONDBLCLK(OnNCLButtonDblClk) + MSG_WM_NCLBUTTONDOWN(OnNCLButtonDown) + MSG_WM_NCLBUTTONUP(OnNCLButtonUp) + MSG_WM_NCMBUTTONDBLCLK(OnNCMButtonDblClk) + MSG_WM_NCMBUTTONDOWN(OnNCMButtonDown) + MSG_WM_NCMBUTTONUP(OnNCMButtonUp) + MSG_WM_NCPAINT(OnNCPaint) + MSG_WM_NCRBUTTONDBLCLK(OnNCRButtonDblClk) + MSG_WM_NCRBUTTONDOWN(OnNCRButtonDown) + MSG_WM_NCRBUTTONUP(OnNCRButtonUp) + MSG_WM_NOTIFY(OnNotify) + MSG_WM_PAINT(OnPaint) + MSG_WM_POWERBROADCAST(OnPowerBroadcast) + MSG_WM_RBUTTONDBLCLK(OnRButtonDblClk) + MSG_WM_RBUTTONDOWN(OnRButtonDown) + MSG_WM_RBUTTONUP(OnRButtonUp) + MSG_WM_SETCURSOR(OnSetCursor) + MSG_WM_SETFOCUS(OnSetFocus) + MSG_WM_SETICON(OnSetIcon) + MSG_WM_SETTEXT(OnSetText) + MSG_WM_SETTINGCHANGE(OnSettingChange) + MSG_WM_SIZE(OnSize) + MSG_WM_SYSCOMMAND(OnSysCommand) + MSG_WM_THEMECHANGED(OnThemeChanged) + MSG_WM_VSCROLL(OnVScroll) + MSG_WM_WINDOWPOSCHANGING(OnWindowPosChanging) + MSG_WM_WINDOWPOSCHANGED(OnWindowPosChanged) + END_MSG_MAP() + + // Overridden from Widget: + virtual void GetBounds(gfx::Rect* out, bool including_frame) const; + virtual gfx::NativeView GetNativeView() const; + virtual void PaintNow(const gfx::Rect& update_rect); + virtual RootView* GetRootView(); + virtual bool IsVisible() const; + virtual bool IsActive() const; + virtual TooltipManager* GetTooltipManager(); + virtual Window* GetWindow(); + virtual const Window* GetWindow() const; + + // Overridden from MessageLoop::Observer: + void WillProcessMessage(const MSG& msg); + virtual void DidProcessMessage(const MSG& msg); + + // Overridden from FocusTraversable: + virtual View* FindNextFocusableView(View* starting_view, + bool reverse, + Direction direction, + bool dont_loop, + FocusTraversable** focus_traversable, + View** focus_traversable_view); + virtual FocusTraversable* GetFocusTraversableParent(); + virtual View* GetFocusTraversableParentView(); + + // Overridden from AcceleratorTarget: + virtual bool AcceleratorPressed(const Accelerator& accelerator) { + return false; + } + + void SetFocusTraversableParent(FocusTraversable* parent); + void SetFocusTraversableParentView(View* parent_view); + + virtual bool GetAccelerator(int cmd_id, Accelerator* accelerator) { + return false; + } + + BOOL IsWindow() const { + return ::IsWindow(GetNativeView()); + } + + BOOL ShowWindow(int command) { + DCHECK(::IsWindow(GetNativeView())); + return ::ShowWindow(GetNativeView(), command); + } + + HWND SetCapture() { + DCHECK(::IsWindow(GetNativeView())); + return ::SetCapture(GetNativeView()); + } + + HWND GetParent() const { + return ::GetParent(GetNativeView()); + } + + LONG GetWindowLong(int index) { + DCHECK(::IsWindow(GetNativeView())); + return ::GetWindowLong(GetNativeView(), index); + } + + BOOL GetWindowRect(RECT* rect) const { + return ::GetWindowRect(GetNativeView(), rect); + } + + LONG SetWindowLong(int index, LONG new_long) { + DCHECK(::IsWindow(GetNativeView())); + return ::SetWindowLong(GetNativeView(), index, new_long); + } + + BOOL SetWindowPos(HWND hwnd_after, int x, int y, int cx, int cy, UINT flags) { + DCHECK(::IsWindow(GetNativeView())); + return ::SetWindowPos(GetNativeView(), hwnd_after, x, y, cx, cy, flags); + } + + BOOL IsZoomed() const { + DCHECK(::IsWindow(GetNativeView())); + return ::IsZoomed(GetNativeView()); + } + + BOOL MoveWindow(int x, int y, int width, int height) { + return MoveWindow(x, y, width, height, TRUE); + } + + BOOL MoveWindow(int x, int y, int width, int height, BOOL repaint) { + DCHECK(::IsWindow(GetNativeView())); + return ::MoveWindow(GetNativeView(), x, y, width, height, repaint); + } + + int SetWindowRgn(HRGN region, BOOL redraw) { + DCHECK(::IsWindow(GetNativeView())); + return ::SetWindowRgn(GetNativeView(), region, redraw); + } + + BOOL GetClientRect(RECT* rect) const { + DCHECK(::IsWindow(GetNativeView())); + return ::GetClientRect(GetNativeView(), rect); + } + + protected: + + // Call close instead of this to Destroy the window. + BOOL DestroyWindow() { + DCHECK(::IsWindow(GetNativeView())); + return ::DestroyWindow(GetNativeView()); + } + + // Message Handlers + // These are all virtual so that specialized Widgets can modify or augment + // processing. + // This list is in _ALPHABETICAL_ order! + // Note: in the base class these functions must do nothing but convert point + // coordinates to client coordinates (if necessary) and forward the + // handling to the appropriate Process* function. This is so that + // subclasses can easily override these methods to do different things + // and have a convenient function to call to get the default behavior. + virtual void OnActivate(UINT action, BOOL minimized, HWND window) { + SetMsgHandled(FALSE); + } + virtual void OnActivateApp(BOOL active, DWORD thread_id) { + SetMsgHandled(FALSE); + } + virtual LRESULT OnAppCommand(HWND window, short app_command, WORD device, + int keystate) { + SetMsgHandled(FALSE); + return 0; + } + virtual void OnCancelMode() {} + virtual void OnCaptureChanged(HWND hwnd); + virtual void OnClose(); + virtual void OnCommand(UINT notification_code, int command_id, HWND window) { + SetMsgHandled(FALSE); + } + virtual LRESULT OnCreate(LPCREATESTRUCT create_struct) { return 0; } + // WARNING: If you override this be sure and invoke super, otherwise we'll + // leak a few things. + virtual void OnDestroy(); + virtual LRESULT OnDwmCompositionChanged(UINT msg, + WPARAM w_param, + LPARAM l_param) { + SetMsgHandled(FALSE); + return 0; + } + virtual void OnEndSession(BOOL ending, UINT logoff) { SetMsgHandled(FALSE); } + virtual void OnEnterSizeMove() { SetMsgHandled(FALSE); } + virtual void OnExitMenuLoop(BOOL is_track_popup_menu) { + SetMsgHandled(FALSE); + } + virtual LRESULT OnEraseBkgnd(HDC dc); + virtual LRESULT OnGetObject(UINT uMsg, WPARAM w_param, LPARAM l_param); + virtual void OnGetMinMaxInfo(MINMAXINFO* minmax_info) { + SetMsgHandled(FALSE); + } + virtual void OnHScroll(int scroll_type, short position, HWND scrollbar) { + SetMsgHandled(FALSE); + } + virtual void OnInitMenu(HMENU menu) { SetMsgHandled(FALSE); } + virtual void OnInitMenuPopup(HMENU menu, UINT position, BOOL is_system_menu) { + SetMsgHandled(FALSE); + } + virtual void OnKeyDown(TCHAR c, UINT rep_cnt, UINT flags); + virtual void OnKeyUp(TCHAR c, UINT rep_cnt, UINT flags); + virtual void OnLButtonDblClk(UINT flags, const CPoint& point); + virtual void OnLButtonDown(UINT flags, const CPoint& point); + virtual void OnLButtonUp(UINT flags, const CPoint& point); + virtual void OnMButtonDblClk(UINT flags, const CPoint& point); + virtual void OnMButtonDown(UINT flags, const CPoint& point); + virtual void OnMButtonUp(UINT flags, const CPoint& point); + virtual LRESULT OnMouseActivate(HWND window, UINT hittest_code, UINT message); + virtual void OnMouseMove(UINT flags, const CPoint& point); + virtual LRESULT OnMouseLeave(UINT message, WPARAM w_param, LPARAM l_param); + virtual LRESULT OnMouseWheel(UINT message, WPARAM w_param, LPARAM l_param); + virtual void OnMove(const CPoint& point) { SetMsgHandled(FALSE); } + virtual void OnMoving(UINT param, const LPRECT new_bounds) { } + virtual LRESULT OnMouseRange(UINT msg, WPARAM w_param, LPARAM l_param); + virtual LRESULT OnNCActivate(BOOL active) { SetMsgHandled(FALSE); return 0; } + virtual LRESULT OnNCCalcSize(BOOL w_param, LPARAM l_param) { + SetMsgHandled(FALSE); + return 0; + } + virtual LRESULT OnNCHitTest(const CPoint& pt) { + SetMsgHandled(FALSE); + return 0; + } + virtual void OnNCLButtonDblClk(UINT flags, const CPoint& point); + virtual void OnNCLButtonDown(UINT flags, const CPoint& point); + virtual void OnNCLButtonUp(UINT flags, const CPoint& point); + virtual void OnNCMButtonDblClk(UINT flags, const CPoint& point); + virtual void OnNCMButtonDown(UINT flags, const CPoint& point); + virtual void OnNCMButtonUp(UINT flags, const CPoint& point); + virtual LRESULT OnNCMouseLeave(UINT uMsg, WPARAM w_param, LPARAM l_param); + virtual LRESULT OnNCMouseMove(UINT flags, const CPoint& point); + virtual void OnNCPaint(HRGN rgn) { SetMsgHandled(FALSE); } + virtual void OnNCRButtonDblClk(UINT flags, const CPoint& point); + virtual void OnNCRButtonDown(UINT flags, const CPoint& point); + virtual void OnNCRButtonUp(UINT flags, const CPoint& point); + virtual LRESULT OnNCUAHDrawCaption(UINT msg, + WPARAM w_param, + LPARAM l_param) { + SetMsgHandled(FALSE); + return 0; + } + virtual LRESULT OnNCUAHDrawFrame(UINT msg, WPARAM w_param, LPARAM l_param) { + SetMsgHandled(FALSE); + return 0; + } + virtual LRESULT OnNotify(int w_param, NMHDR* l_param); + virtual void OnPaint(HDC dc); + virtual LRESULT OnPowerBroadcast(DWORD power_event, DWORD data) { + base::SystemMonitor* monitor = base::SystemMonitor::Get(); + if (monitor) + monitor->ProcessWmPowerBroadcastMessage(power_event); + SetMsgHandled(FALSE); + return 0; + } + virtual void OnRButtonDblClk(UINT flags, const CPoint& point); + virtual void OnRButtonDown(UINT flags, const CPoint& point); + virtual void OnRButtonUp(UINT flags, const CPoint& point); + virtual LRESULT OnReflectedMessage(UINT msg, WPARAM w_param, LPARAM l_param) { + SetMsgHandled(FALSE); + return 0; + } + virtual LRESULT OnSetCursor(HWND window, UINT hittest_code, UINT message) { + SetMsgHandled(FALSE); + return 0; + } + virtual void OnSetFocus(HWND focused_window) { + SetMsgHandled(FALSE); + } + virtual LRESULT OnSetIcon(UINT size_type, HICON new_icon) { + SetMsgHandled(FALSE); + return 0; + } + virtual LRESULT OnSetText(const wchar_t* text) { + SetMsgHandled(FALSE); + return 0; + } + virtual void OnSettingChange(UINT flags, const wchar_t* section) { + SetMsgHandled(FALSE); + } + virtual void OnSize(UINT param, const CSize& size); + virtual void OnSysCommand(UINT notification_code, CPoint click) { } + virtual void OnThemeChanged(); + virtual void OnVScroll(int scroll_type, short position, HWND scrollbar) { + SetMsgHandled(FALSE); + } + virtual void OnWindowPosChanging(WINDOWPOS* window_pos) { + SetMsgHandled(FALSE); + } + virtual void OnWindowPosChanged(WINDOWPOS* window_pos) { + SetMsgHandled(FALSE); + } + + // deletes this window as it is destroyed, override to provide different + // behavior. + virtual void OnFinalMessage(HWND window); + + // Start tracking all mouse events so that this window gets sent mouse leave + // messages too. |is_nonclient| is true when we should track WM_NCMOUSELEAVE + // messages instead of WM_MOUSELEAVE ones. + void TrackMouseEvents(DWORD mouse_tracking_flags); + + // Actually handle mouse events. These functions are called by subclasses who + // override the message handlers above to do the actual real work of handling + // the event in the View system. + bool ProcessMousePressed(const CPoint& point, + UINT flags, + bool dbl_click, + bool non_client); + void ProcessMouseDragged(const CPoint& point, UINT flags); + void ProcessMouseReleased(const CPoint& point, UINT flags); + void ProcessMouseMoved(const CPoint& point, UINT flags, bool is_nonclient); + void ProcessMouseExited(); + + // Handles re-laying out content in response to a window size change. + virtual void ChangeSize(UINT size_param, const CSize& size); + + // Returns whether capture should be released on mouse release. The default + // is true. + virtual bool ReleaseCaptureOnMouseReleased() { return true; } + + enum FrameAction {FA_NONE = 0, FA_RESIZING, FA_MOVING, FA_FORWARDING}; + + virtual RootView* CreateRootView(); + + // Returns true if this WidgetWin is opaque. + bool opaque() const { return opaque_; } + + // The root of the View hierarchy attached to this window. + scoped_ptr<RootView> root_view_; + + // Current frame ui action + FrameAction current_action_; + + // Whether or not we have capture the mouse. + bool has_capture_; + + // If true, the mouse is currently down. + bool is_mouse_down_; + + scoped_ptr<TooltipManager> tooltip_manager_; + + // Are a subclass of WindowWin? + bool is_window_; + + private: + // Implementation of GetWindow. Ascends the parents of |hwnd| returning the + // first ancestor that is a Window. + static Window* GetWindowImpl(HWND hwnd); + + // Resize the bitmap used to contain the contents of the layered window. This + // recreates the entire bitmap. + void SizeContents(const CRect& window_rect); + + // Paint into a DIB and then update the layered window with its contents. + void PaintLayeredWindow(); + + // In layered mode, update the layered window. |dib_dc| represents a handle + // to a device context that contains the contents of the window. + void UpdateWindowFromContents(HDC dib_dc); + + // Invoked from WM_DESTROY. Does appropriate cleanup and invokes OnDestroy + // so that subclasses can do any cleanup they need to. + void OnDestroyImpl(); + + // The windows procedure used by all WidgetWins. + static LRESULT CALLBACK WndProc(HWND window, + UINT message, + WPARAM w_param, + LPARAM l_param); + + // Gets the window class name to use when creating the corresponding HWND. + // If necessary, this registers the window class. + std::wstring GetWindowClassName(); + + // The following factory is used for calls to close the WidgetWin + // instance. + ScopedRunnableMethodFactory<WidgetWin> close_widget_factory_; + + // The flags currently being used with TrackMouseEvent to track mouse + // messages. 0 if there is no active tracking. The value of this member is + // used when tracking is canceled. + DWORD active_mouse_tracking_flags_; + + bool opaque_; + + // Window Styles used when creating the window. + DWORD window_style_; + + // Window Extended Styles used when creating the window. + DWORD window_ex_style_; + + // Style of the class to use. + UINT class_style_; + + // Should we keep an offscreen buffer? This is initially true and if the + // window has WS_EX_LAYERED then it remains true. You can set this to false + // at any time to ditch the buffer, and similarly set back to true to force + // creation of the buffer. + // + // NOTE: this is intended to be used with a layered window (a window with an + // extended window style of WS_EX_LAYERED). If you are using a layered window + // and NOT changing the layered alpha or anything else, then leave this value + // alone. OTOH if you are invoking SetLayeredWindowAttributes then you'll + // must likely want to set this to false, or after changing the alpha toggle + // the extended style bit to false than back to true. See MSDN for more + // details. + bool use_layered_buffer_; + + // The default alpha to be applied to the layered window. + BYTE layered_alpha_; + + // A canvas that contains the window contents in the case of a layered + // window. + scoped_ptr<ChromeCanvas> contents_; + + // Whether or not the window should delete itself when it is destroyed. + // Set this to false via its setter for stack allocated instances. + bool delete_on_destroy_; + + // True if we are allowed to update the layered window from the DIB backing + // store if necessary. + bool can_update_layered_window_; + + // The following are used to detect duplicate mouse move events and not + // deliver them. Displaying a window may result in the system generating + // duplicate move events even though the mouse hasn't moved. + + // If true, the last event was a mouse move event. + bool last_mouse_event_was_move_; + + // Coordinates of the last mouse move event, in screen coordinates. + int last_mouse_move_x_; + int last_mouse_move_y_; + + // Instance of accessibility information and handling for MSAA root + CComPtr<IAccessible> accessibility_root_; + + // Our hwnd. + HWND hwnd_; +}; + +} // namespace views + +#endif // #ifndef VIEWS_WIDGET_WIDGET_WIN_H_ |