WebUI: Use Map instead of Mootools Hash in Torrents table
[qBittorrent.git] / src / gui / mainwindow.cpp
blobb80d0cc706ce6e7d491c05e9a2a247e48678e316
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2022-2024 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 * In addition, as a special exception, the copyright holders give permission to
21 * link this program with the OpenSSL project's "OpenSSL" library (or with
22 * modified versions of it that use the same license as the "OpenSSL" library),
23 * and distribute the linked executables. You must obey the GNU General Public
24 * License in all respects for all of the code used other than "OpenSSL". If you
25 * modify file(s), you may extend this exception to your version of the file(s),
26 * but you are not obligated to do so. If you do not wish to do so, delete this
27 * exception statement from your version.
30 #include "mainwindow.h"
32 #include <QtSystemDetection>
34 #include <algorithm>
35 #include <chrono>
37 #include <QAction>
38 #include <QActionGroup>
39 #include <QClipboard>
40 #include <QCloseEvent>
41 #include <QComboBox>
42 #include <QDebug>
43 #include <QDesktopServices>
44 #include <QFileDialog>
45 #include <QFileSystemWatcher>
46 #include <QKeyEvent>
47 #include <QLabel>
48 #include <QMenu>
49 #include <QMessageBox>
50 #include <QMetaObject>
51 #include <QMimeData>
52 #include <QProcess>
53 #include <QPushButton>
54 #include <QShortcut>
55 #include <QSplitter>
56 #include <QStatusBar>
57 #include <QString>
58 #include <QTimer>
60 #include "base/bittorrent/session.h"
61 #include "base/bittorrent/sessionstatus.h"
62 #include "base/global.h"
63 #include "base/net/downloadmanager.h"
64 #include "base/path.h"
65 #include "base/preferences.h"
66 #include "base/rss/rss_folder.h"
67 #include "base/rss/rss_session.h"
68 #include "base/utils/foreignapps.h"
69 #include "base/utils/fs.h"
70 #include "base/utils/misc.h"
71 #include "base/utils/password.h"
72 #include "base/version.h"
73 #include "aboutdialog.h"
74 #include "autoexpandabledialog.h"
75 #include "cookiesdialog.h"
76 #include "desktopintegration.h"
77 #include "downloadfromurldialog.h"
78 #include "executionlogwidget.h"
79 #include "hidabletabwidget.h"
80 #include "interfaces/iguiapplication.h"
81 #include "lineedit.h"
82 #include "optionsdialog.h"
83 #include "powermanagement/powermanagement.h"
84 #include "properties/peerlistwidget.h"
85 #include "properties/propertieswidget.h"
86 #include "properties/proptabbar.h"
87 #include "rss/rsswidget.h"
88 #include "search/searchwidget.h"
89 #include "speedlimitdialog.h"
90 #include "statsdialog.h"
91 #include "statusbar.h"
92 #include "torrentcreatordialog.h"
93 #include "trackerlist/trackerlistwidget.h"
94 #include "transferlistfilterswidget.h"
95 #include "transferlistmodel.h"
96 #include "transferlistwidget.h"
97 #include "ui_mainwindow.h"
98 #include "uithememanager.h"
99 #include "utils.h"
101 #ifdef Q_OS_MACOS
102 #include "macosdockbadge/badger.h"
103 #endif
104 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
105 #include "programupdater.h"
106 #endif
108 using namespace std::chrono_literals;
110 namespace
112 #define SETTINGS_KEY(name) u"GUI/" name
113 #define EXECUTIONLOG_SETTINGS_KEY(name) (SETTINGS_KEY(u"Log/"_s) name)
115 const std::chrono::seconds PREVENT_SUSPEND_INTERVAL {60};
117 bool isTorrentLink(const QString &str)
119 return str.startsWith(u"magnet:", Qt::CaseInsensitive)
120 || str.endsWith(TORRENT_FILE_EXTENSION, Qt::CaseInsensitive)
121 || (!str.startsWith(u"file:", Qt::CaseInsensitive)
122 && Net::DownloadManager::hasSupportedScheme(str));
126 MainWindow::MainWindow(IGUIApplication *app, const WindowState initialState, const QString &titleSuffix)
127 : GUIApplicationComponent(app)
128 , m_ui {new Ui::MainWindow}
129 , m_downloadRate {Utils::Misc::friendlyUnit(0, true)}
130 , m_uploadRate {Utils::Misc::friendlyUnit(0, true)}
131 , m_storeExecutionLogEnabled {EXECUTIONLOG_SETTINGS_KEY(u"Enabled"_s)}
132 , m_storeDownloadTrackerFavicon {SETTINGS_KEY(u"DownloadTrackerFavicon"_s)}
133 , m_storeExecutionLogTypes {EXECUTIONLOG_SETTINGS_KEY(u"Types"_s), Log::MsgType::ALL}
134 #ifdef Q_OS_MACOS
135 , m_badger {std::make_unique<MacUtils::Badger>()}
136 #endif // Q_OS_MACOS
138 m_ui->setupUi(this);
140 Preferences *const pref = Preferences::instance();
141 m_uiLocked = pref->isUILocked();
142 m_displaySpeedInTitle = pref->speedInTitleBar();
143 // Setting icons
144 #ifndef Q_OS_MACOS
145 setWindowIcon(UIThemeManager::instance()->getIcon(u"qbittorrent"_s));
146 #endif // Q_OS_MACOS
148 setTitleSuffix(titleSuffix);
150 #if (defined(Q_OS_UNIX))
151 m_ui->actionOptions->setText(tr("Preferences"));
152 #endif
154 addToolbarContextMenu();
156 m_ui->actionOpen->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_s));
157 m_ui->actionDownloadFromURL->setIcon(UIThemeManager::instance()->getIcon(u"insert-link"_s));
158 m_ui->actionSetGlobalSpeedLimits->setIcon(UIThemeManager::instance()->getIcon(u"speedometer"_s));
159 m_ui->actionCreateTorrent->setIcon(UIThemeManager::instance()->getIcon(u"torrent-creator"_s, u"document-edit"_s));
160 m_ui->actionAbout->setIcon(UIThemeManager::instance()->getIcon(u"help-about"_s));
161 m_ui->actionStatistics->setIcon(UIThemeManager::instance()->getIcon(u"view-statistics"_s));
162 m_ui->actionTopQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-top"_s));
163 m_ui->actionIncreaseQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-up"_s));
164 m_ui->actionDecreaseQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-down"_s));
165 m_ui->actionBottomQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-bottom"_s));
166 m_ui->actionDelete->setIcon(UIThemeManager::instance()->getIcon(u"list-remove"_s));
167 m_ui->actionDocumentation->setIcon(UIThemeManager::instance()->getIcon(u"help-contents"_s));
168 m_ui->actionDonateMoney->setIcon(UIThemeManager::instance()->getIcon(u"wallet-open"_s));
169 m_ui->actionExit->setIcon(UIThemeManager::instance()->getIcon(u"application-exit"_s));
170 m_ui->actionLock->setIcon(UIThemeManager::instance()->getIcon(u"object-locked"_s));
171 m_ui->actionOptions->setIcon(UIThemeManager::instance()->getIcon(u"configure"_s, u"preferences-system"_s));
172 m_ui->actionStart->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_s, u"media-playback-start"_s));
173 m_ui->actionStop->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_s, u"media-playback-pause"_s));
174 m_ui->actionPauseSession->setIcon(UIThemeManager::instance()->getIcon(u"pause-session"_s, u"media-playback-pause"_s));
175 m_ui->actionResumeSession->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_s, u"media-playback-start"_s));
176 m_ui->menuAutoShutdownOnDownloadsCompletion->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_s, u"application-exit"_s));
177 m_ui->actionManageCookies->setIcon(UIThemeManager::instance()->getIcon(u"browser-cookies"_s, u"preferences-web-browser-cookies"_s));
178 m_ui->menuLog->setIcon(UIThemeManager::instance()->getIcon(u"help-contents"_s));
179 m_ui->actionCheckForUpdates->setIcon(UIThemeManager::instance()->getIcon(u"view-refresh"_s));
181 m_ui->actionPauseSession->setVisible(!BitTorrent::Session::instance()->isPaused());
182 m_ui->actionResumeSession->setVisible(BitTorrent::Session::instance()->isPaused());
183 connect(BitTorrent::Session::instance(), &BitTorrent::Session::paused, this, [this]
185 m_ui->actionPauseSession->setVisible(false);
186 m_ui->actionResumeSession->setVisible(true);
187 refreshWindowTitle();
188 refreshTrayIconTooltip();
190 connect(BitTorrent::Session::instance(), &BitTorrent::Session::resumed, this, [this]
192 m_ui->actionPauseSession->setVisible(true);
193 m_ui->actionResumeSession->setVisible(false);
194 refreshWindowTitle();
195 refreshTrayIconTooltip();
198 auto *lockMenu = new QMenu(m_ui->menuView);
199 lockMenu->addAction(tr("&Set Password"), this, &MainWindow::defineUILockPassword);
200 lockMenu->addAction(tr("&Clear Password"), this, &MainWindow::clearUILockPassword);
201 m_ui->actionLock->setMenu(lockMenu);
203 updateAltSpeedsBtn(BitTorrent::Session::instance()->isAltGlobalSpeedLimitEnabled());
205 connect(BitTorrent::Session::instance(), &BitTorrent::Session::speedLimitModeChanged, this, &MainWindow::updateAltSpeedsBtn);
207 qDebug("create tabWidget");
208 m_tabs = new HidableTabWidget(this);
209 connect(m_tabs.data(), &QTabWidget::currentChanged, this, &MainWindow::tabChanged);
211 m_splitter = new QSplitter(Qt::Horizontal, this);
212 // vSplitter->setChildrenCollapsible(false);
214 auto *hSplitter = new QSplitter(Qt::Vertical, this);
215 hSplitter->setChildrenCollapsible(false);
216 hSplitter->setFrameShape(QFrame::NoFrame);
218 // Torrent filter
219 m_columnFilterEdit = new LineEdit;
220 m_columnFilterEdit->setPlaceholderText(tr("Filter torrents..."));
221 m_columnFilterEdit->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
222 m_columnFilterEdit->setFixedWidth(200);
223 m_columnFilterEdit->setContextMenuPolicy(Qt::CustomContextMenu);
224 connect(m_columnFilterEdit, &QWidget::customContextMenuRequested, this, &MainWindow::showFilterContextMenu);
225 auto *columnFilterLabel = new QLabel(tr("Filter by:"));
226 m_columnFilterComboBox = new QComboBox;
227 QHBoxLayout *columnFilterLayout = new QHBoxLayout(m_columnFilterWidget);
228 columnFilterLayout->setContentsMargins(0, 0, 0, 0);
229 auto *columnFilterSpacer = new QWidget(this);
230 columnFilterSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
231 columnFilterLayout->addWidget(columnFilterSpacer);
232 columnFilterLayout->addWidget(m_columnFilterEdit);
233 columnFilterLayout->addWidget(columnFilterLabel, 0);
234 columnFilterLayout->addWidget(m_columnFilterComboBox, 0);
235 m_columnFilterWidget = new QWidget(this);
236 m_columnFilterWidget->setLayout(columnFilterLayout);
237 m_columnFilterAction = m_ui->toolBar->insertWidget(m_ui->actionLock, m_columnFilterWidget);
239 auto *spacer = new QWidget(this);
240 spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
241 m_ui->toolBar->insertWidget(m_columnFilterAction, spacer);
243 // Transfer List tab
244 m_transferListWidget = new TransferListWidget(hSplitter, this);
245 m_propertiesWidget = new PropertiesWidget(hSplitter);
246 connect(m_transferListWidget, &TransferListWidget::currentTorrentChanged, m_propertiesWidget, &PropertiesWidget::loadTorrentInfos);
247 hSplitter->addWidget(m_transferListWidget);
248 hSplitter->addWidget(m_propertiesWidget);
249 m_splitter->addWidget(hSplitter);
250 m_splitter->setCollapsible(0, false);
251 m_tabs->addTab(m_splitter,
252 #ifndef Q_OS_MACOS
253 UIThemeManager::instance()->getIcon(u"folder-remote"_s),
254 #endif
255 tr("Transfers"));
256 // Filter types
257 const QList<TransferListModel::Column> filterTypes = {TransferListModel::Column::TR_NAME, TransferListModel::Column::TR_SAVE_PATH};
258 for (const TransferListModel::Column type : filterTypes)
260 const QString typeName = m_transferListWidget->getSourceModel()->headerData(type, Qt::Horizontal, Qt::DisplayRole).value<QString>();
261 m_columnFilterComboBox->addItem(typeName, type);
263 connect(m_columnFilterComboBox, &QComboBox::currentIndexChanged, this, &MainWindow::applyTransferListFilter);
264 connect(m_columnFilterEdit, &LineEdit::textChanged, this, &MainWindow::applyTransferListFilter);
265 connect(hSplitter, &QSplitter::splitterMoved, this, &MainWindow::saveSettings);
266 connect(m_splitter, &QSplitter::splitterMoved, this, &MainWindow::saveSplitterSettings);
268 #ifdef Q_OS_MACOS
269 // Increase top spacing to avoid tab overlapping
270 m_ui->centralWidgetLayout->addSpacing(8);
271 #endif
273 m_ui->centralWidgetLayout->addWidget(m_tabs);
275 m_queueSeparator = m_ui->toolBar->insertSeparator(m_ui->actionTopQueuePos);
276 m_queueSeparatorMenu = m_ui->menuEdit->insertSeparator(m_ui->actionTopQueuePos);
278 #ifdef Q_OS_MACOS
279 for (QAction *action : asConst(m_ui->toolBar->actions()))
281 if (action->isSeparator())
283 QWidget *spacer = new QWidget(this);
284 spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
285 spacer->setMinimumWidth(16);
286 m_ui->toolBar->insertWidget(action, spacer);
287 m_ui->toolBar->removeAction(action);
291 QWidget *spacer = new QWidget(this);
292 spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
293 spacer->setMinimumWidth(8);
294 m_ui->toolBar->insertWidget(m_ui->actionDownloadFromURL, spacer);
297 QWidget *spacer = new QWidget(this);
298 spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
299 spacer->setMinimumWidth(8);
300 m_ui->toolBar->addWidget(spacer);
302 #endif // Q_OS_MACOS
304 // Transfer list slots
305 connect(m_ui->actionStart, &QAction::triggered, m_transferListWidget, &TransferListWidget::startSelectedTorrents);
306 connect(m_ui->actionStop, &QAction::triggered, m_transferListWidget, &TransferListWidget::stopSelectedTorrents);
307 connect(m_ui->actionPauseSession, &QAction::triggered, m_transferListWidget, &TransferListWidget::pauseSession);
308 connect(m_ui->actionResumeSession, &QAction::triggered, m_transferListWidget, &TransferListWidget::resumeSession);
309 connect(m_ui->actionDelete, &QAction::triggered, m_transferListWidget, &TransferListWidget::softDeleteSelectedTorrents);
310 connect(m_ui->actionTopQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::topQueuePosSelectedTorrents);
311 connect(m_ui->actionIncreaseQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::increaseQueuePosSelectedTorrents);
312 connect(m_ui->actionDecreaseQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::decreaseQueuePosSelectedTorrents);
313 connect(m_ui->actionBottomQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::bottomQueuePosSelectedTorrents);
314 connect(m_ui->actionMinimize, &QAction::triggered, this, &MainWindow::minimizeWindow);
315 connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds);
317 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
318 connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, [this]() { checkProgramUpdate(true); });
320 // trigger an early check on startup
321 if (pref->isUpdateCheckEnabled())
322 checkProgramUpdate(false);
323 #else
324 m_ui->actionCheckForUpdates->setVisible(false);
325 #endif
327 // Certain menu items should reside at specific places on macOS.
328 // Qt partially does it on its own, but updates and different languages require tuning.
329 m_ui->actionExit->setMenuRole(QAction::QuitRole);
330 m_ui->actionAbout->setMenuRole(QAction::AboutRole);
331 m_ui->actionCheckForUpdates->setMenuRole(QAction::ApplicationSpecificRole);
332 m_ui->actionOptions->setMenuRole(QAction::PreferencesRole);
334 connect(m_ui->actionManageCookies, &QAction::triggered, this, &MainWindow::manageCookies);
336 // Initialise system sleep inhibition timer
337 m_pwr = new PowerManagement(this);
338 m_preventTimer = new QTimer(this);
339 m_preventTimer->setSingleShot(true);
340 connect(m_preventTimer, &QTimer::timeout, this, &MainWindow::updatePowerManagementState);
341 connect(pref, &Preferences::changed, this, &MainWindow::updatePowerManagementState);
342 updatePowerManagementState();
344 // Configure BT session according to options
345 loadPreferences();
347 connect(BitTorrent::Session::instance(), &BitTorrent::Session::statsUpdated, this, &MainWindow::loadSessionStats);
348 connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsUpdated, this, &MainWindow::reloadTorrentStats);
350 // Accept drag 'n drops
351 setAcceptDrops(true);
352 createKeyboardShortcuts();
354 #ifdef Q_OS_MACOS
355 setUnifiedTitleAndToolBarOnMac(true);
356 #endif
358 // View settings
359 m_ui->actionTopToolBar->setChecked(pref->isToolbarDisplayed());
360 m_ui->actionShowStatusbar->setChecked(pref->isStatusbarDisplayed());
361 m_ui->actionSpeedInTitleBar->setChecked(pref->speedInTitleBar());
362 m_ui->actionRSSReader->setChecked(pref->isRSSWidgetEnabled());
363 m_ui->actionSearchWidget->setChecked(pref->isSearchEnabled());
364 m_ui->actionExecutionLogs->setChecked(isExecutionLogEnabled());
366 const Log::MsgTypes flags = executionLogMsgTypes();
367 m_ui->actionNormalMessages->setChecked(flags.testFlag(Log::NORMAL));
368 m_ui->actionInformationMessages->setChecked(flags.testFlag(Log::INFO));
369 m_ui->actionWarningMessages->setChecked(flags.testFlag(Log::WARNING));
370 m_ui->actionCriticalMessages->setChecked(flags.testFlag(Log::CRITICAL));
372 displayRSSTab(m_ui->actionRSSReader->isChecked());
373 on_actionExecutionLogs_triggered(m_ui->actionExecutionLogs->isChecked());
374 on_actionNormalMessages_triggered(m_ui->actionNormalMessages->isChecked());
375 on_actionInformationMessages_triggered(m_ui->actionInformationMessages->isChecked());
376 on_actionWarningMessages_triggered(m_ui->actionWarningMessages->isChecked());
377 on_actionCriticalMessages_triggered(m_ui->actionCriticalMessages->isChecked());
378 if (m_ui->actionSearchWidget->isChecked())
379 QMetaObject::invokeMethod(this, &MainWindow::on_actionSearchWidget_triggered, Qt::QueuedConnection);
380 // Auto shutdown actions
381 auto *autoShutdownGroup = new QActionGroup(this);
382 autoShutdownGroup->setExclusive(true);
383 autoShutdownGroup->addAction(m_ui->actionAutoShutdownDisabled);
384 autoShutdownGroup->addAction(m_ui->actionAutoExit);
385 autoShutdownGroup->addAction(m_ui->actionAutoShutdown);
386 autoShutdownGroup->addAction(m_ui->actionAutoSuspend);
387 autoShutdownGroup->addAction(m_ui->actionAutoHibernate);
388 #if (!defined(Q_OS_UNIX) || defined(Q_OS_MACOS)) || defined(QBT_USES_DBUS)
389 m_ui->actionAutoShutdown->setChecked(pref->shutdownWhenDownloadsComplete());
390 m_ui->actionAutoSuspend->setChecked(pref->suspendWhenDownloadsComplete());
391 m_ui->actionAutoHibernate->setChecked(pref->hibernateWhenDownloadsComplete());
392 #else
393 m_ui->actionAutoShutdown->setDisabled(true);
394 m_ui->actionAutoSuspend->setDisabled(true);
395 m_ui->actionAutoHibernate->setDisabled(true);
396 #endif
397 m_ui->actionAutoExit->setChecked(pref->shutdownqBTWhenDownloadsComplete());
399 if (!autoShutdownGroup->checkedAction())
400 m_ui->actionAutoShutdownDisabled->setChecked(true);
402 // Load Window state and sizes
403 loadSettings();
405 populateDesktopIntegrationMenu();
406 #ifndef Q_OS_MACOS
407 m_ui->actionLock->setVisible(app->desktopIntegration()->isActive());
408 connect(app->desktopIntegration(), &DesktopIntegration::stateChanged, this, [this, app]()
410 m_ui->actionLock->setVisible(app->desktopIntegration()->isActive());
412 #endif
413 connect(app->desktopIntegration(), &DesktopIntegration::notificationClicked, this, &MainWindow::desktopNotificationClicked);
414 connect(app->desktopIntegration(), &DesktopIntegration::activationRequested, this, [this]()
416 #ifdef Q_OS_MACOS
417 if (!isVisible())
418 activate();
419 #else
420 toggleVisibility();
421 #endif
424 #ifdef Q_OS_MACOS
425 if (initialState == WindowState::Normal)
427 show();
428 activateWindow();
429 raise();
431 else
433 // Make sure the Window is visible if we don't have a tray icon
434 showMinimized();
436 #else
437 if (app->desktopIntegration()->isActive())
439 if ((initialState == WindowState::Normal) && !m_uiLocked)
441 show();
442 activateWindow();
443 raise();
445 else if (initialState == WindowState::Minimized)
447 showMinimized();
448 if (pref->minimizeToTray())
450 hide();
451 if (!pref->minimizeToTrayNotified())
453 app->desktopIntegration()->showNotification(tr("qBittorrent is minimized to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
454 pref->setMinimizeToTrayNotified(true);
459 else
461 // Make sure the Window is visible if we don't have a tray icon
462 if (initialState != WindowState::Normal)
464 showMinimized();
466 else
468 show();
469 activateWindow();
470 raise();
473 #endif
475 const bool isFiltersSidebarVisible = pref->isFiltersSidebarVisible();
476 m_ui->actionShowFiltersSidebar->setChecked(isFiltersSidebarVisible);
477 if (isFiltersSidebarVisible)
479 showFiltersSidebar(true);
481 else
483 m_transferListWidget->applyStatusFilter(pref->getTransSelFilter());
484 m_transferListWidget->applyCategoryFilter(QString());
485 m_transferListWidget->applyTagFilter(std::nullopt);
486 m_transferListWidget->applyTrackerFilterAll();
489 // Start watching the executable for updates
490 m_executableWatcher = new QFileSystemWatcher(this);
491 connect(m_executableWatcher, &QFileSystemWatcher::fileChanged, this, &MainWindow::notifyOfUpdate);
492 m_executableWatcher->addPath(qApp->applicationFilePath());
494 m_transferListWidget->setFocus();
496 // Update the number of torrents (tab)
497 updateNbTorrents();
498 connect(m_transferListWidget->getSourceModel(), &QAbstractItemModel::rowsInserted, this, &MainWindow::updateNbTorrents);
499 connect(m_transferListWidget->getSourceModel(), &QAbstractItemModel::rowsRemoved, this, &MainWindow::updateNbTorrents);
501 connect(pref, &Preferences::changed, this, &MainWindow::optionsSaved);
503 qDebug("GUI Built");
506 MainWindow::~MainWindow()
508 delete m_ui;
511 bool MainWindow::isExecutionLogEnabled() const
513 return m_storeExecutionLogEnabled;
516 void MainWindow::setExecutionLogEnabled(const bool value)
518 m_storeExecutionLogEnabled = value;
521 Log::MsgTypes MainWindow::executionLogMsgTypes() const
523 return m_storeExecutionLogTypes;
526 void MainWindow::setExecutionLogMsgTypes(const Log::MsgTypes value)
528 m_executionLog->setMessageTypes(value);
529 m_storeExecutionLogTypes = value;
532 bool MainWindow::isDownloadTrackerFavicon() const
534 return m_storeDownloadTrackerFavicon;
537 void MainWindow::setDownloadTrackerFavicon(const bool value)
539 if (m_transferListFiltersWidget)
540 m_transferListFiltersWidget->setDownloadTrackerFavicon(value);
541 m_storeDownloadTrackerFavicon = value;
544 void MainWindow::setTitleSuffix(const QString &suffix)
546 const auto emDash = QChar(0x2014);
547 const QString separator = u' ' + emDash + u' ';
548 m_windowTitle = QStringLiteral("qBittorrent " QBT_VERSION)
549 + (!suffix.isEmpty() ? (separator + suffix) : QString());
551 refreshWindowTitle();
554 void MainWindow::addToolbarContextMenu()
556 const Preferences *const pref = Preferences::instance();
557 m_toolbarMenu = new QMenu(this);
559 m_ui->toolBar->setContextMenuPolicy(Qt::CustomContextMenu);
560 connect(m_ui->toolBar, &QWidget::customContextMenuRequested, this, &MainWindow::toolbarMenuRequested);
562 QAction *iconsOnly = m_toolbarMenu->addAction(tr("Icons Only"), this, &MainWindow::toolbarIconsOnly);
563 QAction *textOnly = m_toolbarMenu->addAction(tr("Text Only"), this, &MainWindow::toolbarTextOnly);
564 QAction *textBesideIcons = m_toolbarMenu->addAction(tr("Text Alongside Icons"), this, &MainWindow::toolbarTextBeside);
565 QAction *textUnderIcons = m_toolbarMenu->addAction(tr("Text Under Icons"), this, &MainWindow::toolbarTextUnder);
566 QAction *followSystemStyle = m_toolbarMenu->addAction(tr("Follow System Style"), this, &MainWindow::toolbarFollowSystem);
568 auto *textPositionGroup = new QActionGroup(m_toolbarMenu);
569 textPositionGroup->addAction(iconsOnly);
570 iconsOnly->setCheckable(true);
571 textPositionGroup->addAction(textOnly);
572 textOnly->setCheckable(true);
573 textPositionGroup->addAction(textBesideIcons);
574 textBesideIcons->setCheckable(true);
575 textPositionGroup->addAction(textUnderIcons);
576 textUnderIcons->setCheckable(true);
577 textPositionGroup->addAction(followSystemStyle);
578 followSystemStyle->setCheckable(true);
580 const auto buttonStyle = static_cast<Qt::ToolButtonStyle>(pref->getToolbarTextPosition());
581 if ((buttonStyle >= Qt::ToolButtonIconOnly) && (buttonStyle <= Qt::ToolButtonFollowStyle))
582 m_ui->toolBar->setToolButtonStyle(buttonStyle);
583 switch (buttonStyle)
585 case Qt::ToolButtonIconOnly:
586 iconsOnly->setChecked(true);
587 break;
588 case Qt::ToolButtonTextOnly:
589 textOnly->setChecked(true);
590 break;
591 case Qt::ToolButtonTextBesideIcon:
592 textBesideIcons->setChecked(true);
593 break;
594 case Qt::ToolButtonTextUnderIcon:
595 textUnderIcons->setChecked(true);
596 break;
597 default:
598 followSystemStyle->setChecked(true);
602 void MainWindow::manageCookies()
604 auto *cookieDialog = new CookiesDialog(this);
605 cookieDialog->setAttribute(Qt::WA_DeleteOnClose);
606 cookieDialog->open();
609 void MainWindow::toolbarMenuRequested()
611 m_toolbarMenu->popup(QCursor::pos());
614 void MainWindow::toolbarIconsOnly()
616 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
617 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonIconOnly);
620 void MainWindow::toolbarTextOnly()
622 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextOnly);
623 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextOnly);
626 void MainWindow::toolbarTextBeside()
628 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
629 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextBesideIcon);
632 void MainWindow::toolbarTextUnder()
634 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
635 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextUnderIcon);
638 void MainWindow::toolbarFollowSystem()
640 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle);
641 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonFollowStyle);
644 bool MainWindow::defineUILockPassword()
646 bool ok = false;
647 const QString newPassword = AutoExpandableDialog::getText(this, tr("UI lock password")
648 , tr("Please type the UI lock password:"), QLineEdit::Password, {}, &ok);
649 if (!ok)
650 return false;
652 if (newPassword.size() < 3)
654 QMessageBox::warning(this, tr("Invalid password"), tr("The password must be at least 3 characters long"));
655 return false;
658 Preferences::instance()->setUILockPassword(Utils::Password::PBKDF2::generate(newPassword));
659 return true;
662 void MainWindow::clearUILockPassword()
664 const QMessageBox::StandardButton answer = QMessageBox::question(this, tr("Clear the password")
665 , tr("Are you sure you want to clear the password?"), (QMessageBox::Yes | QMessageBox::No), QMessageBox::No);
666 if (answer == QMessageBox::Yes)
667 Preferences::instance()->setUILockPassword({});
670 void MainWindow::on_actionLock_triggered()
672 Preferences *const pref = Preferences::instance();
674 // Check if there is a password
675 if (pref->getUILockPassword().isEmpty())
677 if (!defineUILockPassword())
678 return;
681 // Lock the interface
682 m_uiLocked = true;
683 pref->setUILocked(true);
684 app()->desktopIntegration()->menu()->setEnabled(false);
685 hide();
688 void MainWindow::handleRSSUnreadCountUpdated(int count)
690 m_tabs->setTabText(m_tabs->indexOf(m_rssWidget), tr("RSS (%1)").arg(count));
693 void MainWindow::displayRSSTab(bool enable)
695 if (enable)
697 // RSS tab
698 if (!m_rssWidget)
700 m_rssWidget = new RSSWidget(app(), m_tabs);
701 connect(m_rssWidget.data(), &RSSWidget::unreadCountUpdated, this, &MainWindow::handleRSSUnreadCountUpdated);
702 #ifdef Q_OS_MACOS
703 m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
704 #else
705 const int indexTab = m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
706 m_tabs->setTabIcon(indexTab, UIThemeManager::instance()->getIcon(u"application-rss"_s));
707 #endif
710 else
712 delete m_rssWidget;
716 void MainWindow::showFilterContextMenu()
718 const Preferences *pref = Preferences::instance();
720 QMenu *menu = m_columnFilterEdit->createStandardContextMenu();
721 menu->setAttribute(Qt::WA_DeleteOnClose);
722 menu->addSeparator();
724 QAction *useRegexAct = menu->addAction(tr("Use regular expressions"));
725 useRegexAct->setCheckable(true);
726 useRegexAct->setChecked(pref->getRegexAsFilteringPatternForTransferList());
727 connect(useRegexAct, &QAction::toggled, pref, &Preferences::setRegexAsFilteringPatternForTransferList);
728 connect(useRegexAct, &QAction::toggled, this, &MainWindow::applyTransferListFilter);
730 menu->popup(QCursor::pos());
733 void MainWindow::displaySearchTab(bool enable)
735 Preferences::instance()->setSearchEnabled(enable);
736 if (enable)
738 // RSS tab
739 if (!m_searchWidget)
741 m_searchWidget = new SearchWidget(app(), this);
742 m_tabs->insertTab(1, m_searchWidget,
743 #ifndef Q_OS_MACOS
744 UIThemeManager::instance()->getIcon(u"edit-find"_s),
745 #endif
746 tr("Search"));
749 else
751 delete m_searchWidget;
755 void MainWindow::toggleFocusBetweenLineEdits()
757 if (m_columnFilterEdit->hasFocus() && (m_propertiesWidget->tabBar()->currentIndex() == PropTabBar::FilesTab))
759 m_propertiesWidget->contentFilterLine()->setFocus();
760 m_propertiesWidget->contentFilterLine()->selectAll();
762 else
764 m_columnFilterEdit->setFocus();
765 m_columnFilterEdit->selectAll();
769 void MainWindow::updateNbTorrents()
771 m_tabs->setTabText(0, tr("Transfers (%1)").arg(m_transferListWidget->getSourceModel()->rowCount()));
774 void MainWindow::on_actionDocumentation_triggered() const
776 QDesktopServices::openUrl(QUrl(u"https://doc.qbittorrent.org"_s));
779 void MainWindow::tabChanged([[maybe_unused]] const int newTab)
781 // We cannot rely on the index newTab
782 // because the tab order is undetermined now
783 if (m_tabs->currentWidget() == m_splitter)
785 qDebug("Changed tab to transfer list, refreshing the list");
786 m_propertiesWidget->loadDynamicData();
787 m_columnFilterAction->setVisible(true);
788 return;
790 m_columnFilterAction->setVisible(false);
792 if (m_tabs->currentWidget() == m_searchWidget)
794 qDebug("Changed tab to search engine, giving focus to search input");
795 m_searchWidget->giveFocusToSearchInput();
799 void MainWindow::saveSettings() const
801 auto *pref = Preferences::instance();
802 pref->setMainGeometry(saveGeometry());
803 m_propertiesWidget->saveSettings();
806 void MainWindow::saveSplitterSettings() const
808 if (!m_transferListFiltersWidget)
809 return;
811 auto *pref = Preferences::instance();
812 pref->setFiltersSidebarWidth(m_splitter->sizes()[0]);
815 void MainWindow::cleanup()
817 if (!m_neverShown)
819 saveSettings();
820 saveSplitterSettings();
823 // delete RSSWidget explicitly to avoid crash in
824 // handleRSSUnreadCountUpdated() at application shutdown
825 delete m_rssWidget;
827 delete m_executableWatcher;
829 m_preventTimer->stop();
831 #if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
832 if (m_programUpdateTimer)
833 m_programUpdateTimer->stop();
834 #endif
836 // remove all child widgets
837 while (auto *w = findChild<QWidget *>())
838 delete w;
841 void MainWindow::loadSettings()
843 const auto *pref = Preferences::instance();
845 if (const QByteArray mainGeo = pref->getMainGeometry();
846 !mainGeo.isEmpty() && restoreGeometry(mainGeo))
848 m_posInitialized = true;
852 void MainWindow::desktopNotificationClicked()
854 if (isHidden())
856 if (m_uiLocked)
858 // Ask for UI lock password
859 if (!unlockUI())
860 return;
862 show();
863 if (isMinimized())
864 showNormal();
867 raise();
868 activateWindow();
871 void MainWindow::createKeyboardShortcuts()
873 m_ui->actionCreateTorrent->setShortcut(QKeySequence::New);
874 m_ui->actionOpen->setShortcut(QKeySequence::Open);
875 m_ui->actionDelete->setShortcut(QKeySequence::Delete);
876 m_ui->actionDelete->setShortcutContext(Qt::WidgetShortcut); // nullify its effect: delete key event is handled by respective widgets, not here
877 m_ui->actionDownloadFromURL->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_O);
878 m_ui->actionExit->setShortcut(Qt::CTRL | Qt::Key_Q);
879 #ifdef Q_OS_MACOS
880 m_ui->actionCloseWindow->setShortcut(QKeySequence::Close);
881 #else
882 m_ui->actionCloseWindow->setVisible(false);
883 #endif
885 const auto *switchTransferShortcut = new QShortcut((Qt::ALT | Qt::Key_1), this);
886 connect(switchTransferShortcut, &QShortcut::activated, this, &MainWindow::displayTransferTab);
887 const auto *switchSearchShortcut = new QShortcut((Qt::ALT | Qt::Key_2), this);
888 connect(switchSearchShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displaySearchTab));
889 const auto *switchRSSShortcut = new QShortcut((Qt::ALT | Qt::Key_3), this);
890 connect(switchRSSShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displayRSSTab));
891 const auto *switchExecutionLogShortcut = new QShortcut((Qt::ALT | Qt::Key_4), this);
892 connect(switchExecutionLogShortcut, &QShortcut::activated, this, &MainWindow::displayExecutionLogTab);
893 const auto *switchSearchFilterShortcut = new QShortcut(QKeySequence::Find, m_transferListWidget);
894 connect(switchSearchFilterShortcut, &QShortcut::activated, this, &MainWindow::toggleFocusBetweenLineEdits);
895 const auto *switchSearchFilterShortcutAlternative = new QShortcut((Qt::CTRL | Qt::Key_E), m_transferListWidget);
896 connect(switchSearchFilterShortcutAlternative, &QShortcut::activated, this, &MainWindow::toggleFocusBetweenLineEdits);
898 m_ui->actionDocumentation->setShortcut(QKeySequence::HelpContents);
899 m_ui->actionOptions->setShortcut(Qt::ALT | Qt::Key_O);
900 m_ui->actionStatistics->setShortcut(Qt::CTRL | Qt::Key_I);
901 m_ui->actionStart->setShortcut(Qt::CTRL | Qt::Key_S);
902 m_ui->actionStop->setShortcut(Qt::CTRL | Qt::Key_P);
903 m_ui->actionPauseSession->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_P);
904 m_ui->actionResumeSession->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_S);
905 m_ui->actionBottomQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Minus);
906 m_ui->actionDecreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Minus);
907 m_ui->actionIncreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Plus);
908 m_ui->actionTopQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Plus);
909 #ifdef Q_OS_MACOS
910 m_ui->actionMinimize->setShortcut(Qt::CTRL | Qt::Key_M);
911 addAction(m_ui->actionMinimize);
912 #endif
915 // Keyboard shortcuts slots
916 void MainWindow::displayTransferTab() const
918 m_tabs->setCurrentWidget(m_splitter);
921 void MainWindow::displaySearchTab()
923 if (!m_searchWidget)
925 m_ui->actionSearchWidget->setChecked(true);
926 displaySearchTab(true);
929 m_tabs->setCurrentWidget(m_searchWidget);
932 void MainWindow::displayRSSTab()
934 if (!m_rssWidget)
936 m_ui->actionRSSReader->setChecked(true);
937 displayRSSTab(true);
940 m_tabs->setCurrentWidget(m_rssWidget);
943 void MainWindow::displayExecutionLogTab()
945 if (!m_executionLog)
947 m_ui->actionExecutionLogs->setChecked(true);
948 on_actionExecutionLogs_triggered(true);
951 m_tabs->setCurrentWidget(m_executionLog);
954 // End of keyboard shortcuts slots
956 void MainWindow::on_actionSetGlobalSpeedLimits_triggered()
958 auto *dialog = new SpeedLimitDialog {this};
959 dialog->setAttribute(Qt::WA_DeleteOnClose);
960 dialog->open();
963 // Necessary if we want to close the window
964 // in one time if "close to systray" is enabled
965 void MainWindow::on_actionExit_triggered()
967 // UI locking enforcement.
968 if (isHidden() && m_uiLocked)
969 // Ask for UI lock password
970 if (!unlockUI()) return;
972 m_forceExit = true;
973 close();
976 #ifdef Q_OS_MACOS
977 void MainWindow::on_actionCloseWindow_triggered()
979 // On macOS window close is basically equivalent to window hide.
980 // If you decide to implement this functionality for other OS,
981 // then you will also need ui lock checks like in actionExit.
982 close();
984 #endif
986 QWidget *MainWindow::currentTabWidget() const
988 if (isMinimized() || !isVisible())
989 return nullptr;
990 if (m_tabs->currentIndex() == 0)
991 return m_transferListWidget;
992 return m_tabs->currentWidget();
995 TransferListWidget *MainWindow::transferListWidget() const
997 return m_transferListWidget;
1000 bool MainWindow::unlockUI()
1002 if (m_unlockDlgShowing)
1003 return false;
1005 bool ok = false;
1006 const QString password = AutoExpandableDialog::getText(this, tr("UI lock password")
1007 , tr("Please type the UI lock password:"), QLineEdit::Password, {}, &ok);
1008 if (!ok) return false;
1010 Preferences *const pref = Preferences::instance();
1012 const QByteArray secret = pref->getUILockPassword();
1013 if (!Utils::Password::PBKDF2::verify(secret, password))
1015 QMessageBox::warning(this, tr("Invalid password"), tr("The password is invalid"));
1016 return false;
1019 m_uiLocked = false;
1020 pref->setUILocked(false);
1021 app()->desktopIntegration()->menu()->setEnabled(true);
1022 return true;
1025 void MainWindow::notifyOfUpdate(const QString &)
1027 // Show restart message
1028 m_statusBar->showRestartRequired();
1029 LogMsg(tr("qBittorrent was just updated and needs to be restarted for the changes to be effective.")
1030 , Log::CRITICAL);
1031 // Delete the executable watcher
1032 delete m_executableWatcher;
1033 m_executableWatcher = nullptr;
1036 #ifndef Q_OS_MACOS
1037 // Toggle Main window visibility
1038 void MainWindow::toggleVisibility()
1040 if (isHidden())
1042 if (m_uiLocked && !unlockUI()) // Ask for UI lock password
1043 return;
1045 // Make sure the window is not minimized
1046 setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
1048 // Then show it
1049 show();
1050 raise();
1051 activateWindow();
1053 else
1055 hide();
1058 #endif // Q_OS_MACOS
1060 // Display About Dialog
1061 void MainWindow::on_actionAbout_triggered()
1063 // About dialog
1064 if (m_aboutDlg)
1066 m_aboutDlg->activateWindow();
1068 else
1070 m_aboutDlg = new AboutDialog(this);
1071 m_aboutDlg->setAttribute(Qt::WA_DeleteOnClose);
1072 m_aboutDlg->show();
1076 void MainWindow::on_actionStatistics_triggered()
1078 if (m_statsDlg)
1080 m_statsDlg->activateWindow();
1082 else
1084 m_statsDlg = new StatsDialog(this);
1085 m_statsDlg->setAttribute(Qt::WA_DeleteOnClose);
1086 m_statsDlg->show();
1090 void MainWindow::showEvent(QShowEvent *e)
1092 qDebug("** Show Event **");
1093 e->accept();
1095 if (isVisible())
1097 // preparations before showing the window
1099 if (m_neverShown)
1101 m_propertiesWidget->readSettings();
1102 m_neverShown = false;
1105 if (currentTabWidget() == m_transferListWidget)
1106 m_propertiesWidget->loadDynamicData();
1108 // Make sure the window is initially centered
1109 if (!m_posInitialized)
1111 move(Utils::Gui::screenCenter(this));
1112 m_posInitialized = true;
1115 else
1117 // to avoid blank screen when restoring from tray icon
1118 show();
1122 void MainWindow::keyPressEvent(QKeyEvent *event)
1124 if (event->matches(QKeySequence::Paste))
1126 const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData();
1128 if (mimeData->hasText())
1130 const QStringList lines = mimeData->text().split(u'\n', Qt::SkipEmptyParts);
1132 for (QString line : lines)
1134 line = line.trimmed();
1136 if (!isTorrentLink(line))
1137 continue;
1139 app()->addTorrentManager()->addTorrent(line);
1142 return;
1146 QMainWindow::keyPressEvent(event);
1149 // Called when we close the program
1150 void MainWindow::closeEvent(QCloseEvent *e)
1152 Preferences *const pref = Preferences::instance();
1153 #ifdef Q_OS_MACOS
1154 if (!m_forceExit)
1156 hide();
1157 e->accept();
1158 return;
1160 #else
1161 const bool goToSystrayOnExit = pref->closeToTray();
1162 if (!m_forceExit && app()->desktopIntegration()->isActive() && goToSystrayOnExit && !this->isHidden())
1164 e->ignore();
1165 QMetaObject::invokeMethod(this, &QWidget::hide, Qt::QueuedConnection);
1166 if (!pref->closeToTrayNotified())
1168 app()->desktopIntegration()->showNotification(tr("qBittorrent is closed to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
1169 pref->setCloseToTrayNotified(true);
1171 return;
1173 #endif // Q_OS_MACOS
1175 const QList<BitTorrent::Torrent *> allTorrents = BitTorrent::Session::instance()->torrents();
1176 const bool hasActiveTorrents = std::any_of(allTorrents.cbegin(), allTorrents.cend(), [](BitTorrent::Torrent *torrent)
1178 return torrent->isActive();
1180 if (pref->confirmOnExit() && hasActiveTorrents)
1182 if (e->spontaneous() || m_forceExit)
1184 if (!isVisible())
1185 show();
1186 QMessageBox confirmBox(QMessageBox::Question, tr("Exiting qBittorrent"),
1187 // Split it because the last sentence is used in the WebUI
1188 tr("Some files are currently transferring.") + u'\n' + tr("Are you sure you want to quit qBittorrent?"),
1189 QMessageBox::NoButton, this);
1190 QPushButton *noBtn = confirmBox.addButton(tr("&No"), QMessageBox::NoRole);
1191 confirmBox.addButton(tr("&Yes"), QMessageBox::YesRole);
1192 QPushButton *alwaysBtn = confirmBox.addButton(tr("&Always Yes"), QMessageBox::YesRole);
1193 confirmBox.setDefaultButton(noBtn);
1194 confirmBox.exec();
1195 if (!confirmBox.clickedButton() || (confirmBox.clickedButton() == noBtn))
1197 // Cancel exit
1198 e->ignore();
1199 m_forceExit = false;
1200 return;
1202 if (confirmBox.clickedButton() == alwaysBtn)
1203 // Remember choice
1204 Preferences::instance()->setConfirmOnExit(false);
1208 // Accept exit
1209 e->accept();
1210 qApp->exit();
1213 // Display window to create a torrent
1214 void MainWindow::on_actionCreateTorrent_triggered()
1216 createTorrentTriggered({});
1219 void MainWindow::createTorrentTriggered(const Path &path)
1221 if (m_createTorrentDlg)
1223 m_createTorrentDlg->updateInputPath(path);
1224 m_createTorrentDlg->activateWindow();
1226 else
1228 m_createTorrentDlg = new TorrentCreatorDialog(this, path);
1229 m_createTorrentDlg->setAttribute(Qt::WA_DeleteOnClose);
1230 m_createTorrentDlg->show();
1234 bool MainWindow::event(QEvent *e)
1236 #ifndef Q_OS_MACOS
1237 switch (e->type())
1239 case QEvent::WindowStateChange:
1240 qDebug("Window change event");
1241 // Now check to see if the window is minimised
1242 if (isMinimized())
1244 qDebug("minimisation");
1245 Preferences *const pref = Preferences::instance();
1246 if (app()->desktopIntegration()->isActive() && pref->minimizeToTray())
1248 qDebug() << "Has active window:" << (qApp->activeWindow() != nullptr);
1249 // Check if there is a modal window
1250 const QWidgetList allWidgets = QApplication::allWidgets();
1251 const bool hasModalWindow = std::any_of(allWidgets.cbegin(), allWidgets.cend()
1252 , [](const QWidget *widget) { return widget->isModal(); });
1253 // Iconify if there is no modal window
1254 if (!hasModalWindow)
1256 qDebug("Minimize to Tray enabled, hiding!");
1257 e->ignore();
1258 QMetaObject::invokeMethod(this, &QWidget::hide, Qt::QueuedConnection);
1259 if (!pref->minimizeToTrayNotified())
1261 app()->desktopIntegration()->showNotification(tr("qBittorrent is minimized to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
1262 pref->setMinimizeToTrayNotified(true);
1264 return true;
1268 break;
1269 case QEvent::ToolBarChange:
1271 qDebug("MAC: Received a toolbar change event!");
1272 const bool ret = QMainWindow::event(e);
1274 qDebug("MAC: new toolbar visibility is %d", !m_ui->actionTopToolBar->isChecked());
1275 m_ui->actionTopToolBar->toggle();
1276 Preferences::instance()->setToolbarDisplayed(m_ui->actionTopToolBar->isChecked());
1277 return ret;
1279 default:
1280 break;
1282 #endif // Q_OS_MACOS
1284 return QMainWindow::event(e);
1287 // action executed when a file is dropped
1288 void MainWindow::dropEvent(QDropEvent *event)
1290 event->acceptProposedAction();
1292 // remove scheme
1293 QStringList files;
1294 if (event->mimeData()->hasUrls())
1296 for (const QUrl &url : asConst(event->mimeData()->urls()))
1298 if (url.isEmpty())
1299 continue;
1301 files << ((url.scheme().compare(u"file", Qt::CaseInsensitive) == 0)
1302 ? url.toLocalFile()
1303 : url.toString());
1306 else
1308 files = event->mimeData()->text().split(u'\n');
1311 // differentiate ".torrent" files/links & magnet links from others
1312 QStringList torrentFiles, otherFiles;
1313 for (const QString &file : asConst(files))
1315 if (isTorrentLink(file))
1316 torrentFiles << file;
1317 else
1318 otherFiles << file;
1321 // Download torrents
1322 for (const QString &file : asConst(torrentFiles))
1323 app()->addTorrentManager()->addTorrent(file);
1324 if (!torrentFiles.isEmpty()) return;
1326 // Create torrent
1327 for (const QString &file : asConst(otherFiles))
1329 createTorrentTriggered(Path(file));
1331 // currently only handle the first entry
1332 // this is a stub that can be expanded later to create many torrents at once
1333 break;
1337 // Decode if we accept drag 'n drop or not
1338 void MainWindow::dragEnterEvent(QDragEnterEvent *event)
1340 for (const QString &mime : asConst(event->mimeData()->formats()))
1341 qDebug("mimeData: %s", mime.toLocal8Bit().data());
1343 if (event->mimeData()->hasFormat(u"text/plain"_s) || event->mimeData()->hasFormat(u"text/uri-list"_s))
1344 event->acceptProposedAction();
1347 // Display a dialog to allow user to add
1348 // torrents to download list
1349 void MainWindow::on_actionOpen_triggered()
1351 Preferences *const pref = Preferences::instance();
1352 // Open File Open Dialog
1353 // Note: it is possible to select more than one file
1354 const QStringList pathsList = QFileDialog::getOpenFileNames(this, tr("Open Torrent Files")
1355 , pref->getMainLastDir().data(), tr("Torrent Files") + u" (*" + TORRENT_FILE_EXTENSION + u')');
1357 if (pathsList.isEmpty())
1358 return;
1360 for (const QString &file : pathsList)
1361 app()->addTorrentManager()->addTorrent(file);
1363 // Save last dir to remember it
1364 const Path topDir {pathsList.at(0)};
1365 const Path parentDir = topDir.parentPath();
1366 pref->setMainLastDir(parentDir.isEmpty() ? topDir : parentDir);
1369 void MainWindow::activate()
1371 if (!m_uiLocked || unlockUI())
1373 show();
1374 activateWindow();
1375 raise();
1379 void MainWindow::optionsSaved()
1381 LogMsg(tr("Options saved."));
1382 loadPreferences();
1385 void MainWindow::showStatusBar(bool show)
1387 if (!show)
1389 // Remove status bar
1390 setStatusBar(nullptr);
1392 else if (!m_statusBar)
1394 // Create status bar
1395 m_statusBar = new StatusBar;
1396 connect(m_statusBar.data(), &StatusBar::connectionButtonClicked, this, &MainWindow::showConnectionSettings);
1397 connect(m_statusBar.data(), &StatusBar::alternativeSpeedsButtonClicked, this, &MainWindow::toggleAlternativeSpeeds);
1398 setStatusBar(m_statusBar);
1402 void MainWindow::showFiltersSidebar(const bool show)
1404 if (show && !m_transferListFiltersWidget)
1406 m_transferListFiltersWidget = new TransferListFiltersWidget(m_splitter, m_transferListWidget, isDownloadTrackerFavicon());
1407 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersAdded, m_transferListFiltersWidget, &TransferListFiltersWidget::addTrackers);
1408 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersRemoved, m_transferListFiltersWidget, &TransferListFiltersWidget::removeTrackers);
1409 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersChanged, m_transferListFiltersWidget, &TransferListFiltersWidget::refreshTrackers);
1410 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerEntryStatusesUpdated, m_transferListFiltersWidget, &TransferListFiltersWidget::trackerEntryStatusesUpdated);
1412 m_splitter->insertWidget(0, m_transferListFiltersWidget);
1413 m_splitter->setCollapsible(0, true);
1414 // From https://doc.qt.io/qt-5/qsplitter.html#setSizes:
1415 // Instead, any additional/missing space is distributed amongst the widgets
1416 // according to the relative weight of the sizes.
1417 m_splitter->setStretchFactor(0, 0);
1418 m_splitter->setStretchFactor(1, 1);
1419 m_splitter->setSizes({Preferences::instance()->getFiltersSidebarWidth()});
1421 else if (!show && m_transferListFiltersWidget)
1423 saveSplitterSettings();
1424 delete m_transferListFiltersWidget;
1425 m_transferListFiltersWidget = nullptr;
1429 void MainWindow::loadPreferences()
1431 const Preferences *pref = Preferences::instance();
1433 // General
1434 if (pref->isToolbarDisplayed())
1436 m_ui->toolBar->setVisible(true);
1438 else
1440 // Clear search filter before hiding the top toolbar
1441 m_columnFilterEdit->clear();
1442 m_ui->toolBar->setVisible(false);
1445 showStatusBar(pref->isStatusbarDisplayed());
1447 m_transferListWidget->setAlternatingRowColors(pref->useAlternatingRowColors());
1448 m_propertiesWidget->getFilesList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1449 m_propertiesWidget->getTrackerList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1450 m_propertiesWidget->getPeerList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1452 // Queueing System
1453 if (BitTorrent::Session::instance()->isQueueingSystemEnabled())
1455 if (!m_ui->actionDecreaseQueuePos->isVisible())
1457 m_transferListWidget->hideQueuePosColumn(false);
1458 m_ui->actionDecreaseQueuePos->setVisible(true);
1459 m_ui->actionIncreaseQueuePos->setVisible(true);
1460 m_ui->actionTopQueuePos->setVisible(true);
1461 m_ui->actionBottomQueuePos->setVisible(true);
1462 #ifndef Q_OS_MACOS
1463 m_queueSeparator->setVisible(true);
1464 #endif
1465 m_queueSeparatorMenu->setVisible(true);
1468 else
1470 if (m_ui->actionDecreaseQueuePos->isVisible())
1472 m_transferListWidget->hideQueuePosColumn(true);
1473 m_ui->actionDecreaseQueuePos->setVisible(false);
1474 m_ui->actionIncreaseQueuePos->setVisible(false);
1475 m_ui->actionTopQueuePos->setVisible(false);
1476 m_ui->actionBottomQueuePos->setVisible(false);
1477 #ifndef Q_OS_MACOS
1478 m_queueSeparator->setVisible(false);
1479 #endif
1480 m_queueSeparatorMenu->setVisible(false);
1484 // Torrent properties
1485 m_propertiesWidget->reloadPreferences();
1487 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1488 if (pref->isUpdateCheckEnabled())
1490 if (!m_programUpdateTimer)
1492 m_programUpdateTimer = new QTimer(this);
1493 m_programUpdateTimer->setInterval(24h);
1494 m_programUpdateTimer->setSingleShot(true);
1495 connect(m_programUpdateTimer, &QTimer::timeout, this, [this]() { checkProgramUpdate(false); });
1496 m_programUpdateTimer->start();
1499 else
1501 delete m_programUpdateTimer;
1502 m_programUpdateTimer = nullptr;
1504 #endif
1506 qDebug("GUI settings loaded");
1509 void MainWindow::loadSessionStats()
1511 const auto *btSession = BitTorrent::Session::instance();
1512 const BitTorrent::SessionStatus &status = btSession->status();
1513 m_downloadRate = Utils::Misc::friendlyUnit(status.payloadDownloadRate, true);
1514 m_uploadRate = Utils::Misc::friendlyUnit(status.payloadUploadRate, true);
1516 // update global information
1517 #ifdef Q_OS_MACOS
1518 m_badger->updateSpeed(status.payloadDownloadRate, status.payloadUploadRate);
1519 #else
1520 refreshTrayIconTooltip();
1521 #endif // Q_OS_MACOS
1523 refreshWindowTitle();
1526 void MainWindow::reloadTorrentStats(const QList<BitTorrent::Torrent *> &torrents)
1528 if (currentTabWidget() == m_transferListWidget)
1530 if (torrents.contains(m_propertiesWidget->getCurrentTorrent()))
1531 m_propertiesWidget->loadDynamicData();
1535 void MainWindow::downloadFromURLList(const QStringList &urlList)
1537 for (const QString &url : urlList)
1538 app()->addTorrentManager()->addTorrent(url);
1541 void MainWindow::populateDesktopIntegrationMenu()
1543 auto *menu = app()->desktopIntegration()->menu();
1544 menu->clear();
1546 #ifndef Q_OS_MACOS
1547 connect(menu, &QMenu::aboutToShow, this, [this]()
1549 m_ui->actionToggleVisibility->setText(isVisible() ? tr("Hide") : tr("Show"));
1551 connect(m_ui->actionToggleVisibility, &QAction::triggered, this, &MainWindow::toggleVisibility);
1553 menu->addAction(m_ui->actionToggleVisibility);
1554 menu->addSeparator();
1555 #endif
1557 menu->addAction(m_ui->actionOpen);
1558 menu->addAction(m_ui->actionDownloadFromURL);
1559 menu->addSeparator();
1561 menu->addAction(m_ui->actionUseAlternativeSpeedLimits);
1562 menu->addAction(m_ui->actionSetGlobalSpeedLimits);
1563 menu->addSeparator();
1565 menu->addAction(m_ui->actionResumeSession);
1566 menu->addAction(m_ui->actionPauseSession);
1568 #ifndef Q_OS_MACOS
1569 menu->addSeparator();
1570 menu->addAction(m_ui->actionExit);
1571 #endif
1573 if (m_uiLocked)
1574 menu->setEnabled(false);
1577 void MainWindow::updateAltSpeedsBtn(const bool alternative)
1579 m_ui->actionUseAlternativeSpeedLimits->setChecked(alternative);
1582 PropertiesWidget *MainWindow::propertiesWidget() const
1584 return m_propertiesWidget;
1587 // Display Program Options
1588 void MainWindow::on_actionOptions_triggered()
1590 if (m_options)
1592 m_options->activateWindow();
1594 else
1596 m_options = new OptionsDialog(app(), this);
1597 m_options->setAttribute(Qt::WA_DeleteOnClose);
1598 m_options->open();
1602 void MainWindow::on_actionTopToolBar_triggered()
1604 const bool isVisible = static_cast<QAction *>(sender())->isChecked();
1605 m_ui->toolBar->setVisible(isVisible);
1606 Preferences::instance()->setToolbarDisplayed(isVisible);
1609 void MainWindow::on_actionShowStatusbar_triggered()
1611 const bool isVisible = static_cast<QAction *>(sender())->isChecked();
1612 Preferences::instance()->setStatusbarDisplayed(isVisible);
1613 showStatusBar(isVisible);
1616 void MainWindow::on_actionShowFiltersSidebar_triggered(const bool checked)
1618 Preferences *const pref = Preferences::instance();
1619 pref->setFiltersSidebarVisible(checked);
1620 showFiltersSidebar(checked);
1623 void MainWindow::on_actionSpeedInTitleBar_triggered()
1625 m_displaySpeedInTitle = static_cast<QAction *>(sender())->isChecked();
1626 Preferences::instance()->showSpeedInTitleBar(m_displaySpeedInTitle);
1627 refreshWindowTitle();
1630 void MainWindow::on_actionRSSReader_triggered()
1632 Preferences::instance()->setRSSWidgetVisible(m_ui->actionRSSReader->isChecked());
1633 displayRSSTab(m_ui->actionRSSReader->isChecked());
1636 void MainWindow::on_actionSearchWidget_triggered()
1638 if (m_ui->actionSearchWidget->isChecked())
1640 const Utils::ForeignApps::PythonInfo pyInfo = Utils::ForeignApps::pythonInfo();
1642 // Not found
1643 if (!pyInfo.isValid())
1645 m_ui->actionSearchWidget->setChecked(false);
1646 Preferences::instance()->setSearchEnabled(false);
1648 #ifdef Q_OS_WIN
1649 const QMessageBox::StandardButton buttonPressed = QMessageBox::question(this, tr("Missing Python Runtime")
1650 , tr("Python is required to use the search engine but it does not seem to be installed.\nDo you want to install it now?")
1651 , (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
1652 if (buttonPressed == QMessageBox::Yes)
1653 installPython();
1654 #else
1655 QMessageBox::information(this, tr("Missing Python Runtime")
1656 , tr("Python is required to use the search engine but it does not seem to be installed."));
1657 #endif
1658 return;
1661 // Check version requirement
1662 if (!pyInfo.isSupportedVersion())
1664 m_ui->actionSearchWidget->setChecked(false);
1665 Preferences::instance()->setSearchEnabled(false);
1667 #ifdef Q_OS_WIN
1668 const QMessageBox::StandardButton buttonPressed = QMessageBox::question(this, tr("Old Python Runtime")
1669 , tr("Your Python version (%1) is outdated. Minimum requirement: %2.\nDo you want to install a newer version now?")
1670 .arg(pyInfo.version.toString(), u"3.9.0")
1671 , (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
1672 if (buttonPressed == QMessageBox::Yes)
1673 installPython();
1674 #else
1675 QMessageBox::information(this, tr("Old Python Runtime")
1676 , tr("Your Python version (%1) is outdated. Please upgrade to latest version for search engines to work.\nMinimum requirement: %2.")
1677 .arg(pyInfo.version.toString(), u"3.9.0"));
1678 #endif
1679 return;
1682 m_ui->actionSearchWidget->setChecked(true);
1683 Preferences::instance()->setSearchEnabled(true);
1686 displaySearchTab(m_ui->actionSearchWidget->isChecked());
1689 // Display an input dialog to prompt user for
1690 // an url
1691 void MainWindow::on_actionDownloadFromURL_triggered()
1693 if (!m_downloadFromURLDialog)
1695 m_downloadFromURLDialog = new DownloadFromURLDialog(this);
1696 m_downloadFromURLDialog->setAttribute(Qt::WA_DeleteOnClose);
1697 connect(m_downloadFromURLDialog.data(), &DownloadFromURLDialog::urlsReadyToBeDownloaded, this, &MainWindow::downloadFromURLList);
1698 m_downloadFromURLDialog->open();
1702 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1703 void MainWindow::handleUpdateCheckFinished(ProgramUpdater *updater, const bool invokedByUser)
1705 m_ui->actionCheckForUpdates->setEnabled(true);
1706 m_ui->actionCheckForUpdates->setText(tr("&Check for Updates"));
1707 m_ui->actionCheckForUpdates->setToolTip(tr("Check for program updates"));
1709 const auto cleanup = [this, updater]()
1711 if (m_programUpdateTimer)
1712 m_programUpdateTimer->start();
1713 updater->deleteLater();
1716 const QString newVersion = updater->getNewVersion();
1717 if (!newVersion.isEmpty())
1719 const QString msg {tr("A new version is available.") + u"<br/>"
1720 + tr("Do you want to download %1?").arg(newVersion) + u"<br/><br/>"
1721 + u"<a href=\"https://www.qbittorrent.org/news.php\">%1</a>"_s.arg(tr("Open changelog..."))};
1722 auto *msgBox = new QMessageBox {QMessageBox::Question, tr("qBittorrent Update Available"), msg
1723 , (QMessageBox::Yes | QMessageBox::No), this};
1724 msgBox->setAttribute(Qt::WA_DeleteOnClose);
1725 msgBox->setAttribute(Qt::WA_ShowWithoutActivating);
1726 msgBox->setDefaultButton(QMessageBox::Yes);
1727 msgBox->setWindowModality(Qt::NonModal);
1728 connect(msgBox, &QMessageBox::buttonClicked, this, [msgBox, updater](QAbstractButton *button)
1730 if (msgBox->buttonRole(button) == QMessageBox::YesRole)
1732 updater->updateProgram();
1735 connect(msgBox, &QDialog::finished, this, cleanup);
1736 msgBox->show();
1738 else
1740 if (invokedByUser)
1742 auto *msgBox = new QMessageBox {QMessageBox::Information, u"qBittorrent"_s
1743 , tr("No updates available.\nYou are already using the latest version.")
1744 , QMessageBox::Ok, this};
1745 msgBox->setAttribute(Qt::WA_DeleteOnClose);
1746 msgBox->setWindowModality(Qt::NonModal);
1747 connect(msgBox, &QDialog::finished, this, cleanup);
1748 msgBox->show();
1750 else
1752 cleanup();
1756 #endif
1758 void MainWindow::toggleAlternativeSpeeds()
1760 BitTorrent::Session *const session = BitTorrent::Session::instance();
1761 session->setAltGlobalSpeedLimitEnabled(!session->isAltGlobalSpeedLimitEnabled());
1764 void MainWindow::on_actionDonateMoney_triggered()
1766 QDesktopServices::openUrl(QUrl(u"https://www.qbittorrent.org/donate"_s));
1769 void MainWindow::showConnectionSettings()
1771 on_actionOptions_triggered();
1772 m_options->showConnectionTab();
1775 void MainWindow::minimizeWindow()
1777 setWindowState(windowState() | Qt::WindowMinimized);
1780 void MainWindow::on_actionExecutionLogs_triggered(bool checked)
1782 if (checked)
1784 Q_ASSERT(!m_executionLog);
1785 m_executionLog = new ExecutionLogWidget(executionLogMsgTypes(), m_tabs);
1786 #ifdef Q_OS_MACOS
1787 m_tabs->addTab(m_executionLog, tr("Execution Log"));
1788 #else
1789 const int indexTab = m_tabs->addTab(m_executionLog, tr("Execution Log"));
1790 m_tabs->setTabIcon(indexTab, UIThemeManager::instance()->getIcon(u"help-contents"_s));
1791 #endif
1793 else
1795 delete m_executionLog;
1798 m_ui->actionNormalMessages->setEnabled(checked);
1799 m_ui->actionInformationMessages->setEnabled(checked);
1800 m_ui->actionWarningMessages->setEnabled(checked);
1801 m_ui->actionCriticalMessages->setEnabled(checked);
1802 setExecutionLogEnabled(checked);
1805 void MainWindow::on_actionNormalMessages_triggered(const bool checked)
1807 if (!m_executionLog)
1808 return;
1810 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::NORMAL, checked);
1811 setExecutionLogMsgTypes(flags);
1814 void MainWindow::on_actionInformationMessages_triggered(const bool checked)
1816 if (!m_executionLog)
1817 return;
1819 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::INFO, checked);
1820 setExecutionLogMsgTypes(flags);
1823 void MainWindow::on_actionWarningMessages_triggered(const bool checked)
1825 if (!m_executionLog)
1826 return;
1828 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::WARNING, checked);
1829 setExecutionLogMsgTypes(flags);
1832 void MainWindow::on_actionCriticalMessages_triggered(const bool checked)
1834 if (!m_executionLog)
1835 return;
1837 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::CRITICAL, checked);
1838 setExecutionLogMsgTypes(flags);
1841 void MainWindow::on_actionAutoExit_toggled(bool enabled)
1843 qDebug() << Q_FUNC_INFO << enabled;
1844 Preferences::instance()->setShutdownqBTWhenDownloadsComplete(enabled);
1847 void MainWindow::on_actionAutoSuspend_toggled(bool enabled)
1849 qDebug() << Q_FUNC_INFO << enabled;
1850 Preferences::instance()->setSuspendWhenDownloadsComplete(enabled);
1853 void MainWindow::on_actionAutoHibernate_toggled(bool enabled)
1855 qDebug() << Q_FUNC_INFO << enabled;
1856 Preferences::instance()->setHibernateWhenDownloadsComplete(enabled);
1859 void MainWindow::on_actionAutoShutdown_toggled(bool enabled)
1861 qDebug() << Q_FUNC_INFO << enabled;
1862 Preferences::instance()->setShutdownWhenDownloadsComplete(enabled);
1865 void MainWindow::updatePowerManagementState() const
1867 const auto *pref = Preferences::instance();
1868 const bool preventFromSuspendWhenDownloading = pref->preventFromSuspendWhenDownloading();
1869 const bool preventFromSuspendWhenSeeding = pref->preventFromSuspendWhenSeeding();
1871 const QList<BitTorrent::Torrent *> allTorrents = BitTorrent::Session::instance()->torrents();
1872 const bool inhibitSuspend = std::any_of(allTorrents.cbegin(), allTorrents.cend(), [&](const BitTorrent::Torrent *torrent)
1874 if (preventFromSuspendWhenDownloading && (!torrent->isFinished() && !torrent->isStopped() && !torrent->isErrored() && torrent->hasMetadata()))
1875 return true;
1877 if (preventFromSuspendWhenSeeding && (torrent->isFinished() && !torrent->isStopped()))
1878 return true;
1880 return torrent->isMoving();
1882 m_pwr->setActivityState(inhibitSuspend);
1884 m_preventTimer->start(PREVENT_SUSPEND_INTERVAL);
1887 void MainWindow::applyTransferListFilter()
1889 m_transferListWidget->applyFilter(m_columnFilterEdit->text(), m_columnFilterComboBox->currentData().value<TransferListModel::Column>());
1892 void MainWindow::refreshWindowTitle()
1894 const auto *btSession = BitTorrent::Session::instance();
1895 if (btSession->isPaused())
1897 const QString title = tr("[PAUSED] %1", "%1 is the rest of the window title").arg(m_windowTitle);
1898 setWindowTitle(title);
1900 else
1902 if (m_displaySpeedInTitle)
1904 const QString title = tr("[D: %1, U: %2] %3", "D = Download; U = Upload; %3 is the rest of the window title")
1905 .arg(m_downloadRate, m_uploadRate, m_windowTitle);
1906 setWindowTitle(title);
1908 else
1910 setWindowTitle(m_windowTitle);
1915 void MainWindow::refreshTrayIconTooltip()
1917 const auto *btSession = BitTorrent::Session::instance();
1918 if (!btSession->isPaused())
1920 const auto toolTip = u"%1\n%2"_s.arg(
1921 tr("DL speed: %1", "e.g: Download speed: 10 KiB/s").arg(m_downloadRate)
1922 , tr("UP speed: %1", "e.g: Upload speed: 10 KiB/s").arg(m_uploadRate));
1923 app()->desktopIntegration()->setToolTip(toolTip);
1925 else
1927 app()->desktopIntegration()->setToolTip(tr("Paused"));
1931 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1932 void MainWindow::checkProgramUpdate(const bool invokedByUser)
1934 if (m_programUpdateTimer)
1935 m_programUpdateTimer->stop();
1937 m_ui->actionCheckForUpdates->setEnabled(false);
1938 m_ui->actionCheckForUpdates->setText(tr("Checking for Updates..."));
1939 m_ui->actionCheckForUpdates->setToolTip(tr("Already checking for program updates in the background"));
1941 auto *updater = new ProgramUpdater(this);
1942 connect(updater, &ProgramUpdater::updateCheckFinished
1943 , this, [this, invokedByUser, updater]()
1945 handleUpdateCheckFinished(updater, invokedByUser);
1947 updater->checkForUpdates();
1949 #endif
1951 #ifdef Q_OS_WIN
1952 void MainWindow::installPython()
1954 setCursor(QCursor(Qt::WaitCursor));
1955 // Download python
1956 const auto installerURL = u"https://www.python.org/ftp/python/3.12.4/python-3.12.4-amd64.exe"_s;
1957 Net::DownloadManager::instance()->download(
1958 Net::DownloadRequest(installerURL).saveToFile(true)
1959 , Preferences::instance()->useProxyForGeneralPurposes()
1960 , this, &MainWindow::pythonDownloadFinished);
1963 void MainWindow::pythonDownloadFinished(const Net::DownloadResult &result)
1965 if (result.status != Net::DownloadStatus::Success)
1967 setCursor(QCursor(Qt::ArrowCursor));
1968 QMessageBox::warning(
1969 this, tr("Download error")
1970 , tr("Python setup could not be downloaded, reason: %1.\nPlease install it manually.")
1971 .arg(result.errorString));
1972 return;
1975 setCursor(QCursor(Qt::ArrowCursor));
1976 QProcess installer;
1977 qDebug("Launching Python installer in passive mode...");
1979 const Path exePath = result.filePath + u".exe";
1980 Utils::Fs::renameFile(result.filePath, exePath);
1981 installer.start(exePath.toString(), {u"/passive"_s});
1983 // Wait for setup to complete
1984 installer.waitForFinished(10 * 60 * 1000);
1986 qDebug("Installer stdout: %s", installer.readAllStandardOutput().data());
1987 qDebug("Installer stderr: %s", installer.readAllStandardError().data());
1988 qDebug("Setup should be complete!");
1990 // Delete temp file
1991 Utils::Fs::removeFile(exePath);
1993 // Reload search engine
1994 if (Utils::ForeignApps::pythonInfo().isSupportedVersion())
1996 m_ui->actionSearchWidget->setChecked(true);
1997 displaySearchTab(true);
2000 #endif // Q_OS_WIN