2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2024 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2020 thalieht
5 * Copyright (C) 2011 Christian Kandeler
6 * Copyright (C) 2011 Christophe Dumez <chris@qbittorrent.org>
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 * In addition, as a special exception, the copyright holders give permission to
23 * link this program with the OpenSSL project's "OpenSSL" library (or with
24 * modified versions of it that use the same license as the "OpenSSL" library),
25 * and distribute the linked executables. You must obey the GNU General Public
26 * License in all respects for all of the code used other than "OpenSSL". If you
27 * modify file(s), you may extend this exception to your version of the file(s),
28 * but you are not obligated to do so. If you do not wish to do so, delete this
29 * exception statement from your version.
32 #include "torrentoptionsdialog.h"
37 #include <QMessageBox>
40 #include "base/bittorrent/infohash.h"
41 #include "base/bittorrent/session.h"
42 #include "base/bittorrent/torrent.h"
43 #include "base/global.h"
44 #include "base/unicodestrings.h"
45 #include "base/utils/fs.h"
46 #include "ui_torrentoptionsdialog.h"
49 #define SETTINGS_KEY(name) u"TorrentOptionsDialog/" name
53 void updateSliderValue(QSlider
*slider
, const int value
)
55 if (value
> slider
->maximum())
56 slider
->setMaximum(value
);
57 slider
->setValue(value
);
61 TorrentOptionsDialog::TorrentOptionsDialog(QWidget
*parent
, const QVector
<BitTorrent::Torrent
*> &torrents
)
63 , m_ui
{new Ui::TorrentOptionsDialog
}
64 , m_storeDialogSize
{SETTINGS_KEY(u
"Size"_s
)}
65 , m_currentCategoriesString
{u
"--%1--"_s
.arg(tr("Currently used categories"))}
67 Q_ASSERT(!torrents
.empty());
71 connect(m_ui
->buttonBox
, &QDialogButtonBox::accepted
, this, &QDialog::accept
);
72 connect(m_ui
->buttonBox
, &QDialogButtonBox::rejected
, this, &QDialog::reject
);
74 m_ui
->savePath
->setMode(FileSystemPathEdit::Mode::DirectorySave
);
75 m_ui
->savePath
->setDialogCaption(tr("Choose save path"));
76 m_ui
->downloadPath
->setMode(FileSystemPathEdit::Mode::DirectorySave
);
77 m_ui
->downloadPath
->setDialogCaption(tr("Choose save path"));
79 const auto *session
= BitTorrent::Session::instance();
80 bool allSameUpLimit
= true;
81 bool allSameDownLimit
= true;
82 bool allSameRatio
= true;
83 bool allSameSeedingTime
= true;
84 bool allSameInactiveSeedingTime
= true;
85 bool allTorrentsArePrivate
= true;
86 bool allSameDHT
= true;
87 bool allSamePEX
= true;
88 bool allSameLSD
= true;
89 bool allSameSequential
= true;
90 bool allSameFirstLastPieces
= true;
91 bool allSameAutoTMM
= true;
92 bool allSameSavePath
= true;
93 bool allSameDownloadPath
= true;
95 const bool isFirstTorrentAutoTMMEnabled
= torrents
[0]->isAutoTMMEnabled();
96 const Path firstTorrentSavePath
= torrents
[0]->savePath();
97 const Path firstTorrentDownloadPath
= torrents
[0]->downloadPath();
98 const QString firstTorrentCategory
= torrents
[0]->category();
100 const int firstTorrentUpLimit
= std::max(0, torrents
[0]->uploadLimit());
101 const int firstTorrentDownLimit
= std::max(0, torrents
[0]->downloadLimit());
103 const qreal firstTorrentRatio
= torrents
[0]->ratioLimit();
104 const int firstTorrentSeedingTime
= torrents
[0]->seedingTimeLimit();
105 const int firstTorrentInactiveSeedingTime
= torrents
[0]->inactiveSeedingTimeLimit();
107 const bool isFirstTorrentDHTDisabled
= torrents
[0]->isDHTDisabled();
108 const bool isFirstTorrentPEXDisabled
= torrents
[0]->isPEXDisabled();
109 const bool isFirstTorrentLSDDisabled
= torrents
[0]->isLSDDisabled();
110 const bool isFirstTorrentSequentialEnabled
= torrents
[0]->isSequentialDownload();
111 const bool isFirstTorrentFirstLastPiecesEnabled
= torrents
[0]->hasFirstLastPiecePriority();
113 m_torrentIDs
.reserve(torrents
.size());
114 for (const BitTorrent::Torrent
*torrent
: torrents
)
116 m_torrentIDs
<< torrent
->id();
120 if (torrent
->isAutoTMMEnabled() != isFirstTorrentAutoTMMEnabled
)
121 allSameAutoTMM
= false;
125 if (torrent
->savePath() != firstTorrentSavePath
)
126 allSameSavePath
= false;
128 if (allSameDownloadPath
)
130 if (torrent
->downloadPath() != firstTorrentDownloadPath
)
131 allSameDownloadPath
= false;
133 if (m_allSameCategory
)
135 if (torrent
->category() != firstTorrentCategory
)
136 m_allSameCategory
= false;
140 if (std::max(0, torrent
->uploadLimit()) != firstTorrentUpLimit
)
141 allSameUpLimit
= false;
143 if (allSameDownLimit
)
145 if (std::max(0, torrent
->downloadLimit()) != firstTorrentDownLimit
)
146 allSameDownLimit
= false;
150 if (torrent
->ratioLimit() != firstTorrentRatio
)
151 allSameRatio
= false;
153 if (allSameSeedingTime
)
155 if (torrent
->seedingTimeLimit() != firstTorrentSeedingTime
)
156 allSameSeedingTime
= false;
158 if (allSameInactiveSeedingTime
)
160 if (torrent
->inactiveSeedingTimeLimit() != firstTorrentInactiveSeedingTime
)
161 allSameInactiveSeedingTime
= false;
163 if (allTorrentsArePrivate
)
165 if (!torrent
->isPrivate())
166 allTorrentsArePrivate
= false;
170 if (torrent
->isDHTDisabled() != isFirstTorrentDHTDisabled
)
175 if (torrent
->isPEXDisabled() != isFirstTorrentPEXDisabled
)
180 if (torrent
->isLSDDisabled() != isFirstTorrentLSDDisabled
)
183 if (allSameSequential
)
185 if (torrent
->isSequentialDownload() != isFirstTorrentSequentialEnabled
)
186 allSameSequential
= false;
188 if (allSameFirstLastPieces
)
190 if (torrent
->hasFirstLastPiecePriority() != isFirstTorrentFirstLastPiecesEnabled
)
191 allSameFirstLastPieces
= false;
196 m_ui
->checkAutoTMM
->setChecked(isFirstTorrentAutoTMMEnabled
);
198 m_ui
->checkAutoTMM
->setCheckState(Qt::PartiallyChecked
);
201 m_ui
->savePath
->setSelectedPath(firstTorrentSavePath
);
203 if (allSameDownloadPath
)
205 m_ui
->downloadPath
->setSelectedPath(firstTorrentDownloadPath
);
206 m_ui
->checkUseDownloadPath
->setChecked(!firstTorrentDownloadPath
.isEmpty());
210 m_ui
->checkUseDownloadPath
->setCheckState(Qt::PartiallyChecked
);
213 if (!m_allSameCategory
)
215 m_ui
->comboCategory
->addItem(m_currentCategoriesString
);
216 m_ui
->comboCategory
->clearEditText();
217 m_ui
->comboCategory
->lineEdit()->setPlaceholderText(m_currentCategoriesString
);
219 else if (!firstTorrentCategory
.isEmpty())
221 m_ui
->comboCategory
->setCurrentText(firstTorrentCategory
);
222 m_ui
->comboCategory
->addItem(firstTorrentCategory
);
224 m_ui
->comboCategory
->addItem(QString());
226 m_categories
= session
->categories();
227 std::sort(m_categories
.begin(), m_categories
.end(), Utils::Compare::NaturalLessThan
<Qt::CaseInsensitive
>());
228 for (const QString
&category
: asConst(m_categories
))
230 if (m_allSameCategory
&& (category
== firstTorrentCategory
))
233 m_ui
->comboCategory
->addItem(category
);
236 const bool isAltLimitEnabled
= session
->isAltGlobalSpeedLimitEnabled();
237 const int globalUploadLimit
= isAltLimitEnabled
238 ? (session
->altGlobalUploadSpeedLimit() / 1024)
239 : (session
->globalUploadSpeedLimit() / 1024);
240 const int globalDownloadLimit
= isAltLimitEnabled
241 ? (session
->altGlobalDownloadSpeedLimit() / 1024)
242 : (session
->globalDownloadSpeedLimit() / 1024);
244 const int uploadVal
= std::max(0, (firstTorrentUpLimit
/ 1024));
245 const int downloadVal
= std::max(0, (firstTorrentDownLimit
/ 1024));
246 int maxUpload
= (globalUploadLimit
<= 0) ? 10000 : globalUploadLimit
;
247 int maxDownload
= (globalDownloadLimit
<= 0) ? 10000 : globalDownloadLimit
;
249 // This can happen for example if global rate limit is lower than torrent rate limit.
250 if (uploadVal
> maxUpload
)
251 maxUpload
= uploadVal
;
252 if (downloadVal
> maxDownload
)
253 maxDownload
= downloadVal
;
255 m_ui
->sliderUploadLimit
->setMaximum(maxUpload
);
256 m_ui
->sliderUploadLimit
->setValue(allSameUpLimit
? uploadVal
: (maxUpload
/ 2));
259 m_ui
->spinUploadLimit
->setValue(uploadVal
);
263 m_ui
->spinUploadLimit
->setSpecialValueText(C_INEQUALITY
);
264 m_ui
->spinUploadLimit
->setMinimum(-1);
265 m_ui
->spinUploadLimit
->setValue(-1);
266 connect(m_ui
->spinUploadLimit
, qOverload
<int>(&QSpinBox::valueChanged
)
267 , this, &TorrentOptionsDialog::handleUpSpeedLimitChanged
);
270 m_ui
->sliderDownloadLimit
->setMaximum(maxDownload
);
271 m_ui
->sliderDownloadLimit
->setValue(allSameDownLimit
? downloadVal
: (maxDownload
/ 2));
272 if (allSameDownLimit
)
274 m_ui
->spinDownloadLimit
->setValue(downloadVal
);
278 m_ui
->spinDownloadLimit
->setSpecialValueText(C_INEQUALITY
);
279 m_ui
->spinDownloadLimit
->setMinimum(-1);
280 m_ui
->spinDownloadLimit
->setValue(-1);
281 connect(m_ui
->spinDownloadLimit
, qOverload
<int>(&QSpinBox::valueChanged
)
282 , this, &TorrentOptionsDialog::handleDownSpeedLimitChanged
);
285 m_ui
->torrentShareLimitsWidget
->setDefaultLimits(session
->globalMaxRatio(), session
->globalMaxSeedingMinutes(), session
->globalMaxInactiveSeedingMinutes());
287 m_ui
->torrentShareLimitsWidget
->setRatioLimit(firstTorrentRatio
);
288 if (allSameSeedingTime
)
289 m_ui
->torrentShareLimitsWidget
->setSeedingTimeLimit(firstTorrentSeedingTime
);
290 if (allSameInactiveSeedingTime
)
291 m_ui
->torrentShareLimitsWidget
->setInactiveSeedingTimeLimit(firstTorrentInactiveSeedingTime
);
293 if (!allTorrentsArePrivate
)
296 m_ui
->checkDisableDHT
->setChecked(isFirstTorrentDHTDisabled
);
298 m_ui
->checkDisableDHT
->setCheckState(Qt::PartiallyChecked
);
301 m_ui
->checkDisablePEX
->setChecked(isFirstTorrentPEXDisabled
);
303 m_ui
->checkDisablePEX
->setCheckState(Qt::PartiallyChecked
);
306 m_ui
->checkDisableLSD
->setChecked(isFirstTorrentLSDDisabled
);
308 m_ui
->checkDisableLSD
->setCheckState(Qt::PartiallyChecked
);
312 m_ui
->checkDisableDHT
->setChecked(true);
313 m_ui
->checkDisableDHT
->setEnabled(false);
314 m_ui
->checkDisablePEX
->setChecked(true);
315 m_ui
->checkDisablePEX
->setEnabled(false);
316 m_ui
->checkDisableLSD
->setChecked(true);
317 m_ui
->checkDisableLSD
->setEnabled(false);
320 const QString privateTorrentsTooltip
= tr("Not applicable to private torrents");
321 m_ui
->checkDisableDHT
->setToolTip(privateTorrentsTooltip
);
322 m_ui
->checkDisablePEX
->setToolTip(privateTorrentsTooltip
);
323 m_ui
->checkDisableLSD
->setToolTip(privateTorrentsTooltip
);
325 if (allSameSequential
)
326 m_ui
->checkSequential
->setChecked(isFirstTorrentSequentialEnabled
);
328 m_ui
->checkSequential
->setCheckState(Qt::PartiallyChecked
);
330 if (allSameFirstLastPieces
)
331 m_ui
->checkFirstLastPieces
->setChecked(isFirstTorrentFirstLastPiecesEnabled
);
333 m_ui
->checkFirstLastPieces
->setCheckState(Qt::PartiallyChecked
);
337 .savePath
= m_ui
->savePath
->selectedPath(),
338 .downloadPath
= m_ui
->downloadPath
->selectedPath(),
339 .category
= m_ui
->comboCategory
->currentText(),
340 .ratio
= m_ui
->torrentShareLimitsWidget
->ratioLimit(),
341 .seedingTime
= m_ui
->torrentShareLimitsWidget
->seedingTimeLimit(),
342 .inactiveSeedingTime
= m_ui
->torrentShareLimitsWidget
->inactiveSeedingTimeLimit(),
343 .upSpeedLimit
= m_ui
->spinUploadLimit
->value(),
344 .downSpeedLimit
= m_ui
->spinDownloadLimit
->value(),
345 .autoTMM
= m_ui
->checkAutoTMM
->checkState(),
346 .useDownloadPath
= m_ui
->checkUseDownloadPath
->checkState(),
347 .disableDHT
= m_ui
->checkDisableDHT
->checkState(),
348 .disablePEX
= m_ui
->checkDisablePEX
->checkState(),
349 .disableLSD
= m_ui
->checkDisableLSD
->checkState(),
350 .sequential
= m_ui
->checkSequential
->checkState(),
351 .firstLastPieces
= m_ui
->checkFirstLastPieces
->checkState()
354 // Needs to be called after the initial values struct is initialized
356 handleUseDownloadPathChanged();
358 connect(m_ui
->checkAutoTMM
, &QCheckBox::clicked
, this, &TorrentOptionsDialog::handleTMMChanged
);
359 connect(m_ui
->checkUseDownloadPath
, &QCheckBox::clicked
, this, &TorrentOptionsDialog::handleUseDownloadPathChanged
);
360 connect(m_ui
->comboCategory
, &QComboBox::activated
, this, &TorrentOptionsDialog::handleCategoryChanged
);
362 // Sync up/down speed limit sliders with their corresponding spinboxes
363 connect(m_ui
->sliderUploadLimit
, &QSlider::valueChanged
, m_ui
->spinUploadLimit
, &QSpinBox::setValue
);
364 connect(m_ui
->sliderDownloadLimit
, &QSlider::valueChanged
, m_ui
->spinDownloadLimit
, &QSpinBox::setValue
);
365 connect(m_ui
->spinUploadLimit
, qOverload
<int>(&QSpinBox::valueChanged
)
366 , this, [this](const int value
) { updateSliderValue(m_ui
->sliderUploadLimit
, value
); });
367 connect(m_ui
->spinDownloadLimit
, qOverload
<int>(&QSpinBox::valueChanged
)
368 , this, [this](const int value
) { updateSliderValue(m_ui
->sliderDownloadLimit
, value
); });
370 if (const QSize dialogSize
= m_storeDialogSize
; dialogSize
.isValid())
374 TorrentOptionsDialog::~TorrentOptionsDialog()
376 m_storeDialogSize
= size();
380 void TorrentOptionsDialog::accept()
382 auto *session
= BitTorrent::Session::instance();
383 for (const BitTorrent::TorrentID
&id
: asConst(m_torrentIDs
))
385 BitTorrent::Torrent
*torrent
= session
->getTorrent(id
);
386 if (!torrent
) continue;
388 if (m_initialValues
.autoTMM
!= m_ui
->checkAutoTMM
->checkState())
389 torrent
->setAutoTMMEnabled(m_ui
->checkAutoTMM
->isChecked());
391 if (m_ui
->checkAutoTMM
->checkState() == Qt::Unchecked
)
393 const Path savePath
= m_ui
->savePath
->selectedPath();
394 if (m_initialValues
.savePath
!= savePath
)
395 torrent
->setSavePath(savePath
);
397 const Qt::CheckState useDownloadPathState
= m_ui
->checkUseDownloadPath
->checkState();
398 if (useDownloadPathState
== Qt::Checked
)
400 const Path downloadPath
= m_ui
->downloadPath
->selectedPath();
401 if (m_initialValues
.downloadPath
!= downloadPath
)
402 torrent
->setDownloadPath(downloadPath
);
404 else if (useDownloadPathState
== Qt::Unchecked
)
406 torrent
->setDownloadPath({});
410 const QString category
= m_ui
->comboCategory
->currentText();
411 // index 0 is always the current category
412 if ((m_initialValues
.category
!= category
) || (m_ui
->comboCategory
->currentIndex() != 0))
414 if (!m_categories
.contains(category
))
415 session
->addCategory(category
);
417 torrent
->setCategory(category
);
420 if (m_initialValues
.upSpeedLimit
!= m_ui
->spinUploadLimit
->value())
421 torrent
->setUploadLimit(m_ui
->spinUploadLimit
->value() * 1024);
422 if (m_initialValues
.downSpeedLimit
!= m_ui
->spinDownloadLimit
->value())
423 torrent
->setDownloadLimit(m_ui
->spinDownloadLimit
->value() * 1024);
425 if (const std::optional
<qreal
> ratioLimit
= m_ui
->torrentShareLimitsWidget
->ratioLimit();
426 m_initialValues
.ratio
!= ratioLimit
)
428 torrent
->setRatioLimit(ratioLimit
.value());
431 if (const std::optional
<int> seedingTimeLimit
= m_ui
->torrentShareLimitsWidget
->seedingTimeLimit();
432 m_initialValues
.seedingTime
!= seedingTimeLimit
)
434 torrent
->setSeedingTimeLimit(seedingTimeLimit
.value());
437 if (const std::optional
<int> inactiveSeedingTimeLimit
= m_ui
->torrentShareLimitsWidget
->inactiveSeedingTimeLimit();
438 m_initialValues
.inactiveSeedingTime
!= inactiveSeedingTimeLimit
)
440 torrent
->setInactiveSeedingTimeLimit(inactiveSeedingTimeLimit
.value());
443 if (!torrent
->isPrivate())
445 if (m_initialValues
.disableDHT
!= m_ui
->checkDisableDHT
->checkState())
446 torrent
->setDHTDisabled(m_ui
->checkDisableDHT
->isChecked());
447 if (m_initialValues
.disablePEX
!= m_ui
->checkDisablePEX
->checkState())
448 torrent
->setPEXDisabled(m_ui
->checkDisablePEX
->isChecked());
449 if (m_initialValues
.disableLSD
!= m_ui
->checkDisableLSD
->checkState())
450 torrent
->setLSDDisabled(m_ui
->checkDisableLSD
->isChecked());
453 if (m_initialValues
.sequential
!= m_ui
->checkSequential
->checkState())
454 torrent
->setSequentialDownload(m_ui
->checkSequential
->isChecked());
455 if (m_initialValues
.firstLastPieces
!= m_ui
->checkFirstLastPieces
->checkState())
456 torrent
->setFirstLastPiecePriority(m_ui
->checkFirstLastPieces
->isChecked());
462 void TorrentOptionsDialog::handleCategoryChanged([[maybe_unused
]] const int index
)
464 if (m_ui
->checkAutoTMM
->checkState() == Qt::Checked
)
466 if (!m_allSameCategory
&& (m_ui
->comboCategory
->currentIndex() == 0))
468 m_ui
->savePath
->setSelectedPath({});
472 const Path savePath
= BitTorrent::Session::instance()->categorySavePath(m_ui
->comboCategory
->currentText());
473 m_ui
->savePath
->setSelectedPath(savePath
);
474 const Path downloadPath
= BitTorrent::Session::instance()->categoryDownloadPath(m_ui
->comboCategory
->currentText());
475 m_ui
->downloadPath
->setSelectedPath(downloadPath
);
476 m_ui
->checkUseDownloadPath
->setChecked(!downloadPath
.isEmpty());
480 if (!m_allSameCategory
&& (m_ui
->comboCategory
->currentIndex() == 0))
482 m_ui
->comboCategory
->clearEditText();
483 m_ui
->comboCategory
->lineEdit()->setPlaceholderText(m_currentCategoriesString
);
487 m_ui
->comboCategory
->lineEdit()->setPlaceholderText(QString());
491 void TorrentOptionsDialog::handleTMMChanged()
493 if (m_ui
->checkAutoTMM
->checkState() == Qt::Unchecked
)
495 m_ui
->groupBoxSavePath
->setEnabled(true);
496 m_ui
->savePath
->setSelectedPath(m_initialValues
.savePath
);
497 m_ui
->downloadPath
->setSelectedPath(m_initialValues
.downloadPath
);
498 m_ui
->checkUseDownloadPath
->setCheckState(m_initialValues
.useDownloadPath
);
502 m_ui
->groupBoxSavePath
->setEnabled(false);
503 if (m_ui
->checkAutoTMM
->checkState() == Qt::Checked
)
505 if (!m_allSameCategory
&& (m_ui
->comboCategory
->currentIndex() == 0))
507 m_ui
->savePath
->setSelectedPath({});
508 m_ui
->downloadPath
->setSelectedPath({});
509 m_ui
->checkUseDownloadPath
->setCheckState(Qt::PartiallyChecked
);
513 const Path savePath
= BitTorrent::Session::instance()->categorySavePath(m_ui
->comboCategory
->currentText());
514 m_ui
->savePath
->setSelectedPath(savePath
);
515 const Path downloadPath
= BitTorrent::Session::instance()->categoryDownloadPath(m_ui
->comboCategory
->currentText());
516 m_ui
->downloadPath
->setSelectedPath(downloadPath
);
517 m_ui
->checkUseDownloadPath
->setChecked(!downloadPath
.isEmpty());
520 else // partially checked
522 m_ui
->savePath
->setSelectedPath({});
523 m_ui
->downloadPath
->setSelectedPath({});
524 m_ui
->checkUseDownloadPath
->setCheckState(Qt::PartiallyChecked
);
529 void TorrentOptionsDialog::handleUseDownloadPathChanged()
531 const bool isChecked
= m_ui
->checkUseDownloadPath
->checkState() == Qt::Checked
;
532 m_ui
->downloadPath
->setEnabled(isChecked
);
533 if (isChecked
&& m_ui
->downloadPath
->selectedPath().isEmpty())
534 m_ui
->downloadPath
->setSelectedPath(BitTorrent::Session::instance()->downloadPath());
537 void TorrentOptionsDialog::handleUpSpeedLimitChanged()
539 m_ui
->spinUploadLimit
->setMinimum(0);
540 m_ui
->spinUploadLimit
->setSpecialValueText(C_INFINITY
);
541 disconnect(m_ui
->spinUploadLimit
, qOverload
<int>(&QSpinBox::valueChanged
)
542 , this, &TorrentOptionsDialog::handleUpSpeedLimitChanged
);
545 void TorrentOptionsDialog::handleDownSpeedLimitChanged()
547 m_ui
->spinDownloadLimit
->setMinimum(0);
548 m_ui
->spinDownloadLimit
->setSpecialValueText(C_INFINITY
);
549 disconnect(m_ui
->spinDownloadLimit
, qOverload
<int>(&QSpinBox::valueChanged
)
550 , this, &TorrentOptionsDialog::handleDownSpeedLimitChanged
);