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"
35 #include <QListWidgetItem>
37 #include <QMessageBox>
41 #include <QStackedWidget>
44 #include "base/bittorrent/infohash.h"
45 #include "base/bittorrent/session.h"
46 #include "base/bittorrent/torrent.h"
47 #include "base/path.h"
48 #include "base/preferences.h"
49 #include "base/types.h"
50 #include "base/unicodestrings.h"
51 #include "base/utils/misc.h"
52 #include "base/utils/string.h"
53 #include "gui/autoexpandabledialog.h"
54 #include "gui/filterpatternformatmenu.h"
55 #include "gui/lineedit.h"
56 #include "gui/trackerlist/trackerlistwidget.h"
57 #include "gui/uithememanager.h"
58 #include "gui/utils.h"
59 #include "downloadedpiecesbar.h"
60 #include "peerlistwidget.h"
61 #include "pieceavailabilitybar.h"
62 #include "proptabbar.h"
63 #include "speedwidget.h"
64 #include "ui_propertieswidget.h"
66 PropertiesWidget::PropertiesWidget(QWidget
*parent
)
68 , m_ui
{new Ui::PropertiesWidget
}
69 , m_storeFilterPatternFormat
{u
"GUI/PropertiesWidget/FilterPatternFormat"_s
}
73 setAutoFillBackground(true);
78 // Torrent content filtering
79 m_contentFilterLine
= new LineEdit(this);
80 m_contentFilterLine
->setPlaceholderText(tr("Filter files..."));
81 m_contentFilterLine
->setFixedWidth(300);
82 m_contentFilterLine
->setContextMenuPolicy(Qt::CustomContextMenu
);
83 connect(m_contentFilterLine
, &QWidget::customContextMenuRequested
, this, &PropertiesWidget::showContentFilterContextMenu
);
84 connect(m_contentFilterLine
, &LineEdit::textChanged
, this, &PropertiesWidget::setContentFilterPattern
);
85 m_ui
->contentFilterLayout
->insertWidget(3, m_contentFilterLine
);
87 m_ui
->filesList
->setDoubleClickAction(TorrentContentWidget::DoubleClickAction::Open
);
88 m_ui
->filesList
->setOpenByEnterKey(true);
91 connect(m_ui
->selectAllButton
, &QPushButton::clicked
, m_ui
->filesList
, &TorrentContentWidget::checkAll
);
92 connect(m_ui
->selectNoneButton
, &QPushButton::clicked
, m_ui
->filesList
, &TorrentContentWidget::checkNone
);
93 connect(m_ui
->listWebSeeds
, &QWidget::customContextMenuRequested
, this, &PropertiesWidget::displayWebSeedListMenu
);
94 connect(m_ui
->stackedProperties
, &QStackedWidget::currentChanged
, this, &PropertiesWidget::loadDynamicData
);
95 connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentSavePathChanged
, this, &PropertiesWidget::updateSavePath
);
96 connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentMetadataReceived
, this, &PropertiesWidget::updateTorrentInfos
);
97 connect(m_ui
->filesList
, &TorrentContentWidget::stateChanged
, this, &PropertiesWidget::saveSettings
);
99 // set bar height relative to screen dpi
100 const int barHeight
= 18;
102 // Downloaded pieces progress bar
103 m_ui
->tempProgressBarArea
->setVisible(false);
104 m_downloadedPieces
= new DownloadedPiecesBar(this);
105 m_ui
->groupBarLayout
->addWidget(m_downloadedPieces
, 0, 1);
106 m_downloadedPieces
->setFixedHeight(barHeight
);
107 m_downloadedPieces
->setSizePolicy(QSizePolicy::Expanding
, QSizePolicy::Fixed
);
109 // Pieces availability bar
110 m_ui
->tempAvailabilityBarArea
->setVisible(false);
111 m_piecesAvailability
= new PieceAvailabilityBar(this);
112 m_ui
->groupBarLayout
->addWidget(m_piecesAvailability
, 1, 1);
113 m_piecesAvailability
->setFixedHeight(barHeight
);
114 m_piecesAvailability
->setSizePolicy(QSizePolicy::Expanding
, QSizePolicy::Fixed
);
117 m_trackerList
= new TrackerListWidget(this);
118 m_ui
->trackerUpButton
->setIcon(UIThemeManager::instance()->getIcon(u
"go-up"_s
));
119 m_ui
->trackerUpButton
->setIconSize(Utils::Gui::smallIconSize());
120 m_ui
->trackerDownButton
->setIcon(UIThemeManager::instance()->getIcon(u
"go-down"_s
));
121 m_ui
->trackerDownButton
->setIconSize(Utils::Gui::smallIconSize());
122 connect(m_ui
->trackerUpButton
, &QPushButton::clicked
, m_trackerList
, &TrackerListWidget::decreaseSelectedTrackerTiers
);
123 connect(m_ui
->trackerDownButton
, &QPushButton::clicked
, m_trackerList
, &TrackerListWidget::increaseSelectedTrackerTiers
);
124 m_ui
->hBoxLayoutTrackers
->insertWidget(0, m_trackerList
);
126 m_peerList
= new PeerListWidget(this);
127 m_ui
->vBoxLayoutPeerPage
->addWidget(m_peerList
);
129 m_tabBar
= new PropTabBar(nullptr);
130 m_tabBar
->setContentsMargins(0, 5, 0, 5);
131 m_ui
->verticalLayout
->addLayout(m_tabBar
);
132 connect(m_tabBar
, &PropTabBar::tabChanged
, m_ui
->stackedProperties
, &QStackedWidget::setCurrentIndex
);
133 connect(m_tabBar
, &PropTabBar::tabChanged
, this, &PropertiesWidget::saveSettings
);
134 connect(m_tabBar
, &PropTabBar::visibilityToggled
, this, &PropertiesWidget::setVisibility
);
135 connect(m_tabBar
, &PropTabBar::visibilityToggled
, this, &PropertiesWidget::saveSettings
);
137 const auto *editWebSeedsHotkey
= new QShortcut(Qt::Key_F2
, m_ui
->listWebSeeds
, nullptr, nullptr, Qt::WidgetShortcut
);
138 connect(editWebSeedsHotkey
, &QShortcut::activated
, this, &PropertiesWidget::editWebSeed
);
139 const auto *deleteWebSeedsHotkey
= new QShortcut(QKeySequence::Delete
, m_ui
->listWebSeeds
, nullptr, nullptr, Qt::WidgetShortcut
);
140 connect(deleteWebSeedsHotkey
, &QShortcut::activated
, this, &PropertiesWidget::deleteSelectedUrlSeeds
);
141 connect(m_ui
->listWebSeeds
, &QListWidget::doubleClicked
, this, &PropertiesWidget::editWebSeed
);
144 connect(Preferences::instance(), &Preferences::changed
, this, &PropertiesWidget::configure
);
147 PropertiesWidget::~PropertiesWidget()
153 void PropertiesWidget::showPiecesAvailability(bool show
)
155 m_ui
->labelPiecesAvailability
->setVisible(show
);
156 m_piecesAvailability
->setVisible(show
);
157 m_ui
->labelAverageAvailabilityVal
->setVisible(show
);
158 if (show
|| !m_downloadedPieces
->isVisible())
159 m_ui
->lineBelowBars
->setVisible(show
);
162 void PropertiesWidget::showPiecesDownloaded(bool show
)
164 m_ui
->labelDownloadedPieces
->setVisible(show
);
165 m_downloadedPieces
->setVisible(show
);
166 m_ui
->labelProgressVal
->setVisible(show
);
167 if (show
|| !m_piecesAvailability
->isVisible())
168 m_ui
->lineBelowBars
->setVisible(show
);
171 void PropertiesWidget::setVisibility(const bool visible
)
173 if (!visible
&& (m_state
== VISIBLE
))
175 const int tabBarHeight
= m_tabBar
->geometry().height(); // take height before hiding
176 auto *hSplitter
= static_cast<QSplitter
*>(parentWidget());
177 m_ui
->stackedProperties
->setVisible(false);
178 m_slideSizes
= hSplitter
->sizes();
179 hSplitter
->handle(1)->setVisible(false);
180 hSplitter
->handle(1)->setDisabled(true);
181 m_handleWidth
= hSplitter
->handleWidth();
182 hSplitter
->setHandleWidth(0);
183 const QList
<int> sizes
{(hSplitter
->geometry().height() - tabBarHeight
), tabBarHeight
};
184 hSplitter
->setSizes(sizes
);
185 setMaximumSize(maximumSize().width(), tabBarHeight
);
190 if (visible
&& (m_state
== REDUCED
))
192 m_ui
->stackedProperties
->setVisible(true);
193 auto *hSplitter
= static_cast<QSplitter
*>(parentWidget());
194 if (m_handleWidth
!= -1)
195 hSplitter
->setHandleWidth(m_handleWidth
);
196 hSplitter
->handle(1)->setDisabled(false);
197 hSplitter
->handle(1)->setVisible(true);
198 hSplitter
->setSizes(m_slideSizes
);
200 setMaximumSize(maximumSize().width(), QWIDGETSIZE_MAX
);
206 void PropertiesWidget::clear()
208 qDebug("Clearing torrent properties");
209 m_ui
->labelSavePathVal
->clear();
210 m_ui
->labelCreatedOnVal
->clear();
211 m_ui
->labelTotalPiecesVal
->clear();
212 m_ui
->labelPrivateVal
->clear();
213 m_ui
->labelInfohash1Val
->clear();
214 m_ui
->labelInfohash2Val
->clear();
215 m_ui
->labelCommentVal
->clear();
216 m_ui
->labelProgressVal
->clear();
217 m_ui
->labelAverageAvailabilityVal
->clear();
218 m_ui
->labelWastedVal
->clear();
219 m_ui
->labelUpTotalVal
->clear();
220 m_ui
->labelDlTotalVal
->clear();
221 m_ui
->labelUpLimitVal
->clear();
222 m_ui
->labelDlLimitVal
->clear();
223 m_ui
->labelElapsedVal
->clear();
224 m_ui
->labelConnectionsVal
->clear();
225 m_ui
->labelReannounceInVal
->clear();
226 m_ui
->labelShareRatioVal
->clear();
227 m_ui
->labelPopularityVal
->clear();
228 m_ui
->listWebSeeds
->clear();
229 m_ui
->labelETAVal
->clear();
230 m_ui
->labelSeedsVal
->clear();
231 m_ui
->labelPeersVal
->clear();
232 m_ui
->labelDlSpeedVal
->clear();
233 m_ui
->labelUpSpeedVal
->clear();
234 m_ui
->labelTotalSizeVal
->clear();
235 m_ui
->labelCompletedOnVal
->clear();
236 m_ui
->labelLastSeenCompleteVal
->clear();
237 m_ui
->labelCreatedByVal
->clear();
238 m_ui
->labelAddedOnVal
->clear();
239 m_downloadedPieces
->clear();
240 m_piecesAvailability
->clear();
242 m_contentFilterLine
->clear();
245 BitTorrent::Torrent
*PropertiesWidget::getCurrentTorrent() const
250 TrackerListWidget
*PropertiesWidget::getTrackerList() const
252 return m_trackerList
;
255 PeerListWidget
*PropertiesWidget::getPeerList() const
260 QTreeView
*PropertiesWidget::getFilesList() const
262 return m_ui
->filesList
;
265 PropTabBar
*PropertiesWidget::tabBar() const
270 LineEdit
*PropertiesWidget::contentFilterLine() const
272 return m_contentFilterLine
;
275 void PropertiesWidget::updateSavePath(BitTorrent::Torrent
*const torrent
)
277 if (torrent
== m_torrent
)
278 m_ui
->labelSavePathVal
->setText(m_torrent
->savePath().toString());
281 void PropertiesWidget::showContentFilterContextMenu()
283 QMenu
*menu
= m_contentFilterLine
->createStandardContextMenu();
285 auto *formatMenu
= new FilterPatternFormatMenu(m_storeFilterPatternFormat
.get(FilterPatternFormat::Wildcards
), menu
);
286 connect(formatMenu
, &FilterPatternFormatMenu::patternFormatChanged
, this, [this](const FilterPatternFormat format
)
288 m_storeFilterPatternFormat
= format
;
289 setContentFilterPattern();
292 menu
->addSeparator();
293 menu
->addMenu(formatMenu
);
294 menu
->setAttribute(Qt::WA_DeleteOnClose
);
295 menu
->popup(QCursor::pos());
298 void PropertiesWidget::setContentFilterPattern()
300 m_ui
->filesList
->setFilterPattern(m_contentFilterLine
->text(), m_storeFilterPatternFormat
.get(FilterPatternFormat::Wildcards
));
303 void PropertiesWidget::updateTorrentInfos(BitTorrent::Torrent
*const torrent
)
305 if (torrent
== m_torrent
)
306 loadTorrentInfos(m_torrent
);
309 void PropertiesWidget::loadTorrentInfos(BitTorrent::Torrent
*const torrent
)
313 m_downloadedPieces
->setTorrent(m_torrent
);
314 m_piecesAvailability
->setTorrent(m_torrent
);
315 m_trackerList
->setTorrent(m_torrent
);
316 m_ui
->filesList
->setContentHandler(m_torrent
);
321 updateSavePath(m_torrent
);
323 m_ui
->labelInfohash1Val
->setText(m_torrent
->infoHash().v1().isValid() ? m_torrent
->infoHash().v1().toString() : tr("N/A"));
324 m_ui
->labelInfohash2Val
->setText(m_torrent
->infoHash().v2().isValid() ? m_torrent
->infoHash().v2().toString() : tr("N/A"));
327 if (m_torrent
->hasMetadata())
330 m_ui
->labelCreatedOnVal
->setText(QLocale().toString(m_torrent
->creationDate(), QLocale::ShortFormat
));
332 m_ui
->labelTotalSizeVal
->setText(Utils::Misc::friendlyUnit(m_torrent
->totalSize()));
335 m_ui
->labelCommentVal
->setText(Utils::Misc::parseHtmlLinks(m_torrent
->comment().toHtmlEscaped()));
337 m_ui
->labelCreatedByVal
->setText(m_torrent
->creator());
339 m_ui
->labelPrivateVal
->setText(m_torrent
->isPrivate() ? tr("Yes") : tr("No"));
343 m_ui
->labelPrivateVal
->setText(tr("N/A"));
350 void PropertiesWidget::readSettings()
352 const Preferences
*const pref
= Preferences::instance();
353 // Restore splitter sizes
354 QStringList sizesStr
= pref
->getPropSplitterSizes().split(u
',');
355 if (sizesStr
.size() == 2)
357 m_slideSizes
<< sizesStr
.first().toInt();
358 m_slideSizes
<< sizesStr
.last().toInt();
359 auto *hSplitter
= static_cast<QSplitter
*>(parentWidget());
360 hSplitter
->setSizes(m_slideSizes
);
362 const int currentTab
= pref
->getPropCurTab();
363 const bool visible
= pref
->getPropVisible();
364 m_ui
->filesList
->header()->restoreState(pref
->getPropFileListState());
365 m_tabBar
->setCurrentIndex(currentTab
);
367 setVisibility(false);
370 void PropertiesWidget::saveSettings()
372 Preferences
*const pref
= Preferences::instance();
373 pref
->setPropVisible(m_state
== VISIBLE
);
375 auto *hSplitter
= static_cast<QSplitter
*>(parentWidget());
377 if (m_state
== VISIBLE
)
378 sizes
= hSplitter
->sizes();
380 sizes
= m_slideSizes
;
382 if (sizes
.size() == 2)
383 pref
->setPropSplitterSizes(QString::number(sizes
.first()) + u
',' + QString::number(sizes
.last()));
384 pref
->setPropFileListState(m_ui
->filesList
->header()->saveState());
385 // Remember current tab
386 pref
->setPropCurTab(m_tabBar
->currentIndex());
389 void PropertiesWidget::reloadPreferences()
391 // Take program preferences into consideration
392 m_peerList
->updatePeerHostNameResolutionState();
393 m_peerList
->updatePeerCountryResolutionState();
396 void PropertiesWidget::loadDynamicData()
398 // Refresh only if the torrent handle is valid and visible
399 if (!m_torrent
|| (m_state
!= VISIBLE
)) return;
402 switch (m_ui
->stackedProperties
->currentIndex())
404 case PropTabBar::MainTab
:
406 m_ui
->labelWastedVal
->setText(Utils::Misc::friendlyUnit(m_torrent
->wastedSize()));
408 m_ui
->labelUpTotalVal
->setText(tr("%1 (%2 this session)").arg(Utils::Misc::friendlyUnit(m_torrent
->totalUpload())
409 , Utils::Misc::friendlyUnit(m_torrent
->totalPayloadUpload())));
411 m_ui
->labelDlTotalVal
->setText(tr("%1 (%2 this session)").arg(Utils::Misc::friendlyUnit(m_torrent
->totalDownload())
412 , Utils::Misc::friendlyUnit(m_torrent
->totalPayloadDownload())));
414 m_ui
->labelUpLimitVal
->setText(m_torrent
->uploadLimit() <= 0 ? C_INFINITY
: Utils::Misc::friendlyUnit(m_torrent
->uploadLimit(), true));
416 m_ui
->labelDlLimitVal
->setText(m_torrent
->downloadLimit() <= 0 ? C_INFINITY
: Utils::Misc::friendlyUnit(m_torrent
->downloadLimit(), true));
418 QString elapsedString
;
419 if (m_torrent
->isFinished())
421 elapsedString
= tr("%1 (seeded for %2)", "e.g. 4m39s (seeded for 3m10s)")
422 .arg(Utils::Misc::userFriendlyDuration(m_torrent
->activeTime())
423 , Utils::Misc::userFriendlyDuration(m_torrent
->finishedTime()));
427 elapsedString
= Utils::Misc::userFriendlyDuration(m_torrent
->activeTime());
429 m_ui
->labelElapsedVal
->setText(elapsedString
);
431 m_ui
->labelConnectionsVal
->setText(tr("%1 (%2 max)", "%1 and %2 are numbers, e.g. 3 (10 max)")
432 .arg(m_torrent
->connectionsCount())
433 .arg(m_torrent
->connectionsLimit() < 0 ? C_INFINITY
: QString::number(m_torrent
->connectionsLimit())));
435 m_ui
->labelETAVal
->setText(Utils::Misc::userFriendlyDuration(m_torrent
->eta(), MAX_ETA
));
437 // Update next announce time
438 m_ui
->labelReannounceInVal
->setText(Utils::Misc::userFriendlyDuration(m_torrent
->nextAnnounce()));
441 const qreal ratio
= m_torrent
->realRatio();
442 m_ui
->labelShareRatioVal
->setText(ratio
> BitTorrent::Torrent::MAX_RATIO
? C_INFINITY
: Utils::String::fromDouble(ratio
, 2));
444 const qreal popularity
= m_torrent
->popularity();
445 m_ui
->labelPopularityVal
->setText(popularity
> BitTorrent::Torrent::MAX_RATIO
? C_INFINITY
: Utils::String::fromDouble(popularity
, 2));
447 m_ui
->labelSeedsVal
->setText(tr("%1 (%2 total)", "%1 and %2 are numbers, e.g. 3 (10 total)")
448 .arg(QString::number(m_torrent
->seedsCount())
449 , QString::number(m_torrent
->totalSeedsCount())));
451 m_ui
->labelPeersVal
->setText(tr("%1 (%2 total)", "%1 and %2 are numbers, e.g. 3 (10 total)")
452 .arg(QString::number(m_torrent
->leechsCount())
453 , QString::number(m_torrent
->totalLeechersCount())));
455 const qlonglong dlDuration
= m_torrent
->activeTime() - m_torrent
->finishedTime();
456 const QString dlAvg
= Utils::Misc::friendlyUnit((m_torrent
->totalDownload() / ((dlDuration
== 0) ? -1 : dlDuration
)), true);
457 m_ui
->labelDlSpeedVal
->setText(tr("%1 (%2 avg.)", "%1 and %2 are speed rates, e.g. 200KiB/s (100KiB/s avg.)")
458 .arg(Utils::Misc::friendlyUnit(m_torrent
->downloadPayloadRate(), true), dlAvg
));
460 const qlonglong ulDuration
= m_torrent
->activeTime();
461 const QString ulAvg
= Utils::Misc::friendlyUnit((m_torrent
->totalUpload() / ((ulDuration
== 0) ? -1 : ulDuration
)), true);
462 m_ui
->labelUpSpeedVal
->setText(tr("%1 (%2 avg.)", "%1 and %2 are speed rates, e.g. 200KiB/s (100KiB/s avg.)")
463 .arg(Utils::Misc::friendlyUnit(m_torrent
->uploadPayloadRate(), true), ulAvg
));
465 m_ui
->labelLastSeenCompleteVal
->setText(m_torrent
->lastSeenComplete().isValid() ? QLocale().toString(m_torrent
->lastSeenComplete(), QLocale::ShortFormat
) : tr("Never"));
467 m_ui
->labelCompletedOnVal
->setText(m_torrent
->completedTime().isValid() ? QLocale().toString(m_torrent
->completedTime(), QLocale::ShortFormat
) : QString
{});
469 m_ui
->labelAddedOnVal
->setText(QLocale().toString(m_torrent
->addedTime(), QLocale::ShortFormat
));
471 if (m_torrent
->hasMetadata())
473 using TorrentPtr
= QPointer
<BitTorrent::Torrent
>;
475 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()));
477 if (!m_torrent
->isFinished() && !m_torrent
->isStopped() && !m_torrent
->isQueued() && !m_torrent
->isChecking())
479 // Pieces availability
480 showPiecesAvailability(true);
481 m_torrent
->fetchPieceAvailability([this, torrent
= TorrentPtr(m_torrent
)](const QList
<int> &pieceAvailability
)
483 if (torrent
== m_torrent
)
484 m_piecesAvailability
->setAvailability(pieceAvailability
);
487 m_ui
->labelAverageAvailabilityVal
->setText(Utils::String::fromDouble(m_torrent
->distributedCopies(), 3));
491 showPiecesAvailability(false);
495 qreal progress
= m_torrent
->progress() * 100.;
496 m_ui
->labelProgressVal
->setText(Utils::String::fromDouble(progress
, 1) + u
'%');
498 m_torrent
->fetchDownloadingPieces([this, torrent
= TorrentPtr(m_torrent
)](const QBitArray
&downloadingPieces
)
500 if (torrent
== m_torrent
)
501 m_downloadedPieces
->setProgress(m_torrent
->pieces(), downloadingPieces
);
506 showPiecesAvailability(false);
510 case PropTabBar::PeersTab
:
512 m_peerList
->loadPeers(m_torrent
);
514 case PropTabBar::FilesTab
:
515 m_ui
->filesList
->refresh();
521 void PropertiesWidget::loadUrlSeeds()
526 using TorrentPtr
= QPointer
<BitTorrent::Torrent
>;
527 m_torrent
->fetchURLSeeds([this, torrent
= TorrentPtr(m_torrent
)](const QList
<QUrl
> &urlSeeds
)
529 if (torrent
!= m_torrent
)
532 m_ui
->listWebSeeds
->clear();
533 qDebug("Loading web seeds");
535 for (const QUrl
&urlSeed
: urlSeeds
)
537 qDebug("Loading web seed: %s", qUtf8Printable(urlSeed
.toString()));
538 new QListWidgetItem(urlSeed
.toString(), m_ui
->listWebSeeds
);
543 void PropertiesWidget::displayWebSeedListMenu()
545 if (!m_torrent
) return;
547 const QModelIndexList rows
= m_ui
->listWebSeeds
->selectionModel()->selectedRows();
549 QMenu
*menu
= new QMenu(this);
550 menu
->setAttribute(Qt::WA_DeleteOnClose
);
552 menu
->addAction(UIThemeManager::instance()->getIcon(u
"list-add"_s
), tr("Add web seed..."), this, &PropertiesWidget::askWebSeed
);
556 menu
->addAction(UIThemeManager::instance()->getIcon(u
"edit-clear"_s
, u
"list-remove"_s
), tr("Remove web seed")
557 , this, &PropertiesWidget::deleteSelectedUrlSeeds
);
558 menu
->addSeparator();
559 menu
->addAction(UIThemeManager::instance()->getIcon(u
"edit-copy"_s
), tr("Copy web seed URL")
560 , this, &PropertiesWidget::copySelectedWebSeedsToClipboard
);
561 menu
->addAction(UIThemeManager::instance()->getIcon(u
"edit-rename"_s
), tr("Edit web seed URL...")
562 , this, &PropertiesWidget::editWebSeed
);
565 menu
->popup(QCursor::pos());
568 void PropertiesWidget::configure()
571 if (Preferences::instance()->isSpeedWidgetEnabled())
573 if (!qobject_cast
<SpeedWidget
*>(m_speedWidget
))
577 m_ui
->speedLayout
->removeWidget(m_speedWidget
);
578 delete m_speedWidget
;
581 m_speedWidget
= new SpeedWidget(this);
582 m_ui
->speedLayout
->addWidget(m_speedWidget
);
587 if (!qobject_cast
<QLabel
*>(m_speedWidget
))
591 m_ui
->speedLayout
->removeWidget(m_speedWidget
);
592 delete m_speedWidget
;
595 const auto displayText
= u
"<center><b>%1</b><p>%2</p></center>"_s
596 .arg(tr("Speed graphs are disabled"), tr("You can enable it in Advanced Options"));
597 auto *label
= new QLabel(displayText
, this);
598 label
->setAlignment(Qt::AlignHCenter
| Qt::AlignVCenter
);
599 m_speedWidget
= label
;
600 m_ui
->speedLayout
->addWidget(m_speedWidget
);
605 void PropertiesWidget::askWebSeed()
608 // Ask user for a new url seed
609 const QString urlSeed
= AutoExpandableDialog::getText(this, tr("Add web seed", "Add HTTP source"),
610 tr("Add web seed:"), QLineEdit::Normal
,
611 u
"http://www."_s
, &ok
);
613 qDebug("Adding %s web seed", qUtf8Printable(urlSeed
));
614 if (!m_ui
->listWebSeeds
->findItems(urlSeed
, Qt::MatchFixedString
).empty())
616 QMessageBox::warning(this, u
"qBittorrent"_s
, tr("This web seed is already in the list."), QMessageBox::Ok
);
620 m_torrent
->addUrlSeeds({urlSeed
});
621 // Refresh the seeds list
625 void PropertiesWidget::deleteSelectedUrlSeeds()
627 const QList
<QListWidgetItem
*> selectedItems
= m_ui
->listWebSeeds
->selectedItems();
628 if (selectedItems
.isEmpty()) return;
630 QList
<QUrl
> urlSeeds
;
631 urlSeeds
.reserve(selectedItems
.size());
633 for (const QListWidgetItem
*item
: selectedItems
)
634 urlSeeds
<< item
->text();
636 m_torrent
->removeUrlSeeds(urlSeeds
);
641 void PropertiesWidget::copySelectedWebSeedsToClipboard() const
643 const QList
<QListWidgetItem
*> selectedItems
= m_ui
->listWebSeeds
->selectedItems();
644 if (selectedItems
.isEmpty()) return;
646 QStringList urlsToCopy
;
647 for (const QListWidgetItem
*item
: selectedItems
)
648 urlsToCopy
<< item
->text();
650 QApplication::clipboard()->setText(urlsToCopy
.join(u
'\n'));
653 void PropertiesWidget::editWebSeed()
655 const QList
<QListWidgetItem
*> selectedItems
= m_ui
->listWebSeeds
->selectedItems();
656 if (selectedItems
.size() != 1) return;
658 const QListWidgetItem
*selectedItem
= selectedItems
.last();
659 const QString oldSeed
= selectedItem
->text();
661 const QString newSeed
= AutoExpandableDialog::getText(this, tr("Web seed editing"),
662 tr("Web seed URL:"), QLineEdit::Normal
,
666 if (!m_ui
->listWebSeeds
->findItems(newSeed
, Qt::MatchFixedString
).empty())
668 QMessageBox::warning(this, u
"qBittorrent"_s
,
669 tr("This web seed is already in the list."),
674 m_torrent
->removeUrlSeeds({oldSeed
});
675 m_torrent
->addUrlSeeds({newSeed
});