diff --git a/src/library/library.cpp b/src/library/library.cpp index 36710fc999cc..76c2f5c24708 100644 --- a/src/library/library.cpp +++ b/src/library/library.cpp @@ -343,7 +343,12 @@ void Library::bindSidebarWidget(WLibrarySidebar* pSidebarWidget) { m_pConfig->getValue( kSidebarHoverExpandDelayConfigKey, kSidebarHoverExpandDelayDefault); - pSidebarWidget->slotSetExpandOnHoverDelay(sidebarHoverExpandDelay); + const auto sidebarHoverCollapseDelay = + m_pConfig->getValue( + kSidebarHoverCollapseDelayConfigKey, + kSidebarHoverCollapseDelayDefault); + pSidebarWidget->slotSetExpandCollapseOnHoverDelay( + sidebarHoverExpandDelay, sidebarHoverCollapseDelay); m_pLibraryControl->bindSidebarWidget(pSidebarWidget); @@ -392,9 +397,9 @@ void Library::bindSidebarWidget(WLibrarySidebar* pSidebarWidget) { &WLibrarySidebar::slotSetFont); connect(this, - &Library::setSidebarHoverExpandDelay, + &Library::setSidebarHoverDelay, pSidebarWidget, - &WLibrarySidebar::slotSetExpandOnHoverDelay); + &WLibrarySidebar::slotSetExpandCollapseOnHoverDelay); for (const auto& feature : std::as_const(m_features)) { feature->bindSidebarWidget(pSidebarWidget); diff --git a/src/library/library.h b/src/library/library.h index 978b0d495fc3..61017a66bb1d 100644 --- a/src/library/library.h +++ b/src/library/library.h @@ -175,7 +175,7 @@ class Library: public QObject { void setTrackTableRowHeight(int rowHeight); void setSelectedClick(bool enable); - void setSidebarHoverExpandDelay(int delay); + void setSidebarHoverDelay(int expandDelay, int collapseDelay); void onTrackAnalyzerProgress(TrackId trackId, AnalyzerProgress analyzerProgress); diff --git a/src/library/library_prefs.cpp b/src/library/library_prefs.cpp index 3b4cdeca5a09..095bb3555951 100644 --- a/src/library/library_prefs.cpp +++ b/src/library/library_prefs.cpp @@ -129,3 +129,8 @@ const ConfigKey mixxx::library::prefs::kSidebarHoverExpandDelayConfigKey = ConfigKey{ mixxx::library::prefs::kConfigGroup, QStringLiteral("sidebar_hover_expand_delay")}; + +const ConfigKey mixxx::library::prefs::kSidebarHoverCollapseDelayConfigKey = + ConfigKey{ + mixxx::library::prefs::kConfigGroup, + QStringLiteral("sidebar_hover_collapse_delay")}; diff --git a/src/library/library_prefs.h b/src/library/library_prefs.h index 2dc154debc96..7628f0fc77df 100644 --- a/src/library/library_prefs.h +++ b/src/library/library_prefs.h @@ -66,6 +66,10 @@ const int kSidebarHoverExpandDelayDefault = 500; // ms extern const ConfigKey kSidebarHoverExpandDelayConfigKey; +const int kSidebarHoverCollapseDelayDefault = 750; // ms + +extern const ConfigKey kSidebarHoverCollapseDelayConfigKey; + } // namespace prefs } // namespace library diff --git a/src/library/sidebarmodel.cpp b/src/library/sidebarmodel.cpp index 189e4270029b..90b0462a34cb 100644 --- a/src/library/sidebarmodel.cpp +++ b/src/library/sidebarmodel.cpp @@ -8,9 +8,13 @@ #include "moc_sidebarmodel.cpp" #include "util/assert.h" #include "util/cmdlineargs.h" +#include "util/dnd.h" namespace { +/// The MIME type supported for drag & drop +const QString kUriListMimeType = QStringLiteral("text/uri-list"); + /// The time between selecting and activating (= clicking) a feature item /// in the sidebar tree. This is essential to allow smooth scrolling through /// a list of items with an encoder or the keyboard! A value of 300 ms has @@ -27,6 +31,7 @@ SidebarModel::SidebarModel( : QAbstractItemModel(parent), m_iDefaultSelectedIndex(0), m_pressedUntilClickedTimer(new QTimer(this)) { + m_mimeTypes << kUriListMimeType; m_pressedUntilClickedTimer->setSingleShot(true); connect(m_pressedUntilClickedTimer, &QTimer::timeout, @@ -314,6 +319,9 @@ QVariant SidebarModel::data(const QModelIndex& index, int role) const { return pTreeItem->getData(); case SidebarModel::IconNameRole: // TODO: Add support for icon names in tree items + return QVariant(); + case SidebarModel::UrlRole: + return pTreeItem->getUrl(); default: return QVariant(); } @@ -437,6 +445,102 @@ void SidebarModel::deleteItem(const QModelIndex& index) { } } +QStringList SidebarModel::mimeTypes() const { + return m_mimeTypes; +} + +QMimeData* SidebarModel::mimeData(const QModelIndexList& indexes) const { + if constexpr (kDebug) { + qDebug() << "SidebarModel::mimeData() indexes=" << indexes; + } + DEBUG_ASSERT(mimeTypes().size() == 1 && mimeTypes().at(0) == kUriListMimeType); + const auto urls = collectUrls(indexes); + if (urls.isEmpty()) { + return nullptr; + } else { + QMimeData* mimeData = new QMimeData(); + mimeData->setUrls(urls); + return mimeData; + } +} + +QList SidebarModel::collectUrls(const QModelIndexList& indexes) const { + QList urls; + urls.reserve(indexes.size()); + // The list of indexes we're given may contain separate indices for each + // column, so even if only one row is selected, we might have columnCount() + // indices. We need to only count a single QModelIndex per unique row. + // + // TODO(cr7pt0gr4ph7): An alternative implementation would be to instead + // use a QSet to check if an URL has already been seen. Are there + // any cases where the behavior of these two implementations would differ? + QSet visitedRows; + for (const auto& index : indexes) { + if (!index.isValid()) { + continue; + } + auto uniqueRow = index.siblingAtColumn(0); + if (visitedRows.contains(uniqueRow)) { + continue; + } + visitedRows.insert(uniqueRow); + QUrl url = data(index, Roles::UrlRole).toUrl(); + if (url.isValid()) { + urls.append(url); + } + } + return urls; +} + +Qt::ItemFlags SidebarModel::flags(const QModelIndex& index) const { + Q_UNUSED(index); + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; +} + +QModelIndex SidebarModel::resolveDropIndex(int row, int column, const QModelIndex& parent) const { + Q_UNUSED(row); + Q_UNUSED(column); + return parent; +} + +bool SidebarModel::canDropMimeData(const QMimeData* data, + Qt::DropAction action, + int row, + int column, + const QModelIndex& parent) const { + Q_UNUSED(action); + const QModelIndex index = resolveDropIndex(row, column, parent); + + if (data->hasUrls()) { + return dragMoveAccept(index, data->urls()); + } + + return false; +} + +bool SidebarModel::dropMimeData(const QMimeData* data, + Qt::DropAction action, + int row, + int column, + const QModelIndex& parent) { + Q_UNUSED(action); + QModelIndex index = resolveDropIndex(row, column, parent); + + if (data->hasUrls()) { + const QList urls = data->urls(); + + // m_sourceOfCurrentDragDropEvent will be NULL if + // something is dropped from a different application + return dropAccept(index, urls, m_sourceOfCurrentDragDropEvent); + } + + return false; +} + +void SidebarModel::setSourceOfCurrentDragDropEvent(QObject* source) { + m_sourceOfCurrentDragDropEvent = source; +} + bool SidebarModel::dropAccept(const QModelIndex& index, const QList& urls, QObject* pSource) { if constexpr (kDebug) { qDebug() << "SidebarModel::dropAccept() index=" << index << urls; diff --git a/src/library/sidebarmodel.h b/src/library/sidebarmodel.h index 15c04a8acab5..0abea4f2d891 100644 --- a/src/library/sidebarmodel.h +++ b/src/library/sidebarmodel.h @@ -18,6 +18,7 @@ class SidebarModel : public QAbstractItemModel { enum Roles { IconNameRole = Qt::UserRole + 1, DataRole, + UrlRole, }; Q_ENUM(Roles); @@ -38,6 +39,21 @@ class SidebarModel : public QAbstractItemModel { int columnCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QStringList mimeTypes() const override; + QMimeData* mimeData(const QModelIndexList& indexes) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + QModelIndex resolveDropIndex(int row, int column, const QModelIndex& index) const; + bool canDropMimeData(const QMimeData* data, + Qt::DropAction action, + int row, + int column, + const QModelIndex& index) const override; + bool dropMimeData(const QMimeData* data, + Qt::DropAction action, + int row, + int column, + const QModelIndex& index) override; + void setSourceOfCurrentDragDropEvent(QObject* source); bool dropAccept(const QModelIndex& index, const QList& urls, QObject* pSource); bool dragMoveAccept(const QModelIndex& index, const QList& urls) const; bool hasChildren(const QModelIndex& parent = QModelIndex()) const override; @@ -90,6 +106,7 @@ class SidebarModel : public QAbstractItemModel { QList m_sFeatures; private: + QList collectUrls(const QModelIndexList& indexes) const; QModelIndex translateSourceIndex(const QModelIndex& parent); QModelIndex translateIndex(const QModelIndex& index, const QAbstractItemModel* model); void featureRenamed(LibraryFeature*); @@ -97,6 +114,8 @@ class SidebarModel : public QAbstractItemModel { QTimer* const m_pressedUntilClickedTimer; QModelIndex m_pressedIndex; + QStringList m_mimeTypes; + QObject* m_sourceOfCurrentDragDropEvent; void startPressedUntilClickedTimer(const QModelIndex& pressedIndex); void stopPressedUntilClickedTimer(); diff --git a/src/library/treeitem.h b/src/library/treeitem.h index df80de26b26f..40bf3bca4a59 100644 --- a/src/library/treeitem.h +++ b/src/library/treeitem.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -112,6 +113,13 @@ class TreeItem final { return m_data; } + void setUrl(const QUrl& url) { + m_url = url; + } + const QUrl& getUrl() const { + return m_url; + } + void setIcon(const QIcon& icon) { m_icon = icon; } @@ -145,6 +153,7 @@ class TreeItem final { QString m_label; QVariant m_data; + QUrl m_url; QIcon m_icon; bool m_bold; }; diff --git a/src/library/treeitemmodel.cpp b/src/library/treeitemmodel.cpp index 36e40096fdc6..eda68a92be48 100644 --- a/src/library/treeitemmodel.cpp +++ b/src/library/treeitemmodel.cpp @@ -55,6 +55,8 @@ QVariant TreeItemModel::data(const QModelIndex &index, int role) const { return item->getData(); case kBoldRole: return item->isBold(); + case kUrlRole: + return item->getUrl(); default: return QVariant(); } diff --git a/src/library/treeitemmodel.h b/src/library/treeitemmodel.h index 142d635e2822..a00ff4f64708 100644 --- a/src/library/treeitemmodel.h +++ b/src/library/treeitemmodel.h @@ -10,8 +10,9 @@ class TreeItem; class TreeItemModel : public QAbstractItemModel { Q_OBJECT public: - static const int kDataRole = Qt::UserRole; - static const int kBoldRole = Qt::UserRole + 1; + static constexpr int kDataRole = Qt::UserRole; + static constexpr int kBoldRole = Qt::UserRole + 1; + static constexpr int kUrlRole = Qt::UserRole + 2; explicit TreeItemModel(QObject* parent = nullptr); ~TreeItemModel() override; diff --git a/src/preferences/dialog/dlgpreflibrary.cpp b/src/preferences/dialog/dlgpreflibrary.cpp index 7c549b2ce71a..a80838ec24a7 100644 --- a/src/preferences/dialog/dlgpreflibrary.cpp +++ b/src/preferences/dialog/dlgpreflibrary.cpp @@ -300,6 +300,7 @@ void DlgPrefLibrary::slotResetToDefaults() { } spinBox_sidebar_hover_expand_delay->setValue(kSidebarHoverExpandDelayDefault); + spinBox_sidebar_hover_collapse_delay->setValue(kSidebarHoverCollapseDelayDefault); checkBox_show_rhythmbox->setChecked(true); checkBox_show_banshee->setChecked(true); @@ -464,6 +465,12 @@ void DlgPrefLibrary::slotUpdate() { kSidebarHoverExpandDelayConfigKey, kSidebarHoverExpandDelayDefault); spinBox_sidebar_hover_expand_delay->setValue(sidebarHoverExpandDelay); + + const auto sidebarHoverCollapseDelay = + m_pConfig->getValue( + kSidebarHoverCollapseDelayConfigKey, + kSidebarHoverCollapseDelayDefault); + spinBox_sidebar_hover_collapse_delay->setValue(sidebarHoverCollapseDelay); } void DlgPrefLibrary::slotCancel() { @@ -684,8 +691,10 @@ void DlgPrefLibrary::slotApply() { ConfigValue(checkbox_played_track_color->isChecked())); int sidebarHoverExpandDelay = spinBox_sidebar_hover_expand_delay->value(); + int sidebarHoverCollapseDelay = spinBox_sidebar_hover_collapse_delay->value(); m_pConfig->setValue(kSidebarHoverExpandDelayConfigKey, sidebarHoverExpandDelay); - emit m_pLibrary->setSidebarHoverExpandDelay(sidebarHoverExpandDelay); + m_pConfig->setValue(kSidebarHoverCollapseDelayConfigKey, sidebarHoverCollapseDelay); + emit m_pLibrary->setSidebarHoverDelay(sidebarHoverExpandDelay, sidebarHoverCollapseDelay); // TODO(rryan): Don't save here. m_pConfig->save(); diff --git a/src/preferences/dialog/dlgpreflibrarydlg.ui b/src/preferences/dialog/dlgpreflibrarydlg.ui index f6f89df71916..9bda589a5313 100644 --- a/src/preferences/dialog/dlgpreflibrarydlg.ui +++ b/src/preferences/dialog/dlgpreflibrarydlg.ui @@ -444,7 +444,40 @@ ms - The delay until sidebar items are expanded or collapsed hovered during drag'n'drop. -1 disables auto-expand. + The delay until sidebar items are expanded hovered during drag'n'drop. -1 disables auto-expand. + + + -1 + + + 5000 + + + 50 + + + + + + + + Hover collapse delay: + + + Qt::AlignLeft|Qt::AlignVCenter + + + spinBox_sidebar_hover_collapse_delay + + + + + + + ms + + + The delay until sidebar items are collapsed hovered during drag'n'drop. -1 disables auto-expand. -1 diff --git a/src/widget/wlibrarysidebar.cpp b/src/widget/wlibrarysidebar.cpp index ebd74d94d178..dd370abcafa8 100644 --- a/src/widget/wlibrarysidebar.cpp +++ b/src/widget/wlibrarysidebar.cpp @@ -14,17 +14,17 @@ WLibrarySidebar::WLibrarySidebar(QWidget* parent) : QTreeView(parent), WBaseWidget(this), m_hoverExpandDelay(mixxx::library::prefs::kSidebarHoverExpandDelayDefault), - m_lastDragMoveAccepted(false) { + m_hoverCollapseDelay(mixxx::library::prefs::kSidebarHoverCollapseDelayDefault) { qRegisterMetaType("FocusWidget"); //Set some properties setHeaderHidden(true); setSelectionMode(QAbstractItemView::SingleSelection); //Drag and drop setup - setDragEnabled(false); setDragDropMode(QAbstractItemView::DragDrop); + setDragDropOverwriteMode(true); setDropIndicatorShown(true); - setAcceptDrops(true); setAutoScroll(true); + setAutoExpandDelay(m_hoverExpandDelay); setAttribute(Qt::WA_MacShowFocusRect, false); header()->setStretchLastSection(false); header()->setSectionResizeMode(QHeaderView::ResizeToContents); @@ -44,31 +44,47 @@ void WLibrarySidebar::contextMenuEvent(QContextMenuEvent* pEvent) { //} } +void WLibrarySidebar::setSourceOfCurrentDragDropEvent(QObject* pSource) { + // pEvent->source() will be NULL if something is dropped + // from a different application. This knowledge is used + // inside the LibraryFeature implementations. + SidebarModel* pSidebarModel = qobject_cast(model()); + if (pSidebarModel) { + pSidebarModel->setSourceOfCurrentDragDropEvent(pSource); + } +} + /// Drag enter event, happens when a dragged item enters the track sources view void WLibrarySidebar::dragEnterEvent(QDragEnterEvent* pEvent) { qDebug() << "WLibrarySidebar::dragEnterEvent" << pEvent->mimeData()->formats(); - resetHoverIndexAndDragMoveResult(); - if (pEvent->mimeData()->hasUrls()) { - // We don't have a way to ask the LibraryFeatures whether to accept a - // drag so for now we accept all drags. Since almost every - // LibraryFeature accepts all files in the drop and accepts playlist - // drops we default to those flags to DragAndDropHelper. - // FIXME Unless the cursor is steady after entering the sidebar (which - // is veryhard to achieve for humans) QDragEnterEvent is followed by one - // or more QDragMoveEvent, so don't check here at all and rely on dragMove? - if (DragAndDropHelper::urlsContainSupportedTrackFiles(pEvent->mimeData()->urls(), true)) { - pEvent->acceptProposedAction(); - return; - } + toggleDragHoverPropertyAndUpdateStyle(true); + + // QTreeView::dragEnterEvent will, through some indirection, + // call SidebarModel::mimeTypes() and use it to decide whether + // we could potentially support the drag data at all. In practice, + // this checks whether the drag data contains a list of URLs. + // + // As documented in the Qt source code, the actual check whether any + // of the URLs are actually valid/supported is deferred until the + // dragMoveEvent (see below). + // + // Note: pEvent->source() will be NULL if something is dropped + // from a different application. This knowledge is used + // inside the LibraryFeature implementations. + setSourceOfCurrentDragDropEvent(pEvent->source()); + QTreeView::dragEnterEvent(pEvent); + setSourceOfCurrentDragDropEvent(nullptr); + + if (pEvent->isAccepted()) { + pEvent->acceptProposedAction(); } - pEvent->ignore(); - // QTreeView::dragEnterEvent(pEvent); } -/// Drag leave event, happens when leaving and when the drag is aborted, eg. with Esc. -/// We override this only to reset the drag hover property. +/// Drag leave event, happens when the dragged item leaves the track sources view +/// or when the drag is aborted through Escape or other means. void WLibrarySidebar::dragLeaveEvent(QDragLeaveEvent* pEvent) { // qDebug() << "WLibrarySidebar::dragLeaveEvent"; + m_autoExpandIndex = QModelIndex(); toggleDragHoverPropertyAndUpdateStyle(false); QTreeView::dragLeaveEvent(pEvent); @@ -77,115 +93,91 @@ void WLibrarySidebar::dragLeaveEvent(QDragLeaveEvent* pEvent) { /// Drag move event, happens when a dragged item hovers over the track sources view... void WLibrarySidebar::dragMoveEvent(QDragMoveEvent* pEvent) { // qDebug() << "WLibrarySidebar::dragMoveEvent" << pEvent->mimeData()->formats(); - toggleDragHoverPropertyAndUpdateStyle(true); -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - QPoint pos = pEvent->position().toPoint(); -#else - QPoint pos = pEvent->pos(); -#endif + // QTreeView::dragMoveEvent will, through some indirection, + // call SidebarModel::canDropMimeData, which will call one of either + // LibraryFeature::dragMoveAccept or LibraryFeature::dragMoveAcceptChild, + // depending on the item over which the drag event occurred. + // + // This is where the LibraryFeature subclasses check whether any of + // actual data being dragged is supported, e.g. whether it is + // a list of valid track URLs. + // + // Note: We go through QTreeView/QAbstractItemView here, instead of + // directly calling SidebarModel, to retain other useful features + // from the base class, like e.g. auto-scroll behavior when the mouse + // cursor reaches the boundaries of the tree view. + // + // Note: pEvent->source() will be NULL if something is dropped + // from a different application. This knowledge is used + // inside the LibraryFeature implementations. + + // ======================================================================== + // Fix autoExpand timer reset behavior (workaround for bug in Qt framework) + // + // Starting with at least Qt 5.0.0 (released in 2011) and still present + // in current versions of Qt (Qt 6.8.0 at the time of this commit), there + // is a bug in the implementation of QTreeView::dragMoveEvent and autoExpandDelay: + // + // QT BUG DESCRIPTION + // + // Instead of resetting the delay timer whenever the mouse moves to a + // new item, it is reset on every little mouse movement, which makes + // autoExpand useless e.g. on laptop touchpads. + // + // OUR WORKAROUND + // + // Only reset the delay timer whenever the mouse has moved to a new item, + // by bypassing QTreeView::dragMoveEvent() and directly calling + // QAbstractItemView::dragMoveEvent() instead unless the mouse + // has moved to a new item. + // ======================================================================== + const QPoint pos = pEvent->position().toPoint(); const QModelIndex index = indexAt(pos); - if (m_hoverIndex == index) { - m_lastDragMoveAccepted ? pEvent->acceptProposedAction() : pEvent->ignore(); - return; - } - - m_hoverIndex = index; - - if (m_hoverExpandDelay >= 0) { - // Timeout of < 0 disables auto-expand - m_expandTimer.stop(); - m_expandTimer.start(m_hoverExpandDelay, this); - } - - // This has to be here instead of after, otherwise all drags will be - // rejected -- rryan 3/2011 - QTreeView::dragMoveEvent(pEvent); - if (!pEvent->mimeData()->hasUrls()) { - pEvent->ignore(); - m_lastDragMoveAccepted = false; - return; - } - - const QList urls = pEvent->mimeData()->urls(); - // Drag and drop within this widget - if ((pEvent->source() == this) && (pEvent->possibleActions() & Qt::MoveAction)) { - // Do nothing. - m_lastDragMoveAccepted = false; - pEvent->ignore(); - return; - } - - SidebarModel* pSidebarModel = qobject_cast(model()); - VERIFY_OR_DEBUG_ASSERT(pSidebarModel) { - m_lastDragMoveAccepted = false; - pEvent->ignore(); - return; - } - if (pSidebarModel->dragMoveAccept(index, urls)) { - m_lastDragMoveAccepted = true; - pEvent->acceptProposedAction(); - } else { - m_lastDragMoveAccepted = false; - pEvent->ignore(); - } -} -void WLibrarySidebar::timerEvent(QTimerEvent* pEvent) { - if (pEvent->timerId() == m_expandTimer.timerId()) { - QPoint pos = viewport()->mapFromGlobal(QCursor::pos()); - if (viewport()->rect().contains(pos)) { - QModelIndex index = indexAt(pos); - if (m_hoverIndex == index) { - setExpanded(index, !isExpanded(index)); - } + if (m_autoExpandIndex != index) { + m_autoExpandIndex = index; + if (isExpanded(index)) { + setAutoExpandDelay(m_hoverCollapseDelay); + } else { + setAutoExpandDelay(m_hoverExpandDelay); } - m_expandTimer.stop(); - return; + // QTreeView::dragMoveEvent just restarts the autoExpand timer + // and then calls QAbstractItemView::dragMoveEvent + setSourceOfCurrentDragDropEvent(pEvent->source()); + QTreeView::dragMoveEvent(pEvent); + setSourceOfCurrentDragDropEvent(nullptr); + } else { + // Skip resetting the autoExpand timer (see above) + // because we are still hovering over the same item + setSourceOfCurrentDragDropEvent(pEvent->source()); + QAbstractItemView::dragMoveEvent(pEvent); + setSourceOfCurrentDragDropEvent(nullptr); } - QTreeView::timerEvent(pEvent); } // Drag-and-drop "drop" event. Occurs when something is dropped onto the track sources view void WLibrarySidebar::dropEvent(QDropEvent* pEvent) { // qDebug() << "WLibrarySidebar::dropEvent"; - resetHoverIndexAndDragMoveResult(); + m_autoExpandIndex = QModelIndex(); toggleDragHoverPropertyAndUpdateStyle(false); - if (!pEvent->mimeData()->hasUrls()) { - pEvent->ignore(); - return; - } - // Drag and drop within this widget - if ((pEvent->source() == this) && (pEvent->possibleActions() & Qt::MoveAction)) { - // Do nothing. - pEvent->ignore(); - return; - } - // Drag-and-drop from an external application (eg. a file manager) or the - // track table widget onto the sidebar. - // Reset the selected items (if you had anything highlighted, it clears it) - // this->selectionModel()->clear(); - SidebarModel* pSidebarModel = qobject_cast(model()); - VERIFY_OR_DEBUG_ASSERT(pSidebarModel) { - pEvent->ignore(); - return; - } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - QPoint pos = pEvent->position().toPoint(); -#else - QPoint pos = pEvent->pos(); -#endif - - const QModelIndex destIndex = indexAt(pos); - // pEvent->source() will return NULL if something is dropped from - // a different application - const QList urls = pEvent->mimeData()->urls(); - if (pSidebarModel->dropAccept(destIndex, urls, pEvent->source())) { - pEvent->acceptProposedAction(); - } else { - pEvent->ignore(); - } + // QTreeView::dropEvent will, through some indirection, call + // SidebarModel::dropMimeData, which will call one of either + // LibraryFeature::dropAccept or LibraryFeature::dropAcceptChild, + // depending on where the drop occurred. + // + // Note: We go through QTreeView here instead of directly calling + // SidebarModel to retain other useful features from the base class, + // like e.g. auto-scroll behavior when the mouse cursor reaches + // the boundaries of the tree view. + // + // Note: pEvent->source() will be NULL if something is dropped + // from a different application. This knowledge is used + // inside the LibraryFeature implementations. + setSourceOfCurrentDragDropEvent(pEvent->source()); + QTreeView::dropEvent(pEvent); + setSourceOfCurrentDragDropEvent(nullptr); } void WLibrarySidebar::toggleDragHoverPropertyAndUpdateStyle(bool enabled) { @@ -200,11 +192,6 @@ void WLibrarySidebar::toggleDragHoverPropertyAndUpdateStyle(bool enabled) { update(); } -void WLibrarySidebar::resetHoverIndexAndDragMoveResult() { - m_hoverIndex = QModelIndex(); - m_lastDragMoveAccepted = false; -} - void WLibrarySidebar::renameSelectedItem() { // Rename crate or playlist (internal, external, history) QModelIndex selIndex = selectedIndex(); @@ -496,6 +483,7 @@ void WLibrarySidebar::slotSetFont(const QFont& font) { setIconSize(QSize(iconSize, iconSize)); } -void WLibrarySidebar::slotSetExpandOnHoverDelay(int delay) { - m_hoverExpandDelay = delay; +void WLibrarySidebar::slotSetExpandCollapseOnHoverDelay(int expandDelay, int collapseDelay) { + m_hoverExpandDelay = expandDelay; + m_hoverCollapseDelay = collapseDelay; } diff --git a/src/widget/wlibrarysidebar.h b/src/widget/wlibrarysidebar.h index 55e0baaf6892..0a3c88191b02 100644 --- a/src/widget/wlibrarysidebar.h +++ b/src/widget/wlibrarysidebar.h @@ -23,7 +23,6 @@ class WLibrarySidebar : public QTreeView, public WBaseWidget { void keyPressEvent(QKeyEvent* pEvent) override; void mousePressEvent(QMouseEvent* pEvent) override; void focusInEvent(QFocusEvent* pEvent) override; - void timerEvent(QTimerEvent* pEvent) override; void toggleSelectedItem(); void renameSelectedItem(); bool isLeafNodeSelected(); @@ -34,7 +33,7 @@ class WLibrarySidebar : public QTreeView, public WBaseWidget { void selectIndex(const QModelIndex& index, bool scrollToIndex = true); void selectChildIndex(const QModelIndex&, bool selectItem = true); void slotSetFont(const QFont& font); - void slotSetExpandOnHoverDelay(int delay); + void slotSetExpandCollapseOnHoverDelay(int expandDelay, int collapseDelay); signals: void rightClicked(const QPoint&, const QModelIndex&); @@ -51,10 +50,9 @@ class WLibrarySidebar : public QTreeView, public WBaseWidget { QModelIndex selectedIndex(); void toggleDragHoverPropertyAndUpdateStyle(bool enabled); - void resetHoverIndexAndDragMoveResult(); + void setSourceOfCurrentDragDropEvent(QObject* pSource); - QBasicTimer m_expandTimer; int m_hoverExpandDelay; - QModelIndex m_hoverIndex; - bool m_lastDragMoveAccepted; + int m_hoverCollapseDelay; + QModelIndex m_autoExpandIndex; };