summaryrefslogtreecommitdiffstats
path: root/views
diff options
context:
space:
mode:
authorsky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-19 03:33:44 +0000
committersky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-19 03:33:44 +0000
commit134c47b9e74ab7aa6dce7e1beeaf5e406a925b76 (patch)
treef4db8cfc40f3eb8a66421ee415f575cf6a76d638 /views
parentcf5811e922d5aa215a4094bd88e70c38c6ddc57c (diff)
downloadchromium_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.cc11
-rw-r--r--views/view.h28
-rw-r--r--views/views.gyp2
-rw-r--r--views/widget/drop_helper.cc20
-rw-r--r--views/widget/drop_target_gtk.cc329
-rw-r--r--views/widget/drop_target_gtk.h113
-rw-r--r--views/widget/widget_gtk.cc152
-rw-r--r--views/widget/widget_gtk.h53
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);