| OLD | NEW |
| (Empty) |
| 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/gtk/tab_contents_drag_source.h" | |
| 6 | |
| 7 #include <string> | |
| 8 | |
| 9 #include "app/gtk_dnd_util.h" | |
| 10 #include "base/file_util.h" | |
| 11 #include "base/mime_util.h" | |
| 12 #include "base/utf_string_conversions.h" | |
| 13 #include "chrome/browser/download/download_util.h" | |
| 14 #include "chrome/browser/download/drag_download_file.h" | |
| 15 #include "chrome/browser/download/drag_download_util.h" | |
| 16 #include "chrome/browser/gtk/gtk_util.h" | |
| 17 #include "chrome/browser/renderer_host/render_view_host.h" | |
| 18 #include "chrome/browser/renderer_host/render_view_host_delegate.h" | |
| 19 #include "chrome/browser/tab_contents/tab_contents.h" | |
| 20 #include "chrome/browser/tab_contents/tab_contents_view.h" | |
| 21 #include "gfx/gtk_util.h" | |
| 22 #include "net/base/file_stream.h" | |
| 23 #include "net/base/net_util.h" | |
| 24 #include "webkit/glue/webdropdata.h" | |
| 25 | |
| 26 using WebKit::WebDragOperation; | |
| 27 using WebKit::WebDragOperationsMask; | |
| 28 using WebKit::WebDragOperationNone; | |
| 29 | |
| 30 TabContentsDragSource::TabContentsDragSource( | |
| 31 TabContentsView* tab_contents_view) | |
| 32 : tab_contents_view_(tab_contents_view), | |
| 33 drag_pixbuf_(NULL), | |
| 34 drag_failed_(false), | |
| 35 drag_widget_(gtk_invisible_new()), | |
| 36 drag_context_(NULL), | |
| 37 drag_icon_(gtk_window_new(GTK_WINDOW_POPUP)) { | |
| 38 signals_.Connect(drag_widget_, "drag-failed", | |
| 39 G_CALLBACK(OnDragFailedThunk), this); | |
| 40 signals_.Connect(drag_widget_, "drag-begin", | |
| 41 G_CALLBACK(OnDragBeginThunk), | |
| 42 this); | |
| 43 signals_.Connect(drag_widget_, "drag-end", | |
| 44 G_CALLBACK(OnDragEndThunk), this); | |
| 45 signals_.Connect(drag_widget_, "drag-data-get", | |
| 46 G_CALLBACK(OnDragDataGetThunk), this); | |
| 47 | |
| 48 signals_.Connect(drag_icon_, "expose-event", | |
| 49 G_CALLBACK(OnDragIconExposeThunk), this); | |
| 50 } | |
| 51 | |
| 52 TabContentsDragSource::~TabContentsDragSource() { | |
| 53 // Break the current drag, if any. | |
| 54 if (drop_data_.get()) { | |
| 55 gtk_grab_add(drag_widget_); | |
| 56 gtk_grab_remove(drag_widget_); | |
| 57 MessageLoopForUI::current()->RemoveObserver(this); | |
| 58 drop_data_.reset(); | |
| 59 } | |
| 60 | |
| 61 gtk_widget_destroy(drag_widget_); | |
| 62 gtk_widget_destroy(drag_icon_); | |
| 63 } | |
| 64 | |
| 65 TabContents* TabContentsDragSource::tab_contents() const { | |
| 66 return tab_contents_view_->tab_contents(); | |
| 67 } | |
| 68 | |
| 69 void TabContentsDragSource::StartDragging(const WebDropData& drop_data, | |
| 70 WebDragOperationsMask allowed_ops, | |
| 71 GdkEventButton* last_mouse_down, | |
| 72 const SkBitmap& image, | |
| 73 const gfx::Point& image_offset) { | |
| 74 // Guard against re-starting before previous drag completed. | |
| 75 if (drag_context_) { | |
| 76 NOTREACHED(); | |
| 77 if (tab_contents()->render_view_host()) | |
| 78 tab_contents()->render_view_host()->DragSourceSystemDragEnded(); | |
| 79 return; | |
| 80 } | |
| 81 | |
| 82 int targets_mask = 0; | |
| 83 | |
| 84 if (!drop_data.plain_text.empty()) | |
| 85 targets_mask |= gtk_dnd_util::TEXT_PLAIN; | |
| 86 if (drop_data.url.is_valid()) { | |
| 87 targets_mask |= gtk_dnd_util::TEXT_URI_LIST; | |
| 88 targets_mask |= gtk_dnd_util::CHROME_NAMED_URL; | |
| 89 targets_mask |= gtk_dnd_util::NETSCAPE_URL; | |
| 90 } | |
| 91 if (!drop_data.text_html.empty()) | |
| 92 targets_mask |= gtk_dnd_util::TEXT_HTML; | |
| 93 if (!drop_data.file_contents.empty()) | |
| 94 targets_mask |= gtk_dnd_util::CHROME_WEBDROP_FILE_CONTENTS; | |
| 95 if (!drop_data.download_metadata.empty() && | |
| 96 drag_download_util::ParseDownloadMetadata(drop_data.download_metadata, | |
| 97 &wide_download_mime_type_, | |
| 98 &download_file_name_, | |
| 99 &download_url_)) { | |
| 100 targets_mask |= gtk_dnd_util::DIRECT_SAVE_FILE; | |
| 101 } | |
| 102 | |
| 103 // Short-circuit execution if no targets present. | |
| 104 if (targets_mask == 0) { | |
| 105 if (tab_contents()->render_view_host()) | |
| 106 tab_contents()->render_view_host()->DragSourceSystemDragEnded(); | |
| 107 return; | |
| 108 } | |
| 109 | |
| 110 drop_data_.reset(new WebDropData(drop_data)); | |
| 111 | |
| 112 // The image we get from WebKit makes heavy use of alpha-shading. This looks | |
| 113 // bad on non-compositing WMs. Fall back to the default drag icon. | |
| 114 if (!image.isNull() && gtk_util::IsScreenComposited()) | |
| 115 drag_pixbuf_ = gfx::GdkPixbufFromSkBitmap(&image); | |
| 116 image_offset_ = image_offset; | |
| 117 | |
| 118 GtkTargetList* list = gtk_dnd_util::GetTargetListFromCodeMask(targets_mask); | |
| 119 if (targets_mask & gtk_dnd_util::CHROME_WEBDROP_FILE_CONTENTS) { | |
| 120 drag_file_mime_type_ = gdk_atom_intern( | |
| 121 mime_util::GetDataMimeType(drop_data.file_contents).c_str(), FALSE); | |
| 122 gtk_target_list_add(list, drag_file_mime_type_, | |
| 123 0, gtk_dnd_util::CHROME_WEBDROP_FILE_CONTENTS); | |
| 124 } | |
| 125 | |
| 126 drag_failed_ = false; | |
| 127 // If we don't pass an event, GDK won't know what event time to start grabbing | |
| 128 // mouse events. Technically it's the mouse motion event and not the mouse | |
| 129 // down event that causes the drag, but there's no reliable way to know | |
| 130 // *which* motion event initiated the drag, so this will have to do. | |
| 131 // TODO(estade): This can sometimes be very far off, e.g. if the user clicks | |
| 132 // and holds and doesn't start dragging for a long time. I doubt it matters | |
| 133 // much, but we should probably look into the possibility of getting the | |
| 134 // initiating event from webkit. | |
| 135 drag_context_ = gtk_drag_begin(drag_widget_, list, | |
| 136 gtk_util::WebDragOpToGdkDragAction(allowed_ops), | |
| 137 1, // Drags are always initiated by the left button. | |
| 138 reinterpret_cast<GdkEvent*>(last_mouse_down)); | |
| 139 // The drag adds a ref; let it own the list. | |
| 140 gtk_target_list_unref(list); | |
| 141 | |
| 142 // Sometimes the drag fails to start; |context| will be NULL and we won't | |
| 143 // get a drag-end signal. | |
| 144 if (!drag_context_) { | |
| 145 drag_failed_ = true; | |
| 146 drop_data_.reset(); | |
| 147 if (tab_contents()->render_view_host()) | |
| 148 tab_contents()->render_view_host()->DragSourceSystemDragEnded(); | |
| 149 return; | |
| 150 } | |
| 151 | |
| 152 MessageLoopForUI::current()->AddObserver(this); | |
| 153 } | |
| 154 | |
| 155 void TabContentsDragSource::WillProcessEvent(GdkEvent* event) { | |
| 156 // No-op. | |
| 157 } | |
| 158 | |
| 159 void TabContentsDragSource::DidProcessEvent(GdkEvent* event) { | |
| 160 if (event->type != GDK_MOTION_NOTIFY) | |
| 161 return; | |
| 162 | |
| 163 GdkEventMotion* event_motion = reinterpret_cast<GdkEventMotion*>(event); | |
| 164 gfx::Point client = gtk_util::ClientPoint(GetContentNativeView()); | |
| 165 | |
| 166 if (tab_contents()->render_view_host()) { | |
| 167 tab_contents()->render_view_host()->DragSourceMovedTo( | |
| 168 client.x(), client.y(), | |
| 169 static_cast<int>(event_motion->x_root), | |
| 170 static_cast<int>(event_motion->y_root)); | |
| 171 } | |
| 172 } | |
| 173 | |
| 174 void TabContentsDragSource::OnDragDataGet(GtkWidget* sender, | |
| 175 GdkDragContext* context, GtkSelectionData* selection_data, | |
| 176 guint target_type, guint time) { | |
| 177 const int kBitsPerByte = 8; | |
| 178 | |
| 179 switch (target_type) { | |
| 180 case gtk_dnd_util::TEXT_PLAIN: { | |
| 181 std::string utf8_text = UTF16ToUTF8(drop_data_->plain_text); | |
| 182 gtk_selection_data_set_text(selection_data, utf8_text.c_str(), | |
| 183 utf8_text.length()); | |
| 184 break; | |
| 185 } | |
| 186 | |
| 187 case gtk_dnd_util::TEXT_HTML: { | |
| 188 // TODO(estade): change relative links to be absolute using | |
| 189 // |html_base_url|. | |
| 190 std::string utf8_text = UTF16ToUTF8(drop_data_->text_html); | |
| 191 gtk_selection_data_set(selection_data, | |
| 192 gtk_dnd_util::GetAtomForTarget( | |
| 193 gtk_dnd_util::TEXT_HTML), | |
| 194 kBitsPerByte, | |
| 195 reinterpret_cast<const guchar*>(utf8_text.c_str()), | |
| 196 utf8_text.length()); | |
| 197 break; | |
| 198 } | |
| 199 | |
| 200 case gtk_dnd_util::TEXT_URI_LIST: | |
| 201 case gtk_dnd_util::CHROME_NAMED_URL: | |
| 202 case gtk_dnd_util::NETSCAPE_URL: { | |
| 203 gtk_dnd_util::WriteURLWithName(selection_data, drop_data_->url, | |
| 204 drop_data_->url_title, target_type); | |
| 205 break; | |
| 206 } | |
| 207 | |
| 208 case gtk_dnd_util::CHROME_WEBDROP_FILE_CONTENTS: { | |
| 209 gtk_selection_data_set( | |
| 210 selection_data, | |
| 211 drag_file_mime_type_, kBitsPerByte, | |
| 212 reinterpret_cast<const guchar*>(drop_data_->file_contents.data()), | |
| 213 drop_data_->file_contents.length()); | |
| 214 break; | |
| 215 } | |
| 216 | |
| 217 case gtk_dnd_util::DIRECT_SAVE_FILE: { | |
| 218 char status_code = 'E'; | |
| 219 | |
| 220 // Retrieves the full file path (in file URL format) provided by the | |
| 221 // drop target by reading from the source window's XdndDirectSave0 | |
| 222 // property. | |
| 223 gint file_url_len = 0; | |
| 224 guchar* file_url_value = NULL; | |
| 225 if (gdk_property_get(context->source_window, | |
| 226 gtk_dnd_util::GetAtomForTarget( | |
| 227 gtk_dnd_util::DIRECT_SAVE_FILE), | |
| 228 gtk_dnd_util::GetAtomForTarget( | |
| 229 gtk_dnd_util::TEXT_PLAIN_NO_CHARSET), | |
| 230 0, | |
| 231 1024, | |
| 232 FALSE, | |
| 233 NULL, | |
| 234 NULL, | |
| 235 &file_url_len, | |
| 236 &file_url_value) && | |
| 237 file_url_value) { | |
| 238 // Convert from the file url to the file path. | |
| 239 GURL file_url(std::string(reinterpret_cast<char*>(file_url_value), | |
| 240 file_url_len)); | |
| 241 g_free(file_url_value); | |
| 242 FilePath file_path; | |
| 243 if (net::FileURLToFilePath(file_url, &file_path)) { | |
| 244 // Open the file as a stream. | |
| 245 net::FileStream* file_stream = | |
| 246 drag_download_util::CreateFileStreamForDrop(&file_path); | |
| 247 if (file_stream) { | |
| 248 // Start downloading the file to the stream. | |
| 249 TabContents* tab_contents = tab_contents_view_->tab_contents(); | |
| 250 scoped_refptr<DragDownloadFile> drag_file_downloader = | |
| 251 new DragDownloadFile(file_path, | |
| 252 linked_ptr<net::FileStream>(file_stream), | |
| 253 download_url_, | |
| 254 tab_contents->GetURL(), | |
| 255 tab_contents->encoding(), | |
| 256 tab_contents); | |
| 257 drag_file_downloader->Start( | |
| 258 new drag_download_util::PromiseFileFinalizer( | |
| 259 drag_file_downloader)); | |
| 260 | |
| 261 // Set the status code to success. | |
| 262 status_code = 'S'; | |
| 263 } | |
| 264 } | |
| 265 | |
| 266 // Return the status code to the file manager. | |
| 267 gtk_selection_data_set(selection_data, | |
| 268 selection_data->target, | |
| 269 8, | |
| 270 reinterpret_cast<guchar*>(&status_code), | |
| 271 1); | |
| 272 } | |
| 273 break; | |
| 274 } | |
| 275 | |
| 276 default: | |
| 277 NOTREACHED(); | |
| 278 } | |
| 279 } | |
| 280 | |
| 281 gboolean TabContentsDragSource::OnDragFailed(GtkWidget* sender, | |
| 282 GdkDragContext* context, | |
| 283 GtkDragResult result) { | |
| 284 drag_failed_ = true; | |
| 285 | |
| 286 gfx::Point root = gtk_util::ScreenPoint(GetContentNativeView()); | |
| 287 gfx::Point client = gtk_util::ClientPoint(GetContentNativeView()); | |
| 288 | |
| 289 if (tab_contents()->render_view_host()) { | |
| 290 tab_contents()->render_view_host()->DragSourceEndedAt( | |
| 291 client.x(), client.y(), root.x(), root.y(), | |
| 292 WebDragOperationNone); | |
| 293 } | |
| 294 | |
| 295 // Let the native failure animation run. | |
| 296 return FALSE; | |
| 297 } | |
| 298 | |
| 299 void TabContentsDragSource::OnDragBegin(GtkWidget* sender, | |
| 300 GdkDragContext* drag_context) { | |
| 301 if (!download_url_.is_empty()) { | |
| 302 // Generate the file name based on both mime type and proposed file name. | |
| 303 std::string download_mime_type = UTF16ToUTF8(wide_download_mime_type_); | |
| 304 std::string content_disposition("attachment; filename="); | |
| 305 content_disposition += download_file_name_.value(); | |
| 306 FilePath generated_download_file_name; | |
| 307 download_util::GenerateFileName(download_url_, | |
| 308 content_disposition, | |
| 309 std::string(), | |
| 310 download_mime_type, | |
| 311 &generated_download_file_name); | |
| 312 | |
| 313 // Pass the file name to the drop target by setting the source window's | |
| 314 // XdndDirectSave0 property. | |
| 315 gdk_property_change(drag_context->source_window, | |
| 316 gtk_dnd_util::GetAtomForTarget( | |
| 317 gtk_dnd_util::DIRECT_SAVE_FILE), | |
| 318 gtk_dnd_util::GetAtomForTarget( | |
| 319 gtk_dnd_util::TEXT_PLAIN_NO_CHARSET), | |
| 320 8, | |
| 321 GDK_PROP_MODE_REPLACE, | |
| 322 reinterpret_cast<const guchar*>( | |
| 323 generated_download_file_name.value().c_str()), | |
| 324 generated_download_file_name.value().length()); | |
| 325 } | |
| 326 | |
| 327 if (drag_pixbuf_) { | |
| 328 gtk_widget_set_size_request(drag_icon_, | |
| 329 gdk_pixbuf_get_width(drag_pixbuf_), | |
| 330 gdk_pixbuf_get_height(drag_pixbuf_)); | |
| 331 | |
| 332 // We only need to do this once. | |
| 333 if (!GTK_WIDGET_REALIZED(drag_icon_)) { | |
| 334 GdkScreen* screen = gtk_widget_get_screen(drag_icon_); | |
| 335 GdkColormap* rgba = gdk_screen_get_rgba_colormap(screen); | |
| 336 if (rgba) | |
| 337 gtk_widget_set_colormap(drag_icon_, rgba); | |
| 338 } | |
| 339 | |
| 340 gtk_drag_set_icon_widget(drag_context, drag_icon_, | |
| 341 image_offset_.x(), image_offset_.y()); | |
| 342 } | |
| 343 } | |
| 344 | |
| 345 void TabContentsDragSource::OnDragEnd(GtkWidget* sender, | |
| 346 GdkDragContext* drag_context) { | |
| 347 if (drag_pixbuf_) { | |
| 348 g_object_unref(drag_pixbuf_); | |
| 349 drag_pixbuf_ = NULL; | |
| 350 } | |
| 351 | |
| 352 MessageLoopForUI::current()->RemoveObserver(this); | |
| 353 | |
| 354 if (!download_url_.is_empty()) { | |
| 355 gdk_property_delete(drag_context->source_window, | |
| 356 gtk_dnd_util::GetAtomForTarget( | |
| 357 gtk_dnd_util::DIRECT_SAVE_FILE)); | |
| 358 } | |
| 359 | |
| 360 if (!drag_failed_) { | |
| 361 gfx::Point root = gtk_util::ScreenPoint(GetContentNativeView()); | |
| 362 gfx::Point client = gtk_util::ClientPoint(GetContentNativeView()); | |
| 363 | |
| 364 if (tab_contents()->render_view_host()) { | |
| 365 tab_contents()->render_view_host()->DragSourceEndedAt( | |
| 366 client.x(), client.y(), root.x(), root.y(), | |
| 367 gtk_util::GdkDragActionToWebDragOp(drag_context->action)); | |
| 368 } | |
| 369 } | |
| 370 | |
| 371 if (tab_contents()->render_view_host()) | |
| 372 tab_contents()->render_view_host()->DragSourceSystemDragEnded(); | |
| 373 | |
| 374 drop_data_.reset(); | |
| 375 drag_context_ = NULL; | |
| 376 } | |
| 377 | |
| 378 gfx::NativeView TabContentsDragSource::GetContentNativeView() const { | |
| 379 return tab_contents_view_->GetContentNativeView(); | |
| 380 } | |
| 381 | |
| 382 gboolean TabContentsDragSource::OnDragIconExpose(GtkWidget* sender, | |
| 383 GdkEventExpose* event) { | |
| 384 cairo_t* cr = gdk_cairo_create(event->window); | |
| 385 gdk_cairo_rectangle(cr, &event->area); | |
| 386 cairo_clip(cr); | |
| 387 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); | |
| 388 gdk_cairo_set_source_pixbuf(cr, drag_pixbuf_, 0, 0); | |
| 389 cairo_paint(cr); | |
| 390 cairo_destroy(cr); | |
| 391 | |
| 392 return TRUE; | |
| 393 } | |
| OLD | NEW |