Enable customizing the save statistics time interval
[qBittorrent.git] / src / gui / properties / propertieswidget.cpp
blobc00feabecbc437b7703418c07fafae4b2638aee9
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "propertieswidget.h"
32 #include <QClipboard>
33 #include <QDateTime>
34 #include <QDebug>
35 #include <QListWidgetItem>
36 #include <QMenu>
37 #include <QMessageBox>
38 #include <QPointer>
39 #include <QSplitter>
40 #include <QShortcut>
41 #include <QStackedWidget>
42 #include <QThread>
43 #include <QUrl>
45 #include "base/bittorrent/infohash.h"
46 #include "base/bittorrent/session.h"
47 #include "base/bittorrent/torrent.h"
48 #include "base/path.h"
49 #include "base/preferences.h"
50 #include "base/types.h"
51 #include "base/unicodestrings.h"
52 #include "base/utils/misc.h"
53 #include "base/utils/string.h"
54 #include "gui/autoexpandabledialog.h"
55 #include "gui/filterpatternformatmenu.h"
56 #include "gui/lineedit.h"
57 #include "gui/trackerlist/trackerlistwidget.h"
58 #include "gui/uithememanager.h"
59 #include "gui/utils.h"
60 #include "downloadedpiecesbar.h"
61 #include "peerlistwidget.h"
62 #include "pieceavailabilitybar.h"
63 #include "proptabbar.h"
64 #include "speedwidget.h"
65 #include "ui_propertieswidget.h"
67 PropertiesWidget::PropertiesWidget(QWidget *parent)
68 : QWidget(parent)
69 , m_ui {new Ui::PropertiesWidget}
70 , m_storeFilterPatternFormat {u"GUI/PropertiesWidget/FilterPatternFormat"_s}
72 m_ui->setupUi(this);
73 #ifndef Q_OS_MACOS
74 setAutoFillBackground(true);
75 #endif
77 m_state = VISIBLE;
79 // Torrent content filtering
80 m_contentFilterLine = new LineEdit(this);
81 m_contentFilterLine->setPlaceholderText(tr("Filter files..."));
82 m_contentFilterLine->setFixedWidth(300);
83 m_contentFilterLine->setContextMenuPolicy(Qt::CustomContextMenu);
84 connect(m_contentFilterLine, &QWidget::customContextMenuRequested, this, &PropertiesWidget::showContentFilterContextMenu);
85 connect(m_contentFilterLine, &LineEdit::textChanged, this, &PropertiesWidget::setContentFilterPattern);
86 m_ui->contentFilterLayout->insertWidget(3, m_contentFilterLine);
88 m_ui->filesList->setDoubleClickAction(TorrentContentWidget::DoubleClickAction::Open);
89 m_ui->filesList->setOpenByEnterKey(true);
91 // SIGNAL/SLOTS
92 connect(m_ui->selectAllButton, &QPushButton::clicked, m_ui->filesList, &TorrentContentWidget::checkAll);
93 connect(m_ui->selectNoneButton, &QPushButton::clicked, m_ui->filesList, &TorrentContentWidget::checkNone);
94 connect(m_ui->listWebSeeds, &QWidget::customContextMenuRequested, this, &PropertiesWidget::displayWebSeedListMenu);
95 connect(m_ui->stackedProperties, &QStackedWidget::currentChanged, this, &PropertiesWidget::loadDynamicData);
96 connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentSavePathChanged, this, &PropertiesWidget::updateSavePath);
97 connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentMetadataReceived, this, &PropertiesWidget::updateTorrentInfos);
98 connect(m_ui->filesList, &TorrentContentWidget::stateChanged, this, &PropertiesWidget::saveSettings);
100 // set bar height relative to screen dpi
101 const int barHeight = 18;
103 // Downloaded pieces progress bar
104 m_ui->tempProgressBarArea->setVisible(false);
105 m_downloadedPieces = new DownloadedPiecesBar(this);
106 m_ui->groupBarLayout->addWidget(m_downloadedPieces, 0, 1);
107 m_downloadedPieces->setFixedHeight(barHeight);
108 m_downloadedPieces->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
110 // Pieces availability bar
111 m_ui->tempAvailabilityBarArea->setVisible(false);
112 m_piecesAvailability = new PieceAvailabilityBar(this);
113 m_ui->groupBarLayout->addWidget(m_piecesAvailability, 1, 1);
114 m_piecesAvailability->setFixedHeight(barHeight);
115 m_piecesAvailability->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
117 // Tracker list
118 m_trackerList = new TrackerListWidget(this);
119 m_ui->trackerUpButton->setIcon(UIThemeManager::instance()->getIcon(u"go-up"_s));
120 m_ui->trackerUpButton->setIconSize(Utils::Gui::smallIconSize());
121 m_ui->trackerDownButton->setIcon(UIThemeManager::instance()->getIcon(u"go-down"_s));
122 m_ui->trackerDownButton->setIconSize(Utils::Gui::smallIconSize());
123 connect(m_ui->trackerUpButton, &QPushButton::clicked, m_trackerList, &TrackerListWidget::decreaseSelectedTrackerTiers);
124 connect(m_ui->trackerDownButton, &QPushButton::clicked, m_trackerList, &TrackerListWidget::increaseSelectedTrackerTiers);
125 m_ui->hBoxLayoutTrackers->insertWidget(0, m_trackerList);
126 // Peers list
127 m_peerList = new PeerListWidget(this);
128 m_ui->vBoxLayoutPeerPage->addWidget(m_peerList);
129 // Tab bar
130 m_tabBar = new PropTabBar(nullptr);
131 m_tabBar->setContentsMargins(0, 5, 0, 5);
132 m_ui->verticalLayout->addLayout(m_tabBar);
133 connect(m_tabBar, &PropTabBar::tabChanged, m_ui->stackedProperties, &QStackedWidget::setCurrentIndex);
134 connect(m_tabBar, &PropTabBar::tabChanged, this, &PropertiesWidget::saveSettings);
135 connect(m_tabBar, &PropTabBar::visibilityToggled, this, &PropertiesWidget::setVisibility);
136 connect(m_tabBar, &PropTabBar::visibilityToggled, this, &PropertiesWidget::saveSettings);
138 const auto *editWebSeedsHotkey = new QShortcut(Qt::Key_F2, m_ui->listWebSeeds, nullptr, nullptr, Qt::WidgetShortcut);
139 connect(editWebSeedsHotkey, &QShortcut::activated, this, &PropertiesWidget::editWebSeed);
140 const auto *deleteWebSeedsHotkey = new QShortcut(QKeySequence::Delete, m_ui->listWebSeeds, nullptr, nullptr, Qt::WidgetShortcut);
141 connect(deleteWebSeedsHotkey, &QShortcut::activated, this, &PropertiesWidget::deleteSelectedUrlSeeds);
142 connect(m_ui->listWebSeeds, &QListWidget::doubleClicked, this, &PropertiesWidget::editWebSeed);
144 configure();
145 connect(Preferences::instance(), &Preferences::changed, this, &PropertiesWidget::configure);
148 PropertiesWidget::~PropertiesWidget()
150 delete m_tabBar;
151 delete m_ui;
154 void PropertiesWidget::showPiecesAvailability(bool show)
156 m_ui->labelPiecesAvailability->setVisible(show);
157 m_piecesAvailability->setVisible(show);
158 m_ui->labelAverageAvailabilityVal->setVisible(show);
159 if (show || !m_downloadedPieces->isVisible())
160 m_ui->lineBelowBars->setVisible(show);
163 void PropertiesWidget::showPiecesDownloaded(bool show)
165 m_ui->labelDownloadedPieces->setVisible(show);
166 m_downloadedPieces->setVisible(show);
167 m_ui->labelProgressVal->setVisible(show);
168 if (show || !m_piecesAvailability->isVisible())
169 m_ui->lineBelowBars->setVisible(show);
172 void PropertiesWidget::setVisibility(const bool visible)
174 if (!visible && (m_state == VISIBLE))
176 const int tabBarHeight = m_tabBar->geometry().height(); // take height before hiding
177 auto *hSplitter = static_cast<QSplitter *>(parentWidget());
178 m_ui->stackedProperties->setVisible(false);
179 m_slideSizes = hSplitter->sizes();
180 hSplitter->handle(1)->setVisible(false);
181 hSplitter->handle(1)->setDisabled(true);
182 m_handleWidth = hSplitter->handleWidth();
183 hSplitter->setHandleWidth(0);
184 const QList<int> sizes {(hSplitter->geometry().height() - tabBarHeight), tabBarHeight};
185 hSplitter->setSizes(sizes);
186 setMaximumSize(maximumSize().width(), tabBarHeight);
187 m_state = REDUCED;
188 return;
191 if (visible && (m_state == REDUCED))
193 m_ui->stackedProperties->setVisible(true);
194 auto *hSplitter = static_cast<QSplitter *>(parentWidget());
195 if (m_handleWidth != -1)
196 hSplitter->setHandleWidth(m_handleWidth);
197 hSplitter->handle(1)->setDisabled(false);
198 hSplitter->handle(1)->setVisible(true);
199 hSplitter->setSizes(m_slideSizes);
200 m_state = VISIBLE;
201 setMaximumSize(maximumSize().width(), QWIDGETSIZE_MAX);
202 // Force refresh
203 loadDynamicData();
207 void PropertiesWidget::clear()
209 qDebug("Clearing torrent properties");
210 m_ui->labelSavePathVal->clear();
211 m_ui->labelCreatedOnVal->clear();
212 m_ui->labelTotalPiecesVal->clear();
213 m_ui->labelPrivateVal->clear();
214 m_ui->labelInfohash1Val->clear();
215 m_ui->labelInfohash2Val->clear();
216 m_ui->labelCommentVal->clear();
217 m_ui->labelProgressVal->clear();
218 m_ui->labelAverageAvailabilityVal->clear();
219 m_ui->labelWastedVal->clear();
220 m_ui->labelUpTotalVal->clear();
221 m_ui->labelDlTotalVal->clear();
222 m_ui->labelUpLimitVal->clear();
223 m_ui->labelDlLimitVal->clear();
224 m_ui->labelElapsedVal->clear();
225 m_ui->labelConnectionsVal->clear();
226 m_ui->labelReannounceInVal->clear();
227 m_ui->labelShareRatioVal->clear();
228 m_ui->labelPopularityVal->clear();
229 m_ui->listWebSeeds->clear();
230 m_ui->labelETAVal->clear();
231 m_ui->labelSeedsVal->clear();
232 m_ui->labelPeersVal->clear();
233 m_ui->labelDlSpeedVal->clear();
234 m_ui->labelUpSpeedVal->clear();
235 m_ui->labelTotalSizeVal->clear();
236 m_ui->labelCompletedOnVal->clear();
237 m_ui->labelLastSeenCompleteVal->clear();
238 m_ui->labelCreatedByVal->clear();
239 m_ui->labelAddedOnVal->clear();
240 m_downloadedPieces->clear();
241 m_piecesAvailability->clear();
242 m_peerList->clear();
243 m_contentFilterLine->clear();
246 BitTorrent::Torrent *PropertiesWidget::getCurrentTorrent() const
248 return m_torrent;
251 TrackerListWidget *PropertiesWidget::getTrackerList() const
253 return m_trackerList;
256 PeerListWidget *PropertiesWidget::getPeerList() const
258 return m_peerList;
261 QTreeView *PropertiesWidget::getFilesList() const
263 return m_ui->filesList;
266 PropTabBar *PropertiesWidget::tabBar() const
268 return m_tabBar;
271 LineEdit *PropertiesWidget::contentFilterLine() const
273 return m_contentFilterLine;
276 void PropertiesWidget::updateSavePath(BitTorrent::Torrent *const torrent)
278 if (torrent == m_torrent)
279 m_ui->labelSavePathVal->setText(m_torrent->savePath().toString());
282 void PropertiesWidget::showContentFilterContextMenu()
284 QMenu *menu = m_contentFilterLine->createStandardContextMenu();
286 auto *formatMenu = new FilterPatternFormatMenu(m_storeFilterPatternFormat.get(FilterPatternFormat::Wildcards), menu);
287 connect(formatMenu, &FilterPatternFormatMenu::patternFormatChanged, this, [this](const FilterPatternFormat format)
289 m_storeFilterPatternFormat = format;
290 setContentFilterPattern();
293 menu->addSeparator();
294 menu->addMenu(formatMenu);
295 menu->setAttribute(Qt::WA_DeleteOnClose);
296 menu->popup(QCursor::pos());
299 void PropertiesWidget::setContentFilterPattern()
301 m_ui->filesList->setFilterPattern(m_contentFilterLine->text(), m_storeFilterPatternFormat.get(FilterPatternFormat::Wildcards));
304 void PropertiesWidget::updateTorrentInfos(BitTorrent::Torrent *const torrent)
306 if (torrent == m_torrent)
307 loadTorrentInfos(m_torrent);
310 void PropertiesWidget::loadTorrentInfos(BitTorrent::Torrent *const torrent)
312 clear();
313 m_torrent = torrent;
314 m_downloadedPieces->setTorrent(m_torrent);
315 m_piecesAvailability->setTorrent(m_torrent);
316 m_trackerList->setTorrent(m_torrent);
317 m_ui->filesList->setContentHandler(m_torrent);
318 if (!m_torrent)
319 return;
321 // Save path
322 updateSavePath(m_torrent);
323 // Info hashes
324 m_ui->labelInfohash1Val->setText(m_torrent->infoHash().v1().isValid() ? m_torrent->infoHash().v1().toString() : tr("N/A"));
325 m_ui->labelInfohash2Val->setText(m_torrent->infoHash().v2().isValid() ? m_torrent->infoHash().v2().toString() : tr("N/A"));
326 // URL seeds
327 loadUrlSeeds();
328 if (m_torrent->hasMetadata())
330 // Creation date
331 m_ui->labelCreatedOnVal->setText(QLocale().toString(m_torrent->creationDate(), QLocale::ShortFormat));
333 m_ui->labelTotalSizeVal->setText(Utils::Misc::friendlyUnit(m_torrent->totalSize()));
335 // Comment
336 m_ui->labelCommentVal->setText(Utils::Misc::parseHtmlLinks(m_torrent->comment().toHtmlEscaped()));
338 m_ui->labelCreatedByVal->setText(m_torrent->creator());
340 m_ui->labelPrivateVal->setText(m_torrent->isPrivate() ? tr("Yes") : tr("No"));
342 else
344 m_ui->labelPrivateVal->setText(tr("N/A"));
347 // Load dynamic data
348 loadDynamicData();
351 void PropertiesWidget::readSettings()
353 const Preferences *const pref = Preferences::instance();
354 // Restore splitter sizes
355 QStringList sizesStr = pref->getPropSplitterSizes().split(u',');
356 if (sizesStr.size() == 2)
358 m_slideSizes << sizesStr.first().toInt();
359 m_slideSizes << sizesStr.last().toInt();
360 auto *hSplitter = static_cast<QSplitter *>(parentWidget());
361 hSplitter->setSizes(m_slideSizes);
363 const int currentTab = pref->getPropCurTab();
364 const bool visible = pref->getPropVisible();
365 m_ui->filesList->header()->restoreState(pref->getPropFileListState());
366 m_tabBar->setCurrentIndex(currentTab);
367 if (!visible)
368 setVisibility(false);
371 void PropertiesWidget::saveSettings()
373 Preferences *const pref = Preferences::instance();
374 pref->setPropVisible(m_state == VISIBLE);
375 // Splitter sizes
376 auto *hSplitter = static_cast<QSplitter *>(parentWidget());
377 QList<int> sizes;
378 if (m_state == VISIBLE)
379 sizes = hSplitter->sizes();
380 else
381 sizes = m_slideSizes;
383 if (sizes.size() == 2)
384 pref->setPropSplitterSizes(QString::number(sizes.first()) + u',' + QString::number(sizes.last()));
385 pref->setPropFileListState(m_ui->filesList->header()->saveState());
386 // Remember current tab
387 pref->setPropCurTab(m_tabBar->currentIndex());
390 void PropertiesWidget::reloadPreferences()
392 // Take program preferences into consideration
393 m_peerList->updatePeerHostNameResolutionState();
394 m_peerList->updatePeerCountryResolutionState();
397 void PropertiesWidget::loadDynamicData()
399 // Refresh only if the torrent handle is valid and visible
400 if (!m_torrent || (m_state != VISIBLE)) return;
402 // Transfer infos
403 switch (m_ui->stackedProperties->currentIndex())
405 case PropTabBar::MainTab:
407 m_ui->labelWastedVal->setText(Utils::Misc::friendlyUnit(m_torrent->wastedSize()));
409 m_ui->labelUpTotalVal->setText(tr("%1 (%2 this session)").arg(Utils::Misc::friendlyUnit(m_torrent->totalUpload())
410 , Utils::Misc::friendlyUnit(m_torrent->totalPayloadUpload())));
412 m_ui->labelDlTotalVal->setText(tr("%1 (%2 this session)").arg(Utils::Misc::friendlyUnit(m_torrent->totalDownload())
413 , Utils::Misc::friendlyUnit(m_torrent->totalPayloadDownload())));
415 m_ui->labelUpLimitVal->setText(m_torrent->uploadLimit() <= 0 ? C_INFINITY : Utils::Misc::friendlyUnit(m_torrent->uploadLimit(), true));
417 m_ui->labelDlLimitVal->setText(m_torrent->downloadLimit() <= 0 ? C_INFINITY : Utils::Misc::friendlyUnit(m_torrent->downloadLimit(), true));
419 QString elapsedString;
420 if (m_torrent->isFinished())
422 elapsedString = tr("%1 (seeded for %2)", "e.g. 4m39s (seeded for 3m10s)")
423 .arg(Utils::Misc::userFriendlyDuration(m_torrent->activeTime())
424 , Utils::Misc::userFriendlyDuration(m_torrent->finishedTime()));
426 else
428 elapsedString = Utils::Misc::userFriendlyDuration(m_torrent->activeTime());
430 m_ui->labelElapsedVal->setText(elapsedString);
432 m_ui->labelConnectionsVal->setText(tr("%1 (%2 max)", "%1 and %2 are numbers, e.g. 3 (10 max)")
433 .arg(m_torrent->connectionsCount())
434 .arg(m_torrent->connectionsLimit() < 0 ? C_INFINITY : QString::number(m_torrent->connectionsLimit())));
436 m_ui->labelETAVal->setText(Utils::Misc::userFriendlyDuration(m_torrent->eta(), MAX_ETA));
438 // Update next announce time
439 m_ui->labelReannounceInVal->setText(Utils::Misc::userFriendlyDuration(m_torrent->nextAnnounce()));
441 // Update ratio info
442 const qreal ratio = m_torrent->realRatio();
443 m_ui->labelShareRatioVal->setText(ratio > BitTorrent::Torrent::MAX_RATIO ? C_INFINITY : Utils::String::fromDouble(ratio, 2));
445 const qreal popularity = m_torrent->popularity();
446 m_ui->labelPopularityVal->setText(popularity > BitTorrent::Torrent::MAX_RATIO ? C_INFINITY : Utils::String::fromDouble(popularity, 2));
448 m_ui->labelSeedsVal->setText(tr("%1 (%2 total)", "%1 and %2 are numbers, e.g. 3 (10 total)")
449 .arg(QString::number(m_torrent->seedsCount())
450 , QString::number(m_torrent->totalSeedsCount())));
452 m_ui->labelPeersVal->setText(tr("%1 (%2 total)", "%1 and %2 are numbers, e.g. 3 (10 total)")
453 .arg(QString::number(m_torrent->leechsCount())
454 , QString::number(m_torrent->totalLeechersCount())));
456 const qlonglong dlDuration = m_torrent->activeTime() - m_torrent->finishedTime();
457 const QString dlAvg = Utils::Misc::friendlyUnit((m_torrent->totalDownload() / ((dlDuration == 0) ? -1 : dlDuration)), true);
458 m_ui->labelDlSpeedVal->setText(tr("%1 (%2 avg.)", "%1 and %2 are speed rates, e.g. 200KiB/s (100KiB/s avg.)")
459 .arg(Utils::Misc::friendlyUnit(m_torrent->downloadPayloadRate(), true), dlAvg));
461 const qlonglong ulDuration = m_torrent->activeTime();
462 const QString ulAvg = Utils::Misc::friendlyUnit((m_torrent->totalUpload() / ((ulDuration == 0) ? -1 : ulDuration)), true);
463 m_ui->labelUpSpeedVal->setText(tr("%1 (%2 avg.)", "%1 and %2 are speed rates, e.g. 200KiB/s (100KiB/s avg.)")
464 .arg(Utils::Misc::friendlyUnit(m_torrent->uploadPayloadRate(), true), ulAvg));
466 m_ui->labelLastSeenCompleteVal->setText(m_torrent->lastSeenComplete().isValid() ? QLocale().toString(m_torrent->lastSeenComplete(), QLocale::ShortFormat) : tr("Never"));
468 m_ui->labelCompletedOnVal->setText(m_torrent->completedTime().isValid() ? QLocale().toString(m_torrent->completedTime(), QLocale::ShortFormat) : QString {});
470 m_ui->labelAddedOnVal->setText(QLocale().toString(m_torrent->addedTime(), QLocale::ShortFormat));
472 if (m_torrent->hasMetadata())
474 using TorrentPtr = QPointer<BitTorrent::Torrent>;
476 m_ui->labelTotalPiecesVal->setText(tr("%1 x %2 (have %3)", "(torrent pieces) eg 152 x 4MB (have 25)").arg(m_torrent->piecesCount()).arg(Utils::Misc::friendlyUnit(m_torrent->pieceLength())).arg(m_torrent->piecesHave()));
478 if (!m_torrent->isFinished() && !m_torrent->isStopped() && !m_torrent->isQueued() && !m_torrent->isChecking())
480 // Pieces availability
481 showPiecesAvailability(true);
482 m_torrent->fetchPieceAvailability([this, torrent = TorrentPtr(m_torrent)](const QList<int> &pieceAvailability)
484 if (torrent == m_torrent)
485 m_piecesAvailability->setAvailability(pieceAvailability);
488 m_ui->labelAverageAvailabilityVal->setText(Utils::String::fromDouble(m_torrent->distributedCopies(), 3));
490 else
492 showPiecesAvailability(false);
495 // Progress
496 qreal progress = m_torrent->progress() * 100.;
497 m_ui->labelProgressVal->setText(Utils::String::fromDouble(progress, 1) + u'%');
499 m_torrent->fetchDownloadingPieces([this, torrent = TorrentPtr(m_torrent)](const QBitArray &downloadingPieces)
501 if (torrent == m_torrent)
502 m_downloadedPieces->setProgress(m_torrent->pieces(), downloadingPieces);
505 else
507 showPiecesAvailability(false);
510 break;
511 case PropTabBar::PeersTab:
512 // Load peers
513 m_peerList->loadPeers(m_torrent);
514 break;
515 case PropTabBar::FilesTab:
516 m_ui->filesList->refresh();
517 break;
518 default:;
522 void PropertiesWidget::loadUrlSeeds()
524 if (!m_torrent)
525 return;
527 using TorrentPtr = QPointer<BitTorrent::Torrent>;
528 m_torrent->fetchURLSeeds([this, torrent = TorrentPtr(m_torrent)](const QList<QUrl> &urlSeeds)
530 if (torrent != m_torrent)
531 return;
533 m_ui->listWebSeeds->clear();
534 qDebug("Loading URL seeds");
535 // Add url seeds
536 for (const QUrl &urlSeed : urlSeeds)
538 qDebug("Loading URL seed: %s", qUtf8Printable(urlSeed.toString()));
539 new QListWidgetItem(urlSeed.toString(), m_ui->listWebSeeds);
544 void PropertiesWidget::displayWebSeedListMenu()
546 if (!m_torrent) return;
548 const QModelIndexList rows = m_ui->listWebSeeds->selectionModel()->selectedRows();
550 QMenu *menu = new QMenu(this);
551 menu->setAttribute(Qt::WA_DeleteOnClose);
553 menu->addAction(UIThemeManager::instance()->getIcon(u"list-add"_s), tr("New Web seed"), this, &PropertiesWidget::askWebSeed);
555 if (!rows.isEmpty())
557 menu->addAction(UIThemeManager::instance()->getIcon(u"edit-clear"_s, u"list-remove"_s), tr("Remove Web seed")
558 , this, &PropertiesWidget::deleteSelectedUrlSeeds);
559 menu->addSeparator();
560 menu->addAction(UIThemeManager::instance()->getIcon(u"edit-copy"_s), tr("Copy Web seed URL")
561 , this, &PropertiesWidget::copySelectedWebSeedsToClipboard);
562 menu->addAction(UIThemeManager::instance()->getIcon(u"edit-rename"_s), tr("Edit Web seed URL")
563 , this, &PropertiesWidget::editWebSeed);
566 menu->popup(QCursor::pos());
569 void PropertiesWidget::configure()
571 // Speed widget
572 if (Preferences::instance()->isSpeedWidgetEnabled())
574 if (!qobject_cast<SpeedWidget *>(m_speedWidget))
576 if (m_speedWidget)
578 m_ui->speedLayout->removeWidget(m_speedWidget);
579 delete m_speedWidget;
582 m_speedWidget = new SpeedWidget(this);
583 m_ui->speedLayout->addWidget(m_speedWidget);
586 else
588 if (!qobject_cast<QLabel *>(m_speedWidget))
590 if (m_speedWidget)
592 m_ui->speedLayout->removeWidget(m_speedWidget);
593 delete m_speedWidget;
596 const auto displayText = u"<center><b>%1</b><p>%2</p></center>"_s
597 .arg(tr("Speed graphs are disabled"), tr("You can enable it in Advanced Options"));
598 auto *label = new QLabel(displayText, this);
599 label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
600 m_speedWidget = label;
601 m_ui->speedLayout->addWidget(m_speedWidget);
606 void PropertiesWidget::askWebSeed()
608 bool ok = false;
609 // Ask user for a new url seed
610 const QString urlSeed = AutoExpandableDialog::getText(this, tr("New URL seed", "New HTTP source"),
611 tr("New URL seed:"), QLineEdit::Normal,
612 u"http://www."_s, &ok);
613 if (!ok) return;
614 qDebug("Adding %s web seed", qUtf8Printable(urlSeed));
615 if (!m_ui->listWebSeeds->findItems(urlSeed, Qt::MatchFixedString).empty())
617 QMessageBox::warning(this, u"qBittorrent"_s, tr("This URL seed is already in the list."), QMessageBox::Ok);
618 return;
620 if (m_torrent)
621 m_torrent->addUrlSeeds({urlSeed});
622 // Refresh the seeds list
623 loadUrlSeeds();
626 void PropertiesWidget::deleteSelectedUrlSeeds()
628 const QList<QListWidgetItem *> selectedItems = m_ui->listWebSeeds->selectedItems();
629 if (selectedItems.isEmpty()) return;
631 QList<QUrl> urlSeeds;
632 urlSeeds.reserve(selectedItems.size());
634 for (const QListWidgetItem *item : selectedItems)
635 urlSeeds << item->text();
637 m_torrent->removeUrlSeeds(urlSeeds);
638 // Refresh list
639 loadUrlSeeds();
642 void PropertiesWidget::copySelectedWebSeedsToClipboard() const
644 const QList<QListWidgetItem *> selectedItems = m_ui->listWebSeeds->selectedItems();
645 if (selectedItems.isEmpty()) return;
647 QStringList urlsToCopy;
648 for (const QListWidgetItem *item : selectedItems)
649 urlsToCopy << item->text();
651 QApplication::clipboard()->setText(urlsToCopy.join(u'\n'));
654 void PropertiesWidget::editWebSeed()
656 const QList<QListWidgetItem *> selectedItems = m_ui->listWebSeeds->selectedItems();
657 if (selectedItems.size() != 1) return;
659 const QListWidgetItem *selectedItem = selectedItems.last();
660 const QString oldSeed = selectedItem->text();
661 bool result;
662 const QString newSeed = AutoExpandableDialog::getText(this, tr("Web seed editing"),
663 tr("Web seed URL:"), QLineEdit::Normal,
664 oldSeed, &result);
665 if (!result) return;
667 if (!m_ui->listWebSeeds->findItems(newSeed, Qt::MatchFixedString).empty())
669 QMessageBox::warning(this, u"qBittorrent"_s,
670 tr("This URL seed is already in the list."),
671 QMessageBox::Ok);
672 return;
675 m_torrent->removeUrlSeeds({oldSeed});
676 m_torrent->addUrlSeeds({newSeed});
677 loadUrlSeeds();