diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 93054ce16..8ed310686 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -35,6 +35,7 @@ mainwindow.h optionsdialog.h previewlistdelegate.h previewselectdialog.h +private/tristatewidget.h raisedmessagebox.h scanfoldersdelegate.h shutdownconfirmdialog.h @@ -58,6 +59,7 @@ transferlistfilterswidget.h transferlistmodel.h transferlistsortmodel.h transferlistwidget.h +tristateaction.h uithememanager.h updownratiodialog.h utils.h @@ -82,6 +84,7 @@ loglistwidget.cpp mainwindow.cpp optionsdialog.cpp previewselectdialog.cpp +private/tristatewidget.cpp raisedmessagebox.cpp scanfoldersdelegate.cpp shutdownconfirmdialog.cpp @@ -105,6 +108,7 @@ transferlistfilterswidget.cpp transferlistmodel.cpp transferlistsortmodel.cpp transferlistwidget.cpp +tristateaction.cpp uithememanager.cpp updownratiodialog.cpp utils.cpp diff --git a/src/gui/gui.pri b/src/gui/gui.pri index 452853e0f..02a099668 100644 --- a/src/gui/gui.pri +++ b/src/gui/gui.pri @@ -29,6 +29,7 @@ HEADERS += \ $$PWD/optionsdialog.h \ $$PWD/previewlistdelegate.h \ $$PWD/previewselectdialog.h \ + $$PWD/private/tristatewidget.h \ $$PWD/raisedmessagebox.h \ $$PWD/rss/articlelistwidget.h \ $$PWD/rss/automatedrssdownloader.h \ @@ -63,6 +64,7 @@ HEADERS += \ $$PWD/transferlistmodel.h \ $$PWD/transferlistsortmodel.h \ $$PWD/transferlistwidget.h \ + $$PWD/tristateaction.h \ $$PWD/uithememanager.h \ $$PWD/updownratiodialog.h \ $$PWD/utils.h @@ -87,6 +89,7 @@ SOURCES += \ $$PWD/mainwindow.cpp \ $$PWD/optionsdialog.cpp \ $$PWD/previewselectdialog.cpp \ + $$PWD/private/tristatewidget.cpp \ $$PWD/raisedmessagebox.cpp \ $$PWD/rss/articlelistwidget.cpp \ $$PWD/rss/automatedrssdownloader.cpp \ @@ -121,6 +124,7 @@ SOURCES += \ $$PWD/transferlistmodel.cpp \ $$PWD/transferlistsortmodel.cpp \ $$PWD/transferlistwidget.cpp \ + $$PWD/tristateaction.cpp \ $$PWD/uithememanager.cpp \ $$PWD/updownratiodialog.cpp \ $$PWD/utils.cpp diff --git a/src/gui/private/tristatewidget.cpp b/src/gui/private/tristatewidget.cpp new file mode 100644 index 000000000..ee5f92776 --- /dev/null +++ b/src/gui/private/tristatewidget.cpp @@ -0,0 +1,144 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2019 Mike Tzou (Chocobo1) + * + * 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 "tristatewidget.h" + +#include +#include +#include +#include +#include +#include +#include + +TriStateWidget::TriStateWidget(const QString &text, QWidget *parent) + : QWidget {parent} + , m_closeOnTriggered {true} + , m_checkState {Qt::Unchecked} + , m_text {text} +{ + setMouseTracking(true); // for visual effects via mouse navigation + setFocusPolicy(Qt::TabFocus); // for visual effects via keyboard navigation +} + +void TriStateWidget::setCheckState(const Qt::CheckState checkState) +{ + m_checkState = checkState; +} + +void TriStateWidget::setCloseOnTriggered(const bool enabled) +{ + m_closeOnTriggered = enabled; +} + +QSize TriStateWidget::minimumSizeHint() const +{ + QStyleOptionMenuItem opt; + opt.initFrom(this); + opt.menuHasCheckableItems = true; + const QSize contentSize = fontMetrics().size(Qt::TextSingleLine, m_text); + return style()->sizeFromContents(QStyle::CT_MenuItem, &opt, contentSize, this); +} + +void TriStateWidget::paintEvent(QPaintEvent *) +{ + QStyleOptionMenuItem opt; + opt.initFrom(this); + opt.menuItemType = QStyleOptionMenuItem::Normal; + opt.checkType = QStyleOptionMenuItem::NonExclusive; + opt.menuHasCheckableItems = true; + opt.text = m_text; + + switch (m_checkState) { + case Qt::PartiallyChecked: + opt.state |= QStyle::State_NoChange; + break; + case Qt::Checked: + opt.checked = true; + break; + case Qt::Unchecked: + opt.checked = false; + break; + }; + + if ((opt.state & QStyle::State_HasFocus) + || rect().contains(mapFromGlobal(QCursor::pos()))) { + opt.state |= QStyle::State_Selected; + + if (QApplication::mouseButtons() != Qt::NoButton) + opt.state |= QStyle::State_Sunken; + } + + QPainter painter {this}; + style()->drawControl(QStyle::CE_MenuItem, &opt, &painter, this); +} + +void TriStateWidget::mouseReleaseEvent(QMouseEvent *event) +{ + toggleCheckState(); + + if (m_closeOnTriggered) { + // parent `triggered` signal will be emitted + QWidget::mouseReleaseEvent(event); + } + else { + update(); + // need to emit parent `triggered` signal manually + emit triggered(m_checkState == Qt::Checked); + } +} + +void TriStateWidget::keyPressEvent(QKeyEvent *event) +{ + if ((event->key() == Qt::Key_Return) + || (event->key() == Qt::Key_Enter)) { + toggleCheckState(); + + if (!m_closeOnTriggered) { + update(); + // need to emit parent `triggered` signal manually + emit triggered(m_checkState == Qt::Checked); + return; + } + } + + QWidget::keyPressEvent(event); +} + +void TriStateWidget::toggleCheckState() +{ + switch (m_checkState) { + case Qt::Unchecked: + case Qt::PartiallyChecked: + m_checkState = Qt::Checked; + break; + case Qt::Checked: + m_checkState = Qt::Unchecked; + break; + }; +} diff --git a/src/gui/private/tristatewidget.h b/src/gui/private/tristatewidget.h new file mode 100644 index 000000000..7c7091444 --- /dev/null +++ b/src/gui/private/tristatewidget.h @@ -0,0 +1,61 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2019 Mike Tzou (Chocobo1) + * + * 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 + +class QString; + +class TriStateWidget : public QWidget +{ + Q_OBJECT + Q_DISABLE_COPY(TriStateWidget) + +public: + TriStateWidget(const QString &text, QWidget *parent); + + void setCheckState(Qt::CheckState checkState); + void setCloseOnTriggered(bool enabled); + +signals: + void triggered(bool checked) const; + +private: + QSize minimumSizeHint() const override; + + void paintEvent(QPaintEvent *) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + + void toggleCheckState(); + + bool m_closeOnTriggered; + Qt::CheckState m_checkState; + const QString m_text; +}; diff --git a/src/gui/transferlistwidget.cpp b/src/gui/transferlistwidget.cpp index 4f3fe511d..fa7ff2f64 100644 --- a/src/gui/transferlistwidget.cpp +++ b/src/gui/transferlistwidget.cpp @@ -40,10 +40,8 @@ #include #include #include -#include #include #include -#include #include "base/bittorrent/session.h" #include "base/bittorrent/torrenthandle.h" @@ -67,6 +65,7 @@ #include "transferlistdelegate.h" #include "transferlistmodel.h" #include "transferlistsortmodel.h" +#include "tristateaction.h" #include "updownratiodialog.h" #include "utils.h" @@ -76,8 +75,6 @@ namespace { - using ToggleFn = std::function; - QStringList extractHashes(const QVector &torrents) { QStringList hashes; @@ -87,126 +84,6 @@ namespace return hashes; } - // Helper for setting style parameters when painting check box primitives. - class CheckBoxIconHelper : public QCheckBox - { - public: - explicit CheckBoxIconHelper(QWidget *parent); - QSize sizeHint() const override; - void initStyleOption(QStyleOptionButton *opt) const; - - protected: - void paintEvent(QPaintEvent *) override {} - }; - - CheckBoxIconHelper::CheckBoxIconHelper(QWidget *parent) - : QCheckBox(parent) - { - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - } - - QSize CheckBoxIconHelper::sizeHint() const - { - const int dim = QCheckBox::sizeHint().height(); - return {dim, dim}; - } - - void CheckBoxIconHelper::initStyleOption(QStyleOptionButton *opt) const - { - QCheckBox::initStyleOption(opt); - } - - // Tristate checkbox styled for use in menus. - class MenuCheckBox : public QWidget - { - public: - MenuCheckBox(const QString &text, const ToggleFn &onToggle, Qt::CheckState initialState); - QSize sizeHint() const override; - - protected: - void paintEvent(QPaintEvent *e) override; - void mousePressEvent(QMouseEvent *) override; - - private: - CheckBoxIconHelper *const m_checkBox; - const QString m_text; - QSize m_sizeHint; - QSize m_checkBoxOffset; - }; - - MenuCheckBox::MenuCheckBox(const QString &text, const ToggleFn &onToggle, Qt::CheckState initialState) - : m_checkBox(new CheckBoxIconHelper(this)) - , m_text(text) - , m_sizeHint(QCheckBox(m_text).sizeHint()) - { - m_checkBox->setCheckState(initialState); - connect(m_checkBox, &QCheckBox::stateChanged, this, [this, onToggle](int newState) - { - m_checkBox->setTristate(false); - onToggle(static_cast(newState)); - }); - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - setMouseTracking(true); - - // We attempt to mimic the amount of vertical whitespace padding around a QCheckBox. - QSize layoutPadding(3, 0); - const int sizeHintMargin = (m_sizeHint.height() - m_checkBox->sizeHint().height()) / 2; - if (sizeHintMargin > 0) { - m_checkBoxOffset.setHeight(sizeHintMargin); - } - else { - layoutPadding.setHeight(1); - m_checkBoxOffset.setHeight(1); - } - m_checkBoxOffset.setWidth(layoutPadding.width()); - - auto *layout = new QHBoxLayout(this); - layout->addWidget(m_checkBox); - layout->addStretch(); - layout->setContentsMargins(layoutPadding.width(), layoutPadding.height(), layoutPadding.width(), layoutPadding.height()); - setLayout(layout); - } - - QSize MenuCheckBox::sizeHint() const - { - return m_sizeHint; - } - - void MenuCheckBox::paintEvent(QPaintEvent *e) - { - if (!rect().intersects(e->rect())) - return; - QStylePainter painter(this); - QStyleOptionMenuItem menuOpt; - menuOpt.initFrom(this); - menuOpt.menuItemType = QStyleOptionMenuItem::Normal; - menuOpt.text = m_text; - QStyleOptionButton checkBoxOpt; - m_checkBox->initStyleOption(&checkBoxOpt); - checkBoxOpt.rect.translate(m_checkBoxOffset.width(), m_checkBoxOffset.height()); - if (rect().contains(mapFromGlobal(QCursor::pos()))) { - menuOpt.state |= QStyle::State_Selected; - checkBoxOpt.state |= QStyle::State_MouseOver; - } - painter.drawControl(QStyle::CE_MenuItem, menuOpt); - painter.drawPrimitive(QStyle::PE_IndicatorCheckBox, checkBoxOpt); - } - - void MenuCheckBox::mousePressEvent(QMouseEvent *) - { - m_checkBox->click(); - } - - class CheckBoxMenuItem : public QWidgetAction - { - public: - CheckBoxMenuItem(const QString &text, const ToggleFn &onToggle, Qt::CheckState initialState, QObject *parent) - : QWidgetAction(parent) - { - setDefaultWidget(new MenuCheckBox(text, onToggle, initialState)); - } - }; - bool torrentContainsPreviewableFiles(const BitTorrent::TorrentHandle *const torrent) { if (!torrent->hasMetadata()) @@ -772,27 +649,27 @@ void TransferListWidget::displayDLHoSMenu(const QPoint&) menu->popup(QCursor::pos()); } -void TransferListWidget::toggleSelectedTorrentsSuperSeeding() const +void TransferListWidget::setSelectedTorrentsSuperSeeding(const bool enabled) const { for (BitTorrent::TorrentHandle *const torrent : asConst(getSelectedTorrents())) { if (torrent->hasMetadata()) - torrent->setSuperSeeding(!torrent->superSeeding()); + torrent->setSuperSeeding(enabled); } } -void TransferListWidget::toggleSelectedTorrentsSequentialDownload() const +void TransferListWidget::setSelectedTorrentsSequentialDownload(const bool enabled) const { for (BitTorrent::TorrentHandle *const torrent : asConst(getSelectedTorrents())) - torrent->toggleSequentialDownload(); + torrent->setSequentialDownload(enabled); } -void TransferListWidget::toggleSelectedFirstLastPiecePrio() const +void TransferListWidget::setSelectedFirstLastPiecePrio(const bool enabled) const { for (BitTorrent::TorrentHandle *const torrent : asConst(getSelectedTorrents())) - torrent->toggleFirstLastPiecePriority(); + torrent->setFirstLastPiecePriority(enabled); } -void TransferListWidget::setSelectedAutoTMMEnabled(bool enabled) const +void TransferListWidget::setSelectedAutoTMMEnabled(const bool enabled) const { for (BitTorrent::TorrentHandle *const torrent : asConst(getSelectedTorrents())) torrent->setAutoTMMEnabled(enabled); @@ -932,61 +809,57 @@ void TransferListWidget::displayListMenu(const QPoint &) const QModelIndexList selectedIndexes = selectionModel()->selectedRows(); if (selectedIndexes.isEmpty()) return; - QMenu *listMenu = new QMenu(this); + auto *listMenu = new QMenu(this); listMenu->setAttribute(Qt::WA_DeleteOnClose); // Create actions - QAction *actionStart = new QAction(GuiIconProvider::instance()->getIcon("media-playback-start"), tr("Resume", "Resume/start the torrent"), listMenu); + auto *actionStart = new QAction(GuiIconProvider::instance()->getIcon("media-playback-start"), tr("Resume", "Resume/start the torrent"), listMenu); connect(actionStart, &QAction::triggered, this, &TransferListWidget::startSelectedTorrents); - QAction *actionPause = new QAction(GuiIconProvider::instance()->getIcon("media-playback-pause"), tr("Pause", "Pause the torrent"), listMenu); + auto *actionPause = new QAction(GuiIconProvider::instance()->getIcon("media-playback-pause"), tr("Pause", "Pause the torrent"), listMenu); connect(actionPause, &QAction::triggered, this, &TransferListWidget::pauseSelectedTorrents); - QAction *actionForceStart = new QAction(GuiIconProvider::instance()->getIcon("media-seek-forward"), tr("Force Resume", "Force Resume/start the torrent"), listMenu); + auto *actionForceStart = new QAction(GuiIconProvider::instance()->getIcon("media-seek-forward"), tr("Force Resume", "Force Resume/start the torrent"), listMenu); connect(actionForceStart, &QAction::triggered, this, &TransferListWidget::forceStartSelectedTorrents); - QAction *actionDelete = new QAction(GuiIconProvider::instance()->getIcon("edit-delete"), tr("Delete", "Delete the torrent"), listMenu); + auto *actionDelete = new QAction(GuiIconProvider::instance()->getIcon("edit-delete"), tr("Delete", "Delete the torrent"), listMenu); connect(actionDelete, &QAction::triggered, this, &TransferListWidget::softDeleteSelectedTorrents); - QAction *actionPreviewFile = new QAction(GuiIconProvider::instance()->getIcon("view-preview"), tr("Preview file..."), listMenu); + auto *actionPreviewFile = new QAction(GuiIconProvider::instance()->getIcon("view-preview"), tr("Preview file..."), listMenu); connect(actionPreviewFile, &QAction::triggered, this, &TransferListWidget::previewSelectedTorrents); - QAction *actionSetMaxRatio = new QAction(QIcon(QLatin1String(":/icons/skin/ratio.svg")), tr("Limit share ratio..."), listMenu); + auto *actionSetMaxRatio = new QAction(QIcon(QLatin1String(":/icons/skin/ratio.svg")), tr("Limit share ratio..."), listMenu); connect(actionSetMaxRatio, &QAction::triggered, this, &TransferListWidget::setMaxRatioSelectedTorrents); - QAction *actionSetUploadLimit = new QAction(GuiIconProvider::instance()->getIcon("kt-set-max-upload-speed"), tr("Limit upload rate..."), listMenu); + auto *actionSetUploadLimit = new QAction(GuiIconProvider::instance()->getIcon("kt-set-max-upload-speed"), tr("Limit upload rate..."), listMenu); connect(actionSetUploadLimit, &QAction::triggered, this, &TransferListWidget::setUpLimitSelectedTorrents); - QAction *actionSetDownloadLimit = new QAction(GuiIconProvider::instance()->getIcon("kt-set-max-download-speed"), tr("Limit download rate..."), listMenu); + auto *actionSetDownloadLimit = new QAction(GuiIconProvider::instance()->getIcon("kt-set-max-download-speed"), tr("Limit download rate..."), listMenu); connect(actionSetDownloadLimit, &QAction::triggered, this, &TransferListWidget::setDlLimitSelectedTorrents); - QAction *actionOpenDestinationFolder = new QAction(GuiIconProvider::instance()->getIcon("inode-directory"), tr("Open destination folder"), listMenu); + auto *actionOpenDestinationFolder = new QAction(GuiIconProvider::instance()->getIcon("inode-directory"), tr("Open destination folder"), listMenu); connect(actionOpenDestinationFolder, &QAction::triggered, this, &TransferListWidget::openSelectedTorrentsFolder); - QAction *actionIncreasePriority = new QAction(GuiIconProvider::instance()->getIcon("go-up"), tr("Move up", "i.e. move up in the queue"), listMenu); + auto *actionIncreasePriority = new QAction(GuiIconProvider::instance()->getIcon("go-up"), tr("Move up", "i.e. move up in the queue"), listMenu); connect(actionIncreasePriority, &QAction::triggered, this, &TransferListWidget::increasePrioSelectedTorrents); - QAction *actionDecreasePriority = new QAction(GuiIconProvider::instance()->getIcon("go-down"), tr("Move down", "i.e. Move down in the queue"), listMenu); + auto *actionDecreasePriority = new QAction(GuiIconProvider::instance()->getIcon("go-down"), tr("Move down", "i.e. Move down in the queue"), listMenu); connect(actionDecreasePriority, &QAction::triggered, this, &TransferListWidget::decreasePrioSelectedTorrents); - QAction *actionTopPriority = new QAction(GuiIconProvider::instance()->getIcon("go-top"), tr("Move to top", "i.e. Move to top of the queue"), listMenu); + auto *actionTopPriority = new QAction(GuiIconProvider::instance()->getIcon("go-top"), tr("Move to top", "i.e. Move to top of the queue"), listMenu); connect(actionTopPriority, &QAction::triggered, this, &TransferListWidget::topPrioSelectedTorrents); - QAction *actionBottomPriority = new QAction(GuiIconProvider::instance()->getIcon("go-bottom"), tr("Move to bottom", "i.e. Move to bottom of the queue"), listMenu); + auto *actionBottomPriority = new QAction(GuiIconProvider::instance()->getIcon("go-bottom"), tr("Move to bottom", "i.e. Move to bottom of the queue"), listMenu); connect(actionBottomPriority, &QAction::triggered, this, &TransferListWidget::bottomPrioSelectedTorrents); - QAction *actionSetTorrentPath = new QAction(GuiIconProvider::instance()->getIcon("inode-directory"), tr("Set location..."), listMenu); + auto *actionSetTorrentPath = new QAction(GuiIconProvider::instance()->getIcon("inode-directory"), tr("Set location..."), listMenu); connect(actionSetTorrentPath, &QAction::triggered, this, &TransferListWidget::setSelectedTorrentsLocation); - QAction *actionForceRecheck = new QAction(GuiIconProvider::instance()->getIcon("document-edit-verify"), tr("Force recheck"), listMenu); + auto *actionForceRecheck = new QAction(GuiIconProvider::instance()->getIcon("document-edit-verify"), tr("Force recheck"), listMenu); connect(actionForceRecheck, &QAction::triggered, this, &TransferListWidget::recheckSelectedTorrents); - QAction *actionForceReannounce = new QAction(GuiIconProvider::instance()->getIcon("document-edit-verify"), tr("Force reannounce"), listMenu); + auto *actionForceReannounce = new QAction(GuiIconProvider::instance()->getIcon("document-edit-verify"), tr("Force reannounce"), listMenu); connect(actionForceReannounce, &QAction::triggered, this, &TransferListWidget::reannounceSelectedTorrents); - QAction *actionCopyMagnetLink = new QAction(GuiIconProvider::instance()->getIcon("kt-magnet"), tr("Magnet link"), listMenu); + auto *actionCopyMagnetLink = new QAction(GuiIconProvider::instance()->getIcon("kt-magnet"), tr("Magnet link"), listMenu); connect(actionCopyMagnetLink, &QAction::triggered, this, &TransferListWidget::copySelectedMagnetURIs); - QAction *actionCopyName = new QAction(GuiIconProvider::instance()->getIcon("edit-copy"), tr("Name"), listMenu); + auto *actionCopyName = new QAction(GuiIconProvider::instance()->getIcon("edit-copy"), tr("Name"), listMenu); connect(actionCopyName, &QAction::triggered, this, &TransferListWidget::copySelectedNames); - QAction *actionCopyHash = new QAction(GuiIconProvider::instance()->getIcon("edit-copy"), tr("Hash"), listMenu); + auto *actionCopyHash = new QAction(GuiIconProvider::instance()->getIcon("edit-copy"), tr("Hash"), listMenu); connect(actionCopyHash, &QAction::triggered, this, &TransferListWidget::copySelectedHashes); - QAction *actionSuperSeedingMode = new QAction(tr("Super seeding mode"), listMenu); - actionSuperSeedingMode->setCheckable(true); - connect(actionSuperSeedingMode, &QAction::triggered, this, &TransferListWidget::toggleSelectedTorrentsSuperSeeding); - QAction *actionRename = new QAction(GuiIconProvider::instance()->getIcon("edit-rename"), tr("Rename..."), listMenu); + auto *actionSuperSeedingMode = new TriStateAction(tr("Super seeding mode"), listMenu); + connect(actionSuperSeedingMode, &QAction::triggered, this, &TransferListWidget::setSelectedTorrentsSuperSeeding); + auto *actionRename = new QAction(GuiIconProvider::instance()->getIcon("edit-rename"), tr("Rename..."), listMenu); connect(actionRename, &QAction::triggered, this, &TransferListWidget::renameSelectedTorrent); - QAction *actionSequentialDownload = new QAction(tr("Download in sequential order"), listMenu); - actionSequentialDownload->setCheckable(true); - connect(actionSequentialDownload, &QAction::triggered, this, &TransferListWidget::toggleSelectedTorrentsSequentialDownload); - QAction *actionFirstLastPiecePrio = new QAction(tr("Download first and last pieces first"), listMenu); - actionFirstLastPiecePrio->setCheckable(true); - connect(actionFirstLastPiecePrio, &QAction::triggered, this, &TransferListWidget::toggleSelectedFirstLastPiecePrio); - QAction *actionAutoTMM = new QAction(tr("Automatic Torrent Management"), listMenu); - actionAutoTMM->setCheckable(true); + auto *actionSequentialDownload = new TriStateAction(tr("Download in sequential order"), listMenu); + connect(actionSequentialDownload, &QAction::triggered, this, &TransferListWidget::setSelectedTorrentsSequentialDownload); + auto *actionFirstLastPiecePrio = new TriStateAction(tr("Download first and last pieces first"), listMenu); + connect(actionFirstLastPiecePrio, &QAction::triggered, this, &TransferListWidget::setSelectedFirstLastPiecePrio); + auto *actionAutoTMM = new TriStateAction(tr("Automatic Torrent Management"), listMenu); actionAutoTMM->setToolTip(tr("Automatic mode means that various torrent properties(eg save path) will be decided by the associated category")); connect(actionAutoTMM, &QAction::triggered, this, &TransferListWidget::setSelectedAutoTMMEnabled); QAction *actionEditTracker = new QAction(GuiIconProvider::instance()->getIcon("edit-rename"), tr("Edit trackers..."), listMenu); @@ -1028,6 +901,7 @@ void TransferListWidget::displayListMenu(const QPoint &) else { tagsInAll.intersect(torrent->tags()); } + if (firstAutoTMM != torrent->isAutoTMMEnabled()) allSameAutoTMM = false; @@ -1111,8 +985,8 @@ void TransferListWidget::displayListMenu(const QPoint &) cat->setChecked(true); } - categoryMenu->addAction(cat); connect(cat, &QAction::triggered, this, [this, category]() { setSelectionCategory(category); }); + categoryMenu->addAction(cat); } // Tag Menu @@ -1136,34 +1010,39 @@ void TransferListWidget::displayListMenu(const QPoint &) tagsMenu->addSeparator(); for (const QString &tag : asConst(tags)) { + auto *action = new TriStateAction(tag, tagsMenu); + action->setCloseOnTriggered(false); + const Qt::CheckState initialState = tagsInAll.contains(tag) ? Qt::Checked : tagsInAny.contains(tag) ? Qt::PartiallyChecked : Qt::Unchecked; + action->setCheckState(initialState); - const ToggleFn onToggle = [this, tag](Qt::CheckState newState) + connect(action, &QAction::triggered, this, [this, tag](const bool checked) { - Q_ASSERT(newState == Qt::CheckState::Checked || newState == Qt::CheckState::Unchecked); - if (newState == Qt::CheckState::Checked) + if (checked) addSelectionTag(tag); else removeSelectionTag(tag); - }; + }); - tagsMenu->addAction(new CheckBoxMenuItem(tag, onToggle, initialState, tagsMenu)); + tagsMenu->addAction(action); } - if (allSameAutoTMM) { - actionAutoTMM->setChecked(firstAutoTMM); - listMenu->addAction(actionAutoTMM); - } + actionAutoTMM->setCheckState(allSameAutoTMM + ? (firstAutoTMM ? Qt::Checked : Qt::Unchecked) + : Qt::PartiallyChecked); + listMenu->addAction(actionAutoTMM); listMenu->addSeparator(); if (oneNotSeed) listMenu->addAction(actionSetDownloadLimit); listMenu->addAction(actionSetUploadLimit); listMenu->addAction(actionSetMaxRatio); - if (!oneNotSeed && allSameSuperSeeding && oneHasMetadata) { - actionSuperSeedingMode->setChecked(superSeedingMode); + if (!oneNotSeed && oneHasMetadata) { + actionSuperSeedingMode->setCheckState(allSameSuperSeeding + ? (superSeedingMode ? Qt::Checked : Qt::Unchecked) + : Qt::PartiallyChecked); listMenu->addAction(actionSuperSeedingMode); } listMenu->addSeparator(); @@ -1173,16 +1052,17 @@ void TransferListWidget::displayListMenu(const QPoint &) addedPreviewAction = true; } if (oneNotSeed) { - if (allSameSequentialDownloadMode) { - actionSequentialDownload->setChecked(sequentialDownloadMode); - listMenu->addAction(actionSequentialDownload); - addedPreviewAction = true; - } - if (allSamePrioFirstlast) { - actionFirstLastPiecePrio->setChecked(prioritizeFirstLast); - listMenu->addAction(actionFirstLastPiecePrio); - addedPreviewAction = true; - } + actionSequentialDownload->setCheckState(allSameSequentialDownloadMode + ? (sequentialDownloadMode ? Qt::Checked : Qt::Unchecked) + : Qt::PartiallyChecked); + listMenu->addAction(actionSequentialDownload); + + actionFirstLastPiecePrio->setCheckState(allSamePrioFirstlast + ? (prioritizeFirstLast ? Qt::Checked : Qt::Unchecked) + : Qt::PartiallyChecked); + listMenu->addAction(actionFirstLastPiecePrio); + + addedPreviewAction = true; } if (addedPreviewAction) diff --git a/src/gui/transferlistwidget.h b/src/gui/transferlistwidget.h index 41d3421e5..a0d7e8e13 100644 --- a/src/gui/transferlistwidget.h +++ b/src/gui/transferlistwidget.h @@ -106,9 +106,9 @@ protected slots: void torrentDoubleClicked(); void displayListMenu(const QPoint &); void currentChanged(const QModelIndex ¤t, const QModelIndex&) override; - void toggleSelectedTorrentsSuperSeeding() const; - void toggleSelectedTorrentsSequentialDownload() const; - void toggleSelectedFirstLastPiecePrio() const; + void setSelectedTorrentsSuperSeeding(bool enabled) const; + void setSelectedTorrentsSequentialDownload(bool enabled) const; + void setSelectedFirstLastPiecePrio(bool enabled) const; void setSelectedAutoTMMEnabled(bool enabled) const; void askNewCategoryForSelection(); void saveSettings(); diff --git a/src/gui/tristateaction.cpp b/src/gui/tristateaction.cpp new file mode 100644 index 000000000..8371d5755 --- /dev/null +++ b/src/gui/tristateaction.cpp @@ -0,0 +1,62 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2019 Mike Tzou (Chocobo1) + * + * 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 "tristateaction.h" + +#include +#include + +#include "private/tristatewidget.h" + +TriStateAction::TriStateAction(const QString &text, QWidget *parent) + : QWidgetAction {parent} + , m_triStateWidget {new TriStateWidget {text, parent}} +{ + setCheckable(true); + + // required for QAction::setChecked(bool) to work + connect(this, &QAction::toggled, this, [this](const bool checked) + { + m_triStateWidget->setCheckState(checked ? Qt::Checked : Qt::Unchecked); + }); + + connect(m_triStateWidget, &TriStateWidget::triggered, this, &QAction::setChecked); + connect(m_triStateWidget, &TriStateWidget::triggered, this, &QAction::triggered); + setDefaultWidget(m_triStateWidget); +} + +void TriStateAction::setCheckState(const Qt::CheckState checkState) +{ + QWidgetAction::setChecked((checkState == Qt::Checked) ? true : false); + m_triStateWidget->setCheckState(checkState); +} + +void TriStateAction::setCloseOnTriggered(const bool enabled) +{ + m_triStateWidget->setCloseOnTriggered(enabled); +} diff --git a/src/gui/tristateaction.h b/src/gui/tristateaction.h new file mode 100644 index 000000000..fe014cdbc --- /dev/null +++ b/src/gui/tristateaction.h @@ -0,0 +1,53 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2019 Mike Tzou (Chocobo1) + * + * 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 + +class QString; +class QWidget; + +class TriStateWidget; + +// TriStateWidget is responsible for checkbox state (tri-state) and paint events while +// TriStateAction will keep in sync with it. This allows connecting with the usual +// QAction::triggered slot. +class TriStateAction : public QWidgetAction +{ +public: + TriStateAction(const QString &text, QWidget *parent); + + // should use this function instead of QAction::setChecked(bool) + void setCheckState(Qt::CheckState checkState); + + void setCloseOnTriggered(bool enabled); + +private: + TriStateWidget *m_triStateWidget; +};