diff options
author | wez@chromium.org <wez@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-21 18:17:35 +0000 |
---|---|---|
committer | wez@chromium.org <wez@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-21 18:17:35 +0000 |
commit | 84ad47e975cb77a12881dc0c72053a22993950ca (patch) | |
tree | 343752188cf8571abc5533d9eb37efb1eab03ebd | |
parent | 586cf35708db57b9013a12adc8d8513163e558de (diff) | |
download | chromium_src-84ad47e975cb77a12881dc0c72053a22993950ca.zip chromium_src-84ad47e975cb77a12881dc0c72053a22993950ca.tar.gz chromium_src-84ad47e975cb77a12881dc0c72053a22993950ca.tar.bz2 |
Revert "views: Move widget/ directory to ui/views." properly.
Review URL: http://codereview.chromium.org/8562003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@110960 0039d316-1c4b-4281-b951-d872f2087c98
50 files changed, 14318 insertions, 0 deletions
diff --git a/views/widget/aero_tooltip_manager.cc b/views/widget/aero_tooltip_manager.cc new file mode 100644 index 0000000..0a93687 --- /dev/null +++ b/views/widget/aero_tooltip_manager.cc @@ -0,0 +1,111 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/aero_tooltip_manager.h" + +#include <windows.h> +#include <commctrl.h> +#include <shlobj.h> + +#include "base/bind.h" +#include "base/message_loop.h" +#include "ui/base/l10n/l10n_util_win.h" +#include "ui/base/win/hwnd_util.h" +#include "ui/gfx/point.h" + +namespace views { + +/////////////////////////////////////////////////////////////////////////////// +// AeroTooltipManager, public: + +AeroTooltipManager::AeroTooltipManager(Widget* widget) + : TooltipManagerWin(widget), + initial_delay_(0) { +} + +AeroTooltipManager::~AeroTooltipManager() { + if (initial_timer_) + initial_timer_->Disown(); +} + +void AeroTooltipManager::OnMouse(UINT u_msg, WPARAM w_param, LPARAM l_param) { + if (u_msg == WM_MOUSELEAVE) { + last_mouse_pos_.SetPoint(-1, -1); + UpdateTooltip(); + return; + } + + if (initial_timer_) + initial_timer_->Disown(); + + if (u_msg == WM_MOUSEMOVE || u_msg == WM_NCMOUSEMOVE) { + gfx::Point mouse_pos(l_param); + if (u_msg == WM_NCMOUSEMOVE) { + // NC message coordinates are in screen coordinates. + POINT temp = mouse_pos.ToPOINT(); + ::MapWindowPoints(HWND_DESKTOP, GetParent(), &temp, 1); + mouse_pos.SetPoint(temp.x, temp.y); + } + if (last_mouse_pos_ != mouse_pos) { + last_mouse_pos_ = mouse_pos; + HideKeyboardTooltip(); + UpdateTooltip(mouse_pos); + } + + // 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_timer_->Start(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; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// AeroTooltipManager, private: + +void AeroTooltipManager::OnTimer() { + initial_timer_ = NULL; + + POINT pt = last_mouse_pos_.ToPOINT(); + ::ClientToScreen(GetParent(), &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) + : manager_(manager) { +} + +void AeroTooltipManager::InitialTimer::Start(int time) { + MessageLoop::current()->PostDelayedTask( + FROM_HERE, base::Bind(&InitialTimer::Execute, this), 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..9f322fd --- /dev/null +++ b/views/widget/aero_tooltip_manager.h @@ -0,0 +1,60 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_AERO_TOOLTIP_MANAGER_H_ +#define VIEWS_WIDGET_AERO_TOOLTIP_MANAGER_H_ +#pragma once + +#include "base/memory/ref_counted.h" +#include "views/widget/tooltip_manager_win.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 TooltipManagerWin { + public: + explicit AeroTooltipManager(Widget* widget); + virtual ~AeroTooltipManager(); + + virtual void OnMouse(UINT u_msg, WPARAM w_param, LPARAM l_param); + + private: + void OnTimer(); + + class InitialTimer : public base::RefCounted<InitialTimer> { + public: + explicit InitialTimer(AeroTooltipManager* manager); + void Start(int time); + void Disown(); + void Execute(); + + private: + friend class base::RefCounted<InitialTimer>; + + ~InitialTimer() {} + + 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/child_window_message_processor.cc b/views/widget/child_window_message_processor.cc new file mode 100644 index 0000000..e1cfa31 --- /dev/null +++ b/views/widget/child_window_message_processor.cc @@ -0,0 +1,28 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/child_window_message_processor.h" + +#include "base/logging.h" +#include "ui/base/view_prop.h" + +namespace views { + +static const char* const kChildWindowKey = "__CHILD_WINDOW_MESSAGE_PROCESSOR__"; + +// static +ui::ViewProp* ChildWindowMessageProcessor::Register( + HWND hwnd, + ChildWindowMessageProcessor* processor) { + DCHECK(processor); + return new ui::ViewProp(hwnd, kChildWindowKey, processor); +} + +// static +ChildWindowMessageProcessor* ChildWindowMessageProcessor::Get(HWND hwnd) { + return reinterpret_cast<ChildWindowMessageProcessor*>( + ui::ViewProp::GetValue(hwnd, kChildWindowKey)); +} + +} // namespace diff --git a/views/widget/child_window_message_processor.h b/views/widget/child_window_message_processor.h new file mode 100644 index 0000000..a11b187 --- /dev/null +++ b/views/widget/child_window_message_processor.h @@ -0,0 +1,48 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_CHILD_WINDOW_MESSAGE_PROCESSOR_H_ +#define VIEWS_WIDGET_CHILD_WINDOW_MESSAGE_PROCESSOR_H_ +#pragma once + +#include <windows.h> + +namespace ui { +class ViewProp; +} + +namespace views { + +// Windows sends a handful of messages to the parent window rather than the +// window itself. For example, selection changes of a rich edit (EN_SELCHANGE) +// are sent to the parent, not the window. Typically such message are best +// dealt with by the window rather than the parent. NativeWidgetWin allows for +// registering a ChildWindowMessageProcessor to handle such messages. +class ChildWindowMessageProcessor { + public: + // Registers |processor| for |hwnd|. The caller takes ownership of the + // returned object. + static ui::ViewProp* Register(HWND hwnd, + ChildWindowMessageProcessor* processor); + + // Returns the ChildWindowMessageProcessor for |hwnd|, NULL if there isn't + // one. + static ChildWindowMessageProcessor* Get(HWND hwnd); + + // Invoked for any messages that are sent to the parent and originated from + // the HWND this ChildWindowMessageProcessor was registered for. Returns true + // if the message was handled with a valid result in |result|. Returns false + // if the message was not handled. + virtual bool ProcessMessage(UINT message, + WPARAM w_param, + LPARAM l_param, + LRESULT* result) = 0; + + protected: + virtual ~ChildWindowMessageProcessor() {} +}; + +} // namespace views + +#endif // VIEWS_WIDGET_CHILD_WINDOW_MESSAGE_PROCESSOR_H_ diff --git a/views/widget/default_theme_provider.cc b/views/widget/default_theme_provider.cc new file mode 100644 index 0000000..6fdbb86 --- /dev/null +++ b/views/widget/default_theme_provider.cc @@ -0,0 +1,50 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/default_theme_provider.h" + +#include "ui/base/resource/resource_bundle.h" + +#if defined(OS_WIN) && !defined(USE_AURA) +#include "views/widget/native_widget_win.h" +#endif + +namespace views { + +DefaultThemeProvider::DefaultThemeProvider() {} + +DefaultThemeProvider::~DefaultThemeProvider() {} + +void DefaultThemeProvider::Init(Profile* profile) {} + +SkBitmap* DefaultThemeProvider::GetBitmapNamed(int id) const { + return ResourceBundle::GetSharedInstance().GetBitmapNamed(id); +} + +SkColor DefaultThemeProvider::GetColor(int id) const { + // Return debugging-blue. + return 0xff0000ff; +} + +bool DefaultThemeProvider::GetDisplayProperty(int id, int* result) const { + return false; +} + +bool DefaultThemeProvider::ShouldUseNativeFrame() const { +#if defined(OS_WIN) && !defined(USE_AURA) + return NativeWidgetWin::IsAeroGlassEnabled(); +#else + return false; +#endif +} + +bool DefaultThemeProvider::HasCustomImage(int id) const { + return false; +} + +RefCountedMemory* DefaultThemeProvider::GetRawData(int id) const { + return NULL; +} + +} // namespace views diff --git a/views/widget/default_theme_provider.h b/views/widget/default_theme_provider.h new file mode 100644 index 0000000..9303e83 --- /dev/null +++ b/views/widget/default_theme_provider.h @@ -0,0 +1,44 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_DEFAULT_THEME_PROVIDER_H_ +#define VIEWS_DEFAULT_THEME_PROVIDER_H_ +#pragma once + +#include <vector> + +#include "base/basictypes.h" +#include "ui/base/theme_provider.h" +#include "views/views_export.h" + +class Profile; + +namespace ui { +class ResourceBundle; +} +using ui::ResourceBundle; + +namespace views { + +class VIEWS_EXPORT DefaultThemeProvider : public ui::ThemeProvider { + public: + DefaultThemeProvider(); + virtual ~DefaultThemeProvider(); + + // Overridden from ui::ThemeProvider. + virtual void Init(Profile* profile); + virtual SkBitmap* GetBitmapNamed(int id) const; + virtual SkColor GetColor(int id) const; + virtual bool GetDisplayProperty(int id, int* result) const; + virtual bool ShouldUseNativeFrame() const; + virtual bool HasCustomImage(int id) const; + virtual RefCountedMemory* GetRawData(int id) const; + + private: + DISALLOW_COPY_AND_ASSIGN(DefaultThemeProvider); +}; + +} // namespace views + +#endif // VIEWS_DEFAULT_THEME_PROVIDER_H_ diff --git a/views/widget/drop_helper.cc b/views/widget/drop_helper.cc new file mode 100644 index 0000000..4e5ce109 --- /dev/null +++ b/views/widget/drop_helper.cc @@ -0,0 +1,156 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/drop_helper.h" + +#include "ui/base/dragdrop/drag_drop_types.h" +#include "views/view.h" +#include "views/widget/widget.h" + +namespace views { + +DropHelper::DropHelper(View* root_view) + : root_view_(root_view), + target_view_(NULL), + deepest_view_(NULL) { +} + +DropHelper::~DropHelper() { +} + +void DropHelper::ResetTargetViewIfEquals(View* view) { + if (target_view_ == view) + target_view_ = NULL; + if (deepest_view_ == view) + deepest_view_ = NULL; +} + +int DropHelper::OnDragOver(const OSExchangeData& data, + const gfx::Point& root_view_location, + int drag_operation) { + View* view = CalculateTargetViewImpl(root_view_location, data, true, + &deepest_view_); + + if (view != target_view_) { + // Target changed notify old drag exited, then new drag entered. + NotifyDragExit(); + target_view_ = view; + NotifyDragEntered(data, root_view_location, drag_operation); + } + + return NotifyDragOver(data, root_view_location, drag_operation); +} + +void DropHelper::OnDragExit() { + NotifyDragExit(); + deepest_view_ = target_view_ = NULL; +} + +int DropHelper::OnDrop(const OSExchangeData& data, + const gfx::Point& root_view_location, + int drag_operation) { + View* drop_view = target_view_; + deepest_view_ = target_view_ = NULL; + if (!drop_view) + return ui::DragDropTypes::DRAG_NONE; + + if (drag_operation == ui::DragDropTypes::DRAG_NONE) { + drop_view->OnDragExited(); + return ui::DragDropTypes::DRAG_NONE; + } + + gfx::Point view_location(root_view_location); + View* root_view = drop_view->GetWidget()->GetRootView(); + View::ConvertPointToView(root_view, drop_view, &view_location); + DropTargetEvent drop_event(data, view_location.x(), view_location.y(), + drag_operation); + return drop_view->OnPerformDrop(drop_event); +} + +View* DropHelper::CalculateTargetView( + const gfx::Point& root_view_location, + const OSExchangeData& data, + bool check_can_drop) { + return CalculateTargetViewImpl(root_view_location, data, check_can_drop, + NULL); +} + +View* DropHelper::CalculateTargetViewImpl( + const gfx::Point& root_view_location, + const OSExchangeData& data, + bool check_can_drop, + View** deepest_view) { + View* view = root_view_->GetEventHandlerForPoint(root_view_location); + if (view == deepest_view_) { + // The view the mouse is over hasn't changed; reuse the target. + return target_view_; + } + if (deepest_view) + *deepest_view = view; + // TODO(sky): for the time being these are separate. Once I port chrome menu + // I can switch to the #else implementation and nuke the OS_WIN + // implementation. +#if defined(OS_WIN) + // 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. + while (view && view != target_view_ && + (!view->IsEnabled() || !view->CanDrop(data))) { + view = view->parent(); + } +#else + int formats = 0; + std::set<OSExchangeData::CustomFormat> custom_formats; + while (view && view != target_view_) { + if (view->IsEnabled() && + view->GetDropFormats(&formats, &custom_formats) && + data.HasAnyFormat(formats, custom_formats) && + (!check_can_drop || view->CanDrop(data))) { + // Found the view. + return view; + } + formats = 0; + custom_formats.clear(); + view = view->parent(); + } +#endif + return view; +} + +void DropHelper::NotifyDragEntered(const OSExchangeData& data, + const gfx::Point& root_view_location, + int drag_operation) { + if (!target_view_) + return; + + 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(), + drag_operation); + target_view_->OnDragEntered(enter_event); +} + +int DropHelper::NotifyDragOver(const OSExchangeData& data, + const gfx::Point& root_view_location, + int drag_operation) { + if (!target_view_) + return ui::DragDropTypes::DRAG_NONE; + + 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(), + drag_operation); + return target_view_->OnDragUpdated(enter_event); +} + +void DropHelper::NotifyDragExit() { + if (target_view_) + target_view_->OnDragExited(); +} + +} // namespace views diff --git a/views/widget/drop_helper.h b/views/widget/drop_helper.h new file mode 100644 index 0000000..cc62bfc --- /dev/null +++ b/views/widget/drop_helper.h @@ -0,0 +1,108 @@ +// 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_DROP_HELPER_H_ +#define VIEWS_WIDGET_DROP_HELPER_H_ +#pragma once + +#include "base/basictypes.h" + +namespace gfx { +class Point; +} // namespace gfx + +namespace ui { +class OSExchangeData; +} // namespace ui +using ui::OSExchangeData; + +namespace views { + +class RootView; +class View; + +// DropHelper provides support for managing the view a drop is going to occur +// at during dnd as well as sending the view the appropriate dnd methods. +// DropHelper is intended to be used by a class that interacts with the system +// drag and drop. The system class invokes OnDragOver as the mouse moves, +// then either OnDragExit or OnDrop when the drop is done. +class DropHelper { + public: + explicit DropHelper(View* root_view); + ~DropHelper(); + + // Current view drop events are targeted at, may be NULL. + View* target_view() const { return target_view_; } + + // Returns the RootView the DropHelper was created with. + View* root_view() const { return root_view_; } + + // Resets the target_view_ to NULL if it equals view. + // + // 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); + + // Invoked when a the mouse is dragged over the root view during a drag and + // drop operation. This method returns a bitmask of the types in DragDropTypes + // for the target view. If no view wants the drop, DRAG_NONE is returned. + int OnDragOver(const OSExchangeData& data, + const gfx::Point& root_view_location, + int drag_operation); + + // Invoked when a the mouse is dragged out of the root view during a drag and + // drop operation. + void OnDragExit(); + + // Invoked when the user drops data on the root view during a drag and drop + // operation. See OnDragOver for details on return type. + // + // NOTE: implementations must invoke OnDragOver before invoking this, + // supplying the return value from OnDragOver as the drag_operation. + int OnDrop(const OSExchangeData& data, + const gfx::Point& root_view_location, + int drag_operation); + + // 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, + bool check_can_drop); + + private: + // Implementation of CalculateTargetView. If |deepest_view| is non-NULL it is + // set to the deepest descendant of the RootView that contains the point + // |root_view_location| + View* CalculateTargetViewImpl(const gfx::Point& root_view_location, + const OSExchangeData& data, + bool check_can_drop, + View** deepest_view); + + // Methods to send the appropriate drop notification to the targeted view. + // These do nothing if the target view is NULL. + void NotifyDragEntered(const OSExchangeData& data, + const gfx::Point& root_view_location, + int drag_operation); + int NotifyDragOver(const OSExchangeData& data, + const gfx::Point& root_view_location, + int drag_operation); + void NotifyDragExit(); + + // RootView we were created for. + View* root_view_; + + // View we're targeting events at. + View* target_view_; + + // The deepest view under the current drop coordinate. + View* deepest_view_; + + DISALLOW_COPY_AND_ASSIGN(DropHelper); +}; + +} // namespace views + +#endif // VIEWS_WIDGET_DROP_HELPER_H_ diff --git a/views/widget/drop_target_gtk.cc b/views/widget/drop_target_gtk.cc new file mode 100644 index 0000000..8d8e760 --- /dev/null +++ b/views/widget/drop_target_gtk.cc @@ -0,0 +1,329 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/drop_target_gtk.h" + +#include <algorithm> +#include <iterator> +#include <string> +#include <vector> + +#include "base/file_path.h" +#include "base/utf_string_conversions.h" +#include "net/base/net_util.h" +#include "ui/base/dragdrop/drag_drop_types.h" +#include "ui/base/dragdrop/gtk_dnd_util.h" +#include "ui/base/dragdrop/os_exchange_data_provider_gtk.h" +#include "ui/gfx/point.h" +#include "views/widget/root_view.h" +#include "views/widget/native_widget_gtk.h" + +using ui::OSExchangeData; + +namespace { + +std::string GdkAtomToString(GdkAtom atom) { + gchar* c_name = gdk_atom_name(atom); + std::string name(c_name); + g_free(c_name); + return name; +} + +// Returns true if |name| is a known name of plain text. +bool IsTextType(const std::string& name) { + return name == "text/plain" || name == "TEXT" || + name == "STRING" || name == "UTF8_STRING" || + name == "text/plain;charset=utf-8"; +} + +// Returns the OSExchangeData::Formats in |targets| and all the +// OSExchangeData::CustomFormats in |type_set|. +int CalculateTypes(GList* targets, std::set<GdkAtom>* type_set) { + int types = 0; + for (GList* element = targets; element; + element = g_list_next(element)) { + GdkAtom atom = static_cast<GdkAtom>(element->data); + type_set->insert(atom); + if (atom == GDK_TARGET_STRING) { + types |= OSExchangeData::STRING; + } else if (atom == ui::GetAtomForTarget(ui::CHROME_NAMED_URL)) { + types |= OSExchangeData::URL; + } else if (atom == ui::GetAtomForTarget(ui::TEXT_URI_LIST)) { + // TEXT_URI_LIST is used for files as well as urls. + types |= OSExchangeData::URL | OSExchangeData::FILE_NAME; + } else { + std::string target_name = GdkAtomToString(atom); + if (IsTextType(target_name)) { + types |= OSExchangeData::STRING; + } else { + // Assume any unknown data is pickled. + types |= OSExchangeData::PICKLED_DATA; + } + } + } + return types; +} + +} // namespace + +namespace views { + +DropTargetGtk::DropTargetGtk(internal::RootView* root_view, + GdkDragContext* context) + : helper_(root_view), + requested_formats_(0), + waiting_for_data_(false), + received_drop_(false), + pending_view_(NULL) { + std::set<GdkAtom> all_formats; + int source_formats = CalculateTypes(context->targets, &all_formats); + data_.reset(new OSExchangeData(new OSExchangeDataProviderGtk( + source_formats, all_formats))); +} + +DropTargetGtk::~DropTargetGtk() { +} + +void DropTargetGtk::ResetTargetViewIfEquals(View* view) { + helper_.ResetTargetViewIfEquals(view); +} + +void DropTargetGtk::OnDragDataReceived(GdkDragContext* context, + gint x, + gint y, + GtkSelectionData* data, + guint info, + guint time) { + std::string target_name = GdkAtomToString(data->type); + if (data->type == GDK_TARGET_STRING || IsTextType(target_name)) { + guchar* text_data = gtk_selection_data_get_text(data); + string16 result; + if (text_data) { + char* as_char = reinterpret_cast<char*>(text_data); + UTF8ToUTF16(as_char, strlen(as_char), &result); + g_free(text_data); + } + data_provider().SetString(result); + } else if (requested_custom_formats_.find(data->type) != + requested_custom_formats_.end()) { + Pickle result; + if (data->length > 0) + result = Pickle(reinterpret_cast<char*>(data->data), data->length); + data_provider().SetPickledData(data->type, result); + } else if (data->type == ui::GetAtomForTarget(ui::CHROME_NAMED_URL)) { + GURL url; + string16 title; + ui::ExtractNamedURL(data, &url, &title); + data_provider().SetURL(url, title); + } else if (data->type == ui::GetAtomForTarget(ui::TEXT_URI_LIST)) { + std::vector<GURL> urls; + ui::ExtractURIList(data, &urls); + if (urls.size() == 1 && urls[0].is_valid()) { + data_provider().SetURL(urls[0], string16()); + + // TEXT_URI_LIST is used for files as well as urls. + if (urls[0].SchemeIsFile()) { + FilePath file_path; + if (net::FileURLToFilePath(urls[0], &file_path)) + data_provider().SetFilename(file_path); + } + } else { + // Consumers of OSExchangeData will see this as an invalid URL. That is, + // when GetURL is invoked on the OSExchangeData this triggers false to + // be returned. + data_provider().SetURL(GURL(), string16()); + } + } + + if (!data_->HasAllFormats(requested_formats_, requested_custom_formats_)) + return; // Waiting on more data. + + int drag_operation = ui::DragDropTypes::GdkDragActionToDragOperation( + context->actions); + gfx::Point root_view_location(x, y); + drag_operation = helper_.OnDragOver(*data_, root_view_location, + drag_operation); + GdkDragAction gdk_action = static_cast<GdkDragAction>( + ui::DragDropTypes::DragOperationToGdkDragAction(drag_operation)); + if (!received_drop_) + gdk_drag_status(context, gdk_action, time); + + waiting_for_data_ = false; + + if (pending_view_ && received_drop_) { + FinishDrop(context, x, y, time); + // WARNING: we've been deleted. + return; + } +} + +gboolean DropTargetGtk::OnDragDrop(GdkDragContext* context, + gint x, + gint y, + guint time) { + received_drop_ = true; + OnDragMotion(context, x, y, time); + if (!pending_view_) { + // User isn't over a view, no drop can occur. + static_cast<NativeWidgetGtk*>( + helper_.root_view()->GetWidget()->native_widget())->ResetDropTarget(); + // WARNING: we've been deleted. + return FALSE; + } + + if (!waiting_for_data_) { + // We've got all the data now. + FinishDrop(context, x, y, time); + // WARNING: we've been deleted. + return TRUE; + } + // We're waiting on data. + return TRUE; +} + +void DropTargetGtk::OnDragLeave(GdkDragContext* context, guint time) { + helper_.OnDragExit(); +} + +gboolean DropTargetGtk::OnDragMotion(GdkDragContext* context, + gint x, + gint y, + guint time) { + waiting_for_data_ = false; + gfx::Point root_view_location(x, y); + pending_view_ = + helper_.CalculateTargetView(root_view_location, *data_, false); + if (pending_view_ && + (received_drop_ || (pending_view_ != helper_.target_view() && + pending_view_->AreDropTypesRequired()))) { + // The target requires drop types before it can answer CanDrop, + // ask for the data now. + int formats = 0; + std::set<GdkAtom> custom_formats; + pending_view_->GetDropFormats(&formats, &custom_formats); + IntersectFormats(data_provider().known_formats(), + data_provider().known_custom_formats(), + &formats, &custom_formats); + if (!data_provider().HasDataForAllFormats(formats, custom_formats)) { + if (!received_drop_) + helper_.OnDragExit(); + + // The target needs data for all the types before it can test if the + // drop is valid, but we don't have all the data. Request the data + // now. When we get back the data we'll update the target. + RequestFormats(context, formats, custom_formats, time); + + waiting_for_data_ = true; + + return TRUE; + } + } + + int drag_operation = ui::DragDropTypes::GdkDragActionToDragOperation( + context->actions); + drag_operation = helper_.OnDragOver(*data_, root_view_location, + drag_operation); + if (!received_drop_) { + GdkDragAction gdk_action = + static_cast<GdkDragAction>( + ui::DragDropTypes::DragOperationToGdkDragAction(drag_operation)); + gdk_drag_status(context, gdk_action, time); + } + return TRUE; +} + +void DropTargetGtk::FinishDrop(GdkDragContext* context, + gint x, gint y, guint time) { + gfx::Point root_view_location(x, y); + int drag_operation = ui::DragDropTypes::GdkDragActionToDragOperation( + context->actions); + drag_operation = helper_.OnDrop(*data_, root_view_location, + drag_operation); + GdkDragAction gdk_action = + static_cast<GdkDragAction>( + ui::DragDropTypes::DragOperationToGdkDragAction(drag_operation)); + gtk_drag_finish(context, gdk_action != 0, (gdk_action & GDK_ACTION_MOVE), + time); + + static_cast<NativeWidgetGtk*>(helper_.root_view()->GetWidget()-> + native_widget())->ResetDropTarget(); + // WARNING: we've been deleted. +} + +void DropTargetGtk::IntersectFormats(int f1, const std::set<GdkAtom>& cf1, + int* f2, std::set<GdkAtom>* cf2) { + *f2 = (*f2 & f1); + std::set<GdkAtom> cf; + std::set_intersection( + cf1.begin(), cf1.end(), cf2->begin(), cf2->end(), + std::insert_iterator<std::set<GdkAtom> >(cf, cf.begin())); + cf.swap(*cf2); +} + +void DropTargetGtk::RequestFormats(GdkDragContext* context, + int formats, + const std::set<GdkAtom>& custom_formats, + guint time) { + GtkWidget* widget = static_cast<NativeWidgetGtk*>(helper_.root_view()-> + GetWidget()->native_widget())->window_contents(); + + const std::set<GdkAtom>& known_formats = + data_provider().known_custom_formats(); + if ((formats & OSExchangeData::STRING) != 0 && + (requested_formats_ & OSExchangeData::STRING) == 0) { + requested_formats_ |= OSExchangeData::STRING; + if (known_formats.count(gdk_atom_intern("UTF8_STRING", false))) { + gtk_drag_get_data(widget, context, + gdk_atom_intern("UTF8_STRING", false), time); + } else if (known_formats.count(gdk_atom_intern("text/plain;charset=utf-8", + false))) { + gtk_drag_get_data(widget, context, + gdk_atom_intern("text/plain;charset=utf-8", false), + time); + } else if (known_formats.count(GDK_TARGET_STRING)) { + gtk_drag_get_data(widget, context, GDK_TARGET_STRING, time); + } else if (known_formats.count(gdk_atom_intern("text/plain", false))) { + gtk_drag_get_data(widget, context, gdk_atom_intern("text/plain", false), + time); + } else if (known_formats.count(gdk_atom_intern("TEXT", false))) { + gtk_drag_get_data(widget, context, gdk_atom_intern("TEXT", false), + time); + } else if (known_formats.count(gdk_atom_intern("STRING", false))) { + gtk_drag_get_data(widget, context, gdk_atom_intern("STRING", false), + time); + } + } + if ((formats & OSExchangeData::URL) != 0 && + (requested_formats_ & OSExchangeData::URL) == 0) { + requested_formats_ |= OSExchangeData::URL; + if (known_formats.count(ui::GetAtomForTarget(ui::CHROME_NAMED_URL))) { + gtk_drag_get_data(widget, context, + ui::GetAtomForTarget(ui::CHROME_NAMED_URL), time); + } else if (known_formats.count( + ui::GetAtomForTarget(ui::TEXT_URI_LIST))) { + gtk_drag_get_data(widget, context, + ui::GetAtomForTarget(ui::TEXT_URI_LIST), time); + } + } + if (((formats & OSExchangeData::FILE_NAME) != 0) && + (requested_formats_ & OSExchangeData::FILE_NAME) == 0) { + requested_formats_ |= OSExchangeData::FILE_NAME; + gtk_drag_get_data(widget, context, + ui::GetAtomForTarget(ui::TEXT_URI_LIST), time); + } + for (std::set<GdkAtom>::const_iterator i = custom_formats.begin(); + i != custom_formats.end(); ++i) { + if (requested_custom_formats_.find(*i) == + requested_custom_formats_.end()) { + requested_custom_formats_.insert(*i); + gtk_drag_get_data(widget, context, *i, time); + } + } +} + +OSExchangeDataProviderGtk& DropTargetGtk::data_provider() const { + return static_cast<OSExchangeDataProviderGtk&>(data_->provider()); +} + +} // namespace views diff --git a/views/widget/drop_target_gtk.h b/views/widget/drop_target_gtk.h new file mode 100644 index 0000000..daa08f2 --- /dev/null +++ b/views/widget/drop_target_gtk.h @@ -0,0 +1,120 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_DROP_TARGET_GTK_H_ +#define VIEWS_WIDGET_DROP_TARGET_GTK_H_ +#pragma once + +#include <gtk/gtk.h> +#include <set> + +#include "base/memory/scoped_ptr.h" +#include "ui/base/dragdrop/os_exchange_data.h" +#include "views/widget/drop_helper.h" + +namespace ui { +class OSExchangeDataProviderGtk; +} +using ui::OSExchangeData; +using ui::OSExchangeDataProviderGtk; + +namespace views { + +class View; +namespace internal { +class RootView; +} + +// DropTarget implementation for Gtk. +// +// The data for a drop is not immediately available on X. As such we lazily +// ask for data as necessary. Some Views require data before they can determine +// if the drop is going to be allowed. When such a View is encountered the +// relevant data is requested from the drag source. When the data is available +// the target is notified. Similarly if the drop completes and the data has +// not yet been fetched, it is fetched and the target then notified. +// +// When a drop finishes this class calls back to the containing NativeWidgetGtk +// which results in deleting the DropTargetGtk. +class DropTargetGtk { + public: + DropTargetGtk(internal::RootView* root_view, GdkDragContext* context); + ~DropTargetGtk(); + + // 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); + + // Drop methods from Gtk. These are forwarded from the containing + // NativeWidgetGtk. + void OnDragDataReceived(GdkDragContext* context, + gint x, + gint y, + GtkSelectionData* data, + guint info, + guint time); + gboolean OnDragDrop(GdkDragContext* context, + gint x, + gint y, + guint time); + void OnDragLeave(GdkDragContext* context, guint time); + gboolean OnDragMotion(GdkDragContext* context, + gint x, + gint y, + guint time); + + private: + // Invoked when the drop finishes AND all the data is available. + void FinishDrop(GdkDragContext* context, gint x, gint y, guint time); + + // Returns in |f2| and |cf2| the intersection of |f1| |f2| and + // |cf1|, |cf2|. + void IntersectFormats(int f1, const std::set<GdkAtom>& cf1, + int* f2, std::set<GdkAtom>* cf2); + + // Requests the formats in |formats| and the custom formats in + // |custom_formats|. + void RequestFormats(GdkDragContext* context, + int formats, + const std::set<GdkAtom>& custom_formats, + guint time); + + // Reutrns the Provider of the OSExchangeData we created. + OSExchangeDataProviderGtk& data_provider() const; + + // Manages sending the appropriate drop methods to the view the drop is over. + DropHelper helper_; + + // The formats we've requested from the drag source. + // + // NOTE: these formats are the intersection of the formats requested by the + // drop target and the formats provided by the source. + int requested_formats_; + std::set<GdkAtom> requested_custom_formats_; + + // The data. + scoped_ptr<OSExchangeData> data_; + + // Are we waiting for data from the source before we can notify the view? + // This is set in two distinct ways: when the view requires the data before + // it can answer Can Drop (that is, AreDropTypesRequired returns true) and + // when the user dropped the data but we didn't get it all yet. + bool waiting_for_data_; + + // Has OnDragDrop been invoked? + bool received_drop_; + + // The view under the mouse. This is not necessarily the same as + // helper_.target_view(). The two differ if the view under the mouse requires + // the data. + View* pending_view_; + + DISALLOW_COPY_AND_ASSIGN(DropTargetGtk); +}; + +} // namespace views + +#endif // VIEWS_WIDGET_DROP_TARGET_GTK_H_ diff --git a/views/widget/drop_target_win.cc b/views/widget/drop_target_win.cc new file mode 100644 index 0000000..c4c8e2c --- /dev/null +++ b/views/widget/drop_target_win.cc @@ -0,0 +1,63 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/drop_target_win.h" + +#include "ui/base/dragdrop/drag_drop_types.h" +#include "ui/base/dragdrop/os_exchange_data.h" +#include "ui/base/dragdrop/os_exchange_data_provider_win.h" +#include "ui/gfx/point.h" +#include "views/widget/root_view.h" +#include "views/widget/widget.h" + +using ui::OSExchangeData; +using ui::OSExchangeDataProviderWin; + +namespace views { + +DropTargetWin::DropTargetWin(internal::RootView* root_view) + : ui::DropTarget(root_view->GetWidget()->GetNativeView()), + helper_(root_view) { +} + +DropTargetWin::~DropTargetWin() { +} + +void DropTargetWin::ResetTargetViewIfEquals(View* view) { + helper_.ResetTargetViewIfEquals(view); +} + +DWORD DropTargetWin::OnDragOver(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect) { + gfx::Point root_view_location(cursor_position.x, cursor_position.y); + View::ConvertPointToView(NULL, helper_.root_view(), &root_view_location); + OSExchangeData data(new OSExchangeDataProviderWin(data_object)); + int drop_operation = + helper_.OnDragOver(data, root_view_location, + ui::DragDropTypes::DropEffectToDragOperation(effect)); + return ui::DragDropTypes::DragOperationToDropEffect(drop_operation); +} + +void DropTargetWin::OnDragLeave(IDataObject* data_object) { + helper_.OnDragExit(); +} + +DWORD DropTargetWin::OnDrop(IDataObject* data_object, + DWORD key_state, + POINT cursor_position, + DWORD effect) { + gfx::Point root_view_location(cursor_position.x, cursor_position.y); + View::ConvertPointToView(NULL, helper_.root_view(), &root_view_location); + + OSExchangeData data(new OSExchangeDataProviderWin(data_object)); + int drop_operation = ui::DragDropTypes::DropEffectToDragOperation(effect); + drop_operation = helper_.OnDragOver(data, root_view_location, + drop_operation); + drop_operation = helper_.OnDrop(data, root_view_location, drop_operation); + return ui::DragDropTypes::DragOperationToDropEffect(drop_operation); +} + +} // namespace views diff --git a/views/widget/drop_target_win.h b/views/widget/drop_target_win.h new file mode 100644 index 0000000..966bbc2 --- /dev/null +++ b/views/widget/drop_target_win.h @@ -0,0 +1,56 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_DROP_TARGET_WIN_H_ +#define VIEWS_WIDGET_DROP_TARGET_WIN_H_ +#pragma once + +#include "ui/base/dragdrop/drop_target.h" +#include "views/widget/drop_helper.h" + +namespace views { + +class View; +namespace internal { +class RootView; +} + +// DropTargetWin takes care of managing drag and drop for NativeWidgetWin. It +// converts Windows OLE drop messages into Views drop messages. +// +// DropTargetWin uses DropHelper to manage the appropriate view to target +// drop messages at. +class DropTargetWin : public ui::DropTarget { + public: + explicit DropTargetWin(internal::RootView* root_view); + virtual ~DropTargetWin(); + + // 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: + views::DropHelper helper_; + + DISALLOW_COPY_AND_ASSIGN(DropTargetWin); +}; + +} // namespace views + +#endif // VIEWS_WIDGET_DROP_TARGET_WIN_H_ diff --git a/views/widget/gtk_views_fixed.cc b/views/widget/gtk_views_fixed.cc new file mode 100644 index 0000000..6ab70df8 --- /dev/null +++ b/views/widget/gtk_views_fixed.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/gtk_views_fixed.h" + +#include "base/logging.h" + +// We store whether we use the widget's allocated size as a property. Ideally +// we would stash this in GtkFixedChild, but GtkFixed doesn't allow subclassing +// gtk_fixed_put. Alternatively we could subclass GtkContainer and use our own +// API (effectively duplicating GtkFixed), but that means folks could no longer +// use the GtkFixed API else where in Chrome. For now I'm going with this route. +static const char* kUseAllocatedSize = "__VIEWS_USE_ALLOCATED_SIZE__"; +static const char* kRequisitionWidth = "__VIEWS_REQUISITION_WIDTH__"; +static const char* kRequisitionHeight = "__VIEWS_REQUISITION_HEIGHT__"; + +G_BEGIN_DECLS + +G_DEFINE_TYPE(GtkViewsFixed, gtk_views_fixed, GTK_TYPE_FIXED) + +static void gtk_views_fixed_size_allocate(GtkWidget* widget, + GtkAllocation* allocation) { + widget->allocation = *allocation; + if (!GTK_WIDGET_NO_WINDOW(widget) && GTK_WIDGET_REALIZED(widget)) { + gdk_window_move_resize(widget->window, allocation->x, allocation->y, + allocation->width, allocation->height); + } + + int border_width = GTK_CONTAINER(widget)->border_width; + GList* children = GTK_FIXED(widget)->children; + while (children) { + GtkFixedChild* child = reinterpret_cast<GtkFixedChild*>(children->data); + children = children->next; + + if (GTK_WIDGET_VISIBLE(child->widget)) { + GtkAllocation child_allocation; + + int width, height; + bool use_allocated_size = + gtk_views_fixed_get_widget_size(child->widget, &width, &height); + if (use_allocated_size) { + // NOTE: even though the size isn't changing, we have to call + // size_allocate, otherwise things like buttons won't repaint. + child_allocation.width = width; + child_allocation.height = height; + } else { + GtkRequisition child_requisition; + gtk_widget_get_child_requisition(child->widget, &child_requisition); + child_allocation.width = child_requisition.width; + child_allocation.height = child_requisition.height; + } + child_allocation.x = child->x + border_width; + child_allocation.y = child->y + border_width; + + if (GTK_WIDGET_NO_WINDOW(widget)) { + child_allocation.x += widget->allocation.x; + child_allocation.y += widget->allocation.y; + } + + gtk_widget_size_allocate(child->widget, &child_allocation); + } + } +} + +static void gtk_views_fixed_class_init(GtkViewsFixedClass* views_fixed_class) { + GtkWidgetClass* widget_class = + reinterpret_cast<GtkWidgetClass*>(views_fixed_class); + widget_class->size_allocate = gtk_views_fixed_size_allocate; +} + +static void gtk_views_fixed_init(GtkViewsFixed* fixed) { + GTK_WIDGET_SET_FLAGS(GTK_WIDGET(fixed), GTK_CAN_FOCUS); +} + +GtkWidget* gtk_views_fixed_new(void) { + return GTK_WIDGET(g_object_new(GTK_TYPE_VIEWS_FIXED, NULL)); +} + +void gtk_views_fixed_set_widget_size(GtkWidget* widget, + int width, int height) { + // Remember the allocation request, and set this widget up to use it. + bool use_requested_size = (width != 0 && height != 0); + g_object_set_data(G_OBJECT(widget), kUseAllocatedSize, + reinterpret_cast<gpointer>(use_requested_size ? 1 : 0)); + g_object_set_data(G_OBJECT(widget), kRequisitionWidth, + reinterpret_cast<gpointer>(width)); + g_object_set_data(G_OBJECT(widget), kRequisitionHeight, + reinterpret_cast<gpointer>(height)); + + gtk_widget_queue_resize(widget); +} + +bool gtk_views_fixed_get_widget_size(GtkWidget* widget, + int* width, int* height) { + DCHECK(width); + DCHECK(height); + *width = reinterpret_cast<glong>(g_object_get_data(G_OBJECT(widget), + kRequisitionWidth)); + *height = reinterpret_cast<glong>(g_object_get_data(G_OBJECT(widget), + kRequisitionHeight)); + return (g_object_get_data(G_OBJECT(widget), kUseAllocatedSize) != 0); +} + +G_END_DECLS diff --git a/views/widget/gtk_views_fixed.h b/views/widget/gtk_views_fixed.h new file mode 100644 index 0000000..e9f3fe7 --- /dev/null +++ b/views/widget/gtk_views_fixed.h @@ -0,0 +1,56 @@ +// 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_GTK_VIEWS_FIXED_H_ +#define VIEWS_WIDGET_GTK_VIEWS_FIXED_H_ +#pragma once + +#include <gdk/gdk.h> +#include <gtk/gtkfixed.h> + +// GtkViewsFixed is a subclass of GtkFixed that can give child widgets +// a set size rather than their requisitioned size (which is actually +// a minimum size, and that can cause issues). This behavior is +// controlled by gtk_views_fixed_set_widget_size; the default is to +// use the Widget's requisitioned size. + +G_BEGIN_DECLS + +#define GTK_TYPE_VIEWS_FIXED (gtk_views_fixed_get_type ()) +#define GTK_VIEWS_FIXED(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_VIEWS_FIXED, GtkViewsFixed)) +#define GTK_VIEWS_FIXED_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_VIEWS_FIXED, GtkViewsFixedClass)) +#define GTK_IS_VIEWS_FIXED(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_VIEWS_FIXED)) +#define GTK_IS_VIEWS_FIXED_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_VIEWS_FIXED)) +#define GTK_VIEWS_FIXED_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_VIEWS_FIXED, GtkViewsFixed)) + +typedef struct _GtkViewsFixed GtkViewsFixed; +typedef struct _GtkViewsFixedClass GtkViewsFixedClass; + +struct _GtkViewsFixed { + GtkFixed fixed; +}; + +struct _GtkViewsFixedClass { + GtkFixedClass parent_class; +}; + +GtkWidget* gtk_views_fixed_new(); + +GType gtk_views_fixed_get_type(); + +// If width and height are 0, go back to using the requisitioned size. +// Queues up a re-size on the widget. +void gtk_views_fixed_set_widget_size(GtkWidget* widget, int width, int height); + +bool gtk_views_fixed_get_widget_size(GtkWidget* widget, + int* width, int* height); + +G_END_DECLS + +#endif // VIEWS_WIDGET_GTK_VIEWS_FIXED_H diff --git a/views/widget/gtk_views_window.cc b/views/widget/gtk_views_window.cc new file mode 100644 index 0000000..1e5b48f --- /dev/null +++ b/views/widget/gtk_views_window.cc @@ -0,0 +1,46 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <gtk/gtk.h> + +#include "ui/views/events/event.h" +#include "ui/views/focus/focus_manager.h" +#include "views/widget/gtk_views_window.h" +#include "views/widget/widget.h" + +G_BEGIN_DECLS + +G_DEFINE_TYPE(GtkViewsWindow, gtk_views_window, GTK_TYPE_WINDOW) + +static void gtk_views_window_move_focus(GtkWindow* window, + GtkDirectionType dir) { + views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window); + views::FocusManager* focus_manager = + widget ? widget->GetFocusManager() : NULL; + if (focus_manager) { + GdkEvent* key = gtk_get_current_event(); + if (key && key->type == GDK_KEY_PRESS) { + views::KeyEvent key_event(key); + focus_manager->OnKeyEvent(key_event); + } + } else if (GTK_WINDOW_CLASS(gtk_views_window_parent_class)->move_focus) { + GTK_WINDOW_CLASS(gtk_views_window_parent_class)->move_focus(window, dir); + } +} + +static void gtk_views_window_class_init( + GtkViewsWindowClass* views_window_class) { + GtkWindowClass* window_class = + reinterpret_cast<GtkWindowClass*>(views_window_class); + window_class->move_focus = gtk_views_window_move_focus; +} + +static void gtk_views_window_init(GtkViewsWindow* window) { +} + +GtkWidget* gtk_views_window_new(GtkWindowType type) { + return GTK_WIDGET(g_object_new(GTK_TYPE_VIEWS_WINDOW, "type", type, NULL)); +} + +G_END_DECLS diff --git a/views/widget/gtk_views_window.h b/views/widget/gtk_views_window.h new file mode 100644 index 0000000..8557db0 --- /dev/null +++ b/views/widget/gtk_views_window.h @@ -0,0 +1,44 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_GTK_VIEWS_WINDOW_H_ +#define VIEWS_WIDGET_GTK_VIEWS_WINDOW_H_ +#pragma once + +#include <gtk/gtkwindow.h> + +// GtkViewsWindow is a subclass of GtkWindow that overrides its move_focus +// method so that we can handle focus traversing by ourselves. + +G_BEGIN_DECLS + +#define GTK_TYPE_VIEWS_WINDOW (gtk_views_window_get_type ()) +#define GTK_VIEWS_WINDOW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), GTK_TYPE_VIEWS_WINDOW, GtkViewsWindow)) +#define GTK_VIEWS_WINDOW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), GTK_TYPE_VIEWS_WINDOW, \ + GtkViewsWindowClass)) +#define GTK_IS_VIEWS_WINDOW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_VIEWS_WINDOW)) +#define GTK_IS_VIEWS_WINDOW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_VIEWS_WINDOW)) +#define GTK_VIEWS_WINDOW_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_VIEWS_WINDOW, GtkViewsWindow)) + +typedef struct _GtkViewsWindow GtkViewsWindow; +typedef struct _GtkViewsWindowClass GtkViewsWindowClass; + +struct _GtkViewsWindow { + GtkWindow window; +}; + +struct _GtkViewsWindowClass { + GtkWindowClass parent_class; +}; + +GtkWidget* gtk_views_window_new(GtkWindowType type); + +G_END_DECLS + +#endif // VIEWS_WIDGET_GTK_VIEWS_WINDOW_H diff --git a/views/widget/monitor_win.cc b/views/widget/monitor_win.cc new file mode 100644 index 0000000..d840893 --- /dev/null +++ b/views/widget/monitor_win.cc @@ -0,0 +1,37 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/monitor_win.h" + +#include <shellapi.h> + +#include "base/logging.h" +#include "ui/gfx/rect.h" + +namespace views { + +gfx::Rect GetMonitorBoundsForRect(const gfx::Rect& rect) { + RECT p_rect = rect.ToRECT(); + HMONITOR monitor = MonitorFromRect(&p_rect, MONITOR_DEFAULTTONEAREST); + if (monitor) { + MONITORINFO mi = {0}; + mi.cbSize = sizeof(mi); + GetMonitorInfo(monitor, &mi); + return gfx::Rect(mi.rcWork); + } + NOTREACHED(); + return gfx::Rect(); +} + +HWND GetTopmostAutoHideTaskbarForEdge(UINT edge, HMONITOR monitor) { + APPBARDATA taskbar_data = { sizeof APPBARDATA, NULL, 0, edge }; + HWND taskbar = reinterpret_cast<HWND>(SHAppBarMessage(ABM_GETAUTOHIDEBAR, + &taskbar_data)); + return (::IsWindow(taskbar) && (monitor != NULL) && + (MonitorFromWindow(taskbar, MONITOR_DEFAULTTONULL) == monitor) && + (GetWindowLong(taskbar, GWL_EXSTYLE) & WS_EX_TOPMOST)) ? + taskbar : NULL; +} + +} // namespace views diff --git a/views/widget/monitor_win.h b/views/widget/monitor_win.h new file mode 100644 index 0000000..7ceacfa --- /dev/null +++ b/views/widget/monitor_win.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_MONITOR_WIN_H_ +#define VIEWS_WIDGET_MONITOR_WIN_H_ +#pragma once + +#include <windows.h> +#include "views/views_export.h" + +namespace gfx { +class Rect; +} + +namespace views { + +// Returns the bounds for the monitor that contains the largest area of +// intersection with the specified rectangle. +VIEWS_EXPORT gfx::Rect GetMonitorBoundsForRect(const gfx::Rect& rect); + +// Returns the always-on-top auto-hiding taskbar for edge |edge| (one of +// ABE_LEFT, TOP, RIGHT, or BOTTOM) of monitor |monitor|. NULL is returned +// if nothing is found. +VIEWS_EXPORT HWND GetTopmostAutoHideTaskbarForEdge(UINT edge, HMONITOR monitor); + +} // namespace views + +#endif // VIEWS_WIDGET_MONITOR_WIN_H_ diff --git a/views/widget/native_widget.h b/views/widget/native_widget.h new file mode 100644 index 0000000..9289fde --- /dev/null +++ b/views/widget/native_widget.h @@ -0,0 +1,38 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_NATIVE_WIDGET_H_ +#define VIEWS_WIDGET_NATIVE_WIDGET_H_ +#pragma once + +#include "views/widget/widget.h" + +namespace views { +namespace internal { +class NativeWidgetPrivate; +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidget interface +// +// An interface that serves as the public API base for the +// internal::NativeWidget interface that Widget uses to communicate with a +// backend-specific native widget implementation. This is the only component of +// this interface that is publicly visible, and exists solely for exposure via +// Widget's native_widget() accessor, which code occasionally static_casts to +// a known implementation in platform-specific code. +// +class VIEWS_EXPORT NativeWidget { + public: + virtual ~NativeWidget() {} + + private: + friend class Widget; + + virtual internal::NativeWidgetPrivate* AsNativeWidgetPrivate() = 0; +}; + +} // namespace views + +#endif // VIEWS_WIDGET_NATIVE_WIDGET_H_ diff --git a/views/widget/native_widget_aura.cc b/views/widget/native_widget_aura.cc new file mode 100644 index 0000000..57677f5 --- /dev/null +++ b/views/widget/native_widget_aura.cc @@ -0,0 +1,794 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/native_widget_aura.h" + +#include "base/bind.h" +#include "base/string_util.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/client/drag_drop_client.h" +#include "ui/aura/desktop.h" +#include "ui/aura/desktop_observer.h" +#include "ui/aura/event.h" +#include "ui/aura/window.h" +#include "ui/aura/window_types.h" +#include "ui/base/dragdrop/os_exchange_data.h" +#include "ui/base/ui_base_types.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/compositor/layer.h" +#include "ui/gfx/font.h" +#include "ui/gfx/screen.h" +#include "views/widget/drop_helper.h" +#include "views/widget/native_widget_delegate.h" +#include "views/widget/tooltip_manager_views.h" + +#if defined(OS_WIN) +#include "base/win/scoped_gdi_object.h" +#include "base/win/win_util.h" +#include "ui/base/l10n/l10n_util_win.h" +#endif + +#if defined(HAVE_IBUS) +#include "ui/views/ime/input_method_ibus.h" +#else +#include "ui/views/ime/mock_input_method.h" +#endif + +namespace views { + +namespace { + +aura::WindowType GetAuraWindowTypeForWidgetType(Widget::InitParams::Type type) { + switch (type) { + case Widget::InitParams::TYPE_WINDOW: + return aura::WINDOW_TYPE_NORMAL; + case Widget::InitParams::TYPE_WINDOW_FRAMELESS: + case Widget::InitParams::TYPE_CONTROL: + case Widget::InitParams::TYPE_POPUP: + case Widget::InitParams::TYPE_BUBBLE: + return aura::WINDOW_TYPE_POPUP; + case Widget::InitParams::TYPE_MENU: + return aura::WINDOW_TYPE_MENU; + case Widget::InitParams::TYPE_TOOLTIP: + return aura::WINDOW_TYPE_TOOLTIP; + default: + NOTREACHED() << "Unhandled widget type " << type; + return aura::WINDOW_TYPE_UNKNOWN; + } +} + +} // namespace + +// Used when SetInactiveRenderingDisabled() is invoked to track when active +// status changes in such a way that we should enable inactive rendering. +class NativeWidgetAura::DesktopObserverImpl : public aura::DesktopObserver { + public: + explicit DesktopObserverImpl(NativeWidgetAura* host) + : host_(host) { + aura::Desktop::GetInstance()->AddObserver(this); + } + + virtual ~DesktopObserverImpl() { + aura::Desktop::GetInstance()->RemoveObserver(this); + } + + // DesktopObserver overrides: + virtual void OnActiveWindowChanged(aura::Window* active) OVERRIDE { + if (!active || (active != host_->window_ && + active->transient_parent() != host_->window_)) { + host_->delegate_->EnableInactiveRendering(); + } + } + + private: + NativeWidgetAura* host_; + + DISALLOW_COPY_AND_ASSIGN(DesktopObserverImpl); +}; + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetAura, public: + +NativeWidgetAura::NativeWidgetAura(internal::NativeWidgetDelegate* delegate) + : delegate_(delegate), + ALLOW_THIS_IN_INITIALIZER_LIST(window_(new aura::Window(this))), + ownership_(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET), + ALLOW_THIS_IN_INITIALIZER_LIST(close_widget_factory_(this)), + can_activate_(true), + cursor_(gfx::kNullCursor) { +} + +NativeWidgetAura::~NativeWidgetAura() { + if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) + delete delegate_; + else + CloseNow(); +} + +// static +gfx::Font NativeWidgetAura::GetWindowTitleFont() { +#if defined(OS_WIN) + NONCLIENTMETRICS ncm; + base::win::GetNonClientMetrics(&ncm); + l10n_util::AdjustUIFont(&(ncm.lfCaptionFont)); + base::win::ScopedHFONT caption_font(CreateFontIndirect(&(ncm.lfCaptionFont))); + return gfx::Font(caption_font); +#else + return gfx::Font(); +#endif +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetAura, internal::NativeWidgetPrivate implementation: + +void NativeWidgetAura::InitNativeWidget(const Widget::InitParams& params) { + ownership_ = params.ownership; + window_->set_user_data(this); + Widget::InitParams::Type window_type = + params.child ? Widget::InitParams::TYPE_CONTROL : params.type; + window_->SetType(GetAuraWindowTypeForWidgetType(window_type)); + window_->SetIntProperty(aura::kShowStateKey, ui::SHOW_STATE_NORMAL); + window_->Init(params.create_texture_for_layer ? + ui::Layer::LAYER_HAS_TEXTURE : + ui::Layer::LAYER_HAS_NO_TEXTURE); + if (window_type == Widget::InitParams::TYPE_CONTROL) + window_->Show(); + + window_->layer()->SetFillsBoundsOpaquely(!params.transparent); + delegate_->OnNativeWidgetCreated(); + window_->SetBounds(params.bounds); + if (window_type == Widget::InitParams::TYPE_CONTROL) { + window_->SetParent(params.GetParent()); + } else { + // Set up the transient child before the window is added. This way the + // LayoutManager knows the window has a transient parent. + gfx::NativeView parent = params.GetParent(); + if (parent) + parent->AddTransientChild(window_); + // SetAlwaysOnTop before SetParent so that always-on-top container is used. + SetAlwaysOnTop(params.keep_on_top); + window_->SetParent(NULL); + } + window_->set_ignore_events(!params.accept_events); + // TODO(beng): do this some other way. + delegate_->OnNativeWidgetSizeChanged(params.bounds.size()); + can_activate_ = params.can_activate; + DCHECK(GetWidget()->GetRootView()); + if (params.type != Widget::InitParams::TYPE_TOOLTIP && !params.child) { + views::TooltipManagerViews* manager = new views::TooltipManagerViews( + GetWidget()->GetRootView()); + tooltip_manager_.reset(manager); + } + drop_helper_.reset(new DropHelper(GetWidget()->GetRootView())); + if (params.type != Widget::InitParams::TYPE_TOOLTIP && + params.type != Widget::InitParams::TYPE_POPUP) { + window_->SetProperty(aura::kDragDropDelegateKey, + static_cast<aura::WindowDragDropDelegate*>(this)); + } +} + +NonClientFrameView* NativeWidgetAura::CreateNonClientFrameView() { + return NULL; +} + +void NativeWidgetAura::UpdateFrameAfterFrameChange() { + // We don't support changing the frame type. + NOTREACHED(); +} + +bool NativeWidgetAura::ShouldUseNativeFrame() const { + // There is only one frame type for aura. + return false; +} + +void NativeWidgetAura::FrameTypeChanged() { + // This is called when the Theme has changed; forward the event to the root + // widget. + GetWidget()->ThemeChanged(); + GetWidget()->GetRootView()->SchedulePaint(); +} + +Widget* NativeWidgetAura::GetWidget() { + return delegate_->AsWidget(); +} + +const Widget* NativeWidgetAura::GetWidget() const { + return delegate_->AsWidget(); +} + +gfx::NativeView NativeWidgetAura::GetNativeView() const { + return window_; +} + +gfx::NativeWindow NativeWidgetAura::GetNativeWindow() const { + return window_; +} + +Widget* NativeWidgetAura::GetTopLevelWidget() { + NativeWidgetPrivate* native_widget = GetTopLevelNativeWidget(GetNativeView()); + return native_widget ? native_widget->GetWidget() : NULL; +} + +const ui::Compositor* NativeWidgetAura::GetCompositor() const { + return window_->layer()->GetCompositor(); +} + +ui::Compositor* NativeWidgetAura::GetCompositor() { + return window_->layer()->GetCompositor(); +} + +void NativeWidgetAura::CalculateOffsetToAncestorWithLayer( + gfx::Point* offset, + ui::Layer** layer_parent) { + if (layer_parent) + *layer_parent = window_->layer(); +} + +void NativeWidgetAura::ReorderLayers() { +} + +void NativeWidgetAura::ViewRemoved(View* view) { + // DropTarget stuff. Most likely http://crbug.com/97845 + //NOTIMPLEMENTED(); +} + +void NativeWidgetAura::SetNativeWindowProperty(const char* name, void* value) { + if (window_) + window_->SetProperty(name, value); +} + +void* NativeWidgetAura::GetNativeWindowProperty(const char* name) const { + return window_ ? window_->GetProperty(name) : NULL; +} + +TooltipManager* NativeWidgetAura::GetTooltipManager() const { + return tooltip_manager_.get(); +} + +bool NativeWidgetAura::IsScreenReaderActive() const { + // http://crbug.com/102570 + //NOTIMPLEMENTED(); + return false; +} + +void NativeWidgetAura::SendNativeAccessibilityEvent( + View* view, + ui::AccessibilityTypes::Event event_type) { + // http://crbug.com/102570 + //NOTIMPLEMENTED(); +} + +void NativeWidgetAura::SetMouseCapture() { + window_->SetCapture(); +} + +void NativeWidgetAura::ReleaseMouseCapture() { + window_->ReleaseCapture(); +} + +bool NativeWidgetAura::HasMouseCapture() const { + return window_->HasCapture(); +} + +InputMethod* NativeWidgetAura::CreateInputMethod() { +#if defined(HAVE_IBUS) + InputMethod* input_method = new InputMethodIBus(this); +#else + InputMethod* input_method = new MockInputMethod(this); +#endif + input_method->Init(GetWidget()); + return input_method; +} + +void NativeWidgetAura::CenterWindow(const gfx::Size& size) { + const gfx::Rect parent_bounds = window_->parent()->bounds(); + window_->SetBounds(gfx::Rect((parent_bounds.width() - size.width())/2, + (parent_bounds.height() - size.height())/2, + size.width(), + size.height())); +} + +void NativeWidgetAura::GetWindowPlacement( + gfx::Rect* bounds, + ui::WindowShowState* show_state) const { + *bounds = window_->GetTargetBounds(); + *show_state = static_cast<ui::WindowShowState>( + window_->GetIntProperty(aura::kShowStateKey)); +} + +void NativeWidgetAura::SetWindowTitle(const string16& title) { + window_->set_title(title); +} + +void NativeWidgetAura::SetWindowIcons(const SkBitmap& window_icon, + const SkBitmap& app_icon) { + // Aura doesn't have window icons. +} + +void NativeWidgetAura::SetAccessibleName(const string16& name) { + // http://crbug.com/102570 + //NOTIMPLEMENTED(); +} + +void NativeWidgetAura::SetAccessibleRole(ui::AccessibilityTypes::Role role) { + // http://crbug.com/102570 + //NOTIMPLEMENTED(); +} + +void NativeWidgetAura::SetAccessibleState(ui::AccessibilityTypes::State state) { + // http://crbug.com/102570 + //NOTIMPLEMENTED(); +} + +void NativeWidgetAura::BecomeModal() { + window_->SetIntProperty(aura::kModalKey, 1); +} + +gfx::Rect NativeWidgetAura::GetWindowScreenBounds() const { + return window_->GetScreenBounds(); +} + +gfx::Rect NativeWidgetAura::GetClientAreaScreenBounds() const { + // In Aura, the entire window is the client area. + return window_->GetScreenBounds(); +} + +gfx::Rect NativeWidgetAura::GetRestoredBounds() const { + gfx::Rect* restore_bounds = reinterpret_cast<gfx::Rect*>( + window_->GetProperty(aura::kRestoreBoundsKey)); + return restore_bounds ? *restore_bounds : window_->bounds(); +} + +void NativeWidgetAura::SetBounds(const gfx::Rect& bounds) { + window_->SetBounds(bounds); +} + +void NativeWidgetAura::SetSize(const gfx::Size& size) { + window_->SetBounds(gfx::Rect(window_->bounds().origin(), size)); +} + +void NativeWidgetAura::MoveAbove(gfx::NativeView native_view) { + if (window_->parent() && window_->parent() == native_view->parent()) + window_->parent()->MoveChildAbove(window_, native_view); +} + +void NativeWidgetAura::MoveToTop() { + window_->parent()->MoveChildToFront(window_); +} + +void NativeWidgetAura::SetShape(gfx::NativeRegion region) { + // No need for this. +} + +void NativeWidgetAura::Close() { + // |window_| may already be deleted by parent window. This can happen + // when this widget is child widget or has transient parent + // and ownership is WIDGET_OWNS_NATIVE_WIDGET. + DCHECK(window_ || + ownership_ == Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET); + if (window_) + Hide(); + + window_->SetIntProperty(aura::kModalKey, 0); + + if (!close_widget_factory_.HasWeakPtrs()) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&NativeWidgetAura::CloseNow, + close_widget_factory_.GetWeakPtr())); + } +} + +void NativeWidgetAura::CloseNow() { + delete window_; +} + +void NativeWidgetAura::EnableClose(bool enable) { + // http://crbug.com/102581 + NOTIMPLEMENTED(); +} + +void NativeWidgetAura::Show() { + ShowWithWindowState(ui::SHOW_STATE_INACTIVE); +} + +void NativeWidgetAura::Hide() { + window_->Hide(); +} + +void NativeWidgetAura::ShowMaximizedWithBounds( + const gfx::Rect& restored_bounds) { + window_->SetBounds(restored_bounds); + ShowWithWindowState(ui::SHOW_STATE_MAXIMIZED); +} + +void NativeWidgetAura::ShowWithWindowState(ui::WindowShowState state) { + if (state == ui::SHOW_STATE_MAXIMIZED || + state == ui::SHOW_STATE_FULLSCREEN) { + window_->SetIntProperty(aura::kShowStateKey, state); + } + window_->Show(); + if (can_activate_ && (state != ui::SHOW_STATE_INACTIVE || + !GetWidget()->SetInitialFocus())) { + window_->Activate(); + } +} + +bool NativeWidgetAura::IsVisible() const { + return window_->IsVisible(); +} + +void NativeWidgetAura::Activate() { + window_->Activate(); +} + +void NativeWidgetAura::Deactivate() { + window_->Deactivate(); +} + +bool NativeWidgetAura::IsActive() const { + return aura::Desktop::GetInstance()->active_window() == window_; +} + +void NativeWidgetAura::SetAlwaysOnTop(bool on_top) { + window_->SetIntProperty(aura::kAlwaysOnTopKey, on_top); +} + +void NativeWidgetAura::Maximize() { + window_->SetIntProperty(aura::kShowStateKey, ui::SHOW_STATE_MAXIMIZED); +} + +void NativeWidgetAura::Minimize() { + // No minimized window for aura. crbug.com/104571. + NOTREACHED(); +} + +bool NativeWidgetAura::IsMaximized() const { + return window_->GetIntProperty(aura::kShowStateKey) == + ui::SHOW_STATE_MAXIMIZED; +} + +bool NativeWidgetAura::IsMinimized() const { + return window_->GetIntProperty(aura::kShowStateKey) == + ui::SHOW_STATE_MINIMIZED; +} + +void NativeWidgetAura::Restore() { + window_->SetIntProperty(aura::kShowStateKey, ui::SHOW_STATE_NORMAL); +} + +void NativeWidgetAura::SetFullscreen(bool fullscreen) { + window_->SetIntProperty( + aura::kShowStateKey, + fullscreen ? ui::SHOW_STATE_FULLSCREEN : ui::SHOW_STATE_NORMAL); +} + +bool NativeWidgetAura::IsFullscreen() const { + return window_->GetIntProperty(aura::kShowStateKey) == + ui::SHOW_STATE_FULLSCREEN; +} + +void NativeWidgetAura::SetOpacity(unsigned char opacity) { + window_->layer()->SetOpacity(opacity / 255.0); +} + +void NativeWidgetAura::SetUseDragFrame(bool use_drag_frame) { + NOTIMPLEMENTED(); +} + +bool NativeWidgetAura::IsAccessibleWidget() const { + // http://crbug.com/102570 + //NOTIMPLEMENTED(); + return false; +} + +void NativeWidgetAura::RunShellDrag(View* view, + const ui::OSExchangeData& data, + int operation) { + aura::DragDropClient* client = static_cast<aura::DragDropClient*>( + aura::Desktop::GetInstance()->GetProperty( + aura::kDesktopDragDropClientKey)); + if (client) + client->StartDragAndDrop(data, operation); +} + +void NativeWidgetAura::SchedulePaintInRect(const gfx::Rect& rect) { + if (window_) + window_->SchedulePaintInRect(rect); +} + +void NativeWidgetAura::SetCursor(gfx::NativeCursor cursor) { + cursor_ = cursor; + aura::Desktop::GetInstance()->SetCursor(cursor); +} + +void NativeWidgetAura::ClearNativeFocus() { + if (window_ && window_->GetFocusManager()) + window_->GetFocusManager()->SetFocusedWindow(window_); +} + +void NativeWidgetAura::FocusNativeView(gfx::NativeView native_view) { + // http://crbug.com/102572 + NOTIMPLEMENTED(); +} + +bool NativeWidgetAura::ConvertPointFromAncestor(const Widget* ancestor, + gfx::Point* point) const { + // http://crbug.com/102573 + NOTIMPLEMENTED(); + return false; +} + +gfx::Rect NativeWidgetAura::GetWorkAreaBoundsInScreen() const { + return gfx::Screen::GetMonitorWorkAreaNearestWindow(GetNativeView()); +} + +void NativeWidgetAura::SetInactiveRenderingDisabled(bool value) { + if (!value) + desktop_observer_.reset(); + else + desktop_observer_.reset(new DesktopObserverImpl(this)); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetAura, views::InputMethodDelegate implementation: + +void NativeWidgetAura::DispatchKeyEventPostIME(const KeyEvent& key) { + if (delegate_->OnKeyEvent(key)) + return; + if (key.type() == ui::ET_KEY_PRESSED && GetWidget()->GetFocusManager()) + GetWidget()->GetFocusManager()->OnKeyEvent(key); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetAura, aura::WindowDelegate implementation: + +void NativeWidgetAura::OnBoundsChanging(gfx::Rect* new_bounds) { + // Enforces a minimum size. + const gfx::Size& min_size = delegate_->GetMinimumSize(); + new_bounds->set_width(std::max(min_size.width(), new_bounds->width())); + new_bounds->set_height(std::max(min_size.height(), new_bounds->height())); +} + +void NativeWidgetAura::OnBoundsChanged(const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) { + if (old_bounds.origin() != new_bounds.origin()) + GetWidget()->widget_delegate()->OnWidgetMove(); + if (old_bounds.size() != new_bounds.size()) + delegate_->OnNativeWidgetSizeChanged(new_bounds.size()); +} + +void NativeWidgetAura::OnFocus() { + Widget* widget = GetWidget(); + if (widget->is_top_level()) { + InputMethod* input_method = widget->GetInputMethod(); + input_method->OnFocus(); + // See description of got_initial_focus_in_ for details on this. + // TODO(mazda): Investigate this is actually necessary. + // widget->GetFocusManager()->RestoreFocusedView(); + } + delegate_->OnNativeFocus(window_); +} + +void NativeWidgetAura::OnBlur() { + Widget* widget = GetWidget(); + if (widget->is_top_level()) { + InputMethod* input_method = widget->GetInputMethod(); + input_method->OnBlur(); + widget->GetFocusManager()->StoreFocusedView(); + } + delegate_->OnNativeBlur(NULL); +} + +bool NativeWidgetAura::OnKeyEvent(aura::KeyEvent* event) { + // TODO(beng): Need an InputMethodAura to properly handle character events. + // Right now, we just skip these. + if (event->is_char()) + return false; + + DCHECK(window_->IsVisible()); + InputMethod* input_method = GetWidget()->GetInputMethod(); + DCHECK(input_method); + // TODO(oshima): DispatchKeyEvent should return bool? + KeyEvent views_event(event); + input_method->DispatchKeyEvent(views_event); + return true; +} + +gfx::NativeCursor NativeWidgetAura::GetCursor(const gfx::Point& point) { + return cursor_; +} + +int NativeWidgetAura::GetNonClientComponent(const gfx::Point& point) const { + return delegate_->GetNonClientComponent(point); +} + +bool NativeWidgetAura::OnMouseEvent(aura::MouseEvent* event) { + DCHECK(window_->IsVisible()); + if (event->type() == ui::ET_MOUSEWHEEL) { + MouseWheelEvent wheel_event(event); + if (tooltip_manager_.get()) + tooltip_manager_->UpdateForMouseEvent(wheel_event); + return delegate_->OnMouseEvent(wheel_event); + } + MouseEvent mouse_event(event); + if (tooltip_manager_.get()) + tooltip_manager_->UpdateForMouseEvent(mouse_event); + return delegate_->OnMouseEvent(mouse_event); +} + +ui::TouchStatus NativeWidgetAura::OnTouchEvent(aura::TouchEvent* event) { + DCHECK(window_->IsVisible()); + TouchEvent touch_event(event); + return delegate_->OnTouchEvent(touch_event); +} + +bool NativeWidgetAura::ShouldActivate(aura::Event* event) { + return can_activate_; +} + +void NativeWidgetAura::OnActivated() { + delegate_->OnNativeWidgetActivationChanged(true); + if (IsVisible() && GetWidget()->non_client_view()) + GetWidget()->non_client_view()->SchedulePaint(); +} + +void NativeWidgetAura::OnLostActive() { + delegate_->OnNativeWidgetActivationChanged(false); + if (IsVisible() && GetWidget()->non_client_view()) + GetWidget()->non_client_view()->SchedulePaint(); +} + +void NativeWidgetAura::OnCaptureLost() { + delegate_->OnMouseCaptureLost(); +} + +void NativeWidgetAura::OnPaint(gfx::Canvas* canvas) { + delegate_->OnNativeWidgetPaint(canvas); +} + +void NativeWidgetAura::OnWindowDestroying() { + window_->SetProperty(aura::kDragDropDelegateKey, NULL); + delegate_->OnNativeWidgetDestroying(); + + // If the aura::Window is destroyed, we can no longer show tooltips. + tooltip_manager_.reset(); +} + +void NativeWidgetAura::OnWindowDestroyed() { + window_ = NULL; + tooltip_manager_.reset(); + delegate_->OnNativeWidgetDestroyed(); + if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) + delete this; +} + +void NativeWidgetAura::OnWindowVisibilityChanged(bool visible) { + delegate_->OnNativeWidgetVisibilityChanged(visible); +} + +bool NativeWidgetAura::CanDrop(const aura::DropTargetEvent& event) { + DCHECK(drop_helper_.get() != NULL); + View* view = drop_helper_->target_view(); + if (view) + return view->CanDrop(event.data()); + return false; +} + +void NativeWidgetAura::OnDragEntered(const aura::DropTargetEvent& event) { + DCHECK(drop_helper_.get() != NULL); + drop_helper_->OnDragOver(event.data(), event.location(), + event.source_operations()); +} + +int NativeWidgetAura::OnDragUpdated(const aura::DropTargetEvent& event) { + DCHECK(drop_helper_.get() != NULL); + return drop_helper_->OnDragOver(event.data(), event.location(), + event.source_operations()); +} + +void NativeWidgetAura::OnDragExited() { + DCHECK(drop_helper_.get() != NULL); + drop_helper_->OnDragExit(); +} + +int NativeWidgetAura::OnPerformDrop(const aura::DropTargetEvent& event) { + DCHECK(drop_helper_.get() != NULL); + return drop_helper_->OnDrop(event.data(), event.location(), + event.source_operations()); +} + +//////////////////////////////////////////////////////////////////////////////// +// Widget, public: + +// static +void Widget::NotifyLocaleChanged() { + // http://crbug.com/102574 + NOTIMPLEMENTED(); +} + +// static +void Widget::CloseAllSecondaryWidgets() { + // http://crbug.com/102575 + NOTIMPLEMENTED(); +} + +bool Widget::ConvertRect(const Widget* source, + const Widget* target, + gfx::Rect* rect) { + return false; +} + +namespace internal { + +//////////////////////////////////////////////////////////////////////////////// +// internal::NativeWidgetPrivate, public: + +// static +NativeWidgetPrivate* NativeWidgetPrivate::CreateNativeWidget( + internal::NativeWidgetDelegate* delegate) { + return new NativeWidgetAura(delegate); +} + +// static +NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeView( + gfx::NativeView native_view) { + return reinterpret_cast<NativeWidgetAura*>(native_view->user_data()); +} + +// static +NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeWindow( + gfx::NativeWindow native_window) { + return reinterpret_cast<NativeWidgetAura*>(native_window->user_data()); +} + +// static +NativeWidgetPrivate* NativeWidgetPrivate::GetTopLevelNativeWidget( + gfx::NativeView native_view) { + aura::Window* window = native_view; + NativeWidgetPrivate* top_level_native_widget = NULL; + while (window) { + NativeWidgetPrivate* native_widget = GetNativeWidgetForNativeView(window); + if (native_widget) + top_level_native_widget = native_widget; + window = window->parent(); + } + return top_level_native_widget; +} + +// static +void NativeWidgetPrivate::GetAllChildWidgets(gfx::NativeView native_view, + Widget::Widgets* children) { + { + // Code expects widget for |native_view| to be added to |children|. + NativeWidgetAura* native_widget = static_cast<NativeWidgetAura*>( + GetNativeWidgetForNativeView(native_view)); + if (native_widget->GetWidget()) + children->insert(native_widget->GetWidget()); + } + + const aura::Window::Windows& child_windows = native_view->children(); + for (aura::Window::Windows::const_iterator i = child_windows.begin(); + i != child_windows.end(); ++i) { + NativeWidgetAura* native_widget = + static_cast<NativeWidgetAura*>(GetNativeWidgetForNativeView(*i)); + if (native_widget) + children->insert(native_widget->GetWidget()); + } +} + +// static +void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView native_view, + gfx::NativeView new_parent) { + // http://crbug.com/102576 + NOTIMPLEMENTED(); +} + +// static +bool NativeWidgetPrivate::IsMouseButtonDown() { + return aura::Desktop::GetInstance()->IsMouseButtonDown(); +} + +} // namespace internal +} // namespace views diff --git a/views/widget/native_widget_aura.h b/views/widget/native_widget_aura.h new file mode 100644 index 0000000..8f1383a --- /dev/null +++ b/views/widget/native_widget_aura.h @@ -0,0 +1,186 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_NATIVE_WIDGET_AURA_H_ +#define VIEWS_WIDGET_NATIVE_WIDGET_AURA_H_ +#pragma once + +#include "base/memory/scoped_vector.h" +#include "base/memory/weak_ptr.h" +#include "ui/aura/client/window_drag_drop_delegate.h" +#include "ui/aura/window_delegate.h" +#include "ui/base/events.h" +#include "views/views_export.h" +#include "views/widget/native_widget_private.h" + +namespace aura { +class Window; +} +namespace gfx { +class Font; +} + +namespace views { + +class DropHelper; +class TooltipManagerViews; + +class VIEWS_EXPORT NativeWidgetAura : public internal::NativeWidgetPrivate, + public aura::WindowDelegate, + public aura::WindowDragDropDelegate { + public: + explicit NativeWidgetAura(internal::NativeWidgetDelegate* delegate); + virtual ~NativeWidgetAura(); + + // TODO(beng): Find a better place for this, and the similar method on + // NativeWidgetWin. + static gfx::Font GetWindowTitleFont(); + + // Overridden from internal::NativeWidgetPrivate: + virtual void InitNativeWidget(const Widget::InitParams& params) OVERRIDE; + virtual NonClientFrameView* CreateNonClientFrameView() OVERRIDE; + virtual void UpdateFrameAfterFrameChange() OVERRIDE; + virtual bool ShouldUseNativeFrame() const OVERRIDE; + virtual void FrameTypeChanged() OVERRIDE; + virtual Widget* GetWidget() OVERRIDE; + virtual const Widget* GetWidget() const OVERRIDE; + virtual gfx::NativeView GetNativeView() const OVERRIDE; + virtual gfx::NativeWindow GetNativeWindow() const OVERRIDE; + virtual Widget* GetTopLevelWidget() OVERRIDE; + virtual const ui::Compositor* GetCompositor() const OVERRIDE; + virtual ui::Compositor* GetCompositor() OVERRIDE; + virtual void CalculateOffsetToAncestorWithLayer( + gfx::Point* offset, + ui::Layer** layer_parent) OVERRIDE; + virtual void ReorderLayers() OVERRIDE; + virtual void ViewRemoved(View* view) OVERRIDE; + virtual void SetNativeWindowProperty(const char* name, void* value) OVERRIDE; + virtual void* GetNativeWindowProperty(const char* name) const OVERRIDE; + virtual TooltipManager* GetTooltipManager() const OVERRIDE; + virtual bool IsScreenReaderActive() const OVERRIDE; + virtual void SendNativeAccessibilityEvent( + View* view, + ui::AccessibilityTypes::Event event_type) OVERRIDE; + virtual void SetMouseCapture() OVERRIDE; + virtual void ReleaseMouseCapture() OVERRIDE; + virtual bool HasMouseCapture() const OVERRIDE; + virtual InputMethod* CreateInputMethod() OVERRIDE; + virtual void CenterWindow(const gfx::Size& size) OVERRIDE; + virtual void GetWindowPlacement( + gfx::Rect* bounds, + ui::WindowShowState* maximized) const OVERRIDE; + virtual void SetWindowTitle(const string16& title) OVERRIDE; + virtual void SetWindowIcons(const SkBitmap& window_icon, + const SkBitmap& app_icon) OVERRIDE; + virtual void SetAccessibleName(const string16& name) OVERRIDE; + virtual void SetAccessibleRole(ui::AccessibilityTypes::Role role) OVERRIDE; + virtual void SetAccessibleState(ui::AccessibilityTypes::State state) OVERRIDE; + virtual void BecomeModal() OVERRIDE; + virtual gfx::Rect GetWindowScreenBounds() const OVERRIDE; + virtual gfx::Rect GetClientAreaScreenBounds() const OVERRIDE; + virtual gfx::Rect GetRestoredBounds() const OVERRIDE; + virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE; + virtual void SetSize(const gfx::Size& size) OVERRIDE; + virtual void MoveAbove(gfx::NativeView native_view) OVERRIDE; + virtual void MoveToTop() OVERRIDE; + virtual void SetShape(gfx::NativeRegion shape) OVERRIDE; + virtual void Close() OVERRIDE; + virtual void CloseNow() OVERRIDE; + virtual void EnableClose(bool enable) OVERRIDE; + virtual void Show() OVERRIDE; + virtual void Hide() OVERRIDE; + virtual void ShowMaximizedWithBounds( + const gfx::Rect& restored_bounds) OVERRIDE; + virtual void ShowWithWindowState(ui::WindowShowState state) OVERRIDE; + virtual bool IsVisible() const OVERRIDE; + virtual void Activate() OVERRIDE; + virtual void Deactivate() OVERRIDE; + virtual bool IsActive() const OVERRIDE; + virtual void SetAlwaysOnTop(bool always_on_top) OVERRIDE; + virtual void Maximize() OVERRIDE; + virtual void Minimize() OVERRIDE; + virtual bool IsMaximized() const OVERRIDE; + virtual bool IsMinimized() const OVERRIDE; + virtual void Restore() OVERRIDE; + virtual void SetFullscreen(bool fullscreen) OVERRIDE; + virtual bool IsFullscreen() const OVERRIDE; + virtual void SetOpacity(unsigned char opacity) OVERRIDE; + virtual void SetUseDragFrame(bool use_drag_frame) OVERRIDE; + virtual bool IsAccessibleWidget() const OVERRIDE; + virtual void RunShellDrag(View* view, + const ui::OSExchangeData& data, + int operation) OVERRIDE; + virtual void SchedulePaintInRect(const gfx::Rect& rect) OVERRIDE; + virtual void SetCursor(gfx::NativeCursor cursor) OVERRIDE; + virtual void ClearNativeFocus() OVERRIDE; + virtual void FocusNativeView(gfx::NativeView native_view) OVERRIDE; + virtual bool ConvertPointFromAncestor( + const Widget* ancestor, gfx::Point* point) const OVERRIDE; + virtual gfx::Rect GetWorkAreaBoundsInScreen() const OVERRIDE; + virtual void SetInactiveRenderingDisabled(bool value) OVERRIDE; + + // Overridden from views::InputMethodDelegate: + virtual void DispatchKeyEventPostIME(const KeyEvent& key) OVERRIDE; + + // Overridden from aura::WindowDelegate: + virtual void OnBoundsChanging(gfx::Rect* new_bounds) OVERRIDE; + virtual void OnBoundsChanged(const gfx::Rect& old_bounds, + const gfx::Rect& new_bounds) OVERRIDE; + virtual void OnFocus() OVERRIDE; + virtual void OnBlur() OVERRIDE; + virtual bool OnKeyEvent(aura::KeyEvent* event) OVERRIDE; + virtual gfx::NativeCursor GetCursor(const gfx::Point& point) OVERRIDE; + virtual int GetNonClientComponent(const gfx::Point& point) const OVERRIDE; + virtual bool OnMouseEvent(aura::MouseEvent* event) OVERRIDE; + virtual ui::TouchStatus OnTouchEvent(aura::TouchEvent* event) OVERRIDE; + virtual bool ShouldActivate(aura::Event* event) OVERRIDE; + virtual void OnActivated() OVERRIDE; + virtual void OnLostActive() OVERRIDE; + virtual void OnCaptureLost() OVERRIDE; + virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; + virtual void OnWindowDestroying() OVERRIDE; + virtual void OnWindowDestroyed() OVERRIDE; + virtual void OnWindowVisibilityChanged(bool visible) OVERRIDE; + + // Overridden from aura::WindowDragDropDelegate: + virtual bool CanDrop(const aura::DropTargetEvent& event) OVERRIDE; + virtual void OnDragEntered(const aura::DropTargetEvent& event) OVERRIDE; + virtual int OnDragUpdated(const aura::DropTargetEvent& event) OVERRIDE; + virtual void OnDragExited() OVERRIDE; + virtual int OnPerformDrop(const aura::DropTargetEvent& event) OVERRIDE; + + protected: + internal::NativeWidgetDelegate* delegate() { return delegate_; } + + private: + class DesktopObserverImpl; + + internal::NativeWidgetDelegate* delegate_; + + aura::Window* window_; + + // See class documentation for Widget in widget.h for a note about ownership. + Widget::InitParams::Ownership ownership_; + + // The following factory is used for calls to close the NativeWidgetAura + // instance. + base::WeakPtrFactory<NativeWidgetAura> close_widget_factory_; + + // Can we be made active? + bool can_activate_; + + gfx::NativeCursor cursor_; + + scoped_ptr<TooltipManagerViews> tooltip_manager_; + + scoped_ptr<DesktopObserverImpl> desktop_observer_; + + scoped_ptr<DropHelper> drop_helper_; + + DISALLOW_COPY_AND_ASSIGN(NativeWidgetAura); +}; + +} // namespace views + +#endif // VIEWS_WIDGET_NATIVE_WIDGET_AURA_H_ diff --git a/views/widget/native_widget_delegate.h b/views/widget/native_widget_delegate.h new file mode 100644 index 0000000..3732e97 --- /dev/null +++ b/views/widget/native_widget_delegate.h @@ -0,0 +1,116 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_NATIVE_WIDGET_DELEGATE_H_ +#define VIEWS_WIDGET_NATIVE_WIDGET_DELEGATE_H_ +#pragma once + +#include "views/views_export.h" +#include "ui/base/events.h" + +namespace gfx { +class Canvas; +class Point; +class Size; +} + +namespace views { +class InputMethod; +class KeyEvent; +class MouseEvent; +class TouchEvent; + +namespace internal { + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetDelegate +// +// An interface implemented by the object that handles events sent by a +// NativeWidget implementation. +// +class VIEWS_EXPORT NativeWidgetDelegate { + public: + virtual ~NativeWidgetDelegate() {} + + // Returns true if the window is modal. + virtual bool IsModal() const = 0; + + // Returns true if the window is a dialog box. + virtual bool IsDialogBox() const = 0; + + // Returns true if the window can be activated. + virtual bool CanActivate() const = 0; + + virtual bool IsInactiveRenderingDisabled() const = 0; + virtual void EnableInactiveRendering() = 0; + + // Called when the activation state of a window has changed. + virtual void OnNativeWidgetActivationChanged(bool active) = 0; + + // Called when native focus moves from one native view to another. + virtual void OnNativeFocus(gfx::NativeView focused_view) = 0; + virtual void OnNativeBlur(gfx::NativeView focused_view) = 0; + + // Called when the window is shown/hidden. + virtual void OnNativeWidgetVisibilityChanged(bool visible) = 0; + + // Called when the native widget is created. + virtual void OnNativeWidgetCreated() = 0; + + // Called just before the native widget is destroyed. This is the delegate's + // last chance to do anything with the native widget handle. + virtual void OnNativeWidgetDestroying() = 0; + + // Called just after the native widget is destroyed. + virtual void OnNativeWidgetDestroyed() = 0; + + // Returns the smallest size the window can be resized to by the user. + virtual gfx::Size GetMinimumSize() = 0; + + // Called when the NativeWidget changed size to |new_size|. + virtual void OnNativeWidgetSizeChanged(const gfx::Size& new_size) = 0; + + // Called when the user begins/ends to change the bounds of the window. + virtual void OnNativeWidgetBeginUserBoundsChange() = 0; + virtual void OnNativeWidgetEndUserBoundsChange() = 0; + + // Returns true if the delegate has a FocusManager. + virtual bool HasFocusManager() const = 0; + + // Paints the widget using acceleration. If the widget is not using + // accelerated painting this returns false and does nothing. + virtual bool OnNativeWidgetPaintAccelerated( + const gfx::Rect& dirty_region) = 0; + + // Paints the rootview in the canvas. This will also refresh the compositor + // tree if necessary when accelerated painting is enabled. + virtual void OnNativeWidgetPaint(gfx::Canvas* canvas) = 0; + + // Returns the non-client component (see ui/base/hit_test.h) containing + // |point|, in client coordinates. + virtual int GetNonClientComponent(const gfx::Point& point) = 0; + + // Mouse and key event handlers. + virtual bool OnKeyEvent(const KeyEvent& event) = 0; + virtual bool OnMouseEvent(const MouseEvent& event) = 0; + virtual void OnMouseCaptureLost() = 0; + virtual ui::TouchStatus OnTouchEvent(const TouchEvent& event) = 0; + + // Runs the specified native command. Returns true if the command is handled. + virtual bool ExecuteCommand(int command_id) = 0; + + // Returns the input method of the widget this delegate is associated with. + // Note that this does not use the top level widget, so may return NULL + // if the widget doesn't have input method. + virtual InputMethod* GetInputMethodDirect() = 0; + + // + virtual Widget* AsWidget() = 0; + virtual const Widget* AsWidget() const = 0; +}; + +} // namespace internal +} // namespace views + +#endif // VIEWS_WIDGET_NATIVE_WIDGET_DELEGATE_H_ diff --git a/views/widget/native_widget_gtk.cc b/views/widget/native_widget_gtk.cc new file mode 100644 index 0000000..c7ef5e2 --- /dev/null +++ b/views/widget/native_widget_gtk.cc @@ -0,0 +1,2312 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/native_widget_gtk.h" + +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/extensions/shape.h> +#include <gdk/gdk.h> +#include <gdk/gdkx.h> + +#include <set> +#include <vector> + +#include "base/auto_reset.h" +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/message_loop.h" +#include "base/utf_string_conversions.h" +#include "ui/base/dragdrop/drag_drop_types.h" +#include "ui/base/dragdrop/os_exchange_data.h" +#include "ui/base/dragdrop/os_exchange_data_provider_gtk.h" +#include "ui/base/gtk/g_object_destructor_filo.h" +#include "ui/base/gtk/gtk_signal_registrar.h" +#include "ui/base/gtk/gtk_windowing.h" +#include "ui/base/gtk/scoped_handle_gtk.h" +#include "ui/base/hit_test.h" +#include "ui/base/x/x11_util.h" +#include "ui/gfx/canvas_skia_paint.h" +#include "ui/gfx/compositor/compositor.h" +#include "ui/gfx/gtk_util.h" +#include "ui/gfx/path.h" +#include "ui/gfx/screen.h" +#include "ui/views/bubble/bubble_delegate.h" +#include "ui/views/focus/view_storage.h" +#include "ui/views/ime/input_method_gtk.h" +#include "views/controls/textfield/native_textfield_views.h" +#include "views/views_delegate.h" +#include "views/widget/drop_target_gtk.h" +#include "views/widget/gtk_views_fixed.h" +#include "views/widget/gtk_views_window.h" +#include "views/widget/root_view.h" +#include "views/widget/widget_delegate.h" + +#if defined(TOUCH_UI) +#include "ui/base/touch/touch_factory.h" +#include "views/widget/tooltip_manager_views.h" +#else +#include "views/widget/tooltip_manager_gtk.h" +#endif + +#if defined(HAVE_IBUS) +#include "ui/views/ime/input_method_ibus.h" +#endif + +using ui::OSExchangeData; +using ui::OSExchangeDataProviderGtk; +using ui::ActiveWindowWatcherX; + +namespace views { + +namespace { + +// Links the GtkWidget to its NativeWidget. +const char* const kNativeWidgetKey = "__VIEWS_NATIVE_WIDGET__"; + +// A g_object data key to associate a CompositePainter object to a GtkWidget. +const char* kCompositePainterKey = "__VIEWS_COMPOSITE_PAINTER__"; + +// A g_object data key to associate the flag whether or not the widget +// is composited to a GtkWidget. gtk_widget_is_composited simply tells +// if x11 supports composition and cannot be used to tell if given widget +// is composited. +const char* kCompositeEnabledKey = "__VIEWS_COMPOSITE_ENABLED__"; + +// A g_object data key to associate the expose handler id that is +// used to remove FREEZE_UPDATE property on the window. +const char* kExposeHandlerIdKey = "__VIEWS_EXPOSE_HANDLER_ID__"; + +// CompositePainter draws a composited child widgets image into its +// drawing area. This object is created at most once for a widget and kept +// until the widget is destroyed. +class CompositePainter { + public: + explicit CompositePainter(GtkWidget* parent) + : parent_object_(G_OBJECT(parent)) { + handler_id_ = g_signal_connect_after( + parent_object_, "expose_event", G_CALLBACK(OnCompositePaint), NULL); + } + + static void AddCompositePainter(GtkWidget* widget) { + CompositePainter* painter = static_cast<CompositePainter*>( + g_object_get_data(G_OBJECT(widget), kCompositePainterKey)); + if (!painter) { + g_object_set_data(G_OBJECT(widget), kCompositePainterKey, + new CompositePainter(widget)); + g_signal_connect(widget, "destroy", + G_CALLBACK(&DestroyPainter), NULL); + } + } + + // Set the composition flag. + static void SetComposited(GtkWidget* widget) { + g_object_set_data(G_OBJECT(widget), kCompositeEnabledKey, + const_cast<char*>("")); + } + + // Returns true if the |widget| is composited and ready to be drawn. + static bool IsComposited(GtkWidget* widget) { + return g_object_get_data(G_OBJECT(widget), kCompositeEnabledKey) != NULL; + } + + private: + virtual ~CompositePainter() {} + + // Composes a image from one child. + static void CompositeChildWidget(GtkWidget* child, gpointer data) { + GdkEventExpose* event = static_cast<GdkEventExpose*>(data); + GtkWidget* parent = gtk_widget_get_parent(child); + DCHECK(parent); + if (IsComposited(child)) { + cairo_t* cr = gdk_cairo_create(parent->window); + gdk_cairo_set_source_pixmap(cr, child->window, + child->allocation.x, + child->allocation.y); + GdkRegion* region = gdk_region_rectangle(&child->allocation); + gdk_region_intersect(region, event->region); + gdk_cairo_region(cr, region); + cairo_clip(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_paint(cr); + cairo_destroy(cr); + } + } + + // Expose-event handler that compose & draws children's image into + // the |parent|'s drawing area. + static gboolean OnCompositePaint(GtkWidget* parent, GdkEventExpose* event) { + gtk_container_foreach(GTK_CONTAINER(parent), + CompositeChildWidget, + event); + return false; + } + + static void DestroyPainter(GtkWidget* object) { + CompositePainter* painter = reinterpret_cast<CompositePainter*>( + g_object_get_data(G_OBJECT(object), kCompositePainterKey)); + DCHECK(painter); + delete painter; + } + + GObject* parent_object_; + gulong handler_id_; + + DISALLOW_COPY_AND_ASSIGN(CompositePainter); +}; + +void EnumerateChildWidgetsForNativeWidgets(GtkWidget* child_widget, + gpointer param) { + // Walk child widgets, if necessary. + if (GTK_IS_CONTAINER(child_widget)) { + gtk_container_foreach(GTK_CONTAINER(child_widget), + EnumerateChildWidgetsForNativeWidgets, + param); + } + + Widget* widget = Widget::GetWidgetForNativeView(child_widget); + if (widget) { + Widget::Widgets* widgets = reinterpret_cast<Widget::Widgets*>(param); + widgets->insert(widget); + } +} + +void RemoveExposeHandlerIfExists(GtkWidget* widget) { + gulong id = reinterpret_cast<gulong>(g_object_get_data(G_OBJECT(widget), + kExposeHandlerIdKey)); + if (id) { + g_signal_handler_disconnect(G_OBJECT(widget), id); + g_object_set_data(G_OBJECT(widget), kExposeHandlerIdKey, 0); + } +} + +GtkWindowType WindowTypeToGtkWindowType(Widget::InitParams::Type type) { + switch (type) { + case Widget::InitParams::TYPE_BUBBLE: + case Widget::InitParams::TYPE_WINDOW: + case Widget::InitParams::TYPE_WINDOW_FRAMELESS: + return GTK_WINDOW_TOPLEVEL; + default: + return GTK_WINDOW_POPUP; + } + NOTREACHED(); + return GTK_WINDOW_TOPLEVEL; +} + +// Converts a Windows-style hit test result code into a GDK window edge. +GdkWindowEdge HitTestCodeToGDKWindowEdge(int hittest_code) { + switch (hittest_code) { + case HTBOTTOM: + return GDK_WINDOW_EDGE_SOUTH; + case HTBOTTOMLEFT: + return GDK_WINDOW_EDGE_SOUTH_WEST; + case HTBOTTOMRIGHT: + case HTGROWBOX: + return GDK_WINDOW_EDGE_SOUTH_EAST; + case HTLEFT: + return GDK_WINDOW_EDGE_WEST; + case HTRIGHT: + return GDK_WINDOW_EDGE_EAST; + case HTTOP: + return GDK_WINDOW_EDGE_NORTH; + case HTTOPLEFT: + return GDK_WINDOW_EDGE_NORTH_WEST; + case HTTOPRIGHT: + return GDK_WINDOW_EDGE_NORTH_EAST; + default: + NOTREACHED(); + break; + } + // Default to something defaultish. + return HitTestCodeToGDKWindowEdge(HTGROWBOX); +} + +// Converts a Windows-style hit test result code into a GDK cursor type. +GdkCursorType HitTestCodeToGdkCursorType(int hittest_code) { + switch (hittest_code) { + case HTBOTTOM: + return GDK_BOTTOM_SIDE; + case HTBOTTOMLEFT: + return GDK_BOTTOM_LEFT_CORNER; + case HTBOTTOMRIGHT: + case HTGROWBOX: + return GDK_BOTTOM_RIGHT_CORNER; + case HTLEFT: + return GDK_LEFT_SIDE; + case HTRIGHT: + return GDK_RIGHT_SIDE; + case HTTOP: + return GDK_TOP_SIDE; + case HTTOPLEFT: + return GDK_TOP_LEFT_CORNER; + case HTTOPRIGHT: + return GDK_TOP_RIGHT_CORNER; + default: + break; + } + // Default to something defaultish. + return GDK_LEFT_PTR; +} + +} // namespace + +// During drag and drop GTK sends a drag-leave during a drop. This means we +// have no way to tell the difference between a normal drag leave and a drop. +// To work around that we listen for DROP_START, then ignore the subsequent +// drag-leave that GTK generates. +class NativeWidgetGtk::DropObserver : public MessageLoopForUI::Observer { + public: + DropObserver() {} + + static DropObserver* GetInstance() { + return Singleton<DropObserver>::get(); + } +#if defined(TOUCH_UI) + virtual base::EventStatus WillProcessEvent( + const base::NativeEvent& event) OVERRIDE { + return base::EVENT_CONTINUE; + } + + virtual void DidProcessEvent(const base::NativeEvent& event) OVERRIDE { + } +#else + virtual void WillProcessEvent(GdkEvent* event) { + if (event->type == GDK_DROP_START) { + NativeWidgetGtk* widget = GetNativeWidgetGtkForEvent(event); + if (widget) + widget->ignore_drag_leave_ = true; + } + } + + virtual void DidProcessEvent(GdkEvent* event) { + } +#endif + + private: + NativeWidgetGtk* GetNativeWidgetGtkForEvent(GdkEvent* event) { + GtkWidget* gtk_widget = gtk_get_event_widget(event); + if (!gtk_widget) + return NULL; + + return static_cast<NativeWidgetGtk*>( + internal::NativeWidgetPrivate::GetNativeWidgetForNativeView( + gtk_widget)); + } + + DISALLOW_COPY_AND_ASSIGN(DropObserver); +}; + +// Returns the position of a widget on screen. +static void GetWidgetPositionOnScreen(GtkWidget* widget, int* x, int *y) { + // First get the root window. + GtkWidget* root = widget; + while (root && !GTK_IS_WINDOW(root)) { + root = gtk_widget_get_parent(root); + } + if (!root) { + // If root is null we're not parented. Return 0x0 and assume the caller will + // query again when we're parented. + *x = *y = 0; + return; + } + // Translate the coordinate from widget to root window. + gtk_widget_translate_coordinates(widget, root, 0, 0, x, y); + // Then adjust the position with the position of the root window. + int window_x, window_y; + gtk_window_get_position(GTK_WINDOW(root), &window_x, &window_y); + *x += window_x; + *y += window_y; +} + +// "expose-event" handler of drag icon widget that renders drag image pixbuf. +static gboolean DragIconWidgetPaint(GtkWidget* widget, + GdkEventExpose* event, + gpointer data) { + GdkPixbuf* pixbuf = reinterpret_cast<GdkPixbuf*>(data); + + cairo_t* cr = gdk_cairo_create(widget->window); + + gdk_cairo_region(cr, event->region); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + gdk_cairo_set_source_pixbuf(cr, pixbuf, 0.0, 0.0); + cairo_paint(cr); + + cairo_destroy(cr); + return true; +} + +// Creates a drag icon widget that draws drag_image. +static GtkWidget* CreateDragIconWidget(GdkPixbuf* drag_image) { + GdkColormap* rgba_colormap = + gdk_screen_get_rgba_colormap(gdk_screen_get_default()); + if (!rgba_colormap) + return NULL; + + GtkWidget* drag_widget = gtk_window_new(GTK_WINDOW_POPUP); + + gtk_widget_set_colormap(drag_widget, rgba_colormap); + gtk_widget_set_app_paintable(drag_widget, true); + gtk_widget_set_size_request(drag_widget, + gdk_pixbuf_get_width(drag_image), + gdk_pixbuf_get_height(drag_image)); + + g_signal_connect(G_OBJECT(drag_widget), "expose-event", + G_CALLBACK(&DragIconWidgetPaint), drag_image); + return drag_widget; +} + +// static +GtkWidget* NativeWidgetGtk::null_parent_ = NULL; + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetGtk, public: + +NativeWidgetGtk::NativeWidgetGtk(internal::NativeWidgetDelegate* delegate) + : delegate_(delegate), + widget_(NULL), + window_contents_(NULL), + child_(false), + ALLOW_THIS_IN_INITIALIZER_LIST(close_widget_factory_(this)), + ownership_(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET), + transparent_(false), + ignore_events_(false), + ignore_drag_leave_(false), + opacity_(255), + drag_data_(NULL), + window_state_(GDK_WINDOW_STATE_WITHDRAWN), + is_active_(false), + transient_to_parent_(false), + got_initial_focus_in_(false), + has_focus_(false), + always_on_top_(false), + is_double_buffered_(false), + should_handle_menu_key_release_(false), + dragged_view_(NULL), + painted_(false), + has_pointer_grab_(false), + has_keyboard_grab_(false), + grab_notify_signal_id_(0), + is_menu_(false), + signal_registrar_(new ui::GtkSignalRegistrar) { +#if defined(TOUCH_UI) + // Make sure the touch factory is initialized so that it can setup XInput2 for + // the widget. + ui::TouchFactory::GetInstance(); +#endif + static bool installed_message_loop_observer = false; + if (!installed_message_loop_observer) { + installed_message_loop_observer = true; + MessageLoopForUI* loop = MessageLoopForUI::current(); + if (loop) + loop->AddObserver(DropObserver::GetInstance()); + } +} + +NativeWidgetGtk::~NativeWidgetGtk() { + // We need to delete the input method before calling DestroyRootView(), + // because it'll set focus_manager_ to NULL. + if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) { + DCHECK(widget_ == NULL); + delete delegate_; + } else { + // Disconnect from GObjectDestructorFILO because we're + // deleting the NativeWidgetGtk. + bool has_widget = !!widget_; + if (has_widget) + ui::GObjectDestructorFILO::GetInstance()->Disconnect( + G_OBJECT(widget_), &OnDestroyedThunk, this); + CloseNow(); + // Call OnNativeWidgetDestroyed because we're not calling + // OnDestroyedThunk + if (has_widget) + delegate_->OnNativeWidgetDestroyed(); + } +} + +GtkWindow* NativeWidgetGtk::GetTransientParent() const { + return (!child_ && widget_) ? + gtk_window_get_transient_for(GTK_WINDOW(widget_)) : NULL; +} + +bool NativeWidgetGtk::MakeTransparent() { + // Transparency can only be enabled only if we haven't realized the widget. + DCHECK(!widget_); + + if (!gdk_screen_is_composited(gdk_screen_get_default())) { + // Transparency is only supported for compositing window managers. + // NOTE: there's a race during ChromeOS startup such that X might think + // compositing isn't supported. We ignore it if the wm says compositing + // isn't supported. + DLOG(WARNING) << "compositing not supported; allowing anyway"; + } + + if (!gdk_screen_get_rgba_colormap(gdk_screen_get_default())) { + // We need rgba to make the window transparent. + return false; + } + + transparent_ = true; + return true; +} + +void NativeWidgetGtk::EnableDoubleBuffer(bool enabled) { + is_double_buffered_ = enabled; + if (window_contents_) { + if (is_double_buffered_) + GTK_WIDGET_SET_FLAGS(window_contents_, GTK_DOUBLE_BUFFERED); + else + GTK_WIDGET_UNSET_FLAGS(window_contents_, GTK_DOUBLE_BUFFERED); + } +} + +void NativeWidgetGtk::AddChild(GtkWidget* child) { + gtk_container_add(GTK_CONTAINER(window_contents_), child); +} + +void NativeWidgetGtk::RemoveChild(GtkWidget* child) { + // We can be called after the contents widget has been destroyed, e.g. any + // NativeViewHost not removed from the view hierarchy before the window is + // closed. + if (GTK_IS_CONTAINER(window_contents_)) { + gtk_container_remove(GTK_CONTAINER(window_contents_), child); + gtk_views_fixed_set_widget_size(child, 0, 0); + } +} + +void NativeWidgetGtk::ReparentChild(GtkWidget* child) { + gtk_widget_reparent(child, window_contents_); +} + +void NativeWidgetGtk::PositionChild(GtkWidget* child, int x, int y, int w, + int h) { + gtk_views_fixed_set_widget_size(child, w, h); + gtk_fixed_move(GTK_FIXED(window_contents_), child, x, y); +} + +void NativeWidgetGtk::DoDrag(const OSExchangeData& data, int operation) { + const OSExchangeDataProviderGtk& data_provider = + static_cast<const OSExchangeDataProviderGtk&>(data.provider()); + GtkTargetList* targets = data_provider.GetTargetList(); + GdkEvent* current_event = gtk_get_current_event(); + const OSExchangeDataProviderGtk& provider( + static_cast<const OSExchangeDataProviderGtk&>(data.provider())); + + GdkDragContext* context = gtk_drag_begin( + window_contents_, + targets, + static_cast<GdkDragAction>( + ui::DragDropTypes::DragOperationToGdkDragAction(operation)), + 1, + current_event); + + GtkWidget* drag_icon_widget = NULL; + + // Set the drag image if one was supplied. + if (provider.drag_image()) { + drag_icon_widget = CreateDragIconWidget(provider.drag_image()); + if (drag_icon_widget) { + // Use a widget as the drag icon when compositing is enabled for proper + // transparency handling. + g_object_ref(provider.drag_image()); + gtk_drag_set_icon_widget(context, + drag_icon_widget, + provider.cursor_offset().x(), + provider.cursor_offset().y()); + } else { + gtk_drag_set_icon_pixbuf(context, + provider.drag_image(), + provider.cursor_offset().x(), + provider.cursor_offset().y()); + } + } + + if (current_event) + gdk_event_free(current_event); + gtk_target_list_unref(targets); + + drag_data_ = &data_provider; + + // Block the caller until drag is done by running a nested message loop. + MessageLoopForUI::current()->RunWithDispatcher(NULL); + + drag_data_ = NULL; + + if (drag_icon_widget) { + gtk_widget_destroy(drag_icon_widget); + g_object_unref(provider.drag_image()); + } +} + +void NativeWidgetGtk::OnActiveChanged() { + delegate_->OnNativeWidgetActivationChanged(IsActive()); +} + +void NativeWidgetGtk::ResetDropTarget() { + ignore_drag_leave_ = false; + drop_target_.reset(NULL); +} + +void NativeWidgetGtk::GetRequestedSize(gfx::Size* out) const { + int width, height; + if (GTK_IS_VIEWS_FIXED(widget_) && + gtk_views_fixed_get_widget_size(GetNativeView(), &width, &height)) { + out->SetSize(width, height); + } else { + GtkRequisition requisition; + gtk_widget_get_child_requisition(GetNativeView(), &requisition); + out->SetSize(requisition.width, requisition.height); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetGtk, ActiveWindowWatcherX::Observer implementation: + +void NativeWidgetGtk::ActiveWindowChanged(GdkWindow* active_window) { + if (!GetNativeView()) + return; + + bool was_active = IsActive(); + is_active_ = (active_window == GTK_WIDGET(GetNativeView())->window); + if (!is_active_ && active_window && !child_) { + // We're not active, but the force the window to be rendered as active if + // a child window is transient to us. + gpointer data = NULL; + gdk_window_get_user_data(active_window, &data); + GtkWidget* widget = reinterpret_cast<GtkWidget*>(data); + is_active_ = + (widget && GTK_IS_WINDOW(widget) && + gtk_window_get_transient_for(GTK_WINDOW(widget)) == GTK_WINDOW( + widget_)); + } + if (was_active != IsActive()) { + OnActiveChanged(); + GetWidget()->GetRootView()->SchedulePaint(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetGtk implementation: + +bool NativeWidgetGtk::HandleKeyboardEvent(const KeyEvent& key) { + if (!GetWidget()->GetFocusManager()) + return false; + + const int key_code = key.key_code(); + bool handled = false; + + // Always reset |should_handle_menu_key_release_| unless we are handling a + // VKEY_MENU key release event. It ensures that VKEY_MENU accelerator can only + // be activated when handling a VKEY_MENU key release event which is preceded + // by an un-handled VKEY_MENU key press event. + if (key_code != ui::VKEY_MENU || key.type() != ui::ET_KEY_RELEASED) + should_handle_menu_key_release_ = false; + + if (key.type() == ui::ET_KEY_PRESSED) { + // VKEY_MENU is triggered by key release event. + // FocusManager::OnKeyEvent() returns false when the key has been consumed. + if (key_code != ui::VKEY_MENU) + handled = !GetWidget()->GetFocusManager()->OnKeyEvent(key); + else + should_handle_menu_key_release_ = true; + } else if (key_code == ui::VKEY_MENU && should_handle_menu_key_release_ && + (key.flags() & ~ui::EF_ALT_DOWN) == 0) { + // Trigger VKEY_MENU when only this key is pressed and released, and both + // press and release events are not handled by others. + ui::Accelerator accelerator(ui::VKEY_MENU, false, false, false); + handled = GetWidget()->GetFocusManager()->ProcessAccelerator(accelerator); + } + + return handled; +} + +bool NativeWidgetGtk::SuppressFreezeUpdates() { + if (!painted_) { + painted_ = true; + return true; + } + return false; +} + +// static +void NativeWidgetGtk::UpdateFreezeUpdatesProperty(GtkWindow* window, + bool enable) { + if (!GTK_WIDGET_REALIZED(GTK_WIDGET(window))) + gtk_widget_realize(GTK_WIDGET(window)); + GdkWindow* gdk_window = GTK_WIDGET(window)->window; + + static GdkAtom freeze_atom_ = + gdk_atom_intern("_CHROME_FREEZE_UPDATES", FALSE); + if (enable) { + VLOG(1) << "setting FREEZE UPDATES property. xid=" << + GDK_WINDOW_XID(gdk_window); + int32 val = 1; + gdk_property_change(gdk_window, + freeze_atom_, + freeze_atom_, + 32, + GDK_PROP_MODE_REPLACE, + reinterpret_cast<const guchar*>(&val), + 1); + } else { + VLOG(1) << "deleting FREEZE UPDATES property. xid=" << + GDK_WINDOW_XID(gdk_window); + gdk_property_delete(gdk_window, freeze_atom_); + } +} + +// static +void NativeWidgetGtk::RegisterChildExposeHandler(GtkWidget* child) { + RemoveExposeHandlerIfExists(child); + gulong id = g_signal_connect_after(child, "expose-event", + G_CALLBACK(&ChildExposeHandler), NULL); + g_object_set_data(G_OBJECT(child), kExposeHandlerIdKey, + reinterpret_cast<void*>(id)); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetGtk, NativeWidget implementation: + +void NativeWidgetGtk::InitNativeWidget(const Widget::InitParams& params) { + SetInitParams(params); + + Widget::InitParams modified_params = params; + if (params.parent_widget) { + NativeWidgetGtk* parent_gtk = + static_cast<NativeWidgetGtk*>(params.parent_widget->native_widget()); + modified_params.parent = child_ ? parent_gtk->window_contents() + : params.parent_widget->GetNativeView(); + } + + if (!child_) + ActiveWindowWatcherX::AddObserver(this); + + // Make container here. + CreateGtkWidget(modified_params); + + if (params.type == Widget::InitParams::TYPE_MENU) { + gtk_window_set_destroy_with_parent(GTK_WINDOW(GetNativeView()), TRUE); + gtk_window_set_type_hint(GTK_WINDOW(GetNativeView()), + GDK_WINDOW_TYPE_HINT_MENU); + } + + if (View::get_use_acceleration_when_possible()) { + if (ui::Compositor::compositor_factory()) { + compositor_ = (*ui::Compositor::compositor_factory())(this); + } else { + gint width, height; + gdk_drawable_get_size(window_contents_->window, &width, &height); + compositor_ = ui::Compositor::Create(this, + GDK_WINDOW_XID(window_contents_->window), + gfx::Size(width, height)); + } + if (compositor_.get()) { + View* root_view = delegate_->AsWidget()->GetRootView(); + root_view->SetPaintToLayer(true); + compositor_->SetRootLayer(root_view->layer()); + root_view->SetFillsBoundsOpaquely(!transparent_); + } + } + + delegate_->OnNativeWidgetCreated(); + + if (opacity_ != 255) + SetOpacity(opacity_); + + // Make sure we receive our motion events. + + // In general we register most events on the parent of all widgets. At a + // minimum we need painting to happen on the parent (otherwise painting + // doesn't work at all), and similarly we need mouse release events on the + // parent as windows don't get mouse releases. + gtk_widget_add_events(window_contents_, + 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); + + signal_registrar_->ConnectAfter(G_OBJECT(window_contents_), "size_request", + G_CALLBACK(&OnSizeRequestThunk), this); + signal_registrar_->ConnectAfter(G_OBJECT(window_contents_), "size_allocate", + G_CALLBACK(&OnSizeAllocateThunk), this); + gtk_widget_set_app_paintable(window_contents_, true); + signal_registrar_->Connect(window_contents_, "expose_event", + G_CALLBACK(&OnPaintThunk), this); + signal_registrar_->Connect(window_contents_, "enter_notify_event", + G_CALLBACK(&OnEnterNotifyThunk), this); + signal_registrar_->Connect(window_contents_, "leave_notify_event", + G_CALLBACK(&OnLeaveNotifyThunk), this); + signal_registrar_->Connect(window_contents_, "motion_notify_event", + G_CALLBACK(&OnMotionNotifyThunk), this); + signal_registrar_->Connect(window_contents_, "button_press_event", + G_CALLBACK(&OnButtonPressThunk), this); + signal_registrar_->Connect(window_contents_, "button_release_event", + G_CALLBACK(&OnButtonReleaseThunk), this); + signal_registrar_->Connect(window_contents_, "grab_broken_event", + G_CALLBACK(&OnGrabBrokeEventThunk), this); + signal_registrar_->Connect(window_contents_, "scroll_event", + G_CALLBACK(&OnScrollThunk), this); + signal_registrar_->Connect(window_contents_, "visibility_notify_event", + G_CALLBACK(&OnVisibilityNotifyThunk), this); + + // In order to receive notification when the window is no longer the front + // window, we need to install these on the widget. + // NOTE: this doesn't work with focus follows mouse. + signal_registrar_->Connect(widget_, "focus_in_event", + G_CALLBACK(&OnFocusInThunk), this); + signal_registrar_->Connect(widget_, "focus_out_event", + G_CALLBACK(&OnFocusOutThunk), this); + signal_registrar_->Connect(widget_, "destroy", + G_CALLBACK(&OnDestroyThunk), this); + signal_registrar_->Connect(widget_, "show", + G_CALLBACK(&OnShowThunk), this); + signal_registrar_->Connect(widget_, "map", + G_CALLBACK(&OnMapThunk), this); + signal_registrar_->Connect(widget_, "hide", + G_CALLBACK(&OnHideThunk), this); + signal_registrar_->Connect(widget_, "configure-event", + G_CALLBACK(&OnConfigureEventThunk), this); + + // Views/FocusManager (re)sets the focus to the root window, + // so we need to connect signal handlers to the gtk window. + // See views::Views::Focus and views::FocusManager::ClearNativeFocus + // for more details. + signal_registrar_->Connect(widget_, "key_press_event", + G_CALLBACK(&OnEventKeyThunk), this); + signal_registrar_->Connect(widget_, "key_release_event", + G_CALLBACK(&OnEventKeyThunk), this); + + // Drag and drop. + gtk_drag_dest_set(window_contents_, static_cast<GtkDestDefaults>(0), + NULL, 0, GDK_ACTION_COPY); + signal_registrar_->Connect(window_contents_, "drag_motion", + G_CALLBACK(&OnDragMotionThunk), this); + signal_registrar_->Connect(window_contents_, "drag_data_received", + G_CALLBACK(&OnDragDataReceivedThunk), this); + signal_registrar_->Connect(window_contents_, "drag_drop", + G_CALLBACK(&OnDragDropThunk), this); + signal_registrar_->Connect(window_contents_, "drag_leave", + G_CALLBACK(&OnDragLeaveThunk), this); + signal_registrar_->Connect(window_contents_, "drag_data_get", + G_CALLBACK(&OnDragDataGetThunk), this); + signal_registrar_->Connect(window_contents_, "drag_end", + G_CALLBACK(&OnDragEndThunk), this); + signal_registrar_->Connect(window_contents_, "drag_failed", + G_CALLBACK(&OnDragFailedThunk), this); + signal_registrar_->Connect(G_OBJECT(widget_), "window-state-event", + G_CALLBACK(&OnWindowStateEventThunk), this); + + ui::GObjectDestructorFILO::GetInstance()->Connect( + G_OBJECT(widget_), &OnDestroyedThunk, this); + +#if defined(TOUCH_UI) + if (params.type != Widget::InitParams::TYPE_TOOLTIP) { + views::TooltipManagerViews* manager = new views::TooltipManagerViews( + static_cast<internal::RootView*>(GetWidget()->GetRootView())); + tooltip_manager_.reset(manager); + } +#else + tooltip_manager_.reset(new TooltipManagerGtk(this)); +#endif + + // Register for tooltips. + g_object_set(G_OBJECT(window_contents_), "has-tooltip", TRUE, NULL); + signal_registrar_->Connect(window_contents_, "query_tooltip", + G_CALLBACK(&OnQueryTooltipThunk), this); + + if (child_) { + if (modified_params.parent) + SetBounds(params.bounds); + } else { + gtk_widget_add_events(widget_, + GDK_STRUCTURE_MASK); + if (params.bounds.width() > 0 && params.bounds.height() > 0) + gtk_window_resize(GTK_WINDOW(widget_), params.bounds.width(), + params.bounds.height()); + gtk_window_move(GTK_WINDOW(widget_), params.bounds.x(), params.bounds.y()); + } +} + +NonClientFrameView* NativeWidgetGtk::CreateNonClientFrameView() { + return NULL; +} + +void NativeWidgetGtk::UpdateFrameAfterFrameChange() { + // We currently don't support different frame types on Gtk, so we don't + // need to implement this. + NOTIMPLEMENTED(); +} + +bool NativeWidgetGtk::ShouldUseNativeFrame() const { + return false; +} + +void NativeWidgetGtk::FrameTypeChanged() { + // This is called when the Theme has changed, so forward the event to the root + // widget. + GetWidget()->ThemeChanged(); + GetWidget()->GetRootView()->SchedulePaint(); +} + +Widget* NativeWidgetGtk::GetWidget() { + return delegate_->AsWidget(); +} + +const Widget* NativeWidgetGtk::GetWidget() const { + return delegate_->AsWidget(); +} + +gfx::NativeView NativeWidgetGtk::GetNativeView() const { + return widget_; +} + +gfx::NativeWindow NativeWidgetGtk::GetNativeWindow() const { + return child_ ? NULL : GTK_WINDOW(widget_); +} + +Widget* NativeWidgetGtk::GetTopLevelWidget() { + NativeWidgetPrivate* native_widget = GetTopLevelNativeWidget(GetNativeView()); + return native_widget ? native_widget->GetWidget() : NULL; +} + +const ui::Compositor* NativeWidgetGtk::GetCompositor() const { + return compositor_.get(); +} + +ui::Compositor* NativeWidgetGtk::GetCompositor() { + return compositor_.get(); +} + +void NativeWidgetGtk::CalculateOffsetToAncestorWithLayer( + gfx::Point* offset, + ui::Layer** layer_parent) { +} + +void NativeWidgetGtk::ReorderLayers() { +} + +void NativeWidgetGtk::ViewRemoved(View* view) { + if (drop_target_.get()) + drop_target_->ResetTargetViewIfEquals(view); +} + +void NativeWidgetGtk::SetNativeWindowProperty(const char* name, void* value) { + g_object_set_data(G_OBJECT(widget_), name, value); +} + +void* NativeWidgetGtk::GetNativeWindowProperty(const char* name) const { + return g_object_get_data(G_OBJECT(widget_), name); +} + +TooltipManager* NativeWidgetGtk::GetTooltipManager() const { + return tooltip_manager_.get(); +} + +bool NativeWidgetGtk::IsScreenReaderActive() const { + return false; +} + +void NativeWidgetGtk::SendNativeAccessibilityEvent( + View* view, + ui::AccessibilityTypes::Event event_type) { + // In the future if we add native GTK accessibility support, the + // notification should be sent here. +} + +void NativeWidgetGtk::SetMouseCapture() { + DCHECK(!HasMouseCapture()); + + // Release the current grab. + GtkWidget* current_grab_window = gtk_grab_get_current(); + if (current_grab_window) + gtk_grab_remove(current_grab_window); + + if (is_menu_ && gdk_pointer_is_grabbed()) + gdk_pointer_ungrab(GDK_CURRENT_TIME); + + // Make sure all app mouse/keyboard events are targeted at us only. + gtk_grab_add(window_contents_); + if (gtk_grab_get_current() == window_contents_ && !grab_notify_signal_id_) { + // "grab_notify" is sent any time the grab changes. We only care about grab + // changes when we have done a grab. + grab_notify_signal_id_ = g_signal_connect( + window_contents_, "grab_notify", G_CALLBACK(&OnGrabNotifyThunk), this); + } + + if (is_menu_) { + // For menus we do a pointer grab too. This ensures we get mouse events from + // other apps. In theory we should do this for all widget types, but doing + // so leads to gdk_pointer_grab randomly returning GDK_GRAB_ALREADY_GRABBED. + GdkGrabStatus pointer_grab_status = + gdk_pointer_grab(window_contents()->window, FALSE, + static_cast<GdkEventMask>( + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK), + NULL, NULL, GDK_CURRENT_TIME); + // NOTE: technically grab may fail. We may want to try and continue on in + // that case. + DCHECK_EQ(GDK_GRAB_SUCCESS, pointer_grab_status); + has_pointer_grab_ = pointer_grab_status == GDK_GRAB_SUCCESS; + +#if defined(TOUCH_UI) + ::Window window = GDK_WINDOW_XID(window_contents()->window); + Display* display = GDK_WINDOW_XDISPLAY(window_contents()->window); + bool xi2grab = + ui::TouchFactory::GetInstance()->GrabTouchDevices(display, window); + // xi2grab should always succeed if has_pointer_grab_ succeeded. + DCHECK(xi2grab); + has_pointer_grab_ = has_pointer_grab_ && xi2grab; +#endif + } +} + +void NativeWidgetGtk::ReleaseMouseCapture() { + bool delegate_lost_capture = HasMouseCapture(); + if (GTK_WIDGET_HAS_GRAB(window_contents_)) + gtk_grab_remove(window_contents_); + if (grab_notify_signal_id_) { + g_signal_handler_disconnect(window_contents_, grab_notify_signal_id_); + grab_notify_signal_id_ = 0; + } + if (has_pointer_grab_) { + has_pointer_grab_ = false; + gdk_pointer_ungrab(GDK_CURRENT_TIME); +#if defined(TOUCH_UI) + ui::TouchFactory::GetInstance()->UngrabTouchDevices( + GDK_WINDOW_XDISPLAY(window_contents()->window)); +#endif + } + if (delegate_lost_capture) + delegate_->OnMouseCaptureLost(); +} + +bool NativeWidgetGtk::HasMouseCapture() const { + return GTK_WIDGET_HAS_GRAB(window_contents_) || has_pointer_grab_; +} + +InputMethod* NativeWidgetGtk::CreateInputMethod() { + // Create input method when pure views is enabled but not on views desktop. + // TODO(suzhe): Always enable input method when we start to use + // RenderWidgetHostViewViews in normal ChromeOS. + if (views::Widget::IsPureViews()) { +#if defined(HAVE_IBUS) + InputMethod* input_method = + InputMethodIBus::IsInputMethodIBusEnabled() ? + static_cast<InputMethod*>(new InputMethodIBus(this)) : + static_cast<InputMethod*>(new InputMethodGtk(this)); +#else + InputMethod* input_method = new InputMethodGtk(this); +#endif + input_method->Init(GetWidget()); + return input_method; + } + // GTK's textfield will handle IME. + return NULL; +} + +void NativeWidgetGtk::CenterWindow(const gfx::Size& size) { + gfx::Rect center_rect; + + GtkWindow* parent = gtk_window_get_transient_for(GetNativeWindow()); + if (parent) { + // We have a parent window, center over it. + gint parent_x = 0; + gint parent_y = 0; + gtk_window_get_position(parent, &parent_x, &parent_y); + gint parent_w = 0; + gint parent_h = 0; + gtk_window_get_size(parent, &parent_w, &parent_h); + center_rect = gfx::Rect(parent_x, parent_y, parent_w, parent_h); + } else { + // We have no parent window, center over the screen. + center_rect = gfx::Screen::GetMonitorWorkAreaNearestWindow(GetNativeView()); + } + gfx::Rect bounds(center_rect.x() + (center_rect.width() - size.width()) / 2, + center_rect.y() + (center_rect.height() - size.height()) / 2, + size.width(), size.height()); + GetWidget()->SetBoundsConstrained(bounds); +} + +void NativeWidgetGtk::GetWindowPlacement( + gfx::Rect* bounds, + ui::WindowShowState* show_state) const { + // Do nothing for now. ChromeOS isn't yet saving window placement. +} + +void NativeWidgetGtk::SetWindowTitle(const string16& title) { + // We don't have a window title on ChromeOS (right now). +} + +void NativeWidgetGtk::SetWindowIcons(const SkBitmap& window_icon, + const SkBitmap& app_icon) { + // We don't have window icons on ChromeOS. +} + +void NativeWidgetGtk::SetAccessibleName(const string16& name) { +} + +void NativeWidgetGtk::SetAccessibleRole(ui::AccessibilityTypes::Role role) { +} + +void NativeWidgetGtk::SetAccessibleState(ui::AccessibilityTypes::State state) { +} + +void NativeWidgetGtk::BecomeModal() { + gtk_window_set_modal(GetNativeWindow(), true); +} + +gfx::Rect NativeWidgetGtk::GetWindowScreenBounds() const { + // Client == Window bounds on Gtk. + return GetClientAreaScreenBounds(); +} + +gfx::Rect NativeWidgetGtk::GetClientAreaScreenBounds() const { + // Due to timing we can get a request for bounds after Close(). + // TODO(beng): Figure out if this is bogus. + if (!widget_) + return gfx::Rect(size_); + + int x = 0, y = 0, w = 0, h = 0; + if (GTK_IS_WINDOW(widget_)) { + gtk_window_get_position(GTK_WINDOW(widget_), &x, &y); + // NOTE: this doesn't include frame decorations, but it should be good + // enough for our uses. + gtk_window_get_size(GTK_WINDOW(widget_), &w, &h); + } else { + GetWidgetPositionOnScreen(widget_, &x, &y); + w = widget_->allocation.width; + h = widget_->allocation.height; + } + return gfx::Rect(x, y, w, h); +} + +gfx::Rect NativeWidgetGtk::GetRestoredBounds() const { + // We currently don't support tiling, so this doesn't matter. + return GetWindowScreenBounds(); +} + +void NativeWidgetGtk::SetBounds(const gfx::Rect& bounds) { + if (child_) { + GtkWidget* parent = gtk_widget_get_parent(widget_); + if (GTK_IS_VIEWS_FIXED(parent)) { + NativeWidgetGtk* parent_widget = static_cast<NativeWidgetGtk*>( + internal::NativeWidgetPrivate::GetNativeWidgetForNativeView(parent)); + parent_widget->PositionChild(widget_, bounds.x(), bounds.y(), + bounds.width(), bounds.height()); + } else { + DCHECK(GTK_IS_FIXED(parent)) + << "Parent of NativeWidgetGtk has to be Fixed or ViewsFixed"; + // Just request the size if the parent is not NativeWidgetGtk but plain + // GtkFixed. NativeWidgetGtk does not know the minimum size so we assume + // the caller of the SetBounds knows exactly how big it wants to be. + gtk_widget_set_size_request(widget_, bounds.width(), bounds.height()); + if (parent != null_parent_) + gtk_fixed_move(GTK_FIXED(parent), widget_, bounds.x(), bounds.y()); + } + } else { + if (GTK_WIDGET_MAPPED(widget_)) { + // If the widget is mapped (on screen), we can move and resize with one + // call, which avoids two separate window manager steps. + gdk_window_move_resize(widget_->window, bounds.x(), bounds.y(), + bounds.width(), bounds.height()); + } + + // Always call gtk_window_move and gtk_window_resize so that GtkWindow's + // geometry info is up-to-date. + GtkWindow* gtk_window = GTK_WINDOW(widget_); + // TODO: this may need to set an initial size if not showing. + // TODO: need to constrain based on screen size. + if (!bounds.IsEmpty()) { + gtk_window_resize(gtk_window, bounds.width(), bounds.height()); + } + gtk_window_move(gtk_window, bounds.x(), bounds.y()); + } +} + +void NativeWidgetGtk::SetSize(const gfx::Size& size) { + if (child_) { + GtkWidget* parent = gtk_widget_get_parent(widget_); + if (GTK_IS_VIEWS_FIXED(parent)) { + gtk_views_fixed_set_widget_size(widget_, size.width(), size.height()); + } else { + gtk_widget_set_size_request(widget_, size.width(), size.height()); + } + } else { + if (GTK_WIDGET_MAPPED(widget_)) + gdk_window_resize(widget_->window, size.width(), size.height()); + GtkWindow* gtk_window = GTK_WINDOW(widget_); + if (!size.IsEmpty()) + gtk_window_resize(gtk_window, size.width(), size.height()); + } +} + +void NativeWidgetGtk::MoveAbove(gfx::NativeView native_view) { + ui::StackPopupWindow(GetNativeView(), native_view); +} + +void NativeWidgetGtk::MoveToTop() { + DCHECK(GTK_IS_WINDOW(GetNativeView())); + gtk_window_present(GTK_WINDOW(GetNativeView())); +} + +void NativeWidgetGtk::SetShape(gfx::NativeRegion region) { + if (widget_ && widget_->window) { + gdk_window_shape_combine_region(widget_->window, region, 0, 0); + gdk_region_destroy(region); + } +} + +void NativeWidgetGtk::Close() { + if (!widget_) + return; // No need to do anything. + + // Hide first. + Hide(); + if (!close_widget_factory_.HasWeakPtrs()) { + // And we delay the close just in case we're on the stack. + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&NativeWidgetGtk::CloseNow, + close_widget_factory_.GetWeakPtr())); + } +} + +void NativeWidgetGtk::CloseNow() { + if (widget_) { + gtk_widget_destroy(widget_); // Triggers OnDestroy(). + } +} + +void NativeWidgetGtk::EnableClose(bool enable) { + gtk_window_set_deletable(GetNativeWindow(), enable); +} + +void NativeWidgetGtk::Show() { + if (widget_) { + gtk_widget_show(widget_); + if (widget_->window) + gdk_window_raise(widget_->window); + } +} + +void NativeWidgetGtk::Hide() { + if (widget_) { + gtk_widget_hide(widget_); + if (widget_->window) + gdk_window_lower(widget_->window); + } +} + +void NativeWidgetGtk::ShowMaximizedWithBounds( + const gfx::Rect& restored_bounds) { + // TODO: when we add maximization support update this. + Show(); +} + +void NativeWidgetGtk::ShowWithWindowState(ui::WindowShowState show_state) { + // No concept of maximization (yet) on ChromeOS. + if (show_state == ui::SHOW_STATE_INACTIVE) + gtk_window_set_focus_on_map(GetNativeWindow(), false); + gtk_widget_show(GetNativeView()); +} + +bool NativeWidgetGtk::IsVisible() const { + return GTK_WIDGET_VISIBLE(GetNativeView()) && (GetWidget()->is_top_level() || + GetWidget()->GetTopLevelWidget()->IsVisible()); +} + +void NativeWidgetGtk::Activate() { + gtk_window_present(GetNativeWindow()); +} + +void NativeWidgetGtk::Deactivate() { + gdk_window_lower(GTK_WIDGET(GetNativeView())->window); +} + +bool NativeWidgetGtk::IsActive() const { + DCHECK(!child_); + return is_active_; +} + +void NativeWidgetGtk::SetAlwaysOnTop(bool on_top) { + DCHECK(!child_); + always_on_top_ = on_top; + if (widget_) + gtk_window_set_keep_above(GTK_WINDOW(widget_), on_top); +} + +void NativeWidgetGtk::Maximize() { +#if defined(TOUCH_UI) + // There may not be a window manager. So maximize ourselves: move to the + // top-left corner and resize to the entire bounds of the screen. + gfx::Rect screen = gfx::Screen::GetMonitorAreaNearestWindow(GetNativeView()); + gtk_window_move(GTK_WINDOW(GetNativeWindow()), screen.x(), screen.y()); + // TODO(backer): Remove this driver bug workaround once it is fixed. + gtk_window_resize(GTK_WINDOW(GetNativeWindow()), + screen.width() - 1, screen.height()); +#else + gtk_window_maximize(GetNativeWindow()); +#endif +} + +void NativeWidgetGtk::Minimize() { + gtk_window_iconify(GetNativeWindow()); +} + +bool NativeWidgetGtk::IsMaximized() const { + return window_state_ & GDK_WINDOW_STATE_MAXIMIZED; +} + +bool NativeWidgetGtk::IsMinimized() const { + return window_state_ & GDK_WINDOW_STATE_ICONIFIED; +} + +void NativeWidgetGtk::Restore() { + if (IsFullscreen()) { + SetFullscreen(false); + } else { + if (IsMaximized()) + gtk_window_unmaximize(GetNativeWindow()); + else if (IsMinimized()) + gtk_window_deiconify(GetNativeWindow()); + } +} + +void NativeWidgetGtk::SetFullscreen(bool fullscreen) { + if (fullscreen) + gtk_window_fullscreen(GetNativeWindow()); + else + gtk_window_unfullscreen(GetNativeWindow()); +} + +bool NativeWidgetGtk::IsFullscreen() const { + return window_state_ & GDK_WINDOW_STATE_FULLSCREEN; +} + +void NativeWidgetGtk::SetOpacity(unsigned char opacity) { + opacity_ = opacity; + if (widget_) { + // We can only set the opacity when the widget has been realized. + gdk_window_set_opacity(widget_->window, static_cast<gdouble>(opacity) / + static_cast<gdouble>(255)); + } +} + +void NativeWidgetGtk::SetUseDragFrame(bool use_drag_frame) { + NOTIMPLEMENTED(); +} + +bool NativeWidgetGtk::IsAccessibleWidget() const { + return false; +} + +void NativeWidgetGtk::RunShellDrag(View* view, + const ui::OSExchangeData& data, + int operation) { + DoDrag(data, operation); +} + +void NativeWidgetGtk::SchedulePaintInRect(const gfx::Rect& rect) { + // No need to schedule paint if + // 1) widget_ is NULL. This may happen because this instance may + // be deleted after the gtk widget has been destroyed (See OnDestroy()). + // 2) widget_ is not drawable (mapped and visible) + // 3) If it's never painted before. The first expose event will + // paint the area that has to be painted. + if (widget_ && GTK_WIDGET_DRAWABLE(widget_) && painted_) { + gtk_widget_queue_draw_area(widget_, rect.x(), rect.y(), rect.width(), + rect.height()); + } +} + +void NativeWidgetGtk::SetCursor(gfx::NativeCursor cursor) { +#if defined(TOUCH_UI) + if (!ui::TouchFactory::GetInstance()->is_cursor_visible()) + cursor = gfx::GetCursor(GDK_BLANK_CURSOR); +#endif + // |window_contents_| is placed on top of |widget_|. So the cursor needs to be + // set on |window_contents_| instead of |widget_|. + if (window_contents_) + gdk_window_set_cursor(window_contents_->window, cursor); +} + +void NativeWidgetGtk::ClearNativeFocus() { + DCHECK(!child_); + if (!GetNativeView()) { + NOTREACHED(); + return; + } + gtk_window_set_focus(GTK_WINDOW(GetNativeView()), NULL); +} + +void NativeWidgetGtk::FocusNativeView(gfx::NativeView native_view) { + if (native_view && !gtk_widget_is_focus(native_view)) + gtk_widget_grab_focus(native_view); +} + +bool NativeWidgetGtk::ConvertPointFromAncestor( + const Widget* ancestor, gfx::Point* point) const { + NOTREACHED(); + return false; +} + +gfx::Rect NativeWidgetGtk::GetWorkAreaBoundsInScreen() const { + return gfx::Screen::GetMonitorWorkAreaNearestWindow(GetNativeView()); +} + +void NativeWidgetGtk::SetInactiveRenderingDisabled(bool value) { +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetGtk, protected: + +void NativeWidgetGtk::OnSizeRequest(GtkWidget* widget, + GtkRequisition* requisition) { + // Do only return the preferred size for child windows. GtkWindow interprets + // the requisition as a minimum size for top level windows, returning a + // preferred size for these would prevents us from setting smaller window + // sizes. + if (child_) { + gfx::Size size(GetWidget()->GetRootView()->GetPreferredSize()); + requisition->width = size.width(); + requisition->height = size.height(); + } +} + +void NativeWidgetGtk::OnSizeAllocate(GtkWidget* widget, + GtkAllocation* allocation) { + // See comment next to size_ as to why we do this. Also note, it's tempting + // to put this in the static method so subclasses don't need to worry about + // it, but if a subclasses needs to set a shape then they need to always + // reset the shape in this method regardless of whether the size changed. + gfx::Size new_size(allocation->width, allocation->height); + if (new_size == size_) + return; + size_ = new_size; + if (compositor_.get()) + compositor_->WidgetSizeChanged(size_); + delegate_->OnNativeWidgetSizeChanged(size_); + + if (GetWidget()->non_client_view()) { + // The Window's NonClientView may provide a custom shape for the Window. + gfx::Path window_mask; + GetWidget()->non_client_view()->GetWindowMask(gfx::Size(allocation->width, + allocation->height), + &window_mask); + GdkRegion* mask_region = window_mask.CreateNativeRegion(); + gdk_window_shape_combine_region(GetNativeView()->window, mask_region, 0, 0); + if (mask_region) + gdk_region_destroy(mask_region); + + SaveWindowPosition(); + } +} + +gboolean NativeWidgetGtk::OnPaint(GtkWidget* widget, GdkEventExpose* event) { + gdk_window_set_debug_updates(Widget::IsDebugPaintEnabled()); + + if (transparent_ && child_) { + // Clear the background before drawing any view and native components. + DrawTransparentBackground(widget, event); + if (!CompositePainter::IsComposited(widget_) && + gdk_screen_is_composited(gdk_screen_get_default())) { + // Let the parent draw the content only after something is drawn on + // the widget. + CompositePainter::SetComposited(widget_); + } + } + + ui::ScopedRegion region(gdk_region_copy(event->region)); + if (!gdk_region_empty(region.Get())) { + GdkRectangle clip_bounds; + gdk_region_get_clipbox(region.Get(), &clip_bounds); + if (!delegate_->OnNativeWidgetPaintAccelerated(gfx::Rect(clip_bounds))) { + gfx::CanvasSkiaPaint canvas(event); + if (!canvas.is_empty()) { + canvas.set_composite_alpha(is_transparent()); + delegate_->OnNativeWidgetPaint(&canvas); + } + } + } + + if (!painted_) { + painted_ = true; + if (!child_) + UpdateFreezeUpdatesProperty(GTK_WINDOW(widget_), false /* remove */); + } + return false; // False indicates other widgets should get the event as well. +} + +void NativeWidgetGtk::OnDragDataGet(GtkWidget* widget, + GdkDragContext* context, + GtkSelectionData* data, + guint info, + guint time) { + if (!drag_data_) { + NOTREACHED(); + return; + } + drag_data_->WriteFormatToSelection(info, data); +} + +void NativeWidgetGtk::OnDragDataReceived(GtkWidget* widget, + GdkDragContext* context, + gint x, + gint y, + GtkSelectionData* data, + guint info, + guint time) { + if (drop_target_.get()) + drop_target_->OnDragDataReceived(context, x, y, data, info, time); +} + +gboolean NativeWidgetGtk::OnDragDrop(GtkWidget* widget, + GdkDragContext* context, + gint x, + gint y, + guint time) { + if (drop_target_.get()) { + return drop_target_->OnDragDrop(context, x, y, time); + } + return FALSE; +} + +void NativeWidgetGtk::OnDragEnd(GtkWidget* widget, GdkDragContext* context) { + if (!drag_data_) { + // This indicates we didn't start a drag operation, and should never + // happen. + NOTREACHED(); + return; + } + // Quit the nested message loop we spawned in DoDrag. + MessageLoop::current()->Quit(); +} + +gboolean NativeWidgetGtk::OnDragFailed(GtkWidget* widget, + GdkDragContext* context, + GtkDragResult result) { + return FALSE; +} + +void NativeWidgetGtk::OnDragLeave(GtkWidget* widget, + GdkDragContext* context, + guint time) { + if (ignore_drag_leave_) { + ignore_drag_leave_ = false; + return; + } + if (drop_target_.get()) { + drop_target_->OnDragLeave(context, time); + drop_target_.reset(NULL); + } +} + +gboolean NativeWidgetGtk::OnDragMotion(GtkWidget* widget, + GdkDragContext* context, + gint x, + gint y, + guint time) { + if (!drop_target_.get()) { + drop_target_.reset(new DropTargetGtk( + reinterpret_cast<internal::RootView*>(GetWidget()->GetRootView()), + context)); + } + return drop_target_->OnDragMotion(context, x, y, time); +} + +gboolean NativeWidgetGtk::OnEnterNotify(GtkWidget* widget, + GdkEventCrossing* event) { + if (HasMouseCapture() && event->mode == GDK_CROSSING_GRAB) { + // Doing a grab results an async enter event, regardless of where the mouse + // is. We don't want to generate a mouse move in this case. + return false; + } + + if (!GetWidget()->last_mouse_event_was_move_ && + !GetWidget()->is_mouse_button_pressed_) { + // When a mouse button is pressed gtk generates a leave, enter, press. + // RootView expects to get a mouse move before a press, otherwise enter is + // not set. So we generate a move here. + GdkEventMotion motion = { GDK_MOTION_NOTIFY, event->window, + event->send_event, event->time, event->x, event->y, NULL, event->state, + 0, NULL, event->x_root, event->y_root }; + + // If this event is the result of pressing a button then one of the button + // modifiers is set. Unset it as we're compensating for the leave generated + // when you press a button. + motion.state &= ~(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK); + + MouseEvent mouse_event(TransformEvent(&motion)); + delegate_->OnMouseEvent(mouse_event); + } + return false; +} + +gboolean NativeWidgetGtk::OnLeaveNotify(GtkWidget* widget, + GdkEventCrossing* event) { + gdk_window_set_cursor(widget->window, gfx::GetCursor(GDK_LEFT_PTR)); + + GetWidget()->ResetLastMouseMoveFlag(); + + if (!HasMouseCapture() && !GetWidget()->is_mouse_button_pressed_) { + // Don't convert if the event is synthetic and has 0x0 coordinates. + if (event->x_root || event->y_root || event->x || event->y || + !event->send_event) { + TransformEvent(event); + } + MouseEvent mouse_event(reinterpret_cast<GdkEvent*>(event)); + delegate_->OnMouseEvent(mouse_event); + } + return false; +} + +gboolean NativeWidgetGtk::OnMotionNotify(GtkWidget* widget, + GdkEventMotion* event) { + if (GetWidget()->non_client_view()) { + GdkEventMotion transformed_event = *event; + TransformEvent(&transformed_event); + gfx::Point translated_location(transformed_event.x, transformed_event.y); + + // Update the cursor for the screen edge. + int hittest_code = + GetWidget()->non_client_view()->NonClientHitTest(translated_location); + if (hittest_code != HTCLIENT) { + GdkCursorType cursor_type = HitTestCodeToGdkCursorType(hittest_code); + gdk_window_set_cursor(widget->window, gfx::GetCursor(cursor_type)); + } + } + + MouseEvent mouse_event(TransformEvent(event)); + delegate_->OnMouseEvent(mouse_event); + return true; +} + +gboolean NativeWidgetGtk::OnButtonPress(GtkWidget* widget, + GdkEventButton* event) { + if (GetWidget()->non_client_view()) { + GdkEventButton transformed_event = *event; + MouseEvent mouse_event(TransformEvent(&transformed_event)); + + int hittest_code = GetWidget()->non_client_view()->NonClientHitTest( + mouse_event.location()); + switch (hittest_code) { + case HTCAPTION: { + // Start dragging if the mouse event is a single click and *not* a right + // click. If it is a right click, then pass it through to + // NativeWidgetGtk::OnButtonPress so that View class can show + // ContextMenu upon a mouse release event. We only start drag on single + // clicks as we get a crash in Gtk on double/triple clicks. + if (event->type == GDK_BUTTON_PRESS && + !mouse_event.IsOnlyRightMouseButton()) { + gfx::Point screen_point(event->x, event->y); + View::ConvertPointToScreen(GetWidget()->GetRootView(), &screen_point); + gtk_window_begin_move_drag(GetNativeWindow(), event->button, + screen_point.x(), screen_point.y(), + event->time); + return TRUE; + } + break; + } + case HTBOTTOM: + case HTBOTTOMLEFT: + case HTBOTTOMRIGHT: + case HTGROWBOX: + case HTLEFT: + case HTRIGHT: + case HTTOP: + case HTTOPLEFT: + case HTTOPRIGHT: { + gfx::Point screen_point(event->x, event->y); + View::ConvertPointToScreen(GetWidget()->GetRootView(), &screen_point); + // TODO(beng): figure out how to get a good minimum size. + gtk_widget_set_size_request(GetNativeView(), 100, 100); + gtk_window_begin_resize_drag(GetNativeWindow(), + HitTestCodeToGDKWindowEdge(hittest_code), + event->button, screen_point.x(), + screen_point.y(), event->time); + return TRUE; + } + default: + // Everything else falls into standard client event handling... + break; + } + } + + if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS) { + // The sequence for double clicks is press, release, press, 2press, release. + // This means that at the time we get the second 'press' we don't know + // whether it corresponds to a double click or not. For now we're completely + // ignoring the 2press/3press events as they are duplicate. To make this + // work right we need to write our own code that detects if the press is a + // double/triple. For now we're completely punting, which means we always + // get single clicks. + // TODO: fix this. + return true; + } + + MouseEvent mouse_event(TransformEvent(event)); + // Returns true to consume the event when widget is not transparent. + return delegate_->OnMouseEvent(mouse_event) || !transparent_; +} + +gboolean NativeWidgetGtk::OnButtonRelease(GtkWidget* widget, + GdkEventButton* event) { + // GTK generates a mouse release at the end of dnd. We need to ignore it. + if (!drag_data_) { + MouseEvent mouse_event(TransformEvent(event)); + delegate_->OnMouseEvent(mouse_event); + } + return true; +} + +gboolean NativeWidgetGtk::OnScroll(GtkWidget* widget, GdkEventScroll* event) { + MouseWheelEvent mouse_event(TransformEvent(event)); + return delegate_->OnMouseEvent(mouse_event); +} + +gboolean NativeWidgetGtk::OnFocusIn(GtkWidget* widget, GdkEventFocus* event) { + if (has_focus_) + return false; // This is the second focus-in event in a row, ignore it. + has_focus_ = true; + + should_handle_menu_key_release_ = false; + + if (!GetWidget()->is_top_level()) + return false; + + // Only top-level Widget should have an InputMethod instance. + InputMethod* input_method = GetWidget()->GetInputMethod(); + if (input_method) + input_method->OnFocus(); + + // See description of got_initial_focus_in_ for details on this. + if (!got_initial_focus_in_) { + got_initial_focus_in_ = true; + // Sets initial focus here. On X11/Gtk, window creation + // is asynchronous and a focus request has to be made after a window + // gets created. + GetWidget()->SetInitialFocus(); + } + return false; +} + +gboolean NativeWidgetGtk::OnFocusOut(GtkWidget* widget, GdkEventFocus* event) { + if (!has_focus_) + return false; // This is the second focus-out event in a row, ignore it. + has_focus_ = false; + + if (!GetWidget()->is_top_level()) + return false; + + // Only top-level Widget should have an InputMethod instance. + InputMethod* input_method = GetWidget()->GetInputMethod(); + if (input_method) + input_method->OnBlur(); + return false; +} + +gboolean NativeWidgetGtk::OnEventKey(GtkWidget* widget, GdkEventKey* event) { + KeyEvent key(reinterpret_cast<GdkEvent*>(event)); + InputMethod* input_method = GetWidget()->GetInputMethod(); + if (input_method) + input_method->DispatchKeyEvent(key); + else + DispatchKeyEventPostIME(key); + + // Returns true to prevent GtkWindow's default key event handler. + return true; +} + +gboolean NativeWidgetGtk::OnQueryTooltip(GtkWidget* widget, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip* tooltip) { +#if defined(TOUCH_UI) + return false; // Tell GTK not to draw tooltips as we draw tooltips in views +#else + return static_cast<TooltipManagerGtk*>(tooltip_manager_.get())-> + ShowTooltip(x, y, keyboard_mode, tooltip); +#endif +} + +gboolean NativeWidgetGtk::OnVisibilityNotify(GtkWidget* widget, + GdkEventVisibility* event) { + return false; +} + +gboolean NativeWidgetGtk::OnGrabBrokeEvent(GtkWidget* widget, GdkEvent* event) { + if (!has_pointer_grab_ && !has_keyboard_grab_) { + // We don't have any grabs; don't attempt to do anything. + return false; + } + + // Sent when either the keyboard or pointer grab is broke. We drop both grabs + // in this case. + if (event->grab_broken.keyboard) { + // Keyboard grab was broke. + has_keyboard_grab_ = false; + if (has_pointer_grab_) { + has_pointer_grab_ = false; + gdk_pointer_ungrab(GDK_CURRENT_TIME); + delegate_->OnMouseCaptureLost(); + } + } else { + // Mouse grab was broke. + has_pointer_grab_ = false; + if (has_keyboard_grab_) { + has_keyboard_grab_ = false; + gdk_keyboard_ungrab(GDK_CURRENT_TIME); + } + delegate_->OnMouseCaptureLost(); + } + ReleaseMouseCapture(); + +#if defined(TOUCH_UI) + ui::TouchFactory::GetInstance()->UngrabTouchDevices( + GDK_WINDOW_XDISPLAY(window_contents()->window)); +#endif + return false; // To let other widgets get the event. +} + +void NativeWidgetGtk::OnGrabNotify(GtkWidget* widget, gboolean was_grabbed) { + // Sent when gtk_grab_add changes. + if (!window_contents_) + return; // Grab broke after window destroyed, don't try processing it. + if (!was_grabbed) // Indicates we've been shadowed (lost grab). + HandleGtkGrabBroke(); +} + +void NativeWidgetGtk::OnDestroy(GtkWidget* object) { + signal_registrar_.reset(); + if (grab_notify_signal_id_) { + g_signal_handler_disconnect(window_contents_, grab_notify_signal_id_); + grab_notify_signal_id_ = 0; + } + delegate_->OnNativeWidgetDestroying(); + if (!child_) + ActiveWindowWatcherX::RemoveObserver(this); + // Note that this handler is hooked to GtkObject::destroy. + // NULL out pointers here since we might still be in an observer list + // until deletion happens. + widget_ = window_contents_ = NULL; +} + +void NativeWidgetGtk::OnDestroyed(GObject *where_the_object_was) { + delegate_->OnNativeWidgetDestroyed(); + if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) + delete this; +} + +void NativeWidgetGtk::OnShow(GtkWidget* widget) { + delegate_->OnNativeWidgetVisibilityChanged(true); +} + +void NativeWidgetGtk::OnMap(GtkWidget* widget) { +#if defined(TOUCH_UI) + // Force an expose event to trigger OnPaint for touch. This is + // a workaround for a bug that X Expose event does not trigger + // Gdk's expose signal. This happens when you try to open views menu + // while a virtual keyboard gets kicked in or out. This seems to be + // a bug in message_pump_x.cc as we do get X Expose event but + // it doesn't trigger gtk's expose signal. We're not going to fix this + // as we're removing gtk and migrating to new compositor. + gdk_window_process_all_updates(); +#endif +} + +void NativeWidgetGtk::OnHide(GtkWidget* widget) { + delegate_->OnNativeWidgetVisibilityChanged(false); +} + +gboolean NativeWidgetGtk::OnWindowStateEvent(GtkWidget* widget, + GdkEventWindowState* event) { + if (GetWidget()->non_client_view() && + !(event->new_window_state & GDK_WINDOW_STATE_WITHDRAWN)) { + SaveWindowPosition(); + } + window_state_ = event->new_window_state; + return FALSE; +} + +gboolean NativeWidgetGtk::OnConfigureEvent(GtkWidget* widget, + GdkEventConfigure* event) { + SaveWindowPosition(); + return FALSE; +} + +void NativeWidgetGtk::HandleGtkGrabBroke() { + ReleaseMouseCapture(); + delegate_->OnMouseCaptureLost(); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetGtk, private: + +void NativeWidgetGtk::ScheduleDraw() { + SchedulePaintInRect(gfx::Rect(gfx::Point(), size_)); +} + +void NativeWidgetGtk::DispatchKeyEventPostIME(const KeyEvent& key) { + // Always reset |should_handle_menu_key_release_| unless we are handling a + // VKEY_MENU key release event. It ensures that VKEY_MENU accelerator can only + // be activated when handling a VKEY_MENU key release event which is preceded + // by an unhandled VKEY_MENU key press event. See also HandleKeyboardEvent(). + if (key.key_code() != ui::VKEY_MENU || key.type() != ui::ET_KEY_RELEASED) + should_handle_menu_key_release_ = false; + + // Send the key event to View hierarchy first. + bool handled = delegate_->OnKeyEvent(key); + + if (key.key_code() == ui::VKEY_PROCESSKEY || handled) + return; + + // Dispatch the key event to native GtkWidget hierarchy. + // To prevent GtkWindow from handling the key event as a keybinding, we need + // to bypass GtkWindow's default key event handler and dispatch the event + // here. + GdkEventKey* event = reinterpret_cast<GdkEventKey*>(key.gdk_event()); + if (!handled && event && GTK_IS_WINDOW(widget_)) + handled = gtk_window_propagate_key_event(GTK_WINDOW(widget_), event); + + // On Linux, in order to handle VKEY_MENU (Alt) accelerator key correctly and + // avoid issues like: http://crbug.com/40966 and http://crbug.com/49701, we + // should only send the key event to the focus manager if it's not handled by + // any View or native GtkWidget. + // The flow is different when the focus is in a RenderWidgetHostViewGtk, which + // always consumes the key event and send it back to us later by calling + // HandleKeyboardEvent() directly, if it's not handled by webkit. + if (!handled) + handled = HandleKeyboardEvent(key); + + // Dispatch the key event for bindings processing. + if (!handled && event && GTK_IS_WINDOW(widget_)) + gtk_bindings_activate_event(GTK_OBJECT(widget_), event); +} + +void NativeWidgetGtk::SetInitParams(const Widget::InitParams& params) { + DCHECK(!GetNativeView()); + + ownership_ = params.ownership; + child_ = params.child; + is_menu_ = params.type == Widget::InitParams::TYPE_MENU; + + // TODO(beng): The secondary checks here actually obviate the need for + // params.transient but that's only because NativeWidgetGtk + // considers any top-level widget to be a transient widget. We + // will probably want to ammend this assumption at some point. + if (params.transient || params.parent || params.parent_widget) + transient_to_parent_ = true; + if (params.transparent) + MakeTransparent(); + if (!params.accept_events && !child_) + ignore_events_ = true; + if (params.double_buffer) + EnableDoubleBuffer(true); +} + +gboolean NativeWidgetGtk::OnWindowPaint(GtkWidget* widget, + GdkEventExpose* event) { + // Clear the background to be totally transparent. We don't need to + // paint the root view here as that is done by OnPaint. + DCHECK(transparent_); + DrawTransparentBackground(widget, event); + // The Keyboard layout view has a renderer that covers the entire + // window, which prevents OnPaint from being called on window_contents_, + // so we need to remove the FREEZE_UPDATES property here. + if (!painted_) { + painted_ = true; + UpdateFreezeUpdatesProperty(GTK_WINDOW(widget_), false /* remove */); + } + return false; +} + +void NativeWidgetGtk::OnChildExpose(GtkWidget* child) { + DCHECK(!child_); + if (!painted_) { + painted_ = true; + UpdateFreezeUpdatesProperty(GTK_WINDOW(widget_), false /* remove */); + } + RemoveExposeHandlerIfExists(child); +} + +// static +gboolean NativeWidgetGtk::ChildExposeHandler(GtkWidget* widget, + GdkEventExpose* event) { + GtkWidget* toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW); + CHECK(toplevel); + Widget* views_widget = Widget::GetWidgetForNativeView(toplevel); + CHECK(views_widget); + NativeWidgetGtk* widget_gtk = + static_cast<NativeWidgetGtk*>(views_widget->native_widget()); + widget_gtk->OnChildExpose(widget); + return false; +} + +void NativeWidgetGtk::CreateGtkWidget(const Widget::InitParams& params) { + // We turn off double buffering for two reasons: + // 1. We draw to a canvas then composite to the screen, which means we're + // doing our own double buffering already. + // 2. GTKs double buffering clips to the dirty region. RootView occasionally + // needs to expand the paint region (see RootView::OnPaint). This means + // that if we use GTK's double buffering and we tried to expand the dirty + // region, it wouldn't get painted. + if (child_) { + window_contents_ = widget_ = gtk_views_fixed_new(); + gtk_widget_set_name(widget_, "views-gtkwidget-child-fixed"); + if (!is_double_buffered_) + GTK_WIDGET_UNSET_FLAGS(widget_, GTK_DOUBLE_BUFFERED); + gtk_fixed_set_has_window(GTK_FIXED(widget_), true); + if (!params.parent && !null_parent_) { + GtkWidget* popup = gtk_window_new(GTK_WINDOW_POPUP); + null_parent_ = gtk_fixed_new(); + gtk_widget_set_name(null_parent_, "views-gtkwidget-null-parent"); + gtk_container_add(GTK_CONTAINER(popup), null_parent_); + gtk_widget_realize(null_parent_); + } + if (transparent_) { + // transparency has to be configured before widget is realized. + DCHECK(params.parent) << + "Transparent widget must have parent when initialized"; + ConfigureWidgetForTransparentBackground(params.parent); + } + gtk_container_add( + GTK_CONTAINER(params.parent ? params.parent : null_parent_), widget_); + gtk_widget_realize(widget_); + if (transparent_) { + // The widget has to be realized to set composited flag. + // I tried "realize" signal to set this flag, but it did not work + // when the top level is popup. + DCHECK(GTK_WIDGET_REALIZED(widget_)); + gdk_window_set_composited(widget_->window, true); + } + if (params.parent && !params.bounds.size().IsEmpty()) { + // Make sure that an widget is given it's initial size before + // we're done initializing, to take care of some potential + // corner cases when programmatically arranging hierarchies as + // seen in + // http://code.google.com/p/chromium-os/issues/detail?id=5987 + + // This can't be done without a parent present, or stale data + // might show up on the screen as seen in + // http://code.google.com/p/chromium/issues/detail?id=53870 + GtkAllocation alloc = + { 0, 0, params.bounds.width(), params.bounds.height() }; + gtk_widget_size_allocate(widget_, &alloc); + } + if (params.type == Widget::InitParams::TYPE_CONTROL) { + // Controls are initially visible. + gtk_widget_show(widget_); + } + } else { + Widget::InitParams::Type type = params.type; + if (type == Widget::InitParams::TYPE_BUBBLE && + params.delegate->AsBubbleDelegate() && + params.delegate->AsBubbleDelegate()->use_focusless()) { + // Handles focusless bubble type, which are bubbles that should + // act like popups rather than gtk windows. They do not get focus + // and are not controlled by window manager placement. + type = Widget::InitParams::TYPE_POPUP; + } + + // Use our own window class to override GtkWindow's move_focus method. + widget_ = gtk_views_window_new(WindowTypeToGtkWindowType(type)); + gtk_widget_set_name(widget_, "views-gtkwidget-window"); + if (transient_to_parent_) { + gtk_window_set_transient_for(GTK_WINDOW(widget_), + GTK_WINDOW(params.parent)); + } + GTK_WIDGET_UNSET_FLAGS(widget_, GTK_DOUBLE_BUFFERED); + + // Gtk determines the size for windows based on the requested size of the + // child. For NativeWidgetGtk the child is a fixed. If the fixed ends up + // with a child widget it's possible the child widget will drive the + // requested size of the widget, which we don't want. We explicitly set a + // value of 1x1 here so that gtk doesn't attempt to resize the window if we + // end up with a situation where the requested size of a child of the fixed + // is greater than the size of the window. By setting the size in this + // manner we're also allowing users of WidgetGtk to change the requested + // size at any time. + gtk_widget_set_size_request(widget_, 1, 1); + + if (!params.bounds.size().IsEmpty()) { + // When we realize the window, the window manager is given a size. If we + // don't specify a size before then GTK defaults to 200x200. Specify + // a size now so that the window manager sees the requested size. + GtkAllocation alloc = + { 0, 0, params.bounds.width(), params.bounds.height() }; + gtk_widget_size_allocate(widget_, &alloc); + } + gtk_window_set_decorated(GTK_WINDOW(widget_), false); + // We'll take care of positioning our window. + gtk_window_set_position(GTK_WINDOW(widget_), GTK_WIN_POS_NONE); + + window_contents_ = gtk_views_fixed_new(); + gtk_widget_set_name(window_contents_, "views-gtkwidget-window-fixed"); + if (!is_double_buffered_) + GTK_WIDGET_UNSET_FLAGS(window_contents_, GTK_DOUBLE_BUFFERED); + gtk_fixed_set_has_window(GTK_FIXED(window_contents_), true); + gtk_container_add(GTK_CONTAINER(widget_), window_contents_); + gtk_widget_show(window_contents_); + g_object_set_data(G_OBJECT(window_contents_), kNativeWidgetKey, + static_cast<NativeWidgetGtk*>(this)); + if (transparent_) + ConfigureWidgetForTransparentBackground(NULL); + + if (ignore_events_) + ConfigureWidgetForIgnoreEvents(); + + // Realize the window_contents_ so that we can always get a handle for + // acceleration. Without this we need to check every time paint is + // invoked. + gtk_widget_realize(window_contents_); + + SetAlwaysOnTop(always_on_top_); + // UpdateFreezeUpdatesProperty will realize the widget and handlers like + // size-allocate will function properly. + UpdateFreezeUpdatesProperty(GTK_WINDOW(widget_), true /* add */); + } + SetNativeWindowProperty(kNativeWidgetKey, this); +} + +void NativeWidgetGtk::ConfigureWidgetForTransparentBackground( + GtkWidget* parent) { + DCHECK(widget_ && window_contents_); + + GdkColormap* rgba_colormap = + gdk_screen_get_rgba_colormap(gtk_widget_get_screen(widget_)); + if (!rgba_colormap) { + transparent_ = false; + return; + } + // To make the background transparent we need to install the RGBA colormap + // on both the window and fixed. In addition we need to make sure no + // decorations are drawn. The last bit is to make sure the widget doesn't + // attempt to draw a pixmap in it's background. + if (!child_) { + DCHECK(parent == NULL); + gtk_widget_set_colormap(widget_, rgba_colormap); + gtk_widget_set_app_paintable(widget_, true); + signal_registrar_->Connect(widget_, "expose_event", + G_CALLBACK(&OnWindowPaintThunk), this); + gtk_widget_realize(widget_); + gdk_window_set_decorations(widget_->window, + static_cast<GdkWMDecoration>(0)); + } else { + DCHECK(parent); + CompositePainter::AddCompositePainter(parent); + } + DCHECK(!GTK_WIDGET_REALIZED(window_contents_)); + gtk_widget_set_colormap(window_contents_, rgba_colormap); +} + +void NativeWidgetGtk::ConfigureWidgetForIgnoreEvents() { + gtk_widget_realize(widget_); + GdkWindow* gdk_window = widget_->window; + Display* display = GDK_WINDOW_XDISPLAY(gdk_window); + XID win = GDK_WINDOW_XID(gdk_window); + + // This sets the clickable area to be empty, allowing all events to be + // passed to any windows behind this one. + XShapeCombineRectangles( + display, + win, + ShapeInput, + 0, // x offset + 0, // y offset + NULL, // rectangles + 0, // num rectangles + ShapeSet, + 0); +} + +void NativeWidgetGtk::DrawTransparentBackground(GtkWidget* widget, + GdkEventExpose* event) { + cairo_t* cr = gdk_cairo_create(widget->window); + cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); + gdk_cairo_region(cr, event->region); + cairo_fill(cr); + cairo_destroy(cr); +} + +void NativeWidgetGtk::SaveWindowPosition() { + // The delegate may have gone away on us. + if (!GetWidget()->widget_delegate()) + return; + + ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL; + if (IsMaximized()) + show_state = ui::SHOW_STATE_MAXIMIZED; + else if (IsMinimized()) + show_state = ui::SHOW_STATE_MINIMIZED; + GetWidget()->widget_delegate()->SaveWindowPlacement( + GetWidget()->GetWindowScreenBounds(), + show_state); +} + +//////////////////////////////////////////////////////////////////////////////// +// Widget, public: + +// static +void Widget::NotifyLocaleChanged() { + GList *window_list = gtk_window_list_toplevels(); + for (GList* element = window_list; element; element = g_list_next(element)) { + Widget* widget = + Widget::GetWidgetForNativeWindow(GTK_WINDOW(element->data)); + if (widget) + widget->LocaleChanged(); + } + g_list_free(window_list); +} + +// static +void Widget::CloseAllSecondaryWidgets() { + GList* windows = gtk_window_list_toplevels(); + for (GList* window = windows; window; + window = g_list_next(window)) { + Widget* widget = Widget::GetWidgetForNativeView(GTK_WIDGET(window->data)); + if (widget && widget->is_secondary_widget()) + widget->Close(); + } + g_list_free(windows); +} + +// static +bool Widget::ConvertRect(const Widget* source, + const Widget* target, + gfx::Rect* rect) { + DCHECK(source); + DCHECK(target); + DCHECK(rect); + + // TODO(oshima): Add check if source and target belongs to the same + // screen. + + if (source == target) + return true; + if (!source || !target) + return false; + + gfx::Point source_point = source->GetWindowScreenBounds().origin(); + gfx::Point target_point = target->GetWindowScreenBounds().origin(); + + rect->set_origin( + source_point.Subtract(target_point).Add(rect->origin())); + return true; +} + +namespace internal { + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetPrivate, public: + +// static +NativeWidgetPrivate* NativeWidgetPrivate::CreateNativeWidget( + NativeWidgetDelegate* delegate) { + return new NativeWidgetGtk(delegate); +} + +// static +NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeView( + gfx::NativeView native_view) { + if (!native_view) + return NULL; + return reinterpret_cast<NativeWidgetGtk*>( + g_object_get_data(G_OBJECT(native_view), kNativeWidgetKey)); +} + +// static +NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeWindow( + gfx::NativeWindow native_window) { + if (!native_window) + return NULL; + return reinterpret_cast<NativeWidgetGtk*>( + g_object_get_data(G_OBJECT(native_window), kNativeWidgetKey)); +} + +// static +NativeWidgetPrivate* NativeWidgetPrivate::GetTopLevelNativeWidget( + gfx::NativeView native_view) { + if (!native_view) + return NULL; + + NativeWidgetPrivate* widget = NULL; + + GtkWidget* parent_gtkwidget = native_view; + NativeWidgetPrivate* parent_widget; + do { + parent_widget = GetNativeWidgetForNativeView(parent_gtkwidget); + if (parent_widget) + widget = parent_widget; + parent_gtkwidget = gtk_widget_get_parent(parent_gtkwidget); + } while (parent_gtkwidget); + + return widget && widget->GetWidget()->is_top_level() ? widget : NULL; +} + +// static +void NativeWidgetPrivate::GetAllChildWidgets(gfx::NativeView native_view, + Widget::Widgets* children) { + if (!native_view) + return; + + Widget* widget = Widget::GetWidgetForNativeView(native_view); + if (widget) + children->insert(widget); + gtk_container_foreach(GTK_CONTAINER(native_view), + EnumerateChildWidgetsForNativeWidgets, + reinterpret_cast<gpointer>(children)); +} + +// static +void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView native_view, + gfx::NativeView new_parent) { + if (!native_view) + return; + + gfx::NativeView previous_parent = gtk_widget_get_parent(native_view); + if (previous_parent == new_parent) + return; + + Widget::Widgets widgets; + GetAllChildWidgets(native_view, &widgets); + + // First notify all the widgets that they are being disassociated + // from their previous parent. + for (Widget::Widgets::iterator it = widgets.begin(); + it != widgets.end(); ++it) { + // TODO(beng): Rename this notification to NotifyNativeViewChanging() + // and eliminate the bool parameter. + (*it)->NotifyNativeViewHierarchyChanged(false, previous_parent); + } + + if (gtk_widget_get_parent(native_view)) + gtk_widget_reparent(native_view, new_parent); + else + gtk_container_add(GTK_CONTAINER(new_parent), native_view); + + // And now, notify them that they have a brand new parent. + for (Widget::Widgets::iterator it = widgets.begin(); + it != widgets.end(); ++it) { + (*it)->NotifyNativeViewHierarchyChanged(true, new_parent); + } +} + +// static +bool NativeWidgetPrivate::IsMouseButtonDown() { + bool button_pressed = false; + GdkEvent* event = gtk_get_current_event(); + if (event) { + button_pressed = event->type == GDK_BUTTON_PRESS || + event->type == GDK_2BUTTON_PRESS || + event->type == GDK_3BUTTON_PRESS; + gdk_event_free(event); + } + return button_pressed; +} + +} // namespace internal +} // namespace views diff --git a/views/widget/native_widget_gtk.h b/views/widget/native_widget_gtk.h new file mode 100644 index 0000000..1dcdace --- /dev/null +++ b/views/widget/native_widget_gtk.h @@ -0,0 +1,475 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_NATIVE_WIDGET_GTK_H_ +#define VIEWS_WIDGET_NATIVE_WIDGET_GTK_H_ +#pragma once + +#include <gtk/gtk.h> + +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop.h" +#include "ui/base/gtk/gtk_signal.h" +#include "ui/base/x/active_window_watcher_x.h" +#include "ui/gfx/compositor/compositor.h" +#include "ui/gfx/size.h" +#include "ui/views/focus/focus_manager.h" +#include "views/widget/native_widget_private.h" +#include "views/widget/widget.h" + +namespace gfx { +class Rect; +} + +namespace ui { +class OSExchangeData; +class OSExchangeDataProviderGtk; +class GtkSignalRegistrar; +} +using ui::OSExchangeData; +using ui::OSExchangeDataProviderGtk; + +namespace views { + +class DropTargetGtk; +class InputMethod; +class View; + +namespace internal { +class NativeWidgetDelegate; +} + +// Widget implementation for GTK. +class VIEWS_EXPORT NativeWidgetGtk : public internal::NativeWidgetPrivate, + public ui::CompositorDelegate, + public ui::ActiveWindowWatcherX::Observer { + public: + explicit NativeWidgetGtk(internal::NativeWidgetDelegate* delegate); + virtual ~NativeWidgetGtk(); + + // Returns the transient parent. See make_transient_to_parent for details on + // what the transient parent is. + GtkWindow* GetTransientParent() const; + + // Makes the background of the window totally transparent. This must be + // invoked before Init. This does a couple of checks and returns true if + // the window can be made transparent. The actual work of making the window + // transparent is done by ConfigureWidgetForTransparentBackground. + // This works for both child and window types. + bool MakeTransparent(); + bool is_transparent() const { return transparent_; } + + // Enable/Disable double buffering.This is neccessary to prevent + // flickering in ScrollView, which has both native and view + // controls. + void EnableDoubleBuffer(bool enabled); + bool is_double_buffered() const { return is_double_buffered_; } + + bool is_ignore_events() const { return ignore_events_; } + + // Adds and removes the specified widget as a child of this widget's contents. + // These methods make sure to add the widget to the window's contents + // container if this widget is a window. + void AddChild(GtkWidget* child); + void RemoveChild(GtkWidget* child); + + // A safe way to reparent a child widget to this widget. Calls + // gtk_widget_reparent which handles refcounting to avoid destroying the + // widget when removing it from its old parent. + void ReparentChild(GtkWidget* child); + + // Positions a child GtkWidget at the specified location and bounds. + void PositionChild(GtkWidget* child, int x, int y, int w, int h); + + // Parent GtkWidget all children are added to. When this NativeWidgetGtk + // corresponds to a top level window, this is the GtkFixed within the + // GtkWindow, not the GtkWindow itself. For child widgets, this is the same + // GtkFixed as |widget_|. + GtkWidget* window_contents() const { return window_contents_; } + + // Starts a drag on this widget. This blocks until the drag is done. + void DoDrag(const OSExchangeData& data, int operation); + + // Invoked when the active status changes. + virtual void OnActiveChanged(); + + // Sets the drop target to NULL. This is invoked by DropTargetGTK when the + // drop is done. + void ResetDropTarget(); + + // Gets the requested size of the widget. This can be the size + // stored in properties for a GtkViewsFixed, or in the requisitioned + // size of other kinds of widgets. + void GetRequestedSize(gfx::Size* out) const; + + // Overridden from ui::ActiveWindowWatcherX::Observer. + virtual void ActiveWindowChanged(GdkWindow* active_window) OVERRIDE; + + // Handles a keyboard event by sending it to our focus manager. + // Returns true if it's handled by the focus manager. + bool HandleKeyboardEvent(const KeyEvent& key); + + // Tells widget not to remove FREEZE_UPDATES property when the + // widget is painted. This is used if painting the gtk widget + // is not enough to show the window and has to wait further like + // keyboard overlay. Returns true if this is called before + // FREEZE_UPDATES property is removed, or false otherwise. + bool SuppressFreezeUpdates(); + + // Sets and deletes FREEZE_UPDATES property on given |window|. + // It adds the property when |enable| is true and remove if false. + // Calling this method will realize the window if it's not realized yet. + // This property is used to help WindowManager know when the window + // is fully painted so that WM can map the fully painted window. + // The property is based on Owen Taylor's proposal at + // http://mail.gnome.org/archives/wm-spec-list/2009-June/msg00002.html. + // This is just a hint to WM, and won't change the behavior for WM + // which does not support this property. + static void UpdateFreezeUpdatesProperty(GtkWindow* window, bool enable); + + // Registers a expose handler that removes FREEZE_UPDATES property. + // If you are adding a GtkWidget with its own GdkWindow that may + // fill the entire area of the NativeWidgetGtk to the view hierachy, you + // need use this function to tell WM that when the widget is ready + // to be shown. + // Caller of this method do not need to disconnect this because the + // handler will be removed upon the first invocation of the handler, + // or when the widget is re-attached, and expose won't be emitted on + // detached widget. + static void RegisterChildExposeHandler(GtkWidget* widget); + + // Overridden from internal::NativeWidgetPrivate: + virtual void InitNativeWidget(const Widget::InitParams& params) OVERRIDE; + virtual NonClientFrameView* CreateNonClientFrameView() OVERRIDE; + virtual void UpdateFrameAfterFrameChange() OVERRIDE; + virtual bool ShouldUseNativeFrame() const OVERRIDE; + virtual void FrameTypeChanged() OVERRIDE; + virtual Widget* GetWidget() OVERRIDE; + virtual const Widget* GetWidget() const OVERRIDE; + virtual gfx::NativeView GetNativeView() const OVERRIDE; + virtual gfx::NativeWindow GetNativeWindow() const OVERRIDE; + virtual Widget* GetTopLevelWidget() OVERRIDE; + virtual const ui::Compositor* GetCompositor() const OVERRIDE; + virtual ui::Compositor* GetCompositor() OVERRIDE; + virtual void CalculateOffsetToAncestorWithLayer( + gfx::Point* offset, + ui::Layer** layer_parent) OVERRIDE; + virtual void ReorderLayers() OVERRIDE; + virtual void ViewRemoved(View* view) OVERRIDE; + virtual void SetNativeWindowProperty(const char* name, void* value) OVERRIDE; + virtual void* GetNativeWindowProperty(const char* name) const OVERRIDE; + virtual TooltipManager* GetTooltipManager() const OVERRIDE; + virtual bool IsScreenReaderActive() const OVERRIDE; + virtual void SendNativeAccessibilityEvent( + View* view, + ui::AccessibilityTypes::Event event_type) OVERRIDE; + virtual void SetMouseCapture() OVERRIDE; + virtual void ReleaseMouseCapture() OVERRIDE; + virtual bool HasMouseCapture() const OVERRIDE; + virtual InputMethod* CreateInputMethod() OVERRIDE; + virtual void CenterWindow(const gfx::Size& size) OVERRIDE; + virtual void GetWindowPlacement( + gfx::Rect* bounds, + ui::WindowShowState* show_state) const OVERRIDE; + virtual void SetWindowTitle(const string16& title) OVERRIDE; + virtual void SetWindowIcons(const SkBitmap& window_icon, + const SkBitmap& app_icon) OVERRIDE; + virtual void SetAccessibleName(const string16& name) OVERRIDE; + virtual void SetAccessibleRole(ui::AccessibilityTypes::Role role) OVERRIDE; + virtual void SetAccessibleState(ui::AccessibilityTypes::State state) OVERRIDE; + virtual void BecomeModal() OVERRIDE; + virtual gfx::Rect GetWindowScreenBounds() const OVERRIDE; + virtual gfx::Rect GetClientAreaScreenBounds() const OVERRIDE; + virtual gfx::Rect GetRestoredBounds() const OVERRIDE; + virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE; + virtual void SetSize(const gfx::Size& size) OVERRIDE; + virtual void MoveAbove(gfx::NativeView native_view) OVERRIDE; + virtual void MoveToTop() OVERRIDE; + virtual void SetShape(gfx::NativeRegion shape) OVERRIDE; + virtual void Close() OVERRIDE; + virtual void CloseNow() OVERRIDE; + virtual void EnableClose(bool enable) OVERRIDE; + virtual void Show() OVERRIDE; + virtual void Hide() OVERRIDE; + virtual void ShowMaximizedWithBounds( + const gfx::Rect& restored_bounds) OVERRIDE; + virtual void ShowWithWindowState(ui::WindowShowState window_state) OVERRIDE; + virtual bool IsVisible() const OVERRIDE; + virtual void Activate() OVERRIDE; + virtual void Deactivate() OVERRIDE; + virtual bool IsActive() const OVERRIDE; + virtual void SetAlwaysOnTop(bool always_on_top) OVERRIDE; + virtual void Maximize() OVERRIDE; + virtual void Minimize() OVERRIDE; + virtual bool IsMaximized() const OVERRIDE; + virtual bool IsMinimized() const OVERRIDE; + virtual void Restore() OVERRIDE; + virtual void SetFullscreen(bool fullscreen) OVERRIDE; + virtual bool IsFullscreen() const OVERRIDE; + virtual void SetOpacity(unsigned char opacity) OVERRIDE; + virtual void SetUseDragFrame(bool use_drag_frame) OVERRIDE; + virtual bool IsAccessibleWidget() const OVERRIDE; + virtual void RunShellDrag(View* view, + const ui::OSExchangeData& data, + int operation) OVERRIDE; + virtual void SchedulePaintInRect(const gfx::Rect& rect) OVERRIDE; + virtual void SetCursor(gfx::NativeCursor cursor) OVERRIDE; + virtual void ClearNativeFocus() OVERRIDE; + virtual void FocusNativeView(gfx::NativeView native_view) OVERRIDE; + virtual bool ConvertPointFromAncestor( + const Widget* ancestor, gfx::Point* point) const OVERRIDE; + virtual gfx::Rect GetWorkAreaBoundsInScreen() const OVERRIDE; + virtual void SetInactiveRenderingDisabled(bool value) OVERRIDE; + + protected: + // Modifies event coordinates to the targeted widget contained by this widget. + template<class Event> GdkEvent* TransformEvent(Event* event) { + GdkWindow* dest = GTK_WIDGET(window_contents_)->window; + if (event && event->window != dest) { + gint dest_x, dest_y; + gdk_window_get_root_origin(dest, &dest_x, &dest_y); + event->x = event->x_root - dest_x; + event->y = event->y_root - dest_y; + } + return reinterpret_cast<GdkEvent*>(event); + } + + // Event handlers: + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, gboolean, OnButtonPress, + GdkEventButton*); + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, void, OnSizeRequest, + GtkRequisition*); + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, void, OnSizeAllocate, + GtkAllocation*); + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, gboolean, OnPaint, + GdkEventExpose*); + CHROMEGTK_VIRTUAL_CALLBACK_4(NativeWidgetGtk, void, OnDragDataGet, + GdkDragContext*, GtkSelectionData*, guint, + guint); + CHROMEGTK_VIRTUAL_CALLBACK_6(NativeWidgetGtk, void, OnDragDataReceived, + GdkDragContext*, gint, gint, GtkSelectionData*, + guint, guint); + CHROMEGTK_VIRTUAL_CALLBACK_4(NativeWidgetGtk, gboolean, OnDragDrop, + GdkDragContext*, gint, gint, guint); + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, void, OnDragEnd, + GdkDragContext*); + CHROMEGTK_VIRTUAL_CALLBACK_2(NativeWidgetGtk, gboolean, OnDragFailed, + GdkDragContext*, GtkDragResult); + CHROMEGTK_VIRTUAL_CALLBACK_2(NativeWidgetGtk, void, OnDragLeave, + GdkDragContext*, guint); + CHROMEGTK_VIRTUAL_CALLBACK_4(NativeWidgetGtk, gboolean, OnDragMotion, + GdkDragContext*, gint, gint, guint); + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, gboolean, OnEnterNotify, + GdkEventCrossing*); + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, gboolean, OnLeaveNotify, + GdkEventCrossing*); + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, gboolean, OnMotionNotify, + GdkEventMotion*); + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, gboolean, OnButtonRelease, + GdkEventButton*); + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, gboolean, OnFocusIn, + GdkEventFocus*); + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, gboolean, OnFocusOut, + GdkEventFocus*); + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, gboolean, OnEventKey, + GdkEventKey*); + CHROMEGTK_VIRTUAL_CALLBACK_4(NativeWidgetGtk, gboolean, OnQueryTooltip, gint, + gint, gboolean, GtkTooltip*); + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, gboolean, OnScroll, + GdkEventScroll*); + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, gboolean, OnVisibilityNotify, + GdkEventVisibility*); + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, gboolean, OnGrabBrokeEvent, + GdkEvent*); + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, void, OnGrabNotify, gboolean); + CHROMEGTK_VIRTUAL_CALLBACK_0(NativeWidgetGtk, void, OnDestroy); + CHROMEGTK_VIRTUAL_CALLBACK_0(NativeWidgetGtk, void, OnShow); + CHROMEGTK_VIRTUAL_CALLBACK_0(NativeWidgetGtk, void, OnMap); + CHROMEGTK_VIRTUAL_CALLBACK_0(NativeWidgetGtk, void, OnHide); + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, gboolean, OnWindowStateEvent, + GdkEventWindowState*); + CHROMEGTK_VIRTUAL_CALLBACK_1(NativeWidgetGtk, gboolean, OnConfigureEvent, + GdkEventConfigure*); + + // Invoked when the widget is destroyed and right before the object + // destruction. Useful for overriding. + virtual void OnDestroyed(GObject *where_the_object_was); + static void OnDestroyedThunk(gpointer data, GObject *where_the_object_was) { + reinterpret_cast<NativeWidgetGtk*>(data)->OnDestroyed(where_the_object_was); + } + + // Invoked when gtk grab is stolen by other GtkWidget in the same + // application. + virtual void HandleGtkGrabBroke(); + + const internal::NativeWidgetDelegate* delegate() const { return delegate_; } + internal::NativeWidgetDelegate* delegate() { return delegate_; } + + private: + class DropObserver; + friend class DropObserver; + + // Overridden from ui::CompositorDelegate + virtual void ScheduleDraw(); + + // Overridden from internal::InputMethodDelegate + virtual void DispatchKeyEventPostIME(const KeyEvent& key) OVERRIDE; + + void SetInitParams(const Widget::InitParams& params); + + // This is called only when the window is transparent. + CHROMEGTK_CALLBACK_1(NativeWidgetGtk, gboolean, OnWindowPaint, + GdkEventExpose*); + + // Callbacks for expose event on child widgets. See the description of + // RegisterChildChildExposeHandler. + void OnChildExpose(GtkWidget* child); + static gboolean ChildExposeHandler(GtkWidget* widget, GdkEventExpose* event); + + // Creates the GtkWidget. + void CreateGtkWidget(const Widget::InitParams& params); + + // Invoked from create widget to enable the various bits needed for a + // transparent background. This is only invoked if MakeTransparent has been + // invoked. + void ConfigureWidgetForTransparentBackground(GtkWidget* parent); + + // Invoked from create widget to enable the various bits needed for a + // window which doesn't receive events. + void ConfigureWidgetForIgnoreEvents(); + + // A utility function to draw a transparent background onto the |widget|. + static void DrawTransparentBackground(GtkWidget* widget, + GdkEventExpose* event); + + // Asks the delegate if any to save the window's location and size. + void SaveWindowPosition(); + + // A delegate implementation that handles events received here. + // See class documentation for Widget in widget.h for a note about ownership. + internal::NativeWidgetDelegate* delegate_; + + // Our native views. If we're a window/popup, then widget_ is the window and + // window_contents_ is a GtkFixed. If we're not a window/popup, then widget_ + // and window_contents_ point to the same GtkFixed. + GtkWidget* widget_; + GtkWidget* window_contents_; + + // Child GtkWidgets created with no parent need to be parented to a valid top + // level window otherwise Gtk throws a fit. |null_parent_| is an invisible + // popup that such GtkWidgets are parented to. + static GtkWidget* null_parent_; + + // True if the widget is a child of some other widget. + bool child_; + + // The TooltipManager. + // WARNING: RootView's destructor calls into the TooltipManager. As such, this + // must be destroyed AFTER root_view_. + scoped_ptr<TooltipManager> tooltip_manager_; + + scoped_ptr<DropTargetGtk> drop_target_; + + // The following factory is used to delay destruction. + base::WeakPtrFactory<NativeWidgetGtk> close_widget_factory_; + + // See class documentation for Widget in widget.h for a note about ownership. + Widget::InitParams::Ownership ownership_; + + // See description above make_transparent for details. + bool transparent_; + + // Makes the window pass all events through to any windows behind it. + // Set during SetInitParams before the widget is created. The actual work of + // making the window ignore events is done by ConfigureWidgetForIgnoreEvents. + bool ignore_events_; + + // See note in DropObserver for details on this. + bool ignore_drag_leave_; + + unsigned char opacity_; + + // This is non-null during the life of DoDrag and contains the actual data + // for the drag. + const OSExchangeDataProviderGtk* drag_data_; + + // True to enable debug painting. Enabling causes the damaged + // region to be painted to flash in red. + static bool debug_paint_enabled_; + + // State of the window, such as fullscreen, hidden... + GdkWindowState window_state_; + + // Are we active? + bool is_active_; + + // See make_transient_to_parent for a description. + bool transient_to_parent_; + + // Last size supplied to OnSizeAllocate. We cache this as any time the + // size of a GtkWidget changes size_allocate is called, even if the size + // didn't change. If we didn't cache this and ignore calls when the size + // hasn't changed, we can end up getting stuck in a never ending loop. + gfx::Size size_; + + // This is initially false and when the first focus-in event is received this + // is set to true and no additional processing is done. Subsequently when + // focus-in is received we do the normal focus manager processing. + // + // This behavior is necessitated by Gtk/X sending focus events + // asynchronously. The initial sequence for windows is typically: show, + // request focus on some widget. Because of async events on Gtk this becomes + // show, request focus, get focus in event which ends up clearing focus + // (first request to FocusManager::RestoreFocusedView ends up clearing focus). + bool got_initial_focus_in_; + + // If true, we've received a focus-in event. If false we've received a + // focus-out event. We can get multiple focus-out events in a row, we use + // this to determine whether we should process the event. + bool has_focus_; + + // If true, the window stays on top of the screen. This is only used + // for types other than TYPE_CHILD. + bool always_on_top_; + + // If true, we enable the content widget's double buffering. + // This is false by default. + bool is_double_buffered_; + + // Indicates if we should handle the upcoming Alt key release event. + bool should_handle_menu_key_release_; + + // Valid for the lifetime of StartDragForViewFromMouseEvent, indicates the + // view the drag started from. + View* dragged_view_; + + // If the widget has ever been painted. This is used to guarantee + // that window manager shows the window only after the window is painted. + bool painted_; + + // The compositor for accelerated drawing. + scoped_refptr<ui::Compositor> compositor_; + + // Have we done a pointer grab? + bool has_pointer_grab_; + + // Have we done a keyboard grab? + bool has_keyboard_grab_; + + // ID of the 'grab-notify' signal. If non-zero we're listening for + // 'grab-notify' events. + glong grab_notify_signal_id_; + + // If we were created for a menu. + bool is_menu_; + + scoped_ptr<ui::GtkSignalRegistrar> signal_registrar_; + + DISALLOW_COPY_AND_ASSIGN(NativeWidgetGtk); +}; + +} // namespace views + +#endif // VIEWS_WIDGET_NATIVE_WIDGET_GTK_H_ diff --git a/views/widget/native_widget_private.h b/views/widget/native_widget_private.h new file mode 100644 index 0000000..df5bad3 --- /dev/null +++ b/views/widget/native_widget_private.h @@ -0,0 +1,216 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_NATIVE_WIDGET_PRIVATE_H_ +#define VIEWS_WIDGET_NATIVE_WIDGET_PRIVATE_H_ +#pragma once + +#include "base/string16.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/views/ime/input_method_delegate.h" +#include "views/widget/native_widget.h" + +namespace gfx { +class Rect; +} + +namespace ui { +class OSExchangeData; +} + +namespace views { +class InputMethod; +class TooltipManager; +namespace internal { + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetPrivate interface +// +// A NativeWidget subclass internal to views that provides Widget a conduit for +// communication with a backend-specific native widget implementation. +// +// Many of the methods here are pass-thrus for Widget, and as such there is no +// documentation for them here. In that case, see methods of the same name in +// widget.h. +// +// IMPORTANT: This type is intended for use only by the views system and for +// NativeWidget implementations. This file should not be included +// in code that does not fall into one of these use cases. +// +class VIEWS_EXPORT NativeWidgetPrivate : public NativeWidget, + public internal::InputMethodDelegate { + public: + virtual ~NativeWidgetPrivate() {} + + // Creates an appropriate default NativeWidgetPrivate implementation for the + // current OS/circumstance. + static NativeWidgetPrivate* CreateNativeWidget( + internal::NativeWidgetDelegate* delegate); + + static NativeWidgetPrivate* GetNativeWidgetForNativeView( + gfx::NativeView native_view); + static NativeWidgetPrivate* GetNativeWidgetForNativeWindow( + gfx::NativeWindow native_window); + + // Retrieves the top NativeWidgetPrivate in the hierarchy containing the given + // NativeView, or NULL if there is no NativeWidgetPrivate that contains it. + static NativeWidgetPrivate* GetTopLevelNativeWidget( + gfx::NativeView native_view); + + static void GetAllChildWidgets(gfx::NativeView native_view, + Widget::Widgets* children); + static void ReparentNativeView(gfx::NativeView native_view, + gfx::NativeView new_parent); + + // Returns true if any mouse button is currently down. + static bool IsMouseButtonDown(); + + // Initializes the NativeWidget. + virtual void InitNativeWidget(const Widget::InitParams& params) = 0; + + // Returns a NonClientFrameView for the widget's NonClientView, or NULL if + // the NativeWidget wants no special NonClientFrameView. + virtual NonClientFrameView* CreateNonClientFrameView() = 0; + + virtual void UpdateFrameAfterFrameChange() = 0; + virtual bool ShouldUseNativeFrame() const = 0; + virtual void FrameTypeChanged() = 0; + + // Returns the Widget associated with this NativeWidget. This function is + // guaranteed to return non-NULL for the lifetime of the NativeWidget. + virtual Widget* GetWidget() = 0; + virtual const Widget* GetWidget() const = 0; + + // Returns the NativeView/Window associated with this NativeWidget. + virtual gfx::NativeView GetNativeView() const = 0; + virtual gfx::NativeWindow GetNativeWindow() const = 0; + + // Returns the topmost Widget in a hierarchy. + virtual Widget* GetTopLevelWidget() = 0; + + // Returns the Compositor, or NULL if there isn't one associated with this + // NativeWidget. + virtual const ui::Compositor* GetCompositor() const = 0; + virtual ui::Compositor* GetCompositor() = 0; + + // See description in View for details. + virtual void CalculateOffsetToAncestorWithLayer(gfx::Point* offset, + ui::Layer** layer_parent) = 0; + virtual void ReorderLayers() = 0; + + // Notifies the NativeWidget that a view was removed from the Widget's view + // hierarchy. + virtual void ViewRemoved(View* view) = 0; + + // Sets/Gets a native window property on the underlying native window object. + // Returns NULL if the property does not exist. Setting the property value to + // NULL removes the property. + virtual void SetNativeWindowProperty(const char* name, void* value) = 0; + virtual void* GetNativeWindowProperty(const char* name) const = 0; + + // Returns the native widget's tooltip manager. Called from the View hierarchy + // to update tooltips. + virtual TooltipManager* GetTooltipManager() const = 0; + + // Returns true if a system screen reader is active for the NativeWidget. + virtual bool IsScreenReaderActive() const = 0; + + // Notify native Accessibility clients of an event. + virtual void SendNativeAccessibilityEvent( + View* view, + ui::AccessibilityTypes::Event event_type) = 0; + + // Sets or releases event capturing for this native widget. + virtual void SetMouseCapture() = 0; + virtual void ReleaseMouseCapture() = 0; + + // Returns true if this native widget is capturing mouse events. + virtual bool HasMouseCapture() const = 0; + + // Returns the InputMethod for this native widget. + // Note that all widgets in a widget hierarchy share the same input method. + // TODO(suzhe): rename to GetInputMethod() when NativeWidget implementation + // class doesn't inherit Widget anymore. + virtual InputMethod* CreateInputMethod() = 0; + + + // Centers the window and sizes it to the specified size. + virtual void CenterWindow(const gfx::Size& size) = 0; + + // Retrieves the window's current restored bounds and "show" state, for + // persisting. + virtual void GetWindowPlacement( + gfx::Rect* bounds, + ui::WindowShowState* show_state) const = 0; + + // Sets the NativeWindow title. + virtual void SetWindowTitle(const string16& title) = 0; + + // Sets the Window icons. |window_icon| is a 16x16 icon suitable for use in + // a title bar. |app_icon| is a larger size for use in the host environment + // app switching UI. + virtual void SetWindowIcons(const SkBitmap& window_icon, + const SkBitmap& app_icon) = 0; + + // Update native accessibility properties on the native window. + virtual void SetAccessibleName(const string16& name) = 0; + virtual void SetAccessibleRole(ui::AccessibilityTypes::Role role) = 0; + virtual void SetAccessibleState(ui::AccessibilityTypes::State state) = 0; + + // Makes the NativeWindow modal. + virtual void BecomeModal() = 0; + + // See method documentation in Widget. + virtual gfx::Rect GetWindowScreenBounds() const = 0; + virtual gfx::Rect GetClientAreaScreenBounds() const = 0; + virtual gfx::Rect GetRestoredBounds() const = 0; + virtual void SetBounds(const gfx::Rect& bounds) = 0; + virtual void SetSize(const gfx::Size& size) = 0; + virtual void MoveAbove(gfx::NativeView native_view) = 0; + virtual void MoveToTop() = 0; + virtual void SetShape(gfx::NativeRegion shape) = 0; + virtual void Close() = 0; + virtual void CloseNow() = 0; + virtual void EnableClose(bool enable) = 0; + virtual void Show() = 0; + virtual void Hide() = 0; + // Invoked if the initial show should maximize the window. |restored_bounds| + // is the bounds of the window when not maximized. + virtual void ShowMaximizedWithBounds(const gfx::Rect& restored_bounds) = 0; + virtual void ShowWithWindowState(ui::WindowShowState show_state) = 0; + virtual bool IsVisible() const = 0; + virtual void Activate() = 0; + virtual void Deactivate() = 0; + virtual bool IsActive() const = 0; + virtual void SetAlwaysOnTop(bool always_on_top) = 0; + virtual void Maximize() = 0; + virtual void Minimize() = 0; + virtual bool IsMaximized() const = 0; + virtual bool IsMinimized() const = 0; + virtual void Restore() = 0; + virtual void SetFullscreen(bool fullscreen) = 0; + virtual bool IsFullscreen() const = 0; + virtual void SetOpacity(unsigned char opacity) = 0; + virtual void SetUseDragFrame(bool use_drag_frame) = 0; + virtual bool IsAccessibleWidget() const = 0; + virtual void RunShellDrag(View* view, + const ui::OSExchangeData& data, + int operation) = 0; + virtual void SchedulePaintInRect(const gfx::Rect& rect) = 0; + virtual void SetCursor(gfx::NativeCursor cursor) = 0; + virtual void ClearNativeFocus() = 0; + virtual void FocusNativeView(gfx::NativeView native_view) = 0; + virtual bool ConvertPointFromAncestor( + const Widget* ancestor, gfx::Point* point) const = 0; + virtual gfx::Rect GetWorkAreaBoundsInScreen() const = 0; + virtual void SetInactiveRenderingDisabled(bool value) = 0; + + // Overridden from NativeWidget: + virtual internal::NativeWidgetPrivate* AsNativeWidgetPrivate() OVERRIDE; +}; + +} // namespace internal +} // namespace views + +#endif // VIEWS_WIDGET_NATIVE_WIDGET_PRIVATE_H_ diff --git a/views/widget/native_widget_test_utils.h b/views/widget/native_widget_test_utils.h new file mode 100644 index 0000000..9fe73f6 --- /dev/null +++ b/views/widget/native_widget_test_utils.h @@ -0,0 +1,22 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_NATIVE_WIDGET_TEST_UTILS_H_ +#define VIEWS_WIDGET_NATIVE_WIDGET_TEST_UTILS_H_ +#pragma once + +namespace views { +namespace internal { + +class NativeWidgetPrivate; + +// Create dummy widgets for use in testing. Caller owns the returned +// |NativeWidgetPrivate| object which, in turn, owns the associated Widget. +NativeWidgetPrivate* CreateNativeWidget(); +NativeWidgetPrivate* CreateNativeSubWidget(); + +} // namespace internal +} // namespace views + +#endif // VIEWS_WIDGET_NATIVE_WIDGET_TEST_UTILS_H_ diff --git a/views/widget/native_widget_test_utils_aura.cc b/views/widget/native_widget_test_utils_aura.cc new file mode 100644 index 0000000..7e8926a --- /dev/null +++ b/views/widget/native_widget_test_utils_aura.cc @@ -0,0 +1,37 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/native_widget_test_utils.h" + +#include "views/view.h" +#include "views/widget/native_widget_private.h" +#include "views/widget/widget.h" + +namespace views { +namespace internal { + +namespace { + +NativeWidgetPrivate* CreateNativeWidgetOfType(Widget::InitParams::Type type) { + Widget* widget = new Widget; + Widget::InitParams params(type); + params.ownership = views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET; + params.child = false; // Implicitly set to true by ctor with TYPE_CONTROL. + params.bounds = gfx::Rect(10, 10, 200, 200); + widget->Init(params); + return widget->native_widget_private(); +} + +} // namespace + +NativeWidgetPrivate* CreateNativeWidget() { + return CreateNativeWidgetOfType(Widget::InitParams::TYPE_POPUP); +} + +NativeWidgetPrivate* CreateNativeSubWidget() { + return CreateNativeWidgetOfType(Widget::InitParams::TYPE_CONTROL); +} + +} // namespace internal +} // namespace ui diff --git a/views/widget/native_widget_test_utils_gtk.cc b/views/widget/native_widget_test_utils_gtk.cc new file mode 100644 index 0000000..3217f99 --- /dev/null +++ b/views/widget/native_widget_test_utils_gtk.cc @@ -0,0 +1,36 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/native_widget_test_utils.h" + +#include "views/view.h" +#include "views/widget/native_widget_private.h" +#include "views/widget/widget.h" + +namespace views { +namespace internal { + +namespace { + +NativeWidgetPrivate* CreateNativeWidgetOfType(Widget::InitParams::Type type) { + Widget* widget = new Widget; + Widget::InitParams params(type); + params.ownership = views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET; + params.bounds = gfx::Rect(10, 10, 200, 200); + widget->Init(params); + return widget->native_widget_private(); +} + +} // namespace + +NativeWidgetPrivate* CreateNativeWidget() { + return CreateNativeWidgetOfType(Widget::InitParams::TYPE_POPUP); +} + +NativeWidgetPrivate* CreateNativeSubWidget() { + return CreateNativeWidgetOfType(Widget::InitParams::TYPE_CONTROL); +} + +} // namespace internal +} // namespace ui diff --git a/views/widget/native_widget_test_utils_win.cc b/views/widget/native_widget_test_utils_win.cc new file mode 100644 index 0000000..7e8926a --- /dev/null +++ b/views/widget/native_widget_test_utils_win.cc @@ -0,0 +1,37 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/native_widget_test_utils.h" + +#include "views/view.h" +#include "views/widget/native_widget_private.h" +#include "views/widget/widget.h" + +namespace views { +namespace internal { + +namespace { + +NativeWidgetPrivate* CreateNativeWidgetOfType(Widget::InitParams::Type type) { + Widget* widget = new Widget; + Widget::InitParams params(type); + params.ownership = views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET; + params.child = false; // Implicitly set to true by ctor with TYPE_CONTROL. + params.bounds = gfx::Rect(10, 10, 200, 200); + widget->Init(params); + return widget->native_widget_private(); +} + +} // namespace + +NativeWidgetPrivate* CreateNativeWidget() { + return CreateNativeWidgetOfType(Widget::InitParams::TYPE_POPUP); +} + +NativeWidgetPrivate* CreateNativeSubWidget() { + return CreateNativeWidgetOfType(Widget::InitParams::TYPE_CONTROL); +} + +} // namespace internal +} // namespace ui diff --git a/views/widget/native_widget_unittest.cc b/views/widget/native_widget_unittest.cc new file mode 100644 index 0000000..7fc3818 --- /dev/null +++ b/views/widget/native_widget_unittest.cc @@ -0,0 +1,83 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/views/test/views_test_base.h" +#include "views/controls/native/native_view_host.h" +#include "views/view.h" +#include "views/widget/native_widget_private.h" +#include "views/widget/native_widget_test_utils.h" +#include "views/widget/widget.h" + +namespace views { + +class ScopedTestWidget { + public: + ScopedTestWidget(internal::NativeWidgetPrivate* native_widget) + : native_widget_(native_widget) { + } + ~ScopedTestWidget() { + // |CloseNow| deletes both |native_widget_| and its associated + // |Widget|. + native_widget_->GetWidget()->CloseNow(); + } + + internal::NativeWidgetPrivate* operator->() const { + return native_widget_; + } + internal::NativeWidgetPrivate* get() const { return native_widget_; } + + private: + internal::NativeWidgetPrivate* native_widget_; + DISALLOW_COPY_AND_ASSIGN(ScopedTestWidget); +}; + +class NativeWidgetTest : public ViewsTestBase { + public: + NativeWidgetTest() {} + virtual ~NativeWidgetTest() {} + + private: + DISALLOW_COPY_AND_ASSIGN(NativeWidgetTest); +}; + +TEST_F(NativeWidgetTest, CreateNativeWidget) { + ScopedTestWidget widget(internal::CreateNativeWidget()); + EXPECT_TRUE(widget->GetWidget()->GetNativeView() != NULL); +} + +TEST_F(NativeWidgetTest, GetNativeWidgetForNativeView) { + ScopedTestWidget widget(internal::CreateNativeWidget()); + EXPECT_EQ(widget.get(), + internal::NativeWidgetPrivate::GetNativeWidgetForNativeView( + widget->GetWidget()->GetNativeView())); +} + +// |widget| has the toplevel NativeWidget. +TEST_F(NativeWidgetTest, GetTopLevelNativeWidget1) { + ScopedTestWidget widget(internal::CreateNativeWidget()); + EXPECT_EQ(widget.get(), + internal::NativeWidgetPrivate::GetTopLevelNativeWidget( + widget->GetWidget()->GetNativeView())); +} + +// |toplevel_widget| has the toplevel NativeWidget. +TEST_F(NativeWidgetTest, GetTopLevelNativeWidget2) { + ScopedTestWidget toplevel_widget(internal::CreateNativeWidget()); + + // |toplevel_widget| owns |child_host|. + NativeViewHost* child_host = new NativeViewHost; + toplevel_widget->GetWidget()->SetContentsView(child_host); + + // |child_host| owns |child_widget|. + internal::NativeWidgetPrivate* child_widget = + internal::CreateNativeSubWidget(); + child_host->Attach(child_widget->GetWidget()->GetNativeView()); + + EXPECT_EQ(toplevel_widget.get(), + internal::NativeWidgetPrivate::GetTopLevelNativeWidget( + child_widget->GetWidget()->GetNativeView())); +} + +} // namespace views diff --git a/views/widget/native_widget_win.cc b/views/widget/native_widget_win.cc new file mode 100644 index 0000000..7c013f9 --- /dev/null +++ b/views/widget/native_widget_win.cc @@ -0,0 +1,2524 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/native_widget_win.h" + +#include <dwmapi.h> +#include <shellapi.h> + +#include <algorithm> + +#include "base/bind.h" +#include "base/string_util.h" +#include "base/system_monitor/system_monitor.h" +#include "base/win/scoped_gdi_object.h" +#include "base/win/win_util.h" +#include "base/win/windows_version.h" +#include "ui/base/dragdrop/drag_drop_types.h" +#include "ui/base/dragdrop/drag_source.h" +#include "ui/base/dragdrop/os_exchange_data.h" +#include "ui/base/dragdrop/os_exchange_data_provider_win.h" +#include "ui/base/keycodes/keyboard_code_conversion_win.h" +#include "ui/base/l10n/l10n_util_win.h" +#include "ui/base/theme_provider.h" +#include "ui/base/view_prop.h" +#include "ui/base/win/hwnd_util.h" +#include "ui/base/win/mouse_wheel_util.h" +#include "ui/gfx/canvas_skia.h" +#include "ui/gfx/canvas_skia_paint.h" +#include "ui/gfx/compositor/compositor.h" +#include "ui/gfx/icon_util.h" +#include "ui/gfx/native_theme_win.h" +#include "ui/gfx/path.h" +#include "ui/gfx/screen.h" +#include "ui/views/accessibility/native_view_accessibility_win.h" +#include "ui/views/focus/accelerator_handler.h" +#include "ui/views/focus/view_storage.h" +#include "ui/views/ime/input_method_win.h" +#include "ui/views/window/native_frame_view.h" +#include "views/controls/native_control_win.h" +#include "views/controls/textfield/native_textfield_views.h" +#include "views/views_delegate.h" +#include "views/widget/aero_tooltip_manager.h" +#include "views/widget/child_window_message_processor.h" +#include "views/widget/drop_target_win.h" +#include "views/widget/monitor_win.h" +#include "views/widget/native_widget_delegate.h" +#include "views/widget/root_view.h" +#include "views/widget/widget_delegate.h" + +#pragma comment(lib, "dwmapi.lib") + +using ui::ViewProp; + +namespace views { + +namespace { + +// 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. 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 +// ChildWindowMessageProcessor, 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 ProcessChildWindowMessage(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)) { + ChildWindowMessageProcessor* processor = + ChildWindowMessageProcessor::Get(control_hwnd); + if (processor) + return processor->ProcessMessage(message, w_param, l_param, l_result); + } + + return false; +} + +// Enumeration callback for NativeWidget::GetAllChildWidgets(). Called for each +// child HWND beneath the original HWND. +BOOL CALLBACK EnumerateChildWindowsForNativeWidgets(HWND hwnd, LPARAM l_param) { + Widget* widget = Widget::GetWidgetForNativeView(hwnd); + if (widget) { + Widget::Widgets* widgets = reinterpret_cast<Widget::Widgets*>(l_param); + widgets->insert(widget); + } + return TRUE; +} + +// Returns true if the WINDOWPOS data provided indicates the client area of +// the window may have changed size. This can be caused by the window being +// resized or its frame changing. +bool DidClientAreaSizeChange(const WINDOWPOS* window_pos) { + return !(window_pos->flags & SWP_NOSIZE) || + window_pos->flags & SWP_FRAMECHANGED; +} + +// Callback used to notify child windows that the top level window received a +// DWMCompositionChanged message. +BOOL CALLBACK SendDwmCompositionChanged(HWND window, LPARAM param) { + SendMessage(window, WM_DWMCOMPOSITIONCHANGED, 0, 0); + return TRUE; +} + +// Tells the window its frame (non-client area) has changed. +void SendFrameChanged(HWND window) { + SetWindowPos(window, NULL, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOCOPYBITS | + SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOREPOSITION | + SWP_NOSENDCHANGING | SWP_NOSIZE | SWP_NOZORDER); +} + +// Enables or disables the menu item for the specified command and menu. +void EnableMenuItem(HMENU menu, UINT command, bool enabled) { + UINT flags = MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_DISABLED | MF_GRAYED); + EnableMenuItem(menu, command, flags); +} + +BOOL CALLBACK EnumChildWindowsForRedraw(HWND hwnd, LPARAM lparam) { + DWORD process_id; + GetWindowThreadProcessId(hwnd, &process_id); + int flags = RDW_INVALIDATE | RDW_NOCHILDREN | RDW_FRAME; + if (process_id == GetCurrentProcessId()) + flags |= RDW_UPDATENOW; + RedrawWindow(hwnd, NULL, NULL, flags); + return TRUE; +} + +// See comments in OnNCPaint() for details of this struct. +struct ClipState { + // The window being painted. + HWND parent; + + // DC painting to. + HDC dc; + + // Origin of the window in terms of the screen. + int x; + int y; +}; + +// See comments in OnNCPaint() for details of this function. +static BOOL CALLBACK ClipDCToChild(HWND window, LPARAM param) { + ClipState* clip_state = reinterpret_cast<ClipState*>(param); + if (GetParent(window) == clip_state->parent && IsWindowVisible(window)) { + RECT bounds; + GetWindowRect(window, &bounds); + ExcludeClipRect(clip_state->dc, + bounds.left - clip_state->x, + bounds.top - clip_state->y, + bounds.right - clip_state->x, + bounds.bottom - clip_state->y); + } + return TRUE; +} + +// The thickness of an auto-hide taskbar in pixels. +static const int kAutoHideTaskbarThicknessPx = 2; + +bool GetMonitorAndRects(const RECT& rect, + HMONITOR* monitor, + gfx::Rect* monitor_rect, + gfx::Rect* work_area) { + DCHECK(monitor); + DCHECK(monitor_rect); + DCHECK(work_area); + *monitor = MonitorFromRect(&rect, MONITOR_DEFAULTTONULL); + if (!*monitor) + return false; + MONITORINFO monitor_info = { 0 }; + monitor_info.cbSize = sizeof(monitor_info); + GetMonitorInfo(*monitor, &monitor_info); + *monitor_rect = monitor_info.rcMonitor; + *work_area = monitor_info.rcWork; + return true; +} + +// Links the HWND to its NativeWidget. +const char* const kNativeWidgetKey = "__VIEWS_NATIVE_WIDGET__"; + +// A custom MSAA object id used to determine if a screen reader is actively +// listening for MSAA events. +const int kCustomObjectID = 1; + +const int kDragFrameWindowAlpha = 200; + +} // namespace + +// static +bool NativeWidgetWin::screen_reader_active_ = false; + +// A scoping class that prevents a window from being able to redraw in response +// to invalidations that may occur within it for the lifetime of the object. +// +// Why would we want such a thing? Well, it turns out Windows has some +// "unorthodox" behavior when it comes to painting its non-client areas. +// Occasionally, Windows will paint portions of the default non-client area +// right over the top of the custom frame. This is not simply fixed by handling +// WM_NCPAINT/WM_PAINT, with some investigation it turns out that this +// rendering is being done *inside* the default implementation of some message +// handlers and functions: +// . WM_SETTEXT +// . WM_SETICON +// . WM_NCLBUTTONDOWN +// . EnableMenuItem, called from our WM_INITMENU handler +// The solution is to handle these messages and call DefWindowProc ourselves, +// but prevent the window from being able to update itself for the duration of +// the call. We do this with this class, which automatically calls its +// associated Window's lock and unlock functions as it is created and destroyed. +// See documentation in those methods for the technique used. +// +// The lock only has an effect if the window was visible upon lock creation, as +// it doesn't guard against direct visiblility changes, and multiple locks may +// exist simultaneously to handle certain nested Windows messages. +// +// IMPORTANT: Do not use this scoping object for large scopes or periods of +// time! IT WILL PREVENT THE WINDOW FROM BEING REDRAWN! (duh). +// +// I would love to hear Raymond Chen's explanation for all this. And maybe a +// list of other messages that this applies to ;-) +class NativeWidgetWin::ScopedRedrawLock { + public: + explicit ScopedRedrawLock(NativeWidgetWin* widget) + : widget_(widget), + native_view_(widget_->GetNativeView()), + was_visible_(widget_->IsVisible()), + cancel_unlock_(false) { + if (was_visible_ && ::IsWindow(native_view_)) + widget_->LockUpdates(); + } + + ~ScopedRedrawLock() { + if (!cancel_unlock_ && was_visible_ && ::IsWindow(native_view_)) + widget_->UnlockUpdates(); + } + + // Cancel the unlock operation, call this if the Widget is being destroyed. + void CancelUnlockOperation() { cancel_unlock_ = true; } + + private: + // The widget having its style changed. + NativeWidgetWin* widget_; + // The widget's native view, cached to avoid action after window destruction. + gfx::NativeView native_view_; + // Records the widget visibility at the time of creation. + bool was_visible_; + // A flag indicating that the unlock operation was canceled. + bool cancel_unlock_; +}; + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetWin, public: + +NativeWidgetWin::NativeWidgetWin(internal::NativeWidgetDelegate* delegate) + : delegate_(delegate), + ALLOW_THIS_IN_INITIALIZER_LIST(close_widget_factory_(this)), + active_mouse_tracking_flags_(0), + use_layered_buffer_(false), + layered_alpha_(255), + ALLOW_THIS_IN_INITIALIZER_LIST(paint_layered_window_factory_(this)), + ownership_(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET), + can_update_layered_window_(true), + restore_focus_when_enabled_(false), + accessibility_view_events_index_(-1), + accessibility_view_events_(kMaxAccessibilityViewEvents), + previous_cursor_(NULL), + fullscreen_(false), + force_hidden_count_(0), + lock_updates_count_(0), + saved_window_style_(0), + ignore_window_pos_changes_(false), + ALLOW_THIS_IN_INITIALIZER_LIST(ignore_pos_changes_factory_(this)), + last_monitor_(NULL), + is_right_mouse_pressed_on_caption_(false), + restored_enabled_(false), + destroyed_(NULL), + has_non_client_view_(false) { +} + +NativeWidgetWin::~NativeWidgetWin() { + if (destroyed_ != NULL) + *destroyed_ = true; + + if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) + delete delegate_; + else + CloseNow(); +} + +// static +bool NativeWidgetWin::IsAeroGlassEnabled() { + if (base::win::GetVersion() < base::win::VERSION_VISTA) + return false; + // If composition is not enabled, we behave like on XP. + BOOL enabled = FALSE; + return SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled; +} + +// static +gfx::Font NativeWidgetWin::GetWindowTitleFont() { + NONCLIENTMETRICS ncm; + base::win::GetNonClientMetrics(&ncm); + l10n_util::AdjustUIFont(&(ncm.lfCaptionFont)); + base::win::ScopedHFONT caption_font(CreateFontIndirect(&(ncm.lfCaptionFont))); + return gfx::Font(caption_font); +} + +void NativeWidgetWin::Show(int show_state) { + ShowWindow(show_state); + // When launched from certain programs like bash and Windows Live Messenger, + // show_state is set to SW_HIDE, so we need to correct that condition. We + // don't just change show_state to SW_SHOWNORMAL because MSDN says we must + // always first call ShowWindow with the specified value from STARTUPINFO, + // otherwise all future ShowWindow calls will be ignored (!!#@@#!). Instead, + // we call ShowWindow again in this case. + if (show_state == SW_HIDE) { + show_state = SW_SHOWNORMAL; + ShowWindow(show_state); + } + + // We need to explicitly activate the window if we've been shown with a state + // that should activate, because if we're opened from a desktop shortcut while + // an existing window is already running it doesn't seem to be enough to use + // one of these flags to activate the window. + if (show_state == SW_SHOWNORMAL || show_state == SW_SHOWMAXIMIZED) + Activate(); + + SetInitialFocus(); +} + +View* NativeWidgetWin::GetAccessibilityViewEventAt(int id) { + // Convert from MSAA child id. + id = -(id + 1); + DCHECK(id >= 0 && id < kMaxAccessibilityViewEvents); + return accessibility_view_events_[id]; +} + +int NativeWidgetWin::AddAccessibilityViewEvent(View* view) { + accessibility_view_events_index_ = + (accessibility_view_events_index_ + 1) % kMaxAccessibilityViewEvents; + accessibility_view_events_[accessibility_view_events_index_] = view; + + // Convert to MSAA child id. + return -(accessibility_view_events_index_ + 1); +} + +void NativeWidgetWin::ClearAccessibilityViewEvent(View* view) { + for (std::vector<View*>::iterator it = accessibility_view_events_.begin(); + it != accessibility_view_events_.end(); + ++it) { + if (*it == view) + *it = NULL; + } +} + +void NativeWidgetWin::PushForceHidden() { + if (force_hidden_count_++ == 0) + Hide(); +} + +void NativeWidgetWin::PopForceHidden() { + if (--force_hidden_count_ <= 0) { + force_hidden_count_ = 0; + ShowWindow(SW_SHOW); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetWin, CompositorDelegate implementation: + +void NativeWidgetWin::ScheduleDraw() { + RECT rect; + ::GetClientRect(GetNativeView(), &rect); + InvalidateRect(GetNativeView(), &rect, FALSE); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetWin, NativeWidget implementation: + +void NativeWidgetWin::InitNativeWidget(const Widget::InitParams& params) { + SetInitParams(params); + + GetMonitorAndRects(params.bounds.ToRECT(), &last_monitor_, + &last_monitor_rect_, &last_work_area_); + + // Create the window. + WindowImpl::Init(params.GetParent(), params.bounds); +} + +NonClientFrameView* NativeWidgetWin::CreateNonClientFrameView() { + return GetWidget()->ShouldUseNativeFrame() ? + new NativeFrameView(GetWidget()) : NULL; +} + +void NativeWidgetWin::UpdateFrameAfterFrameChange() { + // We've either gained or lost a custom window region, so reset it now. + ResetWindowRegion(true); +} + +bool NativeWidgetWin::ShouldUseNativeFrame() const { + return IsAeroGlassEnabled(); +} + +void NativeWidgetWin::FrameTypeChanged() { + // Called when the frame type could possibly be changing (theme change or + // DWM composition change). + if (base::win::GetVersion() >= base::win::VERSION_VISTA) { + // We need to toggle the rendering policy of the DWM/glass frame as we + // change from opaque to glass. "Non client rendering enabled" means that + // the DWM's glass non-client rendering is enabled, which is why + // DWMNCRP_ENABLED is used for the native frame case. _DISABLED means the + // DWM doesn't render glass, and so is used in the custom frame case. + DWMNCRENDERINGPOLICY policy = GetWidget()->ShouldUseNativeFrame() ? + DWMNCRP_ENABLED : DWMNCRP_DISABLED; + DwmSetWindowAttribute(GetNativeView(), DWMWA_NCRENDERING_POLICY, + &policy, sizeof(DWMNCRENDERINGPOLICY)); + } + + // Send a frame change notification, since the non-client metrics have + // changed. + SendFrameChanged(GetNativeView()); + + // Update the non-client view with the correct frame view for the active frame + // type. + GetWidget()->non_client_view()->UpdateFrame(); + + // WM_DWMCOMPOSITIONCHANGED is only sent to top level windows, however we want + // to notify our children too, since we can have MDI child windows who need to + // update their appearance. + EnumChildWindows(GetNativeView(), &SendDwmCompositionChanged, NULL); +} + +Widget* NativeWidgetWin::GetWidget() { + return delegate_->AsWidget(); +} + +const Widget* NativeWidgetWin::GetWidget() const { + return delegate_->AsWidget(); +} + +gfx::NativeView NativeWidgetWin::GetNativeView() const { + return WindowImpl::hwnd(); +} + +gfx::NativeWindow NativeWidgetWin::GetNativeWindow() const { + return WindowImpl::hwnd(); +} + +Widget* NativeWidgetWin::GetTopLevelWidget() { + NativeWidgetPrivate* native_widget = GetTopLevelNativeWidget(GetNativeView()); + return native_widget ? native_widget->GetWidget() : NULL; +} + +const ui::Compositor* NativeWidgetWin::GetCompositor() const { + return compositor_.get(); +} + +ui::Compositor* NativeWidgetWin::GetCompositor() { + return compositor_.get(); +} + +void NativeWidgetWin::CalculateOffsetToAncestorWithLayer( + gfx::Point* offset, + ui::Layer** layer_parent) { +} + +void NativeWidgetWin::ReorderLayers() { +} + +void NativeWidgetWin::ViewRemoved(View* view) { + if (drop_target_.get()) + drop_target_->ResetTargetViewIfEquals(view); + + ClearAccessibilityViewEvent(view); +} + +void NativeWidgetWin::SetNativeWindowProperty(const char* name, void* value) { + // Remove the existing property (if any). + for (ViewProps::iterator i = props_.begin(); i != props_.end(); ++i) { + if ((*i)->Key() == name) { + props_.erase(i); + break; + } + } + + if (value) + props_.push_back(new ViewProp(hwnd(), name, value)); +} + +void* NativeWidgetWin::GetNativeWindowProperty(const char* name) const { + return ViewProp::GetValue(hwnd(), name); +} + +TooltipManager* NativeWidgetWin::GetTooltipManager() const { + return tooltip_manager_.get(); +} + +bool NativeWidgetWin::IsScreenReaderActive() const { + return screen_reader_active_; +} + +void NativeWidgetWin::SendNativeAccessibilityEvent( + View* view, + ui::AccessibilityTypes::Event event_type) { + // Now call the Windows-specific method to notify MSAA clients of this + // event. The widget gives us a temporary unique child ID to associate + // with this view so that clients can call get_accChild in + // NativeViewAccessibilityWin to retrieve the IAccessible associated + // with this view. + int child_id = AddAccessibilityViewEvent(view); + ::NotifyWinEvent(NativeViewAccessibilityWin::MSAAEvent(event_type), + GetNativeView(), OBJID_CLIENT, child_id); +} + +void NativeWidgetWin::SetMouseCapture() { + DCHECK(!HasMouseCapture()); + SetCapture(hwnd()); +} + +void NativeWidgetWin::ReleaseMouseCapture() { + ReleaseCapture(); +} + +bool NativeWidgetWin::HasMouseCapture() const { + return GetCapture() == hwnd(); +} + +InputMethod* NativeWidgetWin::CreateInputMethod() { + if (views::Widget::IsPureViews()) { + InputMethod* input_method = new InputMethodWin(this); + input_method->Init(GetWidget()); + return input_method; + } + return NULL; +} + +void NativeWidgetWin::CenterWindow(const gfx::Size& size) { + HWND parent = GetParent(); + if (!IsWindow()) + parent = ::GetWindow(GetNativeView(), GW_OWNER); + ui::CenterAndSizeWindow(parent, GetNativeView(), size, false); +} + +void NativeWidgetWin::GetWindowPlacement( + gfx::Rect* bounds, + ui::WindowShowState* show_state) const { + WINDOWPLACEMENT wp; + wp.length = sizeof(wp); + const bool succeeded = !!::GetWindowPlacement(GetNativeView(), &wp); + DCHECK(succeeded); + + if (bounds != NULL) { + MONITORINFO mi; + mi.cbSize = sizeof(mi); + const bool succeeded = !!GetMonitorInfo( + MonitorFromWindow(GetNativeView(), MONITOR_DEFAULTTONEAREST), &mi); + DCHECK(succeeded); + *bounds = gfx::Rect(wp.rcNormalPosition); + // Convert normal position from workarea coordinates to screen coordinates. + bounds->Offset(mi.rcWork.left - mi.rcMonitor.left, + mi.rcWork.top - mi.rcMonitor.top); + } + + if (show_state != NULL) { + if (wp.showCmd == SW_SHOWMAXIMIZED) + *show_state = ui::SHOW_STATE_MAXIMIZED; + else if (wp.showCmd == SW_SHOWMINIMIZED) + *show_state = ui::SHOW_STATE_MINIMIZED; + else + *show_state = ui::SHOW_STATE_NORMAL; + } +} + +void NativeWidgetWin::SetWindowTitle(const string16& title) { + SetWindowText(GetNativeView(), title.c_str()); + SetAccessibleName(title); +} + +void NativeWidgetWin::SetWindowIcons(const SkBitmap& window_icon, + const SkBitmap& app_icon) { + if (!window_icon.isNull()) { + HICON windows_icon = IconUtil::CreateHICONFromSkBitmap(window_icon); + // We need to make sure to destroy the previous icon, otherwise we'll leak + // these GDI objects until we crash! + HICON old_icon = reinterpret_cast<HICON>( + SendMessage(GetNativeView(), WM_SETICON, ICON_SMALL, + reinterpret_cast<LPARAM>(windows_icon))); + if (old_icon) + DestroyIcon(old_icon); + } + if (!app_icon.isNull()) { + HICON windows_icon = IconUtil::CreateHICONFromSkBitmap(app_icon); + HICON old_icon = reinterpret_cast<HICON>( + SendMessage(GetNativeView(), WM_SETICON, ICON_BIG, + reinterpret_cast<LPARAM>(windows_icon))); + if (old_icon) + DestroyIcon(old_icon); + } +} + +void NativeWidgetWin::SetAccessibleName(const string16& name) { + base::win::ScopedComPtr<IAccPropServices> pAccPropServices; + HRESULT hr = CoCreateInstance(CLSID_AccPropServices, NULL, CLSCTX_SERVER, + IID_IAccPropServices, reinterpret_cast<void**>(&pAccPropServices)); + if (SUCCEEDED(hr)) + hr = pAccPropServices->SetHwndPropStr(GetNativeView(), OBJID_CLIENT, + CHILDID_SELF, PROPID_ACC_NAME, + name.c_str()); +} + +void NativeWidgetWin::SetAccessibleRole(ui::AccessibilityTypes::Role role) { + base::win::ScopedComPtr<IAccPropServices> pAccPropServices; + HRESULT hr = CoCreateInstance(CLSID_AccPropServices, NULL, CLSCTX_SERVER, + IID_IAccPropServices, reinterpret_cast<void**>(&pAccPropServices)); + if (SUCCEEDED(hr)) { + VARIANT var; + if (role) { + var.vt = VT_I4; + var.lVal = NativeViewAccessibilityWin::MSAARole(role); + hr = pAccPropServices->SetHwndProp(GetNativeView(), OBJID_CLIENT, + CHILDID_SELF, PROPID_ACC_ROLE, var); + } + } +} + +void NativeWidgetWin::SetAccessibleState(ui::AccessibilityTypes::State state) { + base::win::ScopedComPtr<IAccPropServices> pAccPropServices; + HRESULT hr = CoCreateInstance(CLSID_AccPropServices, NULL, CLSCTX_SERVER, + IID_IAccPropServices, reinterpret_cast<void**>(&pAccPropServices)); + if (SUCCEEDED(hr)) { + VARIANT var; + if (state) { + var.vt = VT_I4; + var.lVal = NativeViewAccessibilityWin::MSAAState(state); + hr = pAccPropServices->SetHwndProp(GetNativeView(), OBJID_CLIENT, + CHILDID_SELF, PROPID_ACC_STATE, var); + } + } +} + +void NativeWidgetWin::BecomeModal() { + // We implement modality by crawling up the hierarchy of windows starting + // at the owner, disabling all of them so that they don't receive input + // messages. + HWND start = ::GetWindow(GetNativeView(), GW_OWNER); + while (start) { + ::EnableWindow(start, FALSE); + start = ::GetParent(start); + } +} + +gfx::Rect NativeWidgetWin::GetWindowScreenBounds() const { + RECT r; + GetWindowRect(&r); + return gfx::Rect(r); +} + +gfx::Rect NativeWidgetWin::GetClientAreaScreenBounds() const { + RECT r; + GetClientRect(&r); + POINT point = { r.left, r.top }; + ClientToScreen(hwnd(), &point); + return gfx::Rect(point.x, point.y, r.right - r.left, r.bottom - r.top); +} + +gfx::Rect NativeWidgetWin::GetRestoredBounds() const { + // If we're in fullscreen mode, we've changed the normal bounds to the monitor + // rect, so return the saved bounds instead. + if (IsFullscreen()) + return gfx::Rect(saved_window_info_.window_rect); + + gfx::Rect bounds; + GetWindowPlacement(&bounds, NULL); + return bounds; +} + +void NativeWidgetWin::SetBounds(const gfx::Rect& bounds) { + LONG style = GetWindowLong(GWL_STYLE); + if (style & WS_MAXIMIZE) + SetWindowLong(GWL_STYLE, style & ~WS_MAXIMIZE); + SetWindowPos(NULL, bounds.x(), bounds.y(), bounds.width(), bounds.height(), + SWP_NOACTIVATE | SWP_NOZORDER); +} + +void NativeWidgetWin::SetSize(const gfx::Size& size) { + SetWindowPos(NULL, 0, 0, size.width(), size.height(), + SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOMOVE); +} + +void NativeWidgetWin::MoveAbove(gfx::NativeView native_view) { + SetWindowPos(native_view, 0, 0, 0, 0, + SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE); +} + +void NativeWidgetWin::MoveToTop() { + NOTIMPLEMENTED(); +} + +void NativeWidgetWin::SetShape(gfx::NativeRegion region) { + SetWindowRgn(region, TRUE); +} + +void NativeWidgetWin::Close() { + if (!IsWindow()) + return; // No need to do anything. + + // Let's hide ourselves right away. + Hide(); + + // Modal dialog windows disable their owner windows; re-enable them now so + // they can activate as foreground windows upon this window's destruction. + RestoreEnabledIfNecessary(); + + if (!close_widget_factory_.HasWeakPtrs()) { + // 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, + base::Bind(&NativeWidgetWin::CloseNow, + close_widget_factory_.GetWeakPtr())); + } +} + +void NativeWidgetWin::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(hwnd()); +} + +void NativeWidgetWin::EnableClose(bool enable) { + // Disable the native frame's close button regardless of whether or not the + // native frame is in use, since this also affects the system menu. + EnableMenuItem(GetSystemMenu(GetNativeView(), false), SC_CLOSE, enable); + SendFrameChanged(GetNativeView()); +} + +void NativeWidgetWin::Show() { + if (!IsWindow()) + return; + + ShowWindow(SW_SHOWNOACTIVATE); + SetInitialFocus(); +} + +void NativeWidgetWin::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 NativeWidgetWin::ShowMaximizedWithBounds( + const gfx::Rect& restored_bounds) { + WINDOWPLACEMENT placement = { 0 }; + placement.length = sizeof(WINDOWPLACEMENT); + placement.showCmd = SW_SHOWMAXIMIZED; + placement.rcNormalPosition = restored_bounds.ToRECT(); + SetWindowPlacement(hwnd(), &placement); +} + +void NativeWidgetWin::ShowWithWindowState(ui::WindowShowState show_state) { + DWORD native_show_state; + switch (show_state) { + case ui::SHOW_STATE_INACTIVE: + native_show_state = SW_SHOWNOACTIVATE; + break; + case ui::SHOW_STATE_MAXIMIZED: + native_show_state = SW_SHOWMAXIMIZED; + break; + case ui::SHOW_STATE_MINIMIZED: + native_show_state = SW_SHOWMINIMIZED; + break; + default: + native_show_state = GetShowState(); + break; + } + Show(native_show_state); +} + +bool NativeWidgetWin::IsVisible() const { + return !!::IsWindowVisible(hwnd()); +} + +void NativeWidgetWin::Activate() { + if (IsMinimized()) + ::ShowWindow(GetNativeView(), SW_RESTORE); + ::SetWindowPos(GetNativeView(), HWND_TOP, 0, 0, 0, 0, + SWP_NOSIZE | SWP_NOMOVE); + SetForegroundWindow(GetNativeView()); +} + +void NativeWidgetWin::Deactivate() { + HWND hwnd = ::GetNextWindow(GetNativeView(), GW_HWNDNEXT); + if (hwnd) + ::SetForegroundWindow(hwnd); +} + +bool NativeWidgetWin::IsActive() const { + return GetActiveWindow() == hwnd(); +} + +void NativeWidgetWin::SetAlwaysOnTop(bool on_top) { + ::SetWindowPos(GetNativeView(), on_top ? HWND_TOPMOST : HWND_NOTOPMOST, + 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); +} + +void NativeWidgetWin::Maximize() { + ExecuteSystemMenuCommand(SC_MAXIMIZE); +} + +void NativeWidgetWin::Minimize() { + ExecuteSystemMenuCommand(SC_MINIMIZE); + + delegate_->OnNativeBlur(NULL); +} + +bool NativeWidgetWin::IsMaximized() const { + return !!::IsZoomed(GetNativeView()); +} + +bool NativeWidgetWin::IsMinimized() const { + return !!::IsIconic(GetNativeView()); +} + +void NativeWidgetWin::Restore() { + ExecuteSystemMenuCommand(SC_RESTORE); +} + +void NativeWidgetWin::SetFullscreen(bool fullscreen) { + if (fullscreen_ == fullscreen) + return; // Nothing to do. + + // Reduce jankiness during the following position changes by hiding the window + // until it's in the final position. + PushForceHidden(); + + // Size/position/style window appropriately. + if (!fullscreen_) { + // Save current window information. We force the window into restored mode + // before going fullscreen because Windows doesn't seem to hide the + // taskbar if the window is in the maximized state. + saved_window_info_.maximized = IsMaximized(); + if (saved_window_info_.maximized) + Restore(); + saved_window_info_.style = GetWindowLong(GWL_STYLE); + saved_window_info_.ex_style = GetWindowLong(GWL_EXSTYLE); + GetWindowRect(&saved_window_info_.window_rect); + } + + fullscreen_ = fullscreen; + + if (fullscreen_) { + // Set new window style and size. + MONITORINFO monitor_info; + monitor_info.cbSize = sizeof(monitor_info); + GetMonitorInfo(MonitorFromWindow(GetNativeView(), MONITOR_DEFAULTTONEAREST), + &monitor_info); + gfx::Rect monitor_rect(monitor_info.rcMonitor); + SetWindowLong(GWL_STYLE, + saved_window_info_.style & ~(WS_CAPTION | WS_THICKFRAME)); + SetWindowLong(GWL_EXSTYLE, + saved_window_info_.ex_style & ~(WS_EX_DLGMODALFRAME | + WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE)); + SetWindowPos(NULL, monitor_rect.x(), monitor_rect.y(), + monitor_rect.width(), monitor_rect.height(), + SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + } else { + // Reset original window style and size. The multiple window size/moves + // here are ugly, but if SetWindowPos() doesn't redraw, the taskbar won't be + // repainted. Better-looking methods welcome. + gfx::Rect new_rect(saved_window_info_.window_rect); + SetWindowLong(GWL_STYLE, saved_window_info_.style); + SetWindowLong(GWL_EXSTYLE, saved_window_info_.ex_style); + SetWindowPos(NULL, new_rect.x(), new_rect.y(), new_rect.width(), + new_rect.height(), + SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED); + if (saved_window_info_.maximized) + Maximize(); + } + + // Undo our anti-jankiness hacks. + PopForceHidden(); +} + +bool NativeWidgetWin::IsFullscreen() const { + return fullscreen_; +} + +void NativeWidgetWin::SetOpacity(unsigned char opacity) { + layered_alpha_ = static_cast<BYTE>(opacity); +} + +void NativeWidgetWin::SetUseDragFrame(bool use_drag_frame) { + if (use_drag_frame) { + // Make the frame slightly transparent during the drag operation. + drag_frame_saved_window_style_ = GetWindowLong(GWL_STYLE); + drag_frame_saved_window_ex_style_ = GetWindowLong(GWL_EXSTYLE); + SetWindowLong(GWL_EXSTYLE, + drag_frame_saved_window_ex_style_ | WS_EX_LAYERED); + // Remove the captions tyle so the window doesn't have window controls for a + // more "transparent" look. + SetWindowLong(GWL_STYLE, drag_frame_saved_window_style_ & ~WS_CAPTION); + SetLayeredWindowAttributes(GetNativeWindow(), RGB(0xFF, 0xFF, 0xFF), + kDragFrameWindowAlpha, LWA_ALPHA); + } else { + SetWindowLong(GWL_STYLE, drag_frame_saved_window_style_); + SetWindowLong(GWL_EXSTYLE, drag_frame_saved_window_ex_style_); + } +} + +bool NativeWidgetWin::IsAccessibleWidget() const { + return screen_reader_active_; +} + +void NativeWidgetWin::RunShellDrag(View* view, + const ui::OSExchangeData& data, + int operation) { + scoped_refptr<ui::DragSource> drag_source(new ui::DragSource); + DWORD effects; + DoDragDrop(ui::OSExchangeDataProviderWin::GetIDataObject(data), drag_source, + ui::DragDropTypes::DragOperationToDropEffect(operation), &effects); +} + +void NativeWidgetWin::SchedulePaintInRect(const gfx::Rect& rect) { + if (use_layered_buffer_) { + // We must update the back-buffer immediately, since Windows' handling of + // invalid rects is somewhat mysterious. + invalid_rect_ = invalid_rect_.Union(rect); + + // In some situations, such as drag and drop, when Windows itself runs a + // nested message loop our message loop appears to be starved and we don't + // receive calls to DidProcessMessage(). This only seems to affect layered + // windows, so we schedule a redraw manually using a task, since those never + // seem to be starved. Also, wtf. + if (!paint_layered_window_factory_.HasWeakPtrs()) { + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&NativeWidgetWin::RedrawLayeredWindowContents, + paint_layered_window_factory_.GetWeakPtr())); + } + } else { + // InvalidateRect() expects client coordinates. + RECT r = rect.ToRECT(); + InvalidateRect(hwnd(), &r, FALSE); + } +} + +void NativeWidgetWin::SetCursor(gfx::NativeCursor cursor) { + if (cursor) { + previous_cursor_ = ::SetCursor(cursor); + } else if (previous_cursor_) { + ::SetCursor(previous_cursor_); + previous_cursor_ = NULL; + } +} + +void NativeWidgetWin::ClearNativeFocus() { + ::SetFocus(GetNativeView()); +} + +void NativeWidgetWin::FocusNativeView(gfx::NativeView native_view) { + // Only reset focus if hwnd is not already focused. + if (native_view && ::GetFocus() != native_view) + ::SetFocus(native_view); +} + +bool NativeWidgetWin::ConvertPointFromAncestor( + const Widget* ancestor, gfx::Point* point) const { + NOTREACHED(); + return false; +} + +gfx::Rect NativeWidgetWin::GetWorkAreaBoundsInScreen() const { + return gfx::Screen::GetMonitorWorkAreaNearestWindow(GetNativeView()); +} + +void NativeWidgetWin::SetInactiveRenderingDisabled(bool value) { +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetWin, MessageLoop::Observer implementation: + +base::EventStatus NativeWidgetWin::WillProcessEvent( + const base::NativeEvent& event) { + return base::EVENT_CONTINUE; +} + +void NativeWidgetWin::DidProcessEvent(const base::NativeEvent& event) { + RedrawInvalidRect(); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetWin, WindowImpl overrides: + +HICON NativeWidgetWin::GetDefaultWindowIcon() const { + if (ViewsDelegate::views_delegate) + return ViewsDelegate::views_delegate->GetDefaultWindowIcon(); + return NULL; +} + +LRESULT NativeWidgetWin::OnWndProc(UINT message, WPARAM w_param, + LPARAM l_param) { + HWND window = hwnd(); + 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 NativeWidgetWin. + if (ProcessChildWindowMessage(message, w_param, l_param, &result)) + return result; + + // Otherwise we handle everything else. + if (!ProcessWindowMessage(window, message, w_param, l_param, result)) + result = DefWindowProc(window, message, w_param, l_param); + if (message == WM_NCDESTROY) { + MessageLoopForUI::current()->RemoveObserver(this); + OnFinalMessage(window); + } + + // Only top level widget should store/restore focus. + if (message == WM_ACTIVATE && GetWidget()->is_top_level()) + PostProcessActivateMessage(this, LOWORD(w_param)); + if (message == WM_ENABLE && restore_focus_when_enabled_) { + // This path should be executed only for top level as + // restore_focus_when_enabled_ is set in PostProcessActivateMessage. + DCHECK(GetWidget()->is_top_level()); + restore_focus_when_enabled_ = false; + GetWidget()->GetFocusManager()->RestoreFocusedView(); + } + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetWin, protected: + +// Message handlers ------------------------------------------------------------ + +void NativeWidgetWin::OnActivate(UINT action, BOOL minimized, HWND window) { + SetMsgHandled(FALSE); +} + +void NativeWidgetWin::OnActivateApp(BOOL active, DWORD thread_id) { + if (GetWidget()->non_client_view() && !active && + thread_id != GetCurrentThreadId()) { + // Another application was activated, we should reset any state that + // disables inactive rendering now. + delegate_->EnableInactiveRendering(); + // Also update the native frame if it is rendering the non-client area. + if (GetWidget()->ShouldUseNativeFrame()) + DefWindowProcWithRedrawLock(WM_NCACTIVATE, FALSE, 0); + } +} + +LRESULT NativeWidgetWin::OnAppCommand(HWND window, + short app_command, + WORD device, + int keystate) { + // We treat APPCOMMAND ids as an extension of our command namespace, and just + // let the delegate figure out what to do... + BOOL is_handled = (GetWidget()->widget_delegate() && + GetWidget()->widget_delegate()->ExecuteWindowsCommand(app_command)) ? + TRUE : FALSE; + SetMsgHandled(is_handled); + // Make sure to return TRUE if the event was handled or in some cases the + // system will execute the default handler which can cause bugs like going + // forward or back two pages instead of one. + return is_handled; +} + +void NativeWidgetWin::OnCancelMode() { + SetMsgHandled(FALSE); +} + +void NativeWidgetWin::OnCaptureChanged(HWND hwnd) { + delegate_->OnMouseCaptureLost(); +} + +void NativeWidgetWin::OnClose() { + GetWidget()->Close(); +} + +void NativeWidgetWin::OnCommand(UINT notification_code, + int command_id, + HWND window) { + // If the notification code is > 1 it means it is control specific and we + // should ignore it. + if (notification_code > 1 || + GetWidget()->widget_delegate()->ExecuteWindowsCommand(command_id)) { + SetMsgHandled(FALSE); + } +} + +LRESULT NativeWidgetWin::OnCreate(CREATESTRUCT* create_struct) { + SetNativeWindowProperty(kNativeWidgetKey, this); + CHECK_EQ(this, GetNativeWidgetForNativeView(hwnd())); + + use_layered_buffer_ = !!(window_ex_style() & WS_EX_LAYERED); + + // Attempt to detect screen readers by sending an event with our custom id. + if (!IsAccessibleWidget()) + NotifyWinEvent(EVENT_SYSTEM_ALERT, hwnd(), kCustomObjectID, CHILDID_SELF); + + props_.push_back(ui::SetWindowSupportsRerouteMouseWheel(hwnd())); + + drop_target_ = new DropTargetWin( + static_cast<internal::RootView*>(GetWidget()->GetRootView())); + + // We need to add ourselves as a message loop observer so that we can repaint + // aggressively if the contents of our window become invalid. Unfortunately + // WM_PAINT messages are starved and we get flickery redrawing when resizing + // if we do not do this. + 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. + Widget* widget = GetWidget()->GetTopLevelWidget(); + if (widget && widget->ShouldUseNativeFrame()) { + tooltip_manager_.reset(new AeroTooltipManager(GetWidget())); + } else { + tooltip_manager_.reset(new TooltipManagerWin(GetWidget())); + } + if (!tooltip_manager_->Init()) { + // There was a problem creating the TooltipManager. Common error is 127. + // See 82193 for details. + LOG_GETLASTERROR(WARNING) << "tooltip creation failed, disabling tooltips"; + tooltip_manager_.reset(); + } + + // This message initializes the window so that focus border are shown for + // windows. + SendMessage( + hwnd(), 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(hwnd(), NULL, 0); + + // We need to allow the delegate to size its contents since the window may not + // receive a size notification when its initial bounds are specified at window + // creation time. + ClientAreaSizeChanged(); + +#if defined(VIEWS_COMPOSITOR) + if (View::get_use_acceleration_when_possible()) { + if (ui::Compositor::compositor_factory()) { + compositor_ = (*Widget::compositor_factory())(this); + } else { + CRect window_rect; + GetClientRect(&window_rect); + compositor_ = ui::Compositor::Create(this, + hwnd(), + gfx::Size(window_rect.Width(), window_rect.Height())); + } + if (compositor_.get()) { + delegate_->AsWidget()->GetRootView()->SetPaintToLayer(true); + compositor_->SetRootLayer(delegate_->AsWidget()->GetRootView()->layer()); + } + } +#endif + + delegate_->OnNativeWidgetCreated(); + + // Get access to a modifiable copy of the system menu. + GetSystemMenu(hwnd(), false); + return 0; +} + +void NativeWidgetWin::OnDestroy() { + delegate_->OnNativeWidgetDestroying(); + if (drop_target_.get()) { + RevokeDragDrop(hwnd()); + drop_target_ = NULL; + } +} + +void NativeWidgetWin::OnDisplayChange(UINT bits_per_pixel, CSize screen_size) { + GetWidget()->widget_delegate()->OnDisplayChanged(); +} + +LRESULT NativeWidgetWin::OnDwmCompositionChanged(UINT msg, + WPARAM w_param, + LPARAM l_param) { + if (!GetWidget()->non_client_view()) { + SetMsgHandled(FALSE); + return 0; + } + + // For some reason, we need to hide the window while we're changing the frame + // type only when we're changing it in response to WM_DWMCOMPOSITIONCHANGED. + // If we don't, the client area will be filled with black. I'm suspecting + // something skia-ey. + // Frame type toggling caused by the user (e.g. switching theme) doesn't seem + // to have this requirement. + FrameTypeChanged(); + return 0; +} + +void NativeWidgetWin::OnEndSession(BOOL ending, UINT logoff) { + SetMsgHandled(FALSE); +} + +void NativeWidgetWin::OnEnterSizeMove() { + delegate_->OnNativeWidgetBeginUserBoundsChange(); + SetMsgHandled(FALSE); +} + +LRESULT NativeWidgetWin::OnEraseBkgnd(HDC dc) { + // This is needed for magical win32 flicker ju-ju. + return 1; +} + +void NativeWidgetWin::OnExitMenuLoop(BOOL is_track_popup_menu) { + SetMsgHandled(FALSE); +} + +void NativeWidgetWin::OnExitSizeMove() { + delegate_->OnNativeWidgetEndUserBoundsChange(); + SetMsgHandled(FALSE); +} + +LRESULT NativeWidgetWin::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) { + // Retrieve MSAA dispatch object for the root view. + base::win::ScopedComPtr<IAccessible> root( + GetWidget()->GetRootView()->GetNativeViewAccessible()); + + // Create a reference that MSAA will marshall to the client. + reference_result = LresultFromObject(IID_IAccessible, w_param, + static_cast<IAccessible*>(root.Detach())); + } + + if (kCustomObjectID == l_param) { + // An MSAA client requestes our custom id. Assume that we have detected an + // active windows screen reader. + OnScreenReaderDetected(); + + // Return with failure. + return static_cast<LRESULT>(0L); + } + + return reference_result; +} + +void NativeWidgetWin::OnGetMinMaxInfo(MINMAXINFO* minmax_info) { + gfx::Size min_window_size(delegate_->GetMinimumSize()); + // Add the native frame border size to the minimum size if the view reports + // its size as the client size. + if (WidgetSizeIsClientSize()) { + CRect client_rect, window_rect; + GetClientRect(&client_rect); + GetWindowRect(&window_rect); + window_rect -= client_rect; + min_window_size.Enlarge(window_rect.Width(), window_rect.Height()); + } + minmax_info->ptMinTrackSize.x = min_window_size.width(); + minmax_info->ptMinTrackSize.y = min_window_size.height(); + SetMsgHandled(FALSE); +} + +void NativeWidgetWin::OnHScroll(int scroll_type, + short position, + HWND scrollbar) { + SetMsgHandled(FALSE); +} + +LRESULT NativeWidgetWin::OnImeMessages(UINT message, + WPARAM w_param, + LPARAM l_param) { + InputMethod* input_method = GetWidget()->GetInputMethodDirect(); + if (!input_method || input_method->IsMock()) { + SetMsgHandled(FALSE); + return 0; + } + + InputMethodWin* ime_win = static_cast<InputMethodWin*>(input_method); + BOOL handled = FALSE; + LRESULT result = ime_win->OnImeMessages(message, w_param, l_param, &handled); + + SetMsgHandled(handled); + return result; +} + +void NativeWidgetWin::OnInitMenu(HMENU menu) { + bool is_fullscreen = IsFullscreen(); + bool is_minimized = IsMinimized(); + bool is_maximized = IsMaximized(); + bool is_restored = !is_fullscreen && !is_minimized && !is_maximized; + + ScopedRedrawLock lock(this); + EnableMenuItem(menu, SC_RESTORE, is_minimized || is_maximized); + EnableMenuItem(menu, SC_MOVE, is_restored); + EnableMenuItem(menu, SC_SIZE, + GetWidget()->widget_delegate()->CanResize() && is_restored); + EnableMenuItem(menu, SC_MAXIMIZE, + GetWidget()->widget_delegate()->CanMaximize() && + !is_fullscreen && !is_maximized); + EnableMenuItem(menu, SC_MINIMIZE, + GetWidget()->widget_delegate()->CanMaximize() && + !is_minimized); +} + +void NativeWidgetWin::OnInitMenuPopup(HMENU menu, + UINT position, + BOOL is_system_menu) { + SetMsgHandled(FALSE); +} + +void NativeWidgetWin::OnInputLangChange(DWORD character_set, + HKL input_language_id) { + InputMethod* input_method = GetWidget()->GetInputMethodDirect(); + + if (input_method && !input_method->IsMock()) { + static_cast<InputMethodWin*>(input_method)->OnInputLangChange( + character_set, input_language_id); + } +} + +LRESULT NativeWidgetWin::OnKeyEvent(UINT message, + WPARAM w_param, + LPARAM l_param) { + MSG msg = { hwnd(), message, w_param, l_param }; + KeyEvent key(msg); + InputMethod* input_method = GetWidget()->GetInputMethodDirect(); + if (input_method) + input_method->DispatchKeyEvent(key); + else + DispatchKeyEventPostIME(key); + return 0; +} + +void NativeWidgetWin::OnKillFocus(HWND focused_window) { + delegate_->OnNativeBlur(focused_window); + InputMethod* input_method = GetWidget()->GetInputMethodDirect(); + if (input_method) + input_method->OnBlur(); + SetMsgHandled(FALSE); +} + +LRESULT NativeWidgetWin::OnMouseActivate(UINT message, + WPARAM w_param, + LPARAM l_param) { + // TODO(beng): resolve this with the GetWindowLong() check on the subsequent + // line. + if (GetWidget()->non_client_view()) + return delegate_->CanActivate() ? MA_ACTIVATE : MA_NOACTIVATEANDEAT; + if (GetWindowLong(GWL_EXSTYLE) & WS_EX_NOACTIVATE) + return MA_NOACTIVATE; + SetMsgHandled(FALSE); + return MA_ACTIVATE; +} + +LRESULT NativeWidgetWin::OnMouseRange(UINT message, + WPARAM w_param, + LPARAM l_param) { + if (message == WM_RBUTTONUP && is_right_mouse_pressed_on_caption_) { + is_right_mouse_pressed_on_caption_ = false; + ReleaseCapture(); + // |point| is in window coordinates, but WM_NCHITTEST and TrackPopupMenu() + // expect screen coordinates. + CPoint screen_point(l_param); + MapWindowPoints(GetNativeView(), HWND_DESKTOP, &screen_point, 1); + w_param = SendMessage(GetNativeView(), WM_NCHITTEST, 0, + MAKELPARAM(screen_point.x, screen_point.y)); + if (w_param == HTCAPTION || w_param == HTSYSMENU) { + ui::ShowSystemMenu(GetNativeView(), screen_point.x, screen_point.y); + return 0; + } + } else if (message == WM_NCLBUTTONDOWN && + !GetWidget()->ShouldUseNativeFrame()) { + switch (w_param) { + case HTCLOSE: + case HTMINBUTTON: + case HTMAXBUTTON: { + // When the mouse is pressed down in these specific non-client areas, + // we need to tell the RootView to send the mouse pressed event (which + // sets capture, allowing subsequent WM_LBUTTONUP (note, _not_ + // WM_NCLBUTTONUP) to fire so that the appropriate WM_SYSCOMMAND can be + // sent by the applicable button's ButtonListener. We _have_ to do this + // way rather than letting Windows just send the syscommand itself (as + // would happen if we never did this dance) because for some insane + // reason DefWindowProc for WM_NCLBUTTONDOWN also renders the pressed + // window control button appearance, in the Windows classic style, over + // our view! Ick! By handling this message we prevent Windows from + // doing this undesirable thing, but that means we need to roll the + // sys-command handling ourselves. + // Combine |w_param| with common key state message flags. + w_param |= ((GetKeyState(VK_CONTROL) & 0x80) == 0x80)? MK_CONTROL : 0; + w_param |= ((GetKeyState(VK_SHIFT) & 0x80) == 0x80)? MK_SHIFT : 0; + } + } + } else if (message == WM_NCRBUTTONDOWN && + (w_param == HTCAPTION || w_param == HTSYSMENU)) { + is_right_mouse_pressed_on_caption_ = true; + // We SetMouseCapture() to ensure we only show the menu when the button + // down and up are both on the caption. Note: this causes the button up to + // be WM_RBUTTONUP instead of WM_NCRBUTTONUP. + SetMouseCapture(); + } + + MSG msg = { hwnd(), message, w_param, l_param, 0, + { GET_X_LPARAM(l_param), GET_Y_LPARAM(l_param) } }; + MouseEvent event(msg); + + if (!(event.flags() & ui::EF_IS_NON_CLIENT)) + if (tooltip_manager_.get()) + tooltip_manager_->OnMouse(message, w_param, l_param); + + if (event.type() == ui::ET_MOUSE_MOVED && !HasMouseCapture()) { + // 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. + TrackMouseEvents((message == WM_NCMOUSEMOVE) ? + TME_NONCLIENT | TME_LEAVE : TME_LEAVE); + } else if (event.type() == ui::ET_MOUSE_EXITED) { + // Reset our tracking flags so future mouse movement over this + // NativeWidgetWin results in a new tracking session. Fall through for + // OnMouseEvent. + active_mouse_tracking_flags_ = 0; + } else if (event.type() == ui::ET_MOUSEWHEEL) { + // Reroute the mouse wheel to the window under the pointer if applicable. + return (ui::RerouteMouseWheel(hwnd(), w_param, l_param) || + delegate_->OnMouseEvent(MouseWheelEvent(msg))) ? 0 : 1; + } + + bool handled = delegate_->OnMouseEvent(event); + + if (!handled && message == WM_NCLBUTTONDOWN && w_param != HTSYSMENU && + !GetWidget()->ShouldUseNativeFrame()) { + // TODO(msw): Eliminate undesired painting, or re-evaluate this workaround. + // DefWindowProc for WM_NCLBUTTONDOWN does weird non-client painting, so we + // need to call it inside a ScopedRedrawLock. This may cause other negative + // side-effects (ex/ stifling non-client mouse releases). + DefWindowProcWithRedrawLock(message, w_param, l_param); + handled = true; + } + + SetMsgHandled(handled); + return 0; +} + +void NativeWidgetWin::OnMove(const CPoint& point) { + // TODO(beng): move to Widget. + GetWidget()->widget_delegate()->OnWidgetMove(); + SetMsgHandled(FALSE); +} + +void NativeWidgetWin::OnMoving(UINT param, const LPRECT new_bounds) { + // TODO(beng): move to Widget. + GetWidget()->widget_delegate()->OnWidgetMove(); +} + +LRESULT NativeWidgetWin::OnNCActivate(BOOL active) { + if (delegate_->CanActivate()) + delegate_->OnNativeWidgetActivationChanged(!!active); + + if (!GetWidget()->non_client_view()) { + SetMsgHandled(FALSE); + return 0; + } + + if (!delegate_->CanActivate()) + return TRUE; + + // The frame may need to redraw as a result of the activation change. + // We can get WM_NCACTIVATE before we're actually visible. If we're not + // visible, no need to paint. + if (IsVisible()) + GetWidget()->non_client_view()->SchedulePaint(); + + if (!GetWidget()->ShouldUseNativeFrame()) { + // TODO(beng, et al): Hack to redraw this window and child windows + // synchronously upon activation. Not all child windows are redrawing + // themselves leading to issues like http://crbug.com/74604 + // We redraw out-of-process HWNDs asynchronously to avoid hanging the + // whole app if a child HWND belonging to a hung plugin is encountered. + RedrawWindow(GetNativeView(), NULL, NULL, + RDW_NOCHILDREN | RDW_INVALIDATE | RDW_UPDATENOW); + EnumChildWindows(GetNativeView(), EnumChildWindowsForRedraw, NULL); + } + + // If we're active again, we should be allowed to render as inactive, so + // tell the non-client view. + bool inactive_rendering_disabled = delegate_->IsInactiveRenderingDisabled(); + if (IsActive()) + delegate_->EnableInactiveRendering(); + + // Avoid DefWindowProc non-client rendering over our custom frame. + if (!GetWidget()->ShouldUseNativeFrame()) { + SetMsgHandled(TRUE); + return TRUE; + } + + return DefWindowProcWithRedrawLock(WM_NCACTIVATE, + inactive_rendering_disabled || active, 0); +} + +LRESULT NativeWidgetWin::OnNCCalcSize(BOOL mode, LPARAM l_param) { + // We only override the default handling if we need to specify a custom + // non-client edge width. Note that in most cases "no insets" means no + // custom width, but in fullscreen mode we want a custom width of 0. + gfx::Insets insets = GetClientAreaInsets(); + if (insets.empty() && !IsFullscreen()) { + SetMsgHandled(FALSE); + return 0; + } + + RECT* client_rect = mode ? + &(reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param)->rgrc[0]) : + reinterpret_cast<RECT*>(l_param); + client_rect->left += insets.left(); + client_rect->top += insets.top(); + client_rect->bottom -= insets.bottom(); + client_rect->right -= insets.right(); + if (IsMaximized()) { + // Find all auto-hide taskbars along the screen edges and adjust in by the + // thickness of the auto-hide taskbar on each such edge, so the window isn't + // treated as a "fullscreen app", which would cause the taskbars to + // disappear. + HMONITOR monitor = MonitorFromWindow(GetNativeView(), + MONITOR_DEFAULTTONULL); + if (!monitor) { + // We might end up here if the window was previously minimized and the + // user clicks on the taskbar button to restore it in the previously + // maximized position. In that case WM_NCCALCSIZE is sent before the + // window coordinates are restored to their previous values, so our + // (left,top) would probably be (-32000,-32000) like all minimized + // windows. So the above MonitorFromWindow call fails, but if we check + // the window rect given with WM_NCCALCSIZE (which is our previous + // restored window position) we will get the correct monitor handle. + monitor = MonitorFromRect(client_rect, MONITOR_DEFAULTTONULL); + if (!monitor) { + // This is probably an extreme case that we won't hit, but if we don't + // intersect any monitor, let us not adjust the client rect since our + // window will not be visible anyway. + return 0; + } + } + if (GetTopmostAutoHideTaskbarForEdge(ABE_LEFT, monitor)) + client_rect->left += kAutoHideTaskbarThicknessPx; + if (GetTopmostAutoHideTaskbarForEdge(ABE_TOP, monitor)) { + if (GetWidget()->ShouldUseNativeFrame()) { + // Tricky bit. Due to a bug in DwmDefWindowProc()'s handling of + // WM_NCHITTEST, having any nonclient area atop the window causes the + // caption buttons to draw onscreen but not respond to mouse + // hover/clicks. + // So for a taskbar at the screen top, we can't push the + // client_rect->top down; instead, we move the bottom up by one pixel, + // which is the smallest change we can make and still get a client area + // less than the screen size. This is visibly ugly, but there seems to + // be no better solution. + --client_rect->bottom; + } else { + client_rect->top += kAutoHideTaskbarThicknessPx; + } + } + if (GetTopmostAutoHideTaskbarForEdge(ABE_RIGHT, monitor)) + client_rect->right -= kAutoHideTaskbarThicknessPx; + if (GetTopmostAutoHideTaskbarForEdge(ABE_BOTTOM, monitor)) + client_rect->bottom -= kAutoHideTaskbarThicknessPx; + + // We cannot return WVR_REDRAW when there is nonclient area, or Windows + // exhibits bugs where client pixels and child HWNDs are mispositioned by + // the width/height of the upper-left nonclient area. + return 0; + } + + // If the window bounds change, we're going to relayout and repaint anyway. + // Returning WVR_REDRAW avoids an extra paint before that of the old client + // pixels in the (now wrong) location, and thus makes actions like resizing a + // window from the left edge look slightly less broken. + // We special case when left or top insets are 0, since these conditions + // actually require another repaint to correct the layout after glass gets + // turned on and off. + if (insets.left() == 0 || insets.top() == 0) + return 0; + return mode ? WVR_REDRAW : 0; +} + +LRESULT NativeWidgetWin::OnNCHitTest(const CPoint& point) { + if (!GetWidget()->non_client_view()) { + SetMsgHandled(FALSE); + return 0; + } + + // If the DWM is rendering the window controls, we need to give the DWM's + // default window procedure first chance to handle hit testing. + if (GetWidget()->ShouldUseNativeFrame()) { + LRESULT result; + if (DwmDefWindowProc(GetNativeView(), WM_NCHITTEST, 0, + MAKELPARAM(point.x, point.y), &result)) { + return result; + } + } + + // First, give the NonClientView a chance to test the point to see if it + // provides any of the non-client area. + POINT temp = point; + MapWindowPoints(HWND_DESKTOP, GetNativeView(), &temp, 1); + int component = delegate_->GetNonClientComponent(gfx::Point(temp)); + if (component != HTNOWHERE) + return component; + + // Otherwise, we let Windows do all the native frame non-client handling for + // us. + SetMsgHandled(FALSE); + return 0; +} + +void NativeWidgetWin::OnNCPaint(HRGN rgn) { + // We only do non-client painting if we're not using the native frame. + // It's required to avoid some native painting artifacts from appearing when + // the window is resized. + if (!GetWidget()->non_client_view() || GetWidget()->ShouldUseNativeFrame()) { + SetMsgHandled(FALSE); + return; + } + + // We have an NC region and need to paint it. We expand the NC region to + // include the dirty region of the root view. This is done to minimize + // paints. + CRect window_rect; + GetWindowRect(&window_rect); + + if (window_rect.Width() != GetWidget()->GetRootView()->width() || + window_rect.Height() != GetWidget()->GetRootView()->height()) { + // If the size of the window differs from the size of the root view it + // means we're being asked to paint before we've gotten a WM_SIZE. This can + // happen when the user is interactively resizing the window. To avoid + // mass flickering we don't do anything here. Once we get the WM_SIZE we'll + // reset the region of the window which triggers another WM_NCPAINT and + // all is well. + return; + } + + CRect dirty_region; + // A value of 1 indicates paint all. + if (!rgn || rgn == reinterpret_cast<HRGN>(1)) { + dirty_region = CRect(0, 0, window_rect.Width(), window_rect.Height()); + } else { + RECT rgn_bounding_box; + GetRgnBox(rgn, &rgn_bounding_box); + if (!IntersectRect(&dirty_region, &rgn_bounding_box, &window_rect)) + return; // Dirty region doesn't intersect window bounds, bale. + + // rgn_bounding_box is in screen coordinates. Map it to window coordinates. + OffsetRect(&dirty_region, -window_rect.left, -window_rect.top); + } + + // In theory GetDCEx should do what we want, but I couldn't get it to work. + // In particular the docs mentiond DCX_CLIPCHILDREN, but as far as I can tell + // it doesn't work at all. So, instead we get the DC for the window then + // manually clip out the children. + HDC dc = GetWindowDC(GetNativeView()); + ClipState clip_state; + clip_state.x = window_rect.left; + clip_state.y = window_rect.top; + clip_state.parent = GetNativeView(); + clip_state.dc = dc; + EnumChildWindows(GetNativeView(), &ClipDCToChild, + reinterpret_cast<LPARAM>(&clip_state)); + + gfx::Rect old_paint_region = invalid_rect(); + + if (!old_paint_region.IsEmpty()) { + // The root view has a region that needs to be painted. Include it in the + // region we're going to paint. + + CRect old_paint_region_crect = old_paint_region.ToRECT(); + CRect tmp = dirty_region; + UnionRect(&dirty_region, &tmp, &old_paint_region_crect); + } + + GetWidget()->GetRootView()->SchedulePaintInRect(gfx::Rect(dirty_region)); + + // gfx::CanvasSkiaPaint's destructor does the actual painting. As such, wrap + // the following in a block to force paint to occur so that we can release + // the dc. + { + gfx::CanvasSkiaPaint canvas(dc, true, dirty_region.left, + dirty_region.top, dirty_region.Width(), + dirty_region.Height()); + delegate_->OnNativeWidgetPaint(&canvas); + } + + ReleaseDC(GetNativeView(), dc); + // When using a custom frame, we want to avoid calling DefWindowProc() since + // that may render artifacts. + SetMsgHandled(!GetWidget()->ShouldUseNativeFrame()); +} + +LRESULT NativeWidgetWin::OnNCUAHDrawCaption(UINT msg, + WPARAM w_param, + LPARAM l_param) { + // See comment in widget_win.h at the definition of WM_NCUAHDRAWCAPTION for + // an explanation about why we need to handle this message. + SetMsgHandled(!GetWidget()->ShouldUseNativeFrame()); + return 0; +} + +LRESULT NativeWidgetWin::OnNCUAHDrawFrame(UINT msg, + WPARAM w_param, + LPARAM l_param) { + // See comment in widget_win.h at the definition of WM_NCUAHDRAWCAPTION for + // an explanation about why we need to handle this message. + SetMsgHandled(!GetWidget()->ShouldUseNativeFrame()); + return 0; +} + +LRESULT NativeWidgetWin::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 NativeWidgetWin::OnPaint(HDC dc) { + RECT dirty_rect; + // Try to paint accelerated first. + if (GetUpdateRect(hwnd(), &dirty_rect, FALSE) && + !IsRectEmpty(&dirty_rect)) { + if (delegate_->OnNativeWidgetPaintAccelerated( + gfx::Rect(dirty_rect))) { + ValidateRect(hwnd(), NULL); + } else { + scoped_ptr<gfx::CanvasPaint> canvas( + gfx::CanvasPaint::CreateCanvasPaint(hwnd())); + delegate_->OnNativeWidgetPaint(canvas->AsCanvas()); + } + } else { + // TODO(msw): Find a better solution for this crbug.com/93530 workaround. + // Some scenarios otherwise fail to validate minimized app/popup windows. + ValidateRect(hwnd(), NULL); + } +} + +LRESULT NativeWidgetWin::OnPowerBroadcast(DWORD power_event, DWORD data) { + base::SystemMonitor* monitor = base::SystemMonitor::Get(); + if (monitor) + monitor->ProcessWmPowerBroadcastMessage(power_event); + SetMsgHandled(FALSE); + return 0; +} + +LRESULT NativeWidgetWin::OnReflectedMessage(UINT msg, + WPARAM w_param, + LPARAM l_param) { + SetMsgHandled(FALSE); + return 0; +} + +LRESULT NativeWidgetWin::OnSetCursor(UINT message, + WPARAM w_param, + LPARAM l_param) { + // Using ScopedRedrawLock here frequently allows content behind this window to + // paint in front of this window, causing glaring rendering artifacts. + // If omitting ScopedRedrawLock here triggers caption rendering artifacts via + // DefWindowProc message handling, we'll need to find a better solution. + SetMsgHandled(FALSE); + return 0; +} + +void NativeWidgetWin::OnSetFocus(HWND focused_window) { + delegate_->OnNativeFocus(focused_window); + InputMethod* input_method = GetWidget()->GetInputMethodDirect(); + if (input_method) + input_method->OnFocus(); + SetMsgHandled(FALSE); +} + +LRESULT NativeWidgetWin::OnSetIcon(UINT size_type, HICON new_icon) { + // This shouldn't hurt even if we're using the native frame. + return DefWindowProcWithRedrawLock(WM_SETICON, size_type, + reinterpret_cast<LPARAM>(new_icon)); +} + +LRESULT NativeWidgetWin::OnSetText(const wchar_t* text) { + // This shouldn't hurt even if we're using the native frame. + return DefWindowProcWithRedrawLock(WM_SETTEXT, NULL, + reinterpret_cast<LPARAM>(text)); +} + +void NativeWidgetWin::OnSettingChange(UINT flags, const wchar_t* section) { + if (!GetParent() && (flags == SPI_SETWORKAREA) && + !GetWidget()->widget_delegate()->WillProcessWorkAreaChange()) { + // Fire a dummy SetWindowPos() call, so we'll trip the code in + // OnWindowPosChanging() below that notices work area changes. + ::SetWindowPos(GetNativeView(), 0, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | + SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE | SWP_NOOWNERZORDER); + SetMsgHandled(TRUE); + } else { + // TODO(beng): move to Widget. + if (flags == SPI_SETWORKAREA) + GetWidget()->widget_delegate()->OnWorkAreaChanged(); + SetMsgHandled(FALSE); + } +} + +void NativeWidgetWin::OnSize(UINT param, const CSize& size) { + RedrawWindow(GetNativeView(), NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN); + // ResetWindowRegion is going to trigger WM_NCPAINT. By doing it after we've + // invoked OnSize we ensure the RootView has been laid out. + ResetWindowRegion(false); +} + +void NativeWidgetWin::OnSysCommand(UINT notification_code, CPoint click) { + if (!GetWidget()->non_client_view()) + return; + + // Windows uses the 4 lower order bits of |notification_code| for type- + // specific information so we must exclude this when comparing. + static const int sc_mask = 0xFFF0; + // Ignore size/move/maximize in fullscreen mode. + if (IsFullscreen() && + (((notification_code & sc_mask) == SC_SIZE) || + ((notification_code & sc_mask) == SC_MOVE) || + ((notification_code & sc_mask) == SC_MAXIMIZE))) + return; + if (!GetWidget()->ShouldUseNativeFrame()) { + if ((notification_code & sc_mask) == SC_MINIMIZE || + (notification_code & sc_mask) == SC_MAXIMIZE || + (notification_code & sc_mask) == SC_RESTORE) { + GetWidget()->non_client_view()->ResetWindowControls(); + } else if ((notification_code & sc_mask) == SC_MOVE || + (notification_code & sc_mask) == SC_SIZE) { + if (!IsVisible()) { + // Circumvent ScopedRedrawLocks and force visibility before entering a + // resize or move modal loop to get continuous sizing/moving feedback. + SetWindowLong(GWL_STYLE, GetWindowLong(GWL_STYLE) | WS_VISIBLE); + } + } + } + + // Handle SC_KEYMENU, which means that the user has pressed the ALT + // key and released it, so we should focus the menu bar. + if ((notification_code & sc_mask) == SC_KEYMENU && click.x == 0) { + // Retrieve the status of shift and control keys to prevent consuming + // shift+alt keys, which are used by Windows to change input languages. + ui::Accelerator accelerator(ui::KeyboardCodeForWindowsKeyCode(VK_MENU), + !!(GetKeyState(VK_SHIFT) & 0x8000), + !!(GetKeyState(VK_CONTROL) & 0x8000), + false); + GetWidget()->GetFocusManager()->ProcessAccelerator(accelerator); + return; + } + + // If the delegate can't handle it, the system implementation will be called. + if (!delegate_->ExecuteCommand(notification_code)) { + DefWindowProc(GetNativeView(), WM_SYSCOMMAND, notification_code, + MAKELPARAM(click.x, click.y)); + } +} + +void NativeWidgetWin::OnThemeChanged() { + // Notify NativeThemeWin. + gfx::NativeThemeWin::instance()->CloseHandles(); +} + +void NativeWidgetWin::OnVScroll(int scroll_type, + short position, + HWND scrollbar) { + SetMsgHandled(FALSE); +} + +void NativeWidgetWin::OnWindowPosChanging(WINDOWPOS* window_pos) { + if (ignore_window_pos_changes_) { + // If somebody's trying to toggle our visibility, change the nonclient area, + // change our Z-order, or activate us, we should probably let it go through. + if (!(window_pos->flags & ((IsVisible() ? SWP_HIDEWINDOW : SWP_SHOWWINDOW) | + SWP_FRAMECHANGED)) && + (window_pos->flags & (SWP_NOZORDER | SWP_NOACTIVATE))) { + // Just sizing/moving the window; ignore. + window_pos->flags |= SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW; + window_pos->flags &= ~(SWP_SHOWWINDOW | SWP_HIDEWINDOW); + } + } else if (!GetParent()) { + CRect window_rect; + HMONITOR monitor; + gfx::Rect monitor_rect, work_area; + if (GetWindowRect(&window_rect) && + GetMonitorAndRects(window_rect, &monitor, &monitor_rect, &work_area)) { + if (monitor && (monitor == last_monitor_) && + (IsFullscreen() || ((monitor_rect == last_monitor_rect_) && + (work_area != last_work_area_)))) { + // A rect for the monitor we're on changed. Normally Windows notifies + // us about this (and thus we're reaching here due to the SetWindowPos() + // call in OnSettingChange() above), but with some software (e.g. + // nVidia's nView desktop manager) the work area can change asynchronous + // to any notification, and we're just sent a SetWindowPos() call with a + // new (frequently incorrect) position/size. In either case, the best + // response is to throw away the existing position/size information in + // |window_pos| and recalculate it based on the new work rect. + gfx::Rect new_window_rect; + if (IsFullscreen()) { + new_window_rect = monitor_rect; + } else if (IsZoomed()) { + new_window_rect = work_area; + int border_thickness = GetSystemMetrics(SM_CXSIZEFRAME); + new_window_rect.Inset(-border_thickness, -border_thickness); + } else { + new_window_rect = gfx::Rect(window_rect).AdjustToFit(work_area); + } + window_pos->x = new_window_rect.x(); + window_pos->y = new_window_rect.y(); + window_pos->cx = new_window_rect.width(); + window_pos->cy = new_window_rect.height(); + // WARNING! Don't set SWP_FRAMECHANGED here, it breaks moving the child + // HWNDs for some reason. + window_pos->flags &= ~(SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW); + window_pos->flags |= SWP_NOCOPYBITS; + + // Now ignore all immediately-following SetWindowPos() changes. Windows + // likes to (incorrectly) recalculate what our position/size should be + // and send us further updates. + ignore_window_pos_changes_ = true; + DCHECK(!ignore_pos_changes_factory_.HasWeakPtrs()); + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&NativeWidgetWin::StopIgnoringPosChanges, + ignore_pos_changes_factory_.GetWeakPtr())); + } + last_monitor_ = monitor; + last_monitor_rect_ = monitor_rect; + last_work_area_ = work_area; + } + } + + if (force_hidden_count_) { + // Prevent the window from being made visible if we've been asked to do so. + // See comment in header as to why we might want this. + window_pos->flags &= ~SWP_SHOWWINDOW; + } + + // When WM_WINDOWPOSCHANGING message is handled by DefWindowProc, it will + // enforce (cx, cy) not to be smaller than (6, 6) for any non-popup window. + // We work around this by changing cy back to our intended value. + if (!GetParent() && ~(window_pos->flags & SWP_NOSIZE) && window_pos->cy < 6) { + LONG old_cy = window_pos->cy; + DefWindowProc(GetNativeView(), WM_WINDOWPOSCHANGING, 0, + reinterpret_cast<LPARAM>(window_pos)); + window_pos->cy = old_cy; + SetMsgHandled(TRUE); + return; + } + + SetMsgHandled(FALSE); +} + +void NativeWidgetWin::OnWindowPosChanged(WINDOWPOS* window_pos) { + if (DidClientAreaSizeChange(window_pos)) + ClientAreaSizeChanged(); + if (window_pos->flags & SWP_SHOWWINDOW) + delegate_->OnNativeWidgetVisibilityChanged(true); + else if (window_pos->flags & SWP_HIDEWINDOW) + delegate_->OnNativeWidgetVisibilityChanged(false); + SetMsgHandled(FALSE); +} + +void NativeWidgetWin::OnFinalMessage(HWND window) { + // We don't destroy props in WM_DESTROY as we may still get messages after + // WM_DESTROY that assume the properties are still valid (such as WM_CLOSE). + props_.reset(); + delegate_->OnNativeWidgetDestroyed(); + if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) + delete this; +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetWin, protected: + +int NativeWidgetWin::GetShowState() const { + return SW_SHOWNORMAL; +} + +gfx::Insets NativeWidgetWin::GetClientAreaInsets() const { + // Returning an empty Insets object causes the default handling in + // NativeWidgetWin::OnNCCalcSize() to be invoked. + if (!has_non_client_view_ || GetWidget()->ShouldUseNativeFrame()) + return gfx::Insets(); + + if (IsMaximized()) { + // Windows automatically adds a standard width border to all sides when a + // window is maximized. + int border_thickness = GetSystemMetrics(SM_CXSIZEFRAME); + return gfx::Insets(border_thickness, border_thickness, border_thickness, + border_thickness); + } + // This is weird, but highly essential. If we don't offset the bottom edge + // of the client rect, the window client area and window area will match, + // and when returning to glass rendering mode from non-glass, the client + // area will not paint black as transparent. This is because (and I don't + // know why) the client area goes from matching the window rect to being + // something else. If the client area is not the window rect in both + // modes, the blackness doesn't occur. Because of this, we need to tell + // the RootView to lay out to fit the window rect, rather than the client + // rect when using the opaque frame. + // Note: this is only required for non-fullscreen windows. Note that + // fullscreen windows are in restored state, not maximized. + return gfx::Insets(0, 0, IsFullscreen() ? 0 : 1, 0); +} + +void NativeWidgetWin::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 = hwnd(); + 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); + } +} + +void NativeWidgetWin::OnScreenReaderDetected() { + screen_reader_active_ = true; +} + +void NativeWidgetWin::SetInitialFocus() { + if (!GetWidget()->SetInitialFocus() && + !(GetWindowLong(GWL_EXSTYLE) & WS_EX_TRANSPARENT) && + !(GetWindowLong(GWL_EXSTYLE) & WS_EX_NOACTIVATE)) { + // The window does not get keyboard messages unless we focus it. + SetFocus(GetNativeView()); + } +} + +void NativeWidgetWin::ExecuteSystemMenuCommand(int command) { + if (command) + SendMessage(GetNativeView(), WM_SYSCOMMAND, command, 0); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetWin, private: + +// static +void NativeWidgetWin::PostProcessActivateMessage(NativeWidgetWin* widget, + int activation_state) { + DCHECK(widget->GetWidget()->is_top_level()); + FocusManager* focus_manager = widget->GetWidget()->GetFocusManager(); + if (WA_INACTIVE == activation_state) { + // We might get activated/inactivated without being enabled, so we need to + // clear restore_focus_when_enabled_. + widget->restore_focus_when_enabled_ = false; + focus_manager->StoreFocusedView(); + } else { + // We must restore the focus after the message has been DefProc'ed as it + // does set the focus to the last focused HWND. + // Note that if the window is not enabled, we cannot restore the focus as + // calling ::SetFocus on a child of the non-enabled top-window would fail. + // This is the case when showing a modal dialog (such as 'open file', + // 'print'...) from a different thread. + // In that case we delay the focus restoration to when the window is enabled + // again. + if (!IsWindowEnabled(widget->GetNativeView())) { + DCHECK(!widget->restore_focus_when_enabled_); + widget->restore_focus_when_enabled_ = true; + return; + } + focus_manager->RestoreFocusedView(); + } +} + +void NativeWidgetWin::SetInitParams(const Widget::InitParams& params) { + // Set non-style attributes. + ownership_ = params.ownership; + + DWORD style = WS_CLIPCHILDREN | WS_CLIPSIBLINGS; + DWORD ex_style = 0; + DWORD class_style = CS_DBLCLKS; + + // Set type-independent style attributes. + if (params.child) + style |= WS_CHILD; + if (params.show_state == ui::SHOW_STATE_MAXIMIZED) + style |= WS_MAXIMIZE; + if (params.show_state == ui::SHOW_STATE_MINIMIZED) + style |= WS_MINIMIZE; + if (!params.accept_events) + ex_style |= WS_EX_TRANSPARENT; + if (!params.can_activate) + ex_style |= WS_EX_NOACTIVATE; + if (params.keep_on_top) + ex_style |= WS_EX_TOPMOST; + if (params.mirror_origin_in_rtl) + ex_style |= l10n_util::GetExtendedTooltipStyles(); + if (params.transparent) + ex_style |= WS_EX_LAYERED; + if (params.has_dropshadow) { + class_style |= (base::win::GetVersion() < base::win::VERSION_XP) ? + 0 : CS_DROPSHADOW; + } + + // Set type-dependent style attributes. + switch (params.type) { + case Widget::InitParams::TYPE_WINDOW: { + style |= WS_SYSMENU | WS_CAPTION; + bool can_resize = GetWidget()->widget_delegate()->CanResize(); + bool can_maximize = GetWidget()->widget_delegate()->CanMaximize(); + if (can_maximize) { + style |= WS_OVERLAPPEDWINDOW; + } else if (can_resize) { + style |= WS_OVERLAPPED | WS_THICKFRAME; + } + if (delegate_->IsDialogBox()) { + style |= DS_MODALFRAME; + // NOTE: Turning this off means we lose the close button, which is bad. + // Turning it on though means the user can maximize or size the window + // from the system menu, which is worse. We may need to provide our own + // menu to get the close button to appear properly. + // style &= ~WS_SYSMENU; + + // Set the WS_POPUP style for modal dialogs. This ensures that the owner + // window is activated on destruction. This style should not be set for + // non-modal non-top-level dialogs like constrained windows. + style |= delegate_->IsModal() ? WS_POPUP : 0; + } + ex_style |= delegate_->IsDialogBox() ? WS_EX_DLGMODALFRAME : 0; + break; + } + case Widget::InitParams::TYPE_CONTROL: + style |= WS_VISIBLE; + break; + case Widget::InitParams::TYPE_WINDOW_FRAMELESS: + style |= WS_POPUP; + break; + case Widget::InitParams::TYPE_BUBBLE: + style |= WS_POPUP; + style |= WS_CLIPCHILDREN; + break; + case Widget::InitParams::TYPE_POPUP: + style |= WS_POPUP; + ex_style |= WS_EX_TOOLWINDOW; + break; + case Widget::InitParams::TYPE_MENU: + style |= WS_POPUP; + break; + default: + NOTREACHED(); + } + + set_initial_class_style(class_style); + set_window_style(window_style() | style); + set_window_ex_style(window_ex_style() | ex_style); + + has_non_client_view_ = Widget::RequiresNonClientView(params.type); +} + +void NativeWidgetWin::RedrawInvalidRect() { + if (!use_layered_buffer_) { + RECT r = { 0, 0, 0, 0 }; + if (GetUpdateRect(hwnd(), &r, FALSE) && !IsRectEmpty(&r)) { + RedrawWindow(hwnd(), &r, NULL, + RDW_INVALIDATE | RDW_UPDATENOW | RDW_NOCHILDREN); + } + } +} + +void NativeWidgetWin::RedrawLayeredWindowContents() { + if (invalid_rect_.IsEmpty()) + return; + + // We need to clip to the dirty rect ourselves. + layered_window_contents_->sk_canvas()->save(SkCanvas::kClip_SaveFlag); + layered_window_contents_->ClipRect(invalid_rect_); + GetWidget()->GetRootView()->Paint(layered_window_contents_.get()); + layered_window_contents_->sk_canvas()->restore(); + + RECT wr; + GetWindowRect(&wr); + SIZE size = {wr.right - wr.left, wr.bottom - wr.top}; + POINT position = {wr.left, wr.top}; + HDC dib_dc = skia::BeginPlatformPaint(layered_window_contents_->sk_canvas()); + POINT zero = {0, 0}; + BLENDFUNCTION blend = {AC_SRC_OVER, 0, layered_alpha_, AC_SRC_ALPHA}; + UpdateLayeredWindow(hwnd(), NULL, &position, &size, dib_dc, &zero, + RGB(0xFF, 0xFF, 0xFF), &blend, ULW_ALPHA); + invalid_rect_.SetRect(0, 0, 0, 0); + skia::EndPlatformPaint(layered_window_contents_->sk_canvas()); +} + +void NativeWidgetWin::LockUpdates() { + // We skip locked updates when Aero is on for two reasons: + // 1. Because it isn't necessary + // 2. Because toggling the WS_VISIBLE flag may occur while the GPU process is + // attempting to present a child window's backbuffer onscreen. When these + // two actions race with one another, the child window will either flicker + // or will simply stop updating entirely. + if (!IsAeroGlassEnabled() && ++lock_updates_count_ == 1) { + SetWindowLong(GWL_STYLE, GetWindowLong(GWL_STYLE) & ~WS_VISIBLE); + } + // TODO(msw): Remove nested LockUpdates VLOG info for crbug.com/93530. + VLOG_IF(1, (lock_updates_count_ > 1)) << "Nested LockUpdates call: " + << lock_updates_count_ << " locks for widget " << this; +} + +void NativeWidgetWin::UnlockUpdates() { + // TODO(msw): Remove nested LockUpdates VLOG info for crbug.com/93530. + VLOG_IF(1, (lock_updates_count_ > 1)) << "Nested UnlockUpdates call: " + << lock_updates_count_ << " locks for widget " << this; + if (!IsAeroGlassEnabled() && --lock_updates_count_ <= 0) { + SetWindowLong(GWL_STYLE, GetWindowLong(GWL_STYLE) | WS_VISIBLE); + lock_updates_count_ = 0; + } +} + +bool NativeWidgetWin::WidgetSizeIsClientSize() const { + const Widget* widget = GetWidget()->GetTopLevelWidget(); + return IsZoomed() || (widget && widget->ShouldUseNativeFrame()); +} + +void NativeWidgetWin::ClientAreaSizeChanged() { + RECT r; + if (WidgetSizeIsClientSize()) + GetClientRect(&r); + else + GetWindowRect(&r); + gfx::Size s(std::max(0, static_cast<int>(r.right - r.left)), + std::max(0, static_cast<int>(r.bottom - r.top))); + if (compositor_.get()) + compositor_->WidgetSizeChanged(s); + delegate_->OnNativeWidgetSizeChanged(s); + if (use_layered_buffer_) { + layered_window_contents_.reset( + new gfx::CanvasSkia(s.width(), s.height(), false)); + } +} + +void NativeWidgetWin::ResetWindowRegion(bool force) { + // A native frame uses the native window region, and we don't want to mess + // with it. + if (GetWidget()->ShouldUseNativeFrame() || !GetWidget()->non_client_view()) { + if (force) + SetWindowRgn(NULL, TRUE); + return; + } + + // Changing the window region is going to force a paint. Only change the + // window region if the region really differs. + HRGN current_rgn = CreateRectRgn(0, 0, 0, 0); + int current_rgn_result = GetWindowRgn(GetNativeView(), current_rgn); + + CRect window_rect; + GetWindowRect(&window_rect); + HRGN new_region; + if (IsMaximized()) { + HMONITOR monitor = + MonitorFromWindow(GetNativeView(), MONITOR_DEFAULTTONEAREST); + MONITORINFO mi; + mi.cbSize = sizeof mi; + GetMonitorInfo(monitor, &mi); + CRect work_rect = mi.rcWork; + work_rect.OffsetRect(-window_rect.left, -window_rect.top); + new_region = CreateRectRgnIndirect(&work_rect); + } else { + gfx::Path window_mask; + GetWidget()->non_client_view()->GetWindowMask( + gfx::Size(window_rect.Width(), window_rect.Height()), &window_mask); + new_region = window_mask.CreateNativeRegion(); + } + + if (current_rgn_result == ERROR || !EqualRgn(current_rgn, new_region)) { + // SetWindowRgn takes ownership of the HRGN created by CreateNativeRegion. + SetWindowRgn(new_region, TRUE); + } else { + DeleteObject(new_region); + } + + DeleteObject(current_rgn); +} + +LRESULT NativeWidgetWin::DefWindowProcWithRedrawLock(UINT message, + WPARAM w_param, + LPARAM l_param) { + ScopedRedrawLock lock(this); + // The Widget and HWND can be destroyed in the call to DefWindowProc, so use + // the |destroyed_| flag to avoid unlocking (and crashing) after destruction. + bool destroyed = false; + destroyed_ = &destroyed; + LRESULT result = DefWindowProc(GetNativeView(), message, w_param, l_param); + if (destroyed) + lock.CancelUnlockOperation(); + else + destroyed_ = NULL; + return result; +} + +void NativeWidgetWin::RestoreEnabledIfNecessary() { + if (delegate_->IsModal() && !restored_enabled_) { + restored_enabled_ = true; + // If we were run modally, we need to undo the disabled-ness we inflicted on + // the owner's parent hierarchy. + HWND start = ::GetWindow(GetNativeView(), GW_OWNER); + while (start) { + ::EnableWindow(start, TRUE); + start = ::GetParent(start); + } + } +} + +void NativeWidgetWin::DispatchKeyEventPostIME(const KeyEvent& key) { + SetMsgHandled(delegate_->OnKeyEvent(key)); +} + +//////////////////////////////////////////////////////////////////////////////// +// Widget, public: + +// static +void Widget::NotifyLocaleChanged() { + NOTIMPLEMENTED(); +} + +namespace { +BOOL CALLBACK WindowCallbackProc(HWND hwnd, LPARAM lParam) { + Widget* widget = Widget::GetWidgetForNativeView(hwnd); + if (widget && widget->is_secondary_widget()) + widget->Close(); + return TRUE; +} +} // namespace + +// static +void Widget::CloseAllSecondaryWidgets() { + EnumThreadWindows(GetCurrentThreadId(), WindowCallbackProc, 0); +} + +bool Widget::ConvertRect(const Widget* source, + const Widget* target, + gfx::Rect* rect) { + DCHECK(source); + DCHECK(target); + DCHECK(rect); + + HWND source_hwnd = source->GetNativeView(); + HWND target_hwnd = target->GetNativeView(); + if (source_hwnd == target_hwnd) + return true; + + RECT win_rect = rect->ToRECT(); + if (::MapWindowPoints(source_hwnd, target_hwnd, + reinterpret_cast<LPPOINT>(&win_rect), + sizeof(RECT)/sizeof(POINT))) { + *rect = win_rect; + return true; + } + return false; +} + +namespace internal { + +//////////////////////////////////////////////////////////////////////////////// +// internal::NativeWidgetPrivate, public: + +// static +NativeWidgetPrivate* NativeWidgetPrivate::CreateNativeWidget( + internal::NativeWidgetDelegate* delegate) { + return new NativeWidgetWin(delegate); +} + +// static +NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeView( + gfx::NativeView native_view) { + return reinterpret_cast<NativeWidgetWin*>( + ViewProp::GetValue(native_view, kNativeWidgetKey)); +} + +// static +NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeWindow( + gfx::NativeWindow native_window) { + return GetNativeWidgetForNativeView(native_window); +} + +// static +NativeWidgetPrivate* NativeWidgetPrivate::GetTopLevelNativeWidget( + gfx::NativeView native_view) { + if (!native_view) + return NULL; + + // First, check if the top-level window is a Widget. + HWND root = ::GetAncestor(native_view, GA_ROOT); + if (!root) + return NULL; + + NativeWidgetPrivate* widget = GetNativeWidgetForNativeView(root); + if (widget) + return widget; + + // Second, try to locate the last Widget window in the parent hierarchy. + HWND parent_hwnd = native_view; + // If we fail to find the native widget pointer for the root then it probably + // means that the root belongs to a different process in which case we walk up + // the native view chain looking for a parent window which corresponds to a + // valid native widget. We only do this if we fail to find the native widget + // for the current native view which means it is being destroyed. + if (!widget && !GetNativeWidgetForNativeView(native_view)) { + parent_hwnd = ::GetAncestor(parent_hwnd, GA_PARENT); + if (!parent_hwnd) + return NULL; + } + NativeWidgetPrivate* parent_widget; + do { + parent_widget = GetNativeWidgetForNativeView(parent_hwnd); + if (parent_widget) { + widget = parent_widget; + parent_hwnd = ::GetAncestor(parent_hwnd, GA_PARENT); + } + } while (parent_hwnd != NULL && parent_widget != NULL); + + return widget; +} + +// static +void NativeWidgetPrivate::GetAllChildWidgets(gfx::NativeView native_view, + Widget::Widgets* children) { + if (!native_view) + return; + + Widget* widget = Widget::GetWidgetForNativeView(native_view); + if (widget) + children->insert(widget); + EnumChildWindows(native_view, EnumerateChildWindowsForNativeWidgets, + reinterpret_cast<LPARAM>(children)); +} + +// static +void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView native_view, + gfx::NativeView new_parent) { + if (!native_view) + return; + + HWND previous_parent = ::GetParent(native_view); + if (previous_parent == new_parent) + return; + + Widget::Widgets widgets; + GetAllChildWidgets(native_view, &widgets); + + // First notify all the widgets that they are being disassociated + // from their previous parent. + for (Widget::Widgets::iterator it = widgets.begin(); + it != widgets.end(); ++it) { + // TODO(beng): Rename this notification to NotifyNativeViewChanging() + // and eliminate the bool parameter. + (*it)->NotifyNativeViewHierarchyChanged(false, previous_parent); + } + + ::SetParent(native_view, new_parent); + + // And now, notify them that they have a brand new parent. + for (Widget::Widgets::iterator it = widgets.begin(); + it != widgets.end(); ++it) { + (*it)->NotifyNativeViewHierarchyChanged(true, new_parent); + } +} + +// static +bool NativeWidgetPrivate::IsMouseButtonDown() { + return (GetKeyState(VK_LBUTTON) & 0x80) || + (GetKeyState(VK_RBUTTON) & 0x80) || + (GetKeyState(VK_MBUTTON) & 0x80) || + (GetKeyState(VK_XBUTTON1) & 0x80) || + (GetKeyState(VK_XBUTTON2) & 0x80); +} + +} // namespace internal + +} // namespace views diff --git a/views/widget/native_widget_win.h b/views/widget/native_widget_win.h new file mode 100644 index 0000000..e145556 --- /dev/null +++ b/views/widget/native_widget_win.h @@ -0,0 +1,664 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_NATIVE_WIDGET_WIN_H_ +#define VIEWS_WIDGET_NATIVE_WIDGET_WIN_H_ +#pragma once + +#include <atlbase.h> +#include <atlapp.h> +#include <atlcrack.h> +#include <atlmisc.h> + +#include <string> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop.h" +#include "base/win/scoped_comptr.h" +#include "base/win/win_util.h" +#include "ui/base/win/window_impl.h" +#include "ui/gfx/compositor/compositor.h" +#include "ui/views/focus/focus_manager.h" +#include "ui/views/layout/layout_manager.h" +#include "views/widget/native_widget_private.h" + +namespace ui { +class Compositor; +class ViewProp; +} + +namespace gfx { +class CanvasSkia; +class Font; +class Rect; +} + +namespace views { + +class DropTargetWin; +class RootView; +class TooltipManagerWin; + +// 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. +const int WM_NCUAHDRAWCAPTION = 0xAE; +const int WM_NCUAHDRAWFRAME = 0xAF; + +/////////////////////////////////////////////////////////////////////////////// +// +// NativeWidgetWin +// 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 VIEWS_EXPORT NativeWidgetWin : public ui::WindowImpl, + public MessageLoopForUI::Observer, + public ui::CompositorDelegate, + public internal::NativeWidgetPrivate { + public: + explicit NativeWidgetWin(internal::NativeWidgetDelegate* delegate); + virtual ~NativeWidgetWin(); + + // Returns true if we are on Windows Vista or greater and composition is + // enabled. + static bool IsAeroGlassEnabled(); + + // Returns the system set window title font. + static gfx::Font GetWindowTitleFont(); + + // Show the window with the specified show command. + void Show(int show_state); + + // 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; + } + + // Obtain the view event with the given MSAA child id. Used in + // NativeViewAccessibilityWin::get_accChild to support requests for + // children of windowless controls. May return NULL + // (see ViewHierarchyChanged). + View* GetAccessibilityViewEventAt(int id); + + // Add a view that has recently fired an accessibility event. Returns a MSAA + // child id which is generated by: -(index of view in vector + 1) which + // guarantees a negative child id. This distinguishes the view from + // positive MSAA child id's which are direct leaf children of views that have + // associated hWnd's (e.g. NativeWidgetWin). + int AddAccessibilityViewEvent(View* view); + + // Clear a view that has recently been removed on a hierarchy change. + void ClearAccessibilityViewEvent(View* view); + + // Hides the window if it hasn't already been force-hidden. The force hidden + // count is tracked, so calling multiple times is allowed, you just have to + // be sure to call PopForceHidden the same number of times. + void PushForceHidden(); + + // Decrements the force hidden count, showing the window if we have reached + // the top of the stack. See PushForceHidden. + void PopForceHidden(); + + BOOL IsWindow() const { + return ::IsWindow(GetNativeView()); + } + + BOOL ShowWindow(int command) { + DCHECK(::IsWindow(GetNativeView())); + return ::ShowWindow(GetNativeView(), command); + } + + 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); + } + + // Overridden from ui::CompositorDelegate: + virtual void ScheduleDraw(); + + // Overridden from internal::NativeWidgetPrivate: + virtual void InitNativeWidget(const Widget::InitParams& params) OVERRIDE; + virtual NonClientFrameView* CreateNonClientFrameView() OVERRIDE; + virtual void UpdateFrameAfterFrameChange() OVERRIDE; + virtual bool ShouldUseNativeFrame() const OVERRIDE; + virtual void FrameTypeChanged() OVERRIDE; + virtual Widget* GetWidget() OVERRIDE; + virtual const Widget* GetWidget() const OVERRIDE; + virtual gfx::NativeView GetNativeView() const OVERRIDE; + virtual gfx::NativeWindow GetNativeWindow() const OVERRIDE; + virtual Widget* GetTopLevelWidget() OVERRIDE; + virtual const ui::Compositor* GetCompositor() const OVERRIDE; + virtual ui::Compositor* GetCompositor() OVERRIDE; + virtual void CalculateOffsetToAncestorWithLayer( + gfx::Point* offset, + ui::Layer** layer_parent) OVERRIDE; + virtual void ReorderLayers() OVERRIDE; + virtual void ViewRemoved(View* view) OVERRIDE; + virtual void SetNativeWindowProperty(const char* name, void* value) OVERRIDE; + virtual void* GetNativeWindowProperty(const char* name) const OVERRIDE; + virtual TooltipManager* GetTooltipManager() const OVERRIDE; + virtual bool IsScreenReaderActive() const OVERRIDE; + virtual void SendNativeAccessibilityEvent( + View* view, + ui::AccessibilityTypes::Event event_type) OVERRIDE; + virtual void SetMouseCapture() OVERRIDE; + virtual void ReleaseMouseCapture() OVERRIDE; + virtual bool HasMouseCapture() const OVERRIDE; + virtual InputMethod* CreateInputMethod() OVERRIDE; + virtual void CenterWindow(const gfx::Size& size) OVERRIDE; + virtual void GetWindowPlacement( + gfx::Rect* bounds, + ui::WindowShowState* show_state) const OVERRIDE; + virtual void SetWindowTitle(const string16& title) OVERRIDE; + virtual void SetWindowIcons(const SkBitmap& window_icon, + const SkBitmap& app_icon) OVERRIDE; + virtual void SetAccessibleName(const string16& name) OVERRIDE; + virtual void SetAccessibleRole(ui::AccessibilityTypes::Role role) OVERRIDE; + virtual void SetAccessibleState(ui::AccessibilityTypes::State state) OVERRIDE; + virtual void BecomeModal() OVERRIDE; + virtual gfx::Rect GetWindowScreenBounds() const OVERRIDE; + virtual gfx::Rect GetClientAreaScreenBounds() const OVERRIDE; + virtual gfx::Rect GetRestoredBounds() const OVERRIDE; + virtual void SetBounds(const gfx::Rect& bounds) OVERRIDE; + virtual void SetSize(const gfx::Size& size) OVERRIDE; + virtual void MoveAbove(gfx::NativeView native_view) OVERRIDE; + virtual void MoveToTop() OVERRIDE; + virtual void SetShape(gfx::NativeRegion shape) OVERRIDE; + virtual void Close() OVERRIDE; + virtual void CloseNow() OVERRIDE; + virtual void EnableClose(bool enable) OVERRIDE; + virtual void Show() OVERRIDE; + virtual void Hide() OVERRIDE; + virtual void ShowMaximizedWithBounds( + const gfx::Rect& restored_bounds) OVERRIDE; + virtual void ShowWithWindowState(ui::WindowShowState show_state) OVERRIDE; + virtual bool IsVisible() const OVERRIDE; + virtual void Activate() OVERRIDE; + virtual void Deactivate() OVERRIDE; + virtual bool IsActive() const OVERRIDE; + virtual void SetAlwaysOnTop(bool always_on_top) OVERRIDE; + virtual void Maximize() OVERRIDE; + virtual void Minimize() OVERRIDE; + virtual bool IsMaximized() const OVERRIDE; + virtual bool IsMinimized() const OVERRIDE; + virtual void Restore() OVERRIDE; + virtual void SetFullscreen(bool fullscreen) OVERRIDE; + virtual bool IsFullscreen() const OVERRIDE; + virtual void SetOpacity(unsigned char opacity) OVERRIDE; + virtual void SetUseDragFrame(bool use_drag_frame) OVERRIDE; + virtual bool IsAccessibleWidget() const OVERRIDE; + virtual void RunShellDrag(View* view, + const ui::OSExchangeData& data, + int operation) OVERRIDE; + virtual void SchedulePaintInRect(const gfx::Rect& rect) OVERRIDE; + virtual void SetCursor(gfx::NativeCursor cursor) OVERRIDE; + virtual void ClearNativeFocus() OVERRIDE; + virtual void FocusNativeView(gfx::NativeView native_view) OVERRIDE; + virtual bool ConvertPointFromAncestor( + const Widget* ancestor, gfx::Point* point) const OVERRIDE; + virtual gfx::Rect GetWorkAreaBoundsInScreen() const OVERRIDE; + virtual void SetInactiveRenderingDisabled(bool value) OVERRIDE; + + protected: + // Information saved before going into fullscreen mode, used to restore the + // window afterwards. + struct SavedWindowInfo { + bool maximized; + LONG style; + LONG ex_style; + RECT window_rect; + }; + + // Overridden from MessageLoop::Observer: + virtual base::EventStatus WillProcessEvent( + const base::NativeEvent& event) OVERRIDE; + virtual void DidProcessEvent(const base::NativeEvent& event) OVERRIDE; + + // Overridden from WindowImpl: + virtual HICON GetDefaultWindowIcon() const OVERRIDE; + virtual LRESULT OnWndProc(UINT message, + WPARAM w_param, + LPARAM l_param) OVERRIDE; + + // Message Handlers ---------------------------------------------------------- + + BEGIN_MSG_MAP_EX(NativeWidgetWin) + // Range handlers must go first! + MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseRange) + MESSAGE_RANGE_HANDLER_EX(WM_NCMOUSEMOVE, WM_NCXBUTTONDBLCLK, OnMouseRange) + + // Reflected message handler + MESSAGE_HANDLER_EX(base::win::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) + + // Mouse events. + MESSAGE_HANDLER_EX(WM_MOUSEACTIVATE, OnMouseActivate) + MESSAGE_HANDLER_EX(WM_MOUSELEAVE, OnMouseRange) + MESSAGE_HANDLER_EX(WM_NCMOUSELEAVE, OnMouseRange) + MESSAGE_HANDLER_EX(WM_SETCURSOR, OnSetCursor); + + // Key events. + MESSAGE_HANDLER_EX(WM_KEYDOWN, OnKeyEvent) + MESSAGE_HANDLER_EX(WM_KEYUP, OnKeyEvent) + MESSAGE_HANDLER_EX(WM_SYSKEYDOWN, OnKeyEvent) + MESSAGE_HANDLER_EX(WM_SYSKEYUP, OnKeyEvent) + + // IME Events. + MESSAGE_HANDLER_EX(WM_IME_SETCONTEXT, OnImeMessages) + MESSAGE_HANDLER_EX(WM_IME_STARTCOMPOSITION, OnImeMessages) + MESSAGE_HANDLER_EX(WM_IME_COMPOSITION, OnImeMessages) + MESSAGE_HANDLER_EX(WM_IME_ENDCOMPOSITION, OnImeMessages) + MESSAGE_HANDLER_EX(WM_IME_REQUEST, OnImeMessages) + MESSAGE_HANDLER_EX(WM_CHAR, OnImeMessages) + MESSAGE_HANDLER_EX(WM_SYSCHAR, OnImeMessages) + MESSAGE_HANDLER_EX(WM_DEADCHAR, OnImeMessages) + MESSAGE_HANDLER_EX(WM_SYSDEADCHAR, OnImeMessages) + + // 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_DISPLAYCHANGE(OnDisplayChange) + MSG_WM_ERASEBKGND(OnEraseBkgnd) + MSG_WM_ENDSESSION(OnEndSession) + MSG_WM_ENTERSIZEMOVE(OnEnterSizeMove) + MSG_WM_EXITMENULOOP(OnExitMenuLoop) + MSG_WM_EXITSIZEMOVE(OnExitSizeMove) + MSG_WM_GETMINMAXINFO(OnGetMinMaxInfo) + MSG_WM_HSCROLL(OnHScroll) + MSG_WM_INITMENU(OnInitMenu) + MSG_WM_INITMENUPOPUP(OnInitMenuPopup) + MSG_WM_INPUTLANGCHANGE(OnInputLangChange) + MSG_WM_KILLFOCUS(OnKillFocus) + MSG_WM_MOVE(OnMove) + MSG_WM_MOVING(OnMoving) + MSG_WM_NCACTIVATE(OnNCActivate) + MSG_WM_NCCALCSIZE(OnNCCalcSize) + MSG_WM_NCHITTEST(OnNCHitTest) + MSG_WM_NCPAINT(OnNCPaint) + MSG_WM_NOTIFY(OnNotify) + MSG_WM_PAINT(OnPaint) + MSG_WM_POWERBROADCAST(OnPowerBroadcast) + 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() + + // 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); + virtual void OnActivateApp(BOOL active, DWORD thread_id); + virtual LRESULT OnAppCommand(HWND window, short app_command, WORD device, + int keystate); + virtual void OnCancelMode(); + virtual void OnCaptureChanged(HWND hwnd); + virtual void OnClose(); + virtual void OnCommand(UINT notification_code, int command_id, HWND window); + virtual LRESULT OnCreate(CREATESTRUCT* create_struct); + // WARNING: If you override this be sure and invoke super, otherwise we'll + // leak a few things. + virtual void OnDestroy(); + virtual void OnDisplayChange(UINT bits_per_pixel, CSize screen_size); + virtual LRESULT OnDwmCompositionChanged(UINT msg, + WPARAM w_param, + LPARAM l_param); + virtual void OnEndSession(BOOL ending, UINT logoff); + virtual void OnEnterSizeMove(); + virtual LRESULT OnEraseBkgnd(HDC dc); + virtual void OnExitMenuLoop(BOOL is_track_popup_menu); + virtual void OnExitSizeMove(); + virtual LRESULT OnGetObject(UINT uMsg, WPARAM w_param, LPARAM l_param); + virtual void OnGetMinMaxInfo(MINMAXINFO* minmax_info); + virtual void OnHScroll(int scroll_type, short position, HWND scrollbar); + virtual LRESULT OnImeMessages(UINT message, WPARAM w_param, LPARAM l_param); + virtual void OnInitMenu(HMENU menu); + virtual void OnInitMenuPopup(HMENU menu, UINT position, BOOL is_system_menu); + virtual void OnInputLangChange(DWORD character_set, HKL input_language_id); + virtual LRESULT OnKeyEvent(UINT message, WPARAM w_param, LPARAM l_param); + virtual void OnKillFocus(HWND focused_window); + virtual LRESULT OnMouseActivate(UINT message, WPARAM w_param, LPARAM l_param); + virtual LRESULT OnMouseRange(UINT message, WPARAM w_param, LPARAM l_param); + virtual void OnMove(const CPoint& point); + virtual void OnMoving(UINT param, LPRECT new_bounds); + virtual LRESULT OnNCActivate(BOOL active); + virtual LRESULT OnNCCalcSize(BOOL w_param, LPARAM l_param); + virtual LRESULT OnNCHitTest(const CPoint& pt); + virtual void OnNCPaint(HRGN rgn); + virtual LRESULT OnNCUAHDrawCaption(UINT msg, + WPARAM w_param, + LPARAM l_param); + virtual LRESULT OnNCUAHDrawFrame(UINT msg, WPARAM w_param, LPARAM l_param); + virtual LRESULT OnNotify(int w_param, NMHDR* l_param); + virtual void OnPaint(HDC dc); + virtual LRESULT OnPowerBroadcast(DWORD power_event, DWORD data); + virtual LRESULT OnReflectedMessage(UINT msg, WPARAM w_param, LPARAM l_param); + virtual LRESULT OnSetCursor(UINT message, WPARAM w_param, LPARAM l_param); + virtual void OnSetFocus(HWND focused_window); + virtual LRESULT OnSetIcon(UINT size_type, HICON new_icon); + virtual LRESULT OnSetText(const wchar_t* text); + virtual void OnSettingChange(UINT flags, const wchar_t* section); + 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); + virtual void OnWindowPosChanging(WINDOWPOS* window_pos); + virtual void OnWindowPosChanged(WINDOWPOS* window_pos); + + // Deletes this window as it is destroyed, override to provide different + // behavior. + virtual void OnFinalMessage(HWND window); + + // Retrieve the show state of the window. This is one of the SW_SHOW* flags + // passed into Windows' ShowWindow method. For normal windows this defaults + // to SW_SHOWNORMAL, however windows (e.g. the main window) can override this + // method to provide different values (e.g. retrieve the user's specified + // show state from the shortcut starutp info). + virtual int GetShowState() const; + + // Returns the insets of the client area relative to the non-client area of + // the window. Override this function instead of OnNCCalcSize, which is + // crazily complicated. + virtual gfx::Insets GetClientAreaInsets() const; + + // Start tracking all mouse events so that this window gets sent mouse leave + // messages too. + void TrackMouseEvents(DWORD mouse_tracking_flags); + + // Called when a MSAA screen reader client is detected. + virtual void OnScreenReaderDetected(); + + // Executes the specified SC_command. + void ExecuteSystemMenuCommand(int command); + + // The TooltipManager. This is NULL if there is a problem creating the + // underlying tooltip window. + // WARNING: RootView's destructor calls into the TooltipManager. As such, this + // must be destroyed AFTER root_view_. + scoped_ptr<TooltipManagerWin> tooltip_manager_; + + scoped_refptr<DropTargetWin> drop_target_; + + const gfx::Rect& invalid_rect() const { return invalid_rect_; } + + // Saved window information from before entering fullscreen mode. + // TODO(beng): move to private once GetRestoredBounds() moves onto Widget. + SavedWindowInfo saved_window_info_; + + private: + typedef ScopedVector<ui::ViewProp> ViewProps; + + // Called after the WM_ACTIVATE message has been processed by the default + // windows procedure. + static void PostProcessActivateMessage(NativeWidgetWin* widget, + int activation_state); + + void SetInitParams(const Widget::InitParams& params); + + // Synchronously paints the invalid contents of the Widget. + void RedrawInvalidRect(); + + // Synchronously updates the invalid contents of the Widget. Valid for + // layered windows only. + void RedrawLayeredWindowContents(); + + // Lock or unlock the window from being able to redraw itself in response to + // updates to its invalid region. + class ScopedRedrawLock; + void LockUpdates(); + void UnlockUpdates(); + + // Determines whether the delegate expects the client size or the window size. + bool WidgetSizeIsClientSize() const; + + // Responds to the client area changing size, either at window creation time + // or subsequently. + void ClientAreaSizeChanged(); + + // Resets the window region for the current widget bounds if necessary. + // If |force| is true, the window region is reset to NULL even for native + // frame windows. + void ResetWindowRegion(bool force); + + // Calls DefWindowProc, safely wrapping the call in a ScopedRedrawLock to + // prevent frame flicker. DefWindowProc handling can otherwise render the + // classic-look window title bar directly. + LRESULT DefWindowProcWithRedrawLock(UINT message, + WPARAM w_param, + LPARAM l_param); + + // Stops ignoring SetWindowPos() requests (see below). + void StopIgnoringPosChanges() { ignore_window_pos_changes_ = false; } + + void RestoreEnabledIfNecessary(); + + void SetInitialFocus(); + + // Overridden from internal::InputMethodDelegate + virtual void DispatchKeyEventPostIME(const KeyEvent& key) OVERRIDE; + + // A delegate implementation that handles events received here. + // See class documentation for Widget in widget.h for a note about ownership. + internal::NativeWidgetDelegate* delegate_; + + // The following factory is used for calls to close the NativeWidgetWin + // instance. + base::WeakPtrFactory<NativeWidgetWin> 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_; + + // Should we keep an off-screen buffer? This is false by default, set to true + // when WS_EX_LAYERED is specified before the native window is created. + // + // 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 + // most 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<gfx::CanvasSkia> layered_window_contents_; + + // We must track the invalid rect ourselves, for two reasons: + // For layered windows, Windows will not do this properly with + // InvalidateRect()/GetUpdateRect(). (In fact, it'll return misleading + // information from GetUpdateRect()). + // We also need to keep track of the invalid rectangle for the RootView should + // we need to paint the non-client area. The data supplied to WM_NCPAINT seems + // to be insufficient. + gfx::Rect invalid_rect_; + + // A factory that allows us to schedule a redraw for layered windows. + base::WeakPtrFactory<NativeWidgetWin> paint_layered_window_factory_; + + // See class documentation for Widget in widget.h for a note about ownership. + Widget::InitParams::Ownership ownership_; + + // True if we are allowed to update the layered window from the DIB backing + // store if necessary. + bool can_update_layered_window_; + + // Whether the focus should be restored next time we get enabled. Needed to + // restore focus correctly when Windows modal dialogs are displayed. + bool restore_focus_when_enabled_; + + // Instance of accessibility information and handling for MSAA root + base::win::ScopedComPtr<IAccessible> accessibility_root_; + + // Value determines whether the Widget is customized for accessibility. + static bool screen_reader_active_; + + // The maximum number of view events in our vector below. + static const int kMaxAccessibilityViewEvents = 20; + + // A vector used to access views for which we have sent notifications to + // accessibility clients. It is used as a circular queue. + std::vector<View*> accessibility_view_events_; + + // The current position of the view events vector. When incrementing, + // we always mod this value with the max view events above . + int accessibility_view_events_index_; + + // The last cursor that was active before the current one was selected. Saved + // so that we can restore it. + gfx::NativeCursor previous_cursor_; + + ViewProps props_; + + // True if we're in fullscreen mode. + bool fullscreen_; + + // If this is greater than zero, we should prevent attempts to make the window + // visible when we handle WM_WINDOWPOSCHANGING. Some calls like + // ShowWindow(SW_RESTORE) make the window visible in addition to restoring it, + // when all we want to do is restore it. + int force_hidden_count_; + + // The window styles before we modified them for the drag frame appearance. + DWORD drag_frame_saved_window_style_; + DWORD drag_frame_saved_window_ex_style_; + + // Represents the number of ScopedRedrawLocks active against this widget. + // If this is greater than zero, the widget should be locked against updates. + int lock_updates_count_; + + // The window styles of the window before updates were locked. + DWORD saved_window_style_; + + // When true, this flag makes us discard incoming SetWindowPos() requests that + // only change our position/size. (We still allow changes to Z-order, + // activation, etc.) + bool ignore_window_pos_changes_; + + // The following factory is used to ignore SetWindowPos() calls for short time + // periods. + base::WeakPtrFactory<NativeWidgetWin> ignore_pos_changes_factory_; + + // The last-seen monitor containing us, and its rect and work area. These are + // used to catch updates to the rect and work area and react accordingly. + HMONITOR last_monitor_; + gfx::Rect last_monitor_rect_, last_work_area_; + + // Set to true when the user presses the right mouse button on the caption + // area. We need this so we can correctly show the context menu on mouse-up. + bool is_right_mouse_pressed_on_caption_; + + // Whether all ancestors have been enabled. This is only used if is_modal_ is + // true. + bool restored_enabled_; + + // The compositor for accelerated drawing. + scoped_refptr<ui::Compositor> compositor_; + + // This flag can be initialized and checked after certain operations (such as + // DefWindowProc) to avoid stack-controlled NativeWidgetWin operations (such + // as unlocking the Window with a ScopedRedrawLock) after Widget destruction. + bool* destroyed_; + + // True if the widget is going to have a non_client_view. We cache this value + // rather than asking the Widget for the non_client_view so that we know at + // Init time, before the Widget has created the NonClientView. + bool has_non_client_view_; + + DISALLOW_COPY_AND_ASSIGN(NativeWidgetWin); +}; + +} // namespace views + +#endif // VIEWS_WIDGET_NATIVE_WIDGET_WIN_H_ diff --git a/views/widget/native_widget_win_unittest.cc b/views/widget/native_widget_win_unittest.cc new file mode 100644 index 0000000..f2465f4 --- /dev/null +++ b/views/widget/native_widget_win_unittest.cc @@ -0,0 +1,86 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/native_widget_win.h" + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace views { +namespace { + +class NativeWidgetWinTest : public testing::Test { + public: + NativeWidgetWinTest() { + OleInitialize(NULL); + } + + ~NativeWidgetWinTest() { + OleUninitialize(); + } + + virtual void TearDown() { + // Flush the message loop because we have pending release tasks + // and these tasks if un-executed would upset Valgrind. + RunPendingMessages(); + } + + // Create a simple widget win. The caller is responsible for taking ownership + // of the returned value. + NativeWidgetWin* CreateNativeWidgetWin(); + + void RunPendingMessages() { + message_loop_.RunAllPending(); + } + + private: + MessageLoopForUI message_loop_; + + DISALLOW_COPY_AND_ASSIGN(NativeWidgetWinTest); +}; + +NativeWidgetWin* NativeWidgetWinTest::CreateNativeWidgetWin() { + scoped_ptr<Widget> widget(new Widget); + Widget::InitParams params(Widget::InitParams::TYPE_POPUP); + params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.bounds = gfx::Rect(50, 50, 650, 650); + widget->Init(params); + return static_cast<NativeWidgetWin*>(widget.release()->native_widget()); +} + +TEST_F(NativeWidgetWinTest, ZoomWindow) { + scoped_ptr<NativeWidgetWin> window(CreateNativeWidgetWin()); + window->ShowWindow(SW_HIDE); + EXPECT_FALSE(window->IsActive()); + window->ShowWindow(SW_MAXIMIZE); + EXPECT_TRUE(window->IsZoomed()); + window->CloseNow(); +} + +TEST_F(NativeWidgetWinTest, SetBoundsForZoomedWindow) { + scoped_ptr<NativeWidgetWin> window(CreateNativeWidgetWin()); + window->ShowWindow(SW_MAXIMIZE); + EXPECT_TRUE(window->IsZoomed()); + + // Create another window, so that it will be active. + scoped_ptr<NativeWidgetWin> window2(CreateNativeWidgetWin()); + window2->ShowWindow(SW_MAXIMIZE); + EXPECT_TRUE(window2->IsActive()); + EXPECT_FALSE(window->IsActive()); + + // Verify that setting the bounds of a zoomed window will unzoom it and not + // cause it to be activated. + window->SetBounds(gfx::Rect(50, 50, 650, 650)); + EXPECT_FALSE(window->IsZoomed()); + EXPECT_FALSE(window->IsActive()); + + // Cleanup. + window->CloseNow(); + window2->CloseNow(); +} + +} // namespace +} // namespace views diff --git a/views/widget/root_view.cc b/views/widget/root_view.cc new file mode 100644 index 0000000..2682bfc --- /dev/null +++ b/views/widget/root_view.cc @@ -0,0 +1,455 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/root_view.h" + +#include <algorithm> + +#include "base/logging.h" +#include "base/message_loop.h" +#include "ui/base/accessibility/accessible_view_state.h" +#include "ui/base/dragdrop/drag_drop_types.h" +#include "ui/base/keycodes/keyboard_codes.h" +#include "ui/gfx/canvas_skia.h" +#include "ui/gfx/compositor/layer.h" +#include "ui/views/focus/view_storage.h" +#include "ui/views/layout/fill_layout.h" +#include "ui/views/touchui/gesture_manager.h" +#include "views/widget/widget.h" + +namespace views { +namespace internal { + +// static +const char RootView::kViewClassName[] = "views/RootView"; + +//////////////////////////////////////////////////////////////////////////////// +// RootView, public: + +// Creation and lifetime ------------------------------------------------------- + +RootView::RootView(Widget* widget) + : widget_(widget), + mouse_pressed_handler_(NULL), + mouse_move_handler_(NULL), + last_click_handler_(NULL), + explicit_mouse_handler_(false), + last_mouse_event_flags_(0), + last_mouse_event_x_(-1), + last_mouse_event_y_(-1), + gesture_manager_(GestureManager::GetInstance()), + touch_pressed_handler_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(focus_search_(this, false, false)), + focus_traversable_parent_(NULL), + focus_traversable_parent_view_(NULL) { +} + +RootView::~RootView() { + // If we have children remove them explicitly so to make sure a remove + // notification is sent for each one of them. + if (has_children()) + RemoveAllChildViews(true); +} + +// Tree operations ------------------------------------------------------------- + +void RootView::SetContentsView(View* contents_view) { + DCHECK(contents_view && GetWidget()->native_widget()) << + "Can't be called until after the native widget is created!"; + // The ContentsView must be set up _after_ the window is created so that its + // Widget pointer is valid. + SetLayoutManager(new FillLayout); + if (has_children()) + RemoveAllChildViews(true); + AddChildView(contents_view); + + // Force a layout now, since the attached hierarchy won't be ready for the + // containing window's bounds. Note that we call Layout directly rather than + // calling the widget's size changed handler, since the RootView's bounds may + // not have changed, which will cause the Layout not to be done otherwise. + Layout(); +} + +View* RootView::GetContentsView() { + return child_count() > 0 ? child_at(0) : NULL; +} + +void RootView::NotifyNativeViewHierarchyChanged(bool attached, + gfx::NativeView native_view) { + PropagateNativeViewHierarchyChanged(attached, native_view, this); +} + +// Input ----------------------------------------------------------------------- + +bool RootView::OnKeyEvent(const KeyEvent& event) { + bool consumed = false; + + View* v = GetFocusManager()->GetFocusedView(); + // Special case to handle right-click context menus triggered by the + // keyboard. + if (v && v->IsEnabled() && ((event.key_code() == ui::VKEY_APPS) || + (event.key_code() == ui::VKEY_F10 && event.IsShiftDown()))) { + v->ShowContextMenu(v->GetKeyboardContextMenuLocation(), false); + return true; + } + for (; v && v != this && !consumed; v = v->parent()) { + consumed = (event.type() == ui::ET_KEY_PRESSED) ? + v->OnKeyPressed(event) : v->OnKeyReleased(event); + } + return consumed; +} + +// Focus ----------------------------------------------------------------------- + +void RootView::SetFocusTraversableParent(FocusTraversable* focus_traversable) { + DCHECK(focus_traversable != this); + focus_traversable_parent_ = focus_traversable; +} + +void RootView::SetFocusTraversableParentView(View* view) { + focus_traversable_parent_view_ = view; +} + +// System events --------------------------------------------------------------- + +void RootView::ThemeChanged() { + View::PropagateThemeChanged(); +} + +void RootView::LocaleChanged() { + View::PropagateLocaleChanged(); +} + +//////////////////////////////////////////////////////////////////////////////// +// RootView, FocusTraversable implementation: + +FocusSearch* RootView::GetFocusSearch() { + return &focus_search_; +} + +FocusTraversable* RootView::GetFocusTraversableParent() { + return focus_traversable_parent_; +} + +View* RootView::GetFocusTraversableParentView() { + return focus_traversable_parent_view_; +} + +//////////////////////////////////////////////////////////////////////////////// +// RootView, View overrides: + +const Widget* RootView::GetWidget() const { + return widget_; +} + +Widget* RootView::GetWidget() { + return const_cast<Widget*>(const_cast<const RootView*>(this)->GetWidget()); +} + +bool RootView::IsVisibleInRootView() const { + return IsVisible(); +} + +std::string RootView::GetClassName() const { + return kViewClassName; +} + +void RootView::SchedulePaintInRect(const gfx::Rect& rect) { + if (layer()) { + layer()->SchedulePaint(rect); + } else { + gfx::Rect xrect = ConvertRectToParent(rect); + gfx::Rect invalid_rect = GetLocalBounds().Intersect(xrect); + if (!invalid_rect.IsEmpty()) + widget_->SchedulePaintInRect(invalid_rect); + } +} + +bool RootView::OnMousePressed(const MouseEvent& event) { + MouseEvent e(event, this); + 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_ = GetEventHandlerForPoint(e.location()); + mouse_pressed_handler_ && (mouse_pressed_handler_ != this); + mouse_pressed_handler_ = mouse_pressed_handler_->parent()) { + 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.flags() & ~ui::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; + + // 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.flags() & ui::EF_IS_DOUBLE_CLICK) + hit_disabled_view = true; + + last_click_handler_ = NULL; + return hit_disabled_view; +} + +bool RootView::OnMouseDragged(const MouseEvent& event) { + MouseEvent e(event, this); + UpdateCursor(e); + + if (mouse_pressed_handler_) { + SetMouseLocationAndFlags(e); + + MouseEvent mouse_event(e, this, mouse_pressed_handler_); + return mouse_pressed_handler_->ProcessMouseDragged(mouse_event, &drag_info); + } + return false; +} + +void RootView::OnMouseReleased(const MouseEvent& event) { + MouseEvent e(event, this); + UpdateCursor(e); + + if (mouse_pressed_handler_) { + MouseEvent mouse_released(e, this, mouse_pressed_handler_); + // 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_; + SetMouseHandler(NULL); + mouse_pressed_handler->ProcessMouseReleased(mouse_released); + // WARNING: we may have been deleted. + } +} + +void RootView::OnMouseCaptureLost() { + if (mouse_pressed_handler_) { + // Synthesize a release event for UpdateCursor. + MouseEvent release_event(ui::ET_MOUSE_RELEASED, last_mouse_event_x_, + last_mouse_event_y_, last_mouse_event_flags_); + UpdateCursor(release_event); + // We allow the view to delete us from OnMouseCaptureLost. As such, + // configure state such that we're done first, then call View. + View* mouse_pressed_handler = mouse_pressed_handler_; + SetMouseHandler(NULL); + mouse_pressed_handler->OnMouseCaptureLost(); + // WARNING: we may have been deleted. + } +} + +void RootView::OnMouseMoved(const MouseEvent& event) { + MouseEvent e(event, this); + View* v = GetEventHandlerForPoint(e.location()); + // Find the first enabled view, or the existing move handler, whichever comes + // first. The check for the existing handler is because if a view becomes + // disabled while handling moves, it's wrong to suddenly send ET_MOUSE_EXITED + // and ET_MOUSE_ENTERED events, because the mouse hasn't actually exited yet. + while (v && !v->IsEnabled() && (v != mouse_move_handler_)) + v = v->parent(); + if (v && v != this) { + if (v != mouse_move_handler_) { + if (mouse_move_handler_ != NULL) + mouse_move_handler_->OnMouseExited(e); + mouse_move_handler_ = v; + MouseEvent entered_event(e, this, mouse_move_handler_); + mouse_move_handler_->OnMouseEntered(entered_event); + } + MouseEvent moved_event(e, this, mouse_move_handler_); + mouse_move_handler_->OnMouseMoved(moved_event); + if (!(moved_event.flags() & ui::EF_IS_NON_CLIENT)) + widget_->SetCursor(mouse_move_handler_->GetCursor(moved_event)); + } else if (mouse_move_handler_ != NULL) { + mouse_move_handler_->OnMouseExited(e); + widget_->SetCursor(gfx::kNullCursor); + } +} + +void RootView::OnMouseExited(const MouseEvent& event) { + if (mouse_move_handler_ != NULL) { + mouse_move_handler_->OnMouseExited(event); + mouse_move_handler_ = NULL; + } +} + +bool RootView::OnMouseWheel(const MouseWheelEvent& event) { + MouseWheelEvent e(event, this); + bool consumed = false; + for (View* v = GetFocusManager()->GetFocusedView(); + v && v != this && !consumed; v = v->parent()) + consumed = v->OnMouseWheel(e); + return consumed; +} + +ui::TouchStatus RootView::OnTouchEvent(const TouchEvent& event) { + TouchEvent e(event, this); + + // If touch_pressed_handler_ is non null, we are currently processing + // a touch down on the screen situation. In that case we send the + // event to touch_pressed_handler_ + ui::TouchStatus status = ui::TOUCH_STATUS_UNKNOWN; + + if (touch_pressed_handler_) { + TouchEvent touch_event(e, this, touch_pressed_handler_); + status = touch_pressed_handler_->ProcessTouchEvent(touch_event); + if (gesture_manager_->ProcessTouchEventForGesture(e, this, status)) + status = ui::TOUCH_STATUS_SYNTH_MOUSE; + if (status == ui::TOUCH_STATUS_END) + touch_pressed_handler_ = NULL; + return status; + } + + // Walk up the tree until we find a view that wants the touch event. + for (touch_pressed_handler_ = GetEventHandlerForPoint(e.location()); + touch_pressed_handler_ && (touch_pressed_handler_ != this); + touch_pressed_handler_ = touch_pressed_handler_->parent()) { + if (!touch_pressed_handler_->IsEnabled()) { + // Disabled views eat events but are treated as not handled by the + // the GestureManager. + status = ui::TOUCH_STATUS_UNKNOWN; + break; + } + + // See if this view wants to handle the touch + TouchEvent touch_event(e, this, touch_pressed_handler_); + status = touch_pressed_handler_->ProcessTouchEvent(touch_event); + + // The view could have removed itself from the tree when handling + // OnTouchEvent(). So handle as per OnMousePressed. NB: we + // assume that the RootView itself cannot be so removed. + if (!touch_pressed_handler_) + break; + + // The touch event wasn't processed. Go up the view hierarchy and dispatch + // the touch event. + if (status == ui::TOUCH_STATUS_UNKNOWN) + continue; + + // If the touch didn't initiate a touch-sequence, then reset the touch event + // handler. Otherwise, leave it set so that subsequent touch events are + // dispatched to the same handler. + if (status != ui::TOUCH_STATUS_START) + touch_pressed_handler_ = NULL; + + if (gesture_manager_->ProcessTouchEventForGesture(e, this, status)) + status = ui::TOUCH_STATUS_SYNTH_MOUSE; + return status; + } + + // Reset touch_pressed_handler_ to indicate that no processing is occurring. + touch_pressed_handler_ = NULL; + + // Give the touch event to the gesture manager. + if (gesture_manager_->ProcessTouchEventForGesture(e, this, status)) + status = ui::TOUCH_STATUS_SYNTH_MOUSE; + return status; +} + +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::GetAccessibleState(ui::AccessibleViewState* state) { + state->role = ui::AccessibilityTypes::ROLE_APPLICATION; +} + +//////////////////////////////////////////////////////////////////////////////// +// RootView, protected: + +void RootView::ViewHierarchyChanged(bool is_add, View* parent, View* child) { + widget_->ViewHierarchyChanged(is_add, parent, child); + + if (!is_add) { + if (!explicit_mouse_handler_ && mouse_pressed_handler_ == child) + mouse_pressed_handler_ = NULL; + if (mouse_move_handler_ == child) + mouse_move_handler_ = NULL; + if (touch_pressed_handler_ == child) + touch_pressed_handler_ = NULL; + } +} + +void RootView::OnPaint(gfx::Canvas* canvas) { + if (!layer() || !layer()->fills_bounds_opaquely()) + canvas->GetSkCanvas()->drawColor(SK_ColorBLACK, SkXfermode::kClear_Mode); + + // TODO (pkotwicz): Remove this once we switch over to Aura desktop. + // This is needed so that we can set the background behind the RWHV when the + // RWHV is not visible. Not needed once there is a view between the RootView + // and RWHV. + View::OnPaint(canvas); +} + +void RootView::CalculateOffsetToAncestorWithLayer(gfx::Point* offset, + ui::Layer** layer_parent) { + View::CalculateOffsetToAncestorWithLayer(offset, layer_parent); + if (!layer()) + widget_->CalculateOffsetToAncestorWithLayer(offset, layer_parent); +} + +//////////////////////////////////////////////////////////////////////////////// +// RootView, private: + +// Input ----------------------------------------------------------------------- + +void RootView::UpdateCursor(const MouseEvent& event) { + if (!(event.flags() & ui::EF_IS_NON_CLIENT)) { + View* v = GetEventHandlerForPoint(event.location()); + widget_->SetCursor(v->GetCursor(MouseEvent(event, this, v))); + } +} + +void RootView::SetMouseLocationAndFlags(const MouseEvent& event) { + last_mouse_event_flags_ = event.flags(); + last_mouse_event_x_ = event.x(); + last_mouse_event_y_ = event.y(); +} + +} // namespace internal +} // namespace views diff --git a/views/widget/root_view.h b/views/widget/root_view.h new file mode 100644 index 0000000..1a17d38 --- /dev/null +++ b/views/widget/root_view.h @@ -0,0 +1,207 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_ROOT_VIEW_H_ +#define VIEWS_WIDGET_ROOT_VIEW_H_ +#pragma once + +#include <string> + +#include "base/memory/ref_counted.h" +#include "ui/views/focus/focus_manager.h" +#include "ui/views/focus/focus_search.h" +#include "views/view.h" + +namespace ui { +enum TouchStatus; +} + +namespace views { + +class Widget; +class GestureManager; + +// This is a views-internal API and should not be used externally. +// Widget exposes this object as a View*. +namespace internal { + +//////////////////////////////////////////////////////////////////////////////// +// RootView class +// +// The RootView is the root of a View hierarchy. A RootView is attached to a +// Widget. The Widget is responsible for receiving events from the host +// environment, converting them to views-compatible events and then forwarding +// them to the RootView for propagation into the View hierarchy. +// +// A RootView can have only one child, called its "Contents View" which is +// sized to fill the bounds of the RootView (and hence the client area of the +// Widget). Call SetContentsView() after the associated Widget has been +// initialized to attach the contents view to the RootView. +// TODO(beng): Enforce no other callers to AddChildView/tree functions by +// overriding those methods as private here. +// TODO(beng): Clean up API further, make Widget a friend. +// TODO(sky): We don't really want to export this class. +// +class VIEWS_EXPORT RootView : public View, public FocusTraversable { + public: + static const char kViewClassName[]; + + // Creation and lifetime ----------------------------------------------------- + explicit RootView(Widget* widget); + virtual ~RootView(); + + // Tree operations ----------------------------------------------------------- + + // Sets the "contents view" of the RootView. This is the single child view + // that is responsible for laying out the contents of the widget. + void SetContentsView(View* contents_view); + View* GetContentsView(); + + // Called when parent of the host changed. + void NotifyNativeViewHierarchyChanged(bool attached, + gfx::NativeView native_view); + + // Input --------------------------------------------------------------------- + + // 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 OnKeyEvent(const KeyEvent& event); + + // Provided only for testing: + void SetGestureManagerForTesting(GestureManager* g) { gesture_manager_ = g; } + + // Focus --------------------------------------------------------------------- + + // 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); + + // System events ------------------------------------------------------------- + + // Public API for broadcasting theme change notifications to this View + // hierarchy. + void ThemeChanged(); + + // Public API for broadcasting locale change notifications to this View + // hierarchy. + void LocaleChanged(); + + // Overridden from FocusTraversable: + virtual FocusSearch* GetFocusSearch() OVERRIDE; + virtual FocusTraversable* GetFocusTraversableParent() OVERRIDE; + virtual View* GetFocusTraversableParentView() OVERRIDE; + + // Overridden from View: + virtual const Widget* GetWidget() const OVERRIDE; + virtual Widget* GetWidget() OVERRIDE; + virtual bool IsVisibleInRootView() const OVERRIDE; + virtual std::string GetClassName() const OVERRIDE; + virtual void SchedulePaintInRect(const gfx::Rect& rect) OVERRIDE; + virtual bool OnMousePressed(const MouseEvent& event) OVERRIDE; + virtual bool OnMouseDragged(const MouseEvent& event) OVERRIDE; + virtual void OnMouseReleased(const MouseEvent& event) OVERRIDE; + virtual void OnMouseCaptureLost() OVERRIDE; + virtual void OnMouseMoved(const MouseEvent& event) OVERRIDE; + virtual void OnMouseExited(const MouseEvent& event) OVERRIDE; + virtual bool OnMouseWheel(const MouseWheelEvent& event) OVERRIDE; + virtual ui::TouchStatus OnTouchEvent(const TouchEvent& event) OVERRIDE; + virtual void SetMouseHandler(View* new_mouse_handler) OVERRIDE; + virtual void GetAccessibleState(ui::AccessibleViewState* state) OVERRIDE; + + protected: + // Overridden from View: + virtual void ViewHierarchyChanged(bool is_add, View* parent, + View* child) OVERRIDE; + virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE; + virtual void CalculateOffsetToAncestorWithLayer( + gfx::Point* offset, + ui::Layer** layer_parent) OVERRIDE; + + private: + friend class View; + friend class Widget; + + // Required so the GestureManager can call the Process* entry points + // with synthetic events as necessary. + friend class GestureManager; + + // Input --------------------------------------------------------------------- + + // 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. The location of the mouse should be in the + // current coordinate system (i.e. any necessary transformation should be + // applied to the point prior to calling this). + void UpdateCursor(const MouseEvent& event); + + // Updates the last_mouse_* fields from e. The location of the mouse should be + // in the current coordinate system (i.e. any necessary transformation should + // be applied to the point prior to calling this). + void SetMouseLocationAndFlags(const MouseEvent& event); + + ////////////////////////////////////////////////////////////////////////////// + + // Tree operations ----------------------------------------------------------- + + // The host Widget + Widget* widget_; + + // Input --------------------------------------------------------------------- + + // 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_; + + // true if mouse_pressed_handler_ has been explicitly set + bool explicit_mouse_handler_; + + // 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 gesture_manager_ for this. + GestureManager* gesture_manager_; + + // The view currently handling touch events. + View* touch_pressed_handler_; + + // Focus --------------------------------------------------------------------- + + // The focus search algorithm. + FocusSearch focus_search_; + + // Whether this root view belongs to the current active window. + // bool activated_; + + // 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_; + + // Drag and drop ------------------------------------------------------------- + + // Tracks drag state for a view. + View::DragInfo drag_info; + + DISALLOW_IMPLICIT_CONSTRUCTORS(RootView); +}; + +} // namespace internal +} // namespace views + +#endif // VIEWS_WIDGET_ROOT_VIEW_H_ diff --git a/views/widget/tooltip_manager.cc b/views/widget/tooltip_manager.cc new file mode 100644 index 0000000..1d8b285 --- /dev/null +++ b/views/widget/tooltip_manager.cc @@ -0,0 +1,63 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/tooltip_manager.h" + +#include <vector> + +#include "base/string_split.h" +#include "base/utf_string_conversions.h" +#include "ui/base/text/text_elider.h" + +namespace { + +// Maximum number of characters we allow in a tooltip. +const size_t kMaxTooltipLength = 1024; + +// Maximum number of lines we allow in the tooltip. +const size_t kMaxLines = 6; + +} // namespace + +namespace views { + +// static +void TooltipManager::TrimTooltipToFit(string16* text, + int* max_width, + int* line_count, + int x, + int y) { + *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. + if (text->length() > kMaxTooltipLength) + *text = text->substr(0, kMaxTooltipLength); + + // Determine the available width for the tooltip. + int available_width = GetMaxWidth(x, y); + + // Split the string into at most kMaxLines lines. + std::vector<string16> lines; + base::SplitString(*text, '\n', &lines); + if (lines.size() > kMaxLines) + lines.resize(kMaxLines); + *line_count = static_cast<int>(lines.size()); + + // Format each line to fit. + gfx::Font font = GetDefaultFont(); + string16 result; + for (std::vector<string16>::iterator i = lines.begin(); i != lines.end(); + ++i) { + string16 elided_text = ui::ElideText(*i, font, available_width, false); + *max_width = std::max(*max_width, font.GetStringWidth(elided_text)); + if (!result.empty()) + result.push_back('\n'); + result.append(elided_text); + } + *text = result; +} + +} // namespace views diff --git a/views/widget/tooltip_manager.h b/views/widget/tooltip_manager.h new file mode 100644 index 0000000..6b4c8d9 --- /dev/null +++ b/views/widget/tooltip_manager.h @@ -0,0 +1,68 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_TOOLTIP_MANAGER_H_ +#define VIEWS_WIDGET_TOOLTIP_MANAGER_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/string16.h" +#include "views/views_export.h" + +namespace gfx { +class Font; +} // namespace gfx + +namespace views { + +class View; + +// TooltipManager takes care of the wiring to support tooltips for Views. You +// almost never need to interact directly with TooltipManager, rather look to +// the various tooltip methods on View. +class VIEWS_EXPORT 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 gfx::Font GetDefaultFont(); + + // Returns the maximum width of the tooltip. |x| and |y| give the location + // the tooltip is to be displayed on in screen coordinates. + static int GetMaxWidth(int x, int y); + + TooltipManager() {} + virtual ~TooltipManager() {} + + // Notification that the view hierarchy has changed in some way. + virtual void UpdateTooltip() = 0; + + // Invoked when the tooltip text changes for the specified views. + virtual void TooltipTextChanged(View* view) = 0; + + // Invoked when toolbar icon gets focus. + virtual void ShowKeyboardTooltip(View* view) = 0; + + // Invoked when toolbar loses focus. + virtual void HideKeyboardTooltip() = 0; + + protected: + // Trims the tooltip to fit, setting |text| to the clipped result, + // |max_width| to the width (in pixels) of the clipped text and |line_count| + // to the number of lines of text in the tooltip. |x| and |y| give the + // location of the tooltip in screen coordinates. + static void TrimTooltipToFit(string16* text, + int* max_width, + int* line_count, + int x, + int y); +}; + +} // namespace views + +#endif // VIEWS_WIDGET_TOOLTIP_MANAGER_H_ diff --git a/views/widget/tooltip_manager_gtk.cc b/views/widget/tooltip_manager_gtk.cc new file mode 100644 index 0000000..82828fc --- /dev/null +++ b/views/widget/tooltip_manager_gtk.cc @@ -0,0 +1,174 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/tooltip_manager_gtk.h" + +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "ui/gfx/font.h" +#include "ui/gfx/screen.h" +#include "ui/views/focus/focus_manager.h" +#include "views/view.h" +#include "views/widget/native_widget_gtk.h" + +// WARNING: this implementation is good for a start, but it doesn't give us +// control of tooltip positioning both on mouse events and when showing from +// keyboard. We may need to write our own to give us the control we need. + +namespace views { + +static gfx::Font* LoadDefaultFont() { + // Create a tooltip widget and extract the font from it (we have to realize + // it to make sure the correct font gets set). + GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP); + gtk_widget_set_name(window, "gtk-tooltip"); + GtkWidget* label = gtk_label_new(""); + gtk_widget_show(label); + + gtk_container_add(GTK_CONTAINER(window), label); + gtk_widget_realize(window); + + GtkStyle* style = gtk_widget_get_style(label); + gfx::Font* font = new gfx::Font(style->font_desc); + + gtk_widget_destroy(window); + + return font; +} + +// static +int TooltipManager::GetTooltipHeight() { + // This is only used to position the tooltip, and we don't yet support + // positioning the tooltip, it isn't worth trying to implement this. + return 0; +} + +// static +gfx::Font TooltipManager::GetDefaultFont() { + static gfx::Font* font = NULL; + if (!font) + font = LoadDefaultFont(); + + return *font; +} + +// static +int TooltipManager::GetMaxWidth(int x, int y) { + gfx::Rect monitor_bounds = + gfx::Screen::GetMonitorAreaNearestPoint(gfx::Point(x, y)); + // GtkLabel (gtk_label_ensure_layout) forces wrapping at this size. We mirror + // the size here otherwise tooltips wider than the size used by gtklabel end + // up with extraneous empty lines. + return monitor_bounds.width() == 0 ? 800 : (monitor_bounds.width() + 1) / 2; +} + +TooltipManagerGtk::TooltipManagerGtk(NativeWidgetGtk* widget) + : widget_(widget), + keyboard_view_(NULL), + tooltip_window_(widget->window_contents()) { +} + +bool TooltipManagerGtk::ShowTooltip(int x, int y, bool for_keyboard, + GtkTooltip* tooltip) { + const View* view = NULL; + gfx::Point view_loc; + if (keyboard_view_) { + view = keyboard_view_; + view_loc.SetPoint(view->width() / 2, view->height() / 2); + } else if (!for_keyboard) { + View* root_view = widget_->GetWidget()->GetRootView(); + view = root_view->GetEventHandlerForPoint(gfx::Point(x, y)); + view_loc.SetPoint(x, y); + View::ConvertPointFromWidget(view, &view_loc); + } else { + const FocusManager* focus_manager = widget_->GetWidget()->GetFocusManager(); + if (focus_manager) { + view = focus_manager->GetFocusedView(); + if (view) + view_loc.SetPoint(view->width() / 2, view->height() / 2); + } + } + + if (!view) + return false; + + string16 text; + if (!view->GetTooltipText(view_loc, &text)) + return false; + + // Sets the area of the tooltip. This way if different views in the same + // widget have tooltips the tooltip doesn't get stuck at the same location. + gfx::Rect vis_bounds = view->GetVisibleBounds(); + gfx::Point widget_loc(vis_bounds.origin()); + View::ConvertPointToWidget(view, &widget_loc); + GdkRectangle tip_area = { widget_loc.x(), widget_loc.y(), + vis_bounds.width(), vis_bounds.height() }; + gtk_tooltip_set_tip_area(tooltip, &tip_area); + + int max_width, line_count; + gfx::Point screen_loc(x, y); + View::ConvertPointToScreen(widget_->GetWidget()->GetRootView(), &screen_loc); + TrimTooltipToFit(&text, &max_width, &line_count, screen_loc.x(), + screen_loc.y()); + tooltip_window_.SetTooltipText(text); + + return true; +} + +void TooltipManagerGtk::UpdateTooltip() { + // UpdateTooltip may be invoked after the widget has been destroyed. + GtkWidget* widget = widget_->GetNativeView(); + if (!widget) + return; + + GdkDisplay* display = gtk_widget_get_display(widget); + if (display) + gtk_tooltip_trigger_tooltip_query(display); +} + +void TooltipManagerGtk::TooltipTextChanged(View* view) { + UpdateTooltip(); +} + +void TooltipManagerGtk::ShowKeyboardTooltip(View* view) { + if (view == keyboard_view_) + return; // We're already showing the tip for the specified view. + + // We have to hide the current tooltip, then show again. + HideKeyboardTooltip(); + + string16 tooltip_text; + if (!view->GetTooltipText(gfx::Point(), &tooltip_text)) + return; // The view doesn't have a tooltip, nothing to do. + + keyboard_view_ = view; + if (!SendShowHelpSignal()) { + keyboard_view_ = NULL; + return; + } +} + +void TooltipManagerGtk::HideKeyboardTooltip() { + if (!keyboard_view_) + return; + + SendShowHelpSignal(); + keyboard_view_ = NULL; +} + +bool TooltipManagerGtk::SendShowHelpSignal() { + GtkWidget* widget = widget_->window_contents(); + GType itype = G_TYPE_FROM_INSTANCE(G_OBJECT(widget)); + guint signal_id; + GQuark detail; + if (!g_signal_parse_name("show_help", itype, &signal_id, &detail, FALSE)) { + NOTREACHED(); + return false; + } + gboolean result; + g_signal_emit(widget, signal_id, 0, GTK_WIDGET_HELP_TOOLTIP, &result); + return true; +} + +} // namespace views diff --git a/views/widget/tooltip_manager_gtk.h b/views/widget/tooltip_manager_gtk.h new file mode 100644 index 0000000..8273425 --- /dev/null +++ b/views/widget/tooltip_manager_gtk.h @@ -0,0 +1,53 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_TOOLTIP_MANAGER_GTK_H_ +#define VIEWS_WIDGET_TOOLTIP_MANAGER_GTK_H_ +#pragma once + +#include <gtk/gtk.h> + +#include "ui/base/gtk/tooltip_window_gtk.h" +#include "views/widget/tooltip_manager.h" + +namespace views { + +class NativeWidgetGtk; + +// TooltipManager implementation for Gtk. +class TooltipManagerGtk : public TooltipManager { + public: + explicit TooltipManagerGtk(NativeWidgetGtk* widget); + virtual ~TooltipManagerGtk() {} + + // Shows the tooltip at the specified location. Returns true if the tooltip + // should be shown, false otherwise. + bool ShowTooltip(int x, int y, bool for_keyboard, GtkTooltip* gtk_tooltip); + + // TooltipManager. + virtual void UpdateTooltip(); + virtual void TooltipTextChanged(View* view); + virtual void ShowKeyboardTooltip(View* view); + virtual void HideKeyboardTooltip(); + + private: + // Sends the show_help signal to widget_. This signal triggers showing the + // keyboard tooltip if it isn't showing, or hides it if it is showing. + bool SendShowHelpSignal(); + + // Our owner. + NativeWidgetGtk* widget_; + + // The view supplied to the last invocation of ShowKeyboardTooltip. + View* keyboard_view_; + + // Customized tooltip window. + ui::TooltipWindowGtk tooltip_window_; + + DISALLOW_COPY_AND_ASSIGN(TooltipManagerGtk); +}; + +} // namespace views + +#endif // VIEWS_WIDGET_TOOLTIP_MANAGER_GTK_H_ diff --git a/views/widget/tooltip_manager_views.cc b/views/widget/tooltip_manager_views.cc new file mode 100644 index 0000000..904099a --- /dev/null +++ b/views/widget/tooltip_manager_views.cc @@ -0,0 +1,239 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/tooltip_manager_views.h" + +#if defined(USE_X11) +#include <X11/Xlib.h> +#include <X11/extensions/XInput2.h> +#endif + +#if defined(OS_WIN) +#include <windowsx.h> +#endif + +#include "base/event_types.h" +#include "base/logging.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "third_party/skia/include/core/SkColor.h" +#if defined(USE_AURA) +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/event.h" +#include "ui/aura/window.h" +#endif +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/font.h" +#include "ui/gfx/screen.h" +#include "ui/views/events/event.h" +#include "ui/views/focus/focus_manager.h" +#include "views/background.h" +#include "views/border.h" +#include "views/view.h" +#include "views/widget/native_widget.h" + +namespace { +SkColor kTooltipBackground = 0xFF7F7F00; +int kTooltipTimeoutMs = 500; + +// FIXME: get cursor offset from actual cursor size. +int kCursorOffsetX = 10; +int kCursorOffsetY = 15; +} + +namespace views { + +// static +int TooltipManager::GetTooltipHeight() { + // Not used for linux and chromeos. + NOTREACHED(); + return 0; +} + +// static +gfx::Font TooltipManager::GetDefaultFont() { + return ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont); +} + +// static +int TooltipManager::GetMaxWidth(int x, int y) { + // FIXME: change this. This is for now just copied from TooltipManagerGtk. + + // We always display the tooltip inside the root view. So the max width is + // the width of the view. + gfx::Rect monitor_bounds = + gfx::Screen::GetMonitorAreaNearestPoint(gfx::Point(x, y)); + // GtkLabel (gtk_label_ensure_layout) forces wrapping at this size. We mirror + // the size here otherwise tooltips wider than the size used by gtklabel end + // up with extraneous empty lines. + return monitor_bounds.width() == 0 ? 800 : (monitor_bounds.width() + 1) / 2; +} + +TooltipManagerViews::TooltipManagerViews(views::View* root_view) + : root_view_(root_view), + tooltip_view_(NULL) { + tooltip_label_.set_background( + views::Background::CreateSolidBackground(kTooltipBackground)); + tooltip_widget_.reset(CreateTooltip()); + tooltip_widget_->SetContentsView(&tooltip_label_); + tooltip_widget_->Activate(); + tooltip_widget_->SetAlwaysOnTop(true); +} + +TooltipManagerViews::~TooltipManagerViews() { + tooltip_widget_->CloseNow(); +} + +void TooltipManagerViews::UpdateForMouseEvent(const MouseEvent& event) { + switch (event.type()) { + case ui::ET_MOUSE_EXITED: + // Mouse is exiting this widget. Stop showing the tooltip and the timer. + if (tooltip_timer_.IsRunning()) + tooltip_timer_.Stop(); + if (tooltip_widget_->IsVisible()) + tooltip_widget_->Hide(); + break; + case ui::ET_MOUSE_ENTERED: + // Mouse just entered this widget. Start the timer to show the tooltip. + if (tooltip_timer_.IsRunning()) + tooltip_timer_.Stop(); + tooltip_timer_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(kTooltipTimeoutMs), + this, &TooltipManagerViews::TooltipTimerFired); + break; + case ui::ET_MOUSE_MOVED: + OnMouseMoved(event.location().x(), event.location().y()); + break; + case ui::ET_MOUSE_PRESSED: + case ui::ET_MOUSE_RELEASED: + case ui::ET_MOUSE_DRAGGED: + case ui::ET_MOUSEWHEEL: + // Hide the tooltip for click, release, drag, wheel events. + if (tooltip_widget_->IsVisible()) + tooltip_widget_->Hide(); + break; + default: + NOTIMPLEMENTED(); + } +} + +void TooltipManagerViews::UpdateTooltip() { + UpdateIfRequired(curr_mouse_pos_.x(), curr_mouse_pos_.y(), false); +} + +void TooltipManagerViews::TooltipTextChanged(View* view) { + if (tooltip_widget_->IsVisible()) + UpdateIfRequired(curr_mouse_pos_.x(), curr_mouse_pos_.y(), false); +} + +void TooltipManagerViews::ShowKeyboardTooltip(View* view) { + NOTREACHED(); +} + +void TooltipManagerViews::HideKeyboardTooltip() { + NOTREACHED(); +} + +void TooltipManagerViews::TooltipTimerFired() { + UpdateIfRequired(curr_mouse_pos_.x(), curr_mouse_pos_.y(), false); +} + +View* TooltipManagerViews::GetViewForTooltip(int x, int y, bool for_keyboard) { + View* view = NULL; + if (!for_keyboard) { + // Convert x,y from screen coordinates to |root_view_| coordinates. + gfx::Point point(x, y); + View::ConvertPointFromWidget(root_view_, &point); + view = root_view_->GetEventHandlerForPoint(point); + } else { + FocusManager* focus_manager = root_view_->GetFocusManager(); + if (focus_manager) + view = focus_manager->GetFocusedView(); + } + return view; +} + +void TooltipManagerViews::UpdateIfRequired(int x, int y, bool for_keyboard) { + View* view = GetViewForTooltip(x, y, for_keyboard); + string16 tooltip_text; + if (view) + view->GetTooltipText(gfx::Point(x, y), &tooltip_text); + +#if defined(USE_AURA) + // In aura, and aura::Window can also have a tooltip. If the view doesnot have + // a tooltip, we must also check for the aura::Window underneath the cursor. + if (tooltip_text.empty()) { + aura::Window* root = reinterpret_cast<aura::Window*>( + root_view_->GetWidget()->GetNativeView()); + if (root) { + aura::Window* window = root->GetEventHandlerForPoint(gfx::Point(x, y)); + if (window) { + void* property = window->GetProperty(aura::kTooltipTextKey); + if (property) + tooltip_text = *reinterpret_cast<string16*>(property); + } + } + } +#endif + + if (tooltip_view_ != view || tooltip_text_ != tooltip_text) { + tooltip_view_ = view; + tooltip_text_ = tooltip_text; + Update(); + } +} + +void TooltipManagerViews::Update() { + if (tooltip_text_.empty()) { + tooltip_widget_->Hide(); + } else { + int max_width, line_count; + string16 tooltip_text(tooltip_text_); + TrimTooltipToFit(&tooltip_text, &max_width, &line_count, + curr_mouse_pos_.x(), curr_mouse_pos_.y()); + tooltip_label_.SetText(tooltip_text); + + SetTooltipBounds(curr_mouse_pos_, max_width, + tooltip_label_.GetPreferredSize().height()); + tooltip_widget_->Show(); + } +} + +void TooltipManagerViews::SetTooltipBounds(gfx::Point mouse_pos, + int tooltip_width, + int tooltip_height) { + gfx::Rect tooltip_rect(mouse_pos.x(), mouse_pos.y(), tooltip_width, + tooltip_height); + + tooltip_rect.Offset(kCursorOffsetX, kCursorOffsetY); + gfx::Rect monitor_bounds = + gfx::Screen::GetMonitorAreaNearestPoint(tooltip_rect.origin()); + tooltip_widget_->SetBounds(tooltip_rect.AdjustToFit(monitor_bounds)); +} + +Widget* TooltipManagerViews::CreateTooltip() { + Widget* widget = new Widget; + Widget::InitParams params; + // For aura, since we set the type to TOOLTIP_TYPE, the widget will get + // auto-parented to the MenuAndTooltipsContainer. + params.type = Widget::InitParams::TYPE_TOOLTIP; + params.keep_on_top = true; + params.accept_events = false; + params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + widget->Init(params); + widget->SetOpacity(0xFF); + return widget; +} + +void TooltipManagerViews::OnMouseMoved(int x, int y) { + if (tooltip_timer_.IsRunning()) + tooltip_timer_.Reset(); + curr_mouse_pos_.SetPoint(x, y); + + // If tooltip is visible, we may want to hide it. If it is not, we are ok. + if (tooltip_widget_->IsVisible()) + UpdateIfRequired(curr_mouse_pos_.x(), curr_mouse_pos_.y(), false); +} + +} // namespace views diff --git a/views/widget/tooltip_manager_views.h b/views/widget/tooltip_manager_views.h new file mode 100644 index 0000000..4cf2657 --- /dev/null +++ b/views/widget/tooltip_manager_views.h @@ -0,0 +1,80 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_TOOLTIP_MANAGER_VIEWS_H_ +#define VIEWS_WIDGET_TOOLTIP_MANAGER_VIEWS_H_ +#pragma once + +#include "base/message_loop.h" +#include "base/timer.h" +#include "views/controls/label.h" +#include "views/widget/native_widget.h" +#include "views/widget/tooltip_manager.h" +#include "views/widget/widget_delegate.h" +#include "views/view.h" + +#if defined(USE_X11) +typedef union _XEvent XEvent; +#endif + +namespace views { + +class MouseEvent; +class Widget; + +// TooltipManager implementation for Views. +class TooltipManagerViews : public TooltipManager { + public: + explicit TooltipManagerViews(views::View* root_view); + virtual ~TooltipManagerViews(); + + // Updates the state of the tooltip based on the mouse event. The mouse event + // is the same event that goes to a Widget (i.e. it is in the Widget's + // coordinate system). + void UpdateForMouseEvent(const MouseEvent& event); + + // TooltipManager. + virtual void UpdateTooltip() OVERRIDE; + virtual void TooltipTextChanged(View* view) OVERRIDE; + virtual void ShowKeyboardTooltip(View* view) OVERRIDE; + virtual void HideKeyboardTooltip() OVERRIDE; + + private: + void TooltipTimerFired(); + View* GetViewForTooltip(int x, int y, bool for_keyboard); + + // Updates the tooltip if required (if there is any change in the tooltip + // text or the view. + void UpdateIfRequired(int x, int y, bool for_keyboard); + + // Updates the tooltip. Gets the tooltip text from tooltip_view_ and displays + // it at the current mouse position. + void Update(); + + // Adjusts the bounds given by the arguments to fit inside the parent view + // and applies the adjusted bounds to the tooltip_label_. + void SetTooltipBounds(gfx::Point mouse_pos, int tooltip_width, + int tooltip_height); + + // Creates a widget of type TYPE_TOOLTIP + Widget* CreateTooltip(); + + // Invoked when the mose moves. + void OnMouseMoved(int x, int y); + + scoped_ptr<Widget> tooltip_widget_; + views::View* root_view_; + View* tooltip_view_; + string16 tooltip_text_; + Label tooltip_label_; + + gfx::Point curr_mouse_pos_; + base::RepeatingTimer<TooltipManagerViews> tooltip_timer_; + + DISALLOW_COPY_AND_ASSIGN(TooltipManagerViews); +}; + +} // namespace views + +#endif // VIEWS_WIDGET_TOOLTIP_MANAGER_VIEWS_H_ diff --git a/views/widget/tooltip_manager_win.cc b/views/widget/tooltip_manager_win.cc new file mode 100644 index 0000000..521fcc0 --- /dev/null +++ b/views/widget/tooltip_manager_win.cc @@ -0,0 +1,391 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/tooltip_manager_win.h" + +#include <windowsx.h> + +#include <limits> + +#include "base/bind.h" +#include "base/i18n/rtl.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "ui/base/l10n/l10n_util_win.h" +#include "ui/base/win/hwnd_util.h" +#include "ui/gfx/font.h" +#include "ui/gfx/screen.h" +#include "views/view.h" +#include "views/widget/monitor_win.h" +#include "views/widget/widget.h" + +namespace views { + +static int tooltip_height_ = 0; + +// Default timeout for the tooltip displayed using keyboard. +// Timeout is mentioned in milliseconds. +static const int kDefaultTimeout = 4000; + +// static +int TooltipManager::GetTooltipHeight() { + DCHECK_GT(tooltip_height_, 0); + return tooltip_height_; +} + +static gfx::Font DetermineDefaultFont() { + HWND window = CreateWindowEx( + WS_EX_TRANSPARENT | l10n_util::GetExtendedTooltipStyles(), + TOOLTIPS_CLASS, NULL, 0 , 0, 0, 0, 0, NULL, NULL, NULL, NULL); + if (!window) + return gfx::Font(); + HFONT hfont = reinterpret_cast<HFONT>(SendMessage(window, WM_GETFONT, 0, 0)); + gfx::Font font = hfont ? gfx::Font(hfont) : gfx::Font(); + DestroyWindow(window); + return font; +} + +// static +gfx::Font TooltipManager::GetDefaultFont() { + static gfx::Font* font = NULL; + if (!font) + font = new gfx::Font(DetermineDefaultFont()); + return *font; +} + +// static +int TooltipManager::GetMaxWidth(int x, int y) { + gfx::Rect monitor_bounds = + gfx::Screen::GetMonitorAreaNearestPoint(gfx::Point(x, y)); + // Allow the tooltip to be almost as wide as the screen. + // Otherwise, we would truncate important text, since we're not word-wrapping + // the text onto multiple lines. + return monitor_bounds.width() == 0 ? 800 : monitor_bounds.width() - 30; +} + +TooltipManagerWin::TooltipManagerWin(Widget* widget) + : widget_(widget), + tooltip_hwnd_(NULL), + last_mouse_pos_(-1, -1), + tooltip_showing_(false), + last_tooltip_view_(NULL), + last_view_out_of_sync_(false), + tooltip_width_(0), + keyboard_tooltip_hwnd_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(keyboard_tooltip_factory_(this)) { + DCHECK(widget); + DCHECK(widget->GetNativeView()); +} + +TooltipManagerWin::~TooltipManagerWin() { + if (tooltip_hwnd_) + DestroyWindow(tooltip_hwnd_); + if (keyboard_tooltip_hwnd_) + DestroyWindow(keyboard_tooltip_hwnd_); +} + +bool TooltipManagerWin::Init() { + DCHECK(!tooltip_hwnd_); + // Create the tooltip control. + tooltip_hwnd_ = CreateWindowEx( + WS_EX_TRANSPARENT | l10n_util::GetExtendedTooltipStyles(), + TOOLTIPS_CLASS, NULL, TTS_NOPREFIX, 0, 0, 0, 0, + GetParent(), NULL, NULL, NULL); + if (!tooltip_hwnd_) + return false; + + 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<int16>::max()); + + // Add one tool that is used for all tooltips. + toolinfo_.cbSize = sizeof(toolinfo_); + toolinfo_.uFlags = TTF_TRANSPARENT | TTF_IDISHWND; + toolinfo_.hwnd = GetParent(); + toolinfo_.uId = reinterpret_cast<UINT_PTR>(GetParent()); + // Setting this tells windows to call GetParent() back (using a WM_NOTIFY + // message) for the actual tooltip contents. + toolinfo_.lpszText = LPSTR_TEXTCALLBACK; + toolinfo_.lpReserved = NULL; + SetRectEmpty(&toolinfo_.rect); + SendMessage(tooltip_hwnd_, TTM_ADDTOOL, 0, (LPARAM)&toolinfo_); + return true; +} + +gfx::NativeView TooltipManagerWin::GetParent() { + return widget_->GetNativeView(); +} + +void TooltipManagerWin::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 TooltipManagerWin::TooltipTextChanged(View* view) { + if (view == last_tooltip_view_) + UpdateTooltip(last_mouse_pos_); +} + +LRESULT TooltipManagerWin::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. + View* root_view = widget_->GetRootView(); + last_tooltip_view_ = + root_view->GetEventHandlerForPoint(last_mouse_pos_); + 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 its tooltip. + gfx::Point view_loc = last_mouse_pos_; + View::ConvertPointToView(widget_->GetRootView(), + last_tooltip_view_, &view_loc); + if (last_tooltip_view_->GetTooltipText(view_loc, &tooltip_text_) && + !tooltip_text_.empty()) { + // View has a valid tip, copy it into TOOLTIPINFO. + clipped_text_ = tooltip_text_; + gfx::Point screen_loc = last_mouse_pos_; + View::ConvertPointToScreen(widget_->GetRootView(), &screen_loc); + TrimTooltipToFit(&clipped_text_, &tooltip_width_, &line_count_, + screen_loc.x(), screen_loc.y()); + // Adjust the clipped tooltip text for locale direction. + base::i18n::AdjustStringForLocaleDirection(&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_pos_; + View::ConvertPointToView(widget_->GetRootView(), + last_tooltip_view_, &view_loc); + if (last_tooltip_view_->GetTooltipTextOrigin(view_loc, &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 TooltipManagerWin::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 = + views::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 TooltipManagerWin::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 gfx::Font, which should pick + // up the system font. + height = gfx::Font().GetHeight(); + } + // 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 TooltipManagerWin::UpdateTooltip(const gfx::Point& mouse_pos) { + View* root_view = widget_->GetRootView(); + View* view = root_view->GetEventHandlerForPoint(mouse_pos); + 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 = mouse_pos; + View::ConvertPointToView(root_view, last_tooltip_view_, &view_point); + string16 new_tooltip_text; + bool has_tooltip_text = + last_tooltip_view_->GetTooltipText(view_point, &new_tooltip_text); + if (!has_tooltip_text || (new_tooltip_text != tooltip_text_)) { + // The text has changed, hide the popup. + SendMessage(tooltip_hwnd_, TTM_POP, 0, 0); + if (has_tooltip_text && !new_tooltip_text.empty() && tooltip_showing_) { + // New text is valid, show the popup. + SendMessage(tooltip_hwnd_, TTM_POPUP, 0, 0); + } + } + } +} + +void TooltipManagerWin::OnMouse(UINT u_msg, WPARAM w_param, LPARAM l_param) { + gfx::Point mouse_pos(l_param); + + if (u_msg >= WM_NCMOUSEMOVE && u_msg <= WM_NCXBUTTONDBLCLK) { + // NC message coordinates are in screen coordinates. + POINT temp = mouse_pos.ToPOINT(); + ::MapWindowPoints(HWND_DESKTOP, GetParent(), &temp, 1); + mouse_pos.SetPoint(temp.x, temp.y); + } + + if (u_msg != WM_MOUSEMOVE || last_mouse_pos_ != mouse_pos) { + last_mouse_pos_ = mouse_pos; + HideKeyboardTooltip(); + UpdateTooltip(mouse_pos); + } + // Forward the message onto the tooltip. + MSG msg; + msg.hwnd = GetParent(); + msg.message = u_msg; + msg.wParam = w_param; + msg.lParam = l_param; + SendMessage(tooltip_hwnd_, TTM_RELAYEVENT, 0, (LPARAM)&msg); +} + +void TooltipManagerWin::ShowKeyboardTooltip(View* focused_view) { + if (tooltip_showing_) { + SendMessage(tooltip_hwnd_, TTM_POP, 0, 0); + tooltip_text_.clear(); + } + HideKeyboardTooltip(); + string16 tooltip_text; + if (!focused_view->GetTooltipText(gfx::Point(), &tooltip_text)) + return; + gfx::Rect focused_bounds = focused_view->bounds(); + gfx::Point screen_point; + focused_view->ConvertPointToScreen(focused_view, &screen_point); + keyboard_tooltip_hwnd_ = CreateWindowEx( + WS_EX_TRANSPARENT | l10n_util::GetExtendedTooltipStyles(), + TOOLTIPS_CLASS, NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL); + if (!keyboard_tooltip_hwnd_) + return; + + SendMessage(keyboard_tooltip_hwnd_, TTM_SETMAXTIPWIDTH, 0, + std::numeric_limits<int16>::max()); + int tooltip_width; + int line_count; + TrimTooltipToFit(&tooltip_text, &tooltip_width, &line_count, + screen_point.x(), screen_point.y()); + ReplaceSubstringsAfterOffset(&tooltip_text, 0, L"\n", L"\r\n"); + TOOLINFO keyboard_toolinfo; + memset(&keyboard_toolinfo, 0, sizeof(keyboard_toolinfo)); + keyboard_toolinfo.cbSize = sizeof(keyboard_toolinfo); + keyboard_toolinfo.hwnd = GetParent(); + 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 = + views::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, + base::Bind(&TooltipManagerWin::DestroyKeyboardTooltipWindow, + keyboard_tooltip_factory_.GetWeakPtr(), + keyboard_tooltip_hwnd_), + kDefaultTimeout); +} + +void TooltipManagerWin::HideKeyboardTooltip() { + if (keyboard_tooltip_hwnd_ != NULL) { + SendMessage(keyboard_tooltip_hwnd_, WM_CLOSE, 0, 0); + keyboard_tooltip_hwnd_ = NULL; + } +} + +void TooltipManagerWin::DestroyKeyboardTooltipWindow(HWND window_to_destroy) { + if (keyboard_tooltip_hwnd_ == window_to_destroy) + HideKeyboardTooltip(); +} + +} // namespace views diff --git a/views/widget/tooltip_manager_win.h b/views/widget/tooltip_manager_win.h new file mode 100644 index 0000000..f4afaa2 --- /dev/null +++ b/views/widget/tooltip_manager_win.h @@ -0,0 +1,153 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_TOOLTIP_MANAGER_WIN_H_ +#define VIEWS_WIDGET_TOOLTIP_MANAGER_WIN_H_ +#pragma once + +#include <windows.h> +#include <commctrl.h> +#include <string> + +#include "base/basictypes.h" +#include "base/memory/weak_ptr.h" +#include "base/string16.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/point.h" +#include "views/widget/tooltip_manager.h" + +namespace gfx { +class Point; +} + +namespace views { + +class View; +class Widget; + +// TooltipManager implementation for Windows. +// +// This class is intended to be used by NativeWidgetWin. 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 NativeWidgetWin for an example of this in action. +class TooltipManagerWin : public TooltipManager { + public: + // Creates a TooltipManager for the specified Widget and parent window. + explicit TooltipManagerWin(Widget* widget); + virtual ~TooltipManagerWin(); + + // Initializes the TooltipManager returning whether initialization was + // successful. If this returns false the TooltipManager should be destroyed + // and not used. + bool Init(); + + // Notification that the view hierarchy has changed in some way. + virtual void UpdateTooltip(); + + // Invoked when the tooltip text changes for the specified views. + virtual void TooltipTextChanged(View* view); + + // Invoked when toolbar icon gets focus. + virtual void ShowKeyboardTooltip(View* view); + + // Invoked when toolbar loses focus. + virtual 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); + + protected: + // Returns the Widget we're showing tooltips for. + gfx::NativeView GetParent(); + + // Updates the tooltip for the specified location. + void UpdateTooltip(const gfx::Point& location); + + // Tooltip control window. + HWND tooltip_hwnd_; + + // Tooltip information. + TOOLINFO toolinfo_; + + // Last location of the mouse. This is in the coordinates of the rootview. + gfx::Point last_mouse_pos_; + + // 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(); + + // 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. + string16 tooltip_text_; + + // The clipped tooltip. + string16 clipped_text_; + + // Number of lines in the tooltip. + int line_count_; + + // Width of the last tooltip. + int tooltip_width_; + + // control window for tooltip displayed using keyboard. + HWND keyboard_tooltip_hwnd_; + + // Used to register DestroyTooltipWindow function with PostDelayedTask + // function. + base::WeakPtrFactory<TooltipManagerWin> keyboard_tooltip_factory_; + + DISALLOW_COPY_AND_ASSIGN(TooltipManagerWin); +}; + +} // namespace views + +#endif // VIEWS_WIDGET_TOOLTIP_MANAGER_WIN_H_ diff --git a/views/widget/widget.cc b/views/widget/widget.cc new file mode 100644 index 0000000..9394028 --- /dev/null +++ b/views/widget/widget.cc @@ -0,0 +1,1218 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/widget.h" + +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/utf_string_conversions.h" +#include "ui/base/hit_test.h" +#include "ui/base/l10n/l10n_font_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/compositor/compositor.h" +#include "ui/gfx/compositor/layer.h" +#include "ui/gfx/screen.h" +#include "ui/views/focus/focus_manager.h" +#include "ui/views/focus/focus_manager_factory.h" +#include "ui/views/focus/view_storage.h" +#include "ui/views/focus/widget_focus_manager.h" +#include "ui/views/ime/input_method.h" +#include "ui/views/window/custom_frame_view.h" +#include "views/controls/menu/menu_controller.h" +#include "views/views_delegate.h" +#include "views/widget/default_theme_provider.h" +#include "views/widget/native_widget_private.h" +#include "views/widget/root_view.h" +#include "views/widget/tooltip_manager.h" +#include "views/widget/widget_delegate.h" + +namespace { + +// Set to true if a pure Views implementation is preferred +bool use_pure_views = false; + +// True to enable debug paint that indicates where to be painted. +bool debug_paint = false; + +} // namespace + +namespace views { + +// This class is used to keep track of the event a Widget is processing, and +// restore any previously active event afterwards. +class ScopedEvent { + public: + ScopedEvent(Widget* widget, const Event& event) + : widget_(widget), + event_(&event) { + widget->event_stack_.push(this); + } + + ~ScopedEvent() { + if (widget_) + widget_->event_stack_.pop(); + } + + void reset() { + widget_ = NULL; + } + + const Event* event() { + return event_; + } + + private: + Widget* widget_; + const Event* event_; + + DISALLOW_COPY_AND_ASSIGN(ScopedEvent); +}; + +// A default implementation of WidgetDelegate, used by Widget when no +// WidgetDelegate is supplied. +class DefaultWidgetDelegate : public WidgetDelegate { + public: + DefaultWidgetDelegate(Widget* widget, const Widget::InitParams& params) + : widget_(widget), + can_activate_(!params.child && + params.type != Widget::InitParams::TYPE_POPUP) { + } + virtual ~DefaultWidgetDelegate() {} + + // Overridden from WidgetDelegate: + virtual void DeleteDelegate() OVERRIDE { + delete this; + } + virtual Widget* GetWidget() { + return widget_; + } + virtual const Widget* GetWidget() const { + return widget_; + } + + virtual bool CanActivate() const { + return can_activate_; + } + + private: + Widget* widget_; + bool can_activate_; + + DISALLOW_COPY_AND_ASSIGN(DefaultWidgetDelegate); +}; + +//////////////////////////////////////////////////////////////////////////////// +// Widget, InitParams: + +Widget::InitParams::InitParams() + : type(TYPE_WINDOW), + delegate(NULL), + child(false), + transient(false), + transparent(false), + accept_events(true), + can_activate(true), + keep_on_top(false), + ownership(NATIVE_WIDGET_OWNS_WIDGET), + mirror_origin_in_rtl(false), + has_dropshadow(false), + show_state(ui::SHOW_STATE_DEFAULT), + double_buffer(false), + parent(NULL), + parent_widget(NULL), + native_widget(NULL), + top_level(false), + create_texture_for_layer(true) { +} + +Widget::InitParams::InitParams(Type type) + : type(type), + delegate(NULL), + child(type == TYPE_CONTROL), + transient(type == TYPE_BUBBLE || type == TYPE_POPUP || type == TYPE_MENU), + transparent(false), + accept_events(true), + can_activate(type != TYPE_POPUP && type != TYPE_MENU), + keep_on_top(type == TYPE_MENU), + ownership(NATIVE_WIDGET_OWNS_WIDGET), + mirror_origin_in_rtl(false), + has_dropshadow(false), + show_state(ui::SHOW_STATE_DEFAULT), + double_buffer(false), + parent(NULL), + parent_widget(NULL), + native_widget(NULL), + top_level(false), + create_texture_for_layer(true) { +} + +gfx::NativeView Widget::InitParams::GetParent() const { + return parent_widget ? parent_widget->GetNativeView() : parent; +} + +//////////////////////////////////////////////////////////////////////////////// +// Widget, public: + +Widget::Widget() + : native_widget_(NULL), + widget_delegate_(NULL), + non_client_view_(NULL), + dragged_view_(NULL), + event_stack_(), + ownership_(InitParams::NATIVE_WIDGET_OWNS_WIDGET), + is_secondary_widget_(true), + frame_type_(FRAME_TYPE_DEFAULT), + disable_inactive_rendering_(false), + widget_closed_(false), + saved_show_state_(ui::SHOW_STATE_DEFAULT), + focus_on_creation_(true), + is_top_level_(false), + native_widget_initialized_(false), + is_mouse_button_pressed_(false), + last_mouse_event_was_move_(false) { +} + +Widget::~Widget() { + while (!event_stack_.empty()) { + event_stack_.top()->reset(); + event_stack_.pop(); + } + + DestroyRootView(); + if (ownership_ == InitParams::WIDGET_OWNS_NATIVE_WIDGET) + delete native_widget_; +} + +// static +Widget* Widget::CreateWindow(WidgetDelegate* delegate) { + return CreateWindowWithParentAndBounds(delegate, NULL, gfx::Rect()); +} + +// static +Widget* Widget::CreateWindowWithParent(WidgetDelegate* delegate, + gfx::NativeWindow parent) { + return CreateWindowWithParentAndBounds(delegate, parent, gfx::Rect()); +} + +// static +Widget* Widget::CreateWindowWithBounds(WidgetDelegate* delegate, + const gfx::Rect& bounds) { + return CreateWindowWithParentAndBounds(delegate, NULL, bounds); +} + +// static +Widget* Widget::CreateWindowWithParentAndBounds(WidgetDelegate* delegate, + gfx::NativeWindow parent, + const gfx::Rect& bounds) { + Widget* widget = new Widget; + Widget::InitParams params; + params.delegate = delegate; +#if defined(OS_WIN) || defined(USE_AURA) + params.parent = parent; +#endif + params.bounds = bounds; + widget->Init(params); + return widget; +} + +// static +void Widget::SetPureViews(bool pure) { + use_pure_views = pure; +} + +// static +bool Widget::IsPureViews() { +#if defined(USE_AURA) || defined(TOUCH_UI) + return true; +#else + return use_pure_views; +#endif +} + +// static +Widget* Widget::GetWidgetForNativeView(gfx::NativeView native_view) { + internal::NativeWidgetPrivate* native_widget = + internal::NativeWidgetPrivate::GetNativeWidgetForNativeView(native_view); + return native_widget ? native_widget->GetWidget() : NULL; +} + +// static +Widget* Widget::GetWidgetForNativeWindow(gfx::NativeWindow native_window) { + internal::NativeWidgetPrivate* native_widget = + internal::NativeWidgetPrivate::GetNativeWidgetForNativeWindow( + native_window); + return native_widget ? native_widget->GetWidget() : NULL; +} + +// static +Widget* Widget::GetTopLevelWidgetForNativeView(gfx::NativeView native_view) { + internal::NativeWidgetPrivate* native_widget = + internal::NativeWidgetPrivate::GetTopLevelNativeWidget(native_view); + return native_widget ? native_widget->GetWidget() : NULL; +} + + +// static +void Widget::GetAllChildWidgets(gfx::NativeView native_view, + Widgets* children) { + internal::NativeWidgetPrivate::GetAllChildWidgets(native_view, children); +} + +// static +void Widget::ReparentNativeView(gfx::NativeView native_view, + gfx::NativeView new_parent) { + internal::NativeWidgetPrivate::ReparentNativeView(native_view, new_parent); +} + +// static +int Widget::GetLocalizedContentsWidth(int col_resource_id) { + return ui::GetLocalizedContentsWidthForFont(col_resource_id, + ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont)); +} + +// static +int Widget::GetLocalizedContentsHeight(int row_resource_id) { + return ui::GetLocalizedContentsHeightForFont(row_resource_id, + ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont)); +} + +// static +gfx::Size Widget::GetLocalizedContentsSize(int col_resource_id, + int row_resource_id) { + return gfx::Size(GetLocalizedContentsWidth(col_resource_id), + GetLocalizedContentsHeight(row_resource_id)); +} + +// static +void Widget::SetDebugPaintEnabled(bool enabled) { + debug_paint = enabled; +} + +// static +bool Widget::IsDebugPaintEnabled() { + return debug_paint; +} + +// static +bool Widget::RequiresNonClientView(InitParams::Type type) { + return type == InitParams::TYPE_WINDOW || type == InitParams::TYPE_BUBBLE; +} + +void Widget::Init(const InitParams& params) { + is_top_level_ = params.top_level || + (!params.child && + params.type != InitParams::TYPE_CONTROL && + params.type != InitParams::TYPE_TOOLTIP); + widget_delegate_ = params.delegate ? + params.delegate : new DefaultWidgetDelegate(this, params); + ownership_ = params.ownership; + native_widget_ = params.native_widget ? + params.native_widget->AsNativeWidgetPrivate() : + internal::NativeWidgetPrivate::CreateNativeWidget(this); + GetRootView(); + default_theme_provider_.reset(new DefaultThemeProvider); + if (params.type == InitParams::TYPE_MENU) { + is_mouse_button_pressed_ = + internal::NativeWidgetPrivate::IsMouseButtonDown(); + } + native_widget_->InitNativeWidget(params); + if (RequiresNonClientView(params.type)) { + non_client_view_ = new NonClientView; + non_client_view_->SetFrameView(CreateNonClientFrameView()); + // Create the ClientView, add it to the NonClientView and add the + // NonClientView to the RootView. This will cause everything to be parented. + non_client_view_->set_client_view(widget_delegate_->CreateClientView(this)); + SetContentsView(non_client_view_); + SetInitialBounds(params.bounds); + if (params.show_state == ui::SHOW_STATE_MAXIMIZED) + Maximize(); + else if (params.show_state == ui::SHOW_STATE_MINIMIZED) + Minimize(); + UpdateWindowTitle(); + } + native_widget_initialized_ = true; +} + +// Unconverted methods (see header) -------------------------------------------- + +gfx::NativeView Widget::GetNativeView() const { + return native_widget_->GetNativeView(); +} + +gfx::NativeWindow Widget::GetNativeWindow() const { + return native_widget_->GetNativeWindow(); +} + +void Widget::AddObserver(Widget::Observer* observer) { + observers_.AddObserver(observer); +} + +void Widget::RemoveObserver(Widget::Observer* observer) { + observers_.RemoveObserver(observer); +} + +bool Widget::HasObserver(Widget::Observer* observer) { + return observers_.HasObserver(observer); +} + +bool Widget::GetAccelerator(int cmd_id, ui::Accelerator* accelerator) { + return false; +} + +void Widget::ViewHierarchyChanged(bool is_add, View* parent, View* child) { + if (!is_add) { + if (child == dragged_view_) + dragged_view_ = NULL; + + FocusManager* focus_manager = GetFocusManager(); + if (focus_manager) + focus_manager->ViewRemoved(child); + ViewStorage::GetInstance()->ViewRemoved(child); + native_widget_->ViewRemoved(child); + } +} + +void Widget::NotifyNativeViewHierarchyChanged(bool attached, + gfx::NativeView native_view) { + if (!attached) { + FocusManager* focus_manager = GetFocusManager(); + // We are being removed from a window hierarchy. Treat this as + // the root_view_ being removed. + if (focus_manager) + focus_manager->ViewRemoved(root_view_.get()); + } + root_view_->NotifyNativeViewHierarchyChanged(attached, native_view); +} + +// Converted methods (see header) ---------------------------------------------- + +Widget* Widget::GetTopLevelWidget() { + return const_cast<Widget*>( + static_cast<const Widget*>(this)->GetTopLevelWidget()); +} + +const Widget* Widget::GetTopLevelWidget() const { + // GetTopLevelNativeWidget doesn't work during destruction because + // property is gone after gobject gets deleted. Short circuit here + // for toplevel so that InputMethod can remove itself from + // focus manager. + return is_top_level() ? this : native_widget_->GetTopLevelWidget(); +} + +void Widget::SetContentsView(View* view) { + root_view_->SetContentsView(view); + if (non_client_view_ != view) + non_client_view_ = NULL; +} + +View* Widget::GetContentsView() { + return root_view_->GetContentsView(); +} + +gfx::Rect Widget::GetWindowScreenBounds() const { + return native_widget_->GetWindowScreenBounds(); +} + +gfx::Rect Widget::GetClientAreaScreenBounds() const { + return native_widget_->GetClientAreaScreenBounds(); +} + +gfx::Rect Widget::GetRestoredBounds() const { + return native_widget_->GetRestoredBounds(); +} + +void Widget::SetBounds(const gfx::Rect& bounds) { + native_widget_->SetBounds(bounds); +} + +void Widget::SetSize(const gfx::Size& size) { + native_widget_->SetSize(size); +} + +void Widget::SetBoundsConstrained(const gfx::Rect& bounds) { + gfx::Rect work_area = + gfx::Screen::GetMonitorWorkAreaNearestPoint(bounds.origin()); + if (work_area.IsEmpty()) { + SetBounds(bounds); + } else { + // Inset the work area slightly. + work_area.Inset(10, 10, 10, 10); + SetBounds(work_area.AdjustToFit(bounds)); + } +} + +void Widget::MoveAboveWidget(Widget* widget) { + native_widget_->MoveAbove(widget->GetNativeView()); +} + +void Widget::MoveAbove(gfx::NativeView native_view) { + native_widget_->MoveAbove(native_view); +} + +void Widget::MoveToTop() { + native_widget_->MoveToTop(); +} + +void Widget::SetShape(gfx::NativeRegion shape) { + native_widget_->SetShape(shape); +} + +void Widget::Close() { + if (widget_closed_) { + // It appears we can hit this code path if you close a modal dialog then + // close the last browser before the destructor is hit, which triggers + // invoking Close again. + return; + } + + bool can_close = true; + if (non_client_view_) + can_close = non_client_view_->CanClose(); + if (can_close) { + SaveWindowPlacement(); + + // During tear-down the top-level focus manager becomes unavailable to + // GTK tabbed panes and their children, so normal deregistration via + // |FormManager::ViewRemoved()| calls are fouled. We clear focus here + // to avoid these redundant steps and to avoid accessing deleted views + // that may have been in focus. + if (is_top_level() && focus_manager_.get()) + focus_manager_->SetFocusedView(NULL); + + native_widget_->Close(); + widget_closed_ = true; + } +} + +void Widget::CloseNow() { + native_widget_->CloseNow(); +} + +void Widget::EnableClose(bool enable) { + if (non_client_view_) + non_client_view_->EnableClose(enable); + native_widget_->EnableClose(enable); +} + +void Widget::Show() { + if (non_client_view_) { + if (saved_show_state_ == ui::SHOW_STATE_MAXIMIZED && + !initial_restored_bounds_.IsEmpty()) { + native_widget_->ShowMaximizedWithBounds(initial_restored_bounds_); + } else { + native_widget_->ShowWithWindowState(saved_show_state_); + } + // |saved_show_state_| only applies the first time the window is shown. + // If we don't reset the value the window may be shown maximized every time + // it is subsequently shown after being hidden. + saved_show_state_ = ui::SHOW_STATE_NORMAL; + } else { + native_widget_->Show(); + } +} + +void Widget::Hide() { + native_widget_->Hide(); +} + +void Widget::ShowInactive() { + // If this gets called with saved_show_state_ == ui::SHOW_STATE_MAXIMIZED, + // call SetBounds()with the restored bounds to set the correct size. This + // normally should not happen, but if it does we should avoid showing unsized + // windows. + if (saved_show_state_ == ui::SHOW_STATE_MAXIMIZED && + !initial_restored_bounds_.IsEmpty()) { + SetBounds(initial_restored_bounds_); + saved_show_state_ = ui::SHOW_STATE_NORMAL; + } + native_widget_->ShowWithWindowState(ui::SHOW_STATE_INACTIVE); +} + +void Widget::Activate() { + native_widget_->Activate(); +} + +void Widget::Deactivate() { + native_widget_->Deactivate(); +} + +bool Widget::IsActive() const { + return native_widget_->IsActive(); +} + +void Widget::DisableInactiveRendering() { + SetInactiveRenderingDisabled(true); +} + +void Widget::SetAlwaysOnTop(bool on_top) { + native_widget_->SetAlwaysOnTop(on_top); +} + +void Widget::Maximize() { + native_widget_->Maximize(); +} + +void Widget::Minimize() { + native_widget_->Minimize(); +} + +void Widget::Restore() { + native_widget_->Restore(); +} + +bool Widget::IsMaximized() const { + return native_widget_->IsMaximized(); +} + +bool Widget::IsMinimized() const { + return native_widget_->IsMinimized(); +} + +void Widget::SetFullscreen(bool fullscreen) { + native_widget_->SetFullscreen(fullscreen); +} + +bool Widget::IsFullscreen() const { + return native_widget_->IsFullscreen(); +} + +void Widget::SetOpacity(unsigned char opacity) { + native_widget_->SetOpacity(opacity); +} + +void Widget::SetUseDragFrame(bool use_drag_frame) { + native_widget_->SetUseDragFrame(use_drag_frame); +} + +View* Widget::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(); +} + +const View* Widget::GetRootView() const { + return root_view_.get(); +} + +bool Widget::IsVisible() const { + return native_widget_->IsVisible(); +} + +bool Widget::IsAccessibleWidget() const { + return native_widget_->IsAccessibleWidget(); +} + +ThemeProvider* Widget::GetThemeProvider() const { + const Widget* root_widget = GetTopLevelWidget(); + if (root_widget && root_widget != this) { + // Attempt to get the theme provider, and fall back to the default theme + // provider if not found. + ThemeProvider* provider = root_widget->GetThemeProvider(); + if (provider) + return provider; + + provider = root_widget->default_theme_provider_.get(); + if (provider) + return provider; + } + return default_theme_provider_.get(); +} + +FocusManager* Widget::GetFocusManager() { + Widget* toplevel_widget = GetTopLevelWidget(); + return toplevel_widget ? toplevel_widget->focus_manager_.get() : NULL; +} + +const FocusManager* Widget::GetFocusManager() const { + const Widget* toplevel_widget = GetTopLevelWidget(); + return toplevel_widget ? toplevel_widget->focus_manager_.get() : NULL; +} + +InputMethod* Widget::GetInputMethod() { + if (is_top_level()) { + if (!input_method_.get()) + input_method_.reset(native_widget_->CreateInputMethod()); + return input_method_.get(); + } else { + Widget* toplevel = GetTopLevelWidget(); + // If GetTopLevelWidget() returns itself which is not toplevel, + // the widget is detached from toplevel widget. + // TODO(oshima): Fix GetTopLevelWidget() to return NULL + // if there is no toplevel. We probably need to add GetTopMostWidget() + // to replace some use cases. + return (toplevel && toplevel != this) ? toplevel->GetInputMethod() : NULL; + } +} + +void Widget::RunShellDrag(View* view, const ui::OSExchangeData& data, + int operation) { + dragged_view_ = view; + native_widget_->RunShellDrag(view, data, operation); + // If the view is removed during the drag operation, dragged_view_ is set to + // NULL. + if (view && dragged_view_ == view) { + dragged_view_ = NULL; + view->OnDragDone(); + } +} + +void Widget::SchedulePaintInRect(const gfx::Rect& rect) { + native_widget_->SchedulePaintInRect(rect); +} + +void Widget::SetCursor(gfx::NativeCursor cursor) { + native_widget_->SetCursor(cursor); +} + +void Widget::ResetLastMouseMoveFlag() { + last_mouse_event_was_move_ = false; +} + +void Widget::SetNativeWindowProperty(const char* name, void* value) { + native_widget_->SetNativeWindowProperty(name, value); +} + +void* Widget::GetNativeWindowProperty(const char* name) const { + return native_widget_->GetNativeWindowProperty(name); +} + +void Widget::UpdateWindowTitle() { + if (!non_client_view_) + return; + + // If the non-client view is rendering its own title, it'll need to relayout + // now. + non_client_view_->Layout(); + + // Update the native frame's text. We do this regardless of whether or not + // the native frame is being used, since this also updates the taskbar, etc. + string16 window_title; + if (native_widget_->IsScreenReaderActive()) { + window_title = widget_delegate_->GetAccessibleWindowTitle(); + } else { + window_title = widget_delegate_->GetWindowTitle(); + } + base::i18n::AdjustStringForLocaleDirection(&window_title); + native_widget_->SetWindowTitle(window_title); +} + +void Widget::UpdateWindowIcon() { + if (non_client_view_) + non_client_view_->UpdateWindowIcon(); + native_widget_->SetWindowIcons(widget_delegate_->GetWindowIcon(), + widget_delegate_->GetWindowAppIcon()); +} + +FocusTraversable* Widget::GetFocusTraversable() { + return static_cast<internal::RootView*>(root_view_.get()); +} + +void Widget::ThemeChanged() { + root_view_->ThemeChanged(); +} + +void Widget::LocaleChanged() { + root_view_->LocaleChanged(); +} + +void Widget::SetFocusTraversableParent(FocusTraversable* parent) { + root_view_->SetFocusTraversableParent(parent); +} + +void Widget::SetFocusTraversableParentView(View* parent_view) { + root_view_->SetFocusTraversableParentView(parent_view); +} + +void Widget::ClearNativeFocus() { + native_widget_->ClearNativeFocus(); +} + +void Widget::FocusNativeView(gfx::NativeView native_view) { + native_widget_->FocusNativeView(native_view); +} + +void Widget::UpdateFrameAfterFrameChange() { + native_widget_->UpdateFrameAfterFrameChange(); +} + +NonClientFrameView* Widget::CreateNonClientFrameView() { + NonClientFrameView* frame_view = widget_delegate_->CreateNonClientFrameView(); + if (!frame_view) + frame_view = native_widget_->CreateNonClientFrameView(); + return frame_view ? frame_view : new CustomFrameView(this); +} + +bool Widget::ShouldUseNativeFrame() const { + if (frame_type_ != FRAME_TYPE_DEFAULT) + return frame_type_ == FRAME_TYPE_FORCE_NATIVE; + return native_widget_->ShouldUseNativeFrame(); +} + +void Widget::DebugToggleFrameType() { + if (frame_type_ == FRAME_TYPE_DEFAULT) { + frame_type_ = ShouldUseNativeFrame() ? FRAME_TYPE_FORCE_CUSTOM : + FRAME_TYPE_FORCE_NATIVE; + } else { + frame_type_ = frame_type_ == FRAME_TYPE_FORCE_CUSTOM ? + FRAME_TYPE_FORCE_NATIVE : FRAME_TYPE_FORCE_CUSTOM; + } + FrameTypeChanged(); +} + +void Widget::FrameTypeChanged() { + native_widget_->FrameTypeChanged(); +} + +const ui::Compositor* Widget::GetCompositor() const { + return native_widget_->GetCompositor(); +} + +ui::Compositor* Widget::GetCompositor() { + return native_widget_->GetCompositor(); +} + +void Widget::CalculateOffsetToAncestorWithLayer(gfx::Point* offset, + ui::Layer** layer_parent) { + native_widget_->CalculateOffsetToAncestorWithLayer(offset, layer_parent); +} + +void Widget::ReorderLayers() { + native_widget_->ReorderLayers(); +} + +void Widget::NotifyAccessibilityEvent( + View* view, + ui::AccessibilityTypes::Event event_type, + bool send_native_event) { + // Send the notification to the delegate. + if (ViewsDelegate::views_delegate) + ViewsDelegate::views_delegate->NotifyAccessibilityEvent(view, event_type); + + if (send_native_event) + native_widget_->SendNativeAccessibilityEvent(view, event_type); +} + +const NativeWidget* Widget::native_widget() const { + return native_widget_; +} + +NativeWidget* Widget::native_widget() { + return native_widget_; +} + +const Event* Widget::GetCurrentEvent() { + return event_stack_.empty() ? NULL : event_stack_.top()->event(); +} + +void Widget::TooltipTextChanged(View* view) { + TooltipManager* manager = native_widget_private()->GetTooltipManager(); + if (manager) + manager->TooltipTextChanged(view); +} + +bool Widget::SetInitialFocus() { + if (!focus_on_creation_) + return true; + View* v = widget_delegate_->GetInitiallyFocusedView(); + if (v) + v->RequestFocus(); + return !!v; +} + +bool Widget::ConvertPointFromAncestor( + const Widget* ancestor, gfx::Point* point) const { + return native_widget_->ConvertPointFromAncestor(ancestor, point); +} + +View* Widget::GetChildViewParent() { + return GetContentsView() ? GetContentsView() : GetRootView(); +} + +gfx::Rect Widget::GetWorkAreaBoundsInScreen() const { + return native_widget_->GetWorkAreaBoundsInScreen(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Widget, NativeWidgetDelegate implementation: + +bool Widget::IsModal() const { + return widget_delegate_->IsModal(); +} + +bool Widget::IsDialogBox() const { + return !!widget_delegate_->AsDialogDelegate(); +} + +bool Widget::CanActivate() const { + return widget_delegate_->CanActivate(); +} + +bool Widget::IsInactiveRenderingDisabled() const { + return disable_inactive_rendering_; +} + +void Widget::EnableInactiveRendering() { + SetInactiveRenderingDisabled(false); +} + +void Widget::OnNativeWidgetActivationChanged(bool active) { + if (!active) { + SaveWindowPlacement(); + + // Close any open menus. + MenuController* menu_controller = MenuController::GetActiveInstance(); + if (menu_controller) + menu_controller->OnWidgetActivationChanged(); + } + + FOR_EACH_OBSERVER(Observer, observers_, + OnWidgetActivationChanged(this, active)); +} + +void Widget::OnNativeFocus(gfx::NativeView focused_view) { + WidgetFocusManager::GetInstance()->OnWidgetFocusEvent(focused_view, + GetNativeView()); +} + +void Widget::OnNativeBlur(gfx::NativeView focused_view) { + WidgetFocusManager::GetInstance()->OnWidgetFocusEvent(GetNativeView(), + focused_view); +} + +void Widget::OnNativeWidgetVisibilityChanged(bool visible) { + View* root = GetRootView(); + root->PropagateVisibilityNotifications(root, visible); + FOR_EACH_OBSERVER(Observer, observers_, + OnWidgetVisibilityChanged(this, visible)); + if (GetCompositor() && root->layer()) + root->layer()->SetVisible(visible); +} + +void Widget::OnNativeWidgetCreated() { + if (is_top_level()) + focus_manager_.reset(FocusManagerFactory::Create(this)); + + native_widget_->SetAccessibleRole( + widget_delegate_->GetAccessibleWindowRole()); + native_widget_->SetAccessibleState( + widget_delegate_->GetAccessibleWindowState()); + + if (widget_delegate_->IsModal()) + native_widget_->BecomeModal(); +} + +void Widget::OnNativeWidgetDestroying() { + FOR_EACH_OBSERVER(Observer, observers_, OnWidgetClosing(this)); + if (non_client_view_) + non_client_view_->WindowClosing(); + widget_delegate_->WindowClosing(); +} + +void Widget::OnNativeWidgetDestroyed() { + widget_delegate_->DeleteDelegate(); + widget_delegate_ = NULL; +} + +gfx::Size Widget::GetMinimumSize() { + return non_client_view_ ? non_client_view_->GetMinimumSize() : gfx::Size(); +} + +void Widget::OnNativeWidgetSizeChanged(const gfx::Size& new_size) { + root_view_->SetSize(new_size); + + // Size changed notifications can fire prior to full initialization + // i.e. during session restore. Avoid saving session state during these + // startup procedures. + if (native_widget_initialized_) + SaveWindowPlacement(); +} + +void Widget::OnNativeWidgetBeginUserBoundsChange() { + widget_delegate_->OnWindowBeginUserBoundsChange(); +} + +void Widget::OnNativeWidgetEndUserBoundsChange() { + widget_delegate_->OnWindowEndUserBoundsChange(); +} + +bool Widget::HasFocusManager() const { + return !!focus_manager_.get(); +} + +bool Widget::OnNativeWidgetPaintAccelerated(const gfx::Rect& dirty_region) { + ui::Compositor* compositor = GetCompositor(); + if (!compositor) + return false; + + // If the root view is animating, it is likely that it does not cover the same + // set of pixels it did at the last frame, so we must clear when compositing + // to avoid leaving ghosts. + bool force_clear = false; + if (GetRootView()->layer()) { + const ui::Transform& layer_transform = GetRootView()->layer()->transform(); + if (layer_transform != GetRootView()->GetTransform()) { + // The layer has not caught up to the view (i.e., the layer is still + // animating), and so a clear is required. + force_clear = true; + } else { + // Determine if the layer fills the client area. + gfx::Rect layer_bounds = GetRootView()->layer()->bounds(); + layer_transform.TransformRect(&layer_bounds); + gfx::Rect client_bounds = GetClientAreaScreenBounds(); + // Translate bounds to origin (client area bounds are offset to account + // for buttons, etc). + client_bounds.set_origin(gfx::Point(0, 0)); + if (!layer_bounds.Contains(client_bounds)) { + // It doesn't, and so a clear is required. + force_clear = true; + } + } + } + + compositor->Draw(force_clear); + return true; +} + +void Widget::OnNativeWidgetPaint(gfx::Canvas* canvas) { + GetRootView()->Paint(canvas); +} + +int Widget::GetNonClientComponent(const gfx::Point& point) { + return non_client_view_ ? + non_client_view_->NonClientHitTest(point) : + HTNOWHERE; +} + +bool Widget::OnKeyEvent(const KeyEvent& event) { + ScopedEvent scoped(this, event); + return static_cast<internal::RootView*>(GetRootView())->OnKeyEvent(event); +} + +bool Widget::OnMouseEvent(const MouseEvent& event) { + ScopedEvent scoped(this, event); + switch (event.type()) { + case ui::ET_MOUSE_PRESSED: + last_mouse_event_was_move_ = false; + // Make sure we're still visible before we attempt capture as the mouse + // press processing may have made the window hide (as happens with menus). + if (GetRootView()->OnMousePressed(event) && IsVisible()) { + is_mouse_button_pressed_ = true; + if (!native_widget_->HasMouseCapture()) + native_widget_->SetMouseCapture(); + return true; + } + return false; + case ui::ET_MOUSE_RELEASED: + last_mouse_event_was_move_ = false; + is_mouse_button_pressed_ = false; + // Release capture first, to avoid confusion if OnMouseReleased blocks. + if (native_widget_->HasMouseCapture() && + ShouldReleaseCaptureOnMouseReleased()) { + native_widget_->ReleaseMouseCapture(); + } + GetRootView()->OnMouseReleased(event); + return (event.flags() & ui::EF_IS_NON_CLIENT) ? false : true; + case ui::ET_MOUSE_MOVED: + case ui::ET_MOUSE_DRAGGED: + if (native_widget_->HasMouseCapture() && is_mouse_button_pressed_) { + last_mouse_event_was_move_ = false; + GetRootView()->OnMouseDragged(event); + } else if (!last_mouse_event_was_move_ || + last_mouse_event_position_ != event.location()) { + last_mouse_event_position_ = event.location(); + last_mouse_event_was_move_ = true; + GetRootView()->OnMouseMoved(event); + } + return false; + case ui::ET_MOUSE_EXITED: + last_mouse_event_was_move_ = false; + GetRootView()->OnMouseExited(event); + return false; + case ui::ET_MOUSEWHEEL: + return GetRootView()->OnMouseWheel( + reinterpret_cast<const MouseWheelEvent&>(event)); + default: + return false; + } + return true; +} + +void Widget::OnMouseCaptureLost() { + if (is_mouse_button_pressed_) + GetRootView()->OnMouseCaptureLost(); + is_mouse_button_pressed_ = false; +} + +ui::TouchStatus Widget::OnTouchEvent(const TouchEvent& event) { + ScopedEvent scoped(this, event); + return static_cast<internal::RootView*>(GetRootView())->OnTouchEvent(event); +} + +bool Widget::ExecuteCommand(int command_id) { + return widget_delegate_->ExecuteWindowsCommand(command_id); +} + +InputMethod* Widget::GetInputMethodDirect() { + return input_method_.get(); +} + +Widget* Widget::AsWidget() { + return this; +} + +const Widget* Widget::AsWidget() const { + return this; +} + +//////////////////////////////////////////////////////////////////////////////// +// Widget, FocusTraversable implementation: + +FocusSearch* Widget::GetFocusSearch() { + return root_view_->GetFocusSearch(); +} + +FocusTraversable* Widget::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; +} + +View* Widget::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; +} + +//////////////////////////////////////////////////////////////////////////////// +// Widget, protected: + +internal::RootView* Widget::CreateRootView() { + return new internal::RootView(this); +} + +void Widget::DestroyRootView() { + non_client_view_ = NULL; + root_view_.reset(); + // Input method has to be destroyed before focus manager. + input_method_.reset(); + // Defer focus manager's destruction. This is for the case when the + // focus manager is referenced by a child NativeWidgetGtk (e.g. TabbedPane in + // a dialog). When gtk_widget_destroy is called on the parent, the destroy + // signal reaches parent first and then the child. Thus causing the parent + // NativeWidgetGtk's dtor executed before the child's. If child's view + // hierarchy references this focus manager, it crashes. This will defer focus + // manager's destruction after child NativeWidgetGtk's dtor. + FocusManager* focus_manager = focus_manager_.release(); + if (focus_manager) + MessageLoop::current()->DeleteSoon(FROM_HERE, focus_manager); +} + +//////////////////////////////////////////////////////////////////////////////// +// Widget, private: + +bool Widget::ShouldReleaseCaptureOnMouseReleased() const { + return true; +} + +void Widget::SetInactiveRenderingDisabled(bool value) { + disable_inactive_rendering_ = value; + // We need to always notify the NonClientView so that it can trigger a paint. + // TODO: what's really needed is a way to know when either the active state + // changes or |disable_inactive_rendering_| changes. + if (non_client_view_) + non_client_view_->SetInactiveRenderingDisabled(value); + native_widget_->SetInactiveRenderingDisabled(value); +} + +void Widget::SaveWindowPlacement() { + // The window delegate does the actual saving for us. It seems like (judging + // by go/crash) that in some circumstances we can end up here after + // WM_DESTROY, at which point the window delegate is likely gone. So just + // bail. + if (!widget_delegate_) + return; + + ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL; + gfx::Rect bounds; + native_widget_->GetWindowPlacement(&bounds, &show_state); + widget_delegate_->SaveWindowPlacement(bounds, show_state); +} + +void Widget::SetInitialBounds(const gfx::Rect& bounds) { + if (!non_client_view_) + return; + + gfx::Rect saved_bounds; + if (GetSavedWindowPlacement(&saved_bounds, &saved_show_state_)) { + if (saved_show_state_ == ui::SHOW_STATE_MAXIMIZED) { + // If we're going to maximize, wait until Show is invoked to set the + // bounds. That way we avoid a noticable resize. + initial_restored_bounds_ = saved_bounds; + } else { + SetBounds(saved_bounds); + } + } else { + if (bounds.IsEmpty()) { + // No initial bounds supplied, so size the window to its content and + // center over its parent. + native_widget_->CenterWindow(non_client_view_->GetPreferredSize()); + } else { + // Use the supplied initial bounds. + SetBoundsConstrained(bounds); + } + } +} + +bool Widget::GetSavedWindowPlacement(gfx::Rect* bounds, + ui::WindowShowState* show_state) { + // First we obtain the window's saved show-style and store it. We need to do + // this here, rather than in Show() because by the time Show() is called, + // the window's size will have been reset (below) and the saved maximized + // state will have been lost. Sadly there's no way to tell on Windows when + // a window is restored from maximized state, so we can't more accurately + // track maximized state independently of sizing information. + + // Restore the window's placement from the controller. + if (widget_delegate_->GetSavedWindowPlacement(bounds, show_state)) { + if (!widget_delegate_->ShouldRestoreWindowSize()) { + bounds->set_size(non_client_view_->GetPreferredSize()); + } else { + gfx::Size minimum_size = GetMinimumSize(); + // Make sure the bounds are at least the minimum size. + if (bounds->width() < minimum_size.width()) + bounds->set_width(minimum_size.width()); + + if (bounds->height() < minimum_size.height()) + bounds->set_height(minimum_size.height()); + } + return true; + } + return false; +} + +void Widget::ReplaceInputMethod(InputMethod* input_method) { + input_method_.reset(input_method); + // TODO(oshima): Gtk's textfield doesn't need views InputMethod. + // Remove this check once gtk is removed. + if (input_method) { + input_method->set_delegate(native_widget_); + input_method->Init(this); + } +} + +namespace internal { + +//////////////////////////////////////////////////////////////////////////////// +// internal::NativeWidgetPrivate, NativeWidget implementation: + +internal::NativeWidgetPrivate* NativeWidgetPrivate::AsNativeWidgetPrivate() { + return this; +} + +} // namespace internal +} // namespace views diff --git a/views/widget/widget.h b/views/widget/widget.h new file mode 100644 index 0000000..4faf12b --- /dev/null +++ b/views/widget/widget.h @@ -0,0 +1,746 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_WIDGET_H_ +#define VIEWS_WIDGET_WIDGET_H_ +#pragma once + +#include <set> +#include <stack> + +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "ui/base/accessibility/accessibility_types.h" +#include "ui/base/ui_base_types.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/rect.h" +#include "ui/views/focus/focus_manager.h" +#include "ui/views/window/client_view.h" +#include "ui/views/window/non_client_view.h" +#include "views/widget/native_widget_delegate.h" + +#if defined(OS_WIN) +// Windows headers define macros for these function names which screw with us. +#if defined(IsMaximized) +#undef IsMaximized +#endif +#if defined(IsMinimized) +#undef IsMinimized +#endif +#if defined(CreateWindow) +#undef CreateWindow +#endif +#endif + +namespace gfx { +class Canvas; +class Point; +class Rect; +} + +namespace ui { +class Accelerator; +class Compositor; +class OSExchangeData; +class ThemeProvider; +enum TouchStatus; +} +using ui::ThemeProvider; + +namespace views { + +class DefaultThemeProvider; +class InputMethod; +class NativeWidget; +class NonClientFrameView; +class ScopedEvent; +class View; +class WidgetDelegate; +namespace internal { +class NativeWidgetPrivate; +class RootView; +} + +//////////////////////////////////////////////////////////////////////////////// +// Widget class +// +// Encapsulates the platform-specific rendering, event receiving and widget +// management aspects of the UI framework. +// +// Owns a RootView and thus a View hierarchy. Can contain child Widgets. +// Widget is a platform-independent type that communicates with a platform or +// context specific NativeWidget implementation. +// +// A special note on ownership: +// +// Depending on the value of the InitParams' ownership field, the Widget +// either owns or is owned by its NativeWidget: +// +// ownership = NATIVE_WIDGET_OWNS_WIDGET (default) +// The Widget instance is owned by its NativeWidget. When the NativeWidget +// is destroyed (in response to a native destruction message), it deletes +// the Widget from its destructor. +// ownership = WIDGET_OWNS_NATIVE_WIDGET (non-default) +// The Widget instance owns its NativeWidget. This state implies someone +// else wants to control the lifetime of this object. When they destroy +// the Widget it is responsible for destroying the NativeWidget (from its +// destructor). +// +class VIEWS_EXPORT Widget : public internal::NativeWidgetDelegate, + public FocusTraversable { + public: + // Observers can listen to various events on the Widgets. + class VIEWS_EXPORT Observer { + public: + virtual void OnWidgetClosing(Widget* widget) {} + virtual void OnWidgetVisibilityChanged(Widget* widget, bool visible) {} + virtual void OnWidgetActivationChanged(Widget* widget, bool active) {} + }; + + typedef std::set<Widget*> Widgets; + + enum FrameType { + FRAME_TYPE_DEFAULT, // Use whatever the default would be. + FRAME_TYPE_FORCE_CUSTOM, // Force the custom frame. + FRAME_TYPE_FORCE_NATIVE // Force the native frame. + }; + + struct VIEWS_EXPORT InitParams { + enum Type { + TYPE_WINDOW, // A decorated Window, like a frame window. + // Widgets of TYPE_WINDOW will have a NonClientView. + TYPE_WINDOW_FRAMELESS, + // An undecorated Window. + TYPE_CONTROL, // A control, like a button. + TYPE_POPUP, // An undecorated Window, with transient properties. + TYPE_MENU, // An undecorated Window, with transient properties + // specialized to menus. + TYPE_TOOLTIP, + TYPE_BUBBLE, + }; + enum Ownership { + // Default. Creator is not responsible for managing the lifetime of the + // Widget, it is destroyed when the corresponding NativeWidget is + // destroyed. + NATIVE_WIDGET_OWNS_WIDGET, + // Used when the Widget is owned by someone other than the NativeWidget, + // e.g. a scoped_ptr in tests. + WIDGET_OWNS_NATIVE_WIDGET + }; + + InitParams(); + explicit InitParams(Type type); + + // If |parent_widget| is non-null, it's native view is returned, otherwise + // |parent| is returned. + gfx::NativeView GetParent() const; + + Type type; + // If NULL, a default implementation will be constructed. + WidgetDelegate* delegate; + bool child; + bool transient; + // If true, the widget may be fully or partially transparent. If false, + // we can perform optimizations based on the widget being fully opaque. + // Defaults to false. + bool transparent; + bool accept_events; + bool can_activate; + bool keep_on_top; + Ownership ownership; + bool mirror_origin_in_rtl; + bool has_dropshadow; + // Whether the widget should be maximized or minimized. + ui::WindowShowState show_state; + // Should the widget be double buffered? Default is false. + bool double_buffer; + gfx::NativeView parent; + Widget* parent_widget; + // Specifies the initial bounds of the Widget. Default is empty, which means + // the NativeWidget may specify a default size. + gfx::Rect bounds; + // When set, this value is used as the Widget's NativeWidget implementation. + // The Widget will not construct a default one. Default is NULL. + NativeWidget* native_widget; + bool top_level; + // Only used by NativeWidgetAura. Specifies whether the Layer created by + // aura::Window has a texture. The default is true. + bool create_texture_for_layer; + }; + + Widget(); + virtual ~Widget(); + + // Creates a decorated window Widget with the specified properties. + static Widget* CreateWindow(WidgetDelegate* delegate); + static Widget* CreateWindowWithParent(WidgetDelegate* delegate, + gfx::NativeWindow parent); + static Widget* CreateWindowWithBounds(WidgetDelegate* delegate, + const gfx::Rect& bounds); + static Widget* CreateWindowWithParentAndBounds(WidgetDelegate* delegate, + gfx::NativeWindow parent, + const gfx::Rect& bounds); + + // Enumerates all windows pertaining to us and notifies their + // view hierarchies that the locale has changed. + static void NotifyLocaleChanged(); + + // Closes all Widgets that aren't identified as "secondary widgets". Called + // during application shutdown when the last non-secondary widget is closed. + static void CloseAllSecondaryWidgets(); + + // Converts a rectangle from one Widget's coordinate system to another's. + // Returns false if the conversion couldn't be made, because either these two + // Widgets do not have a common ancestor or they are not on the screen yet. + // The value of |*rect| won't be changed when false is returned. + static bool ConvertRect(const Widget* source, + const Widget* target, + gfx::Rect* rect); + + // SetPureViews and IsPureViews update and return the state of a global + // setting that tracks whether to use available pure Views implementations. + static void SetPureViews(bool pure); + static bool IsPureViews(); + + // Retrieves the Widget implementation associated with the given + // NativeView or Window, or NULL if the supplied handle has no associated + // Widget. + static Widget* GetWidgetForNativeView(gfx::NativeView native_view); + static Widget* GetWidgetForNativeWindow(gfx::NativeWindow native_window); + + // Retrieves the top level widget in a native view hierarchy + // starting at |native_view|. Top level widget is a widget with + // TYPE_WINDOW, TYPE_WINDOW_FRAMELESS, POPUP or MENU and has its own + // focus manager. This may be itself if the |native_view| is top level, + // or NULL if there is no toplevel in a native view hierarchy. + static Widget* GetTopLevelWidgetForNativeView(gfx::NativeView native_view); + + // Returns all Widgets in |native_view|'s hierarchy, including itself if + // it is one. + static void GetAllChildWidgets(gfx::NativeView native_view, + Widgets* children); + + // Re-parent a NativeView and notify all Widgets in |native_view|'s hierarchy + // of the change. + static void ReparentNativeView(gfx::NativeView native_view, + gfx::NativeView new_parent); + + // Returns the preferred size of the contents view of this window based on + // its localized size data. The width in cols is held in a localized string + // resource identified by |col_resource_id|, the height in the same fashion. + // TODO(beng): This should eventually live somewhere else, probably closer to + // ClientView. + static int GetLocalizedContentsWidth(int col_resource_id); + static int GetLocalizedContentsHeight(int row_resource_id); + static gfx::Size GetLocalizedContentsSize(int col_resource_id, + int row_resource_id); + + // Enable/Disable debug paint. + static void SetDebugPaintEnabled(bool enabled); + static bool IsDebugPaintEnabled(); + + // Returns true if the specified type requires a NonClientView. + static bool RequiresNonClientView(InitParams::Type type); + + void Init(const InitParams& params); + + // Returns the gfx::NativeView associated with this Widget. + gfx::NativeView GetNativeView() const; + + // Returns the gfx::NativeWindow associated with this Widget. This may return + // NULL on some platforms if the widget was created with a type other than + // TYPE_WINDOW. + gfx::NativeWindow GetNativeWindow() const; + + // Add/remove observer. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + bool HasObserver(Observer* observer); + + // 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, ui::Accelerator* accelerator); + + // Forwarded from the RootView so that the widget can do any cleanup. + void ViewHierarchyChanged(bool is_add, View* parent, View* child); + + // Performs any necessary cleanup and forwards to RootView. + void NotifyNativeViewHierarchyChanged(bool attached, + gfx::NativeView native_view); + + // Returns the top level widget in a hierarchy (see is_top_level() for + // the definition of top level widget.) Will return NULL if called + // before the widget is attached to the top level widget's hierarchy. + Widget* GetTopLevelWidget(); + const Widget* GetTopLevelWidget() const; + + // Gets/Sets the WidgetDelegate. + WidgetDelegate* widget_delegate() const { return widget_delegate_; } + + // Sets the specified view as the contents of this Widget. There can only + // be one contents 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. + void SetContentsView(View* view); + View* GetContentsView(); + + // Returns the bounds of the Widget in screen coordinates. + gfx::Rect GetWindowScreenBounds() const; + + // Returns the bounds of the Widget's client area in screen coordinates. + gfx::Rect GetClientAreaScreenBounds() const; + + // Retrieves the restored bounds for the window. + gfx::Rect GetRestoredBounds() const; + + // Sizes and/or places the widget to the specified bounds, size or position. + void SetBounds(const gfx::Rect& bounds); + void SetSize(const gfx::Size& size); + + // Like SetBounds(), but ensures the Widget is fully visible on screen, + // resizing and/or repositioning as necessary. This is only useful for + // non-child widgets. + void SetBoundsConstrained(const gfx::Rect& bounds); + + // Places the widget in front of the specified widget in z-order. + void MoveAboveWidget(Widget* widget); + void MoveAbove(gfx::NativeView native_view); + void MoveToTop(); + + // Sets a shape on the widget. This takes ownership of shape. + void SetShape(gfx::NativeRegion shape); + + // Hides the widget then closes it after a return to the message loop. + virtual void Close(); + + // TODO(beng): Move off public API. + // Closes the widget immediately. Compare to |Close|. This will destroy the + // window handle associated with this Widget, so should not be called from + // any code that expects it to be valid beyond this call. + void CloseNow(); + + // Toggles the enable state for the Close button (and the Close menu item in + // the system menu). + void EnableClose(bool enable); + + // Shows or hides the widget, without changing activation state. + virtual void Show(); + void Hide(); + + // Like Show(), but does not activate the window. + void ShowInactive(); + + // Activates the widget, assuming it already exists and is visible. + void Activate(); + + // Deactivates the widget, making the next window in the Z order the active + // window. + void Deactivate(); + + // Returns whether the Widget is the currently active window. + virtual bool IsActive() const; + + // Prevents the window from being rendered as deactivated. This state is + // reset automatically as soon as the window becomes activated again. There is + // no ability to control the state through this API as this leads to sync + // problems. + void DisableInactiveRendering(); + + // Sets the widget to be on top of all other widgets in the windowing system. + void SetAlwaysOnTop(bool on_top); + + // Maximizes/minimizes/restores the window. + void Maximize(); + void Minimize(); + void Restore(); + + // Whether or not the window is maximized or minimized. + virtual bool IsMaximized() const; + bool IsMinimized() const; + + // Accessors for fullscreen state. + void SetFullscreen(bool fullscreen); + bool IsFullscreen() const; + + // Sets the opacity of the widget. This may allow widgets behind the widget + // in the Z-order to become visible, depending on the capabilities of the + // underlying windowing system. Note that the caller must then schedule a + // repaint to allow this change to take effect. + void SetOpacity(unsigned char opacity); + + // Sets whether or not the window should show its frame as a "transient drag + // frame" - slightly transparent and without the standard window controls. + void SetUseDragFrame(bool use_drag_frame); + + // Returns the View at the root of the View hierarchy contained by this + // Widget. + View* GetRootView(); + const View* GetRootView() const; + + // A secondary widget is one that is automatically closed (via Close()) when + // all non-secondary widgets are closed. + // Default is true. + // TODO(beng): This is an ugly API, should be handled implicitly via + // transience. + void set_is_secondary_widget(bool is_secondary_widget) { + is_secondary_widget_ = is_secondary_widget; + } + bool is_secondary_widget() const { return is_secondary_widget_; } + + // Returns whether the Widget is visible to the user. + virtual bool IsVisible() const; + + // Returns whether the Widget is customized for accessibility. + bool IsAccessibleWidget() const; + + // Returns the ThemeProvider that provides theme resources for this Widget. + virtual ThemeProvider* GetThemeProvider() const; + + // Returns the FocusManager for this widget. + // Note that all widgets in a widget hierarchy share the same focus manager. + // TODO(beng): remove virtual. + virtual FocusManager* GetFocusManager(); + virtual const FocusManager* GetFocusManager() const; + + // Returns the InputMethod for this widget. + // Note that all widgets in a widget hierarchy share the same input method. + InputMethod* GetInputMethod(); + + // Starts a drag operation for the specified view. This blocks until the drag + // operation completes. |view| can be NULL. + // If the view is non-NULL it can be accessed during the drag by calling + // dragged_view(). If the view has not been deleted during the drag, + // OnDragDone() is called on it. + void RunShellDrag(View* view, const ui::OSExchangeData& data, int operation); + + // Returns the view that requested the current drag operation via + // RunShellDrag(), or NULL if there is no such view or drag operation. + View* dragged_view() { return dragged_view_; } + + // Adds the specified |rect| in client area coordinates to the rectangle to be + // redrawn. + void SchedulePaintInRect(const gfx::Rect& rect); + + // Sets the currently visible cursor. If |cursor| is NULL, the cursor used + // before the current is restored. + void SetCursor(gfx::NativeCursor cursor); + + // Resets the last move flag so that we can go around the optimization + // that disregards duplicate mouse moves when ending animation requires + // a new hit-test to do some highlighting as in TabStrip::RemoveTabAnimation + // to cause the close button to highlight. + void ResetLastMouseMoveFlag(); + + // Sets/Gets a native window property on the underlying native window object. + // Returns NULL if the property does not exist. Setting the property value to + // NULL removes the property. + void SetNativeWindowProperty(const char* name, void* value); + void* GetNativeWindowProperty(const char* name) const; + + // Tell the window to update its title from the delegate. + void UpdateWindowTitle(); + + // Tell the window to update its icon from the delegate. + void UpdateWindowIcon(); + + // Retrieves the focus traversable for this widget. + FocusTraversable* GetFocusTraversable(); + + // Notifies the view hierarchy contained in this widget that theme resources + // changed. + void ThemeChanged(); + + // Notifies the view hierarchy contained in this widget that locale resources + // changed. + void LocaleChanged(); + + void SetFocusTraversableParent(FocusTraversable* parent); + void SetFocusTraversableParentView(View* parent_view); + + // Clear native focus set to the Widget's NativeWidget. + void ClearNativeFocus(); + + // Sets the focus to |native_view|. + void FocusNativeView(gfx::NativeView native_view); + + // Updates the frame after an event caused it to be changed. + virtual void UpdateFrameAfterFrameChange(); + + void set_frame_type(FrameType frame_type) { frame_type_ = frame_type; } + FrameType frame_type() const { return frame_type_; } + + // Creates an appropriate NonClientFrameView for this widget. The + // WidgetDelegate is given the first opportunity to create one, followed by + // the NativeWidget implementation. If both return NULL, a default one is + // created. + virtual NonClientFrameView* CreateNonClientFrameView(); + + // Whether we should be using a native frame. + bool ShouldUseNativeFrame() const; + + // Forces the frame into the alternate frame type (custom or native) depending + // on its current state. + void DebugToggleFrameType(); + + // Tell the window that something caused the frame type to change. + void FrameTypeChanged(); + + NonClientView* non_client_view() { + return const_cast<NonClientView*>( + const_cast<const Widget*>(this)->non_client_view()); + } + const NonClientView* non_client_view() const { + return non_client_view_; + } + + ClientView* client_view() { + return const_cast<ClientView*>( + const_cast<const Widget*>(this)->client_view()); + } + const ClientView* client_view() const { + // non_client_view_ may be NULL, especially during creation. + return non_client_view_ ? non_client_view_->client_view() : NULL; + } + + const ui::Compositor* GetCompositor() const; + ui::Compositor* GetCompositor(); + + // Invokes method of same name on the NativeWidget. + void CalculateOffsetToAncestorWithLayer(gfx::Point* offset, + ui::Layer** layer_parent); + + // Invokes method of same name on the NativeWidget. + void ReorderLayers(); + + // Notifies assistive technology that an accessibility event has + // occurred on |view|, such as when the view is focused or when its + // value changes. Pass true for |send_native_event| except for rare + // cases where the view is a native control that's already sending a + // native accessibility event and the duplicate event would cause + // problems. + void NotifyAccessibilityEvent( + View* view, + ui::AccessibilityTypes::Event event_type, + bool send_native_event); + + const NativeWidget* native_widget() const; + NativeWidget* native_widget(); + + internal::NativeWidgetPrivate* native_widget_private() { + return native_widget_; + } + const internal::NativeWidgetPrivate* native_widget_private() const { + return native_widget_; + } + + // Returns the current event being processed. If there are multiple events + // being processed at the same time (e.g. one event triggers another event), + // then the most recent event is returned. Returns NULL if no event is being + // processed. + const Event* GetCurrentEvent(); + + // Invoked when the tooltip text changes for the specified views. + void TooltipTextChanged(View* view); + + // Sets-up the focus manager with the view that should have focus when the + // window is shown the first time. Returns true if the initial focus has been + // set or the widget should not set the initial focus, or false if the caller + // should set the initial focus (if any). + bool SetInitialFocus(); + + void set_focus_on_creation(bool focus_on_creation) { + focus_on_creation_ = focus_on_creation; + } + + // Converts the |point| in ancestor's coordinate to this widget's coordinates. + // Returns false if |ancestor| is not an ancestor of this widget. + // The receiver has to be pure views widget (NativeWidgetViews) and + // ancestor can be of any type. + bool ConvertPointFromAncestor( + const Widget* ancestor, gfx::Point* point) const; + + // Returns a View* that any child Widgets backed by NativeWidgetViews + // are added to. The default implementation returns the contents view + // if it exists and the root view otherwise. + virtual View* GetChildViewParent(); + + // True if the widget is considered top level widget. Top level widget + // is a widget of TYPE_WINDOW, TYPE_WINDOW_FRAMELESS, BUBBLE, POPUP or MENU, + // and has a focus manager and input method object associated with it. + // TYPE_CONTROL and TYPE_TOOLTIP is not considered top level. + bool is_top_level() const { return is_top_level_; } + + // Returns the bounds of work area in the screen that Widget belongs to. + gfx::Rect GetWorkAreaBoundsInScreen() const; + + // Overridden from NativeWidgetDelegate: + virtual bool IsModal() const OVERRIDE; + virtual bool IsDialogBox() const OVERRIDE; + virtual bool CanActivate() const OVERRIDE; + virtual bool IsInactiveRenderingDisabled() const OVERRIDE; + virtual void EnableInactiveRendering() OVERRIDE; + virtual void OnNativeWidgetActivationChanged(bool active) OVERRIDE; + virtual void OnNativeFocus(gfx::NativeView focused_view) OVERRIDE; + virtual void OnNativeBlur(gfx::NativeView focused_view) OVERRIDE; + virtual void OnNativeWidgetVisibilityChanged(bool visible) OVERRIDE; + virtual void OnNativeWidgetCreated() OVERRIDE; + virtual void OnNativeWidgetDestroying() OVERRIDE; + virtual void OnNativeWidgetDestroyed() OVERRIDE; + virtual gfx::Size GetMinimumSize() OVERRIDE; + virtual void OnNativeWidgetSizeChanged(const gfx::Size& new_size) OVERRIDE; + virtual void OnNativeWidgetBeginUserBoundsChange() OVERRIDE; + virtual void OnNativeWidgetEndUserBoundsChange() OVERRIDE; + virtual bool HasFocusManager() const OVERRIDE; + virtual bool OnNativeWidgetPaintAccelerated( + const gfx::Rect& dirty_region) OVERRIDE; + virtual void OnNativeWidgetPaint(gfx::Canvas* canvas) OVERRIDE; + virtual int GetNonClientComponent(const gfx::Point& point) OVERRIDE; + virtual bool OnKeyEvent(const KeyEvent& event) OVERRIDE; + virtual bool OnMouseEvent(const MouseEvent& event) OVERRIDE; + virtual void OnMouseCaptureLost() OVERRIDE; + virtual ui::TouchStatus OnTouchEvent(const TouchEvent& event) OVERRIDE; + virtual bool ExecuteCommand(int command_id) OVERRIDE; + virtual InputMethod* GetInputMethodDirect() OVERRIDE; + virtual Widget* AsWidget() OVERRIDE; + virtual const Widget* AsWidget() const OVERRIDE; + + // Overridden from FocusTraversable: + virtual FocusSearch* GetFocusSearch() OVERRIDE; + virtual FocusTraversable* GetFocusTraversableParent() OVERRIDE; + virtual View* GetFocusTraversableParentView() OVERRIDE; + + protected: + // Creates the RootView to be used within this Widget. Subclasses may override + // to create custom RootViews that do specialized event processing. + // TODO(beng): Investigate whether or not this is needed. + virtual internal::RootView* CreateRootView(); + + // Provided to allow the NativeWidget implementations to destroy the RootView + // _before_ the focus manager/tooltip manager. + // TODO(beng): remove once we fold those objects onto this one. + void DestroyRootView(); + + private: + // TODO(beng): Remove NativeWidgetGtk's dependence on the mouse state flags. + friend class NativeWidgetGtk; + + friend class NativeTextfieldViewsTest; + friend class NativeComboboxViewsTest; + friend class ScopedEvent; + + // Returns whether capture should be released on mouse release. + virtual bool ShouldReleaseCaptureOnMouseReleased() const; + + // Sets the value of |disable_inactive_rendering_|. If the value changes, + // both the NonClientView and WidgetDelegate are notified. + void SetInactiveRenderingDisabled(bool value); + + // Persists the window's restored position and "show" state using the + // window delegate. + void SaveWindowPlacement(); + + // Sizes and positions the window just after it is created. + void SetInitialBounds(const gfx::Rect& bounds); + + // Returns the bounds and "show" state from the delegate. Returns true if + // the delegate wants to use a specified bounds. + bool GetSavedWindowPlacement(gfx::Rect* bounds, + ui::WindowShowState* show_state); + + // Sets a different InputMethod instance to this widget. The instance + // must not be initialized, the ownership will be assumed by the widget. + // It's only for testing purpose. + void ReplaceInputMethod(InputMethod* input_method); + + internal::NativeWidgetPrivate* native_widget_; + + ObserverList<Observer> observers_; + + // Non-owned pointer to the Widget's delegate. May be NULL if no delegate is + // being used. + WidgetDelegate* widget_delegate_; + + // The root of the View hierarchy attached to this window. + // WARNING: see warning in tooltip_manager_ for ordering dependencies with + // this and tooltip_manager_. + scoped_ptr<internal::RootView> root_view_; + + // The View that provides the non-client area of the window (title bar, + // window controls, sizing borders etc). To use an implementation other than + // the default, this class must be sub-classed and this value set to the + // desired implementation before calling |InitWindow()|. + NonClientView* non_client_view_; + + // The focus manager keeping track of focus for this Widget and any of its + // children. NULL for non top-level widgets. + // WARNING: RootView's destructor calls into the FocusManager. As such, this + // must be destroyed AFTER root_view_. This is enforced in DestroyRootView(). + scoped_ptr<FocusManager> focus_manager_; + + // A theme provider to use when no other theme provider is specified. + scoped_ptr<DefaultThemeProvider> default_theme_provider_; + + // Valid for the lifetime of RunShellDrag(), indicates the view the drag + // started from. + View* dragged_view_; + + // The event stack. + std::stack<ScopedEvent*> event_stack_; + + // See class documentation for Widget above for a note about ownership. + InitParams::Ownership ownership_; + + // See set_is_secondary_widget(). + bool is_secondary_widget_; + + // The current frame type in use by this window. Defaults to + // FRAME_TYPE_DEFAULT. + FrameType frame_type_; + + // True when the window should be rendered as active, regardless of whether + // or not it actually is. + bool disable_inactive_rendering_; + + // Set to true if the widget is in the process of closing. + bool widget_closed_; + + // The saved "show" state for this window. See note in SetInitialBounds + // that explains why we save this. + ui::WindowShowState saved_show_state_; + + // The restored bounds used for the initial show. This is only used if + // |saved_show_state_| is maximized. + gfx::Rect initial_restored_bounds_; + + // Focus is automatically set to the view provided by the delegate + // when the widget is shown. Set this value to false to override + // initial focus for the widget. + bool focus_on_creation_; + + scoped_ptr<InputMethod> input_method_; + + // See |is_top_level()| accessor. + bool is_top_level_; + + // Tracks whether native widget has been initialized. + bool native_widget_initialized_; + + // TODO(beng): Remove NativeWidgetGtk's dependence on these: + // If true, the mouse is currently down. + bool is_mouse_button_pressed_; + + // TODO(beng): Remove NativeWidgetGtk's dependence on these: + // 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. + bool last_mouse_event_was_move_; + gfx::Point last_mouse_event_position_; + + DISALLOW_COPY_AND_ASSIGN(Widget); +}; + +} // namespace views + +#endif // VIEWS_WIDGET_WIDGET_H_ diff --git a/views/widget/widget_delegate.cc b/views/widget/widget_delegate.cc new file mode 100644 index 0000000..c64f8e6 --- /dev/null +++ b/views/widget/widget_delegate.cc @@ -0,0 +1,166 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/widget_delegate.h" + +#include "base/utf_string_conversions.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/views/bubble/bubble_delegate.h" +#include "ui/views/window/client_view.h" +#include "views/view.h" +#include "views/views_delegate.h" +#include "views/widget/widget.h" + +namespace views { + +//////////////////////////////////////////////////////////////////////////////// +// WidgetDelegate: + +WidgetDelegate::WidgetDelegate() : default_contents_view_(NULL) { +} + +void WidgetDelegate::OnWidgetMove() { +} + +void WidgetDelegate::OnDisplayChanged() { +} + +void WidgetDelegate::OnWorkAreaChanged() { +} + +View* WidgetDelegate::GetInitiallyFocusedView() { + return NULL; +} + +BubbleDelegateView* WidgetDelegate::AsBubbleDelegate() { + return NULL; +} + +DialogDelegate* WidgetDelegate::AsDialogDelegate() { + return NULL; +} + +bool WidgetDelegate::CanResize() const { + return false; +} + +bool WidgetDelegate::CanMaximize() const { + return false; +} + +bool WidgetDelegate::CanActivate() const { + return true; +} + +bool WidgetDelegate::IsModal() const { + return false; +} + +ui::AccessibilityTypes::Role WidgetDelegate::GetAccessibleWindowRole() const { + return ui::AccessibilityTypes::ROLE_WINDOW; +} + +ui::AccessibilityTypes::State WidgetDelegate::GetAccessibleWindowState() const { + return 0; +} + +string16 WidgetDelegate::GetAccessibleWindowTitle() const { + return GetWindowTitle(); +} + +string16 WidgetDelegate::GetWindowTitle() const { + return string16(); +} + +bool WidgetDelegate::ShouldShowWindowTitle() const { + return true; +} + +bool WidgetDelegate::ShouldShowClientEdge() const { + return true; +} + +SkBitmap WidgetDelegate::GetWindowAppIcon() { + // Use the window icon as app icon by default. + return GetWindowIcon(); +} + +// Returns the icon to be displayed in the window. +SkBitmap WidgetDelegate::GetWindowIcon() { + return SkBitmap(); +} + +bool WidgetDelegate::ShouldShowWindowIcon() const { + return false; +} + +bool WidgetDelegate::ExecuteWindowsCommand(int command_id) { + return false; +} + +std::string WidgetDelegate::GetWindowName() const { + return std::string(); +} + +void WidgetDelegate::SaveWindowPlacement(const gfx::Rect& bounds, + ui::WindowShowState show_state) { + std::string window_name = GetWindowName(); + if (!ViewsDelegate::views_delegate || window_name.empty()) + return; + + ViewsDelegate::views_delegate->SaveWindowPlacement( + GetWidget(), window_name, bounds, show_state); +} + +bool WidgetDelegate::GetSavedWindowPlacement( + gfx::Rect* bounds, + ui::WindowShowState* show_state) const { + std::string window_name = GetWindowName(); + if (!ViewsDelegate::views_delegate || window_name.empty()) + return false; + + return ViewsDelegate::views_delegate->GetSavedWindowPlacement( + window_name, bounds, show_state); +} + +bool WidgetDelegate::ShouldRestoreWindowSize() const { + return true; +} + +View* WidgetDelegate::GetContentsView() { + if (!default_contents_view_) + default_contents_view_ = new View; + return default_contents_view_; +} + +ClientView* WidgetDelegate::CreateClientView(Widget* widget) { + return new ClientView(widget, GetContentsView()); +} + +NonClientFrameView* WidgetDelegate::CreateNonClientFrameView() { + return NULL; +} + +bool WidgetDelegate::WillProcessWorkAreaChange() const { + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// WidgetDelegateView: + +WidgetDelegateView::WidgetDelegateView() { +} + +WidgetDelegateView::~WidgetDelegateView() { +} + +Widget* WidgetDelegateView::GetWidget() { + return View::GetWidget(); +} + +const Widget* WidgetDelegateView::GetWidget() const { + return View::GetWidget(); +} + +} // namespace views diff --git a/views/widget/widget_delegate.h b/views/widget/widget_delegate.h new file mode 100644 index 0000000..854c70b --- /dev/null +++ b/views/widget/widget_delegate.h @@ -0,0 +1,181 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_WIDGET_DELEGATE_H_ +#define VIEWS_WIDGET_WIDGET_DELEGATE_H_ +#pragma once + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "ui/base/accessibility/accessibility_types.h" +#include "ui/base/ui_base_types.h" +#include "views/view.h" + +class SkBitmap; + +namespace gfx { +class Rect; +} + +namespace views { +class BubbleDelegateView; +class ClientView; +class DialogDelegate; +class NonClientFrameView; +class View; +class Widget; + +// WidgetDelegate interface +// Handles events on Widgets in context-specific ways. +class VIEWS_EXPORT WidgetDelegate { + public: + WidgetDelegate(); + + // Called whenever the widget's position changes. + virtual void OnWidgetMove(); + + // Called with the display changes (color depth or resolution). + virtual void OnDisplayChanged(); + + // Called when the work area (the desktop area minus task bars, + // menu bars, etc.) changes in size. + virtual void OnWorkAreaChanged(); + + // Returns the view that should have the focus when the widget is shown. If + // NULL no view is focused. + virtual View* GetInitiallyFocusedView(); + + // Moved from WindowDelegate: ------------------------------------------------ + // TODO(beng): sort + + virtual BubbleDelegateView* AsBubbleDelegate(); + virtual DialogDelegate* AsDialogDelegate(); + + // Returns true if the window can ever be resized. + virtual bool CanResize() const; + + // Returns true if the window can ever be maximized. + virtual bool CanMaximize() const; + + // Returns true if the window can be activated. + virtual bool CanActivate() const; + + // Returns true if the dialog should be displayed modally to the window that + // opened it. Only windows with WindowType == DIALOG can be modal. + virtual bool IsModal() const; + + virtual ui::AccessibilityTypes::Role GetAccessibleWindowRole() const; + + virtual ui::AccessibilityTypes::State GetAccessibleWindowState() const; + + // Returns the title to be read with screen readers. + virtual string16 GetAccessibleWindowTitle() const; + + // Returns the text to be displayed in the window title. + virtual string16 GetWindowTitle() const; + + // Returns true if the window should show a title in the title bar. + virtual bool ShouldShowWindowTitle() const; + + // Returns true if the window's client view wants a client edge. + virtual bool ShouldShowClientEdge() const; + + // Returns the app icon for the window. On Windows, this is the ICON_BIG used + // in Alt-Tab list and Win7's taskbar. + virtual SkBitmap GetWindowAppIcon(); + + // Returns the icon to be displayed in the window. + virtual SkBitmap GetWindowIcon(); + + // Returns true if a window icon should be shown. + virtual bool ShouldShowWindowIcon() const; + + // Execute a command in the window's controller. Returns true if the command + // was handled, false if it was not. + virtual bool ExecuteWindowsCommand(int command_id); + + // Returns the window's name identifier. Used to identify this window for + // state restoration. + virtual std::string GetWindowName() const; + + // Saves the window's bounds and "show" state. By default this uses the + // process' local state keyed by window name (See GetWindowName above). This + // behavior can be overridden to provide additional functionality. + virtual void SaveWindowPlacement(const gfx::Rect& bounds, + ui::WindowShowState show_state); + + // Retrieves the window's bounds and "show" states. + // This behavior can be overridden to provide additional functionality. + virtual bool GetSavedWindowPlacement(gfx::Rect* bounds, + ui::WindowShowState* show_state) const; + + // Returns true if the window's size should be restored. If this is false, + // only the window's origin is restored and the window is given its + // preferred size. + // Default is true. + virtual bool ShouldRestoreWindowSize() const; + + // Called when the window closes. The delegate MUST NOT delete itself during + // this call, since it can be called afterwards. See DeleteDelegate(). + virtual void WindowClosing() {} + + // Called when the window is destroyed. No events must be sent or received + // after this point. The delegate can use this opportunity to delete itself at + // this time if necessary. + virtual void DeleteDelegate() {} + + // Called when the user begins/ends to change the bounds of the window. + virtual void OnWindowBeginUserBoundsChange() {} + virtual void OnWindowEndUserBoundsChange() {} + + // Returns the Widget associated with this delegate. + virtual Widget* GetWidget() = 0; + virtual const Widget* GetWidget() const = 0; + + // Returns the View that is contained within this Widget. + virtual View* GetContentsView(); + + // Called by the Widget to create the Client View used to host the contents + // of the widget. + virtual ClientView* CreateClientView(Widget* widget); + + // Called by the Widget to create the NonClient Frame View for this widget. + // Return NULL to use the default one. + virtual NonClientFrameView* CreateNonClientFrameView(); + + // Returns true if the window can be notified with the work area change. + // Otherwise, the work area change for the top window will be processed by + // the default window manager. In some cases, like panel, we would like to + // manage the positions by ourselves. + virtual bool WillProcessWorkAreaChange() const; + + protected: + virtual ~WidgetDelegate() {} + + private: + View* default_contents_view_; + + DISALLOW_COPY_AND_ASSIGN(WidgetDelegate); +}; + +// A WidgetDelegate implementation that is-a View. Used to override GetWidget() +// to call View's GetWidget() for the common case where a WidgetDelegate +// implementation is-a View. +class VIEWS_EXPORT WidgetDelegateView : public WidgetDelegate, public View { + public: + WidgetDelegateView(); + virtual ~WidgetDelegateView(); + + // Overridden from WidgetDelegate: + virtual Widget* GetWidget() OVERRIDE; + virtual const Widget* GetWidget() const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(WidgetDelegateView); +}; + +} // namespace views + +#endif // VIEWS_WIDGET_WIDGET_DELEGATE_H_ diff --git a/views/widget/widget_unittest.cc b/views/widget/widget_unittest.cc new file mode 100644 index 0000000..580bed8 --- /dev/null +++ b/views/widget/widget_unittest.cc @@ -0,0 +1,829 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/views/test/test_views_delegate.h" +#include "ui/views/test/views_test_base.h" +#include "ui/gfx/point.h" +#include "ui/gfx/native_widget_types.h" +#include "views/views_delegate.h" +#include "views/widget/native_widget_delegate.h" + +#if defined(USE_AURA) +#include "ui/aura/window.h" +#include "views/widget/native_widget_aura.h" +#elif defined(OS_WIN) +#include "views/widget/native_widget_win.h" +#elif defined(TOOLKIT_USES_GTK) +#include "views/widget/native_widget_gtk.h" +#endif + +namespace views { +namespace { + +// A generic typedef to pick up relevant NativeWidget implementations. +#if defined(USE_AURA) +typedef NativeWidgetAura NativeWidgetPlatform; +#elif defined(OS_WIN) +typedef NativeWidgetWin NativeWidgetPlatform; +#elif defined(TOOLKIT_USES_GTK) +typedef NativeWidgetGtk NativeWidgetPlatform; +#endif + +// A widget that assumes mouse capture always works. It won't on Gtk/Aura in +// testing, so we mock it. +#if defined(TOOLKIT_USES_GTK) || defined(USE_AURA) +class NativeWidgetCapture : public NativeWidgetPlatform { + public: + NativeWidgetCapture(internal::NativeWidgetDelegate* delegate) + : NativeWidgetPlatform(delegate), + mouse_capture_(false) {} + virtual ~NativeWidgetCapture() {} + + virtual void SetMouseCapture() OVERRIDE { + mouse_capture_ = true; + } + virtual void ReleaseMouseCapture() OVERRIDE { + if (mouse_capture_) + delegate()->OnMouseCaptureLost(); + mouse_capture_ = false; + } + virtual bool HasMouseCapture() const OVERRIDE { + return mouse_capture_; + } + + private: + bool mouse_capture_; + + DISALLOW_COPY_AND_ASSIGN(NativeWidgetCapture); +}; +#endif + +// A typedef that inserts our mock-capture NativeWidget implementation for +// relevant platforms. +#if defined(USE_AURA) +typedef NativeWidgetCapture NativeWidgetPlatformForTest; +#elif defined(OS_WIN) +typedef NativeWidgetWin NativeWidgetPlatformForTest; +#elif defined(TOOLKIT_USES_GTK) +typedef NativeWidgetCapture NativeWidgetPlatformForTest; +#endif + +// A view that always processes all mouse events. +class MouseView : public View { + public: + MouseView() : View() { + } + virtual ~MouseView() {} + + virtual bool OnMousePressed(const MouseEvent& event) OVERRIDE { + return true; + } +}; + +typedef ViewsTestBase WidgetTest; + +NativeWidget* CreatePlatformNativeWidget( + internal::NativeWidgetDelegate* delegate) { + return new NativeWidgetPlatformForTest(delegate); +} + +Widget* CreateTopLevelPlatformWidget() { + Widget* toplevel = new Widget; + Widget::InitParams toplevel_params(Widget::InitParams::TYPE_WINDOW); + toplevel_params.native_widget = CreatePlatformNativeWidget(toplevel); + toplevel->Init(toplevel_params); + return toplevel; +} + +Widget* CreateChildPlatformWidget(gfx::NativeView parent_native_view) { + Widget* child = new Widget; + Widget::InitParams child_params(Widget::InitParams::TYPE_CONTROL); + child_params.native_widget = CreatePlatformNativeWidget(child); + child_params.parent = parent_native_view; + child->Init(child_params); + child->SetContentsView(new View); + return child; +} + +#if defined(OS_WIN) && !defined(USE_AURA) +// On Windows, it is possible for us to have a child window that is TYPE_POPUP. +Widget* CreateChildPopupPlatformWidget(gfx::NativeView parent_native_view) { + Widget* child = new Widget; + Widget::InitParams child_params(Widget::InitParams::TYPE_POPUP); + child_params.child = true; + child_params.native_widget = CreatePlatformNativeWidget(child); + child_params.parent = parent_native_view; + child->Init(child_params); + child->SetContentsView(new View); + return child; +} +#endif + +Widget* CreateTopLevelNativeWidget() { + Widget* toplevel = new Widget; + Widget::InitParams params(Widget::InitParams::TYPE_WINDOW); + toplevel->Init(params); + toplevel->SetContentsView(new View); + return toplevel; +} + +Widget* CreateChildNativeWidgetWithParent(Widget* parent) { + Widget* child = new Widget; + Widget::InitParams params(Widget::InitParams::TYPE_CONTROL); + params.parent_widget = parent; + child->Init(params); + child->SetContentsView(new View); + return child; +} + +Widget* CreateChildNativeWidget() { + return CreateChildNativeWidgetWithParent(NULL); +} + +bool WidgetHasMouseCapture(const Widget* widget) { + return static_cast<const internal::NativeWidgetPrivate*>(widget-> + native_widget())->HasMouseCapture(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Widget::GetTopLevelWidget tests. + +TEST_F(WidgetTest, GetTopLevelWidget_Native) { + // Create a hierarchy of native widgets. + Widget* toplevel = CreateTopLevelPlatformWidget(); +#if defined(TOOLKIT_USES_GTK) + NativeWidgetGtk* native_widget = + static_cast<NativeWidgetGtk*>(toplevel->native_widget()); + gfx::NativeView parent = native_widget->window_contents(); +#else + gfx::NativeView parent = toplevel->GetNativeView(); +#endif + Widget* child = CreateChildPlatformWidget(parent); + + EXPECT_EQ(toplevel, toplevel->GetTopLevelWidget()); + EXPECT_EQ(toplevel, child->GetTopLevelWidget()); + + toplevel->CloseNow(); + // |child| should be automatically destroyed with |toplevel|. +} + +TEST_F(WidgetTest, GetTopLevelWidget_Synthetic) { + // Create a hierarchy consisting of a top level platform native widget and a + // child NativeWidget. + Widget* toplevel = CreateTopLevelPlatformWidget(); + Widget* child = CreateTopLevelNativeWidget(); + + EXPECT_EQ(toplevel, toplevel->GetTopLevelWidget()); + EXPECT_EQ(child, child->GetTopLevelWidget()); + + toplevel->CloseNow(); + // |child| should be automatically destroyed with |toplevel|. +} + +// Creates a hierarchy consisting of a desktop platform native widget, a +// toplevel NativeWidget, and a child of that toplevel, another NativeWidget. +TEST_F(WidgetTest, GetTopLevelWidget_SyntheticDesktop) { + // Create a hierarchy consisting of a desktop platform native widget, + // a toplevel NativeWidget and a chlid NativeWidget. + Widget* desktop = CreateTopLevelPlatformWidget(); + Widget* toplevel = CreateTopLevelNativeWidget(); // Will be parented + // automatically to + // |toplevel|. + + Widget* child = CreateChildNativeWidgetWithParent(toplevel); + + EXPECT_EQ(desktop, desktop->GetTopLevelWidget()); + EXPECT_EQ(toplevel, toplevel->GetTopLevelWidget()); + EXPECT_EQ(toplevel, child->GetTopLevelWidget()); + + desktop->CloseNow(); + // |toplevel|, |child| should be automatically destroyed with |toplevel|. +} + +// Tests some grab/ungrab events. +TEST_F(WidgetTest, DISABLED_GrabUngrab) { + Widget* toplevel = CreateTopLevelPlatformWidget(); + Widget* child1 = CreateChildNativeWidgetWithParent(toplevel); + Widget* child2 = CreateChildNativeWidgetWithParent(toplevel); + + toplevel->SetBounds(gfx::Rect(0, 0, 500, 500)); + + child1->SetBounds(gfx::Rect(10, 10, 300, 300)); + View* view = new MouseView(); + view->SetBounds(0, 0, 300, 300); + child1->GetRootView()->AddChildView(view); + + child2->SetBounds(gfx::Rect(200, 10, 200, 200)); + view = new MouseView(); + view->SetBounds(0, 0, 200, 200); + child2->GetRootView()->AddChildView(view); + + toplevel->Show(); + RunPendingMessages(); + + // Click on child1 + MouseEvent pressed(ui::ET_MOUSE_PRESSED, 45, 45, ui::EF_LEFT_BUTTON_DOWN); + toplevel->OnMouseEvent(pressed); + + EXPECT_TRUE(WidgetHasMouseCapture(toplevel)); + EXPECT_TRUE(WidgetHasMouseCapture(child1)); + EXPECT_FALSE(WidgetHasMouseCapture(child2)); + + MouseEvent released(ui::ET_MOUSE_RELEASED, 45, 45, ui::EF_LEFT_BUTTON_DOWN); + toplevel->OnMouseEvent(released); + + EXPECT_FALSE(WidgetHasMouseCapture(toplevel)); + EXPECT_FALSE(WidgetHasMouseCapture(child1)); + EXPECT_FALSE(WidgetHasMouseCapture(child2)); + + RunPendingMessages(); + + // Click on child2 + MouseEvent pressed2(ui::ET_MOUSE_PRESSED, 315, 45, ui::EF_LEFT_BUTTON_DOWN); + EXPECT_TRUE(toplevel->OnMouseEvent(pressed2)); + EXPECT_TRUE(WidgetHasMouseCapture(toplevel)); + EXPECT_TRUE(WidgetHasMouseCapture(child2)); + EXPECT_FALSE(WidgetHasMouseCapture(child1)); + + MouseEvent released2(ui::ET_MOUSE_RELEASED, 315, 45, ui::EF_LEFT_BUTTON_DOWN); + toplevel->OnMouseEvent(released2); + EXPECT_FALSE(WidgetHasMouseCapture(toplevel)); + EXPECT_FALSE(WidgetHasMouseCapture(child1)); + EXPECT_FALSE(WidgetHasMouseCapture(child2)); + + toplevel->CloseNow(); +} + +// Test if a focus manager and an inputmethod work without CHECK failure +// when window activation changes. +TEST_F(WidgetTest, ChangeActivation) { + Widget* top1 = CreateTopLevelPlatformWidget(); + // CreateInputMethod before activated + top1->GetInputMethod(); + top1->Show(); + RunPendingMessages(); + + Widget* top2 = CreateTopLevelPlatformWidget(); + top2->Show(); + RunPendingMessages(); + + top1->Activate(); + RunPendingMessages(); + + // Create InputMethod after deactivated. + top2->GetInputMethod(); + top2->Activate(); + RunPendingMessages(); + + top1->Activate(); + RunPendingMessages(); + + top1->CloseNow(); + top2->CloseNow(); +} + +// Tests visibility of child widgets. +TEST_F(WidgetTest, Visibility) { + Widget* toplevel = CreateTopLevelPlatformWidget(); +#if defined(TOOLKIT_USES_GTK) + NativeWidgetGtk* native_widget = + static_cast<NativeWidgetGtk*>(toplevel->native_widget()); + gfx::NativeView parent = native_widget->window_contents(); +#else + gfx::NativeView parent = toplevel->GetNativeView(); +#endif + Widget* child = CreateChildPlatformWidget(parent); + + EXPECT_FALSE(toplevel->IsVisible()); + EXPECT_FALSE(child->IsVisible()); + + child->Show(); + + EXPECT_FALSE(toplevel->IsVisible()); + EXPECT_FALSE(child->IsVisible()); + + toplevel->Show(); + + EXPECT_TRUE(toplevel->IsVisible()); + EXPECT_TRUE(child->IsVisible()); + + toplevel->CloseNow(); + // |child| should be automatically destroyed with |toplevel|. +} + +#if defined(OS_WIN) && !defined(USE_AURA) +// On Windows, it is possible to have child window that are TYPE_POPUP. Unlike +// regular child windows, these should be created as hidden and must be shown +// explicitly. +TEST_F(WidgetTest, Visibility_ChildPopup) { + Widget* toplevel = CreateTopLevelPlatformWidget(); + Widget* child_popup = CreateChildPopupPlatformWidget( + toplevel->GetNativeView()); + + EXPECT_FALSE(toplevel->IsVisible()); + EXPECT_FALSE(child_popup->IsVisible()); + + toplevel->Show(); + + EXPECT_TRUE(toplevel->IsVisible()); + EXPECT_FALSE(child_popup->IsVisible()); + + child_popup->Show(); + + EXPECT_TRUE(child_popup->IsVisible()); + + toplevel->CloseNow(); + // |child_popup| should be automatically destroyed with |toplevel|. +} +#endif + +// Tests visibility of synthetic child widgets. +TEST_F(WidgetTest, Visibility_Synthetic) { + // Create a hierarchy consisting of a desktop platform native widget, + // a toplevel NativeWidget and a chlid NativeWidget. + Widget* desktop = CreateTopLevelPlatformWidget(); + desktop->Show(); + + Widget* toplevel = CreateTopLevelNativeWidget(); // Will be parented + // automatically to + // |toplevel|. + + Widget* child = CreateChildNativeWidgetWithParent(toplevel); + + EXPECT_FALSE(toplevel->IsVisible()); + EXPECT_FALSE(child->IsVisible()); + + child->Show(); + + EXPECT_FALSE(toplevel->IsVisible()); + EXPECT_FALSE(child->IsVisible()); + + toplevel->Show(); + + EXPECT_TRUE(toplevel->IsVisible()); + EXPECT_TRUE(child->IsVisible()); + + desktop->CloseNow(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Widget ownership tests. +// +// Tests various permutations of Widget ownership specified in the +// InitParams::Ownership param. + +// A WidgetTest that supplies a toplevel widget for NativeWidget to parent to. +class WidgetOwnershipTest : public WidgetTest { + public: + WidgetOwnershipTest() {} + virtual ~WidgetOwnershipTest() {} + + virtual void SetUp() { + WidgetTest::SetUp(); + desktop_widget_ = CreateTopLevelPlatformWidget(); + } + + virtual void TearDown() { + desktop_widget_->CloseNow(); + WidgetTest::TearDown(); + } + + private: + Widget* desktop_widget_; + + DISALLOW_COPY_AND_ASSIGN(WidgetOwnershipTest); +}; + +// A bag of state to monitor destructions. +struct OwnershipTestState { + OwnershipTestState() : widget_deleted(false), native_widget_deleted(false) {} + + bool widget_deleted; + bool native_widget_deleted; +}; + +// A platform NativeWidget subclass that updates a bag of state when it is +// destroyed. +class OwnershipTestNativeWidget : public NativeWidgetPlatform { + public: + OwnershipTestNativeWidget(internal::NativeWidgetDelegate* delegate, + OwnershipTestState* state) + : NativeWidgetPlatform(delegate), + state_(state) { + } + virtual ~OwnershipTestNativeWidget() { + state_->native_widget_deleted = true; + } + + private: + OwnershipTestState* state_; + + DISALLOW_COPY_AND_ASSIGN(OwnershipTestNativeWidget); +}; + +// A views NativeWidget subclass that updates a bag of state when it is +// destroyed. +class OwnershipTestNativeWidgetPlatform : public NativeWidgetPlatformForTest { + public: + OwnershipTestNativeWidgetPlatform(internal::NativeWidgetDelegate* delegate, + OwnershipTestState* state) + : NativeWidgetPlatformForTest(delegate), + state_(state) { + } + virtual ~OwnershipTestNativeWidgetPlatform() { + state_->native_widget_deleted = true; + } + + private: + OwnershipTestState* state_; + + DISALLOW_COPY_AND_ASSIGN(OwnershipTestNativeWidgetPlatform); +}; + +// A Widget subclass that updates a bag of state when it is destroyed. +class OwnershipTestWidget : public Widget { + public: + OwnershipTestWidget(OwnershipTestState* state) : state_(state) {} + virtual ~OwnershipTestWidget() { + state_->widget_deleted = true; + } + + private: + OwnershipTestState* state_; + + DISALLOW_COPY_AND_ASSIGN(OwnershipTestWidget); +}; + +// Widget owns its NativeWidget, part 1: NativeWidget is a platform-native +// widget. +TEST_F(WidgetOwnershipTest, Ownership_WidgetOwnsPlatformNativeWidget) { + OwnershipTestState state; + + scoped_ptr<Widget> widget(new OwnershipTestWidget(&state)); + Widget::InitParams params(Widget::InitParams::TYPE_POPUP); + params.native_widget = + new OwnershipTestNativeWidgetPlatform(widget.get(), &state); + params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + widget->Init(params); + + // Now delete the Widget, which should delete the NativeWidget. + widget.reset(); + + EXPECT_TRUE(state.widget_deleted); + EXPECT_TRUE(state.native_widget_deleted); + + // TODO(beng): write test for this ownership scenario and the NativeWidget + // being deleted out from under the Widget. +} + +// Widget owns its NativeWidget, part 2: NativeWidget is a NativeWidget. +TEST_F(WidgetOwnershipTest, Ownership_WidgetOwnsViewsNativeWidget) { + OwnershipTestState state; + + scoped_ptr<Widget> widget(new OwnershipTestWidget(&state)); + Widget::InitParams params(Widget::InitParams::TYPE_POPUP); + params.native_widget = + new OwnershipTestNativeWidgetPlatform(widget.get(), &state); + params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + widget->Init(params); + + // Now delete the Widget, which should delete the NativeWidget. + widget.reset(); + + EXPECT_TRUE(state.widget_deleted); + EXPECT_TRUE(state.native_widget_deleted); + + // TODO(beng): write test for this ownership scenario and the NativeWidget + // being deleted out from under the Widget. +} + +// Widget owns its NativeWidget, part 3: NativeWidget is a NativeWidget, +// destroy the parent view. +TEST_F(WidgetOwnershipTest, + Ownership_WidgetOwnsViewsNativeWidget_DestroyParentView) { + OwnershipTestState state; + + Widget* toplevel = CreateTopLevelPlatformWidget(); + + scoped_ptr<Widget> widget(new OwnershipTestWidget(&state)); + Widget::InitParams params(Widget::InitParams::TYPE_POPUP); + params.native_widget = + new OwnershipTestNativeWidgetPlatform(widget.get(), &state); + params.parent_widget = toplevel; + params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + widget->Init(params); + + // Now close the toplevel, which deletes the view hierarchy. + toplevel->CloseNow(); + + RunPendingMessages(); + + // This shouldn't delete the widget because it shouldn't be deleted + // from the native side. + EXPECT_FALSE(state.widget_deleted); + EXPECT_FALSE(state.native_widget_deleted); + + // Now delete it explicitly. + widget.reset(); + + EXPECT_TRUE(state.widget_deleted); + EXPECT_TRUE(state.native_widget_deleted); +} + +// NativeWidget owns its Widget, part 1: NativeWidget is a platform-native +// widget. +TEST_F(WidgetOwnershipTest, Ownership_PlatformNativeWidgetOwnsWidget) { + OwnershipTestState state; + + Widget* widget = new OwnershipTestWidget(&state); + Widget::InitParams params(Widget::InitParams::TYPE_POPUP); + params.native_widget = + new OwnershipTestNativeWidgetPlatform(widget, &state); + widget->Init(params); + + // Now destroy the native widget. + widget->CloseNow(); + + EXPECT_TRUE(state.widget_deleted); + EXPECT_TRUE(state.native_widget_deleted); +} + +// NativeWidget owns its Widget, part 2: NativeWidget is a NativeWidget. +#if defined(OS_CHROMEOS) && defined(TOOLKIT_USES_GTK) +// Temporarily disable the test (http://crbug.com/104945). +TEST_F(WidgetOwnershipTest, DISABLED_Ownership_ViewsNativeWidgetOwnsWidget) { +#else +TEST_F(WidgetOwnershipTest, Ownership_ViewsNativeWidgetOwnsWidget) { +#endif + OwnershipTestState state; + + Widget* toplevel = CreateTopLevelPlatformWidget(); + + Widget* widget = new OwnershipTestWidget(&state); + Widget::InitParams params(Widget::InitParams::TYPE_POPUP); + params.native_widget = + new OwnershipTestNativeWidgetPlatform(widget, &state); + params.parent_widget = toplevel; + widget->Init(params); + + // Now destroy the native widget. This is achieved by closing the toplevel. + toplevel->CloseNow(); + + // The NativeWidget won't be deleted until after a return to the message loop + // so we have to run pending messages before testing the destruction status. + RunPendingMessages(); + + EXPECT_TRUE(state.widget_deleted); + EXPECT_TRUE(state.native_widget_deleted); +} + +// NativeWidget owns its Widget, part 3: NativeWidget is a platform-native +// widget, destroyed out from under it by the OS. +TEST_F(WidgetOwnershipTest, + Ownership_PlatformNativeWidgetOwnsWidget_NativeDestroy) { + OwnershipTestState state; + + Widget* widget = new OwnershipTestWidget(&state); + Widget::InitParams params(Widget::InitParams::TYPE_POPUP); + params.native_widget = + new OwnershipTestNativeWidgetPlatform(widget, &state); + widget->Init(params); + + // Now simulate a destroy of the platform native widget from the OS: +#if defined(USE_AURA) + delete widget->GetNativeView(); +#elif defined(OS_WIN) + DestroyWindow(widget->GetNativeView()); +#elif defined(TOOLKIT_USES_GTK) + gtk_widget_destroy(widget->GetNativeView()); +#endif + + EXPECT_TRUE(state.widget_deleted); + EXPECT_TRUE(state.native_widget_deleted); +} + +// NativeWidget owns its Widget, part 4: NativeWidget is a NativeWidget, +// destroyed by the view hierarchy that contains it. +#if defined(OS_CHROMEOS) && defined(TOOLKIT_USES_GTK) +// Temporarily disable the test (http://crbug.com/104945). +TEST_F(WidgetOwnershipTest, + DISABLED_Ownership_ViewsNativeWidgetOwnsWidget_NativeDestroy) { +#else +TEST_F(WidgetOwnershipTest, + Ownership_ViewsNativeWidgetOwnsWidget_NativeDestroy) { +#endif + OwnershipTestState state; + + Widget* toplevel = CreateTopLevelPlatformWidget(); + + Widget* widget = new OwnershipTestWidget(&state); + Widget::InitParams params(Widget::InitParams::TYPE_POPUP); + params.native_widget = + new OwnershipTestNativeWidgetPlatform(widget, &state); + params.parent_widget = toplevel; + widget->Init(params); + + // Destroy the widget (achieved by closing the toplevel). + toplevel->CloseNow(); + + // The NativeWidget won't be deleted until after a return to the message loop + // so we have to run pending messages before testing the destruction status. + RunPendingMessages(); + + EXPECT_TRUE(state.widget_deleted); + EXPECT_TRUE(state.native_widget_deleted); +} + +// NativeWidget owns its Widget, part 5: NativeWidget is a NativeWidget, +// we close it directly. +TEST_F(WidgetOwnershipTest, + Ownership_ViewsNativeWidgetOwnsWidget_Close) { + OwnershipTestState state; + + Widget* toplevel = CreateTopLevelPlatformWidget(); + + Widget* widget = new OwnershipTestWidget(&state); + Widget::InitParams params(Widget::InitParams::TYPE_POPUP); + params.native_widget = + new OwnershipTestNativeWidgetPlatform(widget, &state); + params.parent_widget = toplevel; + widget->Init(params); + + // Destroy the widget. + widget->Close(); + toplevel->CloseNow(); + + // The NativeWidget won't be deleted until after a return to the message loop + // so we have to run pending messages before testing the destruction status. + RunPendingMessages(); + + EXPECT_TRUE(state.widget_deleted); + EXPECT_TRUE(state.native_widget_deleted); +} + +//////////////////////////////////////////////////////////////////////////////// +// Widget observer tests. +// + +class WidgetObserverTest : public WidgetTest, + Widget::Observer { + public: + WidgetObserverTest() + : active_(NULL), + widget_closed_(NULL), + widget_activated_(NULL), + widget_shown_(NULL), + widget_hidden_(NULL) { + } + + virtual ~WidgetObserverTest() {} + + virtual void OnWidgetClosing(Widget* widget) OVERRIDE { + if (active_ == widget) + active_ = NULL; + widget_closed_ = widget; + } + + virtual void OnWidgetActivationChanged(Widget* widget, + bool active) OVERRIDE { + if (active) { + if (widget_activated_) + widget_activated_->Deactivate(); + widget_activated_ = widget; + active_ = widget; + } else { + if (widget_activated_ == widget) + widget_activated_ = NULL; + widget_deactivated_ = widget; + } + } + + virtual void OnWidgetVisibilityChanged(Widget* widget, + bool visible) OVERRIDE { + if (visible) + widget_shown_ = widget; + else + widget_hidden_ = widget; + } + + void reset() { + active_ = NULL; + widget_closed_ = NULL; + widget_activated_ = NULL; + widget_deactivated_ = NULL; + widget_shown_ = NULL; + widget_hidden_ = NULL; + } + + Widget* NewWidget() { + Widget* widget = CreateTopLevelNativeWidget(); + widget->AddObserver(this); + return widget; + } + + const Widget* active() const { return active_; } + const Widget* widget_closed() const { return widget_closed_; } + const Widget* widget_activated() const { return widget_activated_; } + const Widget* widget_deactivated() const { return widget_deactivated_; } + const Widget* widget_shown() const { return widget_shown_; } + const Widget* widget_hidden() const { return widget_hidden_; } + + private: + + Widget* active_; + + Widget* widget_closed_; + Widget* widget_activated_; + Widget* widget_deactivated_; + Widget* widget_shown_; + Widget* widget_hidden_; +}; + +TEST_F(WidgetObserverTest, DISABLED_ActivationChange) { + Widget* toplevel = CreateTopLevelPlatformWidget(); + + Widget* toplevel1 = NewWidget(); + Widget* toplevel2 = NewWidget(); + + toplevel1->Show(); + toplevel2->Show(); + + reset(); + + toplevel1->Activate(); + + RunPendingMessages(); + EXPECT_EQ(toplevel1, widget_activated()); + + toplevel2->Activate(); + RunPendingMessages(); + EXPECT_EQ(toplevel1, widget_deactivated()); + EXPECT_EQ(toplevel2, widget_activated()); + EXPECT_EQ(toplevel2, active()); + + toplevel->CloseNow(); +} + +TEST_F(WidgetObserverTest, DISABLED_VisibilityChange) { + Widget* toplevel = CreateTopLevelPlatformWidget(); + + Widget* child1 = NewWidget(); + Widget* child2 = NewWidget(); + + toplevel->Show(); + child1->Show(); + child2->Show(); + + reset(); + + child1->Hide(); + EXPECT_EQ(child1, widget_hidden()); + + child2->Hide(); + EXPECT_EQ(child2, widget_hidden()); + + child1->Show(); + EXPECT_EQ(child1, widget_shown()); + + child2->Show(); + EXPECT_EQ(child2, widget_shown()); + + toplevel->CloseNow(); +} + +#if !defined(USE_AURA) && defined(OS_WIN) +// Aura needs shell to maximize/fullscreen window. +// NativeWidgetGtk doesn't implement GetRestoredBounds. +TEST_F(WidgetTest, GetRestoredBounds) { + Widget* toplevel = CreateTopLevelPlatformWidget(); + EXPECT_EQ(toplevel->GetWindowScreenBounds().ToString(), + toplevel->GetRestoredBounds().ToString()); + toplevel->Show(); + toplevel->Maximize(); + RunPendingMessages(); + EXPECT_NE(toplevel->GetWindowScreenBounds().ToString(), + toplevel->GetRestoredBounds().ToString()); + EXPECT_GT(toplevel->GetRestoredBounds().width(), 0); + EXPECT_GT(toplevel->GetRestoredBounds().height(), 0); + + toplevel->Restore(); + RunPendingMessages(); + EXPECT_EQ(toplevel->GetWindowScreenBounds().ToString(), + toplevel->GetRestoredBounds().ToString()); + + toplevel->SetFullscreen(true); + RunPendingMessages(); + EXPECT_NE(toplevel->GetWindowScreenBounds().ToString(), + toplevel->GetRestoredBounds().ToString()); + EXPECT_GT(toplevel->GetRestoredBounds().width(), 0); + EXPECT_GT(toplevel->GetRestoredBounds().height(), 0); +} +#endif + +} // namespace +} // namespace views diff --git a/views/widget/window_manager.cc b/views/widget/window_manager.cc new file mode 100644 index 0000000..7e5e7ca --- /dev/null +++ b/views/widget/window_manager.cc @@ -0,0 +1,100 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/window_manager.h" + +#include "base/compiler_specific.h" +#include "ui/views/events/event.h" +#include "views/widget/widget.h" + +namespace { + +views::WindowManager* window_manager = NULL; + +class NullWindowManager : public views::WindowManager { + public: + NullWindowManager() : mouse_capture_(NULL) { + } + + virtual void StartMoveDrag(views::Widget* widget, + const gfx::Point& screen_point) OVERRIDE { + NOTIMPLEMENTED(); + } + + virtual void StartResizeDrag(views::Widget* widget, + const gfx::Point& screen_point, + int hittest_code) OVERRIDE { + NOTIMPLEMENTED(); + } + + virtual bool SetMouseCapture(views::Widget* widget) OVERRIDE { + if (mouse_capture_ == widget) + return true; + if (mouse_capture_) + return false; + mouse_capture_ = widget; + return true; + } + + virtual bool ReleaseMouseCapture(views::Widget* widget) OVERRIDE { + if (widget && mouse_capture_ != widget) + return false; + mouse_capture_ = NULL; + return true; + } + + virtual bool HasMouseCapture(const views::Widget* widget) const OVERRIDE { + return mouse_capture_ == widget; + } + + virtual bool HandleKeyEvent(views::Widget* widget, + const views::KeyEvent& event) OVERRIDE { + return false; + } + + virtual bool HandleMouseEvent(views::Widget* widget, + const views::MouseEvent& event) OVERRIDE { + if (mouse_capture_) { + views::MouseEvent translated(event, widget->GetRootView(), + mouse_capture_->GetRootView()); + mouse_capture_->OnMouseEvent(translated); + return true; + } + return false; + } + + virtual ui::TouchStatus HandleTouchEvent(views::Widget* widget, + const views::TouchEvent& event) OVERRIDE { + return ui::TOUCH_STATUS_UNKNOWN; + } + + void Register(views::Widget* widget) OVERRIDE {} + + private: + views::Widget* mouse_capture_; +}; + +} // namespace + +namespace views { + +WindowManager::WindowManager() { +} + +WindowManager::~WindowManager() { +} + +// static +void WindowManager::Install(WindowManager* wm) { + window_manager = wm; +} + +// static +WindowManager* WindowManager::Get() { + if (!window_manager) + window_manager = new NullWindowManager(); + return window_manager; +} + +} // namespace views diff --git a/views/widget/window_manager.h b/views/widget/window_manager.h new file mode 100644 index 0000000..f634bf0 --- /dev/null +++ b/views/widget/window_manager.h @@ -0,0 +1,79 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef VIEWS_WIDGET_WINDOW_MANAGER_H_ +#define VIEWS_WIDGET_WINDOW_MANAGER_H_ +#pragma once + +#include "base/basictypes.h" +#include "ui/base/events.h" +#include "views/views_export.h" + +namespace gfx { +class Point; +} + +namespace views { +class KeyEvent; +class MouseEvent; +class TouchEvent; +class Widget; + +// A interface to WindowManager. +class VIEWS_EXPORT WindowManager { + public: + WindowManager(); + virtual ~WindowManager(); + + // Starts moving window given by |widget|. |point| represents the + // initial location of the mouse pointer. + virtual void StartMoveDrag(Widget* widget, const gfx::Point& point) = 0; + + // Starts resizing window give by |widget|. |point| represents the + // initial location of the mouse pointer and |hittest_code| represents + // the edge of the window a user selected to resize the window. See + // ui/base/hit_test.h for the hittest_code definition. + virtual void StartResizeDrag( + Widget* widget, const gfx::Point& point, int hittest_code) = 0; + + // Sets mouse capture on |widget|. Returns false if other widget + // already has mouse capture. + virtual bool SetMouseCapture(Widget* widget) = 0; + + // Releases the mouse capture on |widget|. Returns false if |widget| + // haven't capture the mouse. + virtual bool ReleaseMouseCapture(Widget* widget) = 0; + + // Checks if the |widget| has mouse capture. + virtual bool HasMouseCapture(const Widget* widget) const = 0; + + // WindowManager handles mouse event first. It may reisze/move window, + // or send the event to widget that has mouse capture. + virtual bool HandleKeyEvent(Widget* widget, const KeyEvent& event) = 0; + + // WindowManager handles mouse event first. It may resize/move window, + // or send the event to widget that has mouse capture. + virtual bool HandleMouseEvent(Widget* widget, const MouseEvent& event) = 0; + + // WindowManager handles touch event first. It is currently used only to + // activate windows. But it can also be used to move/resize windows. + virtual ui::TouchStatus HandleTouchEvent(Widget* widget, + const TouchEvent& event) = 0; + + // Register widget to the window manager. + virtual void Register(Widget* widget) = 0; + + // Installs window manager. + static void Install(WindowManager* wm); + + // Returns installed WindowManager. + static WindowManager* Get(); + + private: + DISALLOW_COPY_AND_ASSIGN(WindowManager); +}; + +} // namespace views + +#endif // VIEWS_WIDGET_WINDOW_MANAGER_H_ |