// 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 "chrome/browser/gtk/tab_contents_drag_source.h" #include "app/gtk_dnd_util.h" #include "base/mime_util.h" #include "base/string_util.h" #include "chrome/browser/renderer_host/render_view_host_delegate.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/browser/tab_contents/tab_contents_view.h" #include "chrome/common/gtk_util.h" #include "webkit/glue/webdropdata.h" using WebKit::WebDragOperation; using WebKit::WebDragOperationNone; TabContentsDragSource::TabContentsDragSource( TabContentsView* tab_contents_view) : tab_contents_view_(tab_contents_view), drag_failed_(false), drag_widget_(NULL) { drag_widget_ = gtk_invisible_new(); g_signal_connect(drag_widget_, "drag-failed", G_CALLBACK(OnDragFailedThunk), this); g_signal_connect(drag_widget_, "drag-end", G_CALLBACK(OnDragEndThunk), this); g_signal_connect(drag_widget_, "drag-data-get", G_CALLBACK(OnDragDataGetThunk), this); g_object_ref_sink(drag_widget_); } TabContentsDragSource::~TabContentsDragSource() { g_signal_handlers_disconnect_by_func(drag_widget_, reinterpret_cast(OnDragFailedThunk), this); g_signal_handlers_disconnect_by_func(drag_widget_, reinterpret_cast(OnDragEndThunk), this); g_signal_handlers_disconnect_by_func(drag_widget_, reinterpret_cast(OnDragDataGetThunk), this); // Break the current drag, if any. if (drop_data_.get()) { gtk_grab_add(drag_widget_); gtk_grab_remove(drag_widget_); MessageLoopForUI::current()->RemoveObserver(this); drop_data_.reset(); } gtk_widget_destroy(drag_widget_); g_object_unref(drag_widget_); drag_widget_ = NULL; } TabContents* TabContentsDragSource::tab_contents() const { return tab_contents_view_->tab_contents(); } void TabContentsDragSource::StartDragging(const WebDropData& drop_data, GdkEventButton* last_mouse_down) { int targets_mask = 0; if (!drop_data.plain_text.empty()) targets_mask |= GtkDndUtil::TEXT_PLAIN; if (drop_data.url.is_valid()) { targets_mask |= GtkDndUtil::TEXT_URI_LIST; targets_mask |= GtkDndUtil::CHROME_NAMED_URL; } if (!drop_data.text_html.empty()) targets_mask |= GtkDndUtil::TEXT_HTML; if (!drop_data.file_contents.empty()) targets_mask |= GtkDndUtil::CHROME_WEBDROP_FILE_CONTENTS; if (targets_mask == 0) { NOTIMPLEMENTED(); if (tab_contents()->render_view_host()) tab_contents()->render_view_host()->DragSourceSystemDragEnded(); } drop_data_.reset(new WebDropData(drop_data)); GtkTargetList* list = GtkDndUtil::GetTargetListFromCodeMask(targets_mask); if (targets_mask & GtkDndUtil::CHROME_WEBDROP_FILE_CONTENTS) { drag_file_mime_type_ = gdk_atom_intern( mime_util::GetDataMimeType(drop_data.file_contents).c_str(), FALSE); gtk_target_list_add(list, drag_file_mime_type_, 0, GtkDndUtil::CHROME_WEBDROP_FILE_CONTENTS); } drag_failed_ = false; // If we don't pass an event, GDK won't know what event time to start grabbing // mouse events. Technically it's the mouse motion event and not the mouse // down event that causes the drag, but there's no reliable way to know // *which* motion event initiated the drag, so this will have to do. // TODO(estade): This can sometimes be very far off, e.g. if the user clicks // and holds and doesn't start dragging for a long time. I doubt it matters // much, but we should probably look into the possibility of getting the // initiating event from webkit. gtk_drag_begin(drag_widget_, list, GDK_ACTION_COPY, 1, // Drags are always initiated by the left button. reinterpret_cast(last_mouse_down)); MessageLoopForUI::current()->AddObserver(this); // The drag adds a ref; let it own the list. gtk_target_list_unref(list); } void TabContentsDragSource::WillProcessEvent(GdkEvent* event) { // No-op. } void TabContentsDragSource::DidProcessEvent(GdkEvent* event) { if (event->type != GDK_MOTION_NOTIFY) return; GdkEventMotion* event_motion = reinterpret_cast(event); gfx::Point client = gtk_util::ClientPoint(GetContentNativeView()); if (tab_contents()->render_view_host()) { tab_contents()->render_view_host()->DragSourceMovedTo( client.x(), client.y(), static_cast(event_motion->x_root), static_cast(event_motion->y_root)); } } void TabContentsDragSource::OnDragDataGet( GdkDragContext* context, GtkSelectionData* selection_data, guint target_type, guint time) { const int bits_per_byte = 8; switch (target_type) { case GtkDndUtil::TEXT_PLAIN: { std::string utf8_text = UTF16ToUTF8(drop_data_->plain_text); gtk_selection_data_set_text(selection_data, utf8_text.c_str(), utf8_text.length()); break; } case GtkDndUtil::TEXT_HTML: { // TODO(estade): change relative links to be absolute using // |html_base_url|. std::string utf8_text = UTF16ToUTF8(drop_data_->text_html); gtk_selection_data_set(selection_data, GtkDndUtil::GetAtomForTarget( GtkDndUtil::TEXT_HTML), bits_per_byte, reinterpret_cast(utf8_text.c_str()), utf8_text.length()); break; } case GtkDndUtil::TEXT_URI_LIST: case GtkDndUtil::CHROME_NAMED_URL: { GtkDndUtil::WriteURLWithName(selection_data, drop_data_->url, drop_data_->url_title, target_type); break; } case GtkDndUtil::CHROME_WEBDROP_FILE_CONTENTS: { gtk_selection_data_set( selection_data, drag_file_mime_type_, bits_per_byte, reinterpret_cast(drop_data_->file_contents.data()), drop_data_->file_contents.length()); break; } default: NOTREACHED(); } } gboolean TabContentsDragSource::OnDragFailed() { drag_failed_ = true; gfx::Point root = gtk_util::ScreenPoint(GetContentNativeView()); gfx::Point client = gtk_util::ClientPoint(GetContentNativeView()); if (tab_contents()->render_view_host()) { tab_contents()->render_view_host()->DragSourceEndedAt( client.x(), client.y(), root.x(), root.y(), WebDragOperationNone); } // Let the native failure animation run. return FALSE; } void TabContentsDragSource::OnDragEnd(WebDragOperation operation) { MessageLoopForUI::current()->RemoveObserver(this); if (!drag_failed_) { gfx::Point root = gtk_util::ScreenPoint(GetContentNativeView()); gfx::Point client = gtk_util::ClientPoint(GetContentNativeView()); if (tab_contents()->render_view_host()) { tab_contents()->render_view_host()->DragSourceEndedAt( client.x(), client.y(), root.x(), root.y(), operation); } } if (tab_contents()->render_view_host()) tab_contents()->render_view_host()->DragSourceSystemDragEnded(); drop_data_.reset(); } gfx::NativeView TabContentsDragSource::GetContentNativeView() const { return tab_contents_view_->GetContentNativeView(); }