Browse Source

Revamp torrent content widget

PR #18162.
adaptive-webui-19844
Vladimir Golovnev 2 years ago committed by GitHub
parent
commit
1be5b3abd8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/base/CMakeLists.txt
  2. 2
      src/base/base.pri
  3. 15
      src/base/bittorrent/torrent.h
  4. 29
      src/base/bittorrent/torrentcontenthandler.cpp
  5. 62
      src/base/bittorrent/torrentcontenthandler.h
  6. 8
      src/gui/CMakeLists.txt
  7. 499
      src/gui/addnewtorrentdialog.cpp
  8. 15
      src/gui/addnewtorrentdialog.h
  9. 6
      src/gui/addnewtorrentdialog.ui
  10. 8
      src/gui/gui.pri
  11. 329
      src/gui/properties/propertieswidget.cpp
  12. 14
      src/gui/properties/propertieswidget.h
  13. 6
      src/gui/properties/propertieswidget.ui
  14. 32
      src/gui/torrentcontentfiltermodel.cpp
  15. 17
      src/gui/torrentcontentfiltermodel.h
  16. 35
      src/gui/torrentcontentitemdelegate.cpp
  17. 24
      src/gui/torrentcontentitemdelegate.h
  18. 274
      src/gui/torrentcontentmodel.cpp
  19. 31
      src/gui/torrentcontentmodel.h
  20. 2
      src/gui/torrentcontentmodelfolder.h
  21. 165
      src/gui/torrentcontenttreeview.cpp
  22. 489
      src/gui/torrentcontentwidget.cpp
  23. 121
      src/gui/torrentcontentwidget.h

2
src/base/CMakeLists.txt

@ -34,6 +34,7 @@ add_library(qbt_base STATIC @@ -34,6 +34,7 @@ add_library(qbt_base STATIC
bittorrent/sessionstatus.h
bittorrent/speedmonitor.h
bittorrent/torrent.h
bittorrent/torrentcontenthandler.h
bittorrent/torrentcontentlayout.h
bittorrent/torrentcreatorthread.h
bittorrent/torrentimpl.h
@ -129,6 +130,7 @@ add_library(qbt_base STATIC @@ -129,6 +130,7 @@ add_library(qbt_base STATIC
bittorrent/sessionimpl.cpp
bittorrent/speedmonitor.cpp
bittorrent/torrent.cpp
bittorrent/torrentcontenthandler.cpp
bittorrent/torrentcreatorthread.cpp
bittorrent/torrentimpl.cpp
bittorrent/torrentinfo.cpp

2
src/base/base.pri

@ -34,6 +34,7 @@ HEADERS += \ @@ -34,6 +34,7 @@ HEADERS += \
$$PWD/bittorrent/speedmonitor.h \
$$PWD/bittorrent/torrent.h \
$$PWD/bittorrent/torrentcontentlayout.h \
$$PWD/bittorrent/torrentcontenthandler.h \
$$PWD/bittorrent/torrentcreatorthread.h \
$$PWD/bittorrent/torrentimpl.h \
$$PWD/bittorrent/torrentinfo.h \
@ -129,6 +130,7 @@ SOURCES += \ @@ -129,6 +130,7 @@ SOURCES += \
$$PWD/bittorrent/sessionimpl.cpp \
$$PWD/bittorrent/speedmonitor.cpp \
$$PWD/bittorrent/torrent.cpp \
$$PWD/bittorrent/torrentcontenthandler.h \
$$PWD/bittorrent/torrentcreatorthread.cpp \
$$PWD/bittorrent/torrentimpl.cpp \
$$PWD/bittorrent/torrentinfo.cpp \

15
src/base/bittorrent/torrent.h

@ -37,7 +37,7 @@ @@ -37,7 +37,7 @@
#include "base/3rdparty/expected.hpp"
#include "base/pathfwd.h"
#include "base/tagset.h"
#include "abstractfilestorage.h"
#include "torrentcontenthandler.h"
class QBitArray;
class QByteArray;
@ -106,7 +106,7 @@ namespace BitTorrent @@ -106,7 +106,7 @@ namespace BitTorrent
uint qHash(TorrentState key, uint seed = 0);
#endif
class Torrent : public QObject, public AbstractFileStorage
class Torrent : public TorrentContentHandler
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(Torrent)
@ -129,7 +129,7 @@ namespace BitTorrent @@ -129,7 +129,7 @@ namespace BitTorrent
static const qreal MAX_RATIO;
static const int MAX_SEEDING_TIME;
using QObject::QObject;
using TorrentContentHandler::TorrentContentHandler;
virtual InfoHash infoHash() const = 0;
virtual QString name() const = 0;
@ -293,7 +293,6 @@ namespace BitTorrent @@ -293,7 +293,6 @@ namespace BitTorrent
virtual void forceReannounce(int index = -1) = 0;
virtual void forceDHTAnnounce() = 0;
virtual void forceRecheck() = 0;
virtual void prioritizeFiles(const QVector<DownloadPriority> &priorities) = 0;
virtual void setRatioLimit(qreal limit) = 0;
virtual void setSeedingTimeLimit(int limit) = 0;
virtual void setUploadLimit(int limit) = 0;
@ -321,16 +320,8 @@ namespace BitTorrent @@ -321,16 +320,8 @@ namespace BitTorrent
virtual void fetchPeerInfo(std::function<void (QVector<PeerInfo>)> resultHandler) const = 0;
virtual void fetchURLSeeds(std::function<void (QVector<QUrl>)> resultHandler) const = 0;
virtual void fetchFilesProgress(std::function<void (QVector<qreal>)> resultHandler) const = 0;
virtual void fetchPieceAvailability(std::function<void (QVector<int>)> resultHandler) const = 0;
virtual void fetchDownloadingPieces(std::function<void (QBitArray)> resultHandler) const = 0;
/**
* @brief fraction of file pieces that are available at least from one peer
*
* This is not the same as torrrent availability, it is just a fraction of pieces
* that can be downloaded right now. It varies between 0 to 1.
*/
virtual void fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const = 0;
TorrentID id() const;
bool isResumed() const;

29
src/gui/torrentcontenttreeview.h → src/base/bittorrent/torrentcontenthandler.cpp

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2014 Ivan Sorokin <vanyacpp@gmail.com>
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -26,29 +26,4 @@ @@ -26,29 +26,4 @@
* exception statement from your version.
*/
#pragma once
#include <QTreeView>
namespace BitTorrent
{
class AbstractFileStorage;
class Torrent;
class TorrentInfo;
}
class TorrentContentTreeView final : public QTreeView
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(TorrentContentTreeView)
public:
explicit TorrentContentTreeView(QWidget *parent = nullptr);
void keyPressEvent(QKeyEvent *event) override;
void renameSelectedFile(BitTorrent::AbstractFileStorage &fileStorage);
private:
QModelIndex currentNameCell() const;
void wheelEvent(QWheelEvent *event) override;
};
#include "torrentcontenthandler.h"

62
src/base/bittorrent/torrentcontenthandler.h

@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#pragma once
#include <QObject>
#include "base/pathfwd.h"
#include "abstractfilestorage.h"
#include "downloadpriority.h"
namespace BitTorrent
{
class TorrentContentHandler : public QObject, public AbstractFileStorage
{
public:
using QObject::QObject;
virtual bool hasMetadata() const = 0;
virtual Path actualStorageLocation() const = 0;
virtual Path actualFilePath(int fileIndex) const = 0;
virtual QVector<DownloadPriority> filePriorities() const = 0;
virtual QVector<qreal> filesProgress() const = 0;
virtual void fetchFilesProgress(std::function<void (QVector<qreal>)> resultHandler) const = 0;
/**
* @brief fraction of file pieces that are available at least from one peer
*
* This is not the same as torrrent availability, it is just a fraction of pieces
* that can be downloaded right now. It varies between 0 to 1.
*/
virtual QVector<qreal> availableFileFractions() const = 0;
virtual void fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const = 0;
virtual void prioritizeFiles(const QVector<DownloadPriority> &priorities) = 0;
virtual void flushCache() const = 0;
};
}

8
src/gui/CMakeLists.txt

@ -80,7 +80,6 @@ add_library(qbt_gui STATIC @@ -80,7 +80,6 @@ add_library(qbt_gui STATIC
properties/pieceavailabilitybar.h
properties/piecesbar.h
properties/propertieswidget.h
properties/proplistdelegate.h
properties/proptabbar.h
properties/speedplotview.h
properties/speedwidget.h
@ -106,11 +105,12 @@ add_library(qbt_gui STATIC @@ -106,11 +105,12 @@ add_library(qbt_gui STATIC
tagfilterwidget.h
torrentcategorydialog.h
torrentcontentfiltermodel.h
torrentcontentitemdelegate.h
torrentcontentmodel.h
torrentcontentmodelfile.h
torrentcontentmodelfolder.h
torrentcontentmodelitem.h
torrentcontenttreeview.h
torrentcontentwidget.h
torrentcreatordialog.h
torrentoptionsdialog.h
trackerentriesdialog.h
@ -164,7 +164,6 @@ add_library(qbt_gui STATIC @@ -164,7 +164,6 @@ add_library(qbt_gui STATIC
properties/pieceavailabilitybar.cpp
properties/piecesbar.cpp
properties/propertieswidget.cpp
properties/proplistdelegate.cpp
properties/proptabbar.cpp
properties/speedplotview.cpp
properties/speedwidget.cpp
@ -190,11 +189,12 @@ add_library(qbt_gui STATIC @@ -190,11 +189,12 @@ add_library(qbt_gui STATIC
tagfilterwidget.cpp
torrentcategorydialog.cpp
torrentcontentfiltermodel.cpp
torrentcontentitemdelegate.cpp
torrentcontentmodel.cpp
torrentcontentmodelfile.cpp
torrentcontentmodelfolder.cpp
torrentcontentmodelitem.cpp
torrentcontenttreeview.cpp
torrentcontentwidget.cpp
torrentcreatordialog.cpp
torrentoptionsdialog.cpp
trackerentriesdialog.cpp

499
src/gui/addnewtorrentdialog.cpp

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -46,6 +47,7 @@ @@ -46,6 +47,7 @@
#include "base/bittorrent/magneturi.h"
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrent.h"
#include "base/bittorrent/torrentcontenthandler.h"
#include "base/bittorrent/torrentcontentlayout.h"
#include "base/global.h"
#include "base/net/downloadmanager.h"
@ -54,15 +56,10 @@ @@ -54,15 +56,10 @@
#include "base/utils/compare.h"
#include "base/utils/fs.h"
#include "base/utils/misc.h"
#include "autoexpandabledialog.h"
#include "lineedit.h"
#include "properties/proplistdelegate.h"
#include "raisedmessagebox.h"
#include "torrentcontentfiltermodel.h"
#include "torrentcontentmodel.h"
#include "ui_addnewtorrentdialog.h"
#include "uithememanager.h"
#include "utils.h"
namespace
{
@ -79,14 +76,95 @@ namespace @@ -79,14 +76,95 @@ namespace
return SettingsStorage::instance();
}
class FileStorageAdaptor final : public BitTorrent::AbstractFileStorage
// savePath is a folder, not an absolute file path
int indexOfPath(const FileSystemPathComboEdit *fsPathEdit, const Path &savePath)
{
for (int i = 0; i < fsPathEdit->count(); ++i)
{
if (fsPathEdit->item(i) == savePath)
return i;
}
return -1;
}
qint64 queryFreeDiskSpace(const Path &path)
{
const Path root = path.rootItem();
Path current = path;
qint64 freeSpace = Utils::Fs::freeDiskSpaceOnPath(current);
// for non-existent directories (which will be created on demand) `Utils::Fs::freeDiskSpaceOnPath`
// will return invalid value so instead query its parent/ancestor paths
while ((freeSpace < 0) && (current != root))
{
current = current.parentPath();
freeSpace = Utils::Fs::freeDiskSpaceOnPath(current);
}
return freeSpace;
}
void setPath(FileSystemPathComboEdit *fsPathEdit, const Path &newPath)
{
int existingIndex = indexOfPath(fsPathEdit, newPath);
if (existingIndex < 0)
{
// New path, prepend to combo box
fsPathEdit->insertItem(0, newPath);
existingIndex = 0;
}
fsPathEdit->setCurrentIndex(existingIndex);
}
void updatePathHistory(const QString &settingsKey, const Path &path, const int maxLength)
{
public:
FileStorageAdaptor(const BitTorrent::TorrentInfo &torrentInfo, PathList &filePaths)
// Add last used save path to the front of history
auto pathList = settings()->loadValue<QStringList>(settingsKey);
const int selectedSavePathIndex = pathList.indexOf(path.toString());
if (selectedSavePathIndex > -1)
pathList.move(selectedSavePathIndex, 0);
else
pathList.prepend(path.toString());
settings()->storeValue(settingsKey, QStringList(pathList.mid(0, maxLength)));
}
}
class AddNewTorrentDialog::TorrentContentAdaptor final
: public BitTorrent::TorrentContentHandler
{
public:
TorrentContentAdaptor(BitTorrent::TorrentInfo &torrentInfo, PathList &filePaths
, QVector<BitTorrent::DownloadPriority> &filePriorities)
: m_torrentInfo {torrentInfo}
, m_filePaths {filePaths}
, m_filePriorities {filePriorities}
{
Q_ASSERT(filePaths.isEmpty() || (filePaths.size() == torrentInfo.filesCount()));
m_originalRootFolder = Path::findRootFolder(m_torrentInfo.filePaths());
m_currentContentLayout = (m_originalRootFolder.isEmpty()
? BitTorrent::TorrentContentLayout::NoSubfolder
: BitTorrent::TorrentContentLayout::Subfolder);
if (!m_filePriorities.isEmpty())
{
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
const int currentSize = m_filePriorities.size();
m_filePriorities.resize(filesCount());
for (int i = currentSize; i < filesCount(); ++i)
m_filePriorities[i] = BitTorrent::DownloadPriority::Normal;
#else
m_filePriorities.resize(filesCount(), BitTorrent::DownloadPriority::Normal);
#endif
}
}
bool hasMetadata() const override
{
return m_torrentInfo.isValid();
}
int filesCount() const override
@ -119,66 +197,88 @@ namespace @@ -119,66 +197,88 @@ namespace
m_filePaths[index] = newFilePath;
}
private:
const BitTorrent::TorrentInfo &m_torrentInfo;
PathList &m_filePaths;
};
void applyContentLayout(const BitTorrent::TorrentContentLayout contentLayout)
{
Q_ASSERT(hasMetadata());
Q_ASSERT(!m_filePaths.isEmpty());
// savePath is a folder, not an absolute file path
int indexOfPath(const FileSystemPathComboEdit *fsPathEdit, const Path &savePath)
const auto originalContentLayout = (m_originalRootFolder.isEmpty()
? BitTorrent::TorrentContentLayout::NoSubfolder
: BitTorrent::TorrentContentLayout::Subfolder);
const auto newContentLayout = ((contentLayout == BitTorrent::TorrentContentLayout::Original)
? originalContentLayout : contentLayout);
if (newContentLayout != m_currentContentLayout)
{
for (int i = 0; i < fsPathEdit->count(); ++i)
if (newContentLayout == BitTorrent::TorrentContentLayout::NoSubfolder)
{
if (fsPathEdit->item(i) == savePath)
return i;
Path::stripRootFolder(m_filePaths);
}
return -1;
else
{
const auto rootFolder = ((originalContentLayout == BitTorrent::TorrentContentLayout::Subfolder)
? m_originalRootFolder
: m_filePaths.at(0).removedExtension());
Path::addRootFolder(m_filePaths, rootFolder);
}
qint64 queryFreeDiskSpace(const Path &path)
{
const Path root = path.rootItem();
Path current = path;
qint64 freeSpace = Utils::Fs::freeDiskSpaceOnPath(current);
m_currentContentLayout = newContentLayout;
}
}
// for non-existent directories (which will be created on demand) `Utils::Fs::freeDiskSpaceOnPath`
// will return invalid value so instead query its parent/ancestor paths
while ((freeSpace < 0) && (current != root))
QVector<BitTorrent::DownloadPriority> filePriorities() const override
{
current = current.parentPath();
freeSpace = Utils::Fs::freeDiskSpaceOnPath(current);
return m_filePriorities.isEmpty()
? QVector<BitTorrent::DownloadPriority>(filesCount(), BitTorrent::DownloadPriority::Normal)
: m_filePriorities;
}
return freeSpace;
QVector<qreal> filesProgress() const override
{
return QVector<qreal>(filesCount(), 0);
}
void setPath(FileSystemPathComboEdit *fsPathEdit, const Path &newPath)
void fetchFilesProgress(std::function<void (QVector<qreal>)> resultHandler) const override
{
int existingIndex = indexOfPath(fsPathEdit, newPath);
if (existingIndex < 0)
resultHandler(filesProgress());
}
QVector<qreal> availableFileFractions() const override
{
// New path, prepend to combo box
fsPathEdit->insertItem(0, newPath);
existingIndex = 0;
return QVector<qreal>(filesCount(), 0);
}
fsPathEdit->setCurrentIndex(existingIndex);
void fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const override
{
resultHandler(availableFileFractions());
}
void updatePathHistory(const QString &settingsKey, const Path &path, const int maxLength)
void prioritizeFiles(const QVector<BitTorrent::DownloadPriority> &priorities) override
{
// Add last used save path to the front of history
Q_ASSERT(priorities.size() == filesCount());
m_filePriorities = priorities;
}
auto pathList = settings()->loadValue<QStringList>(settingsKey);
Path actualStorageLocation() const override
{
return {};
}
const int selectedSavePathIndex = pathList.indexOf(path.toString());
if (selectedSavePathIndex > -1)
pathList.move(selectedSavePathIndex, 0);
else
pathList.prepend(path.toString());
Path actualFilePath([[maybe_unused]] int fileIndex) const override
{
return {};
}
settings()->storeValue(settingsKey, QStringList(pathList.mid(0, maxLength)));
void flushCache() const override
{
}
}
private:
BitTorrent::TorrentInfo &m_torrentInfo;
PathList &m_filePaths;
QVector<BitTorrent::DownloadPriority> &m_filePriorities;
Path m_originalRootFolder;
BitTorrent::TorrentContentLayout m_currentContentLayout;
};
const int AddNewTorrentDialog::minPathHistoryLength;
const int AddNewTorrentDialog::maxPathHistoryLength;
@ -270,26 +370,37 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP @@ -270,26 +370,37 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
m_ui->categoryComboBox->addItem(u""_qs);
for (const QString &category : asConst(categories))
{
if (category != defaultCategory && category != m_torrentParams.category)
m_ui->categoryComboBox->addItem(category);
m_ui->contentTreeView->header()->setContextMenuPolicy(Qt::CustomContextMenu);
m_ui->contentTreeView->header()->setSortIndicator(0, Qt::AscendingOrder);
connect(m_ui->contentTreeView->header(), &QWidget::customContextMenuRequested, this, &AddNewTorrentDialog::displayColumnHeaderMenu);
}
// Torrent content filtering
m_filterLine->setPlaceholderText(tr("Filter files..."));
m_filterLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
connect(m_filterLine, &LineEdit::textChanged, this, &AddNewTorrentDialog::handleFilterTextChanged);
connect(m_filterLine, &LineEdit::textChanged, m_ui->contentTreeView, &TorrentContentWidget::setFilterPattern);
m_ui->contentFilterLayout->insertWidget(3, m_filterLine);
loadState();
// Signal / slots
connect(m_ui->doNotDeleteTorrentCheckBox, &QCheckBox::clicked, this, &AddNewTorrentDialog::doNotDeleteTorrentClicked);
QShortcut *editHotkey = new QShortcut(Qt::Key_F2, m_ui->contentTreeView, nullptr, nullptr, Qt::WidgetShortcut);
connect(editHotkey, &QShortcut::activated, this, &AddNewTorrentDialog::renameSelectedFile);
connect(m_ui->contentTreeView, &QAbstractItemView::doubleClicked, this, &AddNewTorrentDialog::renameSelectedFile);
connect(m_ui->buttonSelectAll, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkAll);
connect(m_ui->buttonSelectNone, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkNone);
if (const QByteArray state = m_storeTreeHeaderState; !state.isEmpty())
m_ui->contentTreeView->header()->restoreState(state);
// Hide useless columns after loading the header state
m_ui->contentTreeView->hideColumn(TorrentContentWidget::Progress);
m_ui->contentTreeView->hideColumn(TorrentContentWidget::Remaining);
m_ui->contentTreeView->hideColumn(TorrentContentWidget::Availability);
m_ui->contentTreeView->setColumnsVisibilityMode(TorrentContentWidget::ColumnsVisibilityMode::Locked);
m_ui->contentTreeView->setDoubleClickAction(TorrentContentWidget::DoubleClickAction::Rename);
m_ui->labelCommentData->setText(tr("Not Available", "This comment is unavailable"));
m_ui->labelDateData->setText(tr("Not Available", "This date is unavailable"));
m_filterLine->blockSignals(true);
// Default focus
if (m_ui->comboTTM->currentIndex() == 0) // 0 is Manual mode
@ -298,43 +409,9 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP @@ -298,43 +409,9 @@ AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inP
m_ui->categoryComboBox->setFocus();
}
void AddNewTorrentDialog::applyContentLayout()
{
Q_ASSERT(hasMetadata());
Q_ASSERT(!m_torrentParams.filePaths.isEmpty());
const auto originalContentLayout = (m_originalRootFolder.isEmpty()
? BitTorrent::TorrentContentLayout::NoSubfolder
: BitTorrent::TorrentContentLayout::Subfolder);
const int currentIndex = m_ui->contentLayoutComboBox->currentIndex();
const auto contentLayout = ((currentIndex == 0)
? originalContentLayout
: static_cast<BitTorrent::TorrentContentLayout>(currentIndex));
if (contentLayout != m_currentContentLayout)
{
PathList &filePaths = m_torrentParams.filePaths;
if (contentLayout == BitTorrent::TorrentContentLayout::NoSubfolder)
{
Path::stripRootFolder(filePaths);
}
else
{
const auto rootFolder = ((originalContentLayout == BitTorrent::TorrentContentLayout::Subfolder)
? m_originalRootFolder
: filePaths.at(0).removedExtension());
Path::addRootFolder(filePaths, rootFolder);
}
m_currentContentLayout = contentLayout;
}
}
AddNewTorrentDialog::~AddNewTorrentDialog()
{
saveState();
delete m_contentDelegate;
delete m_ui;
}
@ -389,7 +466,7 @@ void AddNewTorrentDialog::saveState() @@ -389,7 +466,7 @@ void AddNewTorrentDialog::saveState()
{
m_storeDialogSize = size();
m_storeSplitterState = m_ui->splitter->saveState();
if (m_contentModel)
if (hasMetadata())
m_storeTreeHeaderState = m_ui->contentTreeView->header()->saveState();
}
@ -537,7 +614,7 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri) @@ -537,7 +614,7 @@ bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
const QString torrentName = magnetUri.name();
setWindowTitle(torrentName.isEmpty() ? tr("Magnet link") : torrentName);
setupTreeview();
updateDiskSpaceLabel();
TMMChanged(m_ui->comboTTM->currentIndex());
BitTorrent::Session::instance()->downloadMetadata(magnetUri);
@ -565,9 +642,7 @@ void AddNewTorrentDialog::updateDiskSpaceLabel() @@ -565,9 +642,7 @@ void AddNewTorrentDialog::updateDiskSpaceLabel()
if (hasMetadata())
{
if (m_contentModel)
{
const QVector<BitTorrent::DownloadPriority> priorities = m_contentModel->model()->getFilePriorities();
const QVector<BitTorrent::DownloadPriority> &priorities = m_contentAdaptor->filePriorities();
Q_ASSERT(priorities.size() == m_torrentInfo.filesCount());
for (int i = 0; i < priorities.size(); ++i)
{
@ -575,11 +650,6 @@ void AddNewTorrentDialog::updateDiskSpaceLabel() @@ -575,11 +650,6 @@ void AddNewTorrentDialog::updateDiskSpaceLabel()
torrentSize += m_torrentInfo.fileSize(i);
}
}
else
{
torrentSize = m_torrentInfo.totalSize();
}
}
const QString freeSpace = Utils::Misc::friendlyUnit(queryFreeDiskSpace(m_ui->savePath->selectedPath()));
const QString sizeString = tr("%1 (Free space on disk: %2)").arg(
@ -637,21 +707,9 @@ void AddNewTorrentDialog::contentLayoutChanged() @@ -637,21 +707,9 @@ void AddNewTorrentDialog::contentLayoutChanged()
if (!hasMetadata())
return;
const auto filePriorities = m_contentModel->model()->getFilePriorities();
m_contentModel->model()->clear();
applyContentLayout();
m_contentModel->model()->setupModelData(FileStorageAdaptor(m_torrentInfo, m_torrentParams.filePaths));
m_contentModel->model()->updateFilesPriorities(filePriorities);
// Expand single-item folders recursively
QModelIndex currentIndex;
while (m_contentModel->rowCount(currentIndex) == 1)
{
currentIndex = m_contentModel->index(0, 0, currentIndex);
m_ui->contentTreeView->setExpanded(currentIndex, true);
}
const auto contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
m_contentAdaptor->applyContentLayout(contentLayout);
m_ui->contentTreeView->setContentHandler(m_contentAdaptor); // to cause reloading
}
void AddNewTorrentDialog::saveTorrentFile()
@ -755,125 +813,6 @@ void AddNewTorrentDialog::populateSavePaths() @@ -755,125 +813,6 @@ void AddNewTorrentDialog::populateSavePaths()
m_ui->groupBoxDownloadPath->blockSignals(false);
}
void AddNewTorrentDialog::displayContentTreeMenu()
{
const QModelIndexList selectedRows = m_ui->contentTreeView->selectionModel()->selectedRows(0);
const auto applyPriorities = [this](const BitTorrent::DownloadPriority prio)
{
const QModelIndexList selectedRows = m_ui->contentTreeView->selectionModel()->selectedRows(0);
for (const QModelIndex &index : selectedRows)
{
m_contentModel->setData(index.sibling(index.row(), PRIORITY)
, static_cast<int>(prio));
}
};
const auto applyPrioritiesByOrder = [this]()
{
// Equally distribute the selected items into groups and for each group assign
// a download priority that will apply to each item. The number of groups depends on how
// many "download priority" are available to be assigned
const QModelIndexList selectedRows = m_ui->contentTreeView->selectionModel()->selectedRows(0);
const qsizetype priorityGroups = 3;
const auto priorityGroupSize = std::max<qsizetype>((selectedRows.length() / priorityGroups), 1);
for (qsizetype i = 0; i < selectedRows.length(); ++i)
{
auto priority = BitTorrent::DownloadPriority::Ignored;
switch (i / priorityGroupSize)
{
case 0:
priority = BitTorrent::DownloadPriority::Maximum;
break;
case 1:
priority = BitTorrent::DownloadPriority::High;
break;
default:
case 2:
priority = BitTorrent::DownloadPriority::Normal;
break;
}
const QModelIndex &index = selectedRows[i];
m_contentModel->setData(index.sibling(index.row(), PRIORITY)
, static_cast<int>(priority));
}
};
QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
if (selectedRows.size() == 1)
{
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-rename"_qs), tr("Rename..."), this, &AddNewTorrentDialog::renameSelectedFile);
menu->addSeparator();
QMenu *priorityMenu = menu->addMenu(tr("Priority"));
priorityMenu->addAction(tr("Do not download"), priorityMenu, [applyPriorities]()
{
applyPriorities(BitTorrent::DownloadPriority::Ignored);
});
priorityMenu->addAction(tr("Normal"), priorityMenu, [applyPriorities]()
{
applyPriorities(BitTorrent::DownloadPriority::Normal);
});
priorityMenu->addAction(tr("High"), priorityMenu, [applyPriorities]()
{
applyPriorities(BitTorrent::DownloadPriority::High);
});
priorityMenu->addAction(tr("Maximum"), priorityMenu, [applyPriorities]()
{
applyPriorities(BitTorrent::DownloadPriority::Maximum);
});
priorityMenu->addSeparator();
priorityMenu->addAction(tr("By shown file order"), priorityMenu, applyPrioritiesByOrder);
}
else
{
menu->addAction(tr("Do not download"), menu, [applyPriorities]()
{
applyPriorities(BitTorrent::DownloadPriority::Ignored);
});
menu->addAction(tr("Normal priority"), menu, [applyPriorities]()
{
applyPriorities(BitTorrent::DownloadPriority::Normal);
});
menu->addAction(tr("High priority"), menu, [applyPriorities]()
{
applyPriorities(BitTorrent::DownloadPriority::High);
});
menu->addAction(tr("Maximum priority"), menu, [applyPriorities]()
{
applyPriorities(BitTorrent::DownloadPriority::Maximum);
});
menu->addSeparator();
menu->addAction(tr("Priority by shown file order"), menu, applyPrioritiesByOrder);
}
menu->popup(QCursor::pos());
}
void AddNewTorrentDialog::displayColumnHeaderMenu()
{
QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->setToolTipsVisible(true);
QAction *resizeAction = menu->addAction(tr("Resize columns"), this, [this]()
{
for (int i = 0, count = m_ui->contentTreeView->header()->count(); i < count; ++i)
{
if (!m_ui->contentTreeView->isColumnHidden(i))
m_ui->contentTreeView->resizeColumnToContents(i);
}
});
resizeAction->setToolTip(tr("Resize all non-hidden columns to the size of their contents"));
menu->popup(QCursor::pos());
}
void AddNewTorrentDialog::accept()
{
// TODO: Check if destination actually exists
@ -886,10 +825,6 @@ void AddNewTorrentDialog::accept() @@ -886,10 +825,6 @@ void AddNewTorrentDialog::accept()
m_storeRememberLastSavePath = m_ui->checkBoxRememberLastSavePath->isChecked();
// Save file priorities
if (m_contentModel)
m_torrentParams.filePriorities = m_contentModel->model()->getFilePriorities();
m_torrentParams.addToQueueTop = m_ui->addToQueueTopCheckBox->isChecked();
m_torrentParams.addPaused = !m_ui->startTorrentCheckBox->isChecked();
m_torrentParams.stopCondition = m_ui->stopConditionComboBox->currentData().value<BitTorrent::Torrent::StopCondition>();
@ -972,15 +907,10 @@ void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, co @@ -972,15 +907,10 @@ void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, co
void AddNewTorrentDialog::setupTreeview()
{
if (!hasMetadata())
{
m_ui->labelCommentData->setText(tr("Not Available", "This comment is unavailable"));
m_ui->labelDateData->setText(tr("Not Available", "This date is unavailable"));
// Prevent crash if something is typed in the filter. m_contentModel is not initialized at this point
m_filterLine->blockSignals(true);
}
else
{
Q_ASSERT(hasMetadata());
if (Q_UNLIKELY(!hasMetadata()))
return;
// Set dialog title
setWindowTitle(m_torrentInfo.name());
@ -988,54 +918,18 @@ void AddNewTorrentDialog::setupTreeview() @@ -988,54 +918,18 @@ void AddNewTorrentDialog::setupTreeview()
m_ui->labelCommentData->setText(Utils::Misc::parseHtmlLinks(m_torrentInfo.comment().toHtmlEscaped()));
m_ui->labelDateData->setText(!m_torrentInfo.creationDate().isNull() ? QLocale().toString(m_torrentInfo.creationDate(), QLocale::ShortFormat) : tr("Not available"));
// Prepare content tree
m_contentModel = new TorrentContentFilterModel(this);
connect(m_contentModel->model(), &TorrentContentModel::filteredFilesChanged, this, &AddNewTorrentDialog::updateDiskSpaceLabel);
m_ui->contentTreeView->setModel(m_contentModel);
m_contentDelegate = new PropListDelegate(nullptr);
m_ui->contentTreeView->setItemDelegate(m_contentDelegate);
connect(m_ui->contentTreeView, &QAbstractItemView::clicked, m_ui->contentTreeView
, qOverload<const QModelIndex &>(&QAbstractItemView::edit));
connect(m_ui->contentTreeView, &QWidget::customContextMenuRequested, this, &AddNewTorrentDialog::displayContentTreeMenu);
connect(m_ui->buttonSelectAll, &QPushButton::clicked, m_contentModel, &TorrentContentFilterModel::selectAll);
connect(m_ui->buttonSelectNone, &QPushButton::clicked, m_contentModel, &TorrentContentFilterModel::selectNone);
if (m_torrentParams.filePaths.isEmpty())
m_torrentParams.filePaths = m_torrentInfo.filePaths();
m_originalRootFolder = Path::findRootFolder(m_torrentInfo.filePaths());
m_currentContentLayout = (m_originalRootFolder.isEmpty()
? BitTorrent::TorrentContentLayout::NoSubfolder
: BitTorrent::TorrentContentLayout::Subfolder);
applyContentLayout();
m_contentAdaptor = new TorrentContentAdaptor(m_torrentInfo, m_torrentParams.filePaths, m_torrentParams.filePriorities);
// List files in torrent
m_contentModel->model()->setupModelData(FileStorageAdaptor(m_torrentInfo, m_torrentParams.filePaths));
if (const QByteArray state = m_storeTreeHeaderState; !state.isEmpty())
m_ui->contentTreeView->header()->restoreState(state);
m_filterLine->blockSignals(false);
// Hide useless columns after loading the header state
m_ui->contentTreeView->hideColumn(PROGRESS);
m_ui->contentTreeView->hideColumn(REMAINING);
m_ui->contentTreeView->hideColumn(AVAILABILITY);
// Expand single-item folders recursively
QModelIndex currentIndex;
while (m_contentModel->rowCount(currentIndex) == 1)
{
currentIndex = m_contentModel->index(0, 0, currentIndex);
m_ui->contentTreeView->setExpanded(currentIndex, true);
}
const auto contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
m_contentAdaptor->applyContentLayout(contentLayout);
if (BitTorrent::Session::instance()->isExcludedFileNamesEnabled())
{
// Check file name blacklist for torrents that are manually added
QVector<BitTorrent::DownloadPriority> priorities = m_contentModel->model()->getFilePriorities();
Q_ASSERT(priorities.size() == m_torrentInfo.filesCount());
QVector<BitTorrent::DownloadPriority> priorities = m_contentAdaptor->filePriorities();
for (int i = 0; i < priorities.size(); ++i)
{
if (priorities[i] == BitTorrent::DownloadPriority::Ignored)
@ -1045,10 +939,13 @@ void AddNewTorrentDialog::setupTreeview() @@ -1045,10 +939,13 @@ void AddNewTorrentDialog::setupTreeview()
priorities[i] = BitTorrent::DownloadPriority::Ignored;
}
m_contentModel->model()->updateFilesPriorities(priorities);
}
m_contentAdaptor->prioritizeFiles(priorities);
}
m_ui->contentTreeView->setContentHandler(m_contentAdaptor);
m_filterLine->blockSignals(false);
updateDiskSpaceLabel();
}
@ -1122,27 +1019,3 @@ void AddNewTorrentDialog::doNotDeleteTorrentClicked(bool checked) @@ -1122,27 +1019,3 @@ void AddNewTorrentDialog::doNotDeleteTorrentClicked(bool checked)
{
m_torrentGuard->setAutoRemove(!checked);
}
void AddNewTorrentDialog::renameSelectedFile()
{
if (hasMetadata())
{
FileStorageAdaptor fileStorageAdaptor {m_torrentInfo, m_torrentParams.filePaths};
m_ui->contentTreeView->renameSelectedFile(fileStorageAdaptor);
}
}
void AddNewTorrentDialog::handleFilterTextChanged(const QString &filter)
{
const QString pattern = Utils::String::wildcardToRegexPattern(filter);
m_contentModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
if (filter.isEmpty())
{
m_ui->contentTreeView->collapseAll();
m_ui->contentTreeView->expand(m_contentModel->index(0, 0));
}
else
{
m_ui->contentTreeView->expandAll();
}
}

15
src/gui/addnewtorrentdialog.h

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -54,8 +55,6 @@ namespace Ui @@ -54,8 +55,6 @@ namespace Ui
}
class LineEdit;
class PropListDelegate;
class TorrentContentFilterModel;
class TorrentFileGuard;
class AddNewTorrentDialog final : public QDialog
@ -80,8 +79,6 @@ public: @@ -80,8 +79,6 @@ public:
static void show(const QString &source, QWidget *parent);
private slots:
void displayContentTreeMenu();
void displayColumnHeaderMenu();
void updateDiskSpaceLabel();
void onSavePathChanged(const Path &newPath);
void onDownloadPathChanged(const Path &newPath);
@ -92,16 +89,15 @@ private slots: @@ -92,16 +89,15 @@ private slots:
void categoryChanged(int index);
void contentLayoutChanged();
void doNotDeleteTorrentClicked(bool checked);
void renameSelectedFile();
void handleFilterTextChanged(const QString &filter);
void accept() override;
void reject() override;
private:
class TorrentContentAdaptor;
explicit AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inParams, QWidget *parent);
void applyContentLayout();
bool loadTorrentFile(const QString &source);
bool loadTorrentImpl();
bool loadMagnet(const BitTorrent::MagnetUri &magnetUri);
@ -116,12 +112,9 @@ private: @@ -116,12 +112,9 @@ private:
void showEvent(QShowEvent *event) override;
Ui::AddNewTorrentDialog *m_ui = nullptr;
TorrentContentFilterModel *m_contentModel = nullptr;
PropListDelegate *m_contentDelegate = nullptr;
TorrentContentAdaptor *m_contentAdaptor = nullptr;
BitTorrent::MagnetUri m_magnetURI;
BitTorrent::TorrentInfo m_torrentInfo;
Path m_originalRootFolder;
BitTorrent::TorrentContentLayout m_currentContentLayout;
int m_savePathIndex = -1;
int m_downloadPathIndex = -1;
bool m_useDownloadPath = false;

6
src/gui/addnewtorrentdialog.ui

@ -510,7 +510,7 @@ @@ -510,7 +510,7 @@
</layout>
</item>
<item>
<widget class="TorrentContentTreeView" name="contentTreeView">
<widget class="TorrentContentWidget" name="contentTreeView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>1</horstretch>
@ -612,9 +612,9 @@ @@ -612,9 +612,9 @@
</widget>
<customwidgets>
<customwidget>
<class>TorrentContentTreeView</class>
<class>TorrentContentWidget</class>
<extends>QTreeView</extends>
<header>gui/torrentcontenttreeview.h</header>
<header>gui/torrentcontentwidget.h</header>
</customwidget>
<customwidget>
<class>FileSystemPathComboEdit</class>

8
src/gui/gui.pri

@ -39,7 +39,6 @@ HEADERS += \ @@ -39,7 +39,6 @@ HEADERS += \
$$PWD/properties/pieceavailabilitybar.h \
$$PWD/properties/piecesbar.h \
$$PWD/properties/propertieswidget.h \
$$PWD/properties/proplistdelegate.h \
$$PWD/properties/proptabbar.h \
$$PWD/properties/speedplotview.h \
$$PWD/properties/speedwidget.h \
@ -65,11 +64,12 @@ HEADERS += \ @@ -65,11 +64,12 @@ HEADERS += \
$$PWD/tagfilterwidget.h \
$$PWD/torrentcategorydialog.h \
$$PWD/torrentcontentfiltermodel.h \
$$PWD/torrentcontentitemdelegate.h \
$$PWD/torrentcontentmodel.h \
$$PWD/torrentcontentmodelfile.h \
$$PWD/torrentcontentmodelfolder.h \
$$PWD/torrentcontentmodelitem.h \
$$PWD/torrentcontenttreeview.h \
$$PWD/torrentcontentwidget.h \
$$PWD/torrentcreatordialog.h \
$$PWD/torrentoptionsdialog.h \
$$PWD/trackerentriesdialog.h \
@ -123,7 +123,6 @@ SOURCES += \ @@ -123,7 +123,6 @@ SOURCES += \
$$PWD/properties/pieceavailabilitybar.cpp \
$$PWD/properties/piecesbar.cpp \
$$PWD/properties/propertieswidget.cpp \
$$PWD/properties/proplistdelegate.cpp \
$$PWD/properties/proptabbar.cpp \
$$PWD/properties/speedplotview.cpp \
$$PWD/properties/speedwidget.cpp \
@ -149,11 +148,12 @@ SOURCES += \ @@ -149,11 +148,12 @@ SOURCES += \
$$PWD/tagfilterwidget.cpp \
$$PWD/torrentcategorydialog.cpp \
$$PWD/torrentcontentfiltermodel.cpp \
$$PWD/torrentcontentitemdelegate.cpp \
$$PWD/torrentcontentmodel.cpp \
$$PWD/torrentcontentmodelfile.cpp \
$$PWD/torrentcontentmodelfolder.cpp \
$$PWD/torrentcontentmodelitem.cpp \
$$PWD/torrentcontenttreeview.cpp \
$$PWD/torrentcontentwidget.cpp \
$$PWD/torrentcreatordialog.cpp \
$$PWD/torrentoptionsdialog.cpp \
$$PWD/trackerentriesdialog.cpp \

329
src/gui/properties/propertieswidget.cpp

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -31,9 +32,9 @@ @@ -31,9 +32,9 @@
#include <QClipboard>
#include <QDateTime>
#include <QDebug>
#include <QHeaderView>
#include <QListWidgetItem>
#include <QMenu>
#include <QMessageBox>
#include <QPointer>
#include <QSplitter>
#include <QShortcut>
@ -41,7 +42,6 @@ @@ -41,7 +42,6 @@
#include <QThread>
#include <QUrl>
#include "base/bittorrent/downloadpriority.h"
#include "base/bittorrent/infohash.h"
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrent.h"
@ -49,32 +49,23 @@ @@ -49,32 +49,23 @@
#include "base/preferences.h"
#include "base/types.h"
#include "base/unicodestrings.h"
#include "base/utils/fs.h"
#include "base/utils/misc.h"
#include "base/utils/string.h"
#include "gui/autoexpandabledialog.h"
#include "gui/lineedit.h"
#include "gui/raisedmessagebox.h"
#include "gui/torrentcontentfiltermodel.h"
#include "gui/torrentcontentmodel.h"
#include "gui/uithememanager.h"
#include "gui/utils.h"
#include "downloadedpiecesbar.h"
#include "peerlistwidget.h"
#include "pieceavailabilitybar.h"
#include "proplistdelegate.h"
#include "proptabbar.h"
#include "speedwidget.h"
#include "trackerlistwidget.h"
#include "ui_propertieswidget.h"
#ifdef Q_OS_MACOS
#include "gui/macutilities.h"
#endif
PropertiesWidget::PropertiesWidget(QWidget *parent)
: QWidget(parent)
, m_ui(new Ui::PropertiesWidget())
, m_ui {new Ui::PropertiesWidget}
{
m_ui->setupUi(this);
#ifndef Q_OS_MACOS
@ -83,40 +74,23 @@ PropertiesWidget::PropertiesWidget(QWidget *parent) @@ -83,40 +74,23 @@ PropertiesWidget::PropertiesWidget(QWidget *parent)
m_state = VISIBLE;
// Files list
m_ui->filesList->header()->setContextMenuPolicy(Qt::CustomContextMenu);
// Set Properties list model
m_propListModel = new TorrentContentFilterModel(this);
m_ui->filesList->setModel(m_propListModel);
m_propListDelegate = new PropListDelegate(this);
m_ui->filesList->setItemDelegate(m_propListDelegate);
m_ui->filesList->setSortingEnabled(true);
// Torrent content filtering
m_contentFilterLine = new LineEdit(this);
m_contentFilterLine->setPlaceholderText(tr("Filter files..."));
m_contentFilterLine->setFixedWidth(300);
connect(m_contentFilterLine, &LineEdit::textChanged, this, &PropertiesWidget::filterText);
connect(m_contentFilterLine, &LineEdit::textChanged, m_ui->filesList, &TorrentContentWidget::setFilterPattern);
m_ui->contentFilterLayout->insertWidget(3, m_contentFilterLine);
m_ui->filesList->setDoubleClickAction(TorrentContentWidget::DoubleClickAction::Open);
// SIGNAL/SLOTS
connect(m_ui->selectAllButton, &QPushButton::clicked, m_propListModel, &TorrentContentFilterModel::selectAll);
connect(m_ui->selectNoneButton, &QPushButton::clicked, m_propListModel, &TorrentContentFilterModel::selectNone);
connect(m_propListModel, &TorrentContentFilterModel::filteredFilesChanged, this, &PropertiesWidget::filteredFilesChanged);
connect(m_ui->selectAllButton, &QPushButton::clicked, m_ui->filesList, &TorrentContentWidget::checkAll);
connect(m_ui->selectNoneButton, &QPushButton::clicked, m_ui->filesList, &TorrentContentWidget::checkNone);
connect(m_ui->listWebSeeds, &QWidget::customContextMenuRequested, this, &PropertiesWidget::displayWebSeedListMenu);
connect(m_propListDelegate, &PropListDelegate::filteredFilesChanged, this, &PropertiesWidget::filteredFilesChanged);
connect(m_ui->stackedProperties, &QStackedWidget::currentChanged, this, &PropertiesWidget::loadDynamicData);
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentSavePathChanged, this, &PropertiesWidget::updateSavePath);
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentMetadataReceived, this, &PropertiesWidget::updateTorrentInfos);
connect(m_ui->filesList, &QAbstractItemView::clicked
, m_ui->filesList, qOverload<const QModelIndex &>(&QAbstractItemView::edit));
connect(m_ui->filesList, &QWidget::customContextMenuRequested, this, &PropertiesWidget::displayFilesListMenu);
connect(m_ui->filesList, &QAbstractItemView::doubleClicked, this, &PropertiesWidget::openItem);
connect(m_ui->filesList->header(), &QWidget::customContextMenuRequested, this, &PropertiesWidget::displayColumnHeaderMenu);
connect(m_ui->filesList->header(), &QHeaderView::sectionMoved, this, &PropertiesWidget::saveSettings);
connect(m_ui->filesList->header(), &QHeaderView::sectionResized, this, &PropertiesWidget::saveSettings);
connect(m_ui->filesList->header(), &QHeaderView::sortIndicatorChanged, this, &PropertiesWidget::saveSettings);
connect(m_ui->filesList, &TorrentContentWidget::stateChanged, this, &PropertiesWidget::saveSettings);
// set bar height relative to screen dpi
const int barHeight = 18;
@ -162,13 +136,6 @@ PropertiesWidget::PropertiesWidget(QWidget *parent) @@ -162,13 +136,6 @@ PropertiesWidget::PropertiesWidget(QWidget *parent)
connect(deleteWebSeedsHotkey, &QShortcut::activated, this, &PropertiesWidget::deleteSelectedUrlSeeds);
connect(m_ui->listWebSeeds, &QListWidget::doubleClicked, this, &PropertiesWidget::editWebSeed);
const auto *renameFileHotkey = new QShortcut(Qt::Key_F2, m_ui->filesList, nullptr, nullptr, Qt::WidgetShortcut);
connect(renameFileHotkey, &QShortcut::activated, this, [this]() { m_ui->filesList->renameSelectedFile(*m_torrent); });
const auto *openFileHotkeyReturn = new QShortcut(Qt::Key_Return, m_ui->filesList, nullptr, nullptr, Qt::WidgetShortcut);
connect(openFileHotkeyReturn, &QShortcut::activated, this, &PropertiesWidget::openSelectedFile);
const auto *openFileHotkeyEnter = new QShortcut(Qt::Key_Enter, m_ui->filesList, nullptr, nullptr, Qt::WidgetShortcut);
connect(openFileHotkeyEnter, &QShortcut::activated, this, &PropertiesWidget::openSelectedFile);
configure();
connect(Preferences::instance(), &Preferences::changed, this, &PropertiesWidget::configure);
}
@ -179,47 +146,6 @@ PropertiesWidget::~PropertiesWidget() @@ -179,47 +146,6 @@ PropertiesWidget::~PropertiesWidget()
delete m_ui;
}
void PropertiesWidget::displayColumnHeaderMenu()
{
QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->setTitle(tr("Column visibility"));
menu->setToolTipsVisible(true);
for (int i = 0; i < TorrentContentModelItem::TreeItemColumns::NB_COL; ++i)
{
const auto columnName = m_propListModel->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString();
QAction *action = menu->addAction(columnName, this, [this, i](const bool checked)
{
m_ui->filesList->setColumnHidden(i, !checked);
if (checked && (m_ui->filesList->columnWidth(i) <= 5))
m_ui->filesList->resizeColumnToContents(i);
saveSettings();
});
action->setCheckable(true);
action->setChecked(!m_ui->filesList->isColumnHidden(i));
if (i == TorrentContentModelItem::TreeItemColumns::COL_NAME)
action->setEnabled(false);
}
menu->addSeparator();
QAction *resizeAction = menu->addAction(tr("Resize columns"), this, [this]()
{
for (int i = 0, count = m_ui->filesList->header()->count(); i < count; ++i)
{
if (!m_ui->filesList->isColumnHidden(i))
m_ui->filesList->resizeColumnToContents(i);
}
saveSettings();
});
resizeAction->setToolTip(tr("Resize all non-hidden columns to the size of their contents"));
menu->popup(QCursor::pos());
}
void PropertiesWidget::showPiecesAvailability(bool show)
{
m_ui->labelPiecesAvailability->setVisible(show);
@ -309,7 +235,6 @@ void PropertiesWidget::clear() @@ -309,7 +235,6 @@ void PropertiesWidget::clear()
m_piecesAvailability->clear();
m_peerList->clear();
m_contentFilterLine->clear();
m_propListModel->model()->clear();
}
BitTorrent::Torrent *PropertiesWidget::getCurrentTorrent() const
@ -356,14 +281,15 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::Torrent *const torrent) @@ -356,14 +281,15 @@ void PropertiesWidget::loadTorrentInfos(BitTorrent::Torrent *const torrent)
m_torrent = torrent;
m_downloadedPieces->setTorrent(m_torrent);
m_piecesAvailability->setTorrent(m_torrent);
if (!m_torrent) return;
m_ui->filesList->setContentHandler(m_torrent);
if (!m_torrent)
return;
// Save path
updateSavePath(m_torrent);
// Info hashes
m_ui->labelInfohash1Val->setText(m_torrent->infoHash().v1().isValid() ? m_torrent->infoHash().v1().toString() : tr("N/A"));
m_ui->labelInfohash2Val->setText(m_torrent->infoHash().v2().isValid() ? m_torrent->infoHash().v2().toString() : tr("N/A"));
m_propListModel->model()->clear();
if (m_torrent->hasMetadata())
{
// Creation date
@ -545,55 +471,7 @@ void PropertiesWidget::loadDynamicData() @@ -545,55 +471,7 @@ void PropertiesWidget::loadDynamicData()
m_peerList->loadPeers(m_torrent);
break;
case PropTabBar::FilesTab:
// Files progress
if (m_torrent->hasMetadata())
{
qDebug("Updating priorities in files tab");
m_ui->filesList->setUpdatesEnabled(false);
using TorrentPtr = QPointer<BitTorrent::Torrent>;
m_torrent->fetchFilesProgress([this, torrent = TorrentPtr(m_torrent)](const QVector<qreal> &filesProgress)
{
if (torrent == m_torrent)
m_propListModel->model()->updateFilesProgress(filesProgress);
});
m_torrent->fetchAvailableFileFractions([this, torrent = TorrentPtr(m_torrent)](const QVector<qreal> &availableFileFractions)
{
if (torrent == m_torrent)
m_propListModel->model()->updateFilesAvailability(availableFileFractions);
});
// Load torrent content if not yet done so
const bool isContentInitialized = m_propListModel->model()->hasIndex(0, 0);
if (!isContentInitialized)
{
// List files in torrent
m_propListModel->model()->setupModelData(*m_torrent);
// Load file priorities
m_propListModel->model()->updateFilesPriorities(m_torrent->filePriorities());
// Expand single-item folders recursively.
// This will trigger sorting and filtering so do it after all relevant data is loaded.
QModelIndex currentIndex;
while (m_propListModel->rowCount(currentIndex) == 1)
{
currentIndex = m_propListModel->index(0, 0, currentIndex);
m_ui->filesList->setExpanded(currentIndex, true);
}
}
else
{
// Torrent content was loaded already, only make some updates
// XXX: We don't update file priorities regularly for performance
// reasons. This means that priorities will not be updated if
// set from the Web UI.
// m_propListModel->model()->updateFilesPriorities(m_torrent->filePriorities());
}
m_ui->filesList->setUpdatesEnabled(true);
}
m_ui->filesList->refresh();
break;
default:;
}
@ -621,149 +499,6 @@ void PropertiesWidget::loadUrlSeeds() @@ -621,149 +499,6 @@ void PropertiesWidget::loadUrlSeeds()
});
}
Path PropertiesWidget::getFullPath(const QModelIndex &index) const
{
if (m_propListModel->itemType(index) == TorrentContentModelItem::FileType)
{
const int fileIdx = m_propListModel->getFileIndex(index);
const Path fullPath = m_torrent->actualStorageLocation() / m_torrent->actualFilePath(fileIdx);
return fullPath;
}
// folder type
const QModelIndex nameIndex {index.sibling(index.row(), TorrentContentModelItem::COL_NAME)};
Path folderPath {nameIndex.data().toString()};
for (QModelIndex modelIdx = m_propListModel->parent(nameIndex); modelIdx.isValid(); modelIdx = modelIdx.parent())
folderPath = Path(modelIdx.data().toString()) / folderPath;
const Path fullPath = m_torrent->actualStorageLocation() / folderPath;
return fullPath;
}
void PropertiesWidget::openItem(const QModelIndex &index) const
{
if (!index.isValid())
return;
m_torrent->flushCache(); // Flush data
Utils::Gui::openPath(getFullPath(index));
}
void PropertiesWidget::openParentFolder(const QModelIndex &index) const
{
const Path path = getFullPath(index);
m_torrent->flushCache(); // Flush data
#ifdef Q_OS_MACOS
MacUtils::openFiles({path});
#else
Utils::Gui::openFolderSelect(path);
#endif
}
void PropertiesWidget::displayFilesListMenu()
{
if (!m_torrent) return;
const QModelIndexList selectedRows = m_ui->filesList->selectionModel()->selectedRows(0);
if (selectedRows.empty()) return;
QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
if (selectedRows.size() == 1)
{
const QModelIndex index = selectedRows[0];
menu->addAction(UIThemeManager::instance()->getIcon(u"folder-documents"_qs), tr("Open")
, this, [this, index]() { openItem(index); });
menu->addAction(UIThemeManager::instance()->getIcon(u"directory"_qs), tr("Open containing folder")
, this, [this, index]() { openParentFolder(index); });
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-rename"_qs), tr("Rename...")
, this, [this]() { m_ui->filesList->renameSelectedFile(*m_torrent); });
menu->addSeparator();
}
const auto applyPriorities = [this](const BitTorrent::DownloadPriority prio)
{
const QModelIndexList selectedRows = m_ui->filesList->selectionModel()->selectedRows(0);
for (const QModelIndex &index : selectedRows)
{
m_propListModel->setData(index.sibling(index.row(), PRIORITY)
, static_cast<int>(prio));
}
// Save changes
this->applyPriorities();
};
QMenu *subMenu = menu->addMenu(tr("Priority"));
subMenu->addAction(tr("Do not download"), subMenu, [applyPriorities]()
{
applyPriorities(BitTorrent::DownloadPriority::Ignored);
});
subMenu->addAction(tr("Normal"), subMenu, [applyPriorities]()
{
applyPriorities(BitTorrent::DownloadPriority::Normal);
});
subMenu->addAction(tr("High"), subMenu, [applyPriorities]()
{
applyPriorities(BitTorrent::DownloadPriority::High);
});
subMenu->addAction(tr("Maximum"), subMenu, [applyPriorities]()
{
applyPriorities(BitTorrent::DownloadPriority::Maximum);
});
subMenu->addSeparator();
subMenu->addAction(tr("By shown file order"), subMenu, [this]()
{
// Equally distribute the selected items into groups and for each group assign
// a download priority that will apply to each item. The number of groups depends on how
// many "download priority" are available to be assigned
const QModelIndexList selectedRows = m_ui->filesList->selectionModel()->selectedRows(0);
const qsizetype priorityGroups = 3;
const auto priorityGroupSize = std::max<qsizetype>((selectedRows.length() / priorityGroups), 1);
for (qsizetype i = 0; i < selectedRows.length(); ++i)
{
auto priority = BitTorrent::DownloadPriority::Ignored;
switch (i / priorityGroupSize)
{
case 0:
priority = BitTorrent::DownloadPriority::Maximum;
break;
case 1:
priority = BitTorrent::DownloadPriority::High;
break;
default:
case 2:
priority = BitTorrent::DownloadPriority::Normal;
break;
}
const QModelIndex &index = selectedRows[i];
m_propListModel->setData(index.sibling(index.row(), PRIORITY)
, static_cast<int>(priority));
// Save changes
this->applyPriorities();
}
});
// The selected torrent might have disappeared during exec()
// so we just close menu when an appropriate model is reset
connect(m_ui->filesList->model(), &QAbstractItemModel::modelAboutToBeReset
, menu, [menu]()
{
menu->setActiveAction(nullptr);
menu->close();
});
menu->popup(QCursor::pos());
}
void PropertiesWidget::displayWebSeedListMenu()
{
if (!m_torrent) return;
@ -789,14 +524,6 @@ void PropertiesWidget::displayWebSeedListMenu() @@ -789,14 +524,6 @@ void PropertiesWidget::displayWebSeedListMenu()
menu->popup(QCursor::pos());
}
void PropertiesWidget::openSelectedFile()
{
const QModelIndexList selectedIndexes = m_ui->filesList->selectionModel()->selectedRows(0);
if (selectedIndexes.size() != 1)
return;
openItem(selectedIndexes.first());
}
void PropertiesWidget::configure()
{
// Speed widget
@ -845,9 +572,7 @@ void PropertiesWidget::askWebSeed() @@ -845,9 +572,7 @@ void PropertiesWidget::askWebSeed()
qDebug("Adding %s web seed", qUtf8Printable(urlSeed));
if (!m_ui->listWebSeeds->findItems(urlSeed, Qt::MatchFixedString).empty())
{
QMessageBox::warning(this, u"qBittorrent"_qs,
tr("This URL seed is already in the list."),
QMessageBox::Ok);
QMessageBox::warning(this, u"qBittorrent"_qs, tr("This URL seed is already in the list."), QMessageBox::Ok);
return;
}
if (m_torrent)
@ -909,29 +634,3 @@ void PropertiesWidget::editWebSeed() @@ -909,29 +634,3 @@ void PropertiesWidget::editWebSeed()
m_torrent->addUrlSeeds({newSeed});
loadUrlSeeds();
}
void PropertiesWidget::applyPriorities()
{
m_torrent->prioritizeFiles(m_propListModel->model()->getFilePriorities());
}
void PropertiesWidget::filteredFilesChanged()
{
if (m_torrent)
applyPriorities();
}
void PropertiesWidget::filterText(const QString &filter)
{
const QString pattern = Utils::String::wildcardToRegexPattern(filter);
m_propListModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
if (filter.isEmpty())
{
m_ui->filesList->collapseAll();
m_ui->filesList->expand(m_propListModel->index(0, 0));
}
else
{
m_ui->filesList->expandAll();
}
}

14
src/gui/properties/propertieswidget.h

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -40,9 +41,7 @@ class DownloadedPiecesBar; @@ -40,9 +41,7 @@ class DownloadedPiecesBar;
class LineEdit;
class PeerListWidget;
class PieceAvailabilityBar;
class PropListDelegate;
class PropTabBar;
class TorrentContentFilterModel;
class TrackerListWidget;
namespace BitTorrent
@ -83,7 +82,6 @@ public slots: @@ -83,7 +82,6 @@ public slots:
void readSettings();
void saveSettings();
void reloadPreferences();
void openItem(const QModelIndex &index) const;
void loadTrackers(BitTorrent::Torrent *const torrent);
protected slots:
@ -93,30 +91,20 @@ protected slots: @@ -93,30 +91,20 @@ protected slots:
void deleteSelectedUrlSeeds();
void copySelectedWebSeedsToClipboard() const;
void editWebSeed();
void displayFilesListMenu();
void displayWebSeedListMenu();
void filteredFilesChanged();
void showPiecesDownloaded(bool show);
void showPiecesAvailability(bool show);
void openSelectedFile();
private slots:
void configure();
void displayColumnHeaderMenu();
void filterText(const QString &filter);
void updateSavePath(BitTorrent::Torrent *const torrent);
private:
QPushButton *getButtonFromIndex(int index);
void applyPriorities();
void openParentFolder(const QModelIndex &index) const;
Path getFullPath(const QModelIndex &index) const;
Ui::PropertiesWidget *m_ui = nullptr;
BitTorrent::Torrent *m_torrent = nullptr;
SlideState m_state;
TorrentContentFilterModel *m_propListModel = nullptr;
PropListDelegate *m_propListDelegate = nullptr;
PeerListWidget *m_peerList = nullptr;
TrackerListWidget *m_trackerList = nullptr;
QWidget *m_speedWidget = nullptr;

6
src/gui/properties/propertieswidget.ui

@ -1082,7 +1082,7 @@ @@ -1082,7 +1082,7 @@
</layout>
</item>
<item>
<widget class="TorrentContentTreeView" name="filesList">
<widget class="TorrentContentWidget" name="filesList">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
@ -1121,9 +1121,9 @@ @@ -1121,9 +1121,9 @@
</widget>
<customwidgets>
<customwidget>
<class>TorrentContentTreeView</class>
<class>TorrentContentWidget</class>
<extends>QTreeView</extends>
<header location="global">gui/torrentcontenttreeview.h</header>
<header location="global">gui/torrentcontentwidget.h</header>
</customwidget>
</customwidgets>
<resources/>

32
src/gui/torrentcontentfiltermodel.cpp

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006-2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -32,10 +33,7 @@ @@ -32,10 +33,7 @@
TorrentContentFilterModel::TorrentContentFilterModel(QObject *parent)
: QSortFilterProxyModel(parent)
, m_model(new TorrentContentModel(this))
{
connect(m_model, &TorrentContentModel::filteredFilesChanged, this, &TorrentContentFilterModel::filteredFilesChanged);
setSourceModel(m_model);
// Filter settings
setFilterKeyColumn(TorrentContentModelItem::COL_NAME);
setFilterRole(TorrentContentModel::UnderlyingDataRole);
@ -44,9 +42,10 @@ TorrentContentFilterModel::TorrentContentFilterModel(QObject *parent) @@ -44,9 +42,10 @@ TorrentContentFilterModel::TorrentContentFilterModel(QObject *parent)
setSortRole(TorrentContentModel::UnderlyingDataRole);
}
TorrentContentModel *TorrentContentFilterModel::model() const
void TorrentContentFilterModel::setSourceModel(TorrentContentModel *model)
{
return m_model;
m_model = model;
QSortFilterProxyModel::setSourceModel(m_model);
}
TorrentContentModelItem::ItemType TorrentContentFilterModel::itemType(const QModelIndex &index) const
@ -61,10 +60,12 @@ int TorrentContentFilterModel::getFileIndex(const QModelIndex &index) const @@ -61,10 +60,12 @@ int TorrentContentFilterModel::getFileIndex(const QModelIndex &index) const
QModelIndex TorrentContentFilterModel::parent(const QModelIndex &child) const
{
if (!child.isValid()) return {};
if (!child.isValid())
return {};
QModelIndex sourceParent = m_model->parent(mapToSource(child));
if (!sourceParent.isValid()) return {};
if (!sourceParent.isValid())
return {};
return mapFromSource(sourceParent);
}
@ -95,6 +96,7 @@ bool TorrentContentFilterModel::lessThan(const QModelIndex &left, const QModelIn @@ -95,6 +96,7 @@ bool TorrentContentFilterModel::lessThan(const QModelIndex &left, const QModelIn
const QString strR = right.data().toString();
return m_naturalLessThan(strL, strR);
}
if ((leftType == TorrentContentModelItem::FolderType) && (sortOrder() == Qt::AscendingOrder))
{
return true;
@ -102,23 +104,12 @@ bool TorrentContentFilterModel::lessThan(const QModelIndex &left, const QModelIn @@ -102,23 +104,12 @@ bool TorrentContentFilterModel::lessThan(const QModelIndex &left, const QModelIn
return false;
}
default:
return QSortFilterProxyModel::lessThan(left, right);
};
}
void TorrentContentFilterModel::selectAll()
{
for (int i = 0; i < rowCount(); ++i)
setData(index(i, TorrentContentModelItem::COL_NAME), Qt::Checked, Qt::CheckStateRole);
}
void TorrentContentFilterModel::selectNone()
{
for (int i = 0; i < rowCount(); ++i)
setData(index(i, TorrentContentModelItem::COL_NAME), Qt::Unchecked, Qt::CheckStateRole);
}
bool TorrentContentFilterModel::hasFiltered(const QModelIndex &folder) const
{
// this should be called only with folders
@ -126,6 +117,7 @@ bool TorrentContentFilterModel::hasFiltered(const QModelIndex &folder) const @@ -126,6 +117,7 @@ bool TorrentContentFilterModel::hasFiltered(const QModelIndex &folder) const
QString name = folder.data().toString();
if (name.contains(filterRegularExpression()))
return true;
for (int child = 0; child < m_model->rowCount(folder); ++child)
{
QModelIndex childIndex = m_model->index(child, 0, folder);
@ -133,8 +125,10 @@ bool TorrentContentFilterModel::hasFiltered(const QModelIndex &folder) const @@ -133,8 +125,10 @@ bool TorrentContentFilterModel::hasFiltered(const QModelIndex &folder) const
{
if (hasFiltered(childIndex))
return true;
continue;
}
name = childIndex.data().toString();
if (name.contains(filterRegularExpression()))
return true;

17
src/gui/torrentcontentfiltermodel.h

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006-2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -41,25 +42,17 @@ class TorrentContentFilterModel final : public QSortFilterProxyModel @@ -41,25 +42,17 @@ class TorrentContentFilterModel final : public QSortFilterProxyModel
Q_DISABLE_COPY_MOVE(TorrentContentFilterModel)
public:
TorrentContentFilterModel(QObject *parent = nullptr);
explicit TorrentContentFilterModel(QObject *parent = nullptr);
TorrentContentModel *model() const;
void setSourceModel(TorrentContentModel *model);
TorrentContentModelItem::ItemType itemType(const QModelIndex &index) const;
int getFileIndex(const QModelIndex &index) const;
QModelIndex parent(const QModelIndex &child) const override;
public slots:
void selectAll();
void selectNone();
signals:
void filteredFilesChanged();
protected:
private:
using QSortFilterProxyModel::setSourceModel;
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
private:
bool hasFiltered(const QModelIndex &folder) const;
TorrentContentModel *m_model = nullptr;

35
src/gui/properties/proplistdelegate.cpp → src/gui/torrentcontentitemdelegate.cpp

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -26,7 +27,7 @@ @@ -26,7 +27,7 @@
* exception statement from your version.
*/
#include "proplistdelegate.h"
#include "torrentcontentitemdelegate.h"
#include <QComboBox>
#include <QModelIndex>
@ -36,15 +37,13 @@ @@ -36,15 +37,13 @@
#include "base/bittorrent/downloadpriority.h"
#include "base/bittorrent/torrent.h"
#include "gui/torrentcontentmodel.h"
#include "propertieswidget.h"
PropListDelegate::PropListDelegate(PropertiesWidget *properties)
: QStyledItemDelegate {properties}
, m_properties {properties}
TorrentContentItemDelegate::TorrentContentItemDelegate(QWidget *parent)
: QStyledItemDelegate(parent)
{
}
void PropListDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
void TorrentContentItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
auto *combobox = static_cast<QComboBox *>(editor);
// Set combobox index
@ -69,18 +68,11 @@ void PropListDelegate::setEditorData(QWidget *editor, const QModelIndex &index) @@ -69,18 +68,11 @@ void PropListDelegate::setEditorData(QWidget *editor, const QModelIndex &index)
}
}
QWidget *PropListDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const
QWidget *TorrentContentItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &index) const
{
if (index.column() != PRIORITY)
if (index.column() != TorrentContentModelItem::COL_PRIO)
return nullptr;
if (m_properties)
{
const BitTorrent::Torrent *torrent = m_properties->getCurrentTorrent();
if (!torrent || !torrent->hasMetadata())
return nullptr;
}
auto *editor = new QComboBox(parent);
editor->setFocusPolicy(Qt::StrongFocus);
editor->addItem(tr("Do not download", "Do not download (priority)"));
@ -97,13 +89,13 @@ QWidget *PropListDelegate::createEditor(QWidget *parent, const QStyleOptionViewI @@ -97,13 +89,13 @@ QWidget *PropListDelegate::createEditor(QWidget *parent, const QStyleOptionViewI
connect(editor, qOverload<int>(&QComboBox::currentIndexChanged), this, [this, editor]()
{
emit const_cast<PropListDelegate *>(this)->commitData(editor);
emit const_cast<TorrentContentItemDelegate *>(this)->commitData(editor);
});
return editor;
}
void PropListDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
void TorrentContentItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
const auto *combobox = static_cast<QComboBox *>(editor);
@ -130,23 +122,22 @@ void PropListDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, @@ -130,23 +122,22 @@ void PropListDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
if (newPriority != previousPriority)
{
model->setData(index, newPriority);
emit filteredFilesChanged();
}
}
void PropListDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const
void TorrentContentItemDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const
{
editor->setGeometry(option.rect);
}
void PropListDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
void TorrentContentItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
switch (index.column())
{
case PropColumn::PROGRESS:
case TorrentContentModelItem::COL_PROGRESS:
{
const int progress = static_cast<int>(index.data(TorrentContentModel::UnderlyingDataRole).toReal());
const int priority = index.sibling(index.row(), PropColumn::PRIORITY).data(TorrentContentModel::UnderlyingDataRole).toInt();
const int priority = index.sibling(index.row(), TorrentContentModelItem::COL_PRIO).data(TorrentContentModel::UnderlyingDataRole).toInt();
const bool isEnabled = static_cast<BitTorrent::DownloadPriority>(priority) != BitTorrent::DownloadPriority::Ignored;
QStyleOptionViewItem customOption {option};

24
src/gui/properties/proplistdelegate.h → src/gui/torrentcontentitemdelegate.h

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -36,26 +37,13 @@ class QAbstractItemModel; @@ -36,26 +37,13 @@ class QAbstractItemModel;
class QModelIndex;
class QStyleOptionViewItem;
class PropertiesWidget;
// Defines for properties list columns
enum PropColumn
{
NAME,
PCSIZE,
PROGRESS,
PRIORITY,
REMAINING,
AVAILABILITY
};
class PropListDelegate final : public QStyledItemDelegate
class TorrentContentItemDelegate final : public QStyledItemDelegate
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(PropListDelegate)
Q_DISABLE_COPY_MOVE(TorrentContentItemDelegate)
public:
explicit PropListDelegate(PropertiesWidget *properties);
explicit TorrentContentItemDelegate(QWidget *parent = nullptr);
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
@ -65,10 +53,6 @@ public slots: @@ -65,10 +53,6 @@ public slots:
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
signals:
void filteredFilesChanged() const;
private:
PropertiesWidget *m_properties = nullptr;
ProgressBarPainter m_progressBarPainter;
};

274
src/gui/torrentcontentmodel.cpp

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006-2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -33,6 +34,8 @@ @@ -33,6 +34,8 @@
#include <QFileIconProvider>
#include <QFileInfo>
#include <QIcon>
#include <QPointer>
#include <QScopeGuard>
#if defined(Q_OS_WIN)
#include <Windows.h>
@ -50,8 +53,9 @@ @@ -50,8 +53,9 @@
#include <QPixmapCache>
#endif
#include "base/bittorrent/abstractfilestorage.h"
#include "base/bittorrent/downloadpriority.h"
#include "base/bittorrent/torrentcontenthandler.h"
#include "base/exceptions.h"
#include "base/global.h"
#include "base/path.h"
#include "base/utils/fs.h"
@ -208,62 +212,89 @@ TorrentContentModel::~TorrentContentModel() @@ -208,62 +212,89 @@ TorrentContentModel::~TorrentContentModel()
delete m_rootItem;
}
void TorrentContentModel::updateFilesProgress(const QVector<qreal> &fp)
void TorrentContentModel::updateFilesProgress()
{
Q_ASSERT(m_filesIndex.size() == fp.size());
Q_ASSERT(m_contentHandler && m_contentHandler->hasMetadata());
using HandlerPtr = QPointer<BitTorrent::TorrentContentHandler>;
m_contentHandler->fetchFilesProgress([this, handler = HandlerPtr(m_contentHandler)](const QVector<qreal> &filesProgress)
{
if (handler != m_contentHandler)
return;
Q_ASSERT(m_filesIndex.size() == filesProgress.size());
// XXX: Why is this necessary?
if (m_filesIndex.size() != fp.size()) return;
if (Q_UNLIKELY(m_filesIndex.size() != filesProgress.size()))
return;
emit layoutAboutToBeChanged();
for (int i = 0; i < fp.size(); ++i)
m_filesIndex[i]->setProgress(fp[i]);
for (int i = 0; i < filesProgress.size(); ++i)
m_filesIndex[i]->setProgress(filesProgress[i]);
// Update folders progress in the tree
m_rootItem->recalculateProgress();
m_rootItem->recalculateAvailability();
const QVector<ColumnInterval> columns =
{
{TorrentContentModelItem::COL_PROGRESS, TorrentContentModelItem::COL_PROGRESS}
};
notifySubtreeUpdated(index(0, 0), columns);
});
}
void TorrentContentModel::updateFilesPriorities(const QVector<BitTorrent::DownloadPriority> &fprio)
void TorrentContentModel::updateFilesPriorities()
{
Q_ASSERT(m_contentHandler && m_contentHandler->hasMetadata());
const QVector<BitTorrent::DownloadPriority> fprio = m_contentHandler->filePriorities();
Q_ASSERT(m_filesIndex.size() == fprio.size());
// XXX: Why is this necessary?
if (m_filesIndex.size() != fprio.size())
return;
emit layoutAboutToBeChanged();
for (int i = 0; i < fprio.size(); ++i)
m_filesIndex[i]->setPriority(static_cast<BitTorrent::DownloadPriority>(fprio[i]));
const QVector<ColumnInterval> columns =
{
{TorrentContentModelItem::COL_NAME, TorrentContentModelItem::COL_NAME},
{TorrentContentModelItem::COL_PRIO, TorrentContentModelItem::COL_PRIO}
};
notifySubtreeUpdated(index(0, 0), columns);
}
void TorrentContentModel::updateFilesAvailability(const QVector<qreal> &fa)
void TorrentContentModel::updateFilesAvailability()
{
Q_ASSERT(m_filesIndex.size() == fa.size());
Q_ASSERT(m_contentHandler && m_contentHandler->hasMetadata());
using HandlerPtr = QPointer<BitTorrent::TorrentContentHandler>;
m_contentHandler->fetchAvailableFileFractions([this, handler = HandlerPtr(m_contentHandler)](const QVector<qreal> &availableFileFractions)
{
if (handler != m_contentHandler)
return;
Q_ASSERT(m_filesIndex.size() == availableFileFractions.size());
// XXX: Why is this necessary?
if (m_filesIndex.size() != fa.size()) return;
if (Q_UNLIKELY(m_filesIndex.size() != availableFileFractions.size()))
return;
emit layoutAboutToBeChanged();
for (int i = 0; i < m_filesIndex.size(); ++i)
m_filesIndex[i]->setAvailability(fa[i]);
m_filesIndex[i]->setAvailability(availableFileFractions[i]);
// Update folders progress in the tree
m_rootItem->recalculateProgress();
});
}
bool TorrentContentModel::setItemPriority(const QModelIndex &index, BitTorrent::DownloadPriority priority)
{
Q_ASSERT(index.isValid());
auto *item = static_cast<TorrentContentModelItem *>(index.internalPointer());
const BitTorrent::DownloadPriority currentPriority = item->priority();
if (currentPriority == priority)
return false;
item->setPriority(priority);
m_contentHandler->prioritizeFiles(getFilePriorities());
// Update folders progress in the tree
m_rootItem->recalculateProgress();
m_rootItem->recalculateAvailability();
const QVector<ColumnInterval> columns =
{
{TorrentContentModelItem::COL_AVAILABILITY, TorrentContentModelItem::COL_AVAILABILITY}
{TorrentContentModelItem::COL_NAME, TorrentContentModelItem::COL_NAME},
{TorrentContentModelItem::COL_PRIO, TorrentContentModelItem::COL_PRIO}
};
notifySubtreeUpdated(index(0, 0), columns);
notifySubtreeUpdated(index, columns);
return true;
}
QVector<BitTorrent::DownloadPriority> TorrentContentModel::getFilePriorities() const
@ -275,14 +306,6 @@ QVector<BitTorrent::DownloadPriority> TorrentContentModel::getFilePriorities() c @@ -275,14 +306,6 @@ QVector<BitTorrent::DownloadPriority> TorrentContentModel::getFilePriorities() c
return prio;
}
bool TorrentContentModel::allFiltered() const
{
return std::all_of(m_filesIndex.cbegin(), m_filesIndex.cend(), [](const TorrentContentModelFile *fileItem)
{
return (fileItem->priority() == BitTorrent::DownloadPriority::Ignored);
});
}
int TorrentContentModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
@ -296,9 +319,6 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu @@ -296,9 +319,6 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu
if ((index.column() == TorrentContentModelItem::COL_NAME) && (role == Qt::CheckStateRole))
{
auto *item = static_cast<TorrentContentModelItem *>(index.internalPointer());
const BitTorrent::DownloadPriority currentPrio = item->priority();
const auto checkState = static_cast<Qt::CheckState>(value.toInt());
const BitTorrent::DownloadPriority newPrio = (checkState == Qt::PartiallyChecked)
? BitTorrent::DownloadPriority::Mixed
@ -306,23 +326,7 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu @@ -306,23 +326,7 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu
? BitTorrent::DownloadPriority::Ignored
: BitTorrent::DownloadPriority::Normal);
if (currentPrio != newPrio)
{
item->setPriority(newPrio);
// Update folders progress in the tree
m_rootItem->recalculateProgress();
m_rootItem->recalculateAvailability();
const QVector<ColumnInterval> columns =
{
{TorrentContentModelItem::COL_NAME, TorrentContentModelItem::COL_NAME},
{TorrentContentModelItem::COL_PRIO, TorrentContentModelItem::COL_PRIO}
};
notifySubtreeUpdated(index, columns);
emit filteredFilesChanged();
return true;
}
return setItemPriority(index, newPrio);
}
if (role == Qt::EditRole)
@ -337,6 +341,23 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu @@ -337,6 +341,23 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu
const QString newName = value.toString();
if (currentName != newName)
{
try
{
const Path parentPath = getItemPath(index.parent());
const Path oldPath = parentPath / Path(currentName);
const Path newPath = parentPath / Path(newName);
if (item->itemType() == TorrentContentModelItem::FileType)
m_contentHandler->renameFile(oldPath, newPath);
else
m_contentHandler->renameFolder(oldPath, newPath);
}
catch (const RuntimeError &error)
{
emit renameFailed(error.message());
return false;
}
item->setName(newName);
emit dataChanged(index, index);
return true;
@ -346,27 +367,8 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu @@ -346,27 +367,8 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu
case TorrentContentModelItem::COL_PRIO:
{
const BitTorrent::DownloadPriority currentPrio = item->priority();
const auto newPrio = static_cast<BitTorrent::DownloadPriority>(value.toInt());
if (currentPrio != newPrio)
{
item->setPriority(newPrio);
const QVector<ColumnInterval> columns =
{
{TorrentContentModelItem::COL_NAME, TorrentContentModelItem::COL_NAME},
{TorrentContentModelItem::COL_PRIO, TorrentContentModelItem::COL_PRIO}
};
notifySubtreeUpdated(index, columns);
if ((newPrio == BitTorrent::DownloadPriority::Ignored)
|| (currentPrio == BitTorrent::DownloadPriority::Ignored))
{
emit filteredFilesChanged();
}
return true;
}
return setItemPriority(index, newPrio);
}
break;
@ -383,16 +385,23 @@ TorrentContentModelItem::ItemType TorrentContentModel::itemType(const QModelInde @@ -383,16 +385,23 @@ TorrentContentModelItem::ItemType TorrentContentModel::itemType(const QModelInde
return static_cast<const TorrentContentModelItem *>(index.internalPointer())->itemType();
}
int TorrentContentModel::getFileIndex(const QModelIndex &index)
int TorrentContentModel::getFileIndex(const QModelIndex &index) const
{
auto *item = static_cast<TorrentContentModelItem *>(index.internalPointer());
if (item->itemType() == TorrentContentModelItem::FileType)
return static_cast<TorrentContentModelFile *>(item)->fileIndex();
Q_ASSERT(item->itemType() == TorrentContentModelItem::FileType);
return -1;
}
Path TorrentContentModel::getItemPath(const QModelIndex &index) const
{
Path path;
for (QModelIndex i = index; i.isValid(); i = i.parent())
path = Path(i.data().toString()) / path;
return path;
}
QVariant TorrentContentModel::data(const QModelIndex &index, const int role) const
{
if (!index.isValid())
@ -403,29 +412,45 @@ QVariant TorrentContentModel::data(const QModelIndex &index, const int role) con @@ -403,29 +412,45 @@ QVariant TorrentContentModel::data(const QModelIndex &index, const int role) con
switch (role)
{
case Qt::DecorationRole:
{
if (index.column() != TorrentContentModelItem::COL_NAME)
return {};
if (item->itemType() == TorrentContentModelItem::FolderType)
return m_fileIconProvider->icon(QFileIconProvider::Folder);
return m_fileIconProvider->icon(QFileInfo(item->name()));
}
case Qt::CheckStateRole:
{
if (index.column() != TorrentContentModelItem::COL_NAME)
return {};
if (item->priority() == BitTorrent::DownloadPriority::Ignored)
return Qt::Unchecked;
if (item->priority() == BitTorrent::DownloadPriority::Mixed)
return Qt::PartiallyChecked;
return Qt::Checked;
{
Q_ASSERT(item->itemType() == TorrentContentModelItem::FolderType);
const auto *folder = static_cast<TorrentContentModelFolder *>(item);
const auto childItems = folder->children();
const bool hasIgnored = std::any_of(childItems.cbegin(), childItems.cend()
, [](const TorrentContentModelItem *childItem)
{
return (childItem->priority() == BitTorrent::DownloadPriority::Ignored);
});
return hasIgnored ? Qt::PartiallyChecked : Qt::Checked;
}
return Qt::Checked;
case Qt::TextAlignmentRole:
if ((index.column() == TorrentContentModelItem::COL_SIZE)
|| (index.column() == TorrentContentModelItem::COL_REMAINING))
{
return QVariant {Qt::AlignRight | Qt::AlignVCenter};
}
return {};
case Qt::DisplayRole:
@ -469,7 +494,10 @@ QVariant TorrentContentModel::headerData(int section, Qt::Orientation orientatio @@ -469,7 +494,10 @@ QVariant TorrentContentModel::headerData(int section, Qt::Orientation orientatio
case Qt::TextAlignmentRole:
if ((section == TorrentContentModelItem::COL_SIZE)
|| (section == TorrentContentModelItem::COL_REMAINING))
{
return QVariant {Qt::AlignRight | Qt::AlignVCenter};
}
return {};
default:
@ -525,26 +553,11 @@ int TorrentContentModel::rowCount(const QModelIndex &parent) const @@ -525,26 +553,11 @@ int TorrentContentModel::rowCount(const QModelIndex &parent) const
return parentItem ? parentItem->childCount() : 0;
}
void TorrentContentModel::clear()
{
qDebug("clear called");
beginResetModel();
m_filesIndex.clear();
m_rootItem->deleteAllChildren();
endResetModel();
}
void TorrentContentModel::setupModelData(const BitTorrent::AbstractFileStorage &info)
void TorrentContentModel::populate()
{
qDebug("setup model data called");
const int filesCount = info.filesCount();
if (filesCount <= 0)
return;
Q_ASSERT(m_contentHandler && m_contentHandler->hasMetadata());
beginResetModel();
// Initialize files_index array
qDebug("Torrent contains %d files", filesCount);
const int filesCount = m_contentHandler->filesCount();
m_filesIndex.reserve(filesCount);
QHash<TorrentContentModelFolder *, QHash<QString, TorrentContentModelFolder *>> folderMap;
@ -553,7 +566,7 @@ void TorrentContentModel::setupModelData(const BitTorrent::AbstractFileStorage & @@ -553,7 +566,7 @@ void TorrentContentModel::setupModelData(const BitTorrent::AbstractFileStorage &
// Iterate over files
for (int i = 0; i < filesCount; ++i)
{
const QString path = info.filePath(i).data();
const QString path = m_contentHandler->filePath(i).data();
// Iterate of parts of the path to create necessary folders
QList<QStringView> pathFolders = QStringView(path).split(u'/', Qt::SkipEmptyParts);
@ -584,13 +597,64 @@ void TorrentContentModel::setupModelData(const BitTorrent::AbstractFileStorage & @@ -584,13 +597,64 @@ void TorrentContentModel::setupModelData(const BitTorrent::AbstractFileStorage &
}
// Actually create the file
TorrentContentModelFile *fileItem = new TorrentContentModelFile(
fileName, info.fileSize(i), lastParent, i);
auto *fileItem = new TorrentContentModelFile(fileName, m_contentHandler->fileSize(i), lastParent, i);
lastParent->appendChild(fileItem);
m_filesIndex.push_back(fileItem);
}
updateFilesProgress();
updateFilesPriorities();
updateFilesAvailability();
}
void TorrentContentModel::setContentHandler(BitTorrent::TorrentContentHandler *contentHandler)
{
beginResetModel();
[[maybe_unused]] const auto modelResetGuard = qScopeGuard([this] { endResetModel(); });
if (m_contentHandler)
{
m_filesIndex.clear();
m_rootItem->deleteAllChildren();
}
m_contentHandler = contentHandler;
if (m_contentHandler && m_contentHandler->hasMetadata())
populate();
}
BitTorrent::TorrentContentHandler *TorrentContentModel::contentHandler() const
{
return m_contentHandler;
}
void TorrentContentModel::refresh()
{
if (!m_contentHandler || !m_contentHandler->hasMetadata())
return;
if (!m_filesIndex.isEmpty())
{
updateFilesProgress();
updateFilesPriorities();
updateFilesAvailability();
const QVector<ColumnInterval> columns =
{
{TorrentContentModelItem::COL_NAME, TorrentContentModelItem::COL_NAME},
{TorrentContentModelItem::COL_PROGRESS, TorrentContentModelItem::COL_PROGRESS},
{TorrentContentModelItem::COL_PRIO, TorrentContentModelItem::COL_PRIO},
{TorrentContentModelItem::COL_AVAILABILITY, TorrentContentModelItem::COL_AVAILABILITY}
};
notifySubtreeUpdated(index(0, 0), columns);
}
else
{
beginResetModel();
populate();
endResetModel();
}
}
void TorrentContentModel::notifySubtreeUpdated(const QModelIndex &index, const QVector<ColumnInterval> &columns)

31
src/gui/torrentcontentmodel.h

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2006-2012 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
@ -32,6 +33,7 @@ @@ -32,6 +33,7 @@
#include <QVector>
#include "base/indexrange.h"
#include "base/pathfwd.h"
#include "torrentcontentmodelitem.h"
class QFileIconProvider;
@ -42,7 +44,7 @@ class TorrentContentModelFile; @@ -42,7 +44,7 @@ class TorrentContentModelFile;
namespace BitTorrent
{
class AbstractFileStorage;
class TorrentContentHandler;
}
class TorrentContentModel final : public QAbstractItemModel
@ -56,35 +58,42 @@ public: @@ -56,35 +58,42 @@ public:
UnderlyingDataRole = Qt::UserRole
};
TorrentContentModel(QObject *parent = nullptr);
explicit TorrentContentModel(QObject *parent = nullptr);
~TorrentContentModel() override;
void updateFilesProgress(const QVector<qreal> &fp);
void updateFilesPriorities(const QVector<BitTorrent::DownloadPriority> &fprio);
void updateFilesAvailability(const QVector<qreal> &fa);
void setContentHandler(BitTorrent::TorrentContentHandler *contentHandler);
BitTorrent::TorrentContentHandler *contentHandler() const;
void refresh();
QVector<BitTorrent::DownloadPriority> getFilePriorities() const;
bool allFiltered() const;
TorrentContentModelItem::ItemType itemType(const QModelIndex &index) const;
int getFileIndex(const QModelIndex &index) const;
Path getItemPath(const QModelIndex &index) const;
int columnCount(const QModelIndex &parent = {}) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
TorrentContentModelItem::ItemType itemType(const QModelIndex &index) const;
int getFileIndex(const QModelIndex &index);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = {}) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = {}) const override;
void clear();
void setupModelData(const BitTorrent::AbstractFileStorage &info);
signals:
void filteredFilesChanged();
void renameFailed(const QString &errorMessage);
private:
using ColumnInterval = IndexInterval<int>;
void populate();
void updateFilesProgress();
void updateFilesPriorities();
void updateFilesAvailability();
bool setItemPriority(const QModelIndex &index, BitTorrent::DownloadPriority priority);
void notifySubtreeUpdated(const QModelIndex &index, const QVector<ColumnInterval> &columns);
BitTorrent::TorrentContentHandler *m_contentHandler = nullptr;
TorrentContentModelFolder *m_rootItem = nullptr;
QVector<TorrentContentModelFile *> m_filesIndex;
QFileIconProvider *m_fileIconProvider = nullptr;

2
src/gui/torrentcontentmodelfolder.h

@ -62,5 +62,5 @@ public: @@ -62,5 +62,5 @@ public:
int childCount() const;
private:
QVector<TorrentContentModelItem*> m_childItems;
QVector<TorrentContentModelItem *> m_childItems;
};

165
src/gui/torrentcontenttreeview.cpp

@ -1,165 +0,0 @@ @@ -1,165 +0,0 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2014 Ivan Sorokin <vanyacpp@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include "torrentcontenttreeview.h"
#include <QDir>
#include <QHeaderView>
#include <QKeyEvent>
#include <QLineEdit>
#include <QMessageBox>
#include <QModelIndexList>
#include <QThread>
#include <QWheelEvent>
#include "base/bittorrent/abstractfilestorage.h"
#include "base/bittorrent/common.h"
#include "base/bittorrent/session.h"
#include "base/bittorrent/torrent.h"
#include "base/bittorrent/torrentinfo.h"
#include "base/exceptions.h"
#include "base/global.h"
#include "base/path.h"
#include "base/utils/fs.h"
#include "autoexpandabledialog.h"
#include "raisedmessagebox.h"
#include "torrentcontentfiltermodel.h"
#include "torrentcontentmodelitem.h"
namespace
{
Path getFullPath(const QModelIndex &idx)
{
Path path;
for (QModelIndex i = idx; i.isValid(); i = i.parent())
path = Path(i.data().toString()) / path;
return path;
}
}
TorrentContentTreeView::TorrentContentTreeView(QWidget *parent)
: QTreeView(parent)
{
setExpandsOnDoubleClick(false);
header()->setFirstSectionMovable(true);
}
void TorrentContentTreeView::keyPressEvent(QKeyEvent *event)
{
if ((event->key() != Qt::Key_Space) && (event->key() != Qt::Key_Select))
{
QTreeView::keyPressEvent(event);
return;
}
event->accept();
const QVariant value = currentNameCell().data(Qt::CheckStateRole);
if (!value.isValid())
{
Q_ASSERT(false);
return;
}
const Qt::CheckState state = (static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked)
? Qt::Unchecked : Qt::Checked;
const QModelIndexList selection = selectionModel()->selectedRows(TorrentContentModelItem::COL_NAME);
for (const QModelIndex &index : selection)
model()->setData(index, state, Qt::CheckStateRole);
}
void TorrentContentTreeView::renameSelectedFile(BitTorrent::AbstractFileStorage &fileStorage)
{
const QModelIndexList selectedIndexes = selectionModel()->selectedRows(0);
if (selectedIndexes.size() != 1) return;
const QPersistentModelIndex modelIndex = selectedIndexes.first();
if (!modelIndex.isValid()) return;
auto model = dynamic_cast<TorrentContentFilterModel *>(TorrentContentTreeView::model());
if (!model) return;
const bool isFile = (model->itemType(modelIndex) == TorrentContentModelItem::FileType);
// Ask for new name
bool ok = false;
QString newName = AutoExpandableDialog::getText(this, tr("Renaming"), tr("New name:"), QLineEdit::Normal
, modelIndex.data().toString(), &ok, isFile).trimmed();
if (!ok || !modelIndex.isValid()) return;
const QString oldName = modelIndex.data().toString();
if (newName == oldName)
return; // Name did not change
const Path parentPath = getFullPath(modelIndex.parent());
const Path oldPath = parentPath / Path(oldName);
const Path newPath = parentPath / Path(newName);
try
{
if (isFile)
fileStorage.renameFile(oldPath, newPath);
else
fileStorage.renameFolder(oldPath, newPath);
model->setData(modelIndex, newName);
}
catch (const RuntimeError &error)
{
RaisedMessageBox::warning(this, tr("Rename error"), error.message(), QMessageBox::Ok);
}
}
QModelIndex TorrentContentTreeView::currentNameCell() const
{
const QModelIndex current = currentIndex();
if (!current.isValid())
{
Q_ASSERT(false);
return {};
}
return current.siblingAtColumn(TorrentContentModelItem::COL_NAME);
}
void TorrentContentTreeView::wheelEvent(QWheelEvent *event)
{
if (event->modifiers() & Qt::ShiftModifier)
{
// Shift + scroll = horizontal scroll
event->accept();
QWheelEvent scrollHEvent {event->position(), event->globalPosition()
, event->pixelDelta(), event->angleDelta().transposed(), event->buttons()
, event->modifiers(), event->phase(), event->inverted(), event->source()};
QTreeView::wheelEvent(&scrollHEvent);
return;
}
QTreeView::wheelEvent(event); // event delegated to base class
}

489
src/gui/torrentcontentwidget.cpp

@ -0,0 +1,489 @@ @@ -0,0 +1,489 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2014 Ivan Sorokin <vanyacpp@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include "torrentcontentwidget.h"
#include <QDir>
#include <QHeaderView>
#include <QKeyEvent>
#include <QLineEdit>
#include <QMenu>
#include <QMessageBox>
#include <QModelIndexList>
#include <QShortcut>
#include <QThread>
#include <QWheelEvent>
#include "base/bittorrent/torrentcontenthandler.h"
#include "base/path.h"
#include "base/utils/string.h"
#include "autoexpandabledialog.h"
#include "raisedmessagebox.h"
#include "torrentcontentfiltermodel.h"
#include "torrentcontentitemdelegate.h"
#include "torrentcontentmodel.h"
#include "torrentcontentmodelitem.h"
#include "uithememanager.h"
#include "utils.h"
#ifdef Q_OS_MACOS
#include "gui/macutilities.h"
#endif
TorrentContentWidget::TorrentContentWidget(QWidget *parent)
: QTreeView(parent)
{
setExpandsOnDoubleClick(false);
setSortingEnabled(true);
header()->setSortIndicator(0, Qt::AscendingOrder);
header()->setFirstSectionMovable(true);
header()->setContextMenuPolicy(Qt::CustomContextMenu);
m_model = new TorrentContentModel(this);
connect(m_model, &TorrentContentModel::renameFailed, this, [this](const QString &errorMessage)
{
RaisedMessageBox::warning(this, tr("Rename error"), errorMessage, QMessageBox::Ok);
});
m_filterModel = new TorrentContentFilterModel(this);
m_filterModel->setSourceModel(m_model);
QTreeView::setModel(m_filterModel);
auto itemDelegate = new TorrentContentItemDelegate(this);
setItemDelegate(itemDelegate);
connect(this, &QAbstractItemView::clicked, this, qOverload<const QModelIndex &>(&QAbstractItemView::edit));
connect(this, &QAbstractItemView::doubleClicked, this, &TorrentContentWidget::onItemDoubleClicked);
connect(this, &QWidget::customContextMenuRequested, this, &TorrentContentWidget::displayContextMenu);
connect(header(), &QWidget::customContextMenuRequested, this, &TorrentContentWidget::displayColumnHeaderMenu);
connect(header(), &QHeaderView::sectionMoved, this, &TorrentContentWidget::stateChanged);
connect(header(), &QHeaderView::sectionResized, this, &TorrentContentWidget::stateChanged);
connect(header(), &QHeaderView::sortIndicatorChanged, this, &TorrentContentWidget::stateChanged);
const auto *renameFileHotkey = new QShortcut(Qt::Key_F2, this, nullptr, nullptr, Qt::WidgetShortcut);
connect(renameFileHotkey, &QShortcut::activated, this, &TorrentContentWidget::renameSelectedFile);
const auto *openFileHotkeyReturn = new QShortcut(Qt::Key_Return, this, nullptr, nullptr, Qt::WidgetShortcut);
connect(openFileHotkeyReturn, &QShortcut::activated, this, &TorrentContentWidget::openSelectedFile);
const auto *openFileHotkeyEnter = new QShortcut(Qt::Key_Enter, this, nullptr, nullptr, Qt::WidgetShortcut);
connect(openFileHotkeyEnter, &QShortcut::activated, this, &TorrentContentWidget::openSelectedFile);
connect(model(), &QAbstractItemModel::modelReset, this, &TorrentContentWidget::expandRecursively);
}
void TorrentContentWidget::setContentHandler(BitTorrent::TorrentContentHandler *contentHandler)
{
m_model->setContentHandler(contentHandler);
if (!contentHandler)
return;
expandRecursively();
}
BitTorrent::TorrentContentHandler *TorrentContentWidget::contentHandler() const
{
return m_model->contentHandler();
}
void TorrentContentWidget::refresh()
{
setUpdatesEnabled(false);
m_model->refresh();
setUpdatesEnabled(true);
}
TorrentContentWidget::DoubleClickAction TorrentContentWidget::doubleClickAction() const
{
return m_doubleClickAction;
}
void TorrentContentWidget::setDoubleClickAction(DoubleClickAction action)
{
m_doubleClickAction = action;
}
TorrentContentWidget::ColumnsVisibilityMode TorrentContentWidget::columnsVisibilityMode() const
{
return m_columnsVisibilityMode;
}
void TorrentContentWidget::setColumnsVisibilityMode(ColumnsVisibilityMode mode)
{
m_columnsVisibilityMode = mode;
}
int TorrentContentWidget::getFileIndex(const QModelIndex &index) const
{
return m_filterModel->getFileIndex(index);
}
Path TorrentContentWidget::getItemPath(const QModelIndex &index) const
{
Path path;
for (QModelIndex i = index; i.isValid(); i = i.parent())
path = Path(i.data().toString()) / path;
return path;
}
void TorrentContentWidget::setFilterPattern(const QString &patternText)
{
const QString pattern = Utils::String::wildcardToRegexPattern(patternText);
m_filterModel->setFilterRegularExpression(QRegularExpression(pattern, QRegularExpression::CaseInsensitiveOption));
if (patternText.isEmpty())
{
collapseAll();
expand(m_filterModel->index(0, 0));
}
else
{
expandAll();
}
}
void TorrentContentWidget::checkAll()
{
for (int i = 0; i < model()->rowCount(); ++i)
model()->setData(model()->index(i, TorrentContentModelItem::COL_NAME), Qt::Checked, Qt::CheckStateRole);
}
void TorrentContentWidget::checkNone()
{
for (int i = 0; i < model()->rowCount(); ++i)
model()->setData(model()->index(i, TorrentContentModelItem::COL_NAME), Qt::Unchecked, Qt::CheckStateRole);
}
void TorrentContentWidget::keyPressEvent(QKeyEvent *event)
{
if ((event->key() != Qt::Key_Space) && (event->key() != Qt::Key_Select))
{
QTreeView::keyPressEvent(event);
return;
}
event->accept();
const QVariant value = currentNameCell().data(Qt::CheckStateRole);
if (!value.isValid())
{
Q_ASSERT(false);
return;
}
const Qt::CheckState state = (static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked)
? Qt::Unchecked : Qt::Checked;
const QModelIndexList selection = selectionModel()->selectedRows(TorrentContentModelItem::COL_NAME);
for (const QModelIndex &index : selection)
model()->setData(index, state, Qt::CheckStateRole);
}
void TorrentContentWidget::renameSelectedFile()
{
const QModelIndexList selectedIndexes = selectionModel()->selectedRows(0);
if (selectedIndexes.size() != 1)
return;
const QPersistentModelIndex modelIndex = selectedIndexes.first();
if (!modelIndex.isValid())
return;
// Ask for new name
const bool isFile = (m_filterModel->itemType(modelIndex) == TorrentContentModelItem::FileType);
bool ok = false;
QString newName = AutoExpandableDialog::getText(this, tr("Renaming"), tr("New name:"), QLineEdit::Normal
, modelIndex.data().toString(), &ok, isFile).trimmed();
if (!ok || !modelIndex.isValid())
return;
model()->setData(modelIndex, newName);
}
void TorrentContentWidget::applyPriorities(const BitTorrent::DownloadPriority priority)
{
const QModelIndexList selectedRows = selectionModel()->selectedRows(0);
for (const QModelIndex &index : selectedRows)
{
model()->setData(index.sibling(index.row(), Priority), static_cast<int>(priority));
}
}
void TorrentContentWidget::applyPrioritiesByOrder()
{
// Equally distribute the selected items into groups and for each group assign
// a download priority that will apply to each item. The number of groups depends on how
// many "download priority" are available to be assigned
const QModelIndexList selectedRows = selectionModel()->selectedRows(0);
const qsizetype priorityGroups = 3;
const auto priorityGroupSize = std::max<qsizetype>((selectedRows.length() / priorityGroups), 1);
for (qsizetype i = 0; i < selectedRows.length(); ++i)
{
auto priority = BitTorrent::DownloadPriority::Ignored;
switch (i / priorityGroupSize)
{
case 0:
priority = BitTorrent::DownloadPriority::Maximum;
break;
case 1:
priority = BitTorrent::DownloadPriority::High;
break;
default:
case 2:
priority = BitTorrent::DownloadPriority::Normal;
break;
}
const QModelIndex &index = selectedRows[i];
model()->setData(index.sibling(index.row(), Priority), static_cast<int>(priority));
}
}
void TorrentContentWidget::openSelectedFile()
{
const QModelIndexList selectedIndexes = selectionModel()->selectedRows(0);
if (selectedIndexes.size() != 1)
return;
openItem(selectedIndexes.first());
}
void TorrentContentWidget::setModel([[maybe_unused]] QAbstractItemModel *model)
{
Q_ASSERT_X(false, Q_FUNC_INFO, "Changing the model of TorrentContentWidget is not allowed.");
}
QModelIndex TorrentContentWidget::currentNameCell() const
{
const QModelIndex current = currentIndex();
if (!current.isValid())
{
Q_ASSERT(false);
return {};
}
return current.siblingAtColumn(TorrentContentModelItem::COL_NAME);
}
void TorrentContentWidget::displayColumnHeaderMenu()
{
QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
menu->setToolTipsVisible(true);
if (m_columnsVisibilityMode == ColumnsVisibilityMode::Editable)
{
menu->setTitle(tr("Column visibility"));
for (int i = 0; i < TorrentContentModelItem::NB_COL; ++i)
{
const auto columnName = model()->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString();
QAction *action = menu->addAction(columnName, this, [this, i](bool checked)
{
setColumnHidden(i, !checked);
if (checked && (columnWidth(i) <= 5))
resizeColumnToContents(i);
emit stateChanged();
});
action->setCheckable(true);
action->setChecked(!isColumnHidden(i));
if (i == TorrentContentModelItem::COL_NAME)
action->setEnabled(false);
}
menu->addSeparator();
}
QAction *resizeAction = menu->addAction(tr("Resize columns"), this, [this]()
{
for (int i = 0, count = header()->count(); i < count; ++i)
{
if (!isColumnHidden(i))
resizeColumnToContents(i);
}
emit stateChanged();
});
resizeAction->setToolTip(tr("Resize all non-hidden columns to the size of their contents"));
menu->popup(QCursor::pos());
}
void TorrentContentWidget::displayContextMenu()
{
const QModelIndexList selectedRows = selectionModel()->selectedRows(0);
if (selectedRows.empty())
return;
QMenu *menu = new QMenu(this);
menu->setAttribute(Qt::WA_DeleteOnClose);
if (selectedRows.size() == 1)
{
const QModelIndex index = selectedRows[0];
if (!contentHandler()->actualStorageLocation().isEmpty())
{
menu->addAction(UIThemeManager::instance()->getIcon(u"folder-documents"_qs), tr("Open")
, this, [this, index]() { openItem(index); });
menu->addAction(UIThemeManager::instance()->getIcon(u"directory"_qs), tr("Open containing folder")
, this, [this, index]() { openParentFolder(index); });
}
menu->addAction(UIThemeManager::instance()->getIcon(u"edit-rename"_qs), tr("Rename...")
, this, &TorrentContentWidget::renameSelectedFile);
menu->addSeparator();
QMenu *subMenu = menu->addMenu(tr("Priority"));
subMenu->addAction(tr("Do not download"), this, [this]
{
applyPriorities(BitTorrent::DownloadPriority::Ignored);
});
subMenu->addAction(tr("Normal"), this, [this]
{
applyPriorities(BitTorrent::DownloadPriority::Normal);
});
subMenu->addAction(tr("High"), this, [this]
{
applyPriorities(BitTorrent::DownloadPriority::High);
});
subMenu->addAction(tr("Maximum"), this, [this]
{
applyPriorities(BitTorrent::DownloadPriority::Maximum);
});
subMenu->addSeparator();
subMenu->addAction(tr("By shown file order"), this, &TorrentContentWidget::applyPrioritiesByOrder);
}
else
{
menu->addAction(tr("Do not download"), this, [this]
{
applyPriorities(BitTorrent::DownloadPriority::Ignored);
});
menu->addAction(tr("Normal priority"), this, [this]
{
applyPriorities(BitTorrent::DownloadPriority::Normal);
});
menu->addAction(tr("High priority"), this, [this]
{
applyPriorities(BitTorrent::DownloadPriority::High);
});
menu->addAction(tr("Maximum priority"), this, [this]
{
applyPriorities(BitTorrent::DownloadPriority::Maximum);
});
menu->addSeparator();
menu->addAction(tr("Priority by shown file order"), this, &TorrentContentWidget::applyPrioritiesByOrder);
}
// The selected torrent might have disappeared during exec()
// so we just close menu when an appropriate model is reset
connect(model(), &QAbstractItemModel::modelAboutToBeReset, menu, [menu]()
{
menu->setActiveAction(nullptr);
menu->close();
});
menu->popup(QCursor::pos());
}
void TorrentContentWidget::openItem(const QModelIndex &index) const
{
if (!index.isValid())
return;
m_model->contentHandler()->flushCache(); // Flush data
Utils::Gui::openPath(getFullPath(index));
}
void TorrentContentWidget::openParentFolder(const QModelIndex &index) const
{
const Path path = getFullPath(index);
m_model->contentHandler()->flushCache(); // Flush data
#ifdef Q_OS_MACOS
MacUtils::openFiles({path});
#else
Utils::Gui::openFolderSelect(path);
#endif
}
Path TorrentContentWidget::getFullPath(const QModelIndex &index) const
{
const auto contentHandler = m_model->contentHandler();
if (const int fileIdx = getFileIndex(index); fileIdx >= 0)
{
const Path fullPath = contentHandler->actualStorageLocation() / contentHandler->actualFilePath(fileIdx);
return fullPath;
}
// folder type
const Path fullPath = contentHandler->actualStorageLocation() / getItemPath(index);
return fullPath;
}
void TorrentContentWidget::onItemDoubleClicked(const QModelIndex &index)
{
const auto contentHandler = m_model->contentHandler();
Q_ASSERT(contentHandler && contentHandler->hasMetadata());
if (Q_UNLIKELY(!contentHandler || !contentHandler->hasMetadata()))
return;
if (m_doubleClickAction == DoubleClickAction::Rename)
renameSelectedFile();
else
openItem(index);
}
void TorrentContentWidget::expandRecursively()
{
QModelIndex currentIndex;
while (model()->rowCount(currentIndex) == 1)
{
currentIndex = model()->index(0, 0, currentIndex);
setExpanded(currentIndex, true);
}
}
void TorrentContentWidget::wheelEvent(QWheelEvent *event)
{
if (event->modifiers() & Qt::ShiftModifier)
{
// Shift + scroll = horizontal scroll
event->accept();
QWheelEvent scrollHEvent {event->position(), event->globalPosition()
, event->pixelDelta(), event->angleDelta().transposed(), event->buttons()
, event->modifiers(), event->phase(), event->inverted(), event->source()};
QTreeView::wheelEvent(&scrollHEvent);
return;
}
QTreeView::wheelEvent(event); // event delegated to base class
}

121
src/gui/torrentcontentwidget.h

@ -0,0 +1,121 @@ @@ -0,0 +1,121 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2022 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2014 Ivan Sorokin <vanyacpp@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#pragma once
#include <QTreeView>
#include "base/bittorrent/downloadpriority.h"
#include "base/pathfwd.h"
namespace BitTorrent
{
class Torrent;
class TorrentContentHandler;
class TorrentInfo;
}
class TorrentContentFilterModel;
class TorrentContentModel;
class TorrentContentWidget final : public QTreeView
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(TorrentContentWidget)
public:
enum Column
{
Name,
Size,
Progress,
Priority,
Remaining,
Availability
};
enum class DoubleClickAction
{
Open,
Rename
};
enum class ColumnsVisibilityMode
{
Editable,
Locked
};
explicit TorrentContentWidget(QWidget *parent = nullptr);
void setContentHandler(BitTorrent::TorrentContentHandler *contentHandler);
BitTorrent::TorrentContentHandler *contentHandler() const;
void refresh();
DoubleClickAction doubleClickAction() const;
void setDoubleClickAction(DoubleClickAction action);
ColumnsVisibilityMode columnsVisibilityMode() const;
void setColumnsVisibilityMode(ColumnsVisibilityMode mode);
int getFileIndex(const QModelIndex &index) const;
Path getItemPath(const QModelIndex &index) const;
void setFilterPattern(const QString &patternText);
void checkAll();
void checkNone();
signals:
void stateChanged();
private:
void setModel(QAbstractItemModel *model) override;
void keyPressEvent(QKeyEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
QModelIndex currentNameCell() const;
void displayColumnHeaderMenu();
void displayContextMenu();
void openItem(const QModelIndex &index) const;
void openParentFolder(const QModelIndex &index) const;
void openSelectedFile();
void renameSelectedFile();
void applyPriorities(BitTorrent::DownloadPriority priority);
void applyPrioritiesByOrder();
Path getFullPath(const QModelIndex &index) const;
void onItemDoubleClicked(const QModelIndex &index);
// Expand single-item folders recursively.
// This will trigger sorting and filtering so do it after all relevant data is loaded.
void expandRecursively();
TorrentContentModel *m_model;
TorrentContentFilterModel *m_filterModel;
DoubleClickAction m_doubleClickAction = DoubleClickAction::Rename;
ColumnsVisibilityMode m_columnsVisibilityMode = ColumnsVisibilityMode::Editable;
};
Loading…
Cancel
Save