Sync translations from Transifex and run lupdate
[qBittorrent.git] / src / gui / addnewtorrentdialog.cpp
blobc9d466313190c4458537f496984445cbfe6dc39d
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2012 Christophe Dumez <chris@qbittorrent.org>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * In addition, as a special exception, the copyright holders give permission to
20 * link this program with the OpenSSL project's "OpenSSL" library (or with
21 * modified versions of it that use the same license as the "OpenSSL" library),
22 * and distribute the linked executables. You must obey the GNU General Public
23 * License in all respects for all of the code used other than "OpenSSL". If you
24 * modify file(s), you may extend this exception to your version of the file(s),
25 * but you are not obligated to do so. If you do not wish to do so, delete this
26 * exception statement from your version.
29 #include "addnewtorrentdialog.h"
31 #include <QDebug>
32 #include <QDir>
33 #include <QFileDialog>
34 #include <QMenu>
35 #include <QPushButton>
36 #include <QShortcut>
37 #include <QString>
38 #include <QUrl>
39 #include <QVector>
41 #include "base/bittorrent/downloadpriority.h"
42 #include "base/bittorrent/infohash.h"
43 #include "base/bittorrent/magneturi.h"
44 #include "base/bittorrent/session.h"
45 #include "base/bittorrent/torrent.h"
46 #include "base/exceptions.h"
47 #include "base/global.h"
48 #include "base/net/downloadmanager.h"
49 #include "base/settingsstorage.h"
50 #include "base/torrentfileguard.h"
51 #include "base/utils/compare.h"
52 #include "base/utils/fs.h"
53 #include "base/utils/misc.h"
54 #include "autoexpandabledialog.h"
55 #include "properties/proplistdelegate.h"
56 #include "raisedmessagebox.h"
57 #include "torrentcontentfiltermodel.h"
58 #include "torrentcontentmodel.h"
59 #include "ui_addnewtorrentdialog.h"
60 #include "uithememanager.h"
61 #include "utils.h"
63 namespace
65 #define SETTINGS_KEY(name) "AddNewTorrentDialog/" name
66 const QString KEY_ENABLED = QStringLiteral(SETTINGS_KEY("Enabled"));
67 const QString KEY_DEFAULTCATEGORY = QStringLiteral(SETTINGS_KEY("DefaultCategory"));
68 const QString KEY_TREEHEADERSTATE = QStringLiteral(SETTINGS_KEY("TreeHeaderState"));
69 const QString KEY_TOPLEVEL = QStringLiteral(SETTINGS_KEY("TopLevel"));
70 const QString KEY_SAVEPATHHISTORY = QStringLiteral(SETTINGS_KEY("SavePathHistory"));
71 const QString KEY_SAVEPATHHISTORYLENGTH = QStringLiteral(SETTINGS_KEY("SavePathHistoryLength"));
72 const QString KEY_REMEMBERLASTSAVEPATH = QStringLiteral(SETTINGS_KEY("RememberLastSavePath"));
74 // just a shortcut
75 inline SettingsStorage *settings()
77 return SettingsStorage::instance();
81 const int AddNewTorrentDialog::minPathHistoryLength;
82 const int AddNewTorrentDialog::maxPathHistoryLength;
84 AddNewTorrentDialog::AddNewTorrentDialog(const BitTorrent::AddTorrentParams &inParams, QWidget *parent)
85 : QDialog(parent)
86 , m_ui(new Ui::AddNewTorrentDialog)
87 , m_contentModel(nullptr)
88 , m_contentDelegate(nullptr)
89 , m_hasMetadata(false)
90 , m_oldIndex(0)
91 , m_torrentParams(inParams)
92 , m_storeDialogSize(SETTINGS_KEY("DialogSize"))
93 , m_storeSplitterState(SETTINGS_KEY("SplitterState"))
95 // TODO: set dialog file properties using m_torrentParams.filePriorities
96 m_ui->setupUi(this);
97 setAttribute(Qt::WA_DeleteOnClose);
99 m_ui->lblMetaLoading->setVisible(false);
100 m_ui->progMetaLoading->setVisible(false);
101 m_ui->buttonSave->setVisible(false);
102 connect(m_ui->buttonSave, &QPushButton::clicked, this, &AddNewTorrentDialog::saveTorrentFile);
104 m_ui->savePath->setMode(FileSystemPathEdit::Mode::DirectorySave);
105 m_ui->savePath->setDialogCaption(tr("Choose save path"));
106 m_ui->savePath->setMaxVisibleItems(20);
108 const auto *session = BitTorrent::Session::instance();
110 m_ui->startTorrentCheckBox->setChecked(!m_torrentParams.addPaused.value_or(session->isAddTorrentPaused()));
112 m_ui->comboTTM->blockSignals(true); // the TreeView size isn't correct if the slot does it job at this point
113 m_ui->comboTTM->setCurrentIndex(!session->isAutoTMMDisabledByDefault());
114 m_ui->comboTTM->blockSignals(false);
115 populateSavePathComboBox();
116 connect(m_ui->savePath, &FileSystemPathEdit::selectedPathChanged, this, &AddNewTorrentDialog::onSavePathChanged);
118 const bool rememberLastSavePath = settings()->loadValue(KEY_REMEMBERLASTSAVEPATH, false);
119 m_ui->checkBoxRememberLastSavePath->setChecked(rememberLastSavePath);
121 m_ui->contentLayoutComboBox->setCurrentIndex(
122 static_cast<int>(m_torrentParams.contentLayout.value_or(session->torrentContentLayout())));
124 m_ui->sequentialCheckBox->setChecked(m_torrentParams.sequential);
125 m_ui->firstLastCheckBox->setChecked(m_torrentParams.firstLastPiecePriority);
127 m_ui->skipCheckingCheckBox->setChecked(m_torrentParams.skipChecking);
128 m_ui->doNotDeleteTorrentCheckBox->setVisible(TorrentFileGuard::autoDeleteMode() != TorrentFileGuard::Never);
130 // Load categories
131 QStringList categories = session->categories().keys();
132 std::sort(categories.begin(), categories.end(), Utils::Compare::NaturalLessThan<Qt::CaseInsensitive>());
133 auto defaultCategory = settings()->loadValue<QString>(KEY_DEFAULTCATEGORY);
135 if (!m_torrentParams.category.isEmpty())
136 m_ui->categoryComboBox->addItem(m_torrentParams.category);
137 if (!defaultCategory.isEmpty())
138 m_ui->categoryComboBox->addItem(defaultCategory);
139 m_ui->categoryComboBox->addItem("");
141 for (const QString &category : asConst(categories))
142 if (category != defaultCategory && category != m_torrentParams.category)
143 m_ui->categoryComboBox->addItem(category);
145 m_ui->contentTreeView->header()->setSortIndicator(0, Qt::AscendingOrder);
146 loadState();
147 // Signal / slots
148 connect(m_ui->doNotDeleteTorrentCheckBox, &QCheckBox::clicked, this, &AddNewTorrentDialog::doNotDeleteTorrentClicked);
149 QShortcut *editHotkey = new QShortcut(Qt::Key_F2, m_ui->contentTreeView, nullptr, nullptr, Qt::WidgetShortcut);
150 connect(editHotkey, &QShortcut::activated
151 , this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
152 connect(m_ui->contentTreeView, &QAbstractItemView::doubleClicked
153 , this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
155 m_ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus();
158 AddNewTorrentDialog::~AddNewTorrentDialog()
160 saveState();
162 delete m_contentDelegate;
163 delete m_ui;
166 bool AddNewTorrentDialog::isEnabled()
168 return SettingsStorage::instance()->loadValue(KEY_ENABLED, true);
171 void AddNewTorrentDialog::setEnabled(bool value)
173 SettingsStorage::instance()->storeValue(KEY_ENABLED, value);
176 bool AddNewTorrentDialog::isTopLevel()
178 return SettingsStorage::instance()->loadValue(KEY_TOPLEVEL, true);
181 void AddNewTorrentDialog::setTopLevel(bool value)
183 SettingsStorage::instance()->storeValue(KEY_TOPLEVEL, value);
186 int AddNewTorrentDialog::savePathHistoryLength()
188 const int defaultHistoryLength = 8;
189 const int value = settings()->loadValue(KEY_SAVEPATHHISTORYLENGTH, defaultHistoryLength);
190 return qBound(minPathHistoryLength, value, maxPathHistoryLength);
193 void AddNewTorrentDialog::setSavePathHistoryLength(int value)
195 const int clampedValue = qBound(minPathHistoryLength, value, maxPathHistoryLength);
196 const int oldValue = savePathHistoryLength();
197 if (clampedValue == oldValue)
198 return;
200 settings()->storeValue(KEY_SAVEPATHHISTORYLENGTH, clampedValue);
201 settings()->storeValue(KEY_SAVEPATHHISTORY
202 , QStringList(settings()->loadValue<QStringList>(KEY_SAVEPATHHISTORY).mid(0, clampedValue)));
205 void AddNewTorrentDialog::loadState()
207 Utils::Gui::resize(this, m_storeDialogSize);
208 m_ui->splitter->restoreState(m_storeSplitterState);
209 m_headerState = settings()->loadValue<QByteArray>(KEY_TREEHEADERSTATE);
212 void AddNewTorrentDialog::saveState()
214 m_storeDialogSize = size();
215 m_storeSplitterState = m_ui->splitter->saveState();
216 if (m_contentModel)
217 settings()->storeValue(KEY_TREEHEADERSTATE, m_ui->contentTreeView->header()->saveState());
220 void AddNewTorrentDialog::show(const QString &source, const BitTorrent::AddTorrentParams &inParams, QWidget *parent)
222 auto *dlg = new AddNewTorrentDialog(inParams, parent);
224 if (Net::DownloadManager::hasSupportedScheme(source))
226 // Launch downloader
227 Net::DownloadManager::instance()->download(
228 Net::DownloadRequest(source).limit(MAX_TORRENT_SIZE)
229 , dlg, &AddNewTorrentDialog::handleDownloadFinished);
230 return;
233 const BitTorrent::MagnetUri magnetUri(source);
234 const bool isLoaded = magnetUri.isValid()
235 ? dlg->loadMagnet(magnetUri)
236 : dlg->loadTorrentFile(source);
238 if (isLoaded)
239 dlg->QDialog::show();
240 else
241 delete dlg;
244 void AddNewTorrentDialog::show(const QString &source, QWidget *parent)
246 show(source, BitTorrent::AddTorrentParams(), parent);
249 bool AddNewTorrentDialog::loadTorrentFile(const QString &torrentPath)
251 const QString decodedPath = torrentPath.startsWith("file://", Qt::CaseInsensitive)
252 ? QUrl::fromEncoded(torrentPath.toLocal8Bit()).toLocalFile()
253 : torrentPath;
255 QString error;
256 m_torrentInfo = BitTorrent::TorrentInfo::loadFromFile(decodedPath, &error);
257 if (!m_torrentInfo.isValid())
259 RaisedMessageBox::critical(this, tr("Invalid torrent")
260 , tr("Failed to load the torrent: %1.\nError: %2", "Don't remove the '\n' characters. They insert a newline.")
261 .arg(Utils::Fs::toNativePath(decodedPath), error));
262 return false;
265 m_torrentGuard = std::make_unique<TorrentFileGuard>(decodedPath);
267 return loadTorrentImpl();
270 bool AddNewTorrentDialog::loadTorrentImpl()
272 m_hasMetadata = true;
273 const auto torrentID = BitTorrent::TorrentID::fromInfoHash(m_torrentInfo.infoHash());
275 // Prevent showing the dialog if download is already present
276 if (BitTorrent::Session::instance()->isKnownTorrent(torrentID))
278 BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->findTorrent(torrentID);
279 if (torrent)
281 if (torrent->isPrivate() || m_torrentInfo.isPrivate())
283 RaisedMessageBox::warning(this, tr("Torrent is already present"), tr("Torrent '%1' is already in the transfer list. Trackers haven't been merged because it is a private torrent.").arg(torrent->name()), QMessageBox::Ok);
285 else
287 torrent->addTrackers(m_torrentInfo.trackers());
288 torrent->addUrlSeeds(m_torrentInfo.urlSeeds());
289 RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Torrent '%1' is already in the transfer list. Trackers have been merged.").arg(torrent->name()), QMessageBox::Ok);
292 else
294 RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Torrent is already queued for processing."), QMessageBox::Ok);
296 return false;
299 m_ui->labelInfohash1Data->setText(m_torrentInfo.infoHash().v1().isValid() ? m_torrentInfo.infoHash().v1().toString() : tr("N/A"));
300 m_ui->labelInfohash2Data->setText(m_torrentInfo.infoHash().v2().isValid() ? m_torrentInfo.infoHash().v2().toString() : tr("N/A"));
301 setupTreeview();
302 TMMChanged(m_ui->comboTTM->currentIndex());
303 return true;
306 bool AddNewTorrentDialog::loadMagnet(const BitTorrent::MagnetUri &magnetUri)
308 if (!magnetUri.isValid())
310 RaisedMessageBox::critical(this, tr("Invalid magnet link"), tr("This magnet link was not recognized"));
311 return false;
314 m_torrentGuard = std::make_unique<TorrentFileGuard>();
316 const auto torrentID = BitTorrent::TorrentID::fromInfoHash(magnetUri.infoHash());
317 // Prevent showing the dialog if download is already present
318 if (BitTorrent::Session::instance()->isKnownTorrent(torrentID))
320 BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->findTorrent(torrentID);
321 if (torrent)
323 if (torrent->isPrivate())
325 RaisedMessageBox::warning(this, tr("Torrent is already present"), tr("Torrent '%1' is already in the transfer list. Trackers haven't been merged because it is a private torrent.").arg(torrent->name()), QMessageBox::Ok);
327 else
329 torrent->addTrackers(magnetUri.trackers());
330 torrent->addUrlSeeds(magnetUri.urlSeeds());
331 RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Magnet link '%1' is already in the transfer list. Trackers have been merged.").arg(torrent->name()), QMessageBox::Ok);
334 else
336 RaisedMessageBox::information(this, tr("Torrent is already present"), tr("Magnet link is already queued for processing."), QMessageBox::Ok);
338 return false;
341 connect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataDownloaded, this, &AddNewTorrentDialog::updateMetadata);
343 // Set dialog title
344 const QString torrentName = magnetUri.name();
345 setWindowTitle(torrentName.isEmpty() ? tr("Magnet link") : torrentName);
347 setupTreeview();
348 TMMChanged(m_ui->comboTTM->currentIndex());
350 BitTorrent::Session::instance()->downloadMetadata(magnetUri);
351 setMetadataProgressIndicator(true, tr("Retrieving metadata..."));
352 m_ui->labelInfohash1Data->setText(magnetUri.infoHash().v1().isValid() ? magnetUri.infoHash().v1().toString() : tr("N/A"));
353 m_ui->labelInfohash2Data->setText(magnetUri.infoHash().v2().isValid() ? magnetUri.infoHash().v2().toString() : tr("N/A"));
355 m_magnetURI = magnetUri;
356 return true;
359 void AddNewTorrentDialog::showEvent(QShowEvent *event)
361 QDialog::showEvent(event);
362 if (!isTopLevel()) return;
364 activateWindow();
365 raise();
368 void AddNewTorrentDialog::saveSavePathHistory() const
370 // Get current history
371 auto history = settings()->loadValue<QStringList>(KEY_SAVEPATHHISTORY);
372 QVector<QDir> historyDirs;
373 for (const QString &path : asConst(history))
374 historyDirs << QDir {path};
376 const QDir selectedSavePath {m_ui->savePath->selectedPath()};
377 const int selectedSavePathIndex = historyDirs.indexOf(selectedSavePath);
378 if (selectedSavePathIndex > 0)
379 history.removeAt(selectedSavePathIndex);
380 if (selectedSavePathIndex != 0)
381 // Add last used save path to the front of history
382 history.push_front(selectedSavePath.absolutePath());
384 // Save history
385 settings()->storeValue(KEY_SAVEPATHHISTORY, QStringList {history.mid(0, savePathHistoryLength())});
388 // savePath is a folder, not an absolute file path
389 int AddNewTorrentDialog::indexOfSavePath(const QString &savePath)
391 QDir saveDir(savePath);
392 for (int i = 0; i < m_ui->savePath->count(); ++i)
393 if (QDir(m_ui->savePath->item(i)) == saveDir)
394 return i;
395 return -1;
398 void AddNewTorrentDialog::updateDiskSpaceLabel()
400 // Determine torrent size
401 qlonglong torrentSize = 0;
403 if (m_hasMetadata)
405 if (m_contentModel)
407 const QVector<BitTorrent::DownloadPriority> priorities = m_contentModel->model()->getFilePriorities();
408 Q_ASSERT(priorities.size() == m_torrentInfo.filesCount());
409 for (int i = 0; i < priorities.size(); ++i)
410 if (priorities[i] > BitTorrent::DownloadPriority::Ignored)
411 torrentSize += m_torrentInfo.fileSize(i);
413 else
415 torrentSize = m_torrentInfo.totalSize();
419 const QString sizeString = tr("%1 (Free space on disk: %2)").arg(
420 ((torrentSize > 0) ? Utils::Misc::friendlyUnit(torrentSize) : tr("Not available", "This size is unavailable."))
421 , Utils::Misc::friendlyUnit(Utils::Fs::freeDiskSpaceOnPath(m_ui->savePath->selectedPath())));
422 m_ui->labelSizeData->setText(sizeString);
425 void AddNewTorrentDialog::onSavePathChanged(const QString &newPath)
427 Q_UNUSED(newPath);
428 // Remember index
429 m_oldIndex = m_ui->savePath->currentIndex();
430 updateDiskSpaceLabel();
433 void AddNewTorrentDialog::categoryChanged(int index)
435 Q_UNUSED(index);
437 if (m_ui->comboTTM->currentIndex() == 1)
439 QString savePath = BitTorrent::Session::instance()->categorySavePath(m_ui->categoryComboBox->currentText());
440 m_ui->savePath->setSelectedPath(Utils::Fs::toNativePath(savePath));
441 updateDiskSpaceLabel();
445 void AddNewTorrentDialog::setSavePath(const QString &newPath)
447 int existingIndex = indexOfSavePath(newPath);
448 if (existingIndex < 0)
450 // New path, prepend to combo box
451 m_ui->savePath->insertItem(0, newPath);
452 existingIndex = 0;
454 m_ui->savePath->setCurrentIndex(existingIndex);
455 onSavePathChanged(newPath);
458 void AddNewTorrentDialog::saveTorrentFile()
460 Q_ASSERT(m_hasMetadata);
462 const QString torrentFileExtension {C_TORRENT_FILE_EXTENSION};
463 const QString filter {tr("Torrent file (*%1)").arg(torrentFileExtension)};
465 QString path = QFileDialog::getSaveFileName(
466 this, tr("Save as torrent file")
467 , QDir::home().absoluteFilePath(m_torrentInfo.name() + torrentFileExtension)
468 , filter);
469 if (path.isEmpty()) return;
471 if (!path.endsWith(torrentFileExtension, Qt::CaseInsensitive))
472 path += torrentFileExtension;
476 m_torrentInfo.saveToFile(path);
478 catch (const RuntimeError &err)
480 QMessageBox::critical(this, tr("I/O Error")
481 , tr("Couldn't export torrent metadata file '%1'. Reason: %2.").arg(path, err.message()));
485 void AddNewTorrentDialog::populateSavePathComboBox()
487 m_ui->savePath->clear();
489 // Load save path history
490 const auto savePathHistory {settings()->loadValue<QStringList>(KEY_SAVEPATHHISTORY)};
491 for (const QString &savePath : savePathHistory)
492 m_ui->savePath->addItem(savePath);
494 const bool rememberLastSavePath {settings()->loadValue(KEY_REMEMBERLASTSAVEPATH, false)};
495 const QString defSavePath {BitTorrent::Session::instance()->defaultSavePath()};
497 if (!m_torrentParams.savePath.isEmpty())
498 setSavePath(m_torrentParams.savePath);
499 else if (!rememberLastSavePath)
500 setSavePath(defSavePath);
501 // else last used save path will be selected since it is the first in the list
504 void AddNewTorrentDialog::displayContentTreeMenu(const QPoint &)
506 const QModelIndexList selectedRows = m_ui->contentTreeView->selectionModel()->selectedRows(0);
508 const auto applyPriorities = [this](const BitTorrent::DownloadPriority prio)
510 const QModelIndexList selectedRows = m_ui->contentTreeView->selectionModel()->selectedRows(0);
511 for (const QModelIndex &index : selectedRows)
513 m_contentModel->setData(index.sibling(index.row(), PRIORITY)
514 , static_cast<int>(prio));
517 const auto applyPrioritiesByOrder = [this]()
519 // Equally distribute the selected items into groups and for each group assign
520 // a download priority that will apply to each item. The number of groups depends on how
521 // many "download priority" are available to be assigned
523 const QModelIndexList selectedRows = m_ui->contentTreeView->selectionModel()->selectedRows(0);
525 const qsizetype priorityGroups = 3;
526 const auto priorityGroupSize = std::max<qsizetype>((selectedRows.length() / priorityGroups), 1);
528 for (qsizetype i = 0; i < selectedRows.length(); ++i)
530 auto priority = BitTorrent::DownloadPriority::Ignored;
531 switch (i / priorityGroupSize)
533 case 0:
534 priority = BitTorrent::DownloadPriority::Maximum;
535 break;
536 case 1:
537 priority = BitTorrent::DownloadPriority::High;
538 break;
539 default:
540 case 2:
541 priority = BitTorrent::DownloadPriority::Normal;
542 break;
545 const QModelIndex &index = selectedRows[i];
546 m_contentModel->setData(index.sibling(index.row(), PRIORITY)
547 , static_cast<int>(priority));
551 QMenu *menu = new QMenu(this);
552 menu->setAttribute(Qt::WA_DeleteOnClose);
553 if (selectedRows.size() == 1)
555 menu->addAction(UIThemeManager::instance()->getIcon("edit-rename"), tr("Rename...")
556 , this, [this]() { m_ui->contentTreeView->renameSelectedFile(m_torrentInfo); });
557 menu->addSeparator();
559 QMenu *priorityMenu = menu->addMenu(tr("Priority"));
560 priorityMenu->addAction(tr("Do not download"), priorityMenu, [applyPriorities]()
562 applyPriorities(BitTorrent::DownloadPriority::Ignored);
564 priorityMenu->addAction(tr("Normal"), priorityMenu, [applyPriorities]()
566 applyPriorities(BitTorrent::DownloadPriority::Normal);
568 priorityMenu->addAction(tr("High"), priorityMenu, [applyPriorities]()
570 applyPriorities(BitTorrent::DownloadPriority::High);
572 priorityMenu->addAction(tr("Maximum"), priorityMenu, [applyPriorities]()
574 applyPriorities(BitTorrent::DownloadPriority::Maximum);
576 priorityMenu->addSeparator();
577 priorityMenu->addAction(tr("By shown file order"), priorityMenu, applyPrioritiesByOrder);
579 else
581 menu->addAction(tr("Do not download"), menu, [applyPriorities]()
583 applyPriorities(BitTorrent::DownloadPriority::Ignored);
585 menu->addAction(tr("Normal priority"), menu, [applyPriorities]()
587 applyPriorities(BitTorrent::DownloadPriority::Normal);
589 menu->addAction(tr("High priority"), menu, [applyPriorities]()
591 applyPriorities(BitTorrent::DownloadPriority::High);
593 menu->addAction(tr("Maximum priority"), menu, [applyPriorities]()
595 applyPriorities(BitTorrent::DownloadPriority::Maximum);
597 menu->addSeparator();
598 menu->addAction(tr("Priority by shown file order"), menu, applyPrioritiesByOrder);
601 menu->popup(QCursor::pos());
604 void AddNewTorrentDialog::accept()
606 // TODO: Check if destination actually exists
607 m_torrentParams.skipChecking = m_ui->skipCheckingCheckBox->isChecked();
609 // Category
610 m_torrentParams.category = m_ui->categoryComboBox->currentText();
611 if (m_ui->defaultCategoryCheckbox->isChecked())
612 settings()->storeValue(KEY_DEFAULTCATEGORY, m_torrentParams.category);
614 settings()->storeValue(KEY_REMEMBERLASTSAVEPATH, m_ui->checkBoxRememberLastSavePath->isChecked());
616 // Save file priorities
617 if (m_contentModel)
618 m_torrentParams.filePriorities = m_contentModel->model()->getFilePriorities();
620 m_torrentParams.addPaused = !m_ui->startTorrentCheckBox->isChecked();
621 m_torrentParams.contentLayout = static_cast<BitTorrent::TorrentContentLayout>(m_ui->contentLayoutComboBox->currentIndex());
623 m_torrentParams.sequential = m_ui->sequentialCheckBox->isChecked();
624 m_torrentParams.firstLastPiecePriority = m_ui->firstLastCheckBox->isChecked();
626 QString savePath = m_ui->savePath->selectedPath();
627 if (m_ui->comboTTM->currentIndex() != 1)
628 { // 0 is Manual mode and 1 is Automatic mode. Handle all non 1 values as manual mode.
629 m_torrentParams.useAutoTMM = false;
630 m_torrentParams.savePath = savePath;
631 saveSavePathHistory();
633 else
635 m_torrentParams.useAutoTMM = true;
638 setEnabled(!m_ui->checkBoxNeverShow->isChecked());
640 // Add torrent
641 if (!m_hasMetadata)
642 BitTorrent::Session::instance()->addTorrent(m_magnetURI, m_torrentParams);
643 else
644 BitTorrent::Session::instance()->addTorrent(m_torrentInfo, m_torrentParams);
646 m_torrentGuard->markAsAddedToSession();
647 QDialog::accept();
650 void AddNewTorrentDialog::reject()
652 if (!m_hasMetadata)
654 setMetadataProgressIndicator(false);
655 BitTorrent::Session::instance()->cancelDownloadMetadata(m_magnetURI.infoHash().toTorrentID());
658 QDialog::reject();
661 void AddNewTorrentDialog::updateMetadata(const BitTorrent::TorrentInfo &metadata)
663 if (metadata.infoHash() != m_magnetURI.infoHash()) return;
665 disconnect(BitTorrent::Session::instance(), &BitTorrent::Session::metadataDownloaded, this, &AddNewTorrentDialog::updateMetadata);
667 if (!metadata.isValid())
669 RaisedMessageBox::critical(this, tr("I/O Error"), ("Invalid metadata."));
670 setMetadataProgressIndicator(false, tr("Invalid metadata"));
671 return;
674 // Good to go
675 m_torrentInfo = metadata;
676 m_hasMetadata = true;
677 setMetadataProgressIndicator(true, tr("Parsing metadata..."));
679 // Update UI
680 setupTreeview();
681 setMetadataProgressIndicator(false, tr("Metadata retrieval complete"));
683 m_ui->buttonSave->setVisible(true);
684 if (m_torrentInfo.infoHash().v2().isValid())
686 m_ui->buttonSave->setEnabled(false);
687 m_ui->buttonSave->setToolTip(tr("Cannot create v2 torrent until its data is fully downloaded."));
691 void AddNewTorrentDialog::setMetadataProgressIndicator(bool visibleIndicator, const QString &labelText)
693 // Always show info label when waiting for metadata
694 m_ui->lblMetaLoading->setVisible(true);
695 m_ui->lblMetaLoading->setText(labelText);
696 m_ui->progMetaLoading->setVisible(visibleIndicator);
699 void AddNewTorrentDialog::setupTreeview()
701 if (!m_hasMetadata)
703 m_ui->labelCommentData->setText(tr("Not Available", "This comment is unavailable"));
704 m_ui->labelDateData->setText(tr("Not Available", "This date is unavailable"));
706 else
708 // Set dialog title
709 setWindowTitle(m_torrentInfo.name());
711 // Set torrent information
712 m_ui->labelCommentData->setText(Utils::Misc::parseHtmlLinks(m_torrentInfo.comment().toHtmlEscaped()));
713 m_ui->labelDateData->setText(!m_torrentInfo.creationDate().isNull() ? QLocale().toString(m_torrentInfo.creationDate(), QLocale::ShortFormat) : tr("Not available"));
715 // Prepare content tree
716 m_contentModel = new TorrentContentFilterModel(this);
717 connect(m_contentModel->model(), &TorrentContentModel::filteredFilesChanged, this, &AddNewTorrentDialog::updateDiskSpaceLabel);
718 m_ui->contentTreeView->setModel(m_contentModel);
719 m_contentDelegate = new PropListDelegate(nullptr);
720 m_ui->contentTreeView->setItemDelegate(m_contentDelegate);
721 connect(m_ui->contentTreeView, &QAbstractItemView::clicked, m_ui->contentTreeView
722 , qOverload<const QModelIndex &>(&QAbstractItemView::edit));
723 connect(m_ui->contentTreeView, &QWidget::customContextMenuRequested, this, &AddNewTorrentDialog::displayContentTreeMenu);
725 // List files in torrent
726 m_contentModel->model()->setupModelData(m_torrentInfo);
727 if (!m_headerState.isEmpty())
728 m_ui->contentTreeView->header()->restoreState(m_headerState);
730 // Hide useless columns after loading the header state
731 m_ui->contentTreeView->hideColumn(PROGRESS);
732 m_ui->contentTreeView->hideColumn(REMAINING);
733 m_ui->contentTreeView->hideColumn(AVAILABILITY);
735 // Expand single-item folders recursively
736 QModelIndex currentIndex;
737 while (m_contentModel->rowCount(currentIndex) == 1)
739 currentIndex = m_contentModel->index(0, 0, currentIndex);
740 m_ui->contentTreeView->setExpanded(currentIndex, true);
744 updateDiskSpaceLabel();
747 void AddNewTorrentDialog::handleDownloadFinished(const Net::DownloadResult &result)
749 QString error;
750 switch (result.status)
752 case Net::DownloadStatus::Success:
753 m_torrentInfo = BitTorrent::TorrentInfo::load(result.data, &error);
754 if (!m_torrentInfo.isValid())
756 RaisedMessageBox::critical(this, tr("Invalid torrent"), tr("Failed to load from URL: %1.\nError: %2")
757 .arg(result.url, error));
758 return;
761 m_torrentGuard = std::make_unique<TorrentFileGuard>();
763 if (loadTorrentImpl())
764 open();
765 else
766 deleteLater();
767 break;
768 case Net::DownloadStatus::RedirectedToMagnet:
769 if (loadMagnet(BitTorrent::MagnetUri(result.magnet)))
770 open();
771 else
772 deleteLater();
773 break;
774 default:
775 RaisedMessageBox::critical(this, tr("Download Error"),
776 tr("Cannot download '%1': %2").arg(result.url, result.errorString));
777 deleteLater();
781 void AddNewTorrentDialog::TMMChanged(int index)
783 if (index != 1)
784 { // 0 is Manual mode and 1 is Automatic mode. Handle all non 1 values as manual mode.
785 populateSavePathComboBox();
786 m_ui->groupBoxSavePath->setEnabled(true);
787 m_ui->savePath->blockSignals(false);
788 m_ui->savePath->setCurrentIndex(m_oldIndex < m_ui->savePath->count() ? m_oldIndex : m_ui->savePath->count() - 1);
790 else
792 m_ui->groupBoxSavePath->setEnabled(false);
793 m_ui->savePath->blockSignals(true);
794 m_ui->savePath->clear();
795 QString savePath = BitTorrent::Session::instance()->categorySavePath(m_ui->categoryComboBox->currentText());
796 m_ui->savePath->addItem(savePath);
797 updateDiskSpaceLabel();
801 void AddNewTorrentDialog::doNotDeleteTorrentClicked(bool checked)
803 m_torrentGuard->setAutoRemove(!checked);