Fix Enter key behavior when add new torrent
[qBittorrent.git] / src / gui / addnewtorrentdialog.cpp
blobe3ceb2880dd325b7f3db46baf2d910804ceb4b1e
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2022-2023 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2012 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 "addnewtorrentdialog.h"
32 #include <algorithm>
34 #include <QAction>
35 #include <QDateTime>
36 #include <QDebug>
37 #include <QDir>
38 #include <QFileDialog>
39 #include <QMenu>
40 #include <QMessageBox>
41 #include <QPushButton>
42 #include <QShortcut>
43 #include <QString>
44 #include <QUrl>
45 #include <QVector>
47 #include "base/bittorrent/downloadpriority.h"
48 #include "base/bittorrent/infohash.h"
49 #include "base/bittorrent/session.h"
50 #include "base/bittorrent/torrent.h"
51 #include "base/bittorrent/torrentcontenthandler.h"
52 #include "base/bittorrent/torrentcontentlayout.h"
53 #include "base/global.h"
54 #include "base/preferences.h"
55 #include "base/settingsstorage.h"
56 #include "base/torrentfileguard.h"
57 #include "base/utils/compare.h"
58 #include "base/utils/fs.h"
59 #include "base/utils/misc.h"
60 #include "lineedit.h"
61 #include "torrenttagsdialog.h"
62 #include "uithememanager.h"
64 #include "ui_addnewtorrentdialog.h"
66 namespace
68 #define SETTINGS_KEY(name) u"AddNewTorrentDialog/" name
69 const QString KEY_SAVEPATHHISTORY = SETTINGS_KEY(u"SavePathHistory"_s);
70 const QString KEY_DOWNLOADPATHHISTORY = SETTINGS_KEY(u"DownloadPathHistory"_s);
72 // just a shortcut
73 inline SettingsStorage *settings()
75 return SettingsStorage::instance();
78 // savePath is a folder, not an absolute file path
79 int indexOfPath(const FileSystemPathComboEdit *fsPathEdit, const Path &savePath)
81 for (int i = 0; i < fsPathEdit->count(); ++i)
83 if (fsPathEdit->item(i) == savePath)
84 return i;
86 return -1;
89 qint64 queryFreeDiskSpace(const Path &path)
91 const Path root = path.rootItem();
92 Path current = path;
93 qint64 freeSpace = Utils::Fs::freeDiskSpaceOnPath(current);
95 // for non-existent directories (which will be created on demand) `Utils::Fs::freeDiskSpaceOnPath`
96 // will return invalid value so instead query its parent/ancestor paths
97 while ((freeSpace < 0) && (current != root))
99 current = current.parentPath();
100 freeSpace = Utils::Fs::freeDiskSpaceOnPath(current);
102 return freeSpace;
105 void setPath(FileSystemPathComboEdit *fsPathEdit, const Path &newPath)
107 int existingIndex = indexOfPath(fsPathEdit, newPath);
108 if (existingIndex < 0)
110 // New path, prepend to combo box
111 fsPathEdit->insertItem(0, newPath);
112 existingIndex = 0;
115 fsPathEdit->setCurrentIndex(existingIndex);
118 void updatePathHistory(const QString &settingsKey, const Path &path, const int maxLength)
120 // Add last used save path to the front of history
122 auto pathList = settings()->loadValue<QStringList>(settingsKey);
124 const int selectedSavePathIndex = pathList.indexOf(path.toString());
125 if (selectedSavePathIndex > -1)
126 pathList.move(selectedSavePathIndex, 0);
127 else
128 pathList.prepend(path.toString());
130 settings()->storeValue(settingsKey, QStringList(pathList.mid(0, maxLength)));
134 class AddNewTorrentDialog::TorrentContentAdaptor final
135 : public BitTorrent::TorrentContentHandler
137 public:
138 TorrentContentAdaptor(const BitTorrent::TorrentInfo &torrentInfo, PathList &filePaths
139 , QVector<BitTorrent::DownloadPriority> &filePriorities)
140 : m_torrentInfo {torrentInfo}
141 , m_filePaths {filePaths}
142 , m_filePriorities {filePriorities}
144 Q_ASSERT(filePaths.isEmpty() || (filePaths.size() == m_torrentInfo.filesCount()));
146 m_originalRootFolder = Path::findRootFolder(m_torrentInfo.filePaths());
147 m_currentContentLayout = (m_originalRootFolder.isEmpty()
148 ? BitTorrent::TorrentContentLayout::NoSubfolder
149 : BitTorrent::TorrentContentLayout::Subfolder);
151 if (const int fileCount = filesCount(); !m_filePriorities.isEmpty() && (fileCount >= 0))
152 m_filePriorities.resize(fileCount, BitTorrent::DownloadPriority::Normal);
155 bool hasMetadata() const override
157 return m_torrentInfo.isValid();
160 int filesCount() const override
162 return m_torrentInfo.filesCount();
165 qlonglong fileSize(const int index) const override
167 Q_ASSERT((index >= 0) && (index < filesCount()));
168 return m_torrentInfo.fileSize(index);
171 Path filePath(const int index) const override
173 Q_ASSERT((index >= 0) && (index < filesCount()));
174 return (m_filePaths.isEmpty() ? m_torrentInfo.filePath(index) : m_filePaths.at(index));
177 void renameFile(const int index, const Path &newFilePath) override
179 Q_ASSERT((index >= 0) && (index < filesCount()));
180 const Path currentFilePath = filePath(index);
181 if (currentFilePath == newFilePath)
182 return;
184 if (m_filePaths.isEmpty())
185 m_filePaths = m_torrentInfo.filePaths();
187 m_filePaths[index] = newFilePath;
190 void applyContentLayout(const BitTorrent::TorrentContentLayout contentLayout)
192 Q_ASSERT(hasMetadata());
193 Q_ASSERT(!m_filePaths.isEmpty());
195 const auto originalContentLayout = (m_originalRootFolder.isEmpty()
196 ? BitTorrent::TorrentContentLayout::NoSubfolder
197 : BitTorrent::TorrentContentLayout::Subfolder);
198 const auto newContentLayout = ((contentLayout == BitTorrent::TorrentContentLayout::Original)
199 ? originalContentLayout : contentLayout);
200 if (newContentLayout != m_currentContentLayout)
202 if (newContentLayout == BitTorrent::TorrentContentLayout::NoSubfolder)
204 Path::stripRootFolder(m_filePaths);
206 else
208 const auto rootFolder = ((originalContentLayout == BitTorrent::TorrentContentLayout::Subfolder)
209 ? m_originalRootFolder : m_filePaths.at(0).removedExtension());
210 Path::addRootFolder(m_filePaths, rootFolder);
213 m_currentContentLayout = newContentLayout;
217 QVector<BitTorrent::DownloadPriority> filePriorities() const override
219 return m_filePriorities.isEmpty()
220 ? QVector<BitTorrent::DownloadPriority>(filesCount(), BitTorrent::DownloadPriority::Normal)
221 : m_filePriorities;
224 QVector<qreal> filesProgress() const override
226 return QVector<qreal>(filesCount(), 0);
229 QVector<qreal> availableFileFractions() const override
231 return QVector<qreal>(filesCount(), 0);
234 void fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const override
236 resultHandler(availableFileFractions());
239 void prioritizeFiles(const QVector<BitTorrent::DownloadPriority> &priorities) override
241 Q_ASSERT(priorities.size() == filesCount());
242 m_filePriorities = priorities;
245 Path actualStorageLocation() const override
247 return {};
250 Path actualFilePath([[maybe_unused]] int fileIndex) const override
252 return {};
255 void flushCache() const override
259 private:
260 const BitTorrent::TorrentInfo &m_torrentInfo;
261 PathList &m_filePaths;
262 QVector<BitTorrent::DownloadPriority> &m_filePriorities;
263 Path m_originalRootFolder;
264 BitTorrent::TorrentContentLayout m_currentContentLayout;
267 AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &torrentDescr
268 , const BitTorrent::AddTorrentParams &inParams, QWidget *parent)
269 : QDialog(parent)
270 , m_ui {new Ui::AddNewTorrentDialog}
271 , m_torrentDescr {torrentDescr}
272 , m_torrentParams {inParams}
273 , m_filterLine {new LineEdit(this)}
274 , m_storeDialogSize {SETTINGS_KEY(u"DialogSize"_s)}
275 , m_storeDefaultCategory {SETTINGS_KEY(u"DefaultCategory"_s)}
276 , m_storeRememberLastSavePath {SETTINGS_KEY(u"RememberLastSavePath"_s)}
277 , m_storeTreeHeaderState {u"GUI/Qt6/" SETTINGS_KEY(u"TreeHeaderState"_s)}
278 , m_storeSplitterState {u"GUI/Qt6/" SETTINGS_KEY(u"SplitterState"_s)}
280 // TODO: set dialog file properties using m_torrentParams.filePriorities
281 m_ui->setupUi(this);
283 connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
284 connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
286 m_ui->lblMetaLoading->setVisible(false);
287 m_ui->progMetaLoading->setVisible(false);
288 m_ui->buttonSave->setVisible(false);
289 connect(m_ui->buttonSave, &QPushButton::clicked, this, &AddNewTorrentDialog::saveTorrentFile);
291 m_ui->savePath->setMode(FileSystemPathEdit::Mode::DirectorySave);
292 m_ui->savePath->setDialogCaption(tr("Choose save path"));
293 m_ui->savePath->setMaxVisibleItems(20);
295 const auto *session = BitTorrent::Session::instance();
297 m_ui->downloadPath->setMode(FileSystemPathEdit::Mode::DirectorySave);
298 m_ui->downloadPath->setDialogCaption(tr("Choose save path"));
299 m_ui->downloadPath->setMaxVisibleItems(20);
301 m_ui->addToQueueTopCheckBox->setChecked(m_torrentParams.addToQueueTop.value_or(session->isAddTorrentToQueueTop()));
302 m_ui->startTorrentCheckBox->setChecked(!m_torrentParams.addPaused.value_or(session->isAddTorrentPaused()));
303 m_ui->stopConditionComboBox->setToolTip(
304 u"<html><body><p><b>" + tr("None") + u"</b> - " + tr("No stop condition is set.") + u"</p><p><b>" +
305 tr("Metadata received") + u"</b> - " + tr("Torrent will stop after metadata is received.") +
306 u" <em>" + tr("Torrents that have metadata initially aren't affected.") + u"</em></p><p><b>" +
307 tr("Files checked") + u"</b> - " + tr("Torrent will stop after files are initially checked.") +
308 u" <em>" + tr("This will also download metadata if it wasn't there initially.") + u"</em></p></body></html>");
309 m_ui->stopConditionComboBox->setItemData(0, QVariant::fromValue(BitTorrent::Torrent::StopCondition::None));
310 m_ui->stopConditionComboBox->setItemData(1, QVariant::fromValue(BitTorrent::Torrent::StopCondition::MetadataReceived));
311 m_ui->stopConditionComboBox->setItemData(2, QVariant::fromValue(BitTorrent::Torrent::StopCondition::FilesChecked));
312 m_ui->stopConditionComboBox->setCurrentIndex(m_ui->stopConditionComboBox->findData(
313 QVariant::fromValue(m_torrentParams.stopCondition.value_or(session->torrentStopCondition()))));
314 m_ui->stopConditionLabel->setEnabled(m_ui->startTorrentCheckBox->isChecked());
315 m_ui->stopConditionComboBox->setEnabled(m_ui->startTorrentCheckBox->isChecked());
316 connect(m_ui->startTorrentCheckBox, &QCheckBox::toggled, this, [this](const bool checked)
318 m_ui->stopConditionLabel->setEnabled(checked);
319 m_ui->stopConditionComboBox->setEnabled(checked);
322 m_ui->comboTTM->blockSignals(true); // the TreeView size isn't correct if the slot does its job at this point
323 m_ui->comboTTM->setCurrentIndex(session->isAutoTMMDisabledByDefault() ? 0 : 1);
324 m_ui->comboTTM->blockSignals(false);
325 connect(m_ui->comboTTM, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::TMMChanged);
327 connect(m_ui->savePath, &FileSystemPathEdit::selectedPathChanged, this, &AddNewTorrentDialog::onSavePathChanged);
328 connect(m_ui->downloadPath, &FileSystemPathEdit::selectedPathChanged, this, &AddNewTorrentDialog::onDownloadPathChanged);
329 connect(m_ui->groupBoxDownloadPath, &QGroupBox::toggled, this, &AddNewTorrentDialog::onUseDownloadPathChanged);
331 m_ui->checkBoxRememberLastSavePath->setChecked(m_storeRememberLastSavePath);
333 m_ui->contentLayoutComboBox->setCurrentIndex(
334 static_cast<int>(m_torrentParams.contentLayout.value_or(session->torrentContentLayout())));
335 connect(m_ui->contentLayoutComboBox, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::contentLayoutChanged);
337 m_ui->sequentialCheckBox->setChecked(m_torrentParams.sequential);
338 m_ui->firstLastCheckBox->setChecked(m_torrentParams.firstLastPiecePriority);
340 m_ui->skipCheckingCheckBox->setChecked(m_torrentParams.skipChecking);
341 m_ui->doNotDeleteTorrentCheckBox->setVisible(TorrentFileGuard::autoDeleteMode() != TorrentFileGuard::Never);
343 // Load categories
344 QStringList categories = session->categories();
345 std::sort(categories.begin(), categories.end(), Utils::Compare::NaturalLessThan<Qt::CaseInsensitive>());
346 const QString defaultCategory = m_storeDefaultCategory;
348 if (!m_torrentParams.category.isEmpty())
349 m_ui->categoryComboBox->addItem(m_torrentParams.category);
350 if (!defaultCategory.isEmpty())
351 m_ui->categoryComboBox->addItem(defaultCategory);
352 m_ui->categoryComboBox->addItem(u""_s);
354 for (const QString &category : asConst(categories))
356 if ((category != defaultCategory) && (category != m_torrentParams.category))
357 m_ui->categoryComboBox->addItem(category);
360 connect(m_ui->categoryComboBox, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::categoryChanged);
362 m_ui->tagsLineEdit->setText(m_torrentParams.tags.join(u", "_s));
363 connect(m_ui->tagsEditButton, &QAbstractButton::clicked, this, [this]
365 auto *dlg = new TorrentTagsDialog(m_torrentParams.tags, this);
366 dlg->setAttribute(Qt::WA_DeleteOnClose);
367 connect(dlg, &TorrentTagsDialog::accepted, this, [this, dlg]
369 m_torrentParams.tags = dlg->tags();
370 m_ui->tagsLineEdit->setText(m_torrentParams.tags.join(u", "_s));
372 dlg->open();
375 // Torrent content filtering
376 m_filterLine->setPlaceholderText(tr("Filter files..."));
377 m_filterLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
378 connect(m_filterLine, &LineEdit::textChanged, m_ui->contentTreeView, &TorrentContentWidget::setFilterPattern);
379 m_ui->contentFilterLayout->insertWidget(3, m_filterLine);
381 loadState();
383 connect(m_ui->buttonSelectAll, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkAll);
384 connect(m_ui->buttonSelectNone, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkNone);
386 if (const QByteArray state = m_storeTreeHeaderState; !state.isEmpty())
387 m_ui->contentTreeView->header()->restoreState(state);
388 // Hide useless columns after loading the header state
389 m_ui->contentTreeView->hideColumn(TorrentContentWidget::Progress);
390 m_ui->contentTreeView->hideColumn(TorrentContentWidget::Remaining);
391 m_ui->contentTreeView->hideColumn(TorrentContentWidget::Availability);
392 m_ui->contentTreeView->setColumnsVisibilityMode(TorrentContentWidget::ColumnsVisibilityMode::Locked);
393 m_ui->contentTreeView->setDoubleClickAction(TorrentContentWidget::DoubleClickAction::Rename);
395 m_ui->labelCommentData->setText(tr("Not Available", "This comment is unavailable"));
396 m_ui->labelDateData->setText(tr("Not Available", "This date is unavailable"));
398 m_filterLine->blockSignals(true);
400 // Default focus
401 if (m_ui->comboTTM->currentIndex() == 0) // 0 is Manual mode
402 m_ui->savePath->setFocus();
403 else
404 m_ui->categoryComboBox->setFocus();
406 connect(Preferences::instance(), &Preferences::changed, []
408 const int length = Preferences::instance()->addNewTorrentDialogSavePathHistoryLength();
409 settings()->storeValue(KEY_SAVEPATHHISTORY
410 , QStringList(settings()->loadValue<QStringList>(KEY_SAVEPATHHISTORY).mid(0, length)));
413 const BitTorrent::InfoHash infoHash = m_torrentDescr.infoHash();
415 m_ui->labelInfohash1Data->setText(infoHash.v1().isValid() ? infoHash.v1().toString() : tr("N/A"));
416 m_ui->labelInfohash2Data->setText(infoHash.v2().isValid() ? infoHash.v2().toString() : tr("N/A"));
418 if (hasMetadata())
420 setupTreeview();
422 else
424 // Set dialog title
425 const QString torrentName = m_torrentDescr.name();
426 setWindowTitle(torrentName.isEmpty() ? tr("Magnet link") : torrentName);
427 updateDiskSpaceLabel();
428 setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
431 TMMChanged(m_ui->comboTTM->currentIndex());
434 AddNewTorrentDialog::~AddNewTorrentDialog()
436 saveState();
437 delete m_ui;
440 BitTorrent::TorrentDescriptor AddNewTorrentDialog::torrentDescriptor() const
442 return m_torrentDescr;
445 BitTorrent::AddTorrentParams AddNewTorrentDialog::addTorrentParams() const
447 return m_torrentParams;
450 bool AddNewTorrentDialog::isDoNotDeleteTorrentChecked() const
452 return m_ui->doNotDeleteTorrentCheckBox->isChecked();
455 void AddNewTorrentDialog::loadState()
457 if (const QSize dialogSize = m_storeDialogSize; dialogSize.isValid())
458 resize(dialogSize);
460 m_ui->splitter->restoreState(m_storeSplitterState);;
463 void AddNewTorrentDialog::saveState()
465 m_storeDialogSize = size();
466 m_storeSplitterState = m_ui->splitter->saveState();
467 if (hasMetadata())
468 m_storeTreeHeaderState = m_ui->contentTreeView->header()->saveState();
471 void AddNewTorrentDialog::showEvent(QShowEvent *event)
473 QDialog::showEvent(event);
474 if (!Preferences::instance()->isAddNewTorrentDialogTopLevel())
475 return;
477 activateWindow();
478 raise();
481 void AddNewTorrentDialog::updateDiskSpaceLabel()
483 // Determine torrent size
484 qlonglong torrentSize = 0;
486 if (hasMetadata())
488 const auto torrentInfo = *m_torrentDescr.info();
489 const QVector<BitTorrent::DownloadPriority> &priorities = m_contentAdaptor->filePriorities();
490 Q_ASSERT(priorities.size() == torrentInfo.filesCount());
491 for (int i = 0; i < priorities.size(); ++i)
493 if (priorities[i] > BitTorrent::DownloadPriority::Ignored)
494 torrentSize += torrentInfo.fileSize(i);
498 const QString freeSpace = Utils::Misc::friendlyUnit(queryFreeDiskSpace(m_ui->savePath->selectedPath()));
499 const QString sizeString = tr("%1 (Free space on disk: %2)").arg(
500 ((torrentSize > 0) ? Utils::Misc::friendlyUnit(torrentSize) : tr("Not available", "This size is unavailable."))
501 , freeSpace);
502 m_ui->labelSizeData->setText(sizeString);
505 void AddNewTorrentDialog::onSavePathChanged([[maybe_unused]] const Path &newPath)
507 // Remember index
508 m_savePathIndex = m_ui->savePath->currentIndex();
509 updateDiskSpaceLabel();
512 void AddNewTorrentDialog::onDownloadPathChanged([[maybe_unused]] const Path &newPath)
514 // Remember index
515 const int currentPathIndex = m_ui->downloadPath->currentIndex();
516 if (currentPathIndex >= 0)
517 m_downloadPathIndex = m_ui->downloadPath->currentIndex();
520 void AddNewTorrentDialog::onUseDownloadPathChanged(const bool checked)
522 m_useDownloadPath = checked;
523 m_ui->downloadPath->setCurrentIndex(checked ? m_downloadPathIndex : -1);
526 void AddNewTorrentDialog::categoryChanged([[maybe_unused]] const int index)
528 if (m_ui->comboTTM->currentIndex() == 1)
530 const auto *btSession = BitTorrent::Session::instance();
531 const QString categoryName = m_ui->categoryComboBox->currentText();
533 const Path savePath = btSession->categorySavePath(categoryName);
534 m_ui->savePath->setSelectedPath(savePath);
536 const Path downloadPath = btSession->categoryDownloadPath(categoryName);
537 m_ui->downloadPath->setSelectedPath(downloadPath);
539 m_ui->groupBoxDownloadPath->setChecked(!m_ui->downloadPath->selectedPath().isEmpty());
541 updateDiskSpaceLabel();
545 void AddNewTorrentDialog::contentLayoutChanged()
547 if (!hasMetadata())
548 return;
550 const auto contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
551 m_contentAdaptor->applyContentLayout(contentLayout);
552 m_ui->contentTreeView->setContentHandler(m_contentAdaptor); // to cause reloading
555 void AddNewTorrentDialog::saveTorrentFile()
557 Q_ASSERT(hasMetadata());
558 if (!hasMetadata()) [[unlikely]]
559 return;
561 const auto torrentInfo = *m_torrentDescr.info();
563 const QString filter {tr("Torrent file (*%1)").arg(TORRENT_FILE_EXTENSION)};
565 Path path {QFileDialog::getSaveFileName(this, tr("Save as torrent file")
566 , QDir::home().absoluteFilePath(torrentInfo.name() + TORRENT_FILE_EXTENSION)
567 , filter)};
568 if (path.isEmpty()) return;
570 if (!path.hasExtension(TORRENT_FILE_EXTENSION))
571 path += TORRENT_FILE_EXTENSION;
573 const auto result = m_torrentDescr.saveToFile(path);
574 if (!result)
576 QMessageBox::critical(this, tr("I/O Error")
577 , tr("Couldn't export torrent metadata file '%1'. Reason: %2.").arg(path.toString(), result.error()));
581 bool AddNewTorrentDialog::hasMetadata() const
583 return m_torrentDescr.info().has_value();
586 void AddNewTorrentDialog::populateSavePaths()
588 const auto *btSession = BitTorrent::Session::instance();
590 m_ui->savePath->blockSignals(true);
591 m_ui->savePath->clear();
592 const auto savePathHistory = settings()->loadValue<QStringList>(KEY_SAVEPATHHISTORY);
593 if (savePathHistory.size() > 0)
595 for (const QString &path : savePathHistory)
596 m_ui->savePath->addItem(Path(path));
598 else
600 m_ui->savePath->addItem(btSession->savePath());
603 if (m_savePathIndex >= 0)
605 m_ui->savePath->setCurrentIndex(std::min(m_savePathIndex, (m_ui->savePath->count() - 1)));
607 else
609 if (!m_torrentParams.savePath.isEmpty())
610 setPath(m_ui->savePath, m_torrentParams.savePath);
611 else if (!m_storeRememberLastSavePath)
612 setPath(m_ui->savePath, btSession->savePath());
613 else
614 m_ui->savePath->setCurrentIndex(0);
616 m_savePathIndex = m_ui->savePath->currentIndex();
619 m_ui->savePath->blockSignals(false);
621 m_ui->downloadPath->blockSignals(true);
622 m_ui->downloadPath->clear();
623 const auto downloadPathHistory = settings()->loadValue<QStringList>(KEY_DOWNLOADPATHHISTORY);
624 if (downloadPathHistory.size() > 0)
626 for (const QString &path : downloadPathHistory)
627 m_ui->downloadPath->addItem(Path(path));
629 else
631 m_ui->downloadPath->addItem(btSession->downloadPath());
634 if (m_downloadPathIndex >= 0)
636 m_ui->downloadPath->setCurrentIndex(m_useDownloadPath ? std::min(m_downloadPathIndex, (m_ui->downloadPath->count() - 1)) : -1);
637 m_ui->groupBoxDownloadPath->setChecked(m_useDownloadPath);
639 else
641 const bool useDownloadPath = m_torrentParams.useDownloadPath.value_or(btSession->isDownloadPathEnabled());
642 m_ui->groupBoxDownloadPath->setChecked(useDownloadPath);
644 if (!m_torrentParams.downloadPath.isEmpty())
645 setPath(m_ui->downloadPath, m_torrentParams.downloadPath);
646 else if (!m_storeRememberLastSavePath)
647 setPath(m_ui->downloadPath, btSession->downloadPath());
648 else
649 m_ui->downloadPath->setCurrentIndex(0);
651 m_downloadPathIndex = m_ui->downloadPath->currentIndex();
652 if (!useDownloadPath)
653 m_ui->downloadPath->setCurrentIndex(-1);
656 m_ui->downloadPath->blockSignals(false);
657 m_ui->groupBoxDownloadPath->blockSignals(false);
660 void AddNewTorrentDialog::accept()
662 // TODO: Check if destination actually exists
663 m_torrentParams.skipChecking = m_ui->skipCheckingCheckBox->isChecked();
665 // Category
666 m_torrentParams.category = m_ui->categoryComboBox->currentText();
667 if (m_ui->defaultCategoryCheckbox->isChecked())
668 m_storeDefaultCategory = m_torrentParams.category;
670 m_storeRememberLastSavePath = m_ui->checkBoxRememberLastSavePath->isChecked();
672 m_torrentParams.addToQueueTop = m_ui->addToQueueTopCheckBox->isChecked();
673 m_torrentParams.addPaused = !m_ui->startTorrentCheckBox->isChecked();
674 m_torrentParams.stopCondition = m_ui->stopConditionComboBox->currentData().value<BitTorrent::Torrent::StopCondition>();
675 m_torrentParams.contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
677 m_torrentParams.sequential = m_ui->sequentialCheckBox->isChecked();
678 m_torrentParams.firstLastPiecePriority = m_ui->firstLastCheckBox->isChecked();
680 const bool useAutoTMM = (m_ui->comboTTM->currentIndex() == 1); // 1 is Automatic mode. Handle all non 1 values as manual mode.
681 m_torrentParams.useAutoTMM = useAutoTMM;
682 if (!useAutoTMM)
684 const int savePathHistoryLength = Preferences::instance()->addNewTorrentDialogSavePathHistoryLength();
685 const Path savePath = m_ui->savePath->selectedPath();
686 m_torrentParams.savePath = savePath;
687 updatePathHistory(KEY_SAVEPATHHISTORY, savePath, savePathHistoryLength);
689 m_torrentParams.useDownloadPath = m_ui->groupBoxDownloadPath->isChecked();
690 if (m_torrentParams.useDownloadPath)
692 const Path downloadPath = m_ui->downloadPath->selectedPath();
693 m_torrentParams.downloadPath = downloadPath;
694 updatePathHistory(KEY_DOWNLOADPATHHISTORY, downloadPath, savePathHistoryLength);
696 else
698 m_torrentParams.downloadPath = Path();
701 else
703 m_torrentParams.savePath = Path();
704 m_torrentParams.downloadPath = Path();
705 m_torrentParams.useDownloadPath = std::nullopt;
708 setEnabled(!m_ui->checkBoxNeverShow->isChecked());
710 QDialog::accept();
713 void AddNewTorrentDialog::reject()
715 if (!hasMetadata())
717 setMetadataProgressIndicator(false);
718 BitTorrent::Session::instance()->cancelDownloadMetadata(m_torrentDescr.infoHash().toTorrentID());
721 QDialog::reject();
724 void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
726 Q_ASSERT(metadata.isValid());
727 if (!metadata.isValid()) [[unlikely]]
728 return;
730 Q_ASSERT(metadata.matchesInfoHash(m_torrentDescr.infoHash()));
731 if (!metadata.matchesInfoHash(m_torrentDescr.infoHash())) [[unlikely]]
732 return;
734 m_torrentDescr.setTorrentInfo(metadata);
735 setMetadataProgressIndicator(true, tr("Parsing metadata..."));
736 const auto stopCondition = m_ui->stopConditionComboBox->currentData().value<BitTorrent::Torrent::StopCondition>();
737 if (stopCondition == BitTorrent::Torrent::StopCondition::MetadataReceived)
738 m_ui->startTorrentCheckBox->setChecked(false);
740 // Update UI
741 setupTreeview();
742 setMetadataProgressIndicator(false, tr("Metadata retrieval complete"));
744 m_ui->buttonSave->setVisible(true);
745 if (m_torrentDescr.infoHash().v2().isValid())
747 m_ui->buttonSave->setEnabled(false);
748 m_ui->buttonSave->setToolTip(tr("Cannot create v2 torrent until its data is fully downloaded."));
752 void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText)
754 // Always show info label when waiting for metadata
755 m_ui->lblMetaLoading->setVisible(true);
756 m_ui->lblMetaLoading->setText(labelText);
757 m_ui->progMetaLoading->setVisible(visibleIndicator);
760 void AddNewTorrentDialog::setupTreeview()
762 Q_ASSERT(hasMetadata());
763 if (!hasMetadata()) [[unlikely]]
764 return;
766 // Set dialog title
767 setWindowTitle(m_torrentDescr.name());
769 const auto &torrentInfo = *m_torrentDescr.info();
771 // Set torrent information
772 m_ui->labelCommentData->setText(Utils::Misc::parseHtmlLinks(torrentInfo.comment().toHtmlEscaped()));
773 m_ui->labelDateData->setText(!torrentInfo.creationDate().isNull() ? QLocale().toString(torrentInfo.creationDate(), QLocale::ShortFormat) : tr("Not available"));
775 if (m_torrentParams.filePaths.isEmpty())
776 m_torrentParams.filePaths = torrentInfo.filePaths();
778 m_contentAdaptor = new TorrentContentAdaptor(torrentInfo, m_torrentParams.filePaths, m_torrentParams.filePriorities);
780 const auto contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
781 m_contentAdaptor->applyContentLayout(contentLayout);
783 if (BitTorrent::Session::instance()->isExcludedFileNamesEnabled())
785 // Check file name blacklist for torrents that are manually added
786 QVector<BitTorrent::DownloadPriority> priorities = m_contentAdaptor->filePriorities();
787 for (int i = 0; i < priorities.size(); ++i)
789 if (priorities[i] == BitTorrent::DownloadPriority::Ignored)
790 continue;
792 if (BitTorrent::Session::instance()->isFilenameExcluded(torrentInfo.filePath(i).filename()))
793 priorities[i] = BitTorrent::DownloadPriority::Ignored;
796 m_contentAdaptor->prioritizeFiles(priorities);
799 m_ui->contentTreeView->setContentHandler(m_contentAdaptor);
801 m_filterLine->blockSignals(false);
803 updateDiskSpaceLabel();
806 void AddNewTorrentDialog::TMMChanged(int index)
808 if (index != 1)
809 { // 0 is Manual mode and 1 is Automatic mode. Handle all non 1 values as manual mode.
810 populateSavePaths();
811 m_ui->groupBoxSavePath->setEnabled(true);
813 else
815 const auto *session = BitTorrent::Session::instance();
817 m_ui->groupBoxSavePath->setEnabled(false);
819 m_ui->savePath->blockSignals(true);
820 m_ui->savePath->clear();
821 const Path savePath = session->categorySavePath(m_ui->categoryComboBox->currentText());
822 m_ui->savePath->addItem(savePath);
824 m_ui->downloadPath->blockSignals(true);
825 m_ui->downloadPath->clear();
826 const Path downloadPath = session->categoryDownloadPath(m_ui->categoryComboBox->currentText());
827 m_ui->downloadPath->addItem(downloadPath);
829 m_ui->groupBoxDownloadPath->blockSignals(true);
830 m_ui->groupBoxDownloadPath->setChecked(!downloadPath.isEmpty());
833 updateDiskSpaceLabel();