From e0c5d2365f151212bb552cd745cf5cae8af5d72f Mon Sep 17 00:00:00 2001
From: Eike Ziller <eike.ziller@digia.com>
Date: Thu, 4 Sep 2014 20:43:09 +0200
Subject: [PATCH] Support drag and drop between splits

Change-Id: Ia1e43cb44639e332ee4f9100c7ce3029e9485198
Reviewed-by: Alessandro Portale <alessandro.portale@digia.com>
Reviewed-by: Daniel Teske <daniel.teske@digia.com>
---
 src/libs/utils/fileutils.cpp                  | 17 +++-
 src/libs/utils/fileutils.h                    | 20 +++--
 .../coreplugin/editormanager/editorview.cpp   |  5 +-
 src/plugins/coreplugin/editortoolbar.cpp      | 83 ++++++++++++++++---
 src/plugins/coreplugin/editortoolbar.h        |  6 +-
 src/plugins/coreplugin/mainwindow.cpp         |  4 +-
 6 files changed, 109 insertions(+), 26 deletions(-)

diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp
index 4ea20d86762..31ab9fd653d 100644
--- a/src/libs/utils/fileutils.cpp
+++ b/src/libs/utils/fileutils.cpp
@@ -711,8 +711,9 @@ static bool isDesktopFileManagerDrop(const QMimeData *d, QStringList *files = 0)
     return hasFiles;
 }
 
-FileDropSupport::FileDropSupport(QWidget *parentWidget)
-    : QObject(parentWidget)
+FileDropSupport::FileDropSupport(QWidget *parentWidget, const DropFilterFunction &filterFunction)
+    : QObject(parentWidget),
+      m_filterFunction(filterFunction)
 {
     QTC_ASSERT(parentWidget, return);
     parentWidget->setAcceptDrops(true);
@@ -744,15 +745,22 @@ bool FileDropSupport::eventFilter(QObject *obj, QEvent *event)
     Q_UNUSED(obj)
     if (event->type() == QEvent::DragEnter) {
         auto dee = static_cast<QDragEnterEvent *>(event);
-        if (isDesktopFileManagerDrop(dee->mimeData()))
+        if (isDesktopFileManagerDrop(dee->mimeData())
+                && (!m_filterFunction || m_filterFunction(dee)))
             event->accept();
         else
             event->ignore();
+        return true;
+    } else if (event->type() == QEvent::DragMove) {
+        event->accept();
+        return true;
     } else if (event->type() == QEvent::Drop) {
         auto de = static_cast<QDropEvent *>(event);
         QStringList tempFiles;
-        if (isDesktopFileManagerDrop(de->mimeData(), &tempFiles)) {
+        if (isDesktopFileManagerDrop(de->mimeData(), &tempFiles)
+                && (!m_filterFunction || m_filterFunction(de))) {
             event->accept();
+            de->acceptProposedAction();
             bool needToScheduleEmit = m_files.isEmpty();
             m_files.append(tempFiles);
             if (needToScheduleEmit) // otherwise we already have a timer pending
@@ -760,6 +768,7 @@ bool FileDropSupport::eventFilter(QObject *obj, QEvent *event)
         } else {
             event->ignore();
         }
+        return true;
     }
     return false;
 }
diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h
index 69457b32bdc..9215603cf36 100644
--- a/src/libs/utils/fileutils.h
+++ b/src/libs/utils/fileutils.h
@@ -37,18 +37,21 @@
 #include <QMetaType>
 #include <QStringList>
 
+#include <functional>
+
 namespace Utils {class FileName; }
 
 QT_BEGIN_NAMESPACE
+class QDataStream;
+class QDateTime;
+class QDir;
+class QDropEvent;
 class QFile;
+class QFileInfo;
 class QMimeData;
 class QTemporaryFile;
-class QWidget;
 class QTextStream;
-class QDataStream;
-class QDateTime;
-class QFileInfo;
-class QDir;
+class QWidget;
 
 QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug dbg, const Utils::FileName &c);
 
@@ -198,7 +201,11 @@ class QTCREATOR_UTILS_EXPORT FileDropSupport : public QObject
 {
     Q_OBJECT
 public:
-    FileDropSupport(QWidget *parentWidget);
+     // returns true if the event should be accepted
+    typedef std::function<bool(QDropEvent*)> DropFilterFunction;
+
+    FileDropSupport(QWidget *parentWidget, const DropFilterFunction &filterFunction
+                    = DropFilterFunction());
 
     static QStringList mimeTypesForFilePaths();
     static QMimeData *mimeDataForFilePaths(const QStringList &filePaths);
@@ -214,6 +221,7 @@ private slots:
     void emitFilesDropped();
 
 private:
+    DropFilterFunction m_filterFunction;
     QStringList m_files;
 
 };
diff --git a/src/plugins/coreplugin/editormanager/editorview.cpp b/src/plugins/coreplugin/editormanager/editorview.cpp
index 585baa0aa6f..e36ec54dca1 100644
--- a/src/plugins/coreplugin/editormanager/editorview.cpp
+++ b/src/plugins/coreplugin/editormanager/editorview.cpp
@@ -78,6 +78,7 @@ EditorView::EditorView(SplitterOrView *parentSplitterOrView, QWidget *parent) :
         connect(m_toolBar, SIGNAL(goForwardClicked()), this, SLOT(goForwardInNavigationHistory()));
         connect(m_toolBar, SIGNAL(closeClicked()), this, SLOT(closeCurrentEditor()));
         connect(m_toolBar, SIGNAL(listSelectionActivated(int)), this, SLOT(listSelectionActivated(int)));
+        connect(m_toolBar, &EditorToolBar::currentDocumentMoved, this, &EditorView::closeCurrentEditor);
         connect(m_toolBar, SIGNAL(horizontalSplitClicked()), this, SLOT(splitHorizontally()));
         connect(m_toolBar, SIGNAL(verticalSplitClicked()), this, SLOT(splitVertically()));
         connect(m_toolBar, SIGNAL(splitNewWindowClicked()), this, SLOT(splitNewWindow()));
@@ -122,7 +123,9 @@ EditorView::EditorView(SplitterOrView *parentSplitterOrView, QWidget *parent) :
     m_container->addWidget(empty);
     m_widgetEditorMap.insert(empty, 0);
 
-    auto dropSupport = new Utils::FileDropSupport(this);
+    auto dropSupport = new Utils::FileDropSupport(this, [this](QDropEvent *event) {
+        return event->source() != m_toolBar; // do not accept drops on ourselves
+    });
     connect(dropSupport, SIGNAL(filesDropped(QStringList)),
             this, SLOT(openDroppedFiles(QStringList)));
 
diff --git a/src/plugins/coreplugin/editortoolbar.cpp b/src/plugins/coreplugin/editortoolbar.cpp
index 7a3240e3d25..1eab4846295 100644
--- a/src/plugins/coreplugin/editortoolbar.cpp
+++ b/src/plugins/coreplugin/editortoolbar.cpp
@@ -29,24 +29,32 @@
 
 #include "editortoolbar.h"
 
+#include <coreplugin/actionmanager/actionmanager.h>
 #include <coreplugin/coreconstants.h>
-#include <coreplugin/editormanager/ieditor.h>
-#include <coreplugin/icore.h>
-
+#include <coreplugin/editormanager/documentmodel.h>
 #include <coreplugin/editormanager/editormanager.h>
 #include <coreplugin/editormanager/editormanager_p.h>
-#include <coreplugin/editormanager/documentmodel.h>
-#include <coreplugin/actionmanager/actionmanager.h>
+#include <coreplugin/editormanager/ieditor.h>
+#include <coreplugin/fileiconprovider.h>
+#include <coreplugin/icore.h>
 
+#include <utils/fileutils.h>
 #include <utils/hostosinfo.h>
 #include <utils/qtcassert.h>
 
-#include <QDir>
 #include <QApplication>
 #include <QComboBox>
-#include <QVBoxLayout>
-#include <QToolButton>
+#include <QDir>
+#include <QDrag>
+#include <QLabel>
 #include <QMenu>
+#include <QMimeData>
+#include <QMouseEvent>
+#include <QTimer>
+#include <QToolButton>
+#include <QVBoxLayout>
+
+#include <QDebug>
 
 enum {
     debug = false
@@ -61,6 +69,7 @@ struct EditorToolBarPrivate
     QComboBox *m_editorList;
     QToolButton *m_closeEditorButton;
     QToolButton *m_lockButton;
+    QToolButton *m_dragHandle;
     QAction *m_goBackAction;
     QAction *m_goForwardAction;
     QToolButton *m_backButton;
@@ -75,22 +84,25 @@ struct EditorToolBarPrivate
     QWidget *m_toolBarPlaceholder;
     QWidget *m_defaultToolBar;
 
+    QPoint m_dragStartPosition;
+
     bool m_isStandalone;
 };
 
 EditorToolBarPrivate::EditorToolBarPrivate(QWidget *parent, EditorToolBar *q) :
     m_editorList(new QComboBox(q)),
-    m_closeEditorButton(new QToolButton),
-    m_lockButton(new QToolButton),
+    m_closeEditorButton(new QToolButton(q)),
+    m_lockButton(new QToolButton(q)),
+    m_dragHandle(new QToolButton(q)),
     m_goBackAction(new QAction(QIcon(QLatin1String(Constants::ICON_PREV)), EditorManager::tr("Go Back"), parent)),
     m_goForwardAction(new QAction(QIcon(QLatin1String(Constants::ICON_NEXT)), EditorManager::tr("Go Forward"), parent)),
-    m_splitButton(new QToolButton),
+    m_splitButton(new QToolButton(q)),
     m_horizontalSplitAction(new QAction(QIcon(QLatin1String(Constants::ICON_SPLIT_HORIZONTAL)), EditorManager::tr("Split"), parent)),
     m_verticalSplitAction(new QAction(QIcon(QLatin1String(Constants::ICON_SPLIT_VERTICAL)), EditorManager::tr("Split Side by Side"), parent)),
     m_splitNewWindowAction(new QAction(EditorManager::tr("Open in New Window"), parent)),
-    m_closeSplitButton(new QToolButton),
+    m_closeSplitButton(new QToolButton(q)),
     m_activeToolBar(0),
-    m_toolBarPlaceholder(new QWidget),
+    m_toolBarPlaceholder(new QWidget(q)),
     m_defaultToolBar(new QWidget(q)),
     m_isStandalone(false)
 {
@@ -115,6 +127,11 @@ EditorToolBar::EditorToolBar(QWidget *parent) :
     d->m_lockButton->setAutoRaise(true);
     d->m_lockButton->setEnabled(false);
 
+    d->m_dragHandle->setCheckable(false);
+    d->m_dragHandle->setChecked(false);
+    d->m_dragHandle->setToolTip(tr("Drag to drag documents between splits"));
+    d->m_dragHandle->installEventFilter(this);
+
     connect(d->m_goBackAction, SIGNAL(triggered()), this, SIGNAL(goBackClicked()));
     connect(d->m_goForwardAction, SIGNAL(triggered()), this, SIGNAL(goForwardClicked()));
 
@@ -164,6 +181,7 @@ EditorToolBar::EditorToolBar(QWidget *parent) :
     toplayout->addWidget(d->m_backButton);
     toplayout->addWidget(d->m_forwardButton);
     toplayout->addWidget(d->m_lockButton);
+    toplayout->addWidget(d->m_dragHandle);
     toplayout->addWidget(d->m_editorList);
     toplayout->addWidget(d->m_closeEditorButton);
     toplayout->addWidget(d->m_toolBarPlaceholder, 1); // Custom toolbar stretches
@@ -367,6 +385,7 @@ void EditorToolBar::updateDocumentStatus(IDocument *document)
         d->m_lockButton->setIcon(QIcon());
         d->m_lockButton->setEnabled(false);
         d->m_lockButton->setToolTip(QString());
+        d->m_dragHandle->setIcon(QIcon());
         d->m_editorList->setToolTip(QString());
         return;
     }
@@ -386,12 +405,50 @@ void EditorToolBar::updateDocumentStatus(IDocument *document)
         d->m_lockButton->setEnabled(false);
         d->m_lockButton->setToolTip(tr("File is writable"));
     }
+
+    if (document->filePath().isEmpty())
+        d->m_dragHandle->setIcon(QIcon());
+    else
+        d->m_dragHandle->setIcon(FileIconProvider::icon(QFileInfo(document->filePath())));
+
     d->m_editorList->setToolTip(
             document->filePath().isEmpty()
             ? document->displayName()
             : QDir::toNativeSeparators(document->filePath()));
 }
 
+bool EditorToolBar::eventFilter(QObject *obj, QEvent *event)
+{
+    if (obj == d->m_dragHandle) {
+        if (event->type() == QEvent::MouseButtonPress) {
+            auto me = static_cast<QMouseEvent *>(event);
+            if (me->buttons() == Qt::LeftButton) {
+                d->m_dragStartPosition = me->pos();
+                return true;
+            }
+            return Utils::StyledBar::eventFilter(obj, event);
+        } else if (event->type() == QEvent::MouseMove) {
+            auto me = static_cast<QMouseEvent *>(event);
+            if (me->buttons() != Qt::LeftButton)
+                return Utils::StyledBar::eventFilter(obj, event);
+            if ((me->pos() - d->m_dragStartPosition).manhattanLength()
+                    < QApplication::startDragDistance())
+                return Utils::StyledBar::eventFilter(obj, event);
+            DocumentModel::Entry *entry = DocumentModel::entryAtRow(
+                        d->m_editorList->currentIndex());
+            if (!entry) // no document
+                return Utils::StyledBar::eventFilter(obj, event);
+            auto *drag = new QDrag(this);
+            drag->setMimeData(Utils::FileDropSupport::mimeDataForFilePath(entry->fileName()));
+            Qt::DropAction action = drag->exec(Qt::MoveAction | Qt::CopyAction, Qt::MoveAction);
+            if (action == Qt::MoveAction)
+                emit currentDocumentMoved();
+            return true;
+        }
+    }
+    return Utils::StyledBar::eventFilter(obj, event);
+}
+
 void EditorToolBar::setNavigationVisible(bool isVisible)
 {
     d->m_goBackAction->setVisible(isVisible);
diff --git a/src/plugins/coreplugin/editortoolbar.h b/src/plugins/coreplugin/editortoolbar.h
index 4ad4b7496d8..7c6c26ea90b 100644
--- a/src/plugins/coreplugin/editortoolbar.h
+++ b/src/plugins/coreplugin/editortoolbar.h
@@ -94,11 +94,15 @@ signals:
     void closeSplitClicked();
     void listSelectionActivated(int row);
     void listContextMenuRequested(QPoint globalpos);
+    void currentDocumentMoved();
+
+protected:
+    bool eventFilter(QObject *obj, QEvent *event);
 
 private slots:
     void updateEditorListSelection(Core::IEditor *newSelection);
     void changeActiveEditor(int row);
-    void listContextMenu(QPoint);
+    void listContextMenu(QPoint pos);
     void makeEditorWritable();
 
     void checkDocumentStatus();
diff --git a/src/plugins/coreplugin/mainwindow.cpp b/src/plugins/coreplugin/mainwindow.cpp
index 561bc2b04af..f16b10e3515 100644
--- a/src/plugins/coreplugin/mainwindow.cpp
+++ b/src/plugins/coreplugin/mainwindow.cpp
@@ -207,7 +207,9 @@ MainWindow::MainWindow() :
 
     statusBar()->setProperty("p_styled", true);
 
-    auto dropSupport = new Utils::FileDropSupport(this);
+    auto dropSupport = new Utils::FileDropSupport(this, [](QDropEvent *event) {
+        return event->source() == 0; // only accept drops from the "outside" (e.g. file manager)
+    });
     connect(dropSupport, SIGNAL(filesDropped(QStringList)),
             this, SLOT(openDroppedFiles(QStringList)));
 }
-- 
GitLab