/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the QtGui module of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "qapplication.h" | |
#ifndef QT_NO_DRAGANDDROP | |
#include "qwidget.h" | |
#include "qdatetime.h" | |
#include "qbitmap.h" | |
#include "qcursor.h" | |
#include "qevent.h" | |
#include "qpainter.h" | |
#include "qdnd_p.h" | |
QT_BEGIN_NAMESPACE | |
QT_USE_NAMESPACE | |
static QPixmap *defaultPm = 0; | |
static const int default_pm_hotx = -2; | |
static const int default_pm_hoty = -16; | |
static const char *const default_pm[] = { | |
"13 9 3 1", | |
". c None", | |
" c #000000", | |
"X c #FFFFFF", | |
"X X X X X X X", | |
" X X X X X X ", | |
"X ......... X", | |
" X.........X ", | |
"X ......... X", | |
" X.........X ", | |
"X ......... X", | |
" X X X X X X ", | |
"X X X X X X X", | |
}; | |
// Shift/Ctrl handling, and final drop status | |
static Qt::DropAction global_accepted_action = Qt::CopyAction; | |
static Qt::DropActions possible_actions = Qt::IgnoreAction; | |
// static variables in place of a proper cross-process solution | |
static QDrag *drag_object; | |
static bool qt_qws_dnd_dragging = false; | |
static Qt::KeyboardModifiers oldstate; | |
class QShapedPixmapWidget : public QWidget { | |
QPixmap pixmap; | |
public: | |
QShapedPixmapWidget() : | |
QWidget(0, Qt::Tool | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint) | |
{ | |
// ### Temporary workaround for 4.2-rc1!!! To prevent flickering when | |
// using drag'n drop in a client application. (task 126956) | |
// setAttribute() should be done unconditionally! | |
if (QApplication::type() == QApplication::GuiServer) | |
setAttribute(Qt::WA_TransparentForMouseEvents); | |
} | |
void setPixmap(QPixmap pm) | |
{ | |
pixmap = pm; | |
if (!pixmap.mask().isNull()) { | |
setMask(pixmap.mask()); | |
} else { | |
clearMask(); | |
} | |
resize(pm.width(),pm.height()); | |
} | |
void paintEvent(QPaintEvent*) | |
{ | |
QPainter p(this); | |
p.drawPixmap(0,0,pixmap); | |
} | |
}; | |
static QShapedPixmapWidget *qt_qws_dnd_deco = 0; | |
void QDragManager::updatePixmap() | |
{ | |
if (qt_qws_dnd_deco) { | |
QPixmap pm; | |
QPoint pm_hot(default_pm_hotx,default_pm_hoty); | |
if (drag_object) { | |
pm = drag_object->pixmap(); | |
if (!pm.isNull()) | |
pm_hot = drag_object->hotSpot(); | |
} | |
if (pm.isNull()) { | |
if (!defaultPm) | |
defaultPm = new QPixmap(default_pm); | |
pm = *defaultPm; | |
} | |
qt_qws_dnd_deco->setPixmap(pm); | |
qt_qws_dnd_deco->move(QCursor::pos()-pm_hot); | |
if (willDrop) { | |
qt_qws_dnd_deco->show(); | |
} else { | |
qt_qws_dnd_deco->hide(); | |
} | |
} | |
} | |
void QDragManager::timerEvent(QTimerEvent *) { } | |
void QDragManager::move(const QPoint &) { } | |
void QDragManager::updateCursor() | |
{ | |
#ifndef QT_NO_CURSOR | |
if (willDrop) { | |
if (qt_qws_dnd_deco) | |
qt_qws_dnd_deco->show(); | |
if (currentActionForOverrideCursor != global_accepted_action) { | |
QApplication::changeOverrideCursor(QCursor(dragCursor(global_accepted_action), 0, 0)); | |
currentActionForOverrideCursor = global_accepted_action; | |
} | |
} else { | |
QCursor *overrideCursor = QApplication::overrideCursor(); | |
if (!overrideCursor || overrideCursor->shape() != Qt::ForbiddenCursor) { | |
QApplication::changeOverrideCursor(QCursor(Qt::ForbiddenCursor)); | |
currentActionForOverrideCursor = Qt::IgnoreAction; | |
} | |
if (qt_qws_dnd_deco) | |
qt_qws_dnd_deco->hide(); | |
} | |
#endif | |
} | |
bool QDragManager::eventFilter(QObject *o, QEvent *e) | |
{ | |
if (beingCancelled) { | |
if (e->type() == QEvent::KeyRelease && static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape) { | |
qApp->removeEventFilter(this); | |
Q_ASSERT(object == 0); | |
beingCancelled = false; | |
eventLoop->exit(); | |
return true; // block the key release | |
} | |
return false; | |
} | |
if (!o->isWidgetType()) | |
return false; | |
switch(e->type()) { | |
case QEvent::ShortcutOverride: | |
// prevent accelerators from firing while dragging | |
e->accept(); | |
return true; | |
case QEvent::KeyPress: | |
case QEvent::KeyRelease: | |
{ | |
QKeyEvent *ke = ((QKeyEvent*)e); | |
if (ke->key() == Qt::Key_Escape && e->type() == QEvent::KeyPress) { | |
cancel(); | |
qApp->removeEventFilter(this); | |
beingCancelled = false; | |
eventLoop->exit(); | |
} else { | |
updateCursor(); | |
} | |
return true; // Eat all key events | |
} | |
case QEvent::MouseButtonPress: | |
case QEvent::MouseMove: | |
{ | |
if (!object) { //#### this should not happen | |
qWarning("QDragManager::eventFilter: No object"); | |
return true; | |
} | |
QDragManager *manager = QDragManager::self(); | |
QMimeData *dropData = manager->object ? manager->dragPrivate()->data : manager->dropData; | |
if (manager->object) | |
possible_actions = manager->dragPrivate()->possible_actions; | |
else | |
possible_actions = Qt::IgnoreAction; | |
QMouseEvent *me = (QMouseEvent *)e; | |
if (me->buttons()) { | |
Qt::DropAction prevAction = global_accepted_action; | |
QWidget *cw = QApplication::widgetAt(me->globalPos()); | |
// Fix for when we move mouse on to the deco widget | |
if (qt_qws_dnd_deco && cw == qt_qws_dnd_deco) | |
cw = object->target(); | |
while (cw && !cw->acceptDrops() && !cw->isWindow()) | |
cw = cw->parentWidget(); | |
if (object->target() != cw) { | |
if (object->target()) { | |
QDragLeaveEvent dle; | |
QApplication::sendEvent(object->target(), &dle); | |
willDrop = false; | |
global_accepted_action = Qt::IgnoreAction; | |
updateCursor(); | |
restoreCursor = true; | |
object->d_func()->target = 0; | |
} | |
if (cw && cw->acceptDrops()) { | |
object->d_func()->target = cw; | |
QDragEnterEvent dee(cw->mapFromGlobal(me->globalPos()), possible_actions, dropData, | |
me->buttons(), me->modifiers()); | |
QApplication::sendEvent(object->target(), &dee); | |
willDrop = dee.isAccepted() && dee.dropAction() != Qt::IgnoreAction; | |
global_accepted_action = willDrop ? dee.dropAction() : Qt::IgnoreAction; | |
updateCursor(); | |
restoreCursor = true; | |
} | |
} else if (cw) { | |
QDragMoveEvent dme(cw->mapFromGlobal(me->globalPos()), possible_actions, dropData, | |
me->buttons(), me->modifiers()); | |
if (global_accepted_action != Qt::IgnoreAction) { | |
dme.setDropAction(global_accepted_action); | |
dme.accept(); | |
} | |
QApplication::sendEvent(cw, &dme); | |
willDrop = dme.isAccepted(); | |
global_accepted_action = willDrop ? dme.dropAction() : Qt::IgnoreAction; | |
updatePixmap(); | |
updateCursor(); | |
} | |
if (global_accepted_action != prevAction) | |
emitActionChanged(global_accepted_action); | |
} | |
return true; // Eat all mouse events | |
} | |
case QEvent::MouseButtonRelease: | |
{ | |
qApp->removeEventFilter(this); | |
if (restoreCursor) { | |
willDrop = false; | |
#ifndef QT_NO_CURSOR | |
QApplication::restoreOverrideCursor(); | |
#endif | |
restoreCursor = false; | |
} | |
if (object && object->target()) { | |
QMouseEvent *me = (QMouseEvent *)e; | |
QDragManager *manager = QDragManager::self(); | |
QMimeData *dropData = manager->object ? manager->dragPrivate()->data : manager->dropData; | |
QDropEvent de(object->target()->mapFromGlobal(me->globalPos()), possible_actions, dropData, | |
me->buttons(), me->modifiers()); | |
QApplication::sendEvent(object->target(), &de); | |
if (de.isAccepted()) | |
global_accepted_action = de.dropAction(); | |
else | |
global_accepted_action = Qt::IgnoreAction; | |
if (object) | |
object->deleteLater(); | |
drag_object = object = 0; | |
} | |
eventLoop->exit(); | |
return true; // Eat all mouse events | |
} | |
default: | |
break; | |
} | |
return false; | |
} | |
Qt::DropAction QDragManager::drag(QDrag *o) | |
{ | |
if (object == o || !o || !o->source()) | |
return Qt::IgnoreAction; | |
if (object) { | |
cancel(); | |
qApp->removeEventFilter(this); | |
beingCancelled = false; | |
} | |
object = drag_object = o; | |
qt_qws_dnd_deco = new QShapedPixmapWidget(); | |
oldstate = Qt::NoModifier; // #### Should use state that caused the drag | |
// drag_mode = mode; | |
willDrop = false; | |
updatePixmap(); | |
updateCursor(); | |
restoreCursor = true; | |
object->d_func()->target = 0; | |
qApp->installEventFilter(this); | |
global_accepted_action = Qt::CopyAction; | |
#ifndef QT_NO_CURSOR | |
qApp->setOverrideCursor(Qt::ArrowCursor); | |
restoreCursor = true; | |
updateCursor(); | |
#endif | |
qt_qws_dnd_dragging = true; | |
eventLoop = new QEventLoop; | |
(void) eventLoop->exec(); | |
delete eventLoop; | |
eventLoop = 0; | |
delete qt_qws_dnd_deco; | |
qt_qws_dnd_deco = 0; | |
qt_qws_dnd_dragging = false; | |
return global_accepted_action; | |
} | |
void QDragManager::cancel(bool deleteSource) | |
{ | |
// qDebug("QDragManager::cancel"); | |
beingCancelled = true; | |
if (object->target()) { | |
QDragLeaveEvent dle; | |
QApplication::sendEvent(object->target(), &dle); | |
} | |
#ifndef QT_NO_CURSOR | |
if (restoreCursor) { | |
QApplication::restoreOverrideCursor(); | |
restoreCursor = false; | |
} | |
#endif | |
if (drag_object) { | |
if (deleteSource) | |
object->deleteLater(); | |
drag_object = object = 0; | |
} | |
delete qt_qws_dnd_deco; | |
qt_qws_dnd_deco = 0; | |
global_accepted_action = Qt::IgnoreAction; | |
} | |
void QDragManager::drop() | |
{ | |
} | |
QVariant QDropData::retrieveData_sys(const QString &mimetype, QVariant::Type type) const | |
{ | |
if (!drag_object) | |
return QVariant(); | |
QByteArray data = drag_object->mimeData()->data(mimetype); | |
if (type == QVariant::String) | |
return QString::fromUtf8(data); | |
return data; | |
} | |
bool QDropData::hasFormat_sys(const QString &format) const | |
{ | |
return formats().contains(format); | |
} | |
QStringList QDropData::formats_sys() const | |
{ | |
if (drag_object) | |
return drag_object->mimeData()->formats(); | |
return QStringList(); | |
} | |
#endif // QT_NO_DRAGANDDROP | |
QT_END_NAMESPACE |