]> cloud.milkyroute.net Git - dolphin.git/commitdiff
Prevent crashes caused by nested event loops run when renaming inline
authorFrank Reininghaus <frank78ac@googlemail.com>
Mon, 5 Nov 2012 21:03:52 +0000 (22:03 +0100)
committerFrank Reininghaus <frank78ac@googlemail.com>
Mon, 5 Nov 2012 21:03:52 +0000 (22:03 +0100)
When renaming inline and starting a drag or invoking the context menu,
a nested event loop will be run. If the role editor loses focus and
emits roleEditingFinished(), we must prevent that deleteLater() is
called because this would delete the role editor inside a nested event
loop which is run from one of its own functions. We would get a crash
when returning from that event loop otherwise.

BUG: 308018
BUG: 309421
FIXED-IN: 4.9.4

src/kitemviews/kstandarditemlistwidget.cpp
src/kitemviews/private/kitemlistroleeditor.cpp
src/kitemviews/private/kitemlistroleeditor.h

index bdc2859c0c1361cb97ccfa1496c7809b6f32f1e1..f92cab50f1be2274b55cba109b44608c2fcad55e 100644 (file)
@@ -609,7 +609,10 @@ void KStandardItemListWidget::editedRoleChanged(const QByteArray& current, const
                        this, SLOT(slotRoleEditingCanceled(int,QByteArray,QVariant)));
             disconnect(m_roleEditor, SIGNAL(roleEditingFinished(int,QByteArray,QVariant)),
                        this, SLOT(slotRoleEditingFinished(int,QByteArray,QVariant)));
-            m_roleEditor->deleteLater();
+            // Do not delete the role editor using deleteLater() because we might be
+            // inside a nested event loop which has been started by one of its event
+            // handlers (contextMenuEvent() or drag&drop inside mouseMoveEvent()).
+            m_roleEditor->deleteWhenIdle();
             m_roleEditor = 0;
         }
         return;
@@ -1274,7 +1277,11 @@ void KStandardItemListWidget::closeRoleEditor()
                this, SLOT(slotRoleEditingCanceled(int,QByteArray,QVariant)));
     disconnect(m_roleEditor, SIGNAL(roleEditingFinished(int,QByteArray,QVariant)),
                this, SLOT(slotRoleEditingFinished(int,QByteArray,QVariant)));
-    m_roleEditor->deleteLater();
+
+    // Do not delete the role editor using deleteLater() because we might be
+    // inside a nested event loop which has been started by one of its event
+    // handlers (contextMenuEvent() or drag&drop inside mouseMoveEvent()).
+    m_roleEditor->deleteWhenIdle();
     m_roleEditor = 0;
 }
 
index 1e4b5fd4e0988c7ce38160124c5d9392cb94f65b..ead5b298edda1f4820c7335a48c4f09a78ff0d4f 100644 (file)
@@ -26,7 +26,9 @@ KItemListRoleEditor::KItemListRoleEditor(QWidget *parent) :
     KTextEdit(parent),
     m_index(0),
     m_role(),
-    m_blockFinishedSignal(false)
+    m_blockFinishedSignal(false),
+    m_eventHandlingLevel(0),
+    m_deleteAfterEventHandling(false)
 {
     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
@@ -64,6 +66,20 @@ QByteArray KItemListRoleEditor::role() const
     return m_role;
 }
 
+void KItemListRoleEditor::deleteWhenIdle()
+{
+    if (m_eventHandlingLevel > 0) {
+        // We are handling an event at the moment. It could be that we
+        // are in a nested event loop run by contextMenuEvent() or a
+        // call of mousePressEvent() which results in drag&drop.
+        // -> do not call deleteLater() to prevent a crash when we
+        //    return from the nested event loop.
+        m_deleteAfterEventHandling = true;
+    } else {
+        deleteLater();
+    }
+}
+
 bool KItemListRoleEditor::eventFilter(QObject* watched, QEvent* event)
 {
     if (watched == parentWidget() && event->type() == QEvent::Resize) {
@@ -75,13 +91,42 @@ bool KItemListRoleEditor::eventFilter(QObject* watched, QEvent* event)
 
 bool KItemListRoleEditor::event(QEvent* event)
 {
+    ++m_eventHandlingLevel;
+
     if (event->type() == QEvent::FocusOut) {
         QFocusEvent* focusEvent = static_cast<QFocusEvent*>(event);
         if (focusEvent->reason() != Qt::PopupFocusReason) {
             emitRoleEditingFinished();
         }
     }
-    return KTextEdit::event(event);
+
+    const int result = KTextEdit::event(event);
+    --m_eventHandlingLevel;
+
+    if (m_deleteAfterEventHandling && m_eventHandlingLevel == 0) {
+        // Schedule this object for deletion and make sure that we do not try
+        // to deleteLater() again when the DeferredDelete event is received.
+        deleteLater();
+        m_deleteAfterEventHandling = false;
+    }
+
+    return result;
+}
+
+bool KItemListRoleEditor::viewportEvent(QEvent* event)
+{
+    ++m_eventHandlingLevel;
+    const bool result = KTextEdit::viewportEvent(event);
+    --m_eventHandlingLevel;
+
+    if (m_deleteAfterEventHandling && m_eventHandlingLevel == 0) {
+        // Schedule this object for deletion and make sure that we do not try
+        // to deleteLater() again when the DeferredDelete event is received.
+        deleteLater();
+        m_deleteAfterEventHandling = false;
+    }
+
+    return result;
 }
 
 void KItemListRoleEditor::keyPressEvent(QKeyEvent* event)
index aa2c977541f0841132f708027da575632b425626..a2f7058086dffe164fd8869c840de8705a037075 100644 (file)
@@ -47,6 +47,15 @@ public:
     void setRole(const QByteArray& role);
     QByteArray role() const;
 
+    /**
+     * Calls deleteLater() if no event is being handled at the moment.
+     * Otherwise, the deletion is deferred until the event handling is
+     * finished. This prevents that the deletion happens inside a nested
+     * event loop which might be run in contextMenuEvent() or
+     * mouseMoveEvent() because this would probably cause a crash.
+     */
+    void deleteWhenIdle();
+
     virtual bool eventFilter(QObject* watched, QEvent* event);
 
 signals:
@@ -55,6 +64,7 @@ signals:
 
 protected:
     virtual bool event(QEvent* event);
+    virtual bool viewportEvent(QEvent* event);
     virtual void keyPressEvent(QKeyEvent* event);
 
 private slots:
@@ -75,6 +85,8 @@ private:
     int m_index;
     QByteArray m_role;
     bool m_blockFinishedSignal;
+    int m_eventHandlingLevel;
+    bool m_deleteAfterEventHandling;
 };
 
 #endif