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