blob: 763d120d094162272660c60632a192986f542ea4 [file] [log] [blame]
/*
* Copyright (C) 2011, Igalia S.L.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "config.h"
#include "GtkDragAndDropHelper.h"
#include "ClipboardUtilitiesGtk.h"
#include "DragData.h"
#include "GtkUtilities.h"
#include "GtkVersioning.h"
#include "PasteboardHelper.h"
#include <gtk/gtk.h>
#include <wtf/PassOwnPtr.h>
namespace WebCore {
struct DroppingContext {
DroppingContext(GdkDragContext* gdkContext, const IntPoint& position)
: gdkContext(gdkContext)
, dataObject(DataObjectGtk::create())
, lastMotionPosition(position)
, dropHappened(false)
, exitedCallback(0)
{
}
GdkDragContext* gdkContext;
RefPtr<WebCore::DataObjectGtk> dataObject;
WebCore::IntPoint lastMotionPosition;
int pendingDataRequests;
bool dropHappened;
DragExitedCallback exitedCallback;
};
typedef HashMap<GdkDragContext*, DroppingContext*> DroppingContextMap;
typedef HashMap<GdkDragContext*, RefPtr<DataObjectGtk> > DraggingDataObjectsMap;
GtkDragAndDropHelper::~GtkDragAndDropHelper()
{
deleteAllValues(m_droppingContexts);
}
bool GtkDragAndDropHelper::handleDragEnd(GdkDragContext* dragContext)
{
DraggingDataObjectsMap::iterator iterator = m_draggingDataObjects.find(dragContext);
if (iterator == m_draggingDataObjects.end())
return false;
m_draggingDataObjects.remove(iterator);
return true;
}
void GtkDragAndDropHelper::handleGetDragData(GdkDragContext* context, GtkSelectionData* selectionData, guint info)
{
DraggingDataObjectsMap::iterator iterator = m_draggingDataObjects.find(context);
if (iterator == m_draggingDataObjects.end())
return;
PasteboardHelper::defaultPasteboardHelper()->fillSelectionData(selectionData, info, iterator->second.get());
}
struct HandleDragLaterData {
DroppingContext* context;
GtkDragAndDropHelper* glue;
};
static gboolean handleDragLeaveLaterCallback(HandleDragLaterData* data)
{
data->glue->handleDragLeaveLater(data->context);
delete data;
return FALSE;
}
void GtkDragAndDropHelper::handleDragLeaveLater(DroppingContext* context)
{
DroppingContextMap::iterator iterator = m_droppingContexts.find(context->gdkContext);
if (iterator == m_droppingContexts.end())
return;
// If the view doesn't know about the drag yet (there are still pending data)
// requests, don't update it with information about the drag.
if (context->pendingDataRequests)
return;
const IntPoint& position = context->lastMotionPosition;
DragData dragData(context->dataObject.get(), position,
convertWidgetPointToScreenPoint(m_widget, position),
DragOperationNone);
context->exitedCallback(m_widget, &dragData, context->dropHappened);
m_droppingContexts.remove(iterator);
delete context;
}
void GtkDragAndDropHelper::handleDragLeave(GdkDragContext* gdkContext, DragExitedCallback exitedCallback)
{
DroppingContextMap::iterator iterator = m_droppingContexts.find(gdkContext);
if (iterator == m_droppingContexts.end())
return;
// During a drop GTK+ will fire a drag-leave signal right before firing
// the drag-drop signal. We want the actions for drag-leave to happen after
// those for drag-drop, so schedule them to happen asynchronously here.
HandleDragLaterData* data = new HandleDragLaterData;
data->context = iterator->second;
data->context->exitedCallback = exitedCallback;
data->glue = this;
g_timeout_add(0, reinterpret_cast<GSourceFunc>(handleDragLeaveLaterCallback), data);
}
static void queryNewDropContextData(DroppingContext* dropContext, GtkWidget* widget, guint time)
{
GdkDragContext* gdkContext = dropContext->gdkContext;
Vector<GdkAtom> acceptableTargets(PasteboardHelper::defaultPasteboardHelper()->dropAtomsForContext(widget, gdkContext));
dropContext->pendingDataRequests = acceptableTargets.size();
for (size_t i = 0; i < acceptableTargets.size(); i++)
gtk_drag_get_data(widget, gdkContext, acceptableTargets.at(i), time);
}
PassOwnPtr<DragData> GtkDragAndDropHelper::handleDragMotion(GdkDragContext* context, const IntPoint& position, unsigned time)
{
DroppingContext* droppingContext = 0;
DroppingContextMap::iterator iterator = m_droppingContexts.find(context);
if (iterator == m_droppingContexts.end()) {
droppingContext = new DroppingContext(context, position);
m_droppingContexts.set(context, droppingContext);
queryNewDropContextData(droppingContext, m_widget, time);
} else {
droppingContext = iterator->second;
droppingContext->lastMotionPosition = position;
}
// Don't send any drag information to WebCore until we've retrieved all
// the data for this drag operation. Otherwise we'd have to block to wait
// for the drag's data.
ASSERT(droppingContext);
if (droppingContext->pendingDataRequests > 0)
return adoptPtr(static_cast<DragData*>(0));
return adoptPtr(new DragData(droppingContext->dataObject.get(), position,
convertWidgetPointToScreenPoint(m_widget, position),
gdkDragActionToDragOperation(gdk_drag_context_get_actions(context))));
}
PassOwnPtr<DragData> GtkDragAndDropHelper::handleDragDataReceived(GdkDragContext* context, GtkSelectionData* selectionData, guint info)
{
DroppingContextMap::iterator iterator = m_droppingContexts.find(context);
if (iterator == m_droppingContexts.end())
return adoptPtr(static_cast<DragData*>(0));
DroppingContext* droppingContext = iterator->second;
droppingContext->pendingDataRequests--;
PasteboardHelper::defaultPasteboardHelper()->fillDataObjectFromDropData(selectionData, info, droppingContext->dataObject.get());
if (droppingContext->pendingDataRequests)
return adoptPtr(static_cast<DragData*>(0));
// The coordinates passed to drag-data-received signal are sometimes
// inaccurate in DRT, so use the coordinates of the last motion event.
const IntPoint& position = droppingContext->lastMotionPosition;
// If there are no more pending requests, start sending dragging data to WebCore.
return adoptPtr(new DragData(droppingContext->dataObject.get(), position,
convertWidgetPointToScreenPoint(m_widget, position),
gdkDragActionToDragOperation(gdk_drag_context_get_actions(context))));
}
PassOwnPtr<DragData> GtkDragAndDropHelper::handleDragDrop(GdkDragContext* context, const IntPoint& position)
{
DroppingContextMap::iterator iterator = m_droppingContexts.find(context);
if (iterator == m_droppingContexts.end())
return adoptPtr(static_cast<DragData*>(0));
DroppingContext* droppingContext = iterator->second;
droppingContext->dropHappened = true;
return adoptPtr(new DragData(droppingContext->dataObject.get(), position,
convertWidgetPointToScreenPoint(m_widget, position),
gdkDragActionToDragOperation(gdk_drag_context_get_actions(context))));
}
void GtkDragAndDropHelper::startedDrag(GdkDragContext* context, DataObjectGtk* dataObject)
{
m_draggingDataObjects.set(context, dataObject);
}
} // namespace WebCore