Add extra offset for dialog frame
[qBittorrent.git] / src / gui / addnewtorrentdialog.cpp
blobc586481fea92609f3023ecbcff50add660b1e058
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>
33 #include <functional>
35 #include <QAction>
36 #include <QByteArray>
37 #include <QDateTime>
38 #include <QDebug>
39 #include <QDir>
40 #include <QFileDialog>
41 #include <QMenu>
42 #include <QMessageBox>
43 #include <QPushButton>
44 #include <QShortcut>
45 #include <QSize>
46 #include <QString>
47 #include <QUrl>
48 #include <QVector>
50 #include "base/bittorrent/downloadpriority.h"
51 #include "base/bittorrent/infohash.h"
52 #include "base/bittorrent/session.h"
53 #include "base/bittorrent/torrent.h"
54 #include "base/bittorrent/torrentcontenthandler.h"
55 #include "base/bittorrent/torrentcontentlayout.h"
56 #include "base/global.h"
57 #include "base/preferences.h"
58 #include "base/settingsstorage.h"
59 #include "base/torrentfileguard.h"
60 #include "base/utils/compare.h"
61 #include "base/utils/fs.h"
62 #include "base/utils/misc.h"
63 #include "base/utils/string.h"
64 #include "lineedit.h"
65 #include "torrenttagsdialog.h"
67 #include "ui_addnewtorrentdialog.h"
69 namespace
71 #define SETTINGS_KEY(name) u"AddNewTorrentDialog/" name
72 const QString KEY_SAVEPATHHISTORY = SETTINGS_KEY(u"SavePathHistory"_s);
73 const QString KEY_DOWNLOADPATHHISTORY = SETTINGS_KEY(u"DownloadPathHistory"_s);
75 // just a shortcut
76 inline SettingsStorage *settings()
78 return SettingsStorage::instance();
81 // savePath is a folder, not an absolute file path
82 int indexOfPath(const FileSystemPathComboEdit *fsPathEdit, const Path &savePath)
84 for (int i = 0; i < fsPathEdit->count(); ++i)
86 if (fsPathEdit->item(i) == savePath)
87 return i;
89 return -1;
92 qint64 queryFreeDiskSpace(const Path &path)
94 const Path root = path.rootItem();
95 Path current = path;
96 qint64 freeSpace = Utils::Fs::freeDiskSpaceOnPath(current);
98 // for non-existent directories (which will be created on demand) `Utils::Fs::freeDiskSpaceOnPath`
99 // will return invalid value so instead query its parent/ancestor paths
100 while ((freeSpace < 0) && (current != root))
102 current = current.parentPath();
103 freeSpace = Utils::Fs::freeDiskSpaceOnPath(current);
105 return freeSpace;
108 void setPath(FileSystemPathComboEdit *fsPathEdit, const Path &newPath)
110 int existingIndex = indexOfPath(fsPathEdit, newPath);
111 if (existingIndex < 0)
113 // New path, prepend to combo box
114 fsPathEdit->insertItem(0, newPath);
115 existingIndex = 0;
118 fsPathEdit->setCurrentIndex(existingIndex);
121 void updatePathHistory(const QString &settingsKey, const Path &path, const int maxLength)
123 // Add last used save path to the front of history
125 auto pathList = settings()->loadValue<QStringList>(settingsKey);
127 const int selectedSavePathIndex = pathList.indexOf(path.toString());
128 if (selectedSavePathIndex > -1)
129 pathList.move(selectedSavePathIndex, 0);
130 else
131 pathList.prepend(path.toString());
133 settings()->storeValue(settingsKey, QStringList(pathList.mid(0, maxLength)));
137 class AddNewTorrentDialog::TorrentContentAdaptor final
138 : public BitTorrent::TorrentContentHandler
140 public:
141 TorrentContentAdaptor(const BitTorrent::TorrentInfo &torrentInfo, PathList &filePaths
142 , QVector<BitTorrent::DownloadPriority> &filePriorities, std::function<void ()> onFilePrioritiesChanged)
143 : m_torrentInfo {torrentInfo}
144 , m_filePaths {filePaths}
145 , m_filePriorities {filePriorities}
146 , m_onFilePrioritiesChanged {std::move(onFilePrioritiesChanged)}
148 Q_ASSERT(filePaths.isEmpty() || (filePaths.size() == m_torrentInfo.filesCount()));
150 m_originalRootFolder = Path::findRootFolder(m_torrentInfo.filePaths());
151 m_currentContentLayout = (m_originalRootFolder.isEmpty()
152 ? BitTorrent::TorrentContentLayout::NoSubfolder
153 : BitTorrent::TorrentContentLayout::Subfolder);
155 if (const int fileCount = filesCount(); !m_filePriorities.isEmpty() && (fileCount >= 0))
156 m_filePriorities.resize(fileCount, BitTorrent::DownloadPriority::Normal);
159 bool hasMetadata() const override
161 return m_torrentInfo.isValid();
164 int filesCount() const override
166 return m_torrentInfo.filesCount();
169 qlonglong fileSize(const int index) const override
171 Q_ASSERT((index >= 0) && (index < filesCount()));
172 return m_torrentInfo.fileSize(index);
175 Path filePath(const int index) const override
177 Q_ASSERT((index >= 0) && (index < filesCount()));
178 return (m_filePaths.isEmpty() ? m_torrentInfo.filePath(index) : m_filePaths.at(index));
181 void renameFile(const int index, const Path &newFilePath) override
183 Q_ASSERT((index >= 0) && (index < filesCount()));
184 const Path currentFilePath = filePath(index);
185 if (currentFilePath == newFilePath)
186 return;
188 if (m_filePaths.isEmpty())
189 m_filePaths = m_torrentInfo.filePaths();
191 m_filePaths[index] = newFilePath;
194 void applyContentLayout(const BitTorrent::TorrentContentLayout contentLayout)
196 Q_ASSERT(hasMetadata());
197 Q_ASSERT(!m_filePaths.isEmpty());
199 const auto originalContentLayout = (m_originalRootFolder.isEmpty()
200 ? BitTorrent::TorrentContentLayout::NoSubfolder
201 : BitTorrent::TorrentContentLayout::Subfolder);
202 const auto newContentLayout = ((contentLayout == BitTorrent::TorrentContentLayout::Original)
203 ? originalContentLayout : contentLayout);
204 if (newContentLayout != m_currentContentLayout)
206 if (newContentLayout == BitTorrent::TorrentContentLayout::NoSubfolder)
208 Path::stripRootFolder(m_filePaths);
210 else
212 const auto rootFolder = ((originalContentLayout == BitTorrent::TorrentContentLayout::Subfolder)
213 ? m_originalRootFolder : m_filePaths.at(0).removedExtension());
214 Path::addRootFolder(m_filePaths, rootFolder);
217 m_currentContentLayout = newContentLayout;
221 QVector<BitTorrent::DownloadPriority> filePriorities() const override
223 return m_filePriorities.isEmpty()
224 ? QVector<BitTorrent::DownloadPriority>(filesCount(), BitTorrent::DownloadPriority::Normal)
225 : m_filePriorities;
228 QVector<qreal> filesProgress() const override
230 return QVector<qreal>(filesCount(), 0);
233 QVector<qreal> availableFileFractions() const override
235 return QVector<qreal>(filesCount(), 0);
238 void fetchAvailableFileFractions(std::function<void (QVector<qreal>)> resultHandler) const override
240 resultHandler(availableFileFractions());
243 void prioritizeFiles(const QVector<BitTorrent::DownloadPriority> &priorities) override
245 Q_ASSERT(priorities.size() == filesCount());
246 m_filePriorities = priorities;
247 if (m_onFilePrioritiesChanged)
248 m_onFilePrioritiesChanged();
251 Path actualStorageLocation() const override
253 return {};
256 Path actualFilePath([[maybe_unused]] int fileIndex) const override
258 return {};
261 void flushCache() const override
265 private:
266 const BitTorrent::TorrentInfo &m_torrentInfo;
267 PathList &m_filePaths;
268 QVector<BitTorrent::DownloadPriority> &m_filePriorities;
269 std::function<void ()> m_onFilePrioritiesChanged;
270 Path m_originalRootFolder;
271 BitTorrent::TorrentContentLayout m_currentContentLayout;
274 AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::TorrentDescriptor &torrentDescr
275 , const BitTorrent::AddTorrentParams &inParams, QWidget *parent)
276 : QDialog(parent)
277 , m_ui {new Ui::AddNewTorrentDialog}
278 , m_torrentDescr {torrentDescr}
279 , m_torrentParams {inParams}
280 , m_filterLine {new LineEdit(this)}
281 , m_storeDialogSize {SETTINGS_KEY(u"DialogSize"_s)}
282 , m_storeDefaultCategory {SETTINGS_KEY(u"DefaultCategory"_s)}
283 , m_storeRememberLastSavePath {SETTINGS_KEY(u"RememberLastSavePath"_s)}
284 , m_storeTreeHeaderState {u"GUI/Qt6/" SETTINGS_KEY(u"TreeHeaderState"_s)}
285 , m_storeSplitterState {u"GUI/Qt6/" SETTINGS_KEY(u"SplitterState"_s)}
287 // TODO: set dialog file properties using m_torrentParams.filePriorities
288 m_ui->setupUi(this);
290 connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
291 connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
293 m_ui->lblMetaLoading->setVisible(false);
294 m_ui->progMetaLoading->setVisible(false);
295 m_ui->buttonSave->setVisible(false);
296 connect(m_ui->buttonSave, &QPushButton::clicked, this, &AddNewTorrentDialog::saveTorrentFile);
298 m_ui->savePath->setMode(FileSystemPathEdit::Mode::DirectorySave);
299 m_ui->savePath->setDialogCaption(tr("Choose save path"));
300 m_ui->savePath->setMaxVisibleItems(20);
302 const auto *session = BitTorrent::Session::instance();
304 m_ui->downloadPath->setMode(FileSystemPathEdit::Mode::DirectorySave);
305 m_ui->downloadPath->setDialogCaption(tr("Choose save path"));
306 m_ui->downloadPath->setMaxVisibleItems(20);
308 m_ui->addToQueueTopCheckBox->setChecked(m_torrentParams.addToQueueTop.value_or(session->isAddTorrentToQueueTop()));
310 m_ui->stopConditionComboBox->setToolTip(
311 u"<html><body><p><b>" + tr("None") + u"</b> - " + tr("No stop condition is set.") + u"</p><p><b>" +
312 tr("Metadata received") + u"</b> - " + tr("Torrent will stop after metadata is received.") +
313 u" <em>" + tr("Torrents that have metadata initially will be added as stopped.") + u"</em></p><p><b>" +
314 tr("Files checked") + u"</b> - " + tr("Torrent will stop after files are initially checked.") +
315 u" <em>" + tr("This will also download metadata if it wasn't there initially.") + u"</em></p></body></html>");
316 m_ui->stopConditionComboBox->addItem(tr("None"), QVariant::fromValue(BitTorrent::Torrent::StopCondition::None));
317 if (!hasMetadata())
318 m_ui->stopConditionComboBox->addItem(tr("Metadata received"), QVariant::fromValue(BitTorrent::Torrent::StopCondition::MetadataReceived));
319 m_ui->stopConditionComboBox->addItem(tr("Files checked"), QVariant::fromValue(BitTorrent::Torrent::StopCondition::FilesChecked));
320 const auto stopCondition = m_torrentParams.stopCondition.value_or(session->torrentStopCondition());
321 if (hasMetadata() && (stopCondition == BitTorrent::Torrent::StopCondition::MetadataReceived))
323 m_ui->startTorrentCheckBox->setChecked(false);
324 m_ui->stopConditionComboBox->setCurrentIndex(m_ui->stopConditionComboBox->findData(QVariant::fromValue(BitTorrent::Torrent::StopCondition::None)));
326 else
328 m_ui->startTorrentCheckBox->setChecked(!m_torrentParams.addStopped.value_or(session->isAddTorrentStopped()));
329 m_ui->stopConditionComboBox->setCurrentIndex(m_ui->stopConditionComboBox->findData(QVariant::fromValue(stopCondition)));
331 m_ui->stopConditionLabel->setEnabled(m_ui->startTorrentCheckBox->isChecked());
332 m_ui->stopConditionComboBox->setEnabled(m_ui->startTorrentCheckBox->isChecked());
333 connect(m_ui->startTorrentCheckBox, &QCheckBox::toggled, this, [this](const bool checked)
335 m_ui->stopConditionLabel->setEnabled(checked);
336 m_ui->stopConditionComboBox->setEnabled(checked);
339 m_ui->comboTTM->blockSignals(true); // the TreeView size isn't correct if the slot does its job at this point
340 m_ui->comboTTM->setCurrentIndex(session->isAutoTMMDisabledByDefault() ? 0 : 1);
341 m_ui->comboTTM->blockSignals(false);
342 connect(m_ui->comboTTM, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::TMMChanged);
344 connect(m_ui->savePath, &FileSystemPathEdit::selectedPathChanged, this, &AddNewTorrentDialog::onSavePathChanged);
345 connect(m_ui->downloadPath, &FileSystemPathEdit::selectedPathChanged, this, &AddNewTorrentDialog::onDownloadPathChanged);
346 connect(m_ui->groupBoxDownloadPath, &QGroupBox::toggled, this, &AddNewTorrentDialog::onUseDownloadPathChanged);
348 m_ui->checkBoxRememberLastSavePath->setChecked(m_storeRememberLastSavePath);
350 m_ui->contentLayoutComboBox->setCurrentIndex(
351 static_cast<int>(m_torrentParams.contentLayout.value_or(session->torrentContentLayout())));
352 connect(m_ui->contentLayoutComboBox, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::contentLayoutChanged);
354 m_ui->sequentialCheckBox->setChecked(m_torrentParams.sequential);
355 m_ui->firstLastCheckBox->setChecked(m_torrentParams.firstLastPiecePriority);
357 m_ui->skipCheckingCheckBox->setChecked(m_torrentParams.skipChecking);
358 m_ui->doNotDeleteTorrentCheckBox->setVisible(TorrentFileGuard::autoDeleteMode() != TorrentFileGuard::Never);
360 // Load categories
361 QStringList categories = session->categories();
362 std::sort(categories.begin(), categories.end(), Utils::Compare::NaturalLessThan<Qt::CaseInsensitive>());
363 const QString defaultCategory = m_storeDefaultCategory;
365 if (!m_torrentParams.category.isEmpty())
366 m_ui->categoryComboBox->addItem(m_torrentParams.category);
367 if (!defaultCategory.isEmpty())
368 m_ui->categoryComboBox->addItem(defaultCategory);
369 m_ui->categoryComboBox->addItem(u""_s);
371 for (const QString &category : asConst(categories))
373 if ((category != defaultCategory) && (category != m_torrentParams.category))
374 m_ui->categoryComboBox->addItem(category);
377 connect(m_ui->categoryComboBox, &QComboBox::currentIndexChanged, this, &AddNewTorrentDialog::categoryChanged);
379 m_ui->tagsLineEdit->setText(Utils::String::joinIntoString(m_torrentParams.tags, u", "_s));
380 connect(m_ui->tagsEditButton, &QAbstractButton::clicked, this, [this]
382 auto *dlg = new TorrentTagsDialog(m_torrentParams.tags, this);
383 dlg->setAttribute(Qt::WA_DeleteOnClose);
384 connect(dlg, &TorrentTagsDialog::accepted, this, [this, dlg]
386 m_torrentParams.tags = dlg->tags();
387 m_ui->tagsLineEdit->setText(Utils::String::joinIntoString(m_torrentParams.tags, u", "_s));
389 dlg->open();
392 // Torrent content filtering
393 m_filterLine->setPlaceholderText(tr("Filter files..."));
394 m_filterLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
395 connect(m_filterLine, &LineEdit::textChanged, m_ui->contentTreeView, &TorrentContentWidget::setFilterPattern);
396 m_ui->contentFilterLayout->insertWidget(3, m_filterLine);
397 const auto *focusSearchHotkey = new QShortcut(QKeySequence::Find, this);
398 connect(focusSearchHotkey, &QShortcut::activated, this, [this]()
400 m_filterLine->setFocus();
401 m_filterLine->selectAll();
404 loadState();
406 connect(m_ui->buttonSelectAll, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkAll);
407 connect(m_ui->buttonSelectNone, &QPushButton::clicked, m_ui->contentTreeView, &TorrentContentWidget::checkNone);
409 if (const QByteArray state = m_storeTreeHeaderState; !state.isEmpty())
410 m_ui->contentTreeView->header()->restoreState(state);
411 // Hide useless columns after loading the header state
412 m_ui->contentTreeView->hideColumn(TorrentContentWidget::Progress);
413 m_ui->contentTreeView->hideColumn(TorrentContentWidget::Remaining);
414 m_ui->contentTreeView->hideColumn(TorrentContentWidget::Availability);
415 m_ui->contentTreeView->setColumnsVisibilityMode(TorrentContentWidget::ColumnsVisibilityMode::Locked);
416 m_ui->contentTreeView->setDoubleClickAction(TorrentContentWidget::DoubleClickAction::Rename);
418 m_ui->labelCommentData->setText(tr("Not Available", "This comment is unavailable"));
419 m_ui->labelDateData->setText(tr("Not Available", "This date is unavailable"));
421 m_filterLine->blockSignals(true);
423 // Default focus
424 if (m_ui->comboTTM->currentIndex() == 0) // 0 is Manual mode
425 m_ui->savePath->setFocus();
426 else
427 m_ui->categoryComboBox->setFocus();
429 connect(Preferences::instance(), &Preferences::changed, []
431 const int length = Preferences::instance()->addNewTorrentDialogSavePathHistoryLength();
432 settings()->storeValue(KEY_SAVEPATHHISTORY
433 , QStringList(settings()->loadValue<QStringList>(KEY_SAVEPATHHISTORY).mid(0, length)));
436 const BitTorrent::InfoHash infoHash = m_torrentDescr.infoHash();
438 m_ui->labelInfohash1Data->setText(infoHash.v1().isValid() ? infoHash.v1().toString() : tr("N/A"));
439 m_ui->labelInfohash2Data->setText(infoHash.v2().isValid() ? infoHash.v2().toString() : tr("N/A"));
441 if (hasMetadata())
443 setupTreeview();
445 else
447 // Set dialog title
448 const QString torrentName = m_torrentDescr.name();
449 setWindowTitle(torrentName.isEmpty() ? tr("Magnet link") : torrentName);
450 updateDiskSpaceLabel();
451 setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
454 TMMChanged(m_ui->comboTTM->currentIndex());
457 AddNewTorrentDialog::~AddNewTorrentDialog()
459 saveState();
460 delete m_ui;
463 BitTorrent::TorrentDescriptor AddNewTorrentDialog::torrentDescriptor() const
465 return m_torrentDescr;
468 BitTorrent::AddTorrentParams AddNewTorrentDialog::addTorrentParams() const
470 return m_torrentParams;
473 bool AddNewTorrentDialog::isDoNotDeleteTorrentChecked() const
475 return m_ui->doNotDeleteTorrentCheckBox->isChecked();
478 void AddNewTorrentDialog::loadState()
480 if (const QSize dialogSize = m_storeDialogSize; dialogSize.isValid())
481 resize(dialogSize);
483 m_ui->splitter->restoreState(m_storeSplitterState);;
486 void AddNewTorrentDialog::saveState()
488 m_storeDialogSize = size();
489 m_storeSplitterState = m_ui->splitter->saveState();
490 if (hasMetadata())
491 m_storeTreeHeaderState = m_ui->contentTreeView->header()->saveState();
494 void AddNewTorrentDialog::showEvent(QShowEvent *event)
496 QDialog::showEvent(event);
497 if (!Preferences::instance()->isAddNewTorrentDialogTopLevel())
498 return;
500 activateWindow();
501 raise();
504 void AddNewTorrentDialog::updateDiskSpaceLabel()
506 // Determine torrent size
507 qlonglong torrentSize = 0;
509 if (hasMetadata())
511 const auto torrentInfo = *m_torrentDescr.info();
512 const QVector<BitTorrent::DownloadPriority> &priorities = m_contentAdaptor->filePriorities();
513 Q_ASSERT(priorities.size() == torrentInfo.filesCount());
514 for (int i = 0; i < priorities.size(); ++i)
516 if (priorities[i] > BitTorrent::DownloadPriority::Ignored)
517 torrentSize += torrentInfo.fileSize(i);
521 const QString freeSpace = Utils::Misc::friendlyUnit(queryFreeDiskSpace(m_ui->savePath->selectedPath()));
522 const QString sizeString = tr("%1 (Free space on disk: %2)").arg(
523 ((torrentSize > 0) ? Utils::Misc::friendlyUnit(torrentSize) : tr("Not available", "This size is unavailable."))
524 , freeSpace);
525 m_ui->labelSizeData->setText(sizeString);
528 void AddNewTorrentDialog::onSavePathChanged([[maybe_unused]] const Path &newPath)
530 // Remember index
531 m_savePathIndex = m_ui->savePath->currentIndex();
532 updateDiskSpaceLabel();
535 void AddNewTorrentDialog::onDownloadPathChanged([[maybe_unused]] const Path &newPath)
537 // Remember index
538 const int currentPathIndex = m_ui->downloadPath->currentIndex();
539 if (currentPathIndex >= 0)
540 m_downloadPathIndex = m_ui->downloadPath->currentIndex();
543 void AddNewTorrentDialog::onUseDownloadPathChanged(const bool checked)
545 m_useDownloadPath = checked;
546 m_ui->downloadPath->setCurrentIndex(checked ? m_downloadPathIndex : -1);
549 void AddNewTorrentDialog::categoryChanged([[maybe_unused]] const int index)
551 if (m_ui->comboTTM->currentIndex() == 1)
553 const auto *btSession = BitTorrent::Session::instance();
554 const QString categoryName = m_ui->categoryComboBox->currentText();
556 const Path savePath = btSession->categorySavePath(categoryName);
557 m_ui->savePath->setSelectedPath(savePath);
559 const Path downloadPath = btSession->categoryDownloadPath(categoryName);
560 m_ui->downloadPath->setSelectedPath(downloadPath);
562 m_ui->groupBoxDownloadPath->setChecked(!m_ui->downloadPath->selectedPath().isEmpty());
564 updateDiskSpaceLabel();
568 void AddNewTorrentDialog::contentLayoutChanged()
570 if (!hasMetadata())
571 return;
573 const auto contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
574 m_contentAdaptor->applyContentLayout(contentLayout);
575 m_ui->contentTreeView->setContentHandler(m_contentAdaptor.get()); // to cause reloading
578 void AddNewTorrentDialog::saveTorrentFile()
580 Q_ASSERT(hasMetadata());
581 if (!hasMetadata()) [[unlikely]]
582 return;
584 const auto torrentInfo = *m_torrentDescr.info();
586 const QString filter {tr("Torrent file (*%1)").arg(TORRENT_FILE_EXTENSION)};
588 Path path {QFileDialog::getSaveFileName(this, tr("Save as torrent file")
589 , QDir::home().absoluteFilePath(torrentInfo.name() + TORRENT_FILE_EXTENSION)
590 , filter)};
591 if (path.isEmpty()) return;
593 if (!path.hasExtension(TORRENT_FILE_EXTENSION))
594 path += TORRENT_FILE_EXTENSION;
596 const auto result = m_torrentDescr.saveToFile(path);
597 if (!result)
599 QMessageBox::critical(this, tr("I/O Error")
600 , tr("Couldn't export torrent metadata file '%1'. Reason: %2.").arg(path.toString(), result.error()));
604 bool AddNewTorrentDialog::hasMetadata() const
606 return m_torrentDescr.info().has_value();
609 void AddNewTorrentDialog::populateSavePaths()
611 const auto *btSession = BitTorrent::Session::instance();
613 m_ui->savePath->blockSignals(true);
614 m_ui->savePath->clear();
615 const auto savePathHistory = settings()->loadValue<QStringList>(KEY_SAVEPATHHISTORY);
616 if (savePathHistory.size() > 0)
618 for (const QString &path : savePathHistory)
619 m_ui->savePath->addItem(Path(path));
621 else
623 m_ui->savePath->addItem(btSession->savePath());
626 if (m_savePathIndex >= 0)
628 m_ui->savePath->setCurrentIndex(std::min(m_savePathIndex, (m_ui->savePath->count() - 1)));
630 else
632 if (!m_torrentParams.savePath.isEmpty())
633 setPath(m_ui->savePath, m_torrentParams.savePath);
634 else if (!m_storeRememberLastSavePath)
635 setPath(m_ui->savePath, btSession->savePath());
636 else
637 m_ui->savePath->setCurrentIndex(0);
639 m_savePathIndex = m_ui->savePath->currentIndex();
642 m_ui->savePath->blockSignals(false);
644 m_ui->downloadPath->blockSignals(true);
645 m_ui->downloadPath->clear();
646 const auto downloadPathHistory = settings()->loadValue<QStringList>(KEY_DOWNLOADPATHHISTORY);
647 if (downloadPathHistory.size() > 0)
649 for (const QString &path : downloadPathHistory)
650 m_ui->downloadPath->addItem(Path(path));
652 else
654 m_ui->downloadPath->addItem(btSession->downloadPath());
657 if (m_downloadPathIndex >= 0)
659 m_ui->downloadPath->setCurrentIndex(m_useDownloadPath ? std::min(m_downloadPathIndex, (m_ui->downloadPath->count() - 1)) : -1);
660 m_ui->groupBoxDownloadPath->setChecked(m_useDownloadPath);
662 else
664 const bool useDownloadPath = m_torrentParams.useDownloadPath.value_or(btSession->isDownloadPathEnabled());
665 m_ui->groupBoxDownloadPath->setChecked(useDownloadPath);
667 if (!m_torrentParams.downloadPath.isEmpty())
668 setPath(m_ui->downloadPath, m_torrentParams.downloadPath);
669 else if (!m_storeRememberLastSavePath)
670 setPath(m_ui->downloadPath, btSession->downloadPath());
671 else
672 m_ui->downloadPath->setCurrentIndex(0);
674 m_downloadPathIndex = m_ui->downloadPath->currentIndex();
675 if (!useDownloadPath)
676 m_ui->downloadPath->setCurrentIndex(-1);
679 m_ui->downloadPath->blockSignals(false);
680 m_ui->groupBoxDownloadPath->blockSignals(false);
683 void AddNewTorrentDialog::accept()
685 // TODO: Check if destination actually exists
686 m_torrentParams.skipChecking = m_ui->skipCheckingCheckBox->isChecked();
688 // Category
689 m_torrentParams.category = m_ui->categoryComboBox->currentText();
690 if (m_ui->defaultCategoryCheckbox->isChecked())
691 m_storeDefaultCategory = m_torrentParams.category;
693 m_storeRememberLastSavePath = m_ui->checkBoxRememberLastSavePath->isChecked();
695 m_torrentParams.addToQueueTop = m_ui->addToQueueTopCheckBox->isChecked();
696 m_torrentParams.addStopped = !m_ui->startTorrentCheckBox->isChecked();
697 m_torrentParams.stopCondition = m_ui->stopConditionComboBox->currentData().value<BitTorrent::Torrent::StopCondition>();
698 m_torrentParams.contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
700 m_torrentParams.sequential = m_ui->sequentialCheckBox->isChecked();
701 m_torrentParams.firstLastPiecePriority = m_ui->firstLastCheckBox->isChecked();
703 const bool useAutoTMM = (m_ui->comboTTM->currentIndex() == 1); // 1 is Automatic mode. Handle all non 1 values as manual mode.
704 m_torrentParams.useAutoTMM = useAutoTMM;
705 if (!useAutoTMM)
707 const int savePathHistoryLength = Preferences::instance()->addNewTorrentDialogSavePathHistoryLength();
708 const Path savePath = m_ui->savePath->selectedPath();
709 m_torrentParams.savePath = savePath;
710 updatePathHistory(KEY_SAVEPATHHISTORY, savePath, savePathHistoryLength);
712 m_torrentParams.useDownloadPath = m_ui->groupBoxDownloadPath->isChecked();
713 if (m_torrentParams.useDownloadPath)
715 const Path downloadPath = m_ui->downloadPath->selectedPath();
716 m_torrentParams.downloadPath = downloadPath;
717 updatePathHistory(KEY_DOWNLOADPATHHISTORY, downloadPath, savePathHistoryLength);
719 else
721 m_torrentParams.downloadPath = Path();
724 else
726 m_torrentParams.savePath = Path();
727 m_torrentParams.downloadPath = Path();
728 m_torrentParams.useDownloadPath = std::nullopt;
731 setEnabled(!m_ui->checkBoxNeverShow->isChecked());
733 QDialog::accept();
736 void AddNewTorrentDialog::reject()
738 if (!hasMetadata())
740 setMetadataProgressIndicator(false);
741 BitTorrent::Session::instance()->cancelDownloadMetadata(m_torrentDescr.infoHash().toTorrentID());
744 QDialog::reject();
747 void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
749 Q_ASSERT(metadata.isValid());
750 if (!metadata.isValid()) [[unlikely]]
751 return;
753 Q_ASSERT(metadata.matchesInfoHash(m_torrentDescr.infoHash()));
754 if (!metadata.matchesInfoHash(m_torrentDescr.infoHash())) [[unlikely]]
755 return;
757 m_torrentDescr.setTorrentInfo(metadata);
758 setMetadataProgressIndicator(true, tr("Parsing metadata..."));
760 // Update UI
761 setupTreeview();
762 setMetadataProgressIndicator(false, tr("Metadata retrieval complete"));
764 if (const auto stopCondition = m_ui->stopConditionComboBox->currentData().value<BitTorrent::Torrent::StopCondition>()
765 ; stopCondition == BitTorrent::Torrent::StopCondition::MetadataReceived)
767 m_ui->startTorrentCheckBox->setChecked(false);
769 const auto index = m_ui->stopConditionComboBox->currentIndex();
770 m_ui->stopConditionComboBox->setCurrentIndex(m_ui->stopConditionComboBox->findData(
771 QVariant::fromValue(BitTorrent::Torrent::StopCondition::None)));
772 m_ui->stopConditionComboBox->removeItem(index);
775 m_ui->buttonSave->setVisible(true);
776 if (m_torrentDescr.infoHash().v2().isValid())
778 m_ui->buttonSave->setEnabled(false);
779 m_ui->buttonSave->setToolTip(tr("Cannot create v2 torrent until its data is fully downloaded."));
783 void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText)
785 // Always show info label when waiting for metadata
786 m_ui->lblMetaLoading->setVisible(true);
787 m_ui->lblMetaLoading->setText(labelText);
788 m_ui->progMetaLoading->setVisible(visibleIndicator);
791 void AddNewTorrentDialog::setupTreeview()
793 Q_ASSERT(hasMetadata());
794 if (!hasMetadata()) [[unlikely]]
795 return;
797 // Set dialog title
798 setWindowTitle(m_torrentDescr.name());
800 const auto &torrentInfo = *m_torrentDescr.info();
802 // Set torrent information
803 m_ui->labelCommentData->setText(Utils::Misc::parseHtmlLinks(torrentInfo.comment().toHtmlEscaped()));
804 m_ui->labelDateData->setText(!torrentInfo.creationDate().isNull() ? QLocale().toString(torrentInfo.creationDate(), QLocale::ShortFormat) : tr("Not available"));
806 if (m_torrentParams.filePaths.isEmpty())
807 m_torrentParams.filePaths = torrentInfo.filePaths();
809 m_contentAdaptor = std::make_unique<TorrentContentAdaptor>(torrentInfo, m_torrentParams.filePaths
810 , m_torrentParams.filePriorities, [this] { updateDiskSpaceLabel(); });
812 const auto contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
813 m_contentAdaptor->applyContentLayout(contentLayout);
815 if (BitTorrent::Session::instance()->isExcludedFileNamesEnabled())
817 // Check file name blacklist for torrents that are manually added
818 QVector<BitTorrent::DownloadPriority> priorities = m_contentAdaptor->filePriorities();
819 for (int i = 0; i < priorities.size(); ++i)
821 if (priorities[i] == BitTorrent::DownloadPriority::Ignored)
822 continue;
824 if (BitTorrent::Session::instance()->isFilenameExcluded(torrentInfo.filePath(i).filename()))
825 priorities[i] = BitTorrent::DownloadPriority::Ignored;
828 m_contentAdaptor->prioritizeFiles(priorities);
831 m_ui->contentTreeView->setContentHandler(m_contentAdaptor.get());
833 m_filterLine->blockSignals(false);
835 updateDiskSpaceLabel();
838 void AddNewTorrentDialog::TMMChanged(int index)
840 if (index != 1)
841 { // 0 is Manual mode and 1 is Automatic mode. Handle all non 1 values as manual mode.
842 populateSavePaths();
843 m_ui->groupBoxSavePath->setEnabled(true);
845 else
847 const auto *session = BitTorrent::Session::instance();
849 m_ui->groupBoxSavePath->setEnabled(false);
851 m_ui->savePath->blockSignals(true);
852 m_ui->savePath->clear();
853 const Path savePath = session->categorySavePath(m_ui->categoryComboBox->currentText());
854 m_ui->savePath->addItem(savePath);
856 m_ui->downloadPath->blockSignals(true);
857 m_ui->downloadPath->clear();
858 const Path downloadPath = session->categoryDownloadPath(m_ui->categoryComboBox->currentText());
859 m_ui->downloadPath->addItem(downloadPath);
861 m_ui->groupBoxDownloadPath->blockSignals(true);
862 m_ui->groupBoxDownloadPath->setChecked(!downloadPath.isEmpty());
865 updateDiskSpaceLabel();