diff --git a/src/base/utils/misc.cpp b/src/base/utils/misc.cpp index 8d6e87765..27d305452 100644 --- a/src/base/utils/misc.cpp +++ b/src/base/utils/misc.cpp @@ -47,6 +47,7 @@ #include #include +#include "base/net/downloadmanager.h" #include "base/path.h" #include "base/unicodestrings.h" #include "base/utils/string.h" @@ -212,6 +213,14 @@ bool Utils::Misc::isPreviewable(const Path &filePath) return multimediaExtensions.contains(filePath.extension().toUpper()); } +bool Utils::Misc::isTorrentLink(const QString &str) +{ + return str.startsWith(u"magnet:", Qt::CaseInsensitive) + || str.endsWith(TORRENT_FILE_EXTENSION, Qt::CaseInsensitive) + || (!str.startsWith(u"file:", Qt::CaseInsensitive) + && Net::DownloadManager::hasSupportedScheme(str)); +} + QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglong maxCap, const TimeResolution resolution) { if (seconds < 0) diff --git a/src/base/utils/misc.h b/src/base/utils/misc.h index d477c3d58..7a1f7b6a9 100644 --- a/src/base/utils/misc.h +++ b/src/base/utils/misc.h @@ -78,6 +78,7 @@ namespace Utils::Misc qint64 sizeInBytes(qreal size, SizeUnit unit); bool isPreviewable(const Path &filePath); + bool isTorrentLink(const QString &str); // Take a number of seconds and return a user-friendly // time duration like "1d 2h 10m". diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index b80d0cc70..9960ef4d6 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -43,7 +43,6 @@ #include #include #include -#include #include #include #include @@ -113,14 +112,6 @@ namespace #define EXECUTIONLOG_SETTINGS_KEY(name) (SETTINGS_KEY(u"Log/"_s) name) const std::chrono::seconds PREVENT_SUSPEND_INTERVAL {60}; - - bool isTorrentLink(const QString &str) - { - return str.startsWith(u"magnet:", Qt::CaseInsensitive) - || str.endsWith(TORRENT_FILE_EXTENSION, Qt::CaseInsensitive) - || (!str.startsWith(u"file:", Qt::CaseInsensitive) - && Net::DownloadManager::hasSupportedScheme(str)); - } } MainWindow::MainWindow(IGUIApplication *app, const WindowState initialState, const QString &titleSuffix) @@ -241,7 +232,7 @@ MainWindow::MainWindow(IGUIApplication *app, const WindowState initialState, con m_ui->toolBar->insertWidget(m_columnFilterAction, spacer); // Transfer List tab - m_transferListWidget = new TransferListWidget(hSplitter, this); + m_transferListWidget = new TransferListWidget(app, this); m_propertiesWidget = new PropertiesWidget(hSplitter); connect(m_transferListWidget, &TransferListWidget::currentTorrentChanged, m_propertiesWidget, &PropertiesWidget::loadTorrentInfos); hSplitter->addWidget(m_transferListWidget); @@ -347,8 +338,6 @@ MainWindow::MainWindow(IGUIApplication *app, const WindowState initialState, con connect(BitTorrent::Session::instance(), &BitTorrent::Session::statsUpdated, this, &MainWindow::loadSessionStats); connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsUpdated, this, &MainWindow::reloadTorrentStats); - // Accept drag 'n drops - setAcceptDrops(true); createKeyboardShortcuts(); #ifdef Q_OS_MACOS @@ -1124,16 +1113,13 @@ void MainWindow::keyPressEvent(QKeyEvent *event) if (event->matches(QKeySequence::Paste)) { const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData(); - if (mimeData->hasText()) { const QStringList lines = mimeData->text().split(u'\n', Qt::SkipEmptyParts); - for (QString line : lines) { line = line.trimmed(); - - if (!isTorrentLink(line)) + if (!Utils::Misc::isTorrentLink(line)) continue; app()->addTorrentManager()->addTorrent(line); @@ -1284,66 +1270,6 @@ bool MainWindow::event(QEvent *e) return QMainWindow::event(e); } -// action executed when a file is dropped -void MainWindow::dropEvent(QDropEvent *event) -{ - event->acceptProposedAction(); - - // remove scheme - QStringList files; - if (event->mimeData()->hasUrls()) - { - for (const QUrl &url : asConst(event->mimeData()->urls())) - { - if (url.isEmpty()) - continue; - - files << ((url.scheme().compare(u"file", Qt::CaseInsensitive) == 0) - ? url.toLocalFile() - : url.toString()); - } - } - else - { - files = event->mimeData()->text().split(u'\n'); - } - - // differentiate ".torrent" files/links & magnet links from others - QStringList torrentFiles, otherFiles; - for (const QString &file : asConst(files)) - { - if (isTorrentLink(file)) - torrentFiles << file; - else - otherFiles << file; - } - - // Download torrents - for (const QString &file : asConst(torrentFiles)) - app()->addTorrentManager()->addTorrent(file); - if (!torrentFiles.isEmpty()) return; - - // Create torrent - for (const QString &file : asConst(otherFiles)) - { - createTorrentTriggered(Path(file)); - - // currently only handle the first entry - // this is a stub that can be expanded later to create many torrents at once - break; - } -} - -// Decode if we accept drag 'n drop or not -void MainWindow::dragEnterEvent(QDragEnterEvent *event) -{ - for (const QString &mime : asConst(event->mimeData()->formats())) - qDebug("mimeData: %s", mime.toLocal8Bit().data()); - - if (event->mimeData()->hasFormat(u"text/plain"_s) || event->mimeData()->hasFormat(u"text/uri-list"_s)) - event->acceptProposedAction(); -} - // Display a dialog to allow user to add // torrents to download list void MainWindow::on_actionOpen_triggered() diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index c4df799cc..68b7f349a 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -191,8 +191,6 @@ private: void installPython(); #endif - void dropEvent(QDropEvent *event) override; - void dragEnterEvent(QDragEnterEvent *event) override; void closeEvent(QCloseEvent *) override; void showEvent(QShowEvent *) override; void keyPressEvent(QKeyEvent *event) override; diff --git a/src/gui/transferlistwidget.cpp b/src/gui/transferlistwidget.cpp index e765b9e38..9435b05be 100644 --- a/src/gui/transferlistwidget.cpp +++ b/src/gui/transferlistwidget.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -57,11 +58,13 @@ #include "base/utils/string.h" #include "autoexpandabledialog.h" #include "deletionconfirmationdialog.h" +#include "interfaces/iguiapplication.h" #include "mainwindow.h" #include "optionsdialog.h" #include "previewselectdialog.h" #include "speedlimitdialog.h" #include "torrentcategorydialog.h" +#include "torrentcreatordialog.h" #include "torrentoptionsdialog.h" #include "trackerentriesdialog.h" #include "transferlistdelegate.h" @@ -123,11 +126,10 @@ namespace } } -TransferListWidget::TransferListWidget(QWidget *parent, MainWindow *mainWindow) - : QTreeView {parent} +TransferListWidget::TransferListWidget(IGUIApplication *app, QWidget *parent) + : GUIApplicationComponent(app, parent) , m_listModel {new TransferListModel {this}} , m_sortFilterModel {new TransferListSortModel {this}} - , m_mainWindow {mainWindow} { // Load settings const bool columnLoaded = loadSettings(); @@ -151,7 +153,9 @@ TransferListWidget::TransferListWidget(QWidget *parent, MainWindow *mainWindow) setSelectionMode(QAbstractItemView::ExtendedSelection); setItemsExpandable(false); setAutoScroll(true); - setDragDropMode(QAbstractItemView::DragOnly); + setAcceptDrops(true); + setDragDropMode(QAbstractItemView::DropOnly); + setDropIndicatorShown(true); #if defined(Q_OS_MACOS) setAttribute(Qt::WA_MacShowFocusRect, false); #endif @@ -431,7 +435,7 @@ void TransferListWidget::permDeleteSelectedTorrents() void TransferListWidget::deleteSelectedTorrents(const bool deleteLocalFiles) { - if (m_mainWindow->currentTabWidget() != this) return; + if (app()->mainWindow()->currentTabWidget() != this) return; const QList torrents = getSelectedTorrents(); if (torrents.empty()) return; @@ -480,26 +484,26 @@ void TransferListWidget::deleteVisibleTorrents() void TransferListWidget::increaseQueuePosSelectedTorrents() { qDebug() << Q_FUNC_INFO; - if (m_mainWindow->currentTabWidget() == this) + if (app()->mainWindow()->currentTabWidget() == this) BitTorrent::Session::instance()->increaseTorrentsQueuePos(extractIDs(getSelectedTorrents())); } void TransferListWidget::decreaseQueuePosSelectedTorrents() { qDebug() << Q_FUNC_INFO; - if (m_mainWindow->currentTabWidget() == this) + if (app()->mainWindow()->currentTabWidget() == this) BitTorrent::Session::instance()->decreaseTorrentsQueuePos(extractIDs(getSelectedTorrents())); } void TransferListWidget::topQueuePosSelectedTorrents() { - if (m_mainWindow->currentTabWidget() == this) + if (app()->mainWindow()->currentTabWidget() == this) BitTorrent::Session::instance()->topTorrentsQueuePos(extractIDs(getSelectedTorrents())); } void TransferListWidget::bottomQueuePosSelectedTorrents() { - if (m_mainWindow->currentTabWidget() == this) + if (app()->mainWindow()->currentTabWidget() == this) BitTorrent::Session::instance()->bottomTorrentsQueuePos(extractIDs(getSelectedTorrents())); } @@ -1354,6 +1358,79 @@ bool TransferListWidget::loadSettings() return header()->restoreState(Preferences::instance()->getTransHeaderState()); } +void TransferListWidget::dragEnterEvent(QDragEnterEvent *event) +{ + if (const QMimeData *data = event->mimeData(); data->hasText() || data->hasUrls()) + { + event->setDropAction(Qt::CopyAction); + event->accept(); + } +} + +void TransferListWidget::dragMoveEvent(QDragMoveEvent *event) +{ + event->acceptProposedAction(); // required, otherwise we won't get `dropEvent` +} + +void TransferListWidget::dropEvent(QDropEvent *event) +{ + event->acceptProposedAction(); + // remove scheme + QStringList files; + if (const QMimeData *data = event->mimeData(); data->hasUrls()) + { + const QList urls = data->urls(); + files.reserve(urls.size()); + + for (const QUrl &url : urls) + { + if (url.isEmpty()) + continue; + + files.append(url.isLocalFile() + ? url.toLocalFile() + : url.toString()); + } + } + else + { + files = data->text().split(u'\n', Qt::SkipEmptyParts); + } + + // differentiate ".torrent" files/links & magnet links from others + QStringList torrentFiles, otherFiles; + torrentFiles.reserve(files.size()); + otherFiles.reserve(files.size()); + for (const QString &file : asConst(files)) + { + if (Utils::Misc::isTorrentLink(file)) + torrentFiles << file; + else + otherFiles << file; + } + + // Download torrents + if (!torrentFiles.isEmpty()) + { + for (const QString &file : asConst(torrentFiles)) + app()->addTorrentManager()->addTorrent(file); + + return; + } + + // Create torrent + for (const QString &file : asConst(otherFiles)) + { + auto torrentCreator = new TorrentCreatorDialog(this, Path(file)); + torrentCreator->setAttribute(Qt::WA_DeleteOnClose); + torrentCreator->show(); + + // currently only handle the first entry + // this is a stub that can be expanded later to create many torrents at once + break; + } +} + void TransferListWidget::wheelEvent(QWheelEvent *event) { if (event->modifiers() & Qt::ShiftModifier) diff --git a/src/gui/transferlistwidget.h b/src/gui/transferlistwidget.h index 1caf965fb..c5b24ba24 100644 --- a/src/gui/transferlistwidget.h +++ b/src/gui/transferlistwidget.h @@ -35,9 +35,9 @@ #include #include "base/bittorrent/infohash.h" +#include "guiapplicationcomponent.h" #include "transferlistmodel.h" -class MainWindow; class Path; class TransferListSortModel; @@ -52,13 +52,13 @@ enum class CopyInfohashPolicy Version2 }; -class TransferListWidget final : public QTreeView +class TransferListWidget final : public GUIApplicationComponent { Q_OBJECT Q_DISABLE_COPY_MOVE(TransferListWidget) public: - TransferListWidget(QWidget *parent, MainWindow *mainWindow); + TransferListWidget(IGUIApplication *app, QWidget *parent); ~TransferListWidget() override; TransferListModel *getSourceModel() const; @@ -119,6 +119,9 @@ private slots: void saveSettings(); private: + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dropEvent(QDropEvent *event) override; void wheelEvent(QWheelEvent *event) override; QModelIndex mapToSource(const QModelIndex &index) const; QModelIndexList mapToSource(const QModelIndexList &indexes) const; @@ -136,5 +139,4 @@ private: TransferListModel *m_listModel = nullptr; TransferListSortModel *m_sortFilterModel = nullptr; - MainWindow *m_mainWindow = nullptr; };