Drop WebUI default credentials
[qBittorrent.git] / src / gui / mainwindow.cpp
blobef8458ef88fc4dd5ad260073c5b2b854e6e97c92
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2022-2023 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 <QTimer>
59 #include "base/bittorrent/session.h"
60 #include "base/bittorrent/sessionstatus.h"
61 #include "base/global.h"
62 #include "base/net/downloadmanager.h"
63 #include "base/path.h"
64 #include "base/preferences.h"
65 #include "base/rss/rss_folder.h"
66 #include "base/rss/rss_session.h"
67 #include "base/utils/foreignapps.h"
68 #include "base/utils/fs.h"
69 #include "base/utils/misc.h"
70 #include "base/utils/password.h"
71 #include "base/version.h"
72 #include "aboutdialog.h"
73 #include "autoexpandabledialog.h"
74 #include "cookiesdialog.h"
75 #include "desktopintegration.h"
76 #include "downloadfromurldialog.h"
77 #include "executionlogwidget.h"
78 #include "hidabletabwidget.h"
79 #include "interfaces/iguiapplication.h"
80 #include "lineedit.h"
81 #include "optionsdialog.h"
82 #include "powermanagement/powermanagement.h"
83 #include "properties/peerlistwidget.h"
84 #include "properties/propertieswidget.h"
85 #include "rss/rsswidget.h"
86 #include "search/searchwidget.h"
87 #include "speedlimitdialog.h"
88 #include "statsdialog.h"
89 #include "statusbar.h"
90 #include "torrentcreatordialog.h"
91 #include "trackerlist/trackerlistwidget.h"
92 #include "transferlistfilterswidget.h"
93 #include "transferlistmodel.h"
94 #include "transferlistwidget.h"
95 #include "ui_mainwindow.h"
96 #include "uithememanager.h"
97 #include "utils.h"
99 #ifdef Q_OS_MACOS
100 #include "macosdockbadge/badger.h"
101 #endif
102 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
103 #include "programupdater.h"
104 #endif
106 using namespace std::chrono_literals;
108 namespace
110 #define SETTINGS_KEY(name) u"GUI/" name
111 #define EXECUTIONLOG_SETTINGS_KEY(name) (SETTINGS_KEY(u"Log/"_s) name)
113 const std::chrono::seconds PREVENT_SUSPEND_INTERVAL {60};
115 bool isTorrentLink(const QString &str)
117 return str.startsWith(u"magnet:", Qt::CaseInsensitive)
118 || str.endsWith(TORRENT_FILE_EXTENSION, Qt::CaseInsensitive)
119 || (!str.startsWith(u"file:", Qt::CaseInsensitive)
120 && Net::DownloadManager::hasSupportedScheme(str));
124 MainWindow::MainWindow(IGUIApplication *app, WindowState initialState)
125 : GUIApplicationComponent(app)
126 , m_ui(new Ui::MainWindow)
127 , m_storeExecutionLogEnabled(EXECUTIONLOG_SETTINGS_KEY(u"Enabled"_s))
128 , m_storeDownloadTrackerFavicon(SETTINGS_KEY(u"DownloadTrackerFavicon"_s))
129 , m_storeExecutionLogTypes(EXECUTIONLOG_SETTINGS_KEY(u"Types"_s), Log::MsgType::ALL)
130 #ifdef Q_OS_MACOS
131 , m_badger(std::make_unique<MacUtils::Badger>())
132 #endif // Q_OS_MACOS
134 m_ui->setupUi(this);
136 Preferences *const pref = Preferences::instance();
137 m_uiLocked = pref->isUILocked();
138 setWindowTitle(QStringLiteral("qBittorrent " QBT_VERSION));
139 m_displaySpeedInTitle = pref->speedInTitleBar();
140 // Setting icons
141 #ifndef Q_OS_MACOS
142 setWindowIcon(UIThemeManager::instance()->getIcon(u"qbittorrent"_s));
143 #endif // Q_OS_MACOS
145 #if (defined(Q_OS_UNIX))
146 m_ui->actionOptions->setText(tr("Preferences"));
147 #endif
149 addToolbarContextMenu();
151 m_ui->actionOpen->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_s));
152 m_ui->actionDownloadFromURL->setIcon(UIThemeManager::instance()->getIcon(u"insert-link"_s));
153 m_ui->actionSetGlobalSpeedLimits->setIcon(UIThemeManager::instance()->getIcon(u"speedometer"_s));
154 m_ui->actionCreateTorrent->setIcon(UIThemeManager::instance()->getIcon(u"torrent-creator"_s, u"document-edit"_s));
155 m_ui->actionAbout->setIcon(UIThemeManager::instance()->getIcon(u"help-about"_s));
156 m_ui->actionStatistics->setIcon(UIThemeManager::instance()->getIcon(u"view-statistics"_s));
157 m_ui->actionTopQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-top"_s));
158 m_ui->actionIncreaseQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-up"_s));
159 m_ui->actionDecreaseQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-down"_s));
160 m_ui->actionBottomQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-bottom"_s));
161 m_ui->actionDelete->setIcon(UIThemeManager::instance()->getIcon(u"list-remove"_s));
162 m_ui->actionDocumentation->setIcon(UIThemeManager::instance()->getIcon(u"help-contents"_s));
163 m_ui->actionDonateMoney->setIcon(UIThemeManager::instance()->getIcon(u"wallet-open"_s));
164 m_ui->actionExit->setIcon(UIThemeManager::instance()->getIcon(u"application-exit"_s));
165 m_ui->actionLock->setIcon(UIThemeManager::instance()->getIcon(u"object-locked"_s));
166 m_ui->actionOptions->setIcon(UIThemeManager::instance()->getIcon(u"configure"_s, u"preferences-system"_s));
167 m_ui->actionPause->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_s, u"media-playback-pause"_s));
168 m_ui->actionPauseAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_s, u"media-playback-pause"_s));
169 m_ui->actionStart->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_s, u"media-playback-start"_s));
170 m_ui->actionStartAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_s, u"media-playback-start"_s));
171 m_ui->menuAutoShutdownOnDownloadsCompletion->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_s, u"application-exit"_s));
172 m_ui->actionManageCookies->setIcon(UIThemeManager::instance()->getIcon(u"browser-cookies"_s, u"preferences-web-browser-cookies"_s));
173 m_ui->menuLog->setIcon(UIThemeManager::instance()->getIcon(u"help-contents"_s));
174 m_ui->actionCheckForUpdates->setIcon(UIThemeManager::instance()->getIcon(u"view-refresh"_s));
176 auto *lockMenu = new QMenu(this);
177 lockMenu->addAction(tr("&Set Password"), this, &MainWindow::defineUILockPassword);
178 lockMenu->addAction(tr("&Clear Password"), this, &MainWindow::clearUILockPassword);
179 m_ui->actionLock->setMenu(lockMenu);
181 // Creating Bittorrent session
182 updateAltSpeedsBtn(BitTorrent::Session::instance()->isAltGlobalSpeedLimitEnabled());
184 connect(BitTorrent::Session::instance(), &BitTorrent::Session::speedLimitModeChanged, this, &MainWindow::updateAltSpeedsBtn);
186 qDebug("create tabWidget");
187 m_tabs = new HidableTabWidget(this);
188 connect(m_tabs.data(), &QTabWidget::currentChanged, this, &MainWindow::tabChanged);
190 m_splitter = new QSplitter(Qt::Horizontal, this);
191 // vSplitter->setChildrenCollapsible(false);
193 auto *hSplitter = new QSplitter(Qt::Vertical, this);
194 hSplitter->setChildrenCollapsible(false);
195 hSplitter->setFrameShape(QFrame::NoFrame);
197 // Torrent filter
198 m_columnFilterEdit = new LineEdit;
199 m_columnFilterEdit->setPlaceholderText(tr("Filter torrents..."));
200 m_columnFilterEdit->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
201 m_columnFilterEdit->setFixedWidth(200);
202 m_columnFilterEdit->setContextMenuPolicy(Qt::CustomContextMenu);
203 connect(m_columnFilterEdit, &QWidget::customContextMenuRequested, this, &MainWindow::showFilterContextMenu);
204 auto *columnFilterLabel = new QLabel(tr("Filter by:"));
205 m_columnFilterComboBox = new QComboBox;
206 QHBoxLayout *columnFilterLayout = new QHBoxLayout(m_columnFilterWidget);
207 columnFilterLayout->setContentsMargins(0, 0, 0, 0);
208 auto *columnFilterSpacer = new QWidget(this);
209 columnFilterSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
210 columnFilterLayout->addWidget(columnFilterSpacer);
211 columnFilterLayout->addWidget(m_columnFilterEdit);
212 columnFilterLayout->addWidget(columnFilterLabel, 0);
213 columnFilterLayout->addWidget(m_columnFilterComboBox, 0);
214 m_columnFilterWidget = new QWidget(this);
215 m_columnFilterWidget->setLayout(columnFilterLayout);
216 m_columnFilterAction = m_ui->toolBar->insertWidget(m_ui->actionLock, m_columnFilterWidget);
218 auto *spacer = new QWidget(this);
219 spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
220 m_ui->toolBar->insertWidget(m_columnFilterAction, spacer);
222 // Transfer List tab
223 m_transferListWidget = new TransferListWidget(hSplitter, this);
224 m_propertiesWidget = new PropertiesWidget(hSplitter);
225 connect(m_transferListWidget, &TransferListWidget::currentTorrentChanged, m_propertiesWidget, &PropertiesWidget::loadTorrentInfos);
226 hSplitter->addWidget(m_transferListWidget);
227 hSplitter->addWidget(m_propertiesWidget);
228 m_splitter->addWidget(hSplitter);
229 m_splitter->setCollapsible(0, false);
230 m_tabs->addTab(m_splitter,
231 #ifndef Q_OS_MACOS
232 UIThemeManager::instance()->getIcon(u"folder-remote"_s),
233 #endif
234 tr("Transfers"));
235 // Filter types
236 const QVector<TransferListModel::Column> filterTypes = {TransferListModel::Column::TR_NAME, TransferListModel::Column::TR_SAVE_PATH};
237 for (const TransferListModel::Column type : filterTypes)
239 const QString typeName = m_transferListWidget->getSourceModel()->headerData(type, Qt::Horizontal, Qt::DisplayRole).value<QString>();
240 m_columnFilterComboBox->addItem(typeName, type);
242 connect(m_columnFilterComboBox, &QComboBox::currentIndexChanged, this, &MainWindow::applyTransferListFilter);
243 connect(m_columnFilterEdit, &LineEdit::textChanged, this, &MainWindow::applyTransferListFilter);
244 connect(hSplitter, &QSplitter::splitterMoved, this, &MainWindow::saveSettings);
245 connect(m_splitter, &QSplitter::splitterMoved, this, &MainWindow::saveSplitterSettings);
247 #ifdef Q_OS_MACOS
248 // Increase top spacing to avoid tab overlapping
249 m_ui->centralWidgetLayout->addSpacing(8);
250 #endif
252 m_ui->centralWidgetLayout->addWidget(m_tabs);
254 m_queueSeparator = m_ui->toolBar->insertSeparator(m_ui->actionTopQueuePos);
255 m_queueSeparatorMenu = m_ui->menuEdit->insertSeparator(m_ui->actionTopQueuePos);
257 #ifdef Q_OS_MACOS
258 for (QAction *action : asConst(m_ui->toolBar->actions()))
260 if (action->isSeparator())
262 QWidget *spacer = new QWidget(this);
263 spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
264 spacer->setMinimumWidth(16);
265 m_ui->toolBar->insertWidget(action, spacer);
266 m_ui->toolBar->removeAction(action);
270 QWidget *spacer = new QWidget(this);
271 spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
272 spacer->setMinimumWidth(8);
273 m_ui->toolBar->insertWidget(m_ui->actionDownloadFromURL, spacer);
276 QWidget *spacer = new QWidget(this);
277 spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
278 spacer->setMinimumWidth(8);
279 m_ui->toolBar->addWidget(spacer);
281 #endif // Q_OS_MACOS
283 // Transfer list slots
284 connect(m_ui->actionStart, &QAction::triggered, m_transferListWidget, &TransferListWidget::startSelectedTorrents);
285 connect(m_ui->actionStartAll, &QAction::triggered, m_transferListWidget, &TransferListWidget::resumeAllTorrents);
286 connect(m_ui->actionPause, &QAction::triggered, m_transferListWidget, &TransferListWidget::pauseSelectedTorrents);
287 connect(m_ui->actionPauseAll, &QAction::triggered, m_transferListWidget, &TransferListWidget::pauseAllTorrents);
288 connect(m_ui->actionDelete, &QAction::triggered, m_transferListWidget, &TransferListWidget::softDeleteSelectedTorrents);
289 connect(m_ui->actionTopQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::topQueuePosSelectedTorrents);
290 connect(m_ui->actionIncreaseQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::increaseQueuePosSelectedTorrents);
291 connect(m_ui->actionDecreaseQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::decreaseQueuePosSelectedTorrents);
292 connect(m_ui->actionBottomQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::bottomQueuePosSelectedTorrents);
293 #ifndef Q_OS_MACOS
294 connect(m_ui->actionToggleVisibility, &QAction::triggered, this, &MainWindow::toggleVisibility);
295 #endif
296 connect(m_ui->actionMinimize, &QAction::triggered, this, &MainWindow::minimizeWindow);
297 connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds);
299 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
300 connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, [this]() { checkProgramUpdate(true); });
302 // trigger an early check on startup
303 if (pref->isUpdateCheckEnabled())
304 checkProgramUpdate(false);
305 #else
306 m_ui->actionCheckForUpdates->setVisible(false);
307 #endif
309 // Certain menu items should reside at specific places on macOS.
310 // Qt partially does it on its own, but updates and different languages require tuning.
311 m_ui->actionExit->setMenuRole(QAction::QuitRole);
312 m_ui->actionAbout->setMenuRole(QAction::AboutRole);
313 m_ui->actionCheckForUpdates->setMenuRole(QAction::ApplicationSpecificRole);
314 m_ui->actionOptions->setMenuRole(QAction::PreferencesRole);
316 connect(m_ui->actionManageCookies, &QAction::triggered, this, &MainWindow::manageCookies);
318 // Initialise system sleep inhibition timer
319 m_pwr = new PowerManagement(this);
320 m_preventTimer = new QTimer(this);
321 m_preventTimer->setSingleShot(true);
322 connect(m_preventTimer, &QTimer::timeout, this, &MainWindow::updatePowerManagementState);
323 connect(pref, &Preferences::changed, this, &MainWindow::updatePowerManagementState);
324 updatePowerManagementState();
326 // Configure BT session according to options
327 loadPreferences();
329 connect(BitTorrent::Session::instance(), &BitTorrent::Session::statsUpdated, this, &MainWindow::reloadSessionStats);
330 connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsUpdated, this, &MainWindow::reloadTorrentStats);
332 // Accept drag 'n drops
333 setAcceptDrops(true);
334 createKeyboardShortcuts();
336 #ifdef Q_OS_MACOS
337 setUnifiedTitleAndToolBarOnMac(true);
338 #endif
340 // View settings
341 m_ui->actionTopToolBar->setChecked(pref->isToolbarDisplayed());
342 m_ui->actionShowStatusbar->setChecked(pref->isStatusbarDisplayed());
343 m_ui->actionSpeedInTitleBar->setChecked(pref->speedInTitleBar());
344 m_ui->actionRSSReader->setChecked(pref->isRSSWidgetEnabled());
345 m_ui->actionSearchWidget->setChecked(pref->isSearchEnabled());
346 m_ui->actionExecutionLogs->setChecked(isExecutionLogEnabled());
348 const Log::MsgTypes flags = executionLogMsgTypes();
349 m_ui->actionNormalMessages->setChecked(flags.testFlag(Log::NORMAL));
350 m_ui->actionInformationMessages->setChecked(flags.testFlag(Log::INFO));
351 m_ui->actionWarningMessages->setChecked(flags.testFlag(Log::WARNING));
352 m_ui->actionCriticalMessages->setChecked(flags.testFlag(Log::CRITICAL));
354 displayRSSTab(m_ui->actionRSSReader->isChecked());
355 on_actionExecutionLogs_triggered(m_ui->actionExecutionLogs->isChecked());
356 on_actionNormalMessages_triggered(m_ui->actionNormalMessages->isChecked());
357 on_actionInformationMessages_triggered(m_ui->actionInformationMessages->isChecked());
358 on_actionWarningMessages_triggered(m_ui->actionWarningMessages->isChecked());
359 on_actionCriticalMessages_triggered(m_ui->actionCriticalMessages->isChecked());
360 if (m_ui->actionSearchWidget->isChecked())
361 QMetaObject::invokeMethod(this, &MainWindow::on_actionSearchWidget_triggered, Qt::QueuedConnection);
362 // Auto shutdown actions
363 auto *autoShutdownGroup = new QActionGroup(this);
364 autoShutdownGroup->setExclusive(true);
365 autoShutdownGroup->addAction(m_ui->actionAutoShutdownDisabled);
366 autoShutdownGroup->addAction(m_ui->actionAutoExit);
367 autoShutdownGroup->addAction(m_ui->actionAutoShutdown);
368 autoShutdownGroup->addAction(m_ui->actionAutoSuspend);
369 autoShutdownGroup->addAction(m_ui->actionAutoHibernate);
370 #if (!defined(Q_OS_UNIX) || defined(Q_OS_MACOS)) || defined(QBT_USES_DBUS)
371 m_ui->actionAutoShutdown->setChecked(pref->shutdownWhenDownloadsComplete());
372 m_ui->actionAutoSuspend->setChecked(pref->suspendWhenDownloadsComplete());
373 m_ui->actionAutoHibernate->setChecked(pref->hibernateWhenDownloadsComplete());
374 #else
375 m_ui->actionAutoShutdown->setDisabled(true);
376 m_ui->actionAutoSuspend->setDisabled(true);
377 m_ui->actionAutoHibernate->setDisabled(true);
378 #endif
379 m_ui->actionAutoExit->setChecked(pref->shutdownqBTWhenDownloadsComplete());
381 if (!autoShutdownGroup->checkedAction())
382 m_ui->actionAutoShutdownDisabled->setChecked(true);
384 // Load Window state and sizes
385 loadSettings();
387 app->desktopIntegration()->setMenu(createDesktopIntegrationMenu());
388 #ifndef Q_OS_MACOS
389 m_ui->actionLock->setVisible(app->desktopIntegration()->isActive());
390 connect(app->desktopIntegration(), &DesktopIntegration::stateChanged, this, [this, app]()
392 m_ui->actionLock->setVisible(app->desktopIntegration()->isActive());
394 #endif
395 connect(app->desktopIntegration(), &DesktopIntegration::notificationClicked, this, &MainWindow::desktopNotificationClicked);
396 connect(app->desktopIntegration(), &DesktopIntegration::activationRequested, this, [this]()
398 #ifdef Q_OS_MACOS
399 if (!isVisible())
400 activate();
401 #else
402 toggleVisibility();
403 #endif
406 #ifdef Q_OS_MACOS
407 if (initialState == WindowState::Normal)
409 show();
410 activateWindow();
411 raise();
413 else
415 // Make sure the Window is visible if we don't have a tray icon
416 showMinimized();
418 #else
419 if (app->desktopIntegration()->isActive())
421 if ((initialState == WindowState::Normal) && !m_uiLocked)
423 show();
424 activateWindow();
425 raise();
427 else if (initialState == WindowState::Minimized)
429 showMinimized();
430 if (pref->minimizeToTray())
432 hide();
433 if (!pref->minimizeToTrayNotified())
435 app->desktopIntegration()->showNotification(tr("qBittorrent is minimized to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
436 pref->setMinimizeToTrayNotified(true);
441 else
443 // Make sure the Window is visible if we don't have a tray icon
444 if (initialState != WindowState::Normal)
446 showMinimized();
448 else
450 show();
451 activateWindow();
452 raise();
455 #endif
457 const bool isFiltersSidebarVisible = pref->isFiltersSidebarVisible();
458 m_ui->actionShowFiltersSidebar->setChecked(isFiltersSidebarVisible);
459 if (isFiltersSidebarVisible)
461 showFiltersSidebar(true);
463 else
465 m_transferListWidget->applyStatusFilter(pref->getTransSelFilter());
466 m_transferListWidget->applyCategoryFilter(QString());
467 m_transferListWidget->applyTagFilter(QString());
468 m_transferListWidget->applyTrackerFilterAll();
471 // Start watching the executable for updates
472 m_executableWatcher = new QFileSystemWatcher(this);
473 connect(m_executableWatcher, &QFileSystemWatcher::fileChanged, this, &MainWindow::notifyOfUpdate);
474 m_executableWatcher->addPath(qApp->applicationFilePath());
476 m_transferListWidget->setFocus();
478 // Update the number of torrents (tab)
479 updateNbTorrents();
480 connect(m_transferListWidget->getSourceModel(), &QAbstractItemModel::rowsInserted, this, &MainWindow::updateNbTorrents);
481 connect(m_transferListWidget->getSourceModel(), &QAbstractItemModel::rowsRemoved, this, &MainWindow::updateNbTorrents);
483 connect(pref, &Preferences::changed, this, &MainWindow::optionsSaved);
485 qDebug("GUI Built");
488 MainWindow::~MainWindow()
490 delete m_ui;
493 bool MainWindow::isExecutionLogEnabled() const
495 return m_storeExecutionLogEnabled;
498 void MainWindow::setExecutionLogEnabled(const bool value)
500 m_storeExecutionLogEnabled = value;
503 Log::MsgTypes MainWindow::executionLogMsgTypes() const
505 return m_storeExecutionLogTypes;
508 void MainWindow::setExecutionLogMsgTypes(const Log::MsgTypes value)
510 m_executionLog->setMessageTypes(value);
511 m_storeExecutionLogTypes = value;
514 bool MainWindow::isDownloadTrackerFavicon() const
516 return m_storeDownloadTrackerFavicon;
519 void MainWindow::setDownloadTrackerFavicon(const bool value)
521 if (m_transferListFiltersWidget)
522 m_transferListFiltersWidget->setDownloadTrackerFavicon(value);
523 m_storeDownloadTrackerFavicon = value;
526 void MainWindow::addToolbarContextMenu()
528 const Preferences *const pref = Preferences::instance();
529 m_toolbarMenu = new QMenu(this);
531 m_ui->toolBar->setContextMenuPolicy(Qt::CustomContextMenu);
532 connect(m_ui->toolBar, &QWidget::customContextMenuRequested, this, &MainWindow::toolbarMenuRequested);
534 QAction *iconsOnly = m_toolbarMenu->addAction(tr("Icons Only"), this, &MainWindow::toolbarIconsOnly);
535 QAction *textOnly = m_toolbarMenu->addAction(tr("Text Only"), this, &MainWindow::toolbarTextOnly);
536 QAction *textBesideIcons = m_toolbarMenu->addAction(tr("Text Alongside Icons"), this, &MainWindow::toolbarTextBeside);
537 QAction *textUnderIcons = m_toolbarMenu->addAction(tr("Text Under Icons"), this, &MainWindow::toolbarTextUnder);
538 QAction *followSystemStyle = m_toolbarMenu->addAction(tr("Follow System Style"), this, &MainWindow::toolbarFollowSystem);
540 auto *textPositionGroup = new QActionGroup(m_toolbarMenu);
541 textPositionGroup->addAction(iconsOnly);
542 iconsOnly->setCheckable(true);
543 textPositionGroup->addAction(textOnly);
544 textOnly->setCheckable(true);
545 textPositionGroup->addAction(textBesideIcons);
546 textBesideIcons->setCheckable(true);
547 textPositionGroup->addAction(textUnderIcons);
548 textUnderIcons->setCheckable(true);
549 textPositionGroup->addAction(followSystemStyle);
550 followSystemStyle->setCheckable(true);
552 const auto buttonStyle = static_cast<Qt::ToolButtonStyle>(pref->getToolbarTextPosition());
553 if ((buttonStyle >= Qt::ToolButtonIconOnly) && (buttonStyle <= Qt::ToolButtonFollowStyle))
554 m_ui->toolBar->setToolButtonStyle(buttonStyle);
555 switch (buttonStyle)
557 case Qt::ToolButtonIconOnly:
558 iconsOnly->setChecked(true);
559 break;
560 case Qt::ToolButtonTextOnly:
561 textOnly->setChecked(true);
562 break;
563 case Qt::ToolButtonTextBesideIcon:
564 textBesideIcons->setChecked(true);
565 break;
566 case Qt::ToolButtonTextUnderIcon:
567 textUnderIcons->setChecked(true);
568 break;
569 default:
570 followSystemStyle->setChecked(true);
574 void MainWindow::manageCookies()
576 auto *cookieDialog = new CookiesDialog(this);
577 cookieDialog->setAttribute(Qt::WA_DeleteOnClose);
578 cookieDialog->open();
581 void MainWindow::toolbarMenuRequested()
583 m_toolbarMenu->popup(QCursor::pos());
586 void MainWindow::toolbarIconsOnly()
588 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
589 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonIconOnly);
592 void MainWindow::toolbarTextOnly()
594 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextOnly);
595 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextOnly);
598 void MainWindow::toolbarTextBeside()
600 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
601 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextBesideIcon);
604 void MainWindow::toolbarTextUnder()
606 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
607 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextUnderIcon);
610 void MainWindow::toolbarFollowSystem()
612 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle);
613 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonFollowStyle);
616 bool MainWindow::defineUILockPassword()
618 bool ok = false;
619 const QString newPassword = AutoExpandableDialog::getText(this, tr("UI lock password")
620 , tr("Please type the UI lock password:"), QLineEdit::Password, {}, &ok);
621 if (!ok)
622 return false;
624 if (newPassword.size() < 3)
626 QMessageBox::warning(this, tr("Invalid password"), tr("The password must be at least 3 characters long"));
627 return false;
630 Preferences::instance()->setUILockPassword(Utils::Password::PBKDF2::generate(newPassword));
631 return true;
634 void MainWindow::clearUILockPassword()
636 const QMessageBox::StandardButton answer = QMessageBox::question(this, tr("Clear the password")
637 , tr("Are you sure you want to clear the password?"), (QMessageBox::Yes | QMessageBox::No), QMessageBox::No);
638 if (answer == QMessageBox::Yes)
639 Preferences::instance()->setUILockPassword({});
642 void MainWindow::on_actionLock_triggered()
644 Preferences *const pref = Preferences::instance();
646 // Check if there is a password
647 if (pref->getUILockPassword().isEmpty())
649 if (!defineUILockPassword())
650 return;
653 // Lock the interface
654 m_uiLocked = true;
655 pref->setUILocked(true);
656 app()->desktopIntegration()->menu()->setEnabled(false);
657 hide();
660 void MainWindow::handleRSSUnreadCountUpdated(int count)
662 m_tabs->setTabText(m_tabs->indexOf(m_rssWidget), tr("RSS (%1)").arg(count));
665 void MainWindow::displayRSSTab(bool enable)
667 if (enable)
669 // RSS tab
670 if (!m_rssWidget)
672 m_rssWidget = new RSSWidget(app(), m_tabs);
673 connect(m_rssWidget.data(), &RSSWidget::unreadCountUpdated, this, &MainWindow::handleRSSUnreadCountUpdated);
674 #ifdef Q_OS_MACOS
675 m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
676 #else
677 const int indexTab = m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
678 m_tabs->setTabIcon(indexTab, UIThemeManager::instance()->getIcon(u"application-rss"_s));
679 #endif
682 else
684 delete m_rssWidget;
688 void MainWindow::showFilterContextMenu()
690 const Preferences *pref = Preferences::instance();
692 QMenu *menu = m_columnFilterEdit->createStandardContextMenu();
693 menu->setAttribute(Qt::WA_DeleteOnClose);
694 menu->addSeparator();
696 QAction *useRegexAct = menu->addAction(tr("Use regular expressions"));
697 useRegexAct->setCheckable(true);
698 useRegexAct->setChecked(pref->getRegexAsFilteringPatternForTransferList());
699 connect(useRegexAct, &QAction::toggled, pref, &Preferences::setRegexAsFilteringPatternForTransferList);
700 connect(useRegexAct, &QAction::toggled, this, &MainWindow::applyTransferListFilter);
702 menu->popup(QCursor::pos());
705 void MainWindow::displaySearchTab(bool enable)
707 Preferences::instance()->setSearchEnabled(enable);
708 if (enable)
710 // RSS tab
711 if (!m_searchWidget)
713 m_searchWidget = new SearchWidget(app(), this);
714 m_tabs->insertTab(1, m_searchWidget,
715 #ifndef Q_OS_MACOS
716 UIThemeManager::instance()->getIcon(u"edit-find"_s),
717 #endif
718 tr("Search"));
721 else
723 delete m_searchWidget;
727 void MainWindow::focusSearchFilter()
729 m_columnFilterEdit->setFocus();
730 m_columnFilterEdit->selectAll();
733 void MainWindow::updateNbTorrents()
735 m_tabs->setTabText(0, tr("Transfers (%1)").arg(m_transferListWidget->getSourceModel()->rowCount()));
738 void MainWindow::on_actionDocumentation_triggered() const
740 QDesktopServices::openUrl(QUrl(u"https://doc.qbittorrent.org"_s));
743 void MainWindow::tabChanged([[maybe_unused]] const int newTab)
745 // We cannot rely on the index newTab
746 // because the tab order is undetermined now
747 if (m_tabs->currentWidget() == m_splitter)
749 qDebug("Changed tab to transfer list, refreshing the list");
750 m_propertiesWidget->loadDynamicData();
751 m_columnFilterAction->setVisible(true);
752 return;
754 m_columnFilterAction->setVisible(false);
756 if (m_tabs->currentWidget() == m_searchWidget)
758 qDebug("Changed tab to search engine, giving focus to search input");
759 m_searchWidget->giveFocusToSearchInput();
763 void MainWindow::saveSettings() const
765 auto *pref = Preferences::instance();
766 pref->setMainGeometry(saveGeometry());
767 m_propertiesWidget->saveSettings();
770 void MainWindow::saveSplitterSettings() const
772 if (!m_transferListFiltersWidget)
773 return;
775 auto *pref = Preferences::instance();
776 pref->setFiltersSidebarWidth(m_splitter->sizes()[0]);
779 void MainWindow::cleanup()
781 saveSettings();
782 saveSplitterSettings();
784 // delete RSSWidget explicitly to avoid crash in
785 // handleRSSUnreadCountUpdated() at application shutdown
786 delete m_rssWidget;
788 delete m_executableWatcher;
790 m_preventTimer->stop();
792 #if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
793 if (m_programUpdateTimer)
794 m_programUpdateTimer->stop();
795 #endif
797 // remove all child widgets
798 while (auto *w = findChild<QWidget *>())
799 delete w;
802 void MainWindow::loadSettings()
804 const auto *pref = Preferences::instance();
806 if (const QByteArray mainGeo = pref->getMainGeometry();
807 !mainGeo.isEmpty() && restoreGeometry(mainGeo))
809 m_posInitialized = true;
813 void MainWindow::desktopNotificationClicked()
815 if (isHidden())
817 if (m_uiLocked)
819 // Ask for UI lock password
820 if (!unlockUI())
821 return;
823 show();
824 if (isMinimized())
825 showNormal();
828 raise();
829 activateWindow();
832 void MainWindow::createKeyboardShortcuts()
834 m_ui->actionCreateTorrent->setShortcut(QKeySequence::New);
835 m_ui->actionOpen->setShortcut(QKeySequence::Open);
836 m_ui->actionDelete->setShortcut(QKeySequence::Delete);
837 m_ui->actionDelete->setShortcutContext(Qt::WidgetShortcut); // nullify its effect: delete key event is handled by respective widgets, not here
838 m_ui->actionDownloadFromURL->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_O);
839 m_ui->actionExit->setShortcut(Qt::CTRL | Qt::Key_Q);
840 #ifdef Q_OS_MACOS
841 m_ui->actionCloseWindow->setShortcut(QKeySequence::Close);
842 #else
843 m_ui->actionCloseWindow->setVisible(false);
844 #endif
846 const auto *switchTransferShortcut = new QShortcut((Qt::ALT | Qt::Key_1), this);
847 connect(switchTransferShortcut, &QShortcut::activated, this, &MainWindow::displayTransferTab);
848 const auto *switchSearchShortcut = new QShortcut((Qt::ALT | Qt::Key_2), this);
849 connect(switchSearchShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displaySearchTab));
850 const auto *switchRSSShortcut = new QShortcut((Qt::ALT | Qt::Key_3), this);
851 connect(switchRSSShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displayRSSTab));
852 const auto *switchExecutionLogShortcut = new QShortcut((Qt::ALT | Qt::Key_4), this);
853 connect(switchExecutionLogShortcut, &QShortcut::activated, this, &MainWindow::displayExecutionLogTab);
854 const auto *switchSearchFilterShortcut = new QShortcut(QKeySequence::Find, m_transferListWidget);
855 connect(switchSearchFilterShortcut, &QShortcut::activated, this, &MainWindow::focusSearchFilter);
856 const auto *switchSearchFilterShortcutAlternative = new QShortcut((Qt::CTRL | Qt::Key_E), m_transferListWidget);
857 connect(switchSearchFilterShortcutAlternative, &QShortcut::activated, this, &MainWindow::focusSearchFilter);
859 m_ui->actionDocumentation->setShortcut(QKeySequence::HelpContents);
860 m_ui->actionOptions->setShortcut(Qt::ALT | Qt::Key_O);
861 m_ui->actionStatistics->setShortcut(Qt::CTRL | Qt::Key_I);
862 m_ui->actionStart->setShortcut(Qt::CTRL | Qt::Key_S);
863 m_ui->actionStartAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_S);
864 m_ui->actionPause->setShortcut(Qt::CTRL | Qt::Key_P);
865 m_ui->actionPauseAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_P);
866 m_ui->actionBottomQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Minus);
867 m_ui->actionDecreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Minus);
868 m_ui->actionIncreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Plus);
869 m_ui->actionTopQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Plus);
870 #ifdef Q_OS_MACOS
871 m_ui->actionMinimize->setShortcut(Qt::CTRL | Qt::Key_M);
872 addAction(m_ui->actionMinimize);
873 #endif
876 // Keyboard shortcuts slots
877 void MainWindow::displayTransferTab() const
879 m_tabs->setCurrentWidget(m_splitter);
882 void MainWindow::displaySearchTab()
884 if (!m_searchWidget)
886 m_ui->actionSearchWidget->setChecked(true);
887 displaySearchTab(true);
890 m_tabs->setCurrentWidget(m_searchWidget);
893 void MainWindow::displayRSSTab()
895 if (!m_rssWidget)
897 m_ui->actionRSSReader->setChecked(true);
898 displayRSSTab(true);
901 m_tabs->setCurrentWidget(m_rssWidget);
904 void MainWindow::displayExecutionLogTab()
906 if (!m_executionLog)
908 m_ui->actionExecutionLogs->setChecked(true);
909 on_actionExecutionLogs_triggered(true);
912 m_tabs->setCurrentWidget(m_executionLog);
915 // End of keyboard shortcuts slots
917 void MainWindow::on_actionSetGlobalSpeedLimits_triggered()
919 auto *dialog = new SpeedLimitDialog {this};
920 dialog->setAttribute(Qt::WA_DeleteOnClose);
921 dialog->open();
924 // Necessary if we want to close the window
925 // in one time if "close to systray" is enabled
926 void MainWindow::on_actionExit_triggered()
928 // UI locking enforcement.
929 if (isHidden() && m_uiLocked)
930 // Ask for UI lock password
931 if (!unlockUI()) return;
933 m_forceExit = true;
934 close();
937 #ifdef Q_OS_MACOS
938 void MainWindow::on_actionCloseWindow_triggered()
940 // On macOS window close is basically equivalent to window hide.
941 // If you decide to implement this functionality for other OS,
942 // then you will also need ui lock checks like in actionExit.
943 close();
945 #endif
947 QWidget *MainWindow::currentTabWidget() const
949 if (isMinimized() || !isVisible())
950 return nullptr;
951 if (m_tabs->currentIndex() == 0)
952 return m_transferListWidget;
953 return m_tabs->currentWidget();
956 TransferListWidget *MainWindow::transferListWidget() const
958 return m_transferListWidget;
961 bool MainWindow::unlockUI()
963 if (m_unlockDlgShowing)
964 return false;
966 bool ok = false;
967 const QString password = AutoExpandableDialog::getText(this, tr("UI lock password")
968 , tr("Please type the UI lock password:"), QLineEdit::Password, {}, &ok);
969 if (!ok) return false;
971 Preferences *const pref = Preferences::instance();
973 const QByteArray secret = pref->getUILockPassword();
974 if (!Utils::Password::PBKDF2::verify(secret, password))
976 QMessageBox::warning(this, tr("Invalid password"), tr("The password is invalid"));
977 return false;
980 m_uiLocked = false;
981 pref->setUILocked(false);
982 app()->desktopIntegration()->menu()->setEnabled(true);
983 return true;
986 void MainWindow::notifyOfUpdate(const QString &)
988 // Show restart message
989 m_statusBar->showRestartRequired();
990 LogMsg(tr("qBittorrent was just updated and needs to be restarted for the changes to be effective.")
991 , Log::CRITICAL);
992 // Delete the executable watcher
993 delete m_executableWatcher;
994 m_executableWatcher = nullptr;
997 #ifndef Q_OS_MACOS
998 // Toggle Main window visibility
999 void MainWindow::toggleVisibility()
1001 if (isHidden())
1003 if (m_uiLocked && !unlockUI()) // Ask for UI lock password
1004 return;
1006 // Make sure the window is not minimized
1007 setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
1009 // Then show it
1010 show();
1011 raise();
1012 activateWindow();
1014 else
1016 hide();
1019 #endif // Q_OS_MACOS
1021 // Display About Dialog
1022 void MainWindow::on_actionAbout_triggered()
1024 // About dialog
1025 if (m_aboutDlg)
1027 m_aboutDlg->activateWindow();
1029 else
1031 m_aboutDlg = new AboutDialog(this);
1032 m_aboutDlg->setAttribute(Qt::WA_DeleteOnClose);
1033 m_aboutDlg->show();
1037 void MainWindow::on_actionStatistics_triggered()
1039 if (m_statsDlg)
1041 m_statsDlg->activateWindow();
1043 else
1045 m_statsDlg = new StatsDialog(this);
1046 m_statsDlg->setAttribute(Qt::WA_DeleteOnClose);
1047 m_statsDlg->show();
1051 void MainWindow::showEvent(QShowEvent *e)
1053 qDebug("** Show Event **");
1054 e->accept();
1056 if (isVisible())
1058 // preparations before showing the window
1060 if (m_neverShown)
1062 m_propertiesWidget->readSettings();
1063 m_neverShown = false;
1066 if (currentTabWidget() == m_transferListWidget)
1067 m_propertiesWidget->loadDynamicData();
1069 // Make sure the window is initially centered
1070 if (!m_posInitialized)
1072 move(Utils::Gui::screenCenter(this));
1073 m_posInitialized = true;
1076 else
1078 // to avoid blank screen when restoring from tray icon
1079 show();
1083 void MainWindow::keyPressEvent(QKeyEvent *event)
1085 if (event->matches(QKeySequence::Paste))
1087 const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData();
1089 if (mimeData->hasText())
1091 const QStringList lines = mimeData->text().split(u'\n', Qt::SkipEmptyParts);
1093 for (QString line : lines)
1095 line = line.trimmed();
1097 if (!isTorrentLink(line))
1098 continue;
1100 app()->addTorrentManager()->addTorrent(line);
1103 return;
1107 QMainWindow::keyPressEvent(event);
1110 // Called when we close the program
1111 void MainWindow::closeEvent(QCloseEvent *e)
1113 Preferences *const pref = Preferences::instance();
1114 #ifdef Q_OS_MACOS
1115 if (!m_forceExit)
1117 hide();
1118 e->accept();
1119 return;
1121 #else
1122 const bool goToSystrayOnExit = pref->closeToTray();
1123 if (!m_forceExit && app()->desktopIntegration()->isActive() && goToSystrayOnExit && !this->isHidden())
1125 e->ignore();
1126 QMetaObject::invokeMethod(this, &QWidget::hide, Qt::QueuedConnection);
1127 if (!pref->closeToTrayNotified())
1129 app()->desktopIntegration()->showNotification(tr("qBittorrent is closed to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
1130 pref->setCloseToTrayNotified(true);
1132 return;
1134 #endif // Q_OS_MACOS
1136 const QVector<BitTorrent::Torrent *> allTorrents = BitTorrent::Session::instance()->torrents();
1137 const bool hasActiveTorrents = std::any_of(allTorrents.cbegin(), allTorrents.cend(), [](BitTorrent::Torrent *torrent)
1139 return torrent->isActive();
1141 if (pref->confirmOnExit() && hasActiveTorrents)
1143 if (e->spontaneous() || m_forceExit)
1145 if (!isVisible())
1146 show();
1147 QMessageBox confirmBox(QMessageBox::Question, tr("Exiting qBittorrent"),
1148 // Split it because the last sentence is used in the WebUI
1149 tr("Some files are currently transferring.") + u'\n' + tr("Are you sure you want to quit qBittorrent?"),
1150 QMessageBox::NoButton, this);
1151 QPushButton *noBtn = confirmBox.addButton(tr("&No"), QMessageBox::NoRole);
1152 confirmBox.addButton(tr("&Yes"), QMessageBox::YesRole);
1153 QPushButton *alwaysBtn = confirmBox.addButton(tr("&Always Yes"), QMessageBox::YesRole);
1154 confirmBox.setDefaultButton(noBtn);
1155 confirmBox.exec();
1156 if (!confirmBox.clickedButton() || (confirmBox.clickedButton() == noBtn))
1158 // Cancel exit
1159 e->ignore();
1160 m_forceExit = false;
1161 return;
1163 if (confirmBox.clickedButton() == alwaysBtn)
1164 // Remember choice
1165 Preferences::instance()->setConfirmOnExit(false);
1169 // Accept exit
1170 e->accept();
1171 qApp->exit();
1174 // Display window to create a torrent
1175 void MainWindow::on_actionCreateTorrent_triggered()
1177 createTorrentTriggered({});
1180 void MainWindow::createTorrentTriggered(const Path &path)
1182 if (m_createTorrentDlg)
1184 m_createTorrentDlg->updateInputPath(path);
1185 m_createTorrentDlg->activateWindow();
1187 else
1189 m_createTorrentDlg = new TorrentCreatorDialog(this, path);
1190 m_createTorrentDlg->setAttribute(Qt::WA_DeleteOnClose);
1191 m_createTorrentDlg->show();
1195 bool MainWindow::event(QEvent *e)
1197 #ifndef Q_OS_MACOS
1198 switch (e->type())
1200 case QEvent::WindowStateChange:
1201 qDebug("Window change event");
1202 // Now check to see if the window is minimised
1203 if (isMinimized())
1205 qDebug("minimisation");
1206 Preferences *const pref = Preferences::instance();
1207 if (app()->desktopIntegration()->isActive() && pref->minimizeToTray())
1209 qDebug() << "Has active window:" << (qApp->activeWindow() != nullptr);
1210 // Check if there is a modal window
1211 const QWidgetList allWidgets = QApplication::allWidgets();
1212 const bool hasModalWindow = std::any_of(allWidgets.cbegin(), allWidgets.cend()
1213 , [](const QWidget *widget) { return widget->isModal(); });
1214 // Iconify if there is no modal window
1215 if (!hasModalWindow)
1217 qDebug("Minimize to Tray enabled, hiding!");
1218 e->ignore();
1219 QMetaObject::invokeMethod(this, &QWidget::hide, Qt::QueuedConnection);
1220 if (!pref->minimizeToTrayNotified())
1222 app()->desktopIntegration()->showNotification(tr("qBittorrent is minimized to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
1223 pref->setMinimizeToTrayNotified(true);
1225 return true;
1229 break;
1230 case QEvent::ToolBarChange:
1232 qDebug("MAC: Received a toolbar change event!");
1233 const bool ret = QMainWindow::event(e);
1235 qDebug("MAC: new toolbar visibility is %d", !m_ui->actionTopToolBar->isChecked());
1236 m_ui->actionTopToolBar->toggle();
1237 Preferences::instance()->setToolbarDisplayed(m_ui->actionTopToolBar->isChecked());
1238 return ret;
1240 default:
1241 break;
1243 #endif // Q_OS_MACOS
1245 return QMainWindow::event(e);
1248 // action executed when a file is dropped
1249 void MainWindow::dropEvent(QDropEvent *event)
1251 event->acceptProposedAction();
1253 // remove scheme
1254 QStringList files;
1255 if (event->mimeData()->hasUrls())
1257 for (const QUrl &url : asConst(event->mimeData()->urls()))
1259 if (url.isEmpty())
1260 continue;
1262 files << ((url.scheme().compare(u"file", Qt::CaseInsensitive) == 0)
1263 ? url.toLocalFile()
1264 : url.toString());
1267 else
1269 files = event->mimeData()->text().split(u'\n');
1272 // differentiate ".torrent" files/links & magnet links from others
1273 QStringList torrentFiles, otherFiles;
1274 for (const QString &file : asConst(files))
1276 if (isTorrentLink(file))
1277 torrentFiles << file;
1278 else
1279 otherFiles << file;
1282 // Download torrents
1283 for (const QString &file : asConst(torrentFiles))
1284 app()->addTorrentManager()->addTorrent(file);
1285 if (!torrentFiles.isEmpty()) return;
1287 // Create torrent
1288 for (const QString &file : asConst(otherFiles))
1290 createTorrentTriggered(Path(file));
1292 // currently only handle the first entry
1293 // this is a stub that can be expanded later to create many torrents at once
1294 break;
1298 // Decode if we accept drag 'n drop or not
1299 void MainWindow::dragEnterEvent(QDragEnterEvent *event)
1301 for (const QString &mime : asConst(event->mimeData()->formats()))
1302 qDebug("mimeData: %s", mime.toLocal8Bit().data());
1304 if (event->mimeData()->hasFormat(u"text/plain"_s) || event->mimeData()->hasFormat(u"text/uri-list"_s))
1305 event->acceptProposedAction();
1308 /*****************************************************
1310 * Torrent *
1312 *****************************************************/
1314 // Display a dialog to allow user to add
1315 // torrents to download list
1316 void MainWindow::on_actionOpen_triggered()
1318 Preferences *const pref = Preferences::instance();
1319 // Open File Open Dialog
1320 // Note: it is possible to select more than one file
1321 const QStringList pathsList = QFileDialog::getOpenFileNames(this, tr("Open Torrent Files")
1322 , pref->getMainLastDir().data(), tr("Torrent Files") + u" (*" + TORRENT_FILE_EXTENSION + u')');
1324 if (pathsList.isEmpty())
1325 return;
1327 for (const QString &file : pathsList)
1328 app()->addTorrentManager()->addTorrent(file);
1330 // Save last dir to remember it
1331 const Path topDir {pathsList.at(0)};
1332 const Path parentDir = topDir.parentPath();
1333 pref->setMainLastDir(parentDir.isEmpty() ? topDir : parentDir);
1336 void MainWindow::activate()
1338 if (!m_uiLocked || unlockUI())
1340 show();
1341 activateWindow();
1342 raise();
1346 void MainWindow::optionsSaved()
1348 LogMsg(tr("Options saved."));
1349 loadPreferences();
1352 void MainWindow::showStatusBar(bool show)
1354 if (!show)
1356 // Remove status bar
1357 setStatusBar(nullptr);
1359 else if (!m_statusBar)
1361 // Create status bar
1362 m_statusBar = new StatusBar;
1363 connect(m_statusBar.data(), &StatusBar::connectionButtonClicked, this, &MainWindow::showConnectionSettings);
1364 connect(m_statusBar.data(), &StatusBar::alternativeSpeedsButtonClicked, this, &MainWindow::toggleAlternativeSpeeds);
1365 setStatusBar(m_statusBar);
1369 void MainWindow::showFiltersSidebar(const bool show)
1371 if (show && !m_transferListFiltersWidget)
1373 m_transferListFiltersWidget = new TransferListFiltersWidget(m_splitter, m_transferListWidget, isDownloadTrackerFavicon());
1374 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersAdded, m_transferListFiltersWidget, &TransferListFiltersWidget::addTrackers);
1375 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersRemoved, m_transferListFiltersWidget, &TransferListFiltersWidget::removeTrackers);
1376 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersChanged, m_transferListFiltersWidget, &TransferListFiltersWidget::refreshTrackers);
1377 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerEntriesUpdated, m_transferListFiltersWidget, &TransferListFiltersWidget::trackerEntriesUpdated);
1379 m_splitter->insertWidget(0, m_transferListFiltersWidget);
1380 m_splitter->setCollapsible(0, true);
1381 // From https://doc.qt.io/qt-5/qsplitter.html#setSizes:
1382 // Instead, any additional/missing space is distributed amongst the widgets
1383 // according to the relative weight of the sizes.
1384 m_splitter->setStretchFactor(0, 0);
1385 m_splitter->setStretchFactor(1, 1);
1386 m_splitter->setSizes({Preferences::instance()->getFiltersSidebarWidth()});
1388 else if (!show && m_transferListFiltersWidget)
1390 saveSplitterSettings();
1391 delete m_transferListFiltersWidget;
1392 m_transferListFiltersWidget = nullptr;
1396 void MainWindow::loadPreferences()
1398 const Preferences *pref = Preferences::instance();
1400 // General
1401 if (pref->isToolbarDisplayed())
1403 m_ui->toolBar->setVisible(true);
1405 else
1407 // Clear search filter before hiding the top toolbar
1408 m_columnFilterEdit->clear();
1409 m_ui->toolBar->setVisible(false);
1412 showStatusBar(pref->isStatusbarDisplayed());
1414 m_transferListWidget->setAlternatingRowColors(pref->useAlternatingRowColors());
1415 m_propertiesWidget->getFilesList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1416 m_propertiesWidget->getTrackerList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1417 m_propertiesWidget->getPeerList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1419 // Queueing System
1420 if (BitTorrent::Session::instance()->isQueueingSystemEnabled())
1422 if (!m_ui->actionDecreaseQueuePos->isVisible())
1424 m_transferListWidget->hideQueuePosColumn(false);
1425 m_ui->actionDecreaseQueuePos->setVisible(true);
1426 m_ui->actionIncreaseQueuePos->setVisible(true);
1427 m_ui->actionTopQueuePos->setVisible(true);
1428 m_ui->actionBottomQueuePos->setVisible(true);
1429 #ifndef Q_OS_MACOS
1430 m_queueSeparator->setVisible(true);
1431 #endif
1432 m_queueSeparatorMenu->setVisible(true);
1435 else
1437 if (m_ui->actionDecreaseQueuePos->isVisible())
1439 m_transferListWidget->hideQueuePosColumn(true);
1440 m_ui->actionDecreaseQueuePos->setVisible(false);
1441 m_ui->actionIncreaseQueuePos->setVisible(false);
1442 m_ui->actionTopQueuePos->setVisible(false);
1443 m_ui->actionBottomQueuePos->setVisible(false);
1444 #ifndef Q_OS_MACOS
1445 m_queueSeparator->setVisible(false);
1446 #endif
1447 m_queueSeparatorMenu->setVisible(false);
1451 // Torrent properties
1452 m_propertiesWidget->reloadPreferences();
1454 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1455 if (pref->isUpdateCheckEnabled())
1457 if (!m_programUpdateTimer)
1459 m_programUpdateTimer = new QTimer(this);
1460 m_programUpdateTimer->setInterval(24h);
1461 m_programUpdateTimer->setSingleShot(true);
1462 connect(m_programUpdateTimer, &QTimer::timeout, this, [this]() { checkProgramUpdate(false); });
1463 m_programUpdateTimer->start();
1466 else
1468 delete m_programUpdateTimer;
1469 m_programUpdateTimer = nullptr;
1471 #endif
1473 qDebug("GUI settings loaded");
1476 void MainWindow::reloadSessionStats()
1478 const BitTorrent::SessionStatus &status = BitTorrent::Session::instance()->status();
1480 // update global information
1481 #ifdef Q_OS_MACOS
1482 m_badger->updateSpeed(status.payloadDownloadRate, status.payloadUploadRate);
1483 #else
1484 const auto toolTip = u"%1\n%2"_s.arg(
1485 tr("DL speed: %1", "e.g: Download speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true))
1486 , tr("UP speed: %1", "e.g: Upload speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadUploadRate, true)));
1487 app()->desktopIntegration()->setToolTip(toolTip); // tray icon
1488 #endif // Q_OS_MACOS
1490 if (m_displaySpeedInTitle)
1492 setWindowTitle(tr("[D: %1, U: %2] qBittorrent %3", "D = Download; U = Upload; %3 is qBittorrent version")
1493 .arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true)
1494 , Utils::Misc::friendlyUnit(status.payloadUploadRate, true)
1495 , QStringLiteral(QBT_VERSION)));
1499 void MainWindow::reloadTorrentStats(const QVector<BitTorrent::Torrent *> &torrents)
1501 if (currentTabWidget() == m_transferListWidget)
1503 if (torrents.contains(m_propertiesWidget->getCurrentTorrent()))
1504 m_propertiesWidget->loadDynamicData();
1508 /*****************************************************
1510 * Utils *
1512 *****************************************************/
1514 void MainWindow::downloadFromURLList(const QStringList &urlList)
1516 for (const QString &url : urlList)
1517 app()->addTorrentManager()->addTorrent(url);
1520 /*****************************************************
1522 * Options *
1524 *****************************************************/
1526 QMenu *MainWindow::createDesktopIntegrationMenu()
1528 auto *menu = new QMenu;
1530 #ifndef Q_OS_MACOS
1531 connect(menu, &QMenu::aboutToShow, this, [this]()
1533 m_ui->actionToggleVisibility->setText(isVisible() ? tr("Hide") : tr("Show"));
1536 menu->addAction(m_ui->actionToggleVisibility);
1537 menu->addSeparator();
1538 #endif
1540 menu->addAction(m_ui->actionOpen);
1541 menu->addAction(m_ui->actionDownloadFromURL);
1542 menu->addSeparator();
1544 menu->addAction(m_ui->actionUseAlternativeSpeedLimits);
1545 menu->addAction(m_ui->actionSetGlobalSpeedLimits);
1546 menu->addSeparator();
1548 menu->addAction(m_ui->actionStartAll);
1549 menu->addAction(m_ui->actionPauseAll);
1551 #ifndef Q_OS_MACOS
1552 menu->addSeparator();
1553 menu->addAction(m_ui->actionExit);
1554 #endif
1556 if (m_uiLocked)
1557 menu->setEnabled(false);
1559 return menu;
1562 void MainWindow::updateAltSpeedsBtn(const bool alternative)
1564 m_ui->actionUseAlternativeSpeedLimits->setChecked(alternative);
1567 PropertiesWidget *MainWindow::propertiesWidget() const
1569 return m_propertiesWidget;
1572 // Display Program Options
1573 void MainWindow::on_actionOptions_triggered()
1575 if (m_options)
1577 m_options->activateWindow();
1579 else
1581 m_options = new OptionsDialog(app(), this);
1582 m_options->setAttribute(Qt::WA_DeleteOnClose);
1583 m_options->open();
1587 void MainWindow::on_actionTopToolBar_triggered()
1589 const bool isVisible = static_cast<QAction *>(sender())->isChecked();
1590 m_ui->toolBar->setVisible(isVisible);
1591 Preferences::instance()->setToolbarDisplayed(isVisible);
1594 void MainWindow::on_actionShowStatusbar_triggered()
1596 const bool isVisible = static_cast<QAction *>(sender())->isChecked();
1597 Preferences::instance()->setStatusbarDisplayed(isVisible);
1598 showStatusBar(isVisible);
1601 void MainWindow::on_actionShowFiltersSidebar_triggered(const bool checked)
1603 Preferences *const pref = Preferences::instance();
1604 pref->setFiltersSidebarVisible(checked);
1605 showFiltersSidebar(checked);
1608 void MainWindow::on_actionSpeedInTitleBar_triggered()
1610 m_displaySpeedInTitle = static_cast<QAction *>(sender())->isChecked();
1611 Preferences::instance()->showSpeedInTitleBar(m_displaySpeedInTitle);
1612 if (m_displaySpeedInTitle)
1613 reloadSessionStats();
1614 else
1615 setWindowTitle(QStringLiteral("qBittorrent " QBT_VERSION));
1618 void MainWindow::on_actionRSSReader_triggered()
1620 Preferences::instance()->setRSSWidgetVisible(m_ui->actionRSSReader->isChecked());
1621 displayRSSTab(m_ui->actionRSSReader->isChecked());
1624 void MainWindow::on_actionSearchWidget_triggered()
1626 if (m_ui->actionSearchWidget->isChecked())
1628 const Utils::ForeignApps::PythonInfo pyInfo = Utils::ForeignApps::pythonInfo();
1630 // Not found
1631 if (!pyInfo.isValid())
1633 m_ui->actionSearchWidget->setChecked(false);
1634 Preferences::instance()->setSearchEnabled(false);
1636 #ifdef Q_OS_WIN
1637 const QMessageBox::StandardButton buttonPressed = QMessageBox::question(this, tr("Missing Python Runtime")
1638 , tr("Python is required to use the search engine but it does not seem to be installed.\nDo you want to install it now?")
1639 , (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
1640 if (buttonPressed == QMessageBox::Yes)
1641 installPython();
1642 #else
1643 QMessageBox::information(this, tr("Missing Python Runtime")
1644 , tr("Python is required to use the search engine but it does not seem to be installed."));
1645 #endif
1646 return;
1649 // Check version requirement
1650 if (!pyInfo.isSupportedVersion())
1652 m_ui->actionSearchWidget->setChecked(false);
1653 Preferences::instance()->setSearchEnabled(false);
1655 #ifdef Q_OS_WIN
1656 const QMessageBox::StandardButton buttonPressed = QMessageBox::question(this, tr("Old Python Runtime")
1657 , tr("Your Python version (%1) is outdated. Minimum requirement: %2.\nDo you want to install a newer version now?")
1658 .arg(pyInfo.version.toString(), u"3.7.0")
1659 , (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
1660 if (buttonPressed == QMessageBox::Yes)
1661 installPython();
1662 #else
1663 QMessageBox::information(this, tr("Old Python Runtime")
1664 , tr("Your Python version (%1) is outdated. Please upgrade to latest version for search engines to work.\nMinimum requirement: %2.")
1665 .arg(pyInfo.version.toString(), u"3.7.0"));
1666 #endif
1667 return;
1670 m_ui->actionSearchWidget->setChecked(true);
1671 Preferences::instance()->setSearchEnabled(true);
1674 displaySearchTab(m_ui->actionSearchWidget->isChecked());
1677 /*****************************************************
1679 * HTTP Downloader *
1681 *****************************************************/
1683 // Display an input dialog to prompt user for
1684 // an url
1685 void MainWindow::on_actionDownloadFromURL_triggered()
1687 if (!m_downloadFromURLDialog)
1689 m_downloadFromURLDialog = new DownloadFromURLDialog(this);
1690 m_downloadFromURLDialog->setAttribute(Qt::WA_DeleteOnClose);
1691 connect(m_downloadFromURLDialog.data(), &DownloadFromURLDialog::urlsReadyToBeDownloaded, this, &MainWindow::downloadFromURLList);
1692 m_downloadFromURLDialog->open();
1696 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1697 void MainWindow::handleUpdateCheckFinished(ProgramUpdater *updater, const bool invokedByUser)
1699 m_ui->actionCheckForUpdates->setEnabled(true);
1700 m_ui->actionCheckForUpdates->setText(tr("&Check for Updates"));
1701 m_ui->actionCheckForUpdates->setToolTip(tr("Check for program updates"));
1703 const auto cleanup = [this, updater]()
1705 if (m_programUpdateTimer)
1706 m_programUpdateTimer->start();
1707 updater->deleteLater();
1710 const QString newVersion = updater->getNewVersion();
1711 if (!newVersion.isEmpty())
1713 const QString msg {tr("A new version is available.") + u"<br/>"
1714 + tr("Do you want to download %1?").arg(newVersion) + u"<br/><br/>"
1715 + u"<a href=\"https://www.qbittorrent.org/news.php\">%1</a>"_s.arg(tr("Open changelog..."))};
1716 auto *msgBox = new QMessageBox {QMessageBox::Question, tr("qBittorrent Update Available"), msg
1717 , (QMessageBox::Yes | QMessageBox::No), this};
1718 msgBox->setAttribute(Qt::WA_DeleteOnClose);
1719 msgBox->setAttribute(Qt::WA_ShowWithoutActivating);
1720 msgBox->setDefaultButton(QMessageBox::Yes);
1721 msgBox->setWindowModality(Qt::NonModal);
1722 connect(msgBox, &QMessageBox::buttonClicked, this, [msgBox, updater](QAbstractButton *button)
1724 if (msgBox->buttonRole(button) == QMessageBox::YesRole)
1726 updater->updateProgram();
1729 connect(msgBox, &QDialog::finished, this, cleanup);
1730 msgBox->show();
1732 else
1734 if (invokedByUser)
1736 auto *msgBox = new QMessageBox {QMessageBox::Information, u"qBittorrent"_s
1737 , tr("No updates available.\nYou are already using the latest version.")
1738 , QMessageBox::Ok, this};
1739 msgBox->setAttribute(Qt::WA_DeleteOnClose);
1740 msgBox->setWindowModality(Qt::NonModal);
1741 connect(msgBox, &QDialog::finished, this, cleanup);
1742 msgBox->show();
1744 else
1746 cleanup();
1750 #endif
1752 void MainWindow::toggleAlternativeSpeeds()
1754 BitTorrent::Session *const session = BitTorrent::Session::instance();
1755 session->setAltGlobalSpeedLimitEnabled(!session->isAltGlobalSpeedLimitEnabled());
1758 void MainWindow::on_actionDonateMoney_triggered()
1760 QDesktopServices::openUrl(QUrl(u"https://www.qbittorrent.org/donate"_s));
1763 void MainWindow::showConnectionSettings()
1765 on_actionOptions_triggered();
1766 m_options->showConnectionTab();
1769 void MainWindow::minimizeWindow()
1771 setWindowState(windowState() | Qt::WindowMinimized);
1774 void MainWindow::on_actionExecutionLogs_triggered(bool checked)
1776 if (checked)
1778 Q_ASSERT(!m_executionLog);
1779 m_executionLog = new ExecutionLogWidget(executionLogMsgTypes(), m_tabs);
1780 #ifdef Q_OS_MACOS
1781 m_tabs->addTab(m_executionLog, tr("Execution Log"));
1782 #else
1783 const int indexTab = m_tabs->addTab(m_executionLog, tr("Execution Log"));
1784 m_tabs->setTabIcon(indexTab, UIThemeManager::instance()->getIcon(u"help-contents"_s));
1785 #endif
1787 else
1789 delete m_executionLog;
1792 m_ui->actionNormalMessages->setEnabled(checked);
1793 m_ui->actionInformationMessages->setEnabled(checked);
1794 m_ui->actionWarningMessages->setEnabled(checked);
1795 m_ui->actionCriticalMessages->setEnabled(checked);
1796 setExecutionLogEnabled(checked);
1799 void MainWindow::on_actionNormalMessages_triggered(const bool checked)
1801 if (!m_executionLog)
1802 return;
1804 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::NORMAL, checked);
1805 setExecutionLogMsgTypes(flags);
1808 void MainWindow::on_actionInformationMessages_triggered(const bool checked)
1810 if (!m_executionLog)
1811 return;
1813 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::INFO, checked);
1814 setExecutionLogMsgTypes(flags);
1817 void MainWindow::on_actionWarningMessages_triggered(const bool checked)
1819 if (!m_executionLog)
1820 return;
1822 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::WARNING, checked);
1823 setExecutionLogMsgTypes(flags);
1826 void MainWindow::on_actionCriticalMessages_triggered(const bool checked)
1828 if (!m_executionLog)
1829 return;
1831 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::CRITICAL, checked);
1832 setExecutionLogMsgTypes(flags);
1835 void MainWindow::on_actionAutoExit_toggled(bool enabled)
1837 qDebug() << Q_FUNC_INFO << enabled;
1838 Preferences::instance()->setShutdownqBTWhenDownloadsComplete(enabled);
1841 void MainWindow::on_actionAutoSuspend_toggled(bool enabled)
1843 qDebug() << Q_FUNC_INFO << enabled;
1844 Preferences::instance()->setSuspendWhenDownloadsComplete(enabled);
1847 void MainWindow::on_actionAutoHibernate_toggled(bool enabled)
1849 qDebug() << Q_FUNC_INFO << enabled;
1850 Preferences::instance()->setHibernateWhenDownloadsComplete(enabled);
1853 void MainWindow::on_actionAutoShutdown_toggled(bool enabled)
1855 qDebug() << Q_FUNC_INFO << enabled;
1856 Preferences::instance()->setShutdownWhenDownloadsComplete(enabled);
1859 void MainWindow::updatePowerManagementState() const
1861 const auto *pref = Preferences::instance();
1862 const bool preventFromSuspendWhenDownloading = pref->preventFromSuspendWhenDownloading();
1863 const bool preventFromSuspendWhenSeeding = pref->preventFromSuspendWhenSeeding();
1865 const QVector<BitTorrent::Torrent *> allTorrents = BitTorrent::Session::instance()->torrents();
1866 const bool inhibitSuspend = std::any_of(allTorrents.cbegin(), allTorrents.cend(), [&](const BitTorrent::Torrent *torrent)
1868 if (preventFromSuspendWhenDownloading && (!torrent->isFinished() && !torrent->isPaused() && !torrent->isErrored() && torrent->hasMetadata()))
1869 return true;
1871 if (preventFromSuspendWhenSeeding && (torrent->isFinished() && !torrent->isPaused()))
1872 return true;
1874 return torrent->isMoving();
1876 m_pwr->setActivityState(inhibitSuspend);
1878 m_preventTimer->start(PREVENT_SUSPEND_INTERVAL);
1881 void MainWindow::applyTransferListFilter()
1883 m_transferListWidget->applyFilter(m_columnFilterEdit->text(), m_columnFilterComboBox->currentData().value<TransferListModel::Column>());
1886 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1887 void MainWindow::checkProgramUpdate(const bool invokedByUser)
1889 if (m_programUpdateTimer)
1890 m_programUpdateTimer->stop();
1892 m_ui->actionCheckForUpdates->setEnabled(false);
1893 m_ui->actionCheckForUpdates->setText(tr("Checking for Updates..."));
1894 m_ui->actionCheckForUpdates->setToolTip(tr("Already checking for program updates in the background"));
1896 auto *updater = new ProgramUpdater(this);
1897 connect(updater, &ProgramUpdater::updateCheckFinished
1898 , this, [this, invokedByUser, updater]()
1900 handleUpdateCheckFinished(updater, invokedByUser);
1902 updater->checkForUpdates();
1904 #endif
1906 #ifdef Q_OS_WIN
1907 void MainWindow::installPython()
1909 setCursor(QCursor(Qt::WaitCursor));
1910 // Download python
1911 const auto installerURL = u"https://www.python.org/ftp/python/3.10.11/python-3.10.11-amd64.exe"_s;
1912 Net::DownloadManager::instance()->download(
1913 Net::DownloadRequest(installerURL).saveToFile(true)
1914 , Preferences::instance()->useProxyForGeneralPurposes()
1915 , this, &MainWindow::pythonDownloadFinished);
1918 void MainWindow::pythonDownloadFinished(const Net::DownloadResult &result)
1920 if (result.status != Net::DownloadStatus::Success)
1922 setCursor(QCursor(Qt::ArrowCursor));
1923 QMessageBox::warning(
1924 this, tr("Download error")
1925 , tr("Python setup could not be downloaded, reason: %1.\nPlease install it manually.")
1926 .arg(result.errorString));
1927 return;
1930 setCursor(QCursor(Qt::ArrowCursor));
1931 QProcess installer;
1932 qDebug("Launching Python installer in passive mode...");
1934 const Path exePath = result.filePath + u".exe";
1935 Utils::Fs::renameFile(result.filePath, exePath);
1936 installer.start(exePath.toString(), {u"/passive"_s});
1938 // Wait for setup to complete
1939 installer.waitForFinished(10 * 60 * 1000);
1941 qDebug("Installer stdout: %s", installer.readAllStandardOutput().data());
1942 qDebug("Installer stderr: %s", installer.readAllStandardError().data());
1943 qDebug("Setup should be complete!");
1945 // Delete temp file
1946 Utils::Fs::removeFile(exePath);
1948 // Reload search engine
1949 if (Utils::ForeignApps::pythonInfo().isSupportedVersion())
1951 m_ui->actionSearchWidget->setChecked(true);
1952 displaySearchTab(true);
1955 #endif // Q_OS_WIN