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>
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
)
69 , m_ui
{new Ui::PropertiesWidget
}
70 , m_storeFilterPatternFormat
{u
"GUI/PropertiesWidget/FilterPatternFormat"_s
}
74 setAutoFillBackground(true);
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);
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
);
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
);
127 m_peerList
= new PeerListWidget(this);
128 m_ui
->vBoxLayoutPeerPage
->addWidget(m_peerList
);
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
);
145 connect(Preferences::instance(), &Preferences::changed
, this, &PropertiesWidget::configure
);
148 PropertiesWidget::~PropertiesWidget()
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
);
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
);
201 setMaximumSize(maximumSize().width(), QWIDGETSIZE_MAX
);
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();
243 m_contentFilterLine
->clear();
246 BitTorrent::Torrent
*PropertiesWidget::getCurrentTorrent() const
251 TrackerListWidget
*PropertiesWidget::getTrackerList() const
253 return m_trackerList
;
256 PeerListWidget
*PropertiesWidget::getPeerList() const
261 QTreeView
*PropertiesWidget::getFilesList() const
263 return m_ui
->filesList
;
266 PropTabBar
*PropertiesWidget::tabBar() const
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
)
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
);
322 updateSavePath(m_torrent
);
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"));
328 if (m_torrent
->hasMetadata())
331 m_ui
->labelCreatedOnVal
->setText(QLocale().toString(m_torrent
->creationDate(), QLocale::ShortFormat
));
333 m_ui
->labelTotalSizeVal
->setText(Utils::Misc::friendlyUnit(m_torrent
->totalSize()));
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"));
344 m_ui
->labelPrivateVal
->setText(tr("N/A"));
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
);
368 setVisibility(false);
371 void PropertiesWidget::saveSettings()
373 Preferences
*const pref
= Preferences::instance();
374 pref
->setPropVisible(m_state
== VISIBLE
);
376 auto *hSplitter
= static_cast<QSplitter
*>(parentWidget());
378 if (m_state
== VISIBLE
)
379 sizes
= hSplitter
->sizes();
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;
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()));
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()));
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));
492 showPiecesAvailability(false);
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
);
507 showPiecesAvailability(false);
511 case PropTabBar::PeersTab
:
513 m_peerList
->loadPeers(m_torrent
);
515 case PropTabBar::FilesTab
:
516 m_ui
->filesList
->refresh();
522 void PropertiesWidget::loadUrlSeeds()
527 using TorrentPtr
= QPointer
<BitTorrent::Torrent
>;
528 m_torrent
->fetchURLSeeds([this, torrent
= TorrentPtr(m_torrent
)](const QList
<QUrl
> &urlSeeds
)
530 if (torrent
!= m_torrent
)
533 m_ui
->listWebSeeds
->clear();
534 qDebug("Loading 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
);
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()
572 if (Preferences::instance()->isSpeedWidgetEnabled())
574 if (!qobject_cast
<SpeedWidget
*>(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
);
588 if (!qobject_cast
<QLabel
*>(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()
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
);
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
);
621 m_torrent
->addUrlSeeds({urlSeed
});
622 // Refresh the seeds list
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
);
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();
662 const QString newSeed
= AutoExpandableDialog::getText(this, tr("Web seed editing"),
663 tr("Web seed URL:"), QLineEdit::Normal
,
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."),
675 m_torrent
->removeUrlSeeds({oldSeed
});
676 m_torrent
->addUrlSeeds({newSeed
});