web_drag_dest_gtk.cc revision 201ade2fbba22bfb27ae029f4d23fca6ded109a0
1// Copyright (c) 2009 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/tab_contents/web_drag_dest_gtk.h" 6 7#include <string> 8 9#include "app/gtk_dnd_util.h" 10#include "base/file_path.h" 11#include "base/utf_string_conversions.h" 12#include "chrome/browser/bookmarks/bookmark_node_data.h" 13#include "chrome/browser/gtk/bookmark_utils_gtk.h" 14#include "chrome/browser/gtk/gtk_util.h" 15#include "chrome/browser/renderer_host/render_view_host.h" 16#include "chrome/browser/tab_contents/tab_contents.h" 17#include "chrome/common/url_constants.h" 18#include "net/base/net_util.h" 19 20using WebKit::WebDragOperation; 21using WebKit::WebDragOperationNone; 22 23namespace { 24 25// Returns the bookmark target atom, based on the underlying toolkit. 26// 27// For GTK, bookmark drag data is encoded as pickle and associated with 28// gtk_dnd_util::CHROME_BOOKMARK_ITEM. See 29// bookmark_utils::WriteBookmarksToSelection() for details. 30// For Views, bookmark drag data is encoded in the same format, and 31// associated with a custom format. See BookmarkNodeData::Write() for 32// details. 33GdkAtom GetBookmarkTargetAtom() { 34#if defined(TOOLKIT_VIEWS) 35 return BookmarkNodeData::GetBookmarkCustomFormat(); 36#else 37 return gtk_dnd_util::GetAtomForTarget(gtk_dnd_util::CHROME_BOOKMARK_ITEM); 38#endif 39} 40 41} // namespace 42 43WebDragDestGtk::WebDragDestGtk(TabContents* tab_contents, GtkWidget* widget) 44 : tab_contents_(tab_contents), 45 widget_(widget), 46 context_(NULL), 47 method_factory_(this) { 48 gtk_drag_dest_set(widget, static_cast<GtkDestDefaults>(0), 49 NULL, 0, 50 static_cast<GdkDragAction>(GDK_ACTION_COPY | 51 GDK_ACTION_LINK | 52 GDK_ACTION_MOVE)); 53 g_signal_connect(widget, "drag-motion", 54 G_CALLBACK(OnDragMotionThunk), this); 55 g_signal_connect(widget, "drag-leave", 56 G_CALLBACK(OnDragLeaveThunk), this); 57 g_signal_connect(widget, "drag-drop", 58 G_CALLBACK(OnDragDropThunk), this); 59 g_signal_connect(widget, "drag-data-received", 60 G_CALLBACK(OnDragDataReceivedThunk), this); 61 // TODO(tony): Need a drag-data-delete handler for moving content out of 62 // the tab contents. http://crbug.com/38989 63 64 destroy_handler_ = g_signal_connect( 65 widget, "destroy", G_CALLBACK(gtk_widget_destroyed), &widget_); 66} 67 68WebDragDestGtk::~WebDragDestGtk() { 69 if (widget_) { 70 gtk_drag_dest_unset(widget_); 71 g_signal_handler_disconnect(widget_, destroy_handler_); 72 } 73} 74 75void WebDragDestGtk::UpdateDragStatus(WebDragOperation operation) { 76 if (context_) { 77 is_drop_target_ = operation != WebDragOperationNone; 78 gdk_drag_status(context_, gtk_util::WebDragOpToGdkDragAction(operation), 79 drag_over_time_); 80 } 81} 82 83void WebDragDestGtk::DragLeave() { 84 tab_contents_->render_view_host()->DragTargetDragLeave(); 85 86 if (tab_contents_->GetBookmarkDragDelegate()) { 87 tab_contents_->GetBookmarkDragDelegate()->OnDragLeave(bookmark_drag_data_); 88 } 89} 90 91gboolean WebDragDestGtk::OnDragMotion(GtkWidget* sender, 92 GdkDragContext* context, 93 gint x, gint y, 94 guint time) { 95 if (context_ != context) { 96 context_ = context; 97 drop_data_.reset(new WebDropData); 98 bookmark_drag_data_.Clear(); 99 is_drop_target_ = false; 100 101 // text/plain must come before text/uri-list. This is a hack that works in 102 // conjunction with OnDragDataReceived. Since some file managers populate 103 // text/plain with file URLs when dragging files, we want to handle 104 // text/uri-list after text/plain so that the plain text can be cleared if 105 // it's a file drag. 106 static int supported_targets[] = { 107 gtk_dnd_util::TEXT_PLAIN, 108 gtk_dnd_util::TEXT_URI_LIST, 109 gtk_dnd_util::TEXT_HTML, 110 gtk_dnd_util::NETSCAPE_URL, 111 gtk_dnd_util::CHROME_NAMED_URL, 112 // TODO(estade): support image drags? 113 }; 114 115 // Add the bookmark target as well. 116 data_requests_ = arraysize(supported_targets) + 1; 117 for (size_t i = 0; i < arraysize(supported_targets); ++i) { 118 gtk_drag_get_data(widget_, context, 119 gtk_dnd_util::GetAtomForTarget(supported_targets[i]), 120 time); 121 } 122 123 gtk_drag_get_data(widget_, context, GetBookmarkTargetAtom(), time); 124 } else if (data_requests_ == 0) { 125 tab_contents_->render_view_host()-> 126 DragTargetDragOver( 127 gtk_util::ClientPoint(widget_), 128 gtk_util::ScreenPoint(widget_), 129 gtk_util::GdkDragActionToWebDragOp(context->actions)); 130 if (tab_contents_->GetBookmarkDragDelegate()) 131 tab_contents_->GetBookmarkDragDelegate()->OnDragOver(bookmark_drag_data_); 132 drag_over_time_ = time; 133 } 134 135 // Pretend we are a drag destination because we don't want to wait for 136 // the renderer to tell us if we really are or not. 137 return TRUE; 138} 139 140void WebDragDestGtk::OnDragDataReceived( 141 GtkWidget* sender, GdkDragContext* context, gint x, gint y, 142 GtkSelectionData* data, guint info, guint time) { 143 // We might get the data from an old get_data() request that we no longer 144 // care about. 145 if (context != context_) 146 return; 147 148 data_requests_--; 149 150 // Decode the data. 151 if (data->data && data->length > 0) { 152 // If the source can't provide us with valid data for a requested target, 153 // data->data will be NULL. 154 if (data->target == 155 gtk_dnd_util::GetAtomForTarget(gtk_dnd_util::TEXT_PLAIN)) { 156 guchar* text = gtk_selection_data_get_text(data); 157 if (text) { 158 drop_data_->plain_text = 159 UTF8ToUTF16(std::string(reinterpret_cast<char*>(text), 160 data->length)); 161 g_free(text); 162 } 163 } else if (data->target == 164 gtk_dnd_util::GetAtomForTarget(gtk_dnd_util::TEXT_URI_LIST)) { 165 gchar** uris = gtk_selection_data_get_uris(data); 166 if (uris) { 167 drop_data_->url = GURL(); 168 for (gchar** uri_iter = uris; *uri_iter; uri_iter++) { 169 // Most file managers populate text/uri-list with file URLs when 170 // dragging files. To avoid exposing file system paths to web content, 171 // file URLs are never set as the URL content for the drop. 172 // TODO(estade): Can the filenames have a non-UTF8 encoding? 173 GURL url(*uri_iter); 174 FilePath file_path; 175 if (url.SchemeIs(chrome::kFileScheme) && 176 net::FileURLToFilePath(url, &file_path)) { 177 drop_data_->filenames.push_back(UTF8ToUTF16(file_path.value())); 178 // This is a hack. Some file managers also populate text/plain with 179 // a file URL when dragging files, so we clear it to avoid exposing 180 // it to the web content. 181 drop_data_->plain_text.clear(); 182 } else if (!drop_data_->url.is_valid()) { 183 // Also set the first non-file URL as the URL content for the drop. 184 drop_data_->url = url; 185 } 186 } 187 g_strfreev(uris); 188 } 189 } else if (data->target == 190 gtk_dnd_util::GetAtomForTarget(gtk_dnd_util::TEXT_HTML)) { 191 // TODO(estade): Can the html have a non-UTF8 encoding? 192 drop_data_->text_html = 193 UTF8ToUTF16(std::string(reinterpret_cast<char*>(data->data), 194 data->length)); 195 // We leave the base URL empty. 196 } else if (data->target == 197 gtk_dnd_util::GetAtomForTarget(gtk_dnd_util::NETSCAPE_URL)) { 198 std::string netscape_url(reinterpret_cast<char*>(data->data), 199 data->length); 200 size_t split = netscape_url.find_first_of('\n'); 201 if (split != std::string::npos) { 202 drop_data_->url = GURL(netscape_url.substr(0, split)); 203 if (split < netscape_url.size() - 1) 204 drop_data_->url_title = UTF8ToUTF16(netscape_url.substr(split + 1)); 205 } 206 } else if (data->target == 207 gtk_dnd_util::GetAtomForTarget(gtk_dnd_util::CHROME_NAMED_URL)) { 208 gtk_dnd_util::ExtractNamedURL(data, 209 &drop_data_->url, &drop_data_->url_title); 210 } 211 } 212 213 // For CHROME_BOOKMARK_ITEM, we have to handle the case where the drag source 214 // doesn't have any data available for us. In this case we try to synthesize a 215 // URL bookmark. 216 // Note that bookmark drag data is encoded in the same format for both 217 // GTK and Views, hence we can share the same logic here. 218 if (data->target == GetBookmarkTargetAtom()) { 219 if (data->data && data->length > 0) { 220 bookmark_drag_data_.ReadFromVector( 221 bookmark_utils::GetNodesFromSelection( 222 NULL, data, 223 gtk_dnd_util::CHROME_BOOKMARK_ITEM, 224 tab_contents_->profile(), NULL, NULL)); 225 bookmark_drag_data_.SetOriginatingProfile(tab_contents_->profile()); 226 } else { 227 bookmark_drag_data_.ReadFromTuple(drop_data_->url, 228 drop_data_->url_title); 229 } 230 } 231 232 if (data_requests_ == 0) { 233 // Tell the renderer about the drag. 234 // |x| and |y| are seemingly arbitrary at this point. 235 tab_contents_->render_view_host()-> 236 DragTargetDragEnter(*drop_data_.get(), 237 gtk_util::ClientPoint(widget_), 238 gtk_util::ScreenPoint(widget_), 239 gtk_util::GdkDragActionToWebDragOp(context->actions)); 240 241 // This is non-null if tab_contents_ is showing an ExtensionDOMUI with 242 // support for (at the moment experimental) drag and drop extensions. 243 if (tab_contents_->GetBookmarkDragDelegate()) { 244 tab_contents_->GetBookmarkDragDelegate()->OnDragEnter( 245 bookmark_drag_data_); 246 } 247 248 drag_over_time_ = time; 249 } 250} 251 252// The drag has left our widget; forward this information to the renderer. 253void WebDragDestGtk::OnDragLeave(GtkWidget* sender, GdkDragContext* context, 254 guint time) { 255 // Set |context_| to NULL to make sure we will recognize the next DragMotion 256 // as an enter. 257 context_ = NULL; 258 drop_data_.reset(); 259 // When GTK sends us a drag-drop signal, it is shortly (and synchronously) 260 // preceded by a drag-leave. The renderer doesn't like getting the signals 261 // in this order so delay telling it about the drag-leave till we are sure 262 // we are not getting a drop as well. 263 MessageLoop::current()->PostTask(FROM_HERE, 264 method_factory_.NewRunnableMethod(&WebDragDestGtk::DragLeave)); 265} 266 267// Called by GTK when the user releases the mouse, executing a drop. 268gboolean WebDragDestGtk::OnDragDrop(GtkWidget* sender, GdkDragContext* context, 269 gint x, gint y, guint time) { 270 // Cancel that drag leave! 271 method_factory_.RevokeAll(); 272 273 tab_contents_->render_view_host()-> 274 DragTargetDrop(gtk_util::ClientPoint(widget_), 275 gtk_util::ScreenPoint(widget_)); 276 277 // This is non-null if tab_contents_ is showing an ExtensionDOMUI with 278 // support for (at the moment experimental) drag and drop extensions. 279 if (tab_contents_->GetBookmarkDragDelegate()) 280 tab_contents_->GetBookmarkDragDelegate()->OnDrop(bookmark_drag_data_); 281 282 // The second parameter is just an educated guess as to whether or not the 283 // drag succeeded, but at least we will get the drag-end animation right 284 // sometimes. 285 gtk_drag_finish(context, is_drop_target_, FALSE, time); 286 return TRUE; 287} 288