Simplify transaction expiry mechanism

This patch makes transaction mechanism less scattered around and
conforms to the xdnd specification:

Don't block and keep a history of previous data. This can be very difficult to implement,
but it is clearly the ideal behavior from the user's perspective because it allows him to
drop something and then continue working with the assurance that the target will get the
data regardless of how slow the network connections are.

When the source receives XdndFinished, it can remove the item from its history, thereby keeping
it from getting too large. The source must also be prepared to throw out extremely old data
in case a target malfunctions.

I assume that 10min for drag-and-drop operation can be considered 'extremely' old data.

Change-Id: I73dcd21aee3ad188d2260e49d80824da6ba040ab
Task-numer: QTBUG-14493
Reviewed-by: David Faure (fixes for KDE) <faure@kde.org>
Reviewed-by: Samuel Rødal <samuel.rodal@digia.com>
This commit is contained in:
Gatis Paeglis 2012-10-01 14:04:09 +02:00 committed by The Qt Project
parent ce81da52ea
commit d70cb66932
2 changed files with 41 additions and 29 deletions

View File

@ -53,6 +53,7 @@
#include <qguiapplication.h>
#include <qrect.h>
#include <qpainter.h>
#include <qtimer.h>
#include <qpa/qwindowsysteminterface.h>
@ -140,8 +141,7 @@ QXcbDrag::QXcbDrag(QXcbConnection *c) : QXcbObject(c)
init();
heartbeat = -1;
transaction_expiry_timer = -1;
cleanup_timer = -1;
}
QXcbDrag::~QXcbDrag()
@ -510,17 +510,21 @@ void QXcbDrag::drop(const QMouseEvent *event)
if (w && (w->window()->windowType() == Qt::Desktop) /*&& !w->acceptDrops()*/)
w = 0;
Transaction t = {
connection()->time(),
current_target,
current_proxy_target,
(w ? w->window() : 0),
// current_embedding_widget,
currentDrag()
// current_embeddig_widget,
currentDrag(),
QTime::currentTime()
};
transactions.append(t);
restartDropExpiryTimer();
// timer is needed only for drops that came from other processes.
if (!t.targetWindow && cleanup_timer == -1) {
cleanup_timer = startTimer(XdndDropTransactionTimeout);
}
if (w) {
handleDrop(w->window(), &drop);
@ -563,16 +567,6 @@ xcb_atom_t QXcbDrag::toXdndAction(Qt::DropAction a) const
}
}
// timer used to discard old XdndDrop transactions
enum { XdndDropTransactionTimeout = 5000 }; // 5 seconds
void QXcbDrag::restartDropExpiryTimer()
{
if (transaction_expiry_timer != -1)
killTimer(transaction_expiry_timer);
transaction_expiry_timer = startTimer(XdndDropTransactionTimeout);
}
int QXcbDrag::findTransactionByWindow(xcb_window_t window)
{
int at = -1;
@ -771,8 +765,6 @@ void QXcbDrag::handle_xdnd_position(QWindow *w, const xcb_client_message_event_t
response.data.data32[3] = 0; // w, h
response.data.data32[4] = toXdndAction(qt_response.acceptedAction()); // action
if (answerRect.left() < 0)
answerRect.setLeft(0);
if (answerRect.right() > 4096)
@ -1015,7 +1007,6 @@ void QXcbDrag::handleFinished(const xcb_client_message_event_t *event)
if (l[0]) {
int at = findTransactionByWindow(l[0]);
if (at != -1) {
restartDropExpiryTimer();
Transaction t = transactions.takeAt(at);
// QDragManager *manager = QDragManager::self();
@ -1044,12 +1035,13 @@ void QXcbDrag::handleFinished(const xcb_client_message_event_t *event)
// current_proxy_target = proxy_target;
// current_embedding_widget = embedding_widget;
// manager->object = currentObject;
} else {
qWarning("QXcbDrag::handleFinished - drop data has expired");
}
}
waiting_for_status = false;
}
void QXcbDrag::timerEvent(QTimerEvent* e)
{
if (e->timerId() == heartbeat && source_sameanswer.isNull()) {
@ -1057,19 +1049,35 @@ void QXcbDrag::timerEvent(QTimerEvent* e)
QMouseEvent me(QEvent::MouseMove, pos, pos, pos, Qt::LeftButton,
QGuiApplication::mouseButtons(), QGuiApplication::keyboardModifiers());
move(&me);
} else if (e->timerId() == transaction_expiry_timer) {
} else if (e->timerId() == cleanup_timer) {
bool stopTimer = true;
for (int i = 0; i < transactions.count(); ++i) {
const Transaction &t = transactions.at(i);
if (t.targetWindow) {
// dnd within the same process, don't delete these
// dnd within the same process, don't delete, these are taken care of
// in handleFinished()
continue;
}
t.drag->deleteLater();
transactions.removeAt(i--);
}
QTime currentTime = QTime::currentTime();
int delta = t.time.msecsTo(currentTime);
if (delta > XdndDropTransactionTimeout) {
/* delete transactions which are older than XdndDropTransactionTimeout. It could mean
one of these:
- client has crashed and as a result we have never received XdndFinished
- showing dialog box on drop event where user's response takes more time than XdndDropTransactionTimeout (QTBUG-14493)
- dnd takes unusually long time to process data
*/
t.drag->deleteLater();
transactions.removeAt(i--);
} else {
stopTimer = false;
}
killTimer(transaction_expiry_timer);
transaction_expiry_timer = -1;
}
if (stopTimer && cleanup_timer != -1) {
killTimer(cleanup_timer);
cleanup_timer = -1;
}
}
}
@ -1123,7 +1131,6 @@ void QXcbDrag::handleSelectionRequest(const xcb_selection_request_event_t *event
QDrag *transactionDrag = 0;
if (at >= 0) {
restartDropExpiryTimer();
transactionDrag = transactions.at(at).drag;
} else if (at == -2) {
transactionDrag = currentDrag();

View File

@ -52,7 +52,7 @@
#include <qsharedpointer.h>
#include <qpointer.h>
#include <qvector.h>
#include <qdatetime.h>
#include <qpixmap.h>
#include <qbackingstore.h>
@ -146,6 +146,10 @@ private:
// timer used when target wants "continuous" move messages (eg. scroll)
int heartbeat;
// 10 minute timer used to discard old XdndDrop transactions
enum { XdndDropTransactionTimeout = 600000 };
int cleanup_timer;
QVector<xcb_atom_t> drag_types;
struct Transaction
@ -156,6 +160,7 @@ private:
QWindow *targetWindow;
// QWidget *embedding_widget;
QDrag *drag;
QTime time;
};
QList<Transaction> transactions;