2023-11-01 21:11:11 +00:00
|
|
|
// This file Copyright © Mnemosyne LLC.
|
2022-02-07 16:25:02 +00:00
|
|
|
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
2022-01-20 18:27:56 +00:00
|
|
|
// or any future license endorsed by Mnemosyne LLC.
|
|
|
|
// License text can be found in the licenses/ folder.
|
2009-04-09 18:55:47 +00:00
|
|
|
|
2020-06-18 20:34:11 +00:00
|
|
|
#include <algorithm>
|
2009-04-09 18:55:47 +00:00
|
|
|
#include <cassert>
|
|
|
|
#include <ctime>
|
2023-03-02 06:33:49 +00:00
|
|
|
#include <map>
|
|
|
|
#include <set>
|
2022-08-17 16:08:36 +00:00
|
|
|
#include <utility>
|
2009-04-09 18:55:47 +00:00
|
|
|
|
2009-10-06 00:27:26 +00:00
|
|
|
#include <QDateTime>
|
2013-09-08 19:03:25 +00:00
|
|
|
#include <QDesktopServices>
|
2009-10-06 00:27:26 +00:00
|
|
|
#include <QEvent>
|
2009-04-09 18:55:47 +00:00
|
|
|
#include <QFont>
|
|
|
|
#include <QFontMetrics>
|
2009-10-06 00:27:26 +00:00
|
|
|
#include <QHeaderView>
|
2014-12-14 15:34:31 +00:00
|
|
|
#include <QHostAddress>
|
2010-06-30 05:55:46 +00:00
|
|
|
#include <QInputDialog>
|
2010-07-27 19:43:32 +00:00
|
|
|
#include <QItemSelectionModel>
|
2009-04-09 18:55:47 +00:00
|
|
|
#include <QLabel>
|
2010-07-27 19:43:32 +00:00
|
|
|
#include <QList>
|
2010-06-30 05:55:46 +00:00
|
|
|
#include <QMessageBox>
|
2009-10-06 00:27:26 +00:00
|
|
|
#include <QResizeEvent>
|
Qt 6 support (#2069)
* Bump minimum Qt version to 5.6
* Switch from QRegExp to QRegularExpression
While still available, QRegExp has been moved to Qt6::Core5Compat module
and is not part of Qt6::Core.
* Use qIsEffectiveTLD instead of QUrl::topLevelDomain
The latter is not part of Qt6::Core. The former is a private utility in
Qt6::Network; using it for now, until (and if) we switch to something
non-Qt-specific.
* Use QStyle::State_Horizontal state when drawing progress bars
Although available for a long time, this state either didn't apply to
progress bars before Qt 6, or was deduced based on bar size. With Qt 6,
failing to specify it results in bad rendering.
* Don't use QStringRef (and associated methods)
While still available, QStringRef has been moved to Qt6::Core5Compat
module and is not part of Qt6::Core. Related method (e.g.
QString::midRef) have been removed in Qt 6.
* Use Qt::ItemIsAutoTristate instead of Qt::ItemIsTristate
The latter was deprecated and replaced with the former in Qt 5.6.
* Don't use QApplication::globalStrut
This property has been deprecated in Qt 5.15 and removed in Qt 6.
* Use QImage::fromHICON instead of QtWin::fromHICON
WinExtras module (providind the latter helper) has been removed in Qt 6.
* Use QStringDecoder instead of QTextCodec
While still available, QTextCodec has been moved to Qt6::Core5Compat
module and is not part of Qt6::Core.
* Don't forward-declare QStringList
Instead of being a standalone class, its definition has changed to
QList<QString> template specialization in Qt 6.
* Use explicit (since Qt 6) QFileInfo constructor
* Use QDateTime's {to,from}SecsSinceEpoch instead of {to,from}Time_t
The latter was deprecated in Qt 5.8 and removed in Qt 6.
* Don't use QFuture<>'s operator==
It has been removed in Qt 6. Since the original issue this code was
solving was caused by future reuse, just don't reuse futures and create
new finished ones when necessary.
* Use std::vector<> instead of QVector<>
The latter has been changed to a typedef for QList<>, which might not be
what one wants, and which also changed behavior a bit leading to
compilation errors.
* Don't use + for flags, cast to int explicitly
Operator+ for enum values has been deleted in Qt 6, so using operator|
instead. Then, there's no conversion from QFlags<> to QVariant, so need
to cast to int.
* Support Qt 6 in CMake and for MSI packaging
* Remove extra (empty) CMake variable use when constructing Qt target names
* Simplify logic in tr_qt_add_translation CMake helper
Co-authored-by: Charles Kerr <charles@charleskerr.com>
2021-11-03 21:20:11 +00:00
|
|
|
#include <QRegularExpression>
|
2010-07-27 19:43:32 +00:00
|
|
|
#include <QStringList>
|
2023-12-25 22:09:20 +00:00
|
|
|
#include <QString>
|
2009-04-09 18:55:47 +00:00
|
|
|
#include <QStyle>
|
|
|
|
#include <QTreeWidgetItem>
|
|
|
|
|
2009-04-13 18:21:22 +00:00
|
|
|
#include <libtransmission/transmission.h>
|
2023-03-02 06:33:49 +00:00
|
|
|
#include <libtransmission/announce-list.h>
|
2017-04-21 07:40:57 +00:00
|
|
|
#include <libtransmission/utils.h> // tr_getRatio()
|
2009-04-13 18:21:22 +00:00
|
|
|
|
2022-02-18 17:52:01 +00:00
|
|
|
#include "BaseDialog.h"
|
2015-06-10 21:27:11 +00:00
|
|
|
#include "ColumnResizer.h"
|
|
|
|
#include "DetailsDialog.h"
|
|
|
|
#include "Formatter.h"
|
2021-12-09 08:13:04 +00:00
|
|
|
#include "IconCache.h"
|
2015-06-10 21:27:11 +00:00
|
|
|
#include "Prefs.h"
|
|
|
|
#include "Session.h"
|
|
|
|
#include "SqueezeLabel.h"
|
|
|
|
#include "Torrent.h"
|
|
|
|
#include "TorrentModel.h"
|
|
|
|
#include "TrackerDelegate.h"
|
|
|
|
#include "TrackerModel.h"
|
|
|
|
#include "TrackerModelFilter.h"
|
|
|
|
#include "Utils.h"
|
2009-04-09 18:55:47 +00:00
|
|
|
|
2022-02-18 17:52:01 +00:00
|
|
|
#include "ui_TrackersDialog.h"
|
|
|
|
|
2009-04-09 18:55:47 +00:00
|
|
|
class Prefs;
|
|
|
|
class Session;
|
|
|
|
|
2024-01-05 05:12:51 +00:00
|
|
|
// ---
|
2009-04-09 18:55:47 +00:00
|
|
|
|
|
|
|
namespace
|
|
|
|
{
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2022-02-18 17:52:01 +00:00
|
|
|
class TrackersDialog : public BaseDialog
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit TrackersDialog(QString tracker_list, QWidget* parent = nullptr)
|
|
|
|
: BaseDialog{ parent }
|
|
|
|
{
|
|
|
|
ui_.setupUi(this);
|
|
|
|
ui_.trackerList->setPlainText(tracker_list);
|
|
|
|
connect(ui_.dialogButtons, &QDialogButtonBox::clicked, this, &TrackersDialog::onButtonBoxClicked);
|
|
|
|
}
|
|
|
|
|
|
|
|
signals:
|
2022-09-08 23:26:18 +00:00
|
|
|
void trackerListEdited(QString /*tracker_list*/);
|
2022-02-18 17:52:01 +00:00
|
|
|
|
|
|
|
private slots:
|
|
|
|
void onButtonBoxClicked(QAbstractButton* button)
|
|
|
|
{
|
|
|
|
if (ui_.dialogButtons->standardButton(button) == QDialogButtonBox::Ok)
|
|
|
|
{
|
|
|
|
emit trackerListEdited(ui_.trackerList->toPlainText());
|
|
|
|
}
|
|
|
|
|
|
|
|
close();
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
Ui::TrackersDialog ui_{};
|
|
|
|
QTimer timer_;
|
|
|
|
};
|
|
|
|
|
2024-01-05 05:12:51 +00:00
|
|
|
constexpr tr_quark priorityKey(int priority)
|
2022-02-18 17:52:01 +00:00
|
|
|
{
|
2024-01-05 05:12:51 +00:00
|
|
|
switch (priority)
|
|
|
|
{
|
|
|
|
case TR_PRI_LOW:
|
|
|
|
return TR_KEY_priority_low;
|
|
|
|
|
|
|
|
case TR_PRI_HIGH:
|
|
|
|
return TR_KEY_priority_high;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return TR_KEY_priority_normal;
|
|
|
|
}
|
|
|
|
}
|
2022-02-18 17:52:01 +00:00
|
|
|
|
2020-07-29 16:56:23 +00:00
|
|
|
int constexpr DebounceIntervalMSec = 100;
|
2020-06-05 19:02:11 +00:00
|
|
|
int constexpr RefreshIntervalMSec = 4000;
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2023-10-01 23:45:52 +00:00
|
|
|
char constexpr const* const PrefKey = "pref_key";
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
enum // peer columns
|
|
|
|
{
|
2013-02-03 19:13:04 +00:00
|
|
|
COL_LOCK,
|
|
|
|
COL_UP,
|
|
|
|
COL_DOWN,
|
|
|
|
COL_PERCENT,
|
|
|
|
COL_STATUS,
|
|
|
|
COL_ADDRESS,
|
|
|
|
COL_CLIENT,
|
|
|
|
N_COLUMNS
|
2017-04-19 12:04:45 +00:00
|
|
|
};
|
2014-12-14 18:12:21 +00:00
|
|
|
|
2020-11-02 01:13:32 +00:00
|
|
|
int measureViewItem(QTreeWidget const* view, int column, QString const& text)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
QTreeWidgetItem const* header_item = view->headerItem();
|
2015-06-15 21:07:46 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
int const item_width = Utils::measureViewItem(view, text);
|
|
|
|
int const header_width = Utils::measureHeaderItem(view->header(), header_item->text(column));
|
2015-06-15 21:07:46 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
return std::max(item_width, header_width);
|
2009-04-09 18:55:47 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
} // namespace
|
|
|
|
|
2024-01-05 05:12:51 +00:00
|
|
|
// ---
|
2009-04-09 18:55:47 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
class PeerItem : public QTreeWidgetItem
|
2009-04-09 18:55:47 +00:00
|
|
|
{
|
2020-06-05 19:02:11 +00:00
|
|
|
Peer peer_;
|
|
|
|
QString mutable collated_address_;
|
|
|
|
QString status_;
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
public:
|
2021-08-15 09:41:48 +00:00
|
|
|
explicit PeerItem(Peer p)
|
2023-11-21 15:02:03 +00:00
|
|
|
: peer_{ std::move(p) }
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
void refresh(Peer const& p)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2020-06-05 19:02:11 +00:00
|
|
|
if (p.address != peer_.address)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-06-05 19:02:11 +00:00
|
|
|
collated_address_.clear();
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
|
2020-06-05 19:02:11 +00:00
|
|
|
peer_ = p;
|
2013-02-03 19:13:04 +00:00
|
|
|
}
|
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
void setStatus(QString const& s)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-06-05 19:02:11 +00:00
|
|
|
status_ = s;
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
bool operator<(QTreeWidgetItem const& other) const override
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2020-05-20 01:32:51 +00:00
|
|
|
auto const* i = dynamic_cast<PeerItem const*>(&other);
|
2020-11-02 01:13:32 +00:00
|
|
|
auto const* tw = treeWidget();
|
2017-04-30 16:25:26 +00:00
|
|
|
int const column = tw != nullptr ? tw->sortColumn() : 0;
|
2015-05-09 08:37:55 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
assert(i != nullptr);
|
2015-05-09 08:37:55 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
switch (column)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
case COL_UP:
|
2020-06-05 19:02:11 +00:00
|
|
|
return peer_.rate_to_peer < i->peer_.rate_to_peer;
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
case COL_DOWN:
|
2020-06-05 19:02:11 +00:00
|
|
|
return peer_.rate_to_client < i->peer_.rate_to_client;
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
case COL_PERCENT:
|
2020-06-05 19:02:11 +00:00
|
|
|
return peer_.progress < i->peer_.progress;
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
case COL_STATUS:
|
2020-06-05 19:02:11 +00:00
|
|
|
return status_ < i->status_;
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
case COL_CLIENT:
|
2020-06-05 19:02:11 +00:00
|
|
|
return peer_.client_name < i->peer_.client_name;
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
case COL_LOCK:
|
2020-06-05 19:02:11 +00:00
|
|
|
return peer_.is_encrypted && !i->peer_.is_encrypted;
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
default:
|
|
|
|
return address() < i->address();
|
2009-04-09 18:55:47 +00:00
|
|
|
}
|
2013-02-03 19:13:04 +00:00
|
|
|
}
|
2014-12-14 15:34:31 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
private:
|
2017-04-20 16:02:19 +00:00
|
|
|
QString const& address() const
|
2014-12-14 15:34:31 +00:00
|
|
|
{
|
2020-06-05 19:02:11 +00:00
|
|
|
if (collated_address_.isEmpty())
|
2014-12-14 15:34:31 +00:00
|
|
|
{
|
2020-11-02 01:13:32 +00:00
|
|
|
collated_address_ = collateAddress(peer_.address);
|
2014-12-14 15:34:31 +00:00
|
|
|
}
|
|
|
|
|
2020-06-05 19:02:11 +00:00
|
|
|
return collated_address_;
|
2014-12-14 15:34:31 +00:00
|
|
|
}
|
2024-01-05 05:12:51 +00:00
|
|
|
|
|
|
|
[[nodiscard]] static QString collateAddress(QString const& address)
|
|
|
|
{
|
|
|
|
auto collated = QString{};
|
|
|
|
|
|
|
|
if (auto ip_address = QHostAddress{}; ip_address.setAddress(address))
|
|
|
|
{
|
|
|
|
if (ip_address.protocol() == QAbstractSocket::IPv4Protocol)
|
|
|
|
{
|
|
|
|
quint32 const ipv4_address = ip_address.toIPv4Address();
|
|
|
|
collated = QStringLiteral("1-") +
|
|
|
|
QString::fromUtf8(QByteArray::number(ipv4_address, 16).rightJustified(8, '0'));
|
|
|
|
}
|
|
|
|
else if (ip_address.protocol() == QAbstractSocket::IPv6Protocol)
|
|
|
|
{
|
|
|
|
Q_IPV6ADDR const ipv6_address = ip_address.toIPv6Address();
|
|
|
|
QByteArray tmp(16, '\0');
|
|
|
|
|
|
|
|
for (int i = 0; i < 16; ++i)
|
|
|
|
{
|
|
|
|
tmp[i] = ipv6_address[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
collated = QStringLiteral("2-") + QString::fromUtf8(tmp.toHex());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (collated.isEmpty())
|
|
|
|
{
|
|
|
|
collated = QStringLiteral("3-") + address.toLower();
|
|
|
|
}
|
|
|
|
|
|
|
|
return collated;
|
|
|
|
}
|
2009-04-09 18:55:47 +00:00
|
|
|
};
|
|
|
|
|
2024-01-05 05:12:51 +00:00
|
|
|
// ---
|
2009-04-09 18:55:47 +00:00
|
|
|
|
2022-05-23 02:22:34 +00:00
|
|
|
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
2022-01-25 05:16:33 +00:00
|
|
|
int DetailsDialog::prev_tab_index_ = 0;
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
DetailsDialog::DetailsDialog(Session& session, Prefs& prefs, TorrentModel const& model, QWidget* parent)
|
2023-07-18 15:20:17 +00:00
|
|
|
: BaseDialog{ parent }
|
|
|
|
, session_{ session }
|
|
|
|
, prefs_{ prefs }
|
|
|
|
, model_{ model }
|
2009-04-09 18:55:47 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.setupUi(this);
|
2014-12-14 18:12:21 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
initInfoTab();
|
|
|
|
initPeersTab();
|
|
|
|
initTrackerTab();
|
|
|
|
initFilesTab();
|
|
|
|
initOptionsTab();
|
2014-12-14 18:12:21 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
adjustSize();
|
2024-01-10 21:01:03 +00:00
|
|
|
ui_.commentTextEdit->setMaximumHeight(QWIDGETSIZE_MAX);
|
2022-01-25 05:16:33 +00:00
|
|
|
ui_.tabs->setCurrentIndex(prev_tab_index_);
|
2014-12-14 18:12:21 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
static std::array<int, 2> constexpr InitKeys = {
|
2020-06-05 19:02:11 +00:00
|
|
|
Prefs::SHOW_TRACKER_SCRAPES,
|
2021-08-15 09:41:48 +00:00
|
|
|
Prefs::SHOW_BACKUP_TRACKERS,
|
2020-06-05 19:02:11 +00:00
|
|
|
};
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2020-06-05 19:02:11 +00:00
|
|
|
for (int const key : InitKeys)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
refreshPref(key);
|
|
|
|
}
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
connect(&model_, &TorrentModel::torrentsChanged, this, &DetailsDialog::onTorrentsChanged);
|
2020-06-18 20:34:11 +00:00
|
|
|
connect(&model_, &TorrentModel::torrentsEdited, this, &DetailsDialog::onTorrentsEdited);
|
2020-05-27 21:53:12 +00:00
|
|
|
connect(&prefs_, &Prefs::changed, this, &DetailsDialog::refreshPref);
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2020-07-29 16:56:23 +00:00
|
|
|
// call refreshModel periodically
|
|
|
|
connect(&model_timer_, &QTimer::timeout, this, &DetailsDialog::refreshModel);
|
|
|
|
model_timer_.setSingleShot(false);
|
|
|
|
model_timer_.start(RefreshIntervalMSec);
|
|
|
|
refreshModel();
|
|
|
|
|
|
|
|
// set up the debounce timer
|
|
|
|
connect(&ui_debounce_timer_, &QTimer::timeout, this, &DetailsDialog::refreshUI);
|
|
|
|
ui_debounce_timer_.setSingleShot(true);
|
2023-12-25 22:09:20 +00:00
|
|
|
|
|
|
|
// set labels
|
|
|
|
connect(ui_.dialogButtons, &QDialogButtonBox::clicked, this, &DetailsDialog::onButtonBoxClicked);
|
2009-04-09 18:55:47 +00:00
|
|
|
}
|
2010-01-05 23:47:50 +00:00
|
|
|
|
2022-01-25 05:16:33 +00:00
|
|
|
DetailsDialog::~DetailsDialog()
|
|
|
|
{
|
|
|
|
prev_tab_index_ = ui_.tabs->currentIndex();
|
|
|
|
}
|
|
|
|
|
2019-11-12 01:37:05 +00:00
|
|
|
void DetailsDialog::setIds(torrent_ids_t const& ids)
|
2009-04-18 23:18:28 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
if (ids != ids_)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2019-11-09 14:44:40 +00:00
|
|
|
setEnabled(false);
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.filesView->clear();
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ids_ = ids;
|
|
|
|
session_.refreshDetailInfo(ids_);
|
|
|
|
tracker_model_->refresh(model_, ids_);
|
2020-07-29 16:56:23 +00:00
|
|
|
|
2023-12-25 22:09:20 +00:00
|
|
|
labels_need_refresh_ = true;
|
|
|
|
|
2020-07-29 16:56:23 +00:00
|
|
|
refreshModel();
|
|
|
|
refreshUI();
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2009-04-18 23:18:28 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::refreshPref(int key)
|
2010-07-27 19:43:32 +00:00
|
|
|
{
|
2020-11-08 19:54:40 +00:00
|
|
|
if (key == Prefs::SHOW_TRACKER_SCRAPES)
|
|
|
|
{
|
|
|
|
auto* selection_model = ui_.trackersView->selectionModel();
|
|
|
|
tracker_delegate_->setShowMore(prefs_.getBool(key));
|
|
|
|
selection_model->clear();
|
|
|
|
ui_.trackersView->reset();
|
|
|
|
selection_model->select(selection_model->selection(), QItemSelectionModel::Select);
|
|
|
|
selection_model->setCurrentIndex(selection_model->currentIndex(), QItemSelectionModel::NoUpdate);
|
|
|
|
}
|
|
|
|
else if (key == Prefs::SHOW_BACKUP_TRACKERS)
|
2010-07-27 19:43:32 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
tracker_filter_->setShowBackupTrackers(prefs_.getBool(key));
|
2010-07-27 19:43:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-05 05:12:51 +00:00
|
|
|
// ---
|
2009-04-09 18:55:47 +00:00
|
|
|
|
2020-07-29 16:56:23 +00:00
|
|
|
void DetailsDialog::refreshModel()
|
2009-04-09 18:55:47 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
if (!ids_.empty())
|
2010-06-16 03:02:17 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
session_.refreshExtraStats(ids_);
|
2010-06-16 03:02:17 +00:00
|
|
|
}
|
2009-04-09 18:55:47 +00:00
|
|
|
}
|
|
|
|
|
2020-06-18 20:34:11 +00:00
|
|
|
void DetailsDialog::onTorrentsEdited(torrent_ids_t const& ids)
|
2019-11-12 23:13:42 +00:00
|
|
|
{
|
2020-06-18 20:34:11 +00:00
|
|
|
// std::set_intersection requires sorted inputs
|
2022-06-17 15:43:04 +00:00
|
|
|
auto a = std::vector<tr_torrent_id_t>{ ids.begin(), ids.end() };
|
2020-06-18 20:34:11 +00:00
|
|
|
std::sort(std::begin(a), std::end(a));
|
2022-06-17 15:43:04 +00:00
|
|
|
auto b = std::vector<tr_torrent_id_t>{ ids_.begin(), ids_.end() };
|
2020-06-18 20:34:11 +00:00
|
|
|
std::sort(std::begin(b), std::end(b));
|
|
|
|
|
|
|
|
// are any of the edited torrents on display here?
|
|
|
|
torrent_ids_t interesting_ids;
|
2021-08-15 09:41:48 +00:00
|
|
|
std::set_intersection(
|
|
|
|
std::begin(a),
|
|
|
|
std::end(a),
|
|
|
|
std::begin(b),
|
|
|
|
std::end(b),
|
2020-06-18 20:34:11 +00:00
|
|
|
std::inserter(interesting_ids, std::begin(interesting_ids)));
|
|
|
|
|
|
|
|
if (!interesting_ids.empty())
|
|
|
|
{
|
|
|
|
session_.refreshDetailInfo(interesting_ids);
|
|
|
|
}
|
2019-11-12 23:13:42 +00:00
|
|
|
}
|
|
|
|
|
2020-06-23 21:11:16 +00:00
|
|
|
void DetailsDialog::onTorrentsChanged(torrent_ids_t const& ids, Torrent::fields_t const& fields)
|
2009-04-09 18:55:47 +00:00
|
|
|
{
|
2020-06-23 21:11:16 +00:00
|
|
|
Q_UNUSED(fields)
|
|
|
|
|
2020-07-29 16:56:23 +00:00
|
|
|
if (ui_debounce_timer_.isActive())
|
2019-11-12 01:37:05 +00:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2019-11-09 14:44:40 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
if (!std::any_of(ids.begin(), ids.end(), [this](auto const& id) { return ids_.count(id) != 0; }))
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2019-11-12 01:37:05 +00:00
|
|
|
return;
|
2009-04-18 23:18:28 +00:00
|
|
|
}
|
2019-11-12 01:37:05 +00:00
|
|
|
|
2020-07-29 16:56:23 +00:00
|
|
|
ui_debounce_timer_.start(DebounceIntervalMSec);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DetailsDialog::onSessionCalled(Session::Tag tag)
|
|
|
|
{
|
|
|
|
if ((pending_changes_tags_.erase(tag) > 0) && canEdit())
|
|
|
|
{
|
|
|
|
// no pending changes left, so stop listening
|
|
|
|
disconnect(pending_changes_connection_);
|
|
|
|
pending_changes_connection_ = {};
|
|
|
|
|
|
|
|
refreshModel();
|
|
|
|
}
|
2009-04-18 23:18:28 +00:00
|
|
|
}
|
|
|
|
|
2023-12-25 22:09:20 +00:00
|
|
|
void DetailsDialog::onButtonBoxClicked(QAbstractButton* button)
|
|
|
|
{
|
|
|
|
if (ui_.dialogButtons->standardButton(button) == QDialogButtonBox::Close)
|
|
|
|
{
|
|
|
|
if (ui_.labelsTextEdit->isReadOnly()) // no edits could have been made
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString const labels_text = ui_.labelsTextEdit->toPlainText().trimmed();
|
|
|
|
|
|
|
|
if (labels_text == labels_baseline_) // no edits have been made
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString const re = QStringLiteral("((,|;)\\s*)");
|
|
|
|
|
|
|
|
//see https://doc.qt.io/qt-5/qt.html#SplitBehaviorFlags-enum
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
|
|
|
QStringList const labels_list = labels_text.split(QRegularExpression(re), QString::SkipEmptyParts);
|
|
|
|
#else
|
|
|
|
QStringList const labels_list = labels_text.split(QRegularExpression(re), Qt::SkipEmptyParts);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
torrentSet(TR_KEY_labels, labels_list);
|
|
|
|
|
|
|
|
if (!ids_.empty())
|
|
|
|
{
|
|
|
|
session_.refreshDetailInfo(ids_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-08 16:23:21 +00:00
|
|
|
namespace
|
|
|
|
{
|
2017-04-30 16:30:03 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void setIfIdle(QComboBox* box, int i)
|
|
|
|
{
|
|
|
|
if (!box->hasFocus())
|
|
|
|
{
|
|
|
|
box->blockSignals(true);
|
|
|
|
box->setCurrentIndex(i);
|
|
|
|
box->blockSignals(false);
|
|
|
|
}
|
2013-02-03 19:13:04 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void setIfIdle(QDoubleSpinBox* spin, double value)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
if (!spin->hasFocus())
|
|
|
|
{
|
|
|
|
spin->blockSignals(true);
|
|
|
|
spin->setValue(value);
|
|
|
|
spin->blockSignals(false);
|
|
|
|
}
|
|
|
|
}
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void setIfIdle(QSpinBox* spin, int value)
|
|
|
|
{
|
|
|
|
if (!spin->hasFocus())
|
2010-12-08 16:23:21 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
spin->blockSignals(true);
|
|
|
|
spin->setValue(value);
|
|
|
|
spin->blockSignals(false);
|
2010-12-08 16:23:21 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2017-04-30 16:30:03 +00:00
|
|
|
|
|
|
|
} // namespace
|
2010-12-08 16:23:21 +00:00
|
|
|
|
2020-07-29 16:56:23 +00:00
|
|
|
void DetailsDialog::refreshUI()
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-11-02 01:13:32 +00:00
|
|
|
bool const single = ids_.size() == 1;
|
2023-07-18 15:20:17 +00:00
|
|
|
auto const blank = QString{};
|
|
|
|
auto const fm = fontMetrics();
|
|
|
|
auto const none = tr("None");
|
|
|
|
auto const mixed = tr("Mixed");
|
|
|
|
auto const unknown = tr("Unknown");
|
2020-11-02 01:13:32 +00:00
|
|
|
auto const now = time(nullptr);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
// build a list of torrents
|
2023-07-18 15:20:17 +00:00
|
|
|
auto torrents = QList<Torrent const*>{};
|
2020-05-27 21:53:12 +00:00
|
|
|
for (int const id : ids_)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
Torrent const* tor = model_.getTorrentFromId(id);
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2017-04-30 16:25:26 +00:00
|
|
|
if (tor != nullptr)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
torrents << tor;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
///
|
|
|
|
/// activity tab
|
|
|
|
///
|
|
|
|
|
|
|
|
// myStateLabel
|
2023-07-18 15:20:17 +00:00
|
|
|
auto string = QString{};
|
2017-04-19 12:04:45 +00:00
|
|
|
if (torrents.empty())
|
2010-12-08 16:23:21 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = none;
|
2010-12-08 16:23:21 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2010-12-08 16:23:21 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
bool is_mixed = false;
|
|
|
|
bool all_paused = true;
|
|
|
|
bool all_finished = true;
|
2017-04-20 16:02:19 +00:00
|
|
|
tr_torrent_activity const baseline = torrents[0]->getActivity();
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const t : torrents)
|
2010-12-08 16:23:21 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
tr_torrent_activity const activity = t->getActivity();
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
if (activity != baseline)
|
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
is_mixed = true;
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (activity != TR_STATUS_STOPPED)
|
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
all_paused = all_finished = false;
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!t->isFinished())
|
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
all_finished = false;
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2010-12-08 16:23:21 +00:00
|
|
|
}
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
if (is_mixed)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
string = mixed;
|
|
|
|
}
|
2020-05-27 21:53:12 +00:00
|
|
|
else if (all_finished)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
string = tr("Finished");
|
|
|
|
}
|
2020-05-27 21:53:12 +00:00
|
|
|
else if (all_paused)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
string = tr("Paused");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
string = torrents[0]->activityString();
|
|
|
|
}
|
2009-04-18 23:18:28 +00:00
|
|
|
}
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.stateValueLabel->setText(string);
|
2023-11-21 15:02:03 +00:00
|
|
|
auto const state_string = string;
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
// myHaveLabel
|
2020-05-27 21:53:12 +00:00
|
|
|
uint64_t size_when_done = 0;
|
2017-04-19 12:04:45 +00:00
|
|
|
uint64_t available = 0;
|
|
|
|
|
|
|
|
if (torrents.empty())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = none;
|
2013-02-03 19:13:04 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
uint64_t left_until_done = 0;
|
|
|
|
int64_t have_verified = 0;
|
|
|
|
int64_t have_unverified = 0;
|
2014-12-12 23:52:17 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const t : torrents)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
if (t->hasMetadata())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
have_unverified += t->haveUnverified();
|
2017-04-20 16:02:19 +00:00
|
|
|
uint64_t const v = t->haveVerified();
|
2020-05-27 21:53:12 +00:00
|
|
|
have_verified += v;
|
|
|
|
size_when_done += t->sizeWhenDone();
|
|
|
|
left_until_done += t->leftUntilDone();
|
2021-10-24 04:45:10 +00:00
|
|
|
available += t->sizeWhenDone() - t->leftUntilDone() + t->haveUnverified() + t->desiredAvailable();
|
2013-02-03 19:13:04 +00:00
|
|
|
}
|
|
|
|
}
|
2009-04-09 18:55:47 +00:00
|
|
|
|
2020-08-11 18:11:55 +00:00
|
|
|
double const d = size_when_done == 0 ?
|
|
|
|
100.0 :
|
|
|
|
100.0 * static_cast<double>(size_when_done - left_until_done) / static_cast<double>(size_when_done);
|
2023-11-16 04:15:40 +00:00
|
|
|
auto const pct = Formatter::percent_to_string(d);
|
|
|
|
auto const size_when_done_str = Formatter::storage_to_string(size_when_done);
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
if (have_unverified == 0 && left_until_done == 0)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
//: Text following the "Have:" label in torrent properties dialog;
|
|
|
|
//: %1 is amount of downloaded and verified data
|
2023-11-16 04:15:40 +00:00
|
|
|
string = tr("%1 (100%)").arg(Formatter::storage_to_string(have_verified));
|
2009-04-26 15:41:04 +00:00
|
|
|
}
|
2020-05-27 21:53:12 +00:00
|
|
|
else if (have_unverified == 0)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
//: Text following the "Have:" label in torrent properties dialog;
|
|
|
|
//: %1 is amount of downloaded and verified data,
|
|
|
|
//: %2 is overall size of torrent data,
|
|
|
|
//: %3 is percentage (%1/%2*100)
|
2023-11-16 04:15:40 +00:00
|
|
|
string = tr("%1 of %2 (%3%)").arg(Formatter::storage_to_string(have_verified)).arg(size_when_done_str).arg(pct);
|
2010-06-03 23:39:31 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2010-09-17 05:43:06 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
//: Text following the "Have:" label in torrent properties dialog;
|
|
|
|
//: %1 is amount of downloaded data (both verified and unverified),
|
|
|
|
//: %2 is overall size of torrent data,
|
|
|
|
//: %3 is percentage (%1/%2*100),
|
|
|
|
//: %4 is amount of downloaded but not yet verified data
|
2020-11-09 03:31:02 +00:00
|
|
|
string = tr("%1 of %2 (%3%), %4 Unverified")
|
2023-11-16 04:15:40 +00:00
|
|
|
.arg(Formatter::storage_to_string(have_verified + have_unverified))
|
2021-08-15 09:41:48 +00:00
|
|
|
.arg(size_when_done_str)
|
|
|
|
.arg(pct)
|
2023-11-16 04:15:40 +00:00
|
|
|
.arg(Formatter::storage_to_string(have_unverified));
|
2010-06-03 23:39:31 +00:00
|
|
|
}
|
2009-04-18 23:18:28 +00:00
|
|
|
}
|
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.haveValueLabel->setText(string);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
// myAvailabilityLabel
|
2020-05-27 21:53:12 +00:00
|
|
|
if (torrents.empty() || size_when_done == 0)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
string = none;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-08-11 18:11:55 +00:00
|
|
|
auto const percent = 100.0 * static_cast<double>(available) / static_cast<double>(size_when_done);
|
2023-11-16 04:15:40 +00:00
|
|
|
string = QStringLiteral("%1%").arg(Formatter::percent_to_string(percent));
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2010-02-02 05:34:26 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.availabilityValueLabel->setText(string);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
// myDownloadedLabel
|
|
|
|
if (torrents.empty())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = none;
|
2013-02-03 19:13:04 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2020-11-09 03:31:02 +00:00
|
|
|
auto d = uint64_t{};
|
|
|
|
auto f = uint64_t{};
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const t : torrents)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
d += t->downloadedEver();
|
|
|
|
f += t->failedEver();
|
|
|
|
}
|
|
|
|
|
2023-11-16 04:15:40 +00:00
|
|
|
auto const dstr = Formatter::storage_to_string(d);
|
|
|
|
auto const fstr = Formatter::storage_to_string(f);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-30 16:25:26 +00:00
|
|
|
if (f != 0)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2022-02-11 07:15:10 +00:00
|
|
|
string = tr("%1 (+%2 discarded after failed checksum)").arg(dstr).arg(fstr);
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
string = dstr;
|
2009-06-23 00:24:37 +00:00
|
|
|
}
|
|
|
|
}
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.downloadedValueLabel->setText(string);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
// myUploadedLabel
|
|
|
|
if (torrents.empty())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = none;
|
2013-02-03 19:13:04 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2022-03-17 18:41:48 +00:00
|
|
|
auto uploaded = uint64_t{};
|
|
|
|
auto denominator = uint64_t{};
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const t : torrents)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2022-03-17 18:41:48 +00:00
|
|
|
uploaded += t->uploadedEver();
|
|
|
|
denominator += t->sizeWhenDone();
|
2010-06-03 23:39:31 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2022-03-17 18:41:48 +00:00
|
|
|
string = tr("%1 (Ratio: %2)")
|
2023-11-16 04:15:40 +00:00
|
|
|
.arg(Formatter::storage_to_string(uploaded))
|
|
|
|
.arg(Formatter::ratio_to_string(tr_getRatio(uploaded, denominator)));
|
2010-06-03 23:39:31 +00:00
|
|
|
}
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.uploadedValueLabel->setText(string);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
// myRunTimeLabel
|
|
|
|
if (torrents.empty())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = none;
|
2013-02-03 19:13:04 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
bool all_paused = true;
|
2019-11-06 23:31:41 +00:00
|
|
|
auto baseline = torrents[0]->lastStarted();
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const t : torrents)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
if (baseline != t->lastStarted())
|
|
|
|
{
|
2019-11-06 23:31:41 +00:00
|
|
|
baseline = 0;
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!t->isPaused())
|
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
all_paused = false;
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2009-04-26 15:41:04 +00:00
|
|
|
}
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
if (all_paused)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
string = state_string; // paused || finished
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2019-11-06 23:31:41 +00:00
|
|
|
else if (baseline == 0)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
string = mixed;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2022-09-08 23:26:18 +00:00
|
|
|
auto const seconds = static_cast<int>(std::difftime(now, baseline));
|
2023-11-16 04:15:40 +00:00
|
|
|
string = Formatter::time_to_string(seconds);
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2009-04-26 15:41:04 +00:00
|
|
|
}
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.runningTimeValueLabel->setText(string);
|
2009-04-26 15:41:04 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
// myETALabel
|
|
|
|
string.clear();
|
|
|
|
|
|
|
|
if (torrents.empty())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = none;
|
2013-02-03 19:13:04 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2022-07-27 14:03:13 +00:00
|
|
|
int const baseline = torrents[0]->getETA();
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const t : torrents)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
if (baseline != t->getETA())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = mixed;
|
|
|
|
break;
|
2009-10-10 20:42:23 +00:00
|
|
|
}
|
|
|
|
}
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (string.isEmpty())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
if (baseline < 0)
|
|
|
|
{
|
|
|
|
string = tr("Unknown");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-11-16 04:15:40 +00:00
|
|
|
string = Formatter::time_to_string(baseline);
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
}
|
2009-10-10 20:42:23 +00:00
|
|
|
}
|
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.remainingTimeValueLabel->setText(string);
|
2009-10-10 20:42:23 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
// myLastActivityLabel
|
|
|
|
if (torrents.empty())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = none;
|
2013-02-03 19:13:04 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2019-11-06 23:31:41 +00:00
|
|
|
auto latest = torrents[0]->lastActivity();
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2024-02-17 19:31:49 +00:00
|
|
|
for (Torrent const* const tor : torrents)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2024-02-17 19:31:49 +00:00
|
|
|
latest = std::max(latest, tor->lastActivity());
|
2009-04-26 15:41:04 +00:00
|
|
|
}
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2022-09-08 23:26:18 +00:00
|
|
|
auto const seconds = static_cast<int>(std::difftime(now, latest));
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2024-10-22 01:56:37 +00:00
|
|
|
if (latest == 0)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
string = none;
|
|
|
|
}
|
|
|
|
else if (seconds < 5)
|
|
|
|
{
|
|
|
|
string = tr("Active now");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-11-16 04:15:40 +00:00
|
|
|
string = tr("%1 ago").arg(Formatter::time_to_string(seconds));
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2009-04-18 23:18:28 +00:00
|
|
|
}
|
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.lastActivityValueLabel->setText(string);
|
2009-04-26 15:41:04 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (torrents.empty())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = none;
|
2013-02-03 19:13:04 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = torrents[0]->getError();
|
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const t : torrents)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
if (string != t->getError())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = mixed;
|
|
|
|
break;
|
2009-04-26 15:41:04 +00:00
|
|
|
}
|
|
|
|
}
|
2009-04-18 23:18:28 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (string.isEmpty())
|
|
|
|
{
|
|
|
|
string = none;
|
|
|
|
}
|
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.errorValueLabel->setText(string);
|
2009-04-26 15:41:04 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
///
|
|
|
|
/// information tab
|
|
|
|
///
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
// mySizeLabel
|
|
|
|
if (torrents.empty())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = none;
|
2009-04-09 18:55:47 +00:00
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
else
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
int pieces = 0;
|
2020-11-09 03:31:02 +00:00
|
|
|
auto size = uint64_t{};
|
2020-05-27 21:53:12 +00:00
|
|
|
uint32_t piece_size = torrents[0]->pieceSize();
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const t : torrents)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
pieces += t->pieceCount();
|
|
|
|
size += t->totalSize();
|
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
if (piece_size != t->pieceSize())
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
piece_size = 0;
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2013-02-03 19:13:04 +00:00
|
|
|
}
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2017-04-30 16:25:26 +00:00
|
|
|
if (size == 0)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = none;
|
|
|
|
}
|
2020-05-27 21:53:12 +00:00
|
|
|
else if (piece_size > 0)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2023-11-16 04:15:40 +00:00
|
|
|
string = tr("%1 (%Ln pieces @ %2)", "", pieces)
|
|
|
|
.arg(Formatter::storage_to_string(size))
|
|
|
|
.arg(Formatter::memory_to_string(piece_size));
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2023-11-16 04:15:40 +00:00
|
|
|
string = tr("%1 (%Ln pieces)", "", pieces).arg(Formatter::storage_to_string(size));
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.sizeValueLabel->setText(string);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
// myHashLabel
|
2020-09-07 21:19:10 +00:00
|
|
|
if (torrents.empty())
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-09-07 21:19:10 +00:00
|
|
|
string = none;
|
|
|
|
}
|
|
|
|
else if (torrents.size() > 1)
|
|
|
|
{
|
|
|
|
string = mixed;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
string = torrents.front()->hash().toString();
|
2009-04-26 15:41:04 +00:00
|
|
|
}
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.hashValueLabel->setText(string);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
// myPrivacyLabel
|
|
|
|
string = none;
|
|
|
|
|
|
|
|
if (!torrents.empty())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2022-07-27 14:03:13 +00:00
|
|
|
bool const b = torrents[0]->isPrivate();
|
2017-04-19 12:04:45 +00:00
|
|
|
string = b ? tr("Private to this tracker -- DHT and PEX disabled") : tr("Public torrent");
|
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const t : torrents)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
if (b != t->isPrivate())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = mixed;
|
|
|
|
break;
|
2009-04-26 15:41:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.privacyValueLabel->setText(string);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2023-12-25 22:09:20 +00:00
|
|
|
// myLabelsTextEdit
|
|
|
|
if (labels_need_refresh_)
|
|
|
|
{
|
|
|
|
labels_need_refresh_ = false;
|
|
|
|
|
|
|
|
if (torrents.empty())
|
|
|
|
{
|
|
|
|
labels_baseline_.clear();
|
2024-01-10 21:01:03 +00:00
|
|
|
ui_.labelsTextEdit->setPlainText({});
|
2023-12-25 22:09:20 +00:00
|
|
|
ui_.labelsTextEdit->setPlaceholderText(none);
|
|
|
|
ui_.labelsTextEdit->setReadOnly(true);
|
|
|
|
ui_.labelsTextEdit->setEnabled(true);
|
|
|
|
}
|
|
|
|
else if (auto const& baseline = torrents[0]->labels(); std::all_of(
|
|
|
|
std::begin(torrents),
|
|
|
|
std::end(torrents),
|
|
|
|
[&baseline](auto const* tor) { return tor->labels() == baseline; }))
|
|
|
|
{
|
|
|
|
labels_baseline_ = baseline.join(QStringLiteral(", "));
|
2024-01-10 21:01:03 +00:00
|
|
|
ui_.labelsTextEdit->setPlainText(labels_baseline_);
|
2023-12-25 22:09:20 +00:00
|
|
|
ui_.labelsTextEdit->setPlaceholderText(none);
|
|
|
|
ui_.labelsTextEdit->setReadOnly(false);
|
|
|
|
ui_.labelsTextEdit->setEnabled(true);
|
|
|
|
}
|
|
|
|
else // mixed
|
|
|
|
{
|
|
|
|
labels_baseline_.clear();
|
2024-01-10 21:01:03 +00:00
|
|
|
ui_.labelsTextEdit->setPlainText({});
|
2023-12-25 22:09:20 +00:00
|
|
|
ui_.labelsTextEdit->setPlaceholderText(mixed);
|
|
|
|
ui_.labelsTextEdit->setEnabled(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
// myCommentBrowser
|
|
|
|
string = none;
|
2020-06-05 19:02:11 +00:00
|
|
|
bool is_comment_mixed = false;
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
if (!torrents.empty())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = torrents[0]->comment();
|
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const t : torrents)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
if (string != t->comment())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = mixed;
|
2020-06-05 19:02:11 +00:00
|
|
|
is_comment_mixed = true;
|
2017-04-19 12:04:45 +00:00
|
|
|
break;
|
2009-04-26 15:41:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2024-01-10 21:01:03 +00:00
|
|
|
if (ui_.commentTextEdit->toPlainText() != string)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2024-01-10 21:01:03 +00:00
|
|
|
ui_.commentTextEdit->setPlainText(string);
|
2013-02-03 18:36:10 +00:00
|
|
|
}
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2024-01-10 21:01:03 +00:00
|
|
|
ui_.commentTextEdit->setEnabled(!is_comment_mixed && !string.isEmpty());
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
// myOriginLabel
|
|
|
|
string = none;
|
|
|
|
|
|
|
|
if (!torrents.empty())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-05-01 15:46:41 +00:00
|
|
|
bool mixed_creator = false;
|
|
|
|
bool mixed_date = false;
|
2023-11-21 15:02:03 +00:00
|
|
|
auto const creator = torrents[0]->creator();
|
2019-11-06 23:31:41 +00:00
|
|
|
auto const date = torrents[0]->dateCreated();
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const t : torrents)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
mixed_creator |= (creator != t->creator());
|
2019-11-06 23:31:41 +00:00
|
|
|
mixed_date |= (date != t->dateCreated());
|
2009-04-26 15:41:04 +00:00
|
|
|
}
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
bool const empty_creator = creator.isEmpty();
|
2019-11-06 23:31:41 +00:00
|
|
|
bool const empty_date = date <= 0;
|
2015-04-22 21:04:49 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (mixed_creator || mixed_date)
|
|
|
|
{
|
|
|
|
string = mixed;
|
|
|
|
}
|
|
|
|
else if (empty_creator && empty_date)
|
|
|
|
{
|
|
|
|
string = tr("N/A");
|
|
|
|
}
|
|
|
|
else if (empty_date && !empty_creator)
|
|
|
|
{
|
|
|
|
string = tr("Created by %1").arg(creator);
|
|
|
|
}
|
|
|
|
else if (empty_creator && !empty_date)
|
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
auto const date_str = QDateTime::fromSecsSinceEpoch(date).toString();
|
|
|
|
string = tr("Created on %1").arg(date_str);
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
auto const date_str = QDateTime::fromSecsSinceEpoch(date).toString();
|
|
|
|
string = tr("Created by %1 on %2").arg(creator).arg(date_str);
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2009-04-26 15:41:04 +00:00
|
|
|
}
|
2010-01-05 23:47:50 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.originValueLabel->setText(string);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
// myLocationLabel
|
|
|
|
string = none;
|
|
|
|
|
|
|
|
if (!torrents.empty())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = torrents[0]->getPath();
|
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const t : torrents)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
if (string != t->getPath())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
string = mixed;
|
|
|
|
break;
|
2009-04-26 15:41:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.locationValueLabel->setText(string);
|
2022-05-23 05:02:46 +00:00
|
|
|
// myAddedLabel
|
|
|
|
string = none;
|
|
|
|
|
|
|
|
if (!torrents.empty())
|
|
|
|
{
|
|
|
|
auto const date = torrents[0]->dateAdded();
|
|
|
|
bool mixed_date = false;
|
|
|
|
|
|
|
|
for (Torrent const* const t : torrents)
|
|
|
|
{
|
|
|
|
mixed_date |= (date != t->dateAdded());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool const empty_date = date <= 0;
|
|
|
|
|
|
|
|
if (empty_date)
|
|
|
|
{
|
|
|
|
string = tr("N/A");
|
|
|
|
}
|
|
|
|
else if (mixed_date)
|
|
|
|
{
|
|
|
|
string = mixed;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
auto const date_str = QDateTime::fromSecsSinceEpoch(date).toString();
|
|
|
|
string = date_str;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-10 21:01:03 +00:00
|
|
|
ui_.addedValueLabel->setText(string);
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
///
|
|
|
|
/// Options Tab
|
|
|
|
///
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2020-07-29 16:56:23 +00:00
|
|
|
if (canEdit() && !torrents.empty())
|
2009-04-18 23:18:28 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
Torrent const& baseline = *torrents.front();
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
// mySessionLimitCheck
|
2022-02-08 03:56:04 +00:00
|
|
|
bool uniform = true;
|
|
|
|
bool baseline_flag = baseline.honorsSessionLimits();
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const tor : torrents)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
if (baseline_flag != tor->honorsSessionLimits())
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
uniform = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.sessionLimitCheck->setChecked(uniform && baseline_flag);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
// mySingleDownCheck
|
|
|
|
uniform = true;
|
2020-05-27 21:53:12 +00:00
|
|
|
baseline_flag = baseline.downloadIsLimited();
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const tor : torrents)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
if (baseline_flag != tor->downloadIsLimited())
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
uniform = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.singleDownCheck->setChecked(uniform && baseline_flag);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
// mySingleUpCheck
|
|
|
|
uniform = true;
|
2020-05-27 21:53:12 +00:00
|
|
|
baseline_flag = baseline.uploadIsLimited();
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const tor : torrents)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
if (baseline_flag != tor->uploadIsLimited())
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
uniform = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.singleUpCheck->setChecked(uniform && baseline_flag);
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
// myBandwidthPriorityCombo
|
|
|
|
uniform = true;
|
2022-07-27 14:03:13 +00:00
|
|
|
int const baseline_int = baseline.getBandwidthPriority();
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const tor : torrents)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
if (baseline_int != tor->getBandwidthPriority())
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
uniform = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2022-07-27 14:03:13 +00:00
|
|
|
int const i = uniform ? ui_.bandwidthPriorityCombo->findData(baseline_int) : -1;
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
setIfIdle(ui_.bandwidthPriorityCombo, i);
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2023-11-13 17:13:17 +00:00
|
|
|
setIfIdle(ui_.singleDownSpin, static_cast<int>(baseline.downloadLimit().count(Speed::Units::KByps)));
|
|
|
|
setIfIdle(ui_.singleUpSpin, static_cast<int>(baseline.uploadLimit().count(Speed::Units::KByps)));
|
2020-05-27 21:53:12 +00:00
|
|
|
setIfIdle(ui_.peerLimitSpin, baseline.peerLimit());
|
2010-07-24 04:14:43 +00:00
|
|
|
}
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (!torrents.empty())
|
2010-07-24 04:14:43 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
Torrent const& baseline = *torrents.front();
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
// ratio
|
|
|
|
bool uniform = true;
|
2020-05-27 21:53:12 +00:00
|
|
|
int baseline_int = baseline.seedRatioMode();
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const tor : torrents)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
if (baseline_int != tor->seedRatioMode())
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
uniform = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2010-07-24 04:14:43 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
setIfIdle(ui_.ratioCombo, uniform ? ui_.ratioCombo->findData(baseline_int) : -1);
|
|
|
|
ui_.ratioSpin->setVisible(uniform && baseline_int == TR_RATIOLIMIT_SINGLE);
|
2009-04-09 18:55:47 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
setIfIdle(ui_.ratioSpin, baseline.seedRatioLimit());
|
2010-07-24 04:14:43 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
// idle
|
|
|
|
uniform = true;
|
2020-05-27 21:53:12 +00:00
|
|
|
baseline_int = baseline.seedIdleMode();
|
2010-07-24 04:14:43 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const tor : torrents)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
if (baseline_int != tor->seedIdleMode())
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
uniform = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2010-07-24 04:14:43 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
setIfIdle(ui_.idleCombo, uniform ? ui_.idleCombo->findData(baseline_int) : -1);
|
|
|
|
ui_.idleSpin->setVisible(uniform && baseline_int == TR_RATIOLIMIT_SINGLE);
|
2010-07-24 04:14:43 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
setIfIdle(ui_.idleSpin, baseline.seedIdleLimit());
|
2017-04-19 12:04:45 +00:00
|
|
|
onIdleLimitChanged();
|
2009-04-18 23:18:28 +00:00
|
|
|
}
|
2009-04-09 18:55:47 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
///
|
|
|
|
/// Tracker tab
|
|
|
|
///
|
2010-06-30 05:55:46 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
tracker_model_->refresh(model_, ids_);
|
2022-02-18 17:52:01 +00:00
|
|
|
ui_.editTrackersButton->setEnabled(std::size(ids_) == 1);
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
///
|
|
|
|
/// Peers tab
|
|
|
|
///
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2023-06-19 21:51:34 +00:00
|
|
|
auto peers2 = decltype(peers_){};
|
2020-06-05 19:02:11 +00:00
|
|
|
QList<QTreeWidgetItem*> new_items;
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (Torrent const* const t : torrents)
|
2009-04-09 18:55:47 +00:00
|
|
|
{
|
2023-11-21 15:02:03 +00:00
|
|
|
auto const id_str = QString::number(t->id());
|
2009-05-04 02:58:05 +00:00
|
|
|
|
2023-06-19 21:51:34 +00:00
|
|
|
for (Peer const& peer : t->peers())
|
2009-05-04 02:58:05 +00:00
|
|
|
{
|
2023-11-21 15:02:03 +00:00
|
|
|
auto const key = id_str + QLatin1Char(':') + peer.address;
|
2009-05-04 02:58:05 +00:00
|
|
|
|
2023-06-19 21:51:34 +00:00
|
|
|
PeerItem* item = nullptr;
|
|
|
|
if (auto iter = peers_.find(key); iter != std::end(peers_))
|
|
|
|
{
|
|
|
|
item = dynamic_cast<PeerItem*>(iter->second);
|
|
|
|
}
|
|
|
|
else // new peer has connected
|
2009-05-04 02:58:05 +00:00
|
|
|
{
|
2023-07-18 15:20:17 +00:00
|
|
|
item = new PeerItem{ peer };
|
2017-04-19 12:04:45 +00:00
|
|
|
item->setTextAlignment(COL_UP, Qt::AlignRight | Qt::AlignVCenter);
|
|
|
|
item->setTextAlignment(COL_DOWN, Qt::AlignRight | Qt::AlignVCenter);
|
|
|
|
item->setTextAlignment(COL_PERCENT, Qt::AlignRight | Qt::AlignVCenter);
|
2020-08-15 15:42:51 +00:00
|
|
|
item->setIcon(COL_LOCK, peer.is_encrypted ? icon_encrypted_ : icon_unencrypted_);
|
2023-07-18 15:20:17 +00:00
|
|
|
item->setToolTip(COL_LOCK, peer.is_encrypted ? tr("Encrypted connection") : QString{});
|
2017-04-19 12:04:45 +00:00
|
|
|
item->setText(COL_ADDRESS, peer.address);
|
2020-05-27 21:53:12 +00:00
|
|
|
item->setText(COL_CLIENT, peer.client_name);
|
2020-06-05 19:02:11 +00:00
|
|
|
new_items << item;
|
2009-05-04 02:58:05 +00:00
|
|
|
}
|
2009-04-09 18:55:47 +00:00
|
|
|
|
2023-06-19 21:51:34 +00:00
|
|
|
auto const& code = peer.flags;
|
2017-04-19 12:04:45 +00:00
|
|
|
item->setStatus(code);
|
|
|
|
item->refresh(peer);
|
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
QString code_tip;
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2017-04-20 16:02:19 +00:00
|
|
|
for (QChar const ch : code)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
QString txt;
|
|
|
|
|
|
|
|
switch (ch.unicode())
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
case 'O':
|
|
|
|
txt = tr("Optimistic unchoke");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'D':
|
|
|
|
txt = tr("Downloading from this peer");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'd':
|
|
|
|
txt = tr("We would download from this peer if they would let us");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'U':
|
|
|
|
txt = tr("Uploading to peer");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'u':
|
|
|
|
txt = tr("We would upload to this peer if they asked");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'K':
|
|
|
|
txt = tr("Peer has unchoked us, but we're not interested");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case '?':
|
|
|
|
txt = tr("We unchoked this peer, but they're not interested");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'E':
|
|
|
|
txt = tr("Encrypted connection");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'H':
|
|
|
|
txt = tr("Peer was discovered through DHT");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'X':
|
|
|
|
txt = tr("Peer was discovered through Peer Exchange (PEX)");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'I':
|
|
|
|
txt = tr("Peer is an incoming connection");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'T':
|
2022-10-11 15:39:41 +00:00
|
|
|
txt = tr("Peer is connected over µTP");
|
2017-04-19 12:04:45 +00:00
|
|
|
break;
|
2020-11-02 01:13:32 +00:00
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
2009-05-04 02:58:05 +00:00
|
|
|
}
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
if (!txt.isEmpty())
|
|
|
|
{
|
2020-05-29 17:40:07 +00:00
|
|
|
code_tip += QStringLiteral("%1: %2\n").arg(ch).arg(txt);
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2009-04-09 18:55:47 +00:00
|
|
|
}
|
2009-05-04 02:58:05 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
if (!code_tip.isEmpty())
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
code_tip.resize(code_tip.size() - 1); // eat the trailing linefeed
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2009-05-04 02:58:05 +00:00
|
|
|
|
2023-11-13 17:13:17 +00:00
|
|
|
item->setText(COL_UP, peer.rate_to_peer.is_zero() ? QString{} : peer.rate_to_peer.to_qstring());
|
|
|
|
item->setText(COL_DOWN, peer.rate_to_client.is_zero() ? QString{} : peer.rate_to_client.to_qstring());
|
2022-09-08 23:26:18 +00:00
|
|
|
item->setText(
|
|
|
|
COL_PERCENT,
|
2023-07-18 15:20:17 +00:00
|
|
|
peer.progress > 0 ? QStringLiteral("%1%").arg(static_cast<int>(peer.progress * 100.0)) : QString{});
|
2017-04-19 12:04:45 +00:00
|
|
|
item->setText(COL_STATUS, code);
|
2020-05-27 21:53:12 +00:00
|
|
|
item->setToolTip(COL_STATUS, code_tip);
|
2009-05-04 02:58:05 +00:00
|
|
|
|
2023-06-19 21:51:34 +00:00
|
|
|
peers2.try_emplace(key, item);
|
2009-04-09 18:55:47 +00:00
|
|
|
}
|
|
|
|
}
|
2013-02-03 19:13:04 +00:00
|
|
|
|
2020-06-05 19:02:11 +00:00
|
|
|
ui_.peersView->addTopLevelItems(new_items);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2023-06-19 21:51:34 +00:00
|
|
|
for (auto const& [key, item] : peers_)
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2023-06-19 21:51:34 +00:00
|
|
|
if (peers2.count(key) == 0U) // old peer has disconnected
|
2013-02-03 19:13:04 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.peersView->takeTopLevelItem(ui_.peersView->indexOfTopLevelItem(item));
|
2017-04-19 12:04:45 +00:00
|
|
|
delete item;
|
2009-04-09 18:55:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-19 21:51:34 +00:00
|
|
|
peers_ = std::move(peers2);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2020-07-29 16:56:23 +00:00
|
|
|
if (single)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-07-29 16:56:23 +00:00
|
|
|
ui_.filesView->update(torrents[0]->files(), canEdit());
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2020-07-29 16:56:23 +00:00
|
|
|
else
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-07-29 16:56:23 +00:00
|
|
|
ui_.filesView->clear();
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2009-04-18 23:18:28 +00:00
|
|
|
|
2019-11-09 14:44:40 +00:00
|
|
|
setEnabled(true);
|
|
|
|
}
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2019-11-09 14:44:40 +00:00
|
|
|
void DetailsDialog::setEnabled(bool enabled)
|
|
|
|
{
|
2020-07-29 16:56:23 +00:00
|
|
|
ui_.tabs->setEnabled(enabled);
|
2009-04-09 18:55:47 +00:00
|
|
|
}
|
|
|
|
|
2024-01-05 05:12:51 +00:00
|
|
|
// ---
|
2009-04-09 18:55:47 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::initInfoTab()
|
2009-04-09 18:55:47 +00:00
|
|
|
{
|
2024-01-10 21:01:03 +00:00
|
|
|
int const cbh = QFontMetrics{ ui_.commentTextEdit->font() }.lineSpacing() * 4;
|
|
|
|
ui_.commentTextEdit->setFixedHeight(cbh);
|
2023-12-25 22:09:20 +00:00
|
|
|
|
|
|
|
int const lteh = QFontMetrics{ ui_.labelsTextEdit->font() }.lineSpacing() * 2;
|
|
|
|
ui_.labelsTextEdit->setFixedHeight(lteh);
|
2024-01-10 21:01:03 +00:00
|
|
|
ui_.labelsTextEdit->setPlainText(QStringLiteral("Initializing..."));
|
2015-01-21 21:14:00 +00:00
|
|
|
|
2023-07-18 15:20:17 +00:00
|
|
|
auto* cr = new ColumnResizer{ this };
|
2020-05-27 21:53:12 +00:00
|
|
|
cr->addLayout(ui_.activitySectionLayout);
|
|
|
|
cr->addLayout(ui_.detailsSectionLayout);
|
2017-04-19 12:04:45 +00:00
|
|
|
cr->update();
|
2009-04-09 18:55:47 +00:00
|
|
|
}
|
|
|
|
|
2024-01-05 05:12:51 +00:00
|
|
|
// ---
|
2009-04-09 18:55:47 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::onShowTrackerScrapesToggled(bool val)
|
2010-04-03 14:23:29 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
prefs_.set(Prefs::SHOW_TRACKER_SCRAPES, val);
|
2010-04-03 14:23:29 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::onShowBackupTrackersToggled(bool val)
|
2010-07-28 14:43:47 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
prefs_.set(Prefs::SHOW_BACKUP_TRACKERS, val);
|
2010-07-28 14:43:47 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::onHonorsSessionLimitsToggled(bool val)
|
2009-04-12 21:15:35 +00:00
|
|
|
{
|
2020-07-29 16:56:23 +00:00
|
|
|
torrentSet(TR_KEY_honorsSessionLimits, val);
|
2009-04-12 21:15:35 +00:00
|
|
|
}
|
2019-07-14 21:30:14 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::onDownloadLimitedToggled(bool val)
|
2009-04-12 21:15:35 +00:00
|
|
|
{
|
2020-07-29 16:56:23 +00:00
|
|
|
torrentSet(TR_KEY_downloadLimited, val);
|
2009-04-12 21:15:35 +00:00
|
|
|
}
|
2019-07-14 21:30:14 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::onSpinBoxEditingFinished()
|
2009-04-12 21:15:35 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
QObject const* spin = sender();
|
2020-06-05 19:02:11 +00:00
|
|
|
tr_quark const key = spin->property(PrefKey).toInt();
|
2020-05-20 01:32:51 +00:00
|
|
|
auto const* d = qobject_cast<QDoubleSpinBox const*>(spin);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2017-04-30 16:25:26 +00:00
|
|
|
if (d != nullptr)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2020-07-29 16:56:23 +00:00
|
|
|
torrentSet(key, d->value());
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-07-29 16:56:23 +00:00
|
|
|
torrentSet(key, qobject_cast<QSpinBox const*>(spin)->value());
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
2009-04-12 21:15:35 +00:00
|
|
|
}
|
2010-12-04 00:19:52 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::onUploadLimitedToggled(bool val)
|
2009-04-12 21:15:35 +00:00
|
|
|
{
|
2020-07-29 16:56:23 +00:00
|
|
|
torrentSet(TR_KEY_uploadLimited, val);
|
2009-04-12 21:15:35 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::onIdleModeChanged(int index)
|
2009-04-12 21:15:35 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
int const val = ui_.idleCombo->itemData(index).toInt();
|
2020-07-29 16:56:23 +00:00
|
|
|
torrentSet(TR_KEY_seedIdleMode, val);
|
2009-04-12 21:15:35 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::onIdleLimitChanged()
|
2014-12-22 03:06:09 +00:00
|
|
|
{
|
2023-04-14 18:38:56 +00:00
|
|
|
//: Spin box format, "Stop seeding if idle for: [ 5 minutes ]"
|
2023-04-18 04:11:09 +00:00
|
|
|
auto const* const units_format = QT_TRANSLATE_N_NOOP("DetailsDialog", "%1 minute(s)");
|
2023-04-14 18:38:56 +00:00
|
|
|
auto const placeholder = QStringLiteral("%1");
|
|
|
|
Utils::updateSpinBoxFormat(ui_.idleSpin, "DetailsDialog", units_format, placeholder);
|
2014-12-22 03:06:09 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::onRatioModeChanged(int index)
|
2010-07-24 04:14:43 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
int const val = ui_.ratioCombo->itemData(index).toInt();
|
2020-07-29 16:56:23 +00:00
|
|
|
torrentSet(TR_KEY_seedRatioMode, val);
|
2010-07-24 04:14:43 +00:00
|
|
|
}
|
2009-04-19 02:48:55 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::onBandwidthPriorityChanged(int index)
|
2009-04-18 23:18:28 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
if (index != -1)
|
2009-04-18 23:18:28 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
int const priority = ui_.bandwidthPriorityCombo->itemData(index).toInt();
|
2020-07-29 16:56:23 +00:00
|
|
|
torrentSet(TR_KEY_bandwidthPriority, priority);
|
2009-04-18 23:18:28 +00:00
|
|
|
}
|
2009-04-09 18:55:47 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::onTrackerSelectionChanged()
|
2010-06-30 05:55:46 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
int const selection_count = ui_.trackersView->selectionModel()->selectedRows().size();
|
|
|
|
ui_.removeTrackerButton->setEnabled(selection_count > 0);
|
2010-06-30 05:55:46 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::onAddTrackerClicked()
|
2010-06-30 05:55:46 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
bool ok = false;
|
2023-03-02 06:33:49 +00:00
|
|
|
auto const text_qstr = QInputDialog::getMultiLineText(
|
2021-10-11 03:19:36 +00:00
|
|
|
this,
|
2022-10-11 15:39:41 +00:00
|
|
|
tr("Add URL(s)"),
|
2021-10-11 03:19:36 +00:00
|
|
|
tr("Add tracker announce URLs, one per line:"),
|
|
|
|
{},
|
|
|
|
&ok);
|
2023-03-02 06:33:49 +00:00
|
|
|
if (!ok)
|
2010-07-27 19:43:32 +00:00
|
|
|
{
|
2023-03-02 06:33:49 +00:00
|
|
|
return;
|
|
|
|
}
|
2010-06-30 05:55:46 +00:00
|
|
|
|
2023-03-02 06:33:49 +00:00
|
|
|
// for each URL entered by the user...
|
|
|
|
auto announce_list = tr_announce_list{};
|
|
|
|
announce_list.parse(text_qstr.toStdString());
|
|
|
|
auto url_to_ids = std::map<QString, std::set<tr_torrent_id_t>>{};
|
|
|
|
for (auto const& info : announce_list)
|
|
|
|
{
|
|
|
|
// for each selected torrent...
|
|
|
|
auto sv = info.announce.sv();
|
|
|
|
auto const announce_url = QString::fromUtf8(std::data(sv), std::size(sv));
|
|
|
|
for (auto const& id : ids_)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2023-03-02 06:33:49 +00:00
|
|
|
// make a note if the torrent doesn't already have the URL
|
|
|
|
if (tracker_model_->find(id, announce_url) == -1)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
2023-03-02 06:33:49 +00:00
|
|
|
url_to_ids[announce_url].insert(id);
|
2017-04-19 12:04:45 +00:00
|
|
|
}
|
|
|
|
}
|
2023-03-02 06:33:49 +00:00
|
|
|
}
|
2010-06-30 05:55:46 +00:00
|
|
|
|
2023-03-02 06:33:49 +00:00
|
|
|
// now reverse the map so that if we're adding identical trackers
|
|
|
|
// to more than one torrent, that can be batched into a single call
|
|
|
|
auto ids_to_urls = std::map<std::set<tr_torrent_id_t>, std::set<QString>>{};
|
|
|
|
for (auto& [announce_url, ids] : url_to_ids)
|
|
|
|
{
|
|
|
|
ids_to_urls[ids].insert(announce_url);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (std::empty(ids_to_urls))
|
|
|
|
{
|
|
|
|
QMessageBox::warning(this, tr("Error"), tr("No new URLs found."));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (auto const& [ids, urls] : ids_to_urls)
|
2010-07-27 19:43:32 +00:00
|
|
|
{
|
2023-03-16 23:59:31 +00:00
|
|
|
auto urls_list = QList<QString>{};
|
|
|
|
urls_list.reserve(std::size(urls));
|
|
|
|
for (auto const& url : urls)
|
2023-04-14 16:47:54 +00:00
|
|
|
{
|
2023-03-16 23:59:31 +00:00
|
|
|
urls_list << url;
|
2023-04-14 16:47:54 +00:00
|
|
|
}
|
2023-03-16 23:59:31 +00:00
|
|
|
|
|
|
|
torrentSet(torrent_ids_t{ std::begin(ids), std::end(ids) }, TR_KEY_trackerAdd, urls_list);
|
2010-07-27 19:43:32 +00:00
|
|
|
}
|
2010-06-30 05:55:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-18 17:52:01 +00:00
|
|
|
void DetailsDialog::onTrackerListEdited(QString tracker_list)
|
2010-06-30 05:55:46 +00:00
|
|
|
{
|
2022-02-18 17:52:01 +00:00
|
|
|
torrentSet(TR_KEY_trackerList, tracker_list);
|
|
|
|
}
|
2012-12-22 20:35:19 +00:00
|
|
|
|
2022-02-18 17:52:01 +00:00
|
|
|
void DetailsDialog::onEditTrackersClicked()
|
|
|
|
{
|
|
|
|
if (std::size(ids_) != 1)
|
2010-06-30 05:55:46 +00:00
|
|
|
{
|
2022-02-18 17:52:01 +00:00
|
|
|
return;
|
2010-07-27 19:43:32 +00:00
|
|
|
}
|
2022-02-18 17:52:01 +00:00
|
|
|
|
|
|
|
auto const* const tor = model_.getTorrentFromId(*std::begin(ids_));
|
|
|
|
if (tor == nullptr)
|
2010-07-27 19:43:32 +00:00
|
|
|
{
|
2022-02-18 17:52:01 +00:00
|
|
|
return;
|
2010-07-27 19:43:32 +00:00
|
|
|
}
|
2010-06-30 05:55:46 +00:00
|
|
|
|
2023-07-18 15:20:17 +00:00
|
|
|
auto* dialog = new TrackersDialog{ tor->trackerList(), this };
|
2022-02-18 17:52:01 +00:00
|
|
|
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
connect(dialog, &TrackersDialog::trackerListEdited, this, &DetailsDialog::onTrackerListEdited);
|
|
|
|
dialog->open();
|
2010-06-30 05:55:46 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::onRemoveTrackerClicked()
|
2010-06-30 05:55:46 +00:00
|
|
|
{
|
2017-04-19 12:04:45 +00:00
|
|
|
// make a map of torrentIds to announce URLs to remove
|
2020-05-27 21:53:12 +00:00
|
|
|
QItemSelectionModel* selection_model = ui_.trackersView->selectionModel();
|
2022-07-27 14:03:13 +00:00
|
|
|
QModelIndexList const selected_rows = selection_model->selectedRows();
|
2023-10-25 01:14:37 +00:00
|
|
|
auto torrent_id_to_tracker_ids = std::map<int, std::set<int>>{};
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2023-10-25 01:14:37 +00:00
|
|
|
for (auto const& model_index : selected_rows)
|
2010-07-27 19:43:32 +00:00
|
|
|
{
|
2023-10-25 01:14:37 +00:00
|
|
|
auto const inf = ui_.trackersView->model()->data(model_index, TrackerModel::TrackerRole).value<TrackerInfo>();
|
|
|
|
torrent_id_to_tracker_ids[inf.torrent_id].insert(inf.st.id);
|
2010-07-27 19:43:32 +00:00
|
|
|
}
|
2010-06-30 05:55:46 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
// batch all of a tracker's torrents into one command
|
2023-10-25 01:14:37 +00:00
|
|
|
for (auto const& [torrent_id, tracker_ids] : torrent_id_to_tracker_ids)
|
2010-07-27 19:43:32 +00:00
|
|
|
{
|
2023-10-25 01:14:37 +00:00
|
|
|
auto const ids = torrent_ids_t{ torrent_id };
|
|
|
|
auto const values = std::vector<int>{ std::begin(tracker_ids), std::end(tracker_ids) };
|
|
|
|
torrentSet(ids, TR_KEY_trackerRemove, values);
|
2010-06-30 05:55:46 +00:00
|
|
|
}
|
2010-09-14 06:23:48 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
selection_model->clearSelection();
|
2010-06-30 05:55:46 +00:00
|
|
|
}
|
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::initOptionsTab()
|
2009-04-09 18:55:47 +00:00
|
|
|
{
|
2023-11-16 04:15:40 +00:00
|
|
|
auto const speed_unit_suffix = QStringLiteral(" %1").arg(Speed::display_name(Speed::Units::KByps));
|
|
|
|
ui_.singleDownSpin->setSuffix(speed_unit_suffix);
|
|
|
|
ui_.singleUpSpin->setSuffix(speed_unit_suffix);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2020-06-05 19:02:11 +00:00
|
|
|
ui_.singleDownSpin->setProperty(PrefKey, TR_KEY_downloadLimit);
|
|
|
|
ui_.singleUpSpin->setProperty(PrefKey, TR_KEY_uploadLimit);
|
|
|
|
ui_.ratioSpin->setProperty(PrefKey, TR_KEY_seedRatioLimit);
|
|
|
|
ui_.idleSpin->setProperty(PrefKey, TR_KEY_seedIdleLimit);
|
|
|
|
ui_.peerLimitSpin->setProperty(PrefKey, TR_KEY_peer_limit);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.bandwidthPriorityCombo->addItem(tr("High"), TR_PRI_HIGH);
|
|
|
|
ui_.bandwidthPriorityCombo->addItem(tr("Normal"), TR_PRI_NORMAL);
|
|
|
|
ui_.bandwidthPriorityCombo->addItem(tr("Low"), TR_PRI_LOW);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.ratioCombo->addItem(tr("Use Global Settings"), TR_RATIOLIMIT_GLOBAL);
|
|
|
|
ui_.ratioCombo->addItem(tr("Seed regardless of ratio"), TR_RATIOLIMIT_UNLIMITED);
|
|
|
|
ui_.ratioCombo->addItem(tr("Stop seeding at ratio:"), TR_RATIOLIMIT_SINGLE);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.idleCombo->addItem(tr("Use Global Settings"), TR_IDLELIMIT_GLOBAL);
|
|
|
|
ui_.idleCombo->addItem(tr("Seed regardless of activity"), TR_IDLELIMIT_UNLIMITED);
|
|
|
|
ui_.idleCombo->addItem(tr("Stop seeding if idle for:"), TR_IDLELIMIT_SINGLE);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2023-07-18 15:20:17 +00:00
|
|
|
auto* cr = new ColumnResizer{ this };
|
2020-05-27 21:53:12 +00:00
|
|
|
cr->addLayout(ui_.speedSectionLayout);
|
2024-01-10 21:01:03 +00:00
|
|
|
cr->addLayout(ui_.seedingLimitsSectionLayout);
|
2020-05-27 21:53:12 +00:00
|
|
|
cr->addLayout(ui_.peerConnectionsSectionLayout);
|
2017-04-19 12:04:45 +00:00
|
|
|
cr->update();
|
|
|
|
|
2024-10-21 01:37:06 +00:00
|
|
|
// clang-format off
|
|
|
|
void (QComboBox::* const combo_index_changed)(int) = &QComboBox::currentIndexChanged;
|
|
|
|
void (QSpinBox::* const spin_value_changed)(int) = &QSpinBox::valueChanged;
|
|
|
|
// clang-format on
|
2020-06-05 19:02:11 +00:00
|
|
|
connect(ui_.bandwidthPriorityCombo, combo_index_changed, this, &DetailsDialog::onBandwidthPriorityChanged);
|
|
|
|
connect(ui_.idleCombo, combo_index_changed, this, &DetailsDialog::onIdleModeChanged);
|
2020-05-27 21:53:12 +00:00
|
|
|
connect(ui_.idleSpin, &QSpinBox::editingFinished, this, &DetailsDialog::onSpinBoxEditingFinished);
|
2020-06-05 19:02:11 +00:00
|
|
|
connect(ui_.idleSpin, spin_value_changed, this, &DetailsDialog::onIdleLimitChanged);
|
2020-05-27 21:53:12 +00:00
|
|
|
connect(ui_.peerLimitSpin, &QSpinBox::editingFinished, this, &DetailsDialog::onSpinBoxEditingFinished);
|
2020-06-05 19:02:11 +00:00
|
|
|
connect(ui_.ratioCombo, combo_index_changed, this, &DetailsDialog::onRatioModeChanged);
|
2020-05-27 21:53:12 +00:00
|
|
|
connect(ui_.ratioSpin, &QSpinBox::editingFinished, this, &DetailsDialog::onSpinBoxEditingFinished);
|
|
|
|
connect(ui_.sessionLimitCheck, &QCheckBox::clicked, this, &DetailsDialog::onHonorsSessionLimitsToggled);
|
|
|
|
connect(ui_.singleDownCheck, &QCheckBox::clicked, this, &DetailsDialog::onDownloadLimitedToggled);
|
|
|
|
connect(ui_.singleDownSpin, &QSpinBox::editingFinished, this, &DetailsDialog::onSpinBoxEditingFinished);
|
|
|
|
connect(ui_.singleUpCheck, &QCheckBox::clicked, this, &DetailsDialog::onUploadLimitedToggled);
|
|
|
|
connect(ui_.singleUpSpin, &QSpinBox::editingFinished, this, &DetailsDialog::onSpinBoxEditingFinished);
|
2009-04-09 18:55:47 +00:00
|
|
|
}
|
|
|
|
|
2024-01-05 05:12:51 +00:00
|
|
|
// ---
|
2009-04-09 18:55:47 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::initTrackerTab()
|
2009-04-09 18:55:47 +00:00
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
auto deleter = [](QObject* o)
|
|
|
|
{
|
|
|
|
o->deleteLater();
|
|
|
|
};
|
2020-11-08 19:54:40 +00:00
|
|
|
|
|
|
|
// NOLINTNEXTLINE(modernize-make-shared) no custom deleters in make_shared
|
|
|
|
tracker_model_.reset(new TrackerModel, deleter);
|
|
|
|
// NOLINTNEXTLINE(modernize-make-shared) no custom deleters in make_shared
|
|
|
|
tracker_filter_.reset(new TrackerModelFilter, deleter);
|
|
|
|
tracker_filter_->setSourceModel(tracker_model_.get());
|
|
|
|
// NOLINTNEXTLINE(modernize-make-shared) no custom deleters in make_shared
|
|
|
|
tracker_delegate_.reset(new TrackerDelegate, deleter);
|
|
|
|
|
|
|
|
ui_.trackersView->setModel(tracker_filter_.get());
|
|
|
|
ui_.trackersView->setItemDelegate(tracker_delegate_.get());
|
2020-05-27 21:53:12 +00:00
|
|
|
|
2022-04-02 22:42:51 +00:00
|
|
|
auto const& icons = IconCache::get();
|
2021-12-09 08:13:04 +00:00
|
|
|
ui_.addTrackerButton->setIcon(icons.getThemeIcon(QStringLiteral("list-add"), QStyle::SP_DialogOpenButton));
|
2022-02-18 17:52:01 +00:00
|
|
|
ui_.editTrackersButton->setIcon(icons.getThemeIcon(QStringLiteral("document-properties"), QStyle::SP_DesktopIcon));
|
2021-12-09 08:13:04 +00:00
|
|
|
ui_.removeTrackerButton->setIcon(icons.getThemeIcon(QStringLiteral("list-remove"), QStyle::SP_TrashIcon));
|
2020-05-27 21:53:12 +00:00
|
|
|
|
|
|
|
ui_.showTrackerScrapesCheck->setChecked(prefs_.getBool(Prefs::SHOW_TRACKER_SCRAPES));
|
|
|
|
ui_.showBackupTrackersCheck->setChecked(prefs_.getBool(Prefs::SHOW_BACKUP_TRACKERS));
|
|
|
|
|
|
|
|
connect(ui_.addTrackerButton, &QAbstractButton::clicked, this, &DetailsDialog::onAddTrackerClicked);
|
2022-02-18 17:52:01 +00:00
|
|
|
connect(ui_.editTrackersButton, &QAbstractButton::clicked, this, &DetailsDialog::onEditTrackersClicked);
|
2020-05-27 21:53:12 +00:00
|
|
|
connect(ui_.removeTrackerButton, &QAbstractButton::clicked, this, &DetailsDialog::onRemoveTrackerClicked);
|
|
|
|
connect(ui_.showBackupTrackersCheck, &QAbstractButton::clicked, this, &DetailsDialog::onShowBackupTrackersToggled);
|
|
|
|
connect(ui_.showTrackerScrapesCheck, &QAbstractButton::clicked, this, &DetailsDialog::onShowTrackerScrapesToggled);
|
2019-11-09 14:44:40 +00:00
|
|
|
connect(
|
2021-08-15 09:41:48 +00:00
|
|
|
ui_.trackersView->selectionModel(),
|
|
|
|
&QItemSelectionModel::selectionChanged,
|
|
|
|
this,
|
2019-11-09 14:44:40 +00:00
|
|
|
&DetailsDialog::onTrackerSelectionChanged);
|
2017-04-19 12:04:45 +00:00
|
|
|
|
|
|
|
onTrackerSelectionChanged();
|
2009-04-09 18:55:47 +00:00
|
|
|
}
|
|
|
|
|
2024-01-05 05:12:51 +00:00
|
|
|
// ---
|
2009-04-09 18:55:47 +00:00
|
|
|
|
2017-04-19 12:04:45 +00:00
|
|
|
void DetailsDialog::initPeersTab()
|
2009-04-09 18:55:47 +00:00
|
|
|
{
|
2023-11-16 04:15:40 +00:00
|
|
|
auto const speed_width_str = Speed{ 1024U, Speed::Units::MByps }.to_qstring();
|
|
|
|
|
2023-07-18 15:20:17 +00:00
|
|
|
ui_.peersView->setHeaderLabels({ QString{}, tr("Up"), tr("Down"), tr("%"), tr("Status"), tr("Address"), tr("Client") });
|
2020-05-27 21:53:12 +00:00
|
|
|
ui_.peersView->sortByColumn(COL_ADDRESS, Qt::AscendingOrder);
|
|
|
|
|
|
|
|
ui_.peersView->setColumnWidth(COL_LOCK, 20);
|
2023-11-16 04:15:40 +00:00
|
|
|
ui_.peersView->setColumnWidth(COL_UP, measureViewItem(ui_.peersView, COL_UP, speed_width_str));
|
|
|
|
ui_.peersView->setColumnWidth(COL_DOWN, measureViewItem(ui_.peersView, COL_DOWN, speed_width_str));
|
2020-05-29 17:40:07 +00:00
|
|
|
ui_.peersView->setColumnWidth(COL_PERCENT, measureViewItem(ui_.peersView, COL_PERCENT, QStringLiteral("100%")));
|
|
|
|
ui_.peersView->setColumnWidth(COL_STATUS, measureViewItem(ui_.peersView, COL_STATUS, QStringLiteral("ODUK?EXI")));
|
|
|
|
ui_.peersView->setColumnWidth(COL_ADDRESS, measureViewItem(ui_.peersView, COL_ADDRESS, QStringLiteral("888.888.888.888")));
|
2009-04-09 18:55:47 +00:00
|
|
|
}
|
|
|
|
|
2024-01-05 05:12:51 +00:00
|
|
|
// ---
|
2009-04-09 18:55:47 +00:00
|
|
|
|
2020-11-09 03:31:02 +00:00
|
|
|
void DetailsDialog::initFilesTab() const
|
2009-04-09 18:55:47 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
connect(ui_.filesView, &FileTreeView::openRequested, this, &DetailsDialog::onOpenRequested);
|
|
|
|
connect(ui_.filesView, &FileTreeView::pathEdited, this, &DetailsDialog::onPathEdited);
|
|
|
|
connect(ui_.filesView, &FileTreeView::priorityChanged, this, &DetailsDialog::onFilePriorityChanged);
|
|
|
|
connect(ui_.filesView, &FileTreeView::wantedChanged, this, &DetailsDialog::onFileWantedChanged);
|
2009-04-09 18:55:47 +00:00
|
|
|
}
|
|
|
|
|
2023-10-25 01:14:37 +00:00
|
|
|
void DetailsDialog::onFilePriorityChanged(file_indices_t const& indices, int priority)
|
2022-02-08 03:56:04 +00:00
|
|
|
{
|
2023-10-25 01:14:37 +00:00
|
|
|
torrentSet(priorityKey(priority), std::vector<int>{ std::begin(indices), std::end(indices) });
|
2009-04-09 18:55:47 +00:00
|
|
|
}
|
|
|
|
|
2023-10-25 01:14:37 +00:00
|
|
|
void DetailsDialog::onFileWantedChanged(file_indices_t const& indices, bool wanted)
|
2009-04-09 18:55:47 +00:00
|
|
|
{
|
2017-04-20 16:02:19 +00:00
|
|
|
tr_quark const key = wanted ? TR_KEY_files_wanted : TR_KEY_files_unwanted;
|
2023-10-25 01:14:37 +00:00
|
|
|
torrentSet(key, std::vector<int>{ std::begin(indices), std::end(indices) });
|
2009-04-09 18:55:47 +00:00
|
|
|
}
|
2013-01-20 01:31:58 +00:00
|
|
|
|
2022-09-08 23:26:18 +00:00
|
|
|
void DetailsDialog::onPathEdited(QString const& old_path, QString const& new_name)
|
2013-01-20 01:31:58 +00:00
|
|
|
{
|
2022-09-08 23:26:18 +00:00
|
|
|
session_.torrentRenamePath(ids_, old_path, new_name);
|
2013-01-20 01:31:58 +00:00
|
|
|
}
|
2013-09-08 19:03:25 +00:00
|
|
|
|
2020-11-09 03:31:02 +00:00
|
|
|
void DetailsDialog::onOpenRequested(QString const& path) const
|
2013-09-08 19:03:25 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
if (!session_.isLocal())
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2013-09-08 19:03:25 +00:00
|
|
|
|
2020-05-27 21:53:12 +00:00
|
|
|
for (int const id : ids_)
|
2013-09-08 19:03:25 +00:00
|
|
|
{
|
2020-05-27 21:53:12 +00:00
|
|
|
Torrent const* const tor = model_.getTorrentFromId(id);
|
2013-09-08 19:03:25 +00:00
|
|
|
|
2017-04-30 09:29:58 +00:00
|
|
|
if (tor == nullptr)
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2013-09-08 19:03:25 +00:00
|
|
|
|
2023-11-21 15:02:03 +00:00
|
|
|
auto const local_file_path = tor->getPath() + QLatin1Char('/') + path;
|
2017-04-19 12:04:45 +00:00
|
|
|
|
2020-06-05 19:02:11 +00:00
|
|
|
if (!QFile::exists(local_file_path))
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-06-05 19:02:11 +00:00
|
|
|
if (QDesktopServices::openUrl(QUrl::fromLocalFile(local_file_path)))
|
2017-04-19 12:04:45 +00:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
2013-09-08 19:03:25 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-18 17:52:01 +00:00
|
|
|
|
|
|
|
#include "DetailsDialog.moc"
|