Allow drop action only on transfer list

Now drop action is only allowed on transfer list, previously it was on main window.
Having drop action on the whole main window is not preferred because it could allow drop action
on other unrelated widgets, such as execution log or RSS widget which is unexpected behavior.

PR #21332.
This commit is contained in:
Chocobo1 2024-09-30 16:59:57 +08:00 committed by GitHub
commit 10eb921d70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 104 additions and 91 deletions

View file

@ -47,6 +47,7 @@
#include <QStringView> #include <QStringView>
#include <QSysInfo> #include <QSysInfo>
#include "base/net/downloadmanager.h"
#include "base/path.h" #include "base/path.h"
#include "base/unicodestrings.h" #include "base/unicodestrings.h"
#include "base/utils/string.h" #include "base/utils/string.h"
@ -212,6 +213,14 @@ bool Utils::Misc::isPreviewable(const Path &filePath)
return multimediaExtensions.contains(filePath.extension().toUpper()); 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) QString Utils::Misc::userFriendlyDuration(const qlonglong seconds, const qlonglong maxCap, const TimeResolution resolution)
{ {
if (seconds < 0) if (seconds < 0)

View file

@ -78,6 +78,7 @@ namespace Utils::Misc
qint64 sizeInBytes(qreal size, SizeUnit unit); qint64 sizeInBytes(qreal size, SizeUnit unit);
bool isPreviewable(const Path &filePath); bool isPreviewable(const Path &filePath);
bool isTorrentLink(const QString &str);
// Take a number of seconds and return a user-friendly // Take a number of seconds and return a user-friendly
// time duration like "1d 2h 10m". // time duration like "1d 2h 10m".

View file

@ -43,7 +43,6 @@
#include <QDesktopServices> #include <QDesktopServices>
#include <QFileDialog> #include <QFileDialog>
#include <QFileSystemWatcher> #include <QFileSystemWatcher>
#include <QKeyEvent>
#include <QLabel> #include <QLabel>
#include <QMenu> #include <QMenu>
#include <QMessageBox> #include <QMessageBox>
@ -113,14 +112,6 @@ namespace
#define EXECUTIONLOG_SETTINGS_KEY(name) (SETTINGS_KEY(u"Log/"_s) name) #define EXECUTIONLOG_SETTINGS_KEY(name) (SETTINGS_KEY(u"Log/"_s) name)
const std::chrono::seconds PREVENT_SUSPEND_INTERVAL {60}; 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) 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); m_ui->toolBar->insertWidget(m_columnFilterAction, spacer);
// Transfer List tab // Transfer List tab
m_transferListWidget = new TransferListWidget(hSplitter, this); m_transferListWidget = new TransferListWidget(app, this);
m_propertiesWidget = new PropertiesWidget(hSplitter); m_propertiesWidget = new PropertiesWidget(hSplitter);
connect(m_transferListWidget, &TransferListWidget::currentTorrentChanged, m_propertiesWidget, &PropertiesWidget::loadTorrentInfos); connect(m_transferListWidget, &TransferListWidget::currentTorrentChanged, m_propertiesWidget, &PropertiesWidget::loadTorrentInfos);
hSplitter->addWidget(m_transferListWidget); 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::statsUpdated, this, &MainWindow::loadSessionStats);
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsUpdated, this, &MainWindow::reloadTorrentStats); connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsUpdated, this, &MainWindow::reloadTorrentStats);
// Accept drag 'n drops
setAcceptDrops(true);
createKeyboardShortcuts(); createKeyboardShortcuts();
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
@ -1124,16 +1113,13 @@ void MainWindow::keyPressEvent(QKeyEvent *event)
if (event->matches(QKeySequence::Paste)) if (event->matches(QKeySequence::Paste))
{ {
const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData(); const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData();
if (mimeData->hasText()) if (mimeData->hasText())
{ {
const QStringList lines = mimeData->text().split(u'\n', Qt::SkipEmptyParts); const QStringList lines = mimeData->text().split(u'\n', Qt::SkipEmptyParts);
for (QString line : lines) for (QString line : lines)
{ {
line = line.trimmed(); line = line.trimmed();
if (!Utils::Misc::isTorrentLink(line))
if (!isTorrentLink(line))
continue; continue;
app()->addTorrentManager()->addTorrent(line); app()->addTorrentManager()->addTorrent(line);
@ -1284,66 +1270,6 @@ bool MainWindow::event(QEvent *e)
return QMainWindow::event(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 // Display a dialog to allow user to add
// torrents to download list // torrents to download list
void MainWindow::on_actionOpen_triggered() void MainWindow::on_actionOpen_triggered()

View file

@ -191,8 +191,6 @@ private:
void installPython(); void installPython();
#endif #endif
void dropEvent(QDropEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void closeEvent(QCloseEvent *) override; void closeEvent(QCloseEvent *) override;
void showEvent(QShowEvent *) override; void showEvent(QShowEvent *) override;
void keyPressEvent(QKeyEvent *event) override; void keyPressEvent(QKeyEvent *event) override;

View file

@ -38,6 +38,7 @@
#include <QList> #include <QList>
#include <QMenu> #include <QMenu>
#include <QMessageBox> #include <QMessageBox>
#include <QMimeData>
#include <QRegularExpression> #include <QRegularExpression>
#include <QSet> #include <QSet>
#include <QShortcut> #include <QShortcut>
@ -57,11 +58,13 @@
#include "base/utils/string.h" #include "base/utils/string.h"
#include "autoexpandabledialog.h" #include "autoexpandabledialog.h"
#include "deletionconfirmationdialog.h" #include "deletionconfirmationdialog.h"
#include "interfaces/iguiapplication.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "optionsdialog.h" #include "optionsdialog.h"
#include "previewselectdialog.h" #include "previewselectdialog.h"
#include "speedlimitdialog.h" #include "speedlimitdialog.h"
#include "torrentcategorydialog.h" #include "torrentcategorydialog.h"
#include "torrentcreatordialog.h"
#include "torrentoptionsdialog.h" #include "torrentoptionsdialog.h"
#include "trackerentriesdialog.h" #include "trackerentriesdialog.h"
#include "transferlistdelegate.h" #include "transferlistdelegate.h"
@ -123,11 +126,10 @@ namespace
} }
} }
TransferListWidget::TransferListWidget(QWidget *parent, MainWindow *mainWindow) TransferListWidget::TransferListWidget(IGUIApplication *app, QWidget *parent)
: QTreeView {parent} : GUIApplicationComponent(app, parent)
, m_listModel {new TransferListModel {this}} , m_listModel {new TransferListModel {this}}
, m_sortFilterModel {new TransferListSortModel {this}} , m_sortFilterModel {new TransferListSortModel {this}}
, m_mainWindow {mainWindow}
{ {
// Load settings // Load settings
const bool columnLoaded = loadSettings(); const bool columnLoaded = loadSettings();
@ -151,7 +153,9 @@ TransferListWidget::TransferListWidget(QWidget *parent, MainWindow *mainWindow)
setSelectionMode(QAbstractItemView::ExtendedSelection); setSelectionMode(QAbstractItemView::ExtendedSelection);
setItemsExpandable(false); setItemsExpandable(false);
setAutoScroll(true); setAutoScroll(true);
setDragDropMode(QAbstractItemView::DragOnly); setAcceptDrops(true);
setDragDropMode(QAbstractItemView::DropOnly);
setDropIndicatorShown(true);
#if defined(Q_OS_MACOS) #if defined(Q_OS_MACOS)
setAttribute(Qt::WA_MacShowFocusRect, false); setAttribute(Qt::WA_MacShowFocusRect, false);
#endif #endif
@ -431,7 +435,7 @@ void TransferListWidget::permDeleteSelectedTorrents()
void TransferListWidget::deleteSelectedTorrents(const bool deleteLocalFiles) void TransferListWidget::deleteSelectedTorrents(const bool deleteLocalFiles)
{ {
if (m_mainWindow->currentTabWidget() != this) return; if (app()->mainWindow()->currentTabWidget() != this) return;
const QList<BitTorrent::Torrent *> torrents = getSelectedTorrents(); const QList<BitTorrent::Torrent *> torrents = getSelectedTorrents();
if (torrents.empty()) return; if (torrents.empty()) return;
@ -480,26 +484,26 @@ void TransferListWidget::deleteVisibleTorrents()
void TransferListWidget::increaseQueuePosSelectedTorrents() void TransferListWidget::increaseQueuePosSelectedTorrents()
{ {
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
if (m_mainWindow->currentTabWidget() == this) if (app()->mainWindow()->currentTabWidget() == this)
BitTorrent::Session::instance()->increaseTorrentsQueuePos(extractIDs(getSelectedTorrents())); BitTorrent::Session::instance()->increaseTorrentsQueuePos(extractIDs(getSelectedTorrents()));
} }
void TransferListWidget::decreaseQueuePosSelectedTorrents() void TransferListWidget::decreaseQueuePosSelectedTorrents()
{ {
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
if (m_mainWindow->currentTabWidget() == this) if (app()->mainWindow()->currentTabWidget() == this)
BitTorrent::Session::instance()->decreaseTorrentsQueuePos(extractIDs(getSelectedTorrents())); BitTorrent::Session::instance()->decreaseTorrentsQueuePos(extractIDs(getSelectedTorrents()));
} }
void TransferListWidget::topQueuePosSelectedTorrents() void TransferListWidget::topQueuePosSelectedTorrents()
{ {
if (m_mainWindow->currentTabWidget() == this) if (app()->mainWindow()->currentTabWidget() == this)
BitTorrent::Session::instance()->topTorrentsQueuePos(extractIDs(getSelectedTorrents())); BitTorrent::Session::instance()->topTorrentsQueuePos(extractIDs(getSelectedTorrents()));
} }
void TransferListWidget::bottomQueuePosSelectedTorrents() void TransferListWidget::bottomQueuePosSelectedTorrents()
{ {
if (m_mainWindow->currentTabWidget() == this) if (app()->mainWindow()->currentTabWidget() == this)
BitTorrent::Session::instance()->bottomTorrentsQueuePos(extractIDs(getSelectedTorrents())); BitTorrent::Session::instance()->bottomTorrentsQueuePos(extractIDs(getSelectedTorrents()));
} }
@ -1354,6 +1358,79 @@ bool TransferListWidget::loadSettings()
return header()->restoreState(Preferences::instance()->getTransHeaderState()); 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<QUrl> 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) void TransferListWidget::wheelEvent(QWheelEvent *event)
{ {
if (event->modifiers() & Qt::ShiftModifier) if (event->modifiers() & Qt::ShiftModifier)

View file

@ -35,9 +35,9 @@
#include <QTreeView> #include <QTreeView>
#include "base/bittorrent/infohash.h" #include "base/bittorrent/infohash.h"
#include "guiapplicationcomponent.h"
#include "transferlistmodel.h" #include "transferlistmodel.h"
class MainWindow;
class Path; class Path;
class TransferListSortModel; class TransferListSortModel;
@ -52,13 +52,13 @@ enum class CopyInfohashPolicy
Version2 Version2
}; };
class TransferListWidget final : public QTreeView class TransferListWidget final : public GUIApplicationComponent<QTreeView>
{ {
Q_OBJECT Q_OBJECT
Q_DISABLE_COPY_MOVE(TransferListWidget) Q_DISABLE_COPY_MOVE(TransferListWidget)
public: public:
TransferListWidget(QWidget *parent, MainWindow *mainWindow); TransferListWidget(IGUIApplication *app, QWidget *parent);
~TransferListWidget() override; ~TransferListWidget() override;
TransferListModel *getSourceModel() const; TransferListModel *getSourceModel() const;
@ -119,6 +119,9 @@ private slots:
void saveSettings(); void saveSettings();
private: private:
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dropEvent(QDropEvent *event) override;
void wheelEvent(QWheelEvent *event) override; void wheelEvent(QWheelEvent *event) override;
QModelIndex mapToSource(const QModelIndex &index) const; QModelIndex mapToSource(const QModelIndex &index) const;
QModelIndexList mapToSource(const QModelIndexList &indexes) const; QModelIndexList mapToSource(const QModelIndexList &indexes) const;
@ -136,5 +139,4 @@ private:
TransferListModel *m_listModel = nullptr; TransferListModel *m_listModel = nullptr;
TransferListSortModel *m_sortFilterModel = nullptr; TransferListSortModel *m_sortFilterModel = nullptr;
MainWindow *m_mainWindow = nullptr;
}; };