diff options
author | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-19 03:33:44 +0000 |
---|---|---|
committer | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-19 03:33:44 +0000 |
commit | 134c47b9e74ab7aa6dce7e1beeaf5e406a925b76 (patch) | |
tree | f4db8cfc40f3eb8a66421ee415f575cf6a76d638 /views | |
parent | cf5811e922d5aa215a4094bd88e70c38c6ddc57c (diff) | |
download | chromium_src-134c47b9e74ab7aa6dce7e1beeaf5e406a925b76.zip chromium_src-134c47b9e74ab7aa6dce7e1beeaf5e406a925b76.tar.gz chromium_src-134c47b9e74ab7aa6dce7e1beeaf5e406a925b76.tar.bz2 |
Relands drop support:
Adds drop support for views on gtk. As X lazily provides drop data I
needed to tweak the views API a bit.
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/173025
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@23690 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views')
-rw-r--r-- | views/view.cc | 11 | ||||
-rw-r--r-- | views/view.h | 28 | ||||
-rw-r--r-- | views/views.gyp | 2 | ||||
-rw-r--r-- | views/widget/drop_helper.cc | 20 | ||||
-rw-r--r-- | views/widget/drop_target_gtk.cc | 329 | ||||
-rw-r--r-- | views/widget/drop_target_gtk.h | 113 | ||||
-rw-r--r-- | views/widget/widget_gtk.cc | 152 | ||||
-rw-r--r-- | views/widget/widget_gtk.h | 53 |
8 files changed, 688 insertions, 20 deletions
diff --git a/views/view.cc b/views/view.cc index 4adef5b..30a6e74 100644 --- a/views/view.cc +++ b/views/view.cc @@ -1238,7 +1238,18 @@ DragController* View::GetDragController() { return drag_controller_; } +bool View::GetDropFormats( + int* formats, + std::set<OSExchangeData::CustomFormat>* custom_formats) { + return false; +} + +bool View::AreDropTypesRequired() { + return false; +} + bool View::CanDrop(const OSExchangeData& data) { + // TODO(sky): when I finish up migration, this should default to true. return false; } diff --git a/views/view.h b/views/view.h index ba892c1..66ef76f 100644 --- a/views/view.h +++ b/views/view.h @@ -9,9 +9,11 @@ #include <algorithm> #include <map> +#include <set> #include <string> #include <vector> +#include "app/os_exchange_data.h" #include "base/gfx/native_widget_types.h" #include "base/gfx/rect.h" #include "base/scoped_ptr.h" @@ -20,17 +22,12 @@ #include "views/background.h" #include "views/border.h" -#if defined(OS_WIN) -struct IDataObject; -#endif // defined(OS_WIN) - namespace gfx { class Canvas; class Insets; class Path; } -class OSExchangeData; class ViewAccessibilityWrapper; class ThemeProvider; @@ -742,9 +739,12 @@ class View : public AcceleratorTarget { DragController* GetDragController(); // During a drag and drop session when the mouse moves the view under the - // mouse is queried to see if it should be a target for the drag and drop - // session. A view indicates it is a valid target by returning true from - // CanDrop. If a view returns true from CanDrop, + // mouse is queried for the drop types it supports by way of the + // GetDropFormats methods. If the view returns true and the drag site can + // provide data in one of the formats, the view is asked if the drop data + // is required before any other drop events are sent. Once the + // data is available the view is asked if it supports the drop (by way of + // the CanDrop method). If a view returns true from CanDrop, // OnDragEntered is sent to the view when the mouse first enters the view, // as the mouse moves around within the view OnDragUpdated is invoked. // If the user releases the mouse over the view and OnDragUpdated returns a @@ -756,6 +756,18 @@ class View : public AcceleratorTarget { // the mouse does not support the drop, the ancestors are walked until one // is found that supports the drop. + // Override and return the set of formats that can be dropped on this view. + // |formats| is a bitmask of the formats defined bye OSExchangeData::Format. + // The default implementation returns false, which means the view doesn't + // support dropping. + virtual bool GetDropFormats( + int* formats, + std::set<OSExchangeData::CustomFormat>* custom_formats); + + // Override and return true if the data must be available before any drop + // methods should be invoked. The default is false. + virtual bool AreDropTypesRequired(); + // A view that supports drag and drop must override this and return true if // data contains a type that may be dropped on this view. virtual bool CanDrop(const OSExchangeData& data); diff --git a/views/views.gyp b/views/views.gyp index 7c458a9..3e57c01 100644 --- a/views/views.gyp +++ b/views/views.gyp @@ -214,6 +214,8 @@ 'widget/default_theme_provider.h', 'widget/drop_helper.cc', 'widget/drop_helper.h', + 'widget/drop_target_gtk.cc', + 'widget/drop_target_gtk.h', 'widget/drop_target_win.cc', 'widget/drop_target_win.h', 'widget/root_view.cc', diff --git a/views/widget/drop_helper.cc b/views/widget/drop_helper.cc index 35dcbd0..19b70b71 100644 --- a/views/widget/drop_helper.cc +++ b/views/widget/drop_helper.cc @@ -87,6 +87,10 @@ View* DropHelper::CalculateTargetViewImpl( } 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. @@ -94,6 +98,22 @@ View* DropHelper::CalculateTargetViewImpl( (!view->IsEnabled() || !view->CanDrop(data))) { view = view->GetParent(); } +#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->GetParent(); + } +#endif return view; } diff --git a/views/widget/drop_target_gtk.cc b/views/widget/drop_target_gtk.cc new file mode 100644 index 0000000..d262e3e --- /dev/null +++ b/views/widget/drop_target_gtk.cc @@ -0,0 +1,329 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "views/widget/drop_target_gtk.h" + +#include <algorithm> +#include <iterator> + +#include "app/drag_drop_types.h" +#include "app/gtk_dnd_util.h" +#include "app/os_exchange_data_provider_gtk.h" +#include "base/gfx/point.h" +#include "base/string_util.h" +#include "views/widget/root_view.h" +#include "views/widget/widget_gtk.h" + +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"; +} + +// 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; + NOTIMPLEMENTED(); // Need to support FILE_NAME, FILE_CONTENTS + 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 == GtkDndUtil::GetAtomForTarget( + GtkDndUtil::CHROME_NAMED_URL) || + atom == GtkDndUtil::GetAtomForTarget( + GtkDndUtil::TEXT_URI_LIST)) { + types |= OSExchangeData::URL; + } else { + std::string target_name = GdkAtomToString(atom); + if (target_name == "text/html") { + types |= OSExchangeData::HTML; + } else 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(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(UTF16ToWideHack(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 == GtkDndUtil::GetAtomForTarget( + GtkDndUtil::CHROME_NAMED_URL)) { + GURL url; + string16 title; + GtkDndUtil::ExtractNamedURL(data, &url, &title); + data_provider().SetURL(url, UTF16ToWideHack(title)); + } else if (data->type == GtkDndUtil::GetAtomForTarget( + GtkDndUtil::TEXT_URI_LIST)) { + std::vector<GURL> urls; + GtkDndUtil::ExtractURIList(data, &urls); + if (urls.size() == 1) { + data_provider().SetURL(urls[0], std::wstring()); + } 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(), std::wstring()); + } + } else { + NOTIMPLEMENTED(); // Need to support FILE_NAME, FILE_CONTENTS, HTML. + } + + if (!data_->HasAllFormats(requested_formats_, requested_custom_formats_)) + return; // Waiting on more data. + + int drag_operation = 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>( + 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<WidgetGtk*>( + helper_.root_view()->GetWidget())->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 = DragDropTypes::GdkDragActionToDragOperation( + context->actions); + drag_operation = helper_.OnDragOver(*data_, root_view_location, + drag_operation); + if (!received_drop_) { + GdkDragAction gdk_action = + static_cast<GdkDragAction>( + 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 = DragDropTypes::GdkDragActionToDragOperation( + context->actions); + drag_operation = helper_.OnDrop(*data_, root_view_location, + drag_operation); + GdkDragAction gdk_action = + static_cast<GdkDragAction>( + DragDropTypes::DragOperationToGdkDragAction(drag_operation)); + gtk_drag_finish(context, gdk_action != 0, (gdk_action & GDK_ACTION_MOVE), + time); + + static_cast<WidgetGtk*>(helper_.root_view()->GetWidget())->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<WidgetGtk*>(helper_.root_view()->GetWidget())-> + 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_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); + } else if (known_formats.count(gdk_atom_intern("UTF8_STRING", false))) { + gtk_drag_get_data(widget, context, + gdk_atom_intern("UTF8_STRING", false), time); + } + } + if ((formats & OSExchangeData::URL) != 0 && + (requested_formats_ & OSExchangeData::URL) == 0) { + requested_formats_ |= OSExchangeData::URL; + if (known_formats.count( + GtkDndUtil::GetAtomForTarget(GtkDndUtil::CHROME_NAMED_URL))) { + gtk_drag_get_data(widget, context, + GtkDndUtil::GetAtomForTarget( + GtkDndUtil::CHROME_NAMED_URL), time); + } else if (known_formats.count( + GtkDndUtil::GetAtomForTarget(GtkDndUtil::TEXT_URI_LIST))) { + gtk_drag_get_data(widget, context, + GtkDndUtil::GetAtomForTarget( + GtkDndUtil::TEXT_URI_LIST), time); + } + } + if ((formats & OSExchangeData::FILE_CONTENTS) != 0 && + (requested_formats_ & OSExchangeData::FILE_CONTENTS) == 0) { + requested_formats_ |= OSExchangeData::FILE_CONTENTS; + NOTIMPLEMENTED(); + } + if ((formats & OSExchangeData::FILE_NAME != 0) && + (requested_formats_ & OSExchangeData::FILE_NAME) == 0) { + requested_formats_ |= OSExchangeData::FILE_NAME; + NOTIMPLEMENTED(); + } + if ((formats & OSExchangeData::HTML) != 0 && + (requested_formats_ & OSExchangeData::HTML) == 0) { + requested_formats_ |= OSExchangeData::HTML; + NOTIMPLEMENTED(); + } + 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..b4a5c56 --- /dev/null +++ b/views/widget/drop_target_gtk.h @@ -0,0 +1,113 @@ +// 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_DROP_TARGET_GTK_H_ +#define VIEWS_WIDGET_DROP_TARGET_GTK_H_ + +#include <gtk/gtk.h> +#include <set> + +#include "app/os_exchange_data.h" +#include "base/scoped_ptr.h" +#include "views/widget/drop_helper.h" + +class OSExchangeDataProviderGtk; + +namespace views { + +class RootView; +class View; + +// 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 WidgetGtk +// which results in deleting the DropTargetGtk. +class DropTargetGtk { + public: + explicit DropTargetGtk(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 WidgetGtk. + 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/widget_gtk.cc b/views/widget/widget_gtk.cc index 6534cca..d75ccf1 100644 --- a/views/widget/widget_gtk.cc +++ b/views/widget/widget_gtk.cc @@ -7,12 +7,44 @@ #include "app/gfx/path.h" #include "base/compiler_specific.h" #include "views/widget/default_theme_provider.h" +#include "views/widget/drop_target_gtk.h" #include "views/widget/root_view.h" #include "views/widget/tooltip_manager_gtk.h" #include "views/window/window_gtk.h" namespace views { +// 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 WidgetGtk::DropObserver : public MessageLoopForUI::Observer { + public: + DropObserver() { } + + virtual void WillProcessEvent(GdkEvent* event) { + if (event->type == GDK_DROP_START) { + WidgetGtk* widget = GetWidgetGtkForEvent(event); + if (widget) + widget->ignore_drag_leave_ = true; + } + } + + virtual void DidProcessEvent(GdkEvent* event) { + } + + private: + WidgetGtk* GetWidgetGtkForEvent(GdkEvent* event) { + GtkWidget* gtk_widget = gtk_get_event_widget(event); + if (!gtk_widget) + return NULL; + + return WidgetGtk::GetViewForNative(gtk_widget); + } + + DISALLOW_COPY_AND_ASSIGN(DropObserver); +}; + // Returns the position of a widget on screen. static void GetWidgetPositionOnScreen(GtkWidget* widget, int* x, int *y) { while (widget) { @@ -69,7 +101,16 @@ WidgetGtk::WidgetGtk(Type type) ALLOW_THIS_IN_INITIALIZER_LIST(close_widget_factory_(this)), delete_on_destroy_(true), transparent_(false), + ignore_drag_leave_(false), opacity_(255) { + 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(new DropObserver()); + } + if (type_ != TYPE_CHILD) focus_manager_.reset(new FocusManager(this)); } @@ -204,16 +245,17 @@ void WidgetGtk::Init(GtkWidget* parent, G_CALLBACK(CallWindowPaint), this); } - // TODO(erg): Ignore these signals for now because they're such a drag. - // - // g_signal_connect(G_OBJECT(widget_), "drag_motion", - // G_CALLBACK(drag_motion_event_cb), NULL); - // g_signal_connect(G_OBJECT(widget_), "drag_leave", - // G_CALLBACK(drag_leave_event_cb), NULL); - // g_signal_connect(G_OBJECT(widget_), "drag_drop", - // G_CALLBACK(drag_drop_event_cb), NULL); - // g_signal_connect(G_OBJECT(widget_), "drag_data_received", - // G_CALLBACK(drag_data_received_event_cb), NULL); + // Drag and drop. + gtk_drag_dest_set(window_contents_, static_cast<GtkDestDefaults>(0), + NULL, 0, GDK_ACTION_COPY); + g_signal_connect(G_OBJECT(window_contents_), "drag_motion", + G_CALLBACK(CallDragMotion), this); + g_signal_connect(G_OBJECT(window_contents_), "drag_data_received", + G_CALLBACK(CallDragDataReceived), this); + g_signal_connect(G_OBJECT(window_contents_), "drag_drop", + G_CALLBACK(CallDragDrop), this); + g_signal_connect(G_OBJECT(window_contents_), "drag_leave", + G_CALLBACK(CallDragLeave), this); tooltip_manager_.reset(new TooltipManagerGtk(this)); @@ -389,8 +431,8 @@ FocusManager* WidgetGtk::GetFocusManager() { void WidgetGtk::ViewHierarchyChanged(bool is_add, View *parent, View *child) { - // Needs dnd support (see WidgetWin::ViewHierarchyChanged). - NOTIMPLEMENTED(); + if (drop_target_.get()) + drop_target_->ResetTargetViewIfEquals(child); } //////////////////////////////////////////////////////////////////////////////// @@ -504,6 +546,47 @@ void WidgetGtk::OnPaint(GtkWidget* widget, GdkEventExpose* event) { root_view_->OnPaint(event); } +void WidgetGtk::OnDragDataReceived(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 WidgetGtk::OnDragDrop(GdkDragContext* context, + gint x, + gint y, + guint time) { + if (drop_target_.get()) { + return drop_target_->OnDragDrop(context, x, y, time); + } + return FALSE; +} + +void WidgetGtk::OnDragLeave(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 WidgetGtk::OnDragMotion(GdkDragContext* context, + gint x, + gint y, + guint time) { + if (!drop_target_.get()) + drop_target_.reset(new DropTargetGtk(GetRootView(), context)); + return drop_target_->OnDragMotion(context, x, y, time); +} + gboolean WidgetGtk::OnEnterNotify(GtkWidget* widget, GdkEventCrossing* event) { return false; } @@ -623,6 +706,11 @@ WidgetGtk* WidgetGtk::GetViewForNative(GtkWidget* widget) { return static_cast<WidgetGtk*>(user_data); } +void WidgetGtk::ResetDropTarget() { + ignore_drag_leave_ = false; + drop_target_.reset(NULL); +} + // static void WidgetGtk::SetViewForNative(GtkWidget* widget, WidgetGtk* view) { g_object_set_data(G_OBJECT(widget), "chrome-views", view); @@ -664,6 +752,46 @@ gboolean WidgetGtk::CallWindowPaint(GtkWidget* widget, return false; // False indicates other widgets should get the event as well. } +// static +void WidgetGtk::CallDragDataReceived(GtkWidget* widget, + GdkDragContext* context, + gint x, + gint y, + GtkSelectionData* data, + guint info, + guint time, + WidgetGtk* host) { + return host->OnDragDataReceived(context, x, y, data, info, time); +} + +// static +gboolean WidgetGtk::CallDragDrop(GtkWidget* widget, + GdkDragContext* context, + gint x, + gint y, + guint time, + WidgetGtk* host) { + return host->OnDragDrop(context, x, y, time); +} + +// static +void WidgetGtk::CallDragLeave(GtkWidget* widget, + GdkDragContext* context, + guint time, + WidgetGtk* host) { + host->OnDragLeave(context, time); +} + +// static +gboolean WidgetGtk::CallDragMotion(GtkWidget* widget, + GdkDragContext* context, + gint x, + gint y, + guint time, + WidgetGtk* host) { + return host->OnDragMotion(context, x, y, time); +} + gboolean WidgetGtk::CallEnterNotify(GtkWidget* widget, GdkEventCrossing* event) { WidgetGtk* widget_gtk = GetViewForNative(widget); if (!widget_gtk) diff --git a/views/widget/widget_gtk.h b/views/widget/widget_gtk.h index f8c6a20..f8bc57f 100644 --- a/views/widget/widget_gtk.h +++ b/views/widget/widget_gtk.h @@ -17,6 +17,7 @@ class Rect; namespace views { class DefaultThemeProvider; +class DropTargetGtk; class TooltipManagerGtk; class View; class WindowGtk; @@ -106,9 +107,29 @@ class WidgetGtk : public Widget, public MessageLoopForUI::Observer { // Retrieves the WindowGtk associated with |widget|. static WindowGtk* GetWindowForNative(GtkWidget* widget); + // Sets the drop target to NULL. This is invoked by DropTargetGTK when the + // drop is done. + void ResetDropTarget(); + protected: virtual void OnSizeAllocate(GtkWidget* widget, GtkAllocation* allocation); virtual void OnPaint(GtkWidget* widget, GdkEventExpose* event); + virtual void OnDragDataReceived(GdkDragContext* context, + gint x, + gint y, + GtkSelectionData* data, + guint info, + guint time); + virtual gboolean OnDragDrop(GdkDragContext* context, + gint x, + gint y, + guint time); + virtual void OnDragLeave(GdkDragContext* context, + guint time); + virtual gboolean OnDragMotion(GdkDragContext* context, + gint x, + gint y, + guint time); virtual gboolean OnEnterNotify(GtkWidget* widget, GdkEventCrossing* event); virtual gboolean OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event); virtual gboolean OnMotionNotify(GtkWidget* widget, GdkEventMotion* event); @@ -144,6 +165,9 @@ class WidgetGtk : public Widget, public MessageLoopForUI::Observer { bool is_window_; private: + class DropObserver; + friend class DropObserver; + virtual RootView* CreateRootView(); void OnWindowPaint(GtkWidget* widget, GdkEventExpose* event); @@ -166,6 +190,30 @@ class WidgetGtk : public Widget, public MessageLoopForUI::Observer { static gboolean CallWindowPaint(GtkWidget* widget, GdkEventExpose* event, WidgetGtk* widget_gtk); + static void CallDragDataReceived(GtkWidget* widget, + GdkDragContext* context, + gint x, + gint y, + GtkSelectionData* data, + guint info, + guint time, + WidgetGtk* host); + static gboolean CallDragDrop(GtkWidget* widget, + GdkDragContext* context, + gint x, + gint y, + guint time, + WidgetGtk* host); + static void CallDragLeave(GtkWidget* widget, + GdkDragContext* context, + guint time, + WidgetGtk* host); + static gboolean CallDragMotion(GtkWidget* widget, + GdkDragContext* context, + gint x, + gint y, + guint time, + WidgetGtk* host); static gboolean CallEnterNotify(GtkWidget* widget, GdkEventCrossing* event); static gboolean CallLeaveNotify(GtkWidget* widget, GdkEventCrossing* event); static gboolean CallMotionNotify(GtkWidget* widget, GdkEventMotion* event); @@ -216,6 +264,8 @@ class WidgetGtk : public Widget, public MessageLoopForUI::Observer { // must be destroyed AFTER root_view_. scoped_ptr<TooltipManagerGtk> tooltip_manager_; + scoped_ptr<DropTargetGtk> drop_target_; + // 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 @@ -253,6 +303,9 @@ class WidgetGtk : public Widget, public MessageLoopForUI::Observer { scoped_ptr<DefaultThemeProvider> default_theme_provider_; + // See note in DropObserver for details on this. + bool ignore_drag_leave_; + unsigned char opacity_; DISALLOW_COPY_AND_ASSIGN(WidgetGtk); |