Merge pull request #18104 from sledgehammer999/remove_dead_code
[qBittorrent.git] / src / gui / mainwindow.cpp
blob93eff1ea94dda0a626b5a463e6ac345030567e43
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 * In addition, as a special exception, the copyright holders give permission to
20 * link this program with the OpenSSL project's "OpenSSL" library (or with
21 * modified versions of it that use the same license as the "OpenSSL" library),
22 * and distribute the linked executables. You must obey the GNU General Public
23 * License in all respects for all of the code used other than "OpenSSL". If you
24 * modify file(s), you may extend this exception to your version of the file(s),
25 * but you are not obligated to do so. If you do not wish to do so, delete this
26 * exception statement from your version.
29 #include "mainwindow.h"
31 #include <algorithm>
32 #include <chrono>
34 #include <QActionGroup>
35 #include <QClipboard>
36 #include <QCloseEvent>
37 #include <QDebug>
38 #include <QDesktopServices>
39 #include <QFileDialog>
40 #include <QFileSystemWatcher>
41 #include <QKeyEvent>
42 #include <QMessageBox>
43 #include <QMetaObject>
44 #include <QMimeData>
45 #include <QProcess>
46 #include <QPushButton>
47 #include <QShortcut>
48 #include <QSplitter>
49 #include <QStatusBar>
50 #include <QtGlobal>
51 #include <QTimer>
53 #include "base/bittorrent/session.h"
54 #include "base/bittorrent/sessionstatus.h"
55 #include "base/global.h"
56 #include "base/net/downloadmanager.h"
57 #include "base/path.h"
58 #include "base/preferences.h"
59 #include "base/rss/rss_folder.h"
60 #include "base/rss/rss_session.h"
61 #include "base/utils/foreignapps.h"
62 #include "base/utils/fs.h"
63 #include "base/utils/misc.h"
64 #include "base/utils/password.h"
65 #include "base/version.h"
66 #include "aboutdialog.h"
67 #include "addnewtorrentdialog.h"
68 #include "autoexpandabledialog.h"
69 #include "cookiesdialog.h"
70 #include "desktopintegration.h"
71 #include "downloadfromurldialog.h"
72 #include "executionlogwidget.h"
73 #include "hidabletabwidget.h"
74 #include "lineedit.h"
75 #include "optionsdialog.h"
76 #include "powermanagement/powermanagement.h"
77 #include "properties/peerlistwidget.h"
78 #include "properties/propertieswidget.h"
79 #include "properties/trackerlistwidget.h"
80 #include "rss/rsswidget.h"
81 #include "search/searchwidget.h"
82 #include "speedlimitdialog.h"
83 #include "statsdialog.h"
84 #include "statusbar.h"
85 #include "torrentcreatordialog.h"
86 #include "transferlistfilterswidget.h"
87 #include "transferlistmodel.h"
88 #include "transferlistwidget.h"
89 #include "ui_mainwindow.h"
90 #include "uithememanager.h"
91 #include "utils.h"
93 #ifdef Q_OS_MACOS
94 #include "macutilities.h"
95 #endif
96 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
97 #include "programupdater.h"
98 #endif
100 using namespace std::chrono_literals;
102 namespace
104 #define SETTINGS_KEY(name) u"GUI/" name
105 #define EXECUTIONLOG_SETTINGS_KEY(name) (SETTINGS_KEY(u"Log/"_qs) name)
107 const std::chrono::seconds PREVENT_SUSPEND_INTERVAL {60};
109 bool isTorrentLink(const QString &str)
111 return str.startsWith(u"magnet:", Qt::CaseInsensitive)
112 || str.endsWith(TORRENT_FILE_EXTENSION, Qt::CaseInsensitive)
113 || (!str.startsWith(u"file:", Qt::CaseInsensitive)
114 && Net::DownloadManager::hasSupportedScheme(str));
118 MainWindow::MainWindow(IGUIApplication *app, const State initialState)
119 : GUIApplicationComponent(app)
120 , m_ui(new Ui::MainWindow)
121 , m_storeExecutionLogEnabled(EXECUTIONLOG_SETTINGS_KEY(u"Enabled"_qs))
122 , m_storeDownloadTrackerFavicon(SETTINGS_KEY(u"DownloadTrackerFavicon"_qs))
123 , m_storeExecutionLogTypes(EXECUTIONLOG_SETTINGS_KEY(u"Types"_qs), Log::MsgType::ALL)
125 m_ui->setupUi(this);
127 Preferences *const pref = Preferences::instance();
128 m_uiLocked = pref->isUILocked();
129 setWindowTitle(QStringLiteral("qBittorrent " QBT_VERSION));
130 m_displaySpeedInTitle = pref->speedInTitleBar();
131 // Setting icons
132 #ifndef Q_OS_MACOS
133 const QIcon appLogo(UIThemeManager::instance()->getIcon(u"qbittorrent"_qs, u"qbittorrent-tray"_qs));
134 setWindowIcon(appLogo);
135 #endif // Q_OS_MACOS
137 #if (defined(Q_OS_UNIX))
138 m_ui->actionOptions->setText(tr("Preferences"));
139 #endif
141 addToolbarContextMenu();
143 m_ui->actionOpen->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_qs));
144 m_ui->actionDownloadFromURL->setIcon(UIThemeManager::instance()->getIcon(u"insert-link"_qs));
145 m_ui->actionSetGlobalSpeedLimits->setIcon(UIThemeManager::instance()->getIcon(u"speedometer"_qs));
146 m_ui->actionCreateTorrent->setIcon(UIThemeManager::instance()->getIcon(u"torrent-creator"_qs));
147 m_ui->actionAbout->setIcon(UIThemeManager::instance()->getIcon(u"help-about"_qs));
148 m_ui->actionStatistics->setIcon(UIThemeManager::instance()->getIcon(u"view-statistics"_qs));
149 m_ui->actionTopQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-top"_qs));
150 m_ui->actionIncreaseQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-up"_qs));
151 m_ui->actionDecreaseQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-down"_qs));
152 m_ui->actionBottomQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-bottom"_qs));
153 m_ui->actionDelete->setIcon(UIThemeManager::instance()->getIcon(u"list-remove"_qs));
154 m_ui->actionDocumentation->setIcon(UIThemeManager::instance()->getIcon(u"help-contents"_qs));
155 m_ui->actionDonateMoney->setIcon(UIThemeManager::instance()->getIcon(u"wallet-open"_qs));
156 m_ui->actionExit->setIcon(UIThemeManager::instance()->getIcon(u"application-exit"_qs));
157 m_ui->actionLock->setIcon(UIThemeManager::instance()->getIcon(u"object-locked"_qs));
158 m_ui->actionOptions->setIcon(UIThemeManager::instance()->getIcon(u"configure"_qs));
159 m_ui->actionPause->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs));
160 m_ui->actionPauseAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs));
161 m_ui->actionStart->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_qs));
162 m_ui->actionStartAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_qs));
163 m_ui->menuAutoShutdownOnDownloadsCompletion->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_qs));
164 m_ui->actionManageCookies->setIcon(UIThemeManager::instance()->getIcon(u"browser-cookies"_qs));
165 m_ui->menuLog->setIcon(UIThemeManager::instance()->getIcon(u"help-contents"_qs));
166 m_ui->actionCheckForUpdates->setIcon(UIThemeManager::instance()->getIcon(u"view-refresh"_qs));
168 auto *lockMenu = new QMenu(this);
169 lockMenu->addAction(tr("&Set Password"), this, &MainWindow::defineUILockPassword);
170 lockMenu->addAction(tr("&Clear Password"), this, &MainWindow::clearUILockPassword);
171 m_ui->actionLock->setMenu(lockMenu);
173 // Creating Bittorrent session
174 updateAltSpeedsBtn(BitTorrent::Session::instance()->isAltGlobalSpeedLimitEnabled());
176 connect(BitTorrent::Session::instance(), &BitTorrent::Session::speedLimitModeChanged, this, &MainWindow::updateAltSpeedsBtn);
177 connect(BitTorrent::Session::instance(), &BitTorrent::Session::recursiveTorrentDownloadPossible, this, &MainWindow::askRecursiveTorrentDownloadConfirmation);
179 qDebug("create tabWidget");
180 m_tabs = new HidableTabWidget(this);
181 connect(m_tabs.data(), &QTabWidget::currentChanged, this, &MainWindow::tabChanged);
183 m_splitter = new QSplitter(Qt::Horizontal, this);
184 // vSplitter->setChildrenCollapsible(false);
186 auto *hSplitter = new QSplitter(Qt::Vertical, this);
187 hSplitter->setChildrenCollapsible(false);
188 hSplitter->setFrameShape(QFrame::NoFrame);
190 // Name filter
191 m_searchFilter = new LineEdit(this);
192 m_searchFilter->setPlaceholderText(tr("Filter torrent names..."));
193 m_searchFilter->setFixedWidth(200);
194 m_searchFilter->setContextMenuPolicy(Qt::CustomContextMenu);
195 connect(m_searchFilter, &QWidget::customContextMenuRequested, this, &MainWindow::showFilterContextMenu);
196 m_searchFilterAction = m_ui->toolBar->insertWidget(m_ui->actionLock, m_searchFilter);
198 QWidget *spacer = new QWidget(this);
199 spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
200 m_ui->toolBar->insertWidget(m_searchFilterAction, spacer);
202 // Transfer List tab
203 m_transferListWidget = new TransferListWidget(hSplitter, this);
204 // m_transferListWidget->setStyleSheet("QTreeView {border: none;}"); // borderless
205 m_propertiesWidget = new PropertiesWidget(hSplitter);
206 connect(m_transferListWidget, &TransferListWidget::currentTorrentChanged, m_propertiesWidget, &PropertiesWidget::loadTorrentInfos);
207 hSplitter->addWidget(m_transferListWidget);
208 hSplitter->addWidget(m_propertiesWidget);
209 m_splitter->addWidget(hSplitter);
210 m_splitter->setCollapsible(0, false);
211 m_tabs->addTab(m_splitter,
212 #ifndef Q_OS_MACOS
213 UIThemeManager::instance()->getIcon(u"folder-remote"_qs),
214 #endif
215 tr("Transfers"));
217 connect(m_searchFilter, &LineEdit::textChanged, m_transferListWidget, &TransferListWidget::applyNameFilter);
218 connect(hSplitter, &QSplitter::splitterMoved, this, &MainWindow::saveSettings);
219 connect(m_splitter, &QSplitter::splitterMoved, this, &MainWindow::saveSplitterSettings);
220 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersChanged, m_propertiesWidget, &PropertiesWidget::loadTrackers);
222 #ifdef Q_OS_MACOS
223 // Increase top spacing to avoid tab overlapping
224 m_ui->centralWidgetLayout->addSpacing(8);
225 #endif
227 m_ui->centralWidgetLayout->addWidget(m_tabs);
229 m_queueSeparator = m_ui->toolBar->insertSeparator(m_ui->actionTopQueuePos);
230 m_queueSeparatorMenu = m_ui->menuEdit->insertSeparator(m_ui->actionTopQueuePos);
232 #ifdef Q_OS_MACOS
233 for (QAction *action : asConst(m_ui->toolBar->actions()))
235 if (action->isSeparator())
237 QWidget *spacer = new QWidget(this);
238 spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
239 spacer->setMinimumWidth(16);
240 m_ui->toolBar->insertWidget(action, spacer);
241 m_ui->toolBar->removeAction(action);
245 QWidget *spacer = new QWidget(this);
246 spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
247 spacer->setMinimumWidth(8);
248 m_ui->toolBar->insertWidget(m_ui->actionDownloadFromURL, spacer);
251 QWidget *spacer = new QWidget(this);
252 spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
253 spacer->setMinimumWidth(8);
254 m_ui->toolBar->addWidget(spacer);
256 #endif // Q_OS_MACOS
258 // Transfer list slots
259 connect(m_ui->actionStart, &QAction::triggered, m_transferListWidget, &TransferListWidget::startSelectedTorrents);
260 connect(m_ui->actionStartAll, &QAction::triggered, m_transferListWidget, &TransferListWidget::resumeAllTorrents);
261 connect(m_ui->actionPause, &QAction::triggered, m_transferListWidget, &TransferListWidget::pauseSelectedTorrents);
262 connect(m_ui->actionPauseAll, &QAction::triggered, m_transferListWidget, &TransferListWidget::pauseAllTorrents);
263 connect(m_ui->actionDelete, &QAction::triggered, m_transferListWidget, &TransferListWidget::softDeleteSelectedTorrents);
264 connect(m_ui->actionTopQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::topQueuePosSelectedTorrents);
265 connect(m_ui->actionIncreaseQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::increaseQueuePosSelectedTorrents);
266 connect(m_ui->actionDecreaseQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::decreaseQueuePosSelectedTorrents);
267 connect(m_ui->actionBottomQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::bottomQueuePosSelectedTorrents);
268 #ifndef Q_OS_MACOS
269 connect(m_ui->actionToggleVisibility, &QAction::triggered, this, &MainWindow::toggleVisibility);
270 #endif
271 connect(m_ui->actionMinimize, &QAction::triggered, this, &MainWindow::minimizeWindow);
272 connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds);
274 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
275 connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, [this]() { checkProgramUpdate(true); });
277 // trigger an early check on startup
278 if (pref->isUpdateCheckEnabled())
279 checkProgramUpdate(false);
280 #else
281 m_ui->actionCheckForUpdates->setVisible(false);
282 #endif
284 // Certain menu items should reside at specific places on macOS.
285 // Qt partially does it on its own, but updates and different languages require tuning.
286 m_ui->actionExit->setMenuRole(QAction::QuitRole);
287 m_ui->actionAbout->setMenuRole(QAction::AboutRole);
288 m_ui->actionCheckForUpdates->setMenuRole(QAction::ApplicationSpecificRole);
289 m_ui->actionOptions->setMenuRole(QAction::PreferencesRole);
291 connect(m_ui->actionManageCookies, &QAction::triggered, this, &MainWindow::manageCookies);
293 m_pwr = new PowerManagement(this);
294 m_preventTimer = new QTimer(this);
295 connect(m_preventTimer, &QTimer::timeout, this, &MainWindow::updatePowerManagementState);
297 // Configure BT session according to options
298 loadPreferences();
300 connect(BitTorrent::Session::instance(), &BitTorrent::Session::statsUpdated, this, &MainWindow::reloadSessionStats);
301 connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsUpdated, this, &MainWindow::reloadTorrentStats);
303 // Accept drag 'n drops
304 setAcceptDrops(true);
305 createKeyboardShortcuts();
307 #ifdef Q_OS_MACOS
308 setUnifiedTitleAndToolBarOnMac(true);
309 #endif
311 // View settings
312 m_ui->actionTopToolBar->setChecked(pref->isToolbarDisplayed());
313 m_ui->actionShowStatusbar->setChecked(pref->isStatusbarDisplayed());
314 m_ui->actionSpeedInTitleBar->setChecked(pref->speedInTitleBar());
315 m_ui->actionRSSReader->setChecked(pref->isRSSWidgetEnabled());
316 m_ui->actionSearchWidget->setChecked(pref->isSearchEnabled());
317 m_ui->actionExecutionLogs->setChecked(isExecutionLogEnabled());
319 const Log::MsgTypes flags = executionLogMsgTypes();
320 m_ui->actionNormalMessages->setChecked(flags.testFlag(Log::NORMAL));
321 m_ui->actionInformationMessages->setChecked(flags.testFlag(Log::INFO));
322 m_ui->actionWarningMessages->setChecked(flags.testFlag(Log::WARNING));
323 m_ui->actionCriticalMessages->setChecked(flags.testFlag(Log::CRITICAL));
325 displayRSSTab(m_ui->actionRSSReader->isChecked());
326 on_actionExecutionLogs_triggered(m_ui->actionExecutionLogs->isChecked());
327 on_actionNormalMessages_triggered(m_ui->actionNormalMessages->isChecked());
328 on_actionInformationMessages_triggered(m_ui->actionInformationMessages->isChecked());
329 on_actionWarningMessages_triggered(m_ui->actionWarningMessages->isChecked());
330 on_actionCriticalMessages_triggered(m_ui->actionCriticalMessages->isChecked());
331 if (m_ui->actionSearchWidget->isChecked())
332 QMetaObject::invokeMethod(this, &MainWindow::on_actionSearchWidget_triggered, Qt::QueuedConnection);
333 // Auto shutdown actions
334 auto *autoShutdownGroup = new QActionGroup(this);
335 autoShutdownGroup->setExclusive(true);
336 autoShutdownGroup->addAction(m_ui->actionAutoShutdownDisabled);
337 autoShutdownGroup->addAction(m_ui->actionAutoExit);
338 autoShutdownGroup->addAction(m_ui->actionAutoShutdown);
339 autoShutdownGroup->addAction(m_ui->actionAutoSuspend);
340 autoShutdownGroup->addAction(m_ui->actionAutoHibernate);
341 #if (!defined(Q_OS_UNIX) || defined(Q_OS_MACOS)) || defined(QT_DBUS_LIB)
342 m_ui->actionAutoShutdown->setChecked(pref->shutdownWhenDownloadsComplete());
343 m_ui->actionAutoSuspend->setChecked(pref->suspendWhenDownloadsComplete());
344 m_ui->actionAutoHibernate->setChecked(pref->hibernateWhenDownloadsComplete());
345 #else
346 m_ui->actionAutoShutdown->setDisabled(true);
347 m_ui->actionAutoSuspend->setDisabled(true);
348 m_ui->actionAutoHibernate->setDisabled(true);
349 #endif
350 m_ui->actionAutoExit->setChecked(pref->shutdownqBTWhenDownloadsComplete());
352 if (!autoShutdownGroup->checkedAction())
353 m_ui->actionAutoShutdownDisabled->setChecked(true);
355 // Load Window state and sizes
356 loadSettings();
358 app->desktopIntegration()->setMenu(createDesktopIntegrationMenu());
359 #ifndef Q_OS_MACOS
360 m_ui->actionLock->setVisible(app->desktopIntegration()->isActive());
361 connect(app->desktopIntegration(), &DesktopIntegration::stateChanged, this, [this, app]()
363 m_ui->actionLock->setVisible(app->desktopIntegration()->isActive());
365 #endif
366 connect(app->desktopIntegration(), &DesktopIntegration::notificationClicked, this, &MainWindow::desktopNotificationClicked);
367 connect(app->desktopIntegration(), &DesktopIntegration::activationRequested, this, [this]()
369 #ifdef Q_OS_MACOS
370 if (!isVisible())
371 activate();
372 #else
373 toggleVisibility();
374 #endif
377 #ifdef Q_OS_MACOS
378 // Make sure the Window is visible if we don't have a tray icon
379 if (initialState == Minimized)
381 showMinimized();
383 else
385 show();
386 activateWindow();
387 raise();
389 #else
390 if (app->desktopIntegration()->isActive())
392 if ((initialState != Minimized) && !m_uiLocked)
394 show();
395 activateWindow();
396 raise();
398 else if (initialState == Minimized)
400 showMinimized();
401 if (pref->minimizeToTray())
403 hide();
404 if (!pref->minimizeToTrayNotified())
406 app->desktopIntegration()->showNotification(tr("qBittorrent is minimized to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
407 pref->setMinimizeToTrayNotified(true);
412 else
414 // Make sure the Window is visible if we don't have a tray icon
415 if (initialState == Minimized)
417 showMinimized();
419 else
421 show();
422 activateWindow();
423 raise();
426 #endif
428 m_propertiesWidget->readSettings();
430 const bool isFiltersSidebarVisible = pref->isFiltersSidebarVisible();
431 m_ui->actionShowFiltersSidebar->setChecked(isFiltersSidebarVisible);
432 if (isFiltersSidebarVisible)
434 showFiltersSidebar(true);
436 else
438 m_transferListWidget->applyStatusFilter(pref->getTransSelFilter());
439 m_transferListWidget->applyCategoryFilter(QString());
440 m_transferListWidget->applyTagFilter(QString());
441 m_transferListWidget->applyTrackerFilterAll();
444 // Start watching the executable for updates
445 m_executableWatcher = new QFileSystemWatcher(this);
446 connect(m_executableWatcher, &QFileSystemWatcher::fileChanged, this, &MainWindow::notifyOfUpdate);
447 m_executableWatcher->addPath(qApp->applicationFilePath());
449 m_transferListWidget->setFocus();
451 // Update the number of torrents (tab)
452 updateNbTorrents();
453 connect(m_transferListWidget->getSourceModel(), &QAbstractItemModel::rowsInserted, this, &MainWindow::updateNbTorrents);
454 connect(m_transferListWidget->getSourceModel(), &QAbstractItemModel::rowsRemoved, this, &MainWindow::updateNbTorrents);
456 connect(pref, &Preferences::changed, this, &MainWindow::optionsSaved);
458 qDebug("GUI Built");
461 MainWindow::~MainWindow()
463 app()->desktopIntegration()->setMenu(nullptr);
464 delete m_ui;
467 bool MainWindow::isExecutionLogEnabled() const
469 return m_storeExecutionLogEnabled;
472 void MainWindow::setExecutionLogEnabled(const bool value)
474 m_storeExecutionLogEnabled = value;
477 Log::MsgTypes MainWindow::executionLogMsgTypes() const
479 return m_storeExecutionLogTypes;
482 void MainWindow::setExecutionLogMsgTypes(const Log::MsgTypes value)
484 m_executionLog->setMessageTypes(value);
485 m_storeExecutionLogTypes = value;
488 bool MainWindow::isDownloadTrackerFavicon() const
490 return m_storeDownloadTrackerFavicon;
493 void MainWindow::setDownloadTrackerFavicon(const bool value)
495 if (m_transferListFiltersWidget)
496 m_transferListFiltersWidget->setDownloadTrackerFavicon(value);
497 m_storeDownloadTrackerFavicon = value;
500 void MainWindow::addToolbarContextMenu()
502 const Preferences *const pref = Preferences::instance();
503 m_toolbarMenu = new QMenu(this);
505 m_ui->toolBar->setContextMenuPolicy(Qt::CustomContextMenu);
506 connect(m_ui->toolBar, &QWidget::customContextMenuRequested, this, &MainWindow::toolbarMenuRequested);
508 QAction *iconsOnly = m_toolbarMenu->addAction(tr("Icons Only"), this, &MainWindow::toolbarIconsOnly);
509 QAction *textOnly = m_toolbarMenu->addAction(tr("Text Only"), this, &MainWindow::toolbarTextOnly);
510 QAction *textBesideIcons = m_toolbarMenu->addAction(tr("Text Alongside Icons"), this, &MainWindow::toolbarTextBeside);
511 QAction *textUnderIcons = m_toolbarMenu->addAction(tr("Text Under Icons"), this, &MainWindow::toolbarTextUnder);
512 QAction *followSystemStyle = m_toolbarMenu->addAction(tr("Follow System Style"), this, &MainWindow::toolbarFollowSystem);
514 auto *textPositionGroup = new QActionGroup(m_toolbarMenu);
515 textPositionGroup->addAction(iconsOnly);
516 iconsOnly->setCheckable(true);
517 textPositionGroup->addAction(textOnly);
518 textOnly->setCheckable(true);
519 textPositionGroup->addAction(textBesideIcons);
520 textBesideIcons->setCheckable(true);
521 textPositionGroup->addAction(textUnderIcons);
522 textUnderIcons->setCheckable(true);
523 textPositionGroup->addAction(followSystemStyle);
524 followSystemStyle->setCheckable(true);
526 const auto buttonStyle = static_cast<Qt::ToolButtonStyle>(pref->getToolbarTextPosition());
527 if ((buttonStyle >= Qt::ToolButtonIconOnly) && (buttonStyle <= Qt::ToolButtonFollowStyle))
528 m_ui->toolBar->setToolButtonStyle(buttonStyle);
529 switch (buttonStyle)
531 case Qt::ToolButtonIconOnly:
532 iconsOnly->setChecked(true);
533 break;
534 case Qt::ToolButtonTextOnly:
535 textOnly->setChecked(true);
536 break;
537 case Qt::ToolButtonTextBesideIcon:
538 textBesideIcons->setChecked(true);
539 break;
540 case Qt::ToolButtonTextUnderIcon:
541 textUnderIcons->setChecked(true);
542 break;
543 default:
544 followSystemStyle->setChecked(true);
548 void MainWindow::manageCookies()
550 auto *cookieDialog = new CookiesDialog(this);
551 cookieDialog->setAttribute(Qt::WA_DeleteOnClose);
552 cookieDialog->open();
555 void MainWindow::toolbarMenuRequested()
557 m_toolbarMenu->popup(QCursor::pos());
560 void MainWindow::toolbarIconsOnly()
562 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
563 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonIconOnly);
566 void MainWindow::toolbarTextOnly()
568 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextOnly);
569 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextOnly);
572 void MainWindow::toolbarTextBeside()
574 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
575 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextBesideIcon);
578 void MainWindow::toolbarTextUnder()
580 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
581 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextUnderIcon);
584 void MainWindow::toolbarFollowSystem()
586 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle);
587 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonFollowStyle);
590 bool MainWindow::defineUILockPassword()
592 bool ok = false;
593 const QString newPassword = AutoExpandableDialog::getText(this, tr("UI lock password")
594 , tr("Please type the UI lock password:"), QLineEdit::Password, {}, &ok);
595 if (!ok)
596 return false;
598 if (newPassword.size() < 3)
600 QMessageBox::warning(this, tr("Invalid password"), tr("The password must be at least 3 characters long"));
601 return false;
604 Preferences::instance()->setUILockPassword(Utils::Password::PBKDF2::generate(newPassword));
605 return true;
608 void MainWindow::clearUILockPassword()
610 const QMessageBox::StandardButton answer = QMessageBox::question(this, tr("Clear the password")
611 , tr("Are you sure you want to clear the password?"), (QMessageBox::Yes | QMessageBox::No), QMessageBox::No);
612 if (answer == QMessageBox::Yes)
613 Preferences::instance()->setUILockPassword({});
616 void MainWindow::on_actionLock_triggered()
618 Preferences *const pref = Preferences::instance();
620 // Check if there is a password
621 if (pref->getUILockPassword().isEmpty())
623 if (!defineUILockPassword())
624 return;
627 // Lock the interface
628 m_uiLocked = true;
629 pref->setUILocked(true);
630 app()->desktopIntegration()->menu()->setEnabled(false);
631 hide();
634 void MainWindow::handleRSSUnreadCountUpdated(int count)
636 m_tabs->setTabText(m_tabs->indexOf(m_rssWidget), tr("RSS (%1)").arg(count));
639 void MainWindow::displayRSSTab(bool enable)
641 if (enable)
643 // RSS tab
644 if (!m_rssWidget)
646 m_rssWidget = new RSSWidget(m_tabs);
647 connect(m_rssWidget.data(), &RSSWidget::unreadCountUpdated, this, &MainWindow::handleRSSUnreadCountUpdated);
648 #ifdef Q_OS_MACOS
649 m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
650 #else
651 const int indexTab = m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
652 m_tabs->setTabIcon(indexTab, UIThemeManager::instance()->getIcon(u"application-rss"_qs));
653 #endif
656 else
658 delete m_rssWidget;
662 void MainWindow::showFilterContextMenu()
664 const Preferences *pref = Preferences::instance();
666 QMenu *menu = m_searchFilter->createStandardContextMenu();
667 menu->setAttribute(Qt::WA_DeleteOnClose);
668 menu->addSeparator();
670 QAction *useRegexAct = menu->addAction(tr("Use regular expressions"));
671 useRegexAct->setCheckable(true);
672 useRegexAct->setChecked(pref->getRegexAsFilteringPatternForTransferList());
673 connect(useRegexAct, &QAction::toggled, pref, &Preferences::setRegexAsFilteringPatternForTransferList);
674 connect(useRegexAct, &QAction::toggled, this, [this]() { m_transferListWidget->applyNameFilter(m_searchFilter->text()); });
676 menu->popup(QCursor::pos());
679 void MainWindow::displaySearchTab(bool enable)
681 Preferences::instance()->setSearchEnabled(enable);
682 if (enable)
684 // RSS tab
685 if (!m_searchWidget)
687 m_searchWidget = new SearchWidget(app(), this);
688 m_tabs->insertTab(1, m_searchWidget,
689 #ifndef Q_OS_MACOS
690 UIThemeManager::instance()->getIcon(u"edit-find"_qs),
691 #endif
692 tr("Search"));
695 else
697 delete m_searchWidget;
701 void MainWindow::focusSearchFilter()
703 m_searchFilter->setFocus();
704 m_searchFilter->selectAll();
707 void MainWindow::updateNbTorrents()
709 m_tabs->setTabText(0, tr("Transfers (%1)").arg(m_transferListWidget->getSourceModel()->rowCount()));
712 void MainWindow::on_actionDocumentation_triggered() const
714 QDesktopServices::openUrl(QUrl(u"http://doc.qbittorrent.org"_qs));
717 void MainWindow::tabChanged(int newTab)
719 Q_UNUSED(newTab);
720 // We cannot rely on the index newTab
721 // because the tab order is undetermined now
722 if (m_tabs->currentWidget() == m_splitter)
724 qDebug("Changed tab to transfer list, refreshing the list");
725 m_propertiesWidget->loadDynamicData();
726 m_searchFilterAction->setVisible(true);
727 return;
729 m_searchFilterAction->setVisible(false);
731 if (m_tabs->currentWidget() == m_searchWidget)
733 qDebug("Changed tab to search engine, giving focus to search input");
734 m_searchWidget->giveFocusToSearchInput();
738 void MainWindow::saveSettings() const
740 auto *pref = Preferences::instance();
741 pref->setMainGeometry(saveGeometry());
742 m_propertiesWidget->saveSettings();
745 void MainWindow::saveSplitterSettings() const
747 if (!m_transferListFiltersWidget)
748 return;
750 auto *pref = Preferences::instance();
751 pref->setFiltersSidebarWidth(m_splitter->sizes()[0]);
754 void MainWindow::cleanup()
756 saveSettings();
757 saveSplitterSettings();
759 // delete RSSWidget explicitly to avoid crash in
760 // handleRSSUnreadCountUpdated() at application shutdown
761 delete m_rssWidget;
763 delete m_executableWatcher;
765 m_preventTimer->stop();
767 #if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
768 if (m_programUpdateTimer)
769 m_programUpdateTimer->stop();
770 #endif
772 // remove all child widgets
773 while (auto *w = findChild<QWidget *>())
774 delete w;
777 void MainWindow::loadSettings()
779 const auto *pref = Preferences::instance();
781 if (const QByteArray mainGeo = pref->getMainGeometry();
782 !mainGeo.isEmpty() && restoreGeometry(mainGeo))
784 m_posInitialized = true;
788 void MainWindow::desktopNotificationClicked()
790 if (isHidden())
792 if (m_uiLocked)
794 // Ask for UI lock password
795 if (!unlockUI())
796 return;
798 show();
799 if (isMinimized())
800 showNormal();
803 raise();
804 activateWindow();
807 void MainWindow::createKeyboardShortcuts()
809 m_ui->actionCreateTorrent->setShortcut(QKeySequence::New);
810 m_ui->actionOpen->setShortcut(QKeySequence::Open);
811 m_ui->actionDelete->setShortcut(QKeySequence::Delete);
812 m_ui->actionDelete->setShortcutContext(Qt::WidgetShortcut); // nullify its effect: delete key event is handled by respective widgets, not here
813 m_ui->actionDownloadFromURL->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_O);
814 m_ui->actionExit->setShortcut(Qt::CTRL | Qt::Key_Q);
815 #ifdef Q_OS_MACOS
816 m_ui->actionCloseWindow->setShortcut(QKeySequence::Close);
817 #else
818 m_ui->actionCloseWindow->setVisible(false);
819 #endif
821 const auto *switchTransferShortcut = new QShortcut((Qt::ALT | Qt::Key_1), this);
822 connect(switchTransferShortcut, &QShortcut::activated, this, &MainWindow::displayTransferTab);
823 const auto *switchSearchShortcut = new QShortcut((Qt::ALT | Qt::Key_2), this);
824 connect(switchSearchShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displaySearchTab));
825 const auto *switchRSSShortcut = new QShortcut((Qt::ALT | Qt::Key_3), this);
826 connect(switchRSSShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displayRSSTab));
827 const auto *switchExecutionLogShortcut = new QShortcut((Qt::ALT | Qt::Key_4), this);
828 connect(switchExecutionLogShortcut, &QShortcut::activated, this, &MainWindow::displayExecutionLogTab);
829 const auto *switchSearchFilterShortcut = new QShortcut(QKeySequence::Find, m_transferListWidget);
830 connect(switchSearchFilterShortcut, &QShortcut::activated, this, &MainWindow::focusSearchFilter);
832 m_ui->actionDocumentation->setShortcut(QKeySequence::HelpContents);
833 m_ui->actionOptions->setShortcut(Qt::ALT | Qt::Key_O);
834 m_ui->actionStatistics->setShortcut(Qt::CTRL | Qt::Key_I);
835 m_ui->actionStart->setShortcut(Qt::CTRL | Qt::Key_S);
836 m_ui->actionStartAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_S);
837 m_ui->actionPause->setShortcut(Qt::CTRL | Qt::Key_P);
838 m_ui->actionPauseAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_P);
839 m_ui->actionBottomQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Minus);
840 m_ui->actionDecreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Minus);
841 m_ui->actionIncreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Plus);
842 m_ui->actionTopQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Plus);
843 #ifdef Q_OS_MACOS
844 m_ui->actionMinimize->setShortcut(Qt::CTRL + Qt::Key_M);
845 addAction(m_ui->actionMinimize);
846 #endif
849 // Keyboard shortcuts slots
850 void MainWindow::displayTransferTab() const
852 m_tabs->setCurrentWidget(m_transferListWidget);
855 void MainWindow::displaySearchTab()
857 if (!m_searchWidget)
859 m_ui->actionSearchWidget->setChecked(true);
860 displaySearchTab(true);
863 m_tabs->setCurrentWidget(m_searchWidget);
866 void MainWindow::displayRSSTab()
868 if (!m_rssWidget)
870 m_ui->actionRSSReader->setChecked(true);
871 displayRSSTab(true);
874 m_tabs->setCurrentWidget(m_rssWidget);
877 void MainWindow::displayExecutionLogTab()
879 if (!m_executionLog)
881 m_ui->actionExecutionLogs->setChecked(true);
882 on_actionExecutionLogs_triggered(true);
885 m_tabs->setCurrentWidget(m_executionLog);
888 // End of keyboard shortcuts slots
890 void MainWindow::askRecursiveTorrentDownloadConfirmation(const BitTorrent::Torrent *torrent)
892 if (!Preferences::instance()->isRecursiveDownloadEnabled())
893 return;
895 const auto torrentID = torrent->id();
897 QMessageBox *confirmBox = new QMessageBox(QMessageBox::Question, tr("Recursive download confirmation")
898 , tr("The torrent '%1' contains .torrent files, do you want to proceed with their downloads?").arg(torrent->name())
899 , (QMessageBox::Yes | QMessageBox::No | QMessageBox::NoToAll), this);
900 confirmBox->setAttribute(Qt::WA_DeleteOnClose);
902 const QAbstractButton *yesButton = confirmBox->button(QMessageBox::Yes);
903 QAbstractButton *neverButton = confirmBox->button(QMessageBox::NoToAll);
904 neverButton->setText(tr("Never"));
906 connect(confirmBox, &QMessageBox::buttonClicked, this
907 , [torrentID, yesButton, neverButton](const QAbstractButton *button)
909 if (button == yesButton)
911 BitTorrent::Session::instance()->recursiveTorrentDownload(torrentID);
913 else if (button == neverButton)
915 Preferences::instance()->setRecursiveDownloadEnabled(false);
918 confirmBox->open();
921 void MainWindow::on_actionSetGlobalSpeedLimits_triggered()
923 auto dialog = new SpeedLimitDialog {this};
924 dialog->setAttribute(Qt::WA_DeleteOnClose);
925 dialog->open();
928 // Necessary if we want to close the window
929 // in one time if "close to systray" is enabled
930 void MainWindow::on_actionExit_triggered()
932 // UI locking enforcement.
933 if (isHidden() && m_uiLocked)
934 // Ask for UI lock password
935 if (!unlockUI()) return;
937 m_forceExit = true;
938 close();
941 #ifdef Q_OS_MACOS
942 void MainWindow::on_actionCloseWindow_triggered()
944 // On macOS window close is basically equivalent to window hide.
945 // If you decide to implement this functionality for other OS,
946 // then you will also need ui lock checks like in actionExit.
947 close();
949 #endif
951 QWidget *MainWindow::currentTabWidget() const
953 if (isMinimized() || !isVisible())
954 return nullptr;
955 if (m_tabs->currentIndex() == 0)
956 return m_transferListWidget;
957 return m_tabs->currentWidget();
960 TransferListWidget *MainWindow::transferListWidget() const
962 return m_transferListWidget;
965 bool MainWindow::unlockUI()
967 if (m_unlockDlgShowing)
968 return false;
970 bool ok = false;
971 const QString password = AutoExpandableDialog::getText(this, tr("UI lock password")
972 , tr("Please type the UI lock password:"), QLineEdit::Password, {}, &ok);
973 if (!ok) return false;
975 Preferences *const pref = Preferences::instance();
977 const QByteArray secret = pref->getUILockPassword();
978 if (!Utils::Password::PBKDF2::verify(secret, password))
980 QMessageBox::warning(this, tr("Invalid password"), tr("The password is invalid"));
981 return false;
984 m_uiLocked = false;
985 pref->setUILocked(false);
986 app()->desktopIntegration()->menu()->setEnabled(true);
987 return true;
990 void MainWindow::notifyOfUpdate(const QString &)
992 // Show restart message
993 m_statusBar->showRestartRequired();
994 LogMsg(tr("qBittorrent was just updated and needs to be restarted for the changes to be effective.")
995 , Log::CRITICAL);
996 // Delete the executable watcher
997 delete m_executableWatcher;
998 m_executableWatcher = nullptr;
1001 #ifndef Q_OS_MACOS
1002 // Toggle Main window visibility
1003 void MainWindow::toggleVisibility()
1005 if (isHidden())
1007 if (m_uiLocked && !unlockUI()) // Ask for UI lock password
1008 return;
1010 // Make sure the window is not minimized
1011 setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
1013 // Then show it
1014 show();
1015 raise();
1016 activateWindow();
1018 else
1020 hide();
1023 #endif // Q_OS_MACOS
1025 // Display About Dialog
1026 void MainWindow::on_actionAbout_triggered()
1028 // About dialog
1029 if (m_aboutDlg)
1031 m_aboutDlg->activateWindow();
1033 else
1035 m_aboutDlg = new AboutDialog(this);
1036 m_aboutDlg->setAttribute(Qt::WA_DeleteOnClose);
1037 m_aboutDlg->show();
1041 void MainWindow::on_actionStatistics_triggered()
1043 if (m_statsDlg)
1045 m_statsDlg->activateWindow();
1047 else
1049 m_statsDlg = new StatsDialog(this);
1050 m_statsDlg->setAttribute(Qt::WA_DeleteOnClose);
1051 m_statsDlg->show();
1055 void MainWindow::showEvent(QShowEvent *e)
1057 qDebug("** Show Event **");
1058 e->accept();
1060 if (isVisible())
1062 // preparations before showing the window
1064 if (currentTabWidget() == m_transferListWidget)
1065 m_propertiesWidget->loadDynamicData();
1067 // Make sure the window is initially centered
1068 if (!m_posInitialized)
1070 move(Utils::Gui::screenCenter(this));
1071 m_posInitialized = true;
1074 else
1076 // to avoid blank screen when restoring from tray icon
1077 show();
1081 void MainWindow::keyPressEvent(QKeyEvent *event)
1083 if (event->matches(QKeySequence::Paste))
1085 const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData();
1087 if (mimeData->hasText())
1089 const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
1090 const QStringList lines = mimeData->text().split(u'\n', Qt::SkipEmptyParts);
1092 for (QString line : lines)
1094 line = line.trimmed();
1096 if (!isTorrentLink(line))
1097 continue;
1099 if (useTorrentAdditionDialog)
1100 AddNewTorrentDialog::show(line, this);
1101 else
1102 BitTorrent::Session::instance()->addTorrent(line);
1105 return;
1109 QMainWindow::keyPressEvent(event);
1112 // Called when we close the program
1113 void MainWindow::closeEvent(QCloseEvent *e)
1115 Preferences *const pref = Preferences::instance();
1116 #ifdef Q_OS_MACOS
1117 if (!m_forceExit)
1119 hide();
1120 e->accept();
1121 return;
1123 #else
1124 const bool goToSystrayOnExit = pref->closeToTray();
1125 if (!m_forceExit && app()->desktopIntegration()->isActive() && goToSystrayOnExit && !this->isHidden())
1127 e->ignore();
1128 QMetaObject::invokeMethod(this, &QWidget::hide, Qt::QueuedConnection);
1129 if (!pref->closeToTrayNotified())
1131 app()->desktopIntegration()->showNotification(tr("qBittorrent is closed to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
1132 pref->setCloseToTrayNotified(true);
1134 return;
1136 #endif // Q_OS_MACOS
1138 if (pref->confirmOnExit() && BitTorrent::Session::instance()->hasActiveTorrents())
1140 if (e->spontaneous() || m_forceExit)
1142 if (!isVisible())
1143 show();
1144 QMessageBox confirmBox(QMessageBox::Question, tr("Exiting qBittorrent"),
1145 // Split it because the last sentence is used in the Web UI
1146 tr("Some files are currently transferring.") + u'\n' + tr("Are you sure you want to quit qBittorrent?"),
1147 QMessageBox::NoButton, this);
1148 QPushButton *noBtn = confirmBox.addButton(tr("&No"), QMessageBox::NoRole);
1149 confirmBox.addButton(tr("&Yes"), QMessageBox::YesRole);
1150 QPushButton *alwaysBtn = confirmBox.addButton(tr("&Always Yes"), QMessageBox::YesRole);
1151 confirmBox.setDefaultButton(noBtn);
1152 confirmBox.exec();
1153 if (!confirmBox.clickedButton() || (confirmBox.clickedButton() == noBtn))
1155 // Cancel exit
1156 e->ignore();
1157 m_forceExit = false;
1158 return;
1160 if (confirmBox.clickedButton() == alwaysBtn)
1161 // Remember choice
1162 Preferences::instance()->setConfirmOnExit(false);
1166 // Accept exit
1167 e->accept();
1168 qApp->exit();
1171 // Display window to create a torrent
1172 void MainWindow::on_actionCreateTorrent_triggered()
1174 createTorrentTriggered({});
1177 void MainWindow::createTorrentTriggered(const Path &path)
1179 if (m_createTorrentDlg)
1181 m_createTorrentDlg->updateInputPath(path);
1182 m_createTorrentDlg->activateWindow();
1184 else
1186 m_createTorrentDlg = new TorrentCreatorDialog(this, path);
1187 m_createTorrentDlg->setAttribute(Qt::WA_DeleteOnClose);
1188 m_createTorrentDlg->show();
1192 bool MainWindow::event(QEvent *e)
1194 #ifndef Q_OS_MACOS
1195 switch (e->type())
1197 case QEvent::WindowStateChange:
1198 qDebug("Window change event");
1199 // Now check to see if the window is minimised
1200 if (isMinimized())
1202 qDebug("minimisation");
1203 Preferences *const pref = Preferences::instance();
1204 if (app()->desktopIntegration()->isActive() && pref->minimizeToTray())
1206 qDebug() << "Has active window:" << (qApp->activeWindow() != nullptr);
1207 // Check if there is a modal window
1208 const QWidgetList allWidgets = QApplication::allWidgets();
1209 const bool hasModalWindow = std::any_of(allWidgets.cbegin(), allWidgets.cend()
1210 , [](const QWidget *widget) { return widget->isModal(); });
1211 // Iconify if there is no modal window
1212 if (!hasModalWindow)
1214 qDebug("Minimize to Tray enabled, hiding!");
1215 e->ignore();
1216 QMetaObject::invokeMethod(this, &QWidget::hide, Qt::QueuedConnection);
1217 if (!pref->minimizeToTrayNotified())
1219 app()->desktopIntegration()->showNotification(tr("qBittorrent is minimized to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
1220 pref->setMinimizeToTrayNotified(true);
1222 return true;
1226 break;
1227 case QEvent::ToolBarChange:
1229 qDebug("MAC: Received a toolbar change event!");
1230 const bool ret = QMainWindow::event(e);
1232 qDebug("MAC: new toolbar visibility is %d", !m_ui->actionTopToolBar->isChecked());
1233 m_ui->actionTopToolBar->toggle();
1234 Preferences::instance()->setToolbarDisplayed(m_ui->actionTopToolBar->isChecked());
1235 return ret;
1237 default:
1238 break;
1240 #endif // Q_OS_MACOS
1242 return QMainWindow::event(e);
1245 // action executed when a file is dropped
1246 void MainWindow::dropEvent(QDropEvent *event)
1248 event->acceptProposedAction();
1250 // remove scheme
1251 QStringList files;
1252 if (event->mimeData()->hasUrls())
1254 for (const QUrl &url : asConst(event->mimeData()->urls()))
1256 if (url.isEmpty())
1257 continue;
1259 files << ((url.scheme().compare(u"file", Qt::CaseInsensitive) == 0)
1260 ? url.toLocalFile()
1261 : url.toString());
1264 else
1266 files = event->mimeData()->text().split(u'\n');
1269 // differentiate ".torrent" files/links & magnet links from others
1270 QStringList torrentFiles, otherFiles;
1271 for (const QString &file : asConst(files))
1273 if (isTorrentLink(file))
1274 torrentFiles << file;
1275 else
1276 otherFiles << file;
1279 // Download torrents
1280 const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
1281 for (const QString &file : asConst(torrentFiles))
1283 if (useTorrentAdditionDialog)
1284 AddNewTorrentDialog::show(file, this);
1285 else
1286 BitTorrent::Session::instance()->addTorrent(file);
1288 if (!torrentFiles.isEmpty()) return;
1290 // Create torrent
1291 for (const QString &file : asConst(otherFiles))
1293 createTorrentTriggered(Path(file));
1295 // currently only handle the first entry
1296 // this is a stub that can be expanded later to create many torrents at once
1297 break;
1301 // Decode if we accept drag 'n drop or not
1302 void MainWindow::dragEnterEvent(QDragEnterEvent *event)
1304 for (const QString &mime : asConst(event->mimeData()->formats()))
1305 qDebug("mimeData: %s", mime.toLocal8Bit().data());
1307 if (event->mimeData()->hasFormat(u"text/plain"_qs) || event->mimeData()->hasFormat(u"text/uri-list"_qs))
1308 event->acceptProposedAction();
1311 /*****************************************************
1313 * Torrent *
1315 *****************************************************/
1317 // Display a dialog to allow user to add
1318 // torrents to download list
1319 void MainWindow::on_actionOpen_triggered()
1321 Preferences *const pref = Preferences::instance();
1322 // Open File Open Dialog
1323 // Note: it is possible to select more than one file
1324 const QStringList pathsList =
1325 QFileDialog::getOpenFileNames(this, tr("Open Torrent Files"), pref->getMainLastDir().data(),
1326 tr("Torrent Files") + u" (*" + TORRENT_FILE_EXTENSION + u')');
1328 if (pathsList.isEmpty())
1329 return;
1331 const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
1333 for (const QString &file : pathsList)
1335 if (useTorrentAdditionDialog)
1336 AddNewTorrentDialog::show(file, this);
1337 else
1338 BitTorrent::Session::instance()->addTorrent(file);
1341 // Save last dir to remember it
1342 const Path topDir {pathsList.at(0)};
1343 const Path parentDir = topDir.parentPath();
1344 pref->setMainLastDir(parentDir.isEmpty() ? topDir : parentDir);
1347 void MainWindow::activate()
1349 if (!m_uiLocked || unlockUI())
1351 show();
1352 activateWindow();
1353 raise();
1357 void MainWindow::optionsSaved()
1359 LogMsg(tr("Options saved."));
1360 loadPreferences();
1363 void MainWindow::showStatusBar(bool show)
1365 if (!show)
1367 // Remove status bar
1368 setStatusBar(nullptr);
1370 else if (!m_statusBar)
1372 // Create status bar
1373 m_statusBar = new StatusBar;
1374 connect(m_statusBar.data(), &StatusBar::connectionButtonClicked, this, &MainWindow::showConnectionSettings);
1375 connect(m_statusBar.data(), &StatusBar::alternativeSpeedsButtonClicked, this, &MainWindow::toggleAlternativeSpeeds);
1376 setStatusBar(m_statusBar);
1380 void MainWindow::showFiltersSidebar(const bool show)
1382 if (show && !m_transferListFiltersWidget)
1384 m_transferListFiltersWidget = new TransferListFiltersWidget(m_splitter, m_transferListWidget, isDownloadTrackerFavicon());
1385 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersAdded, m_transferListFiltersWidget, &TransferListFiltersWidget::addTrackers);
1386 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersRemoved, m_transferListFiltersWidget, &TransferListFiltersWidget::removeTrackers);
1387 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersChanged, m_transferListFiltersWidget, &TransferListFiltersWidget::refreshTrackers);
1388 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerlessStateChanged, m_transferListFiltersWidget, &TransferListFiltersWidget::changeTrackerless);
1389 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerEntriesUpdated, m_transferListFiltersWidget, &TransferListFiltersWidget::trackerEntriesUpdated);
1391 m_splitter->insertWidget(0, m_transferListFiltersWidget);
1392 m_splitter->setCollapsible(0, true);
1393 // From https://doc.qt.io/qt-5/qsplitter.html#setSizes:
1394 // Instead, any additional/missing space is distributed amongst the widgets
1395 // according to the relative weight of the sizes.
1396 m_splitter->setStretchFactor(0, 0);
1397 m_splitter->setStretchFactor(1, 1);
1398 m_splitter->setSizes({Preferences::instance()->getFiltersSidebarWidth()});
1400 else if (!show && m_transferListFiltersWidget)
1402 saveSplitterSettings();
1403 delete m_transferListFiltersWidget;
1404 m_transferListFiltersWidget = nullptr;
1408 void MainWindow::loadPreferences()
1410 const Preferences *pref = Preferences::instance();
1412 // General
1413 if (pref->isToolbarDisplayed())
1415 m_ui->toolBar->setVisible(true);
1417 else
1419 // Clear search filter before hiding the top toolbar
1420 m_searchFilter->clear();
1421 m_ui->toolBar->setVisible(false);
1424 showStatusBar(pref->isStatusbarDisplayed());
1426 if (pref->preventFromSuspendWhenDownloading() || pref->preventFromSuspendWhenSeeding())
1428 if (!m_preventTimer->isActive())
1430 updatePowerManagementState();
1431 m_preventTimer->start(PREVENT_SUSPEND_INTERVAL);
1434 else
1436 m_preventTimer->stop();
1437 m_pwr->setActivityState(false);
1440 m_transferListWidget->setAlternatingRowColors(pref->useAlternatingRowColors());
1441 m_propertiesWidget->getFilesList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1442 m_propertiesWidget->getTrackerList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1443 m_propertiesWidget->getPeerList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1445 // Queueing System
1446 if (BitTorrent::Session::instance()->isQueueingSystemEnabled())
1448 if (!m_ui->actionDecreaseQueuePos->isVisible())
1450 m_transferListWidget->hideQueuePosColumn(false);
1451 m_ui->actionDecreaseQueuePos->setVisible(true);
1452 m_ui->actionIncreaseQueuePos->setVisible(true);
1453 m_ui->actionTopQueuePos->setVisible(true);
1454 m_ui->actionBottomQueuePos->setVisible(true);
1455 #ifndef Q_OS_MACOS
1456 m_queueSeparator->setVisible(true);
1457 #endif
1458 m_queueSeparatorMenu->setVisible(true);
1461 else
1463 if (m_ui->actionDecreaseQueuePos->isVisible())
1465 m_transferListWidget->hideQueuePosColumn(true);
1466 m_ui->actionDecreaseQueuePos->setVisible(false);
1467 m_ui->actionIncreaseQueuePos->setVisible(false);
1468 m_ui->actionTopQueuePos->setVisible(false);
1469 m_ui->actionBottomQueuePos->setVisible(false);
1470 #ifndef Q_OS_MACOS
1471 m_queueSeparator->setVisible(false);
1472 #endif
1473 m_queueSeparatorMenu->setVisible(false);
1477 // Torrent properties
1478 m_propertiesWidget->reloadPreferences();
1480 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1481 if (pref->isUpdateCheckEnabled())
1483 if (!m_programUpdateTimer)
1485 m_programUpdateTimer = new QTimer(this);
1486 m_programUpdateTimer->setInterval(24h);
1487 m_programUpdateTimer->setSingleShot(true);
1488 connect(m_programUpdateTimer, &QTimer::timeout, this, [this]() { checkProgramUpdate(false); });
1489 m_programUpdateTimer->start();
1492 else
1494 delete m_programUpdateTimer;
1495 m_programUpdateTimer = nullptr;
1497 #endif
1499 qDebug("GUI settings loaded");
1502 void MainWindow::reloadSessionStats()
1504 const BitTorrent::SessionStatus &status = BitTorrent::Session::instance()->status();
1506 // update global information
1507 #ifdef Q_OS_MACOS
1508 if (status.payloadDownloadRate > 0)
1510 MacUtils::setBadgeLabelText(tr("%1/s", "s is a shorthand for seconds")
1511 .arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate)));
1513 else if (!MacUtils::badgeLabelText().isEmpty())
1515 MacUtils::setBadgeLabelText({});
1517 #else
1518 const auto toolTip = u"%1\n%2"_qs.arg(
1519 tr("DL speed: %1", "e.g: Download speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true))
1520 , tr("UP speed: %1", "e.g: Upload speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadUploadRate, true)));
1521 app()->desktopIntegration()->setToolTip(toolTip); // tray icon
1522 #endif // Q_OS_MACOS
1524 if (m_displaySpeedInTitle)
1526 setWindowTitle(tr("[D: %1, U: %2] qBittorrent %3", "D = Download; U = Upload; %3 is qBittorrent version")
1527 .arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true)
1528 , Utils::Misc::friendlyUnit(status.payloadUploadRate, true)
1529 , QStringLiteral(QBT_VERSION)));
1533 void MainWindow::reloadTorrentStats(const QVector<BitTorrent::Torrent *> &torrents)
1535 if (currentTabWidget() == m_transferListWidget)
1537 if (torrents.contains(m_propertiesWidget->getCurrentTorrent()))
1538 m_propertiesWidget->loadDynamicData();
1542 /*****************************************************
1544 * Utils *
1546 *****************************************************/
1548 void MainWindow::downloadFromURLList(const QStringList &urlList)
1550 const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
1551 for (const QString &url : urlList)
1553 if (useTorrentAdditionDialog)
1554 AddNewTorrentDialog::show(url, this);
1555 else
1556 BitTorrent::Session::instance()->addTorrent(url);
1560 /*****************************************************
1562 * Options *
1564 *****************************************************/
1566 QMenu *MainWindow::createDesktopIntegrationMenu()
1568 auto *menu = new QMenu(this);
1570 #ifndef Q_OS_MACOS
1571 connect(menu, &QMenu::aboutToShow, this, [this]()
1573 m_ui->actionToggleVisibility->setText(isVisible() ? tr("Hide") : tr("Show"));
1576 menu->addAction(m_ui->actionToggleVisibility);
1577 menu->addSeparator();
1578 #endif
1580 menu->addAction(m_ui->actionOpen);
1581 menu->addAction(m_ui->actionDownloadFromURL);
1582 menu->addSeparator();
1584 menu->addAction(m_ui->actionUseAlternativeSpeedLimits);
1585 menu->addAction(m_ui->actionSetGlobalSpeedLimits);
1586 menu->addSeparator();
1588 menu->addAction(m_ui->actionStartAll);
1589 menu->addAction(m_ui->actionPauseAll);
1591 #ifndef Q_OS_MACOS
1592 menu->addSeparator();
1593 menu->addAction(m_ui->actionExit);
1594 #endif
1596 if (m_uiLocked)
1597 menu->setEnabled(false);
1599 return menu;
1602 void MainWindow::updateAltSpeedsBtn(const bool alternative)
1604 m_ui->actionUseAlternativeSpeedLimits->setChecked(alternative);
1607 PropertiesWidget *MainWindow::propertiesWidget() const
1609 return m_propertiesWidget;
1612 // Display Program Options
1613 void MainWindow::on_actionOptions_triggered()
1615 if (m_options)
1617 m_options->activateWindow();
1619 else
1621 m_options = new OptionsDialog(app(), this);
1622 m_options->setAttribute(Qt::WA_DeleteOnClose);
1623 m_options->open();
1627 void MainWindow::on_actionTopToolBar_triggered()
1629 const bool isVisible = static_cast<QAction *>(sender())->isChecked();
1630 m_ui->toolBar->setVisible(isVisible);
1631 Preferences::instance()->setToolbarDisplayed(isVisible);
1634 void MainWindow::on_actionShowStatusbar_triggered()
1636 const bool isVisible = static_cast<QAction *>(sender())->isChecked();
1637 Preferences::instance()->setStatusbarDisplayed(isVisible);
1638 showStatusBar(isVisible);
1641 void MainWindow::on_actionShowFiltersSidebar_triggered(const bool checked)
1643 Preferences *const pref = Preferences::instance();
1644 pref->setFiltersSidebarVisible(checked);
1645 showFiltersSidebar(checked);
1648 void MainWindow::on_actionSpeedInTitleBar_triggered()
1650 m_displaySpeedInTitle = static_cast<QAction *>(sender())->isChecked();
1651 Preferences::instance()->showSpeedInTitleBar(m_displaySpeedInTitle);
1652 if (m_displaySpeedInTitle)
1653 reloadSessionStats();
1654 else
1655 setWindowTitle(QStringLiteral("qBittorrent " QBT_VERSION));
1658 void MainWindow::on_actionRSSReader_triggered()
1660 Preferences::instance()->setRSSWidgetVisible(m_ui->actionRSSReader->isChecked());
1661 displayRSSTab(m_ui->actionRSSReader->isChecked());
1664 void MainWindow::on_actionSearchWidget_triggered()
1666 if (!m_hasPython && m_ui->actionSearchWidget->isChecked())
1668 const Utils::ForeignApps::PythonInfo pyInfo = Utils::ForeignApps::pythonInfo();
1670 // Not installed
1671 if (!pyInfo.isValid())
1673 m_ui->actionSearchWidget->setChecked(false);
1674 Preferences::instance()->setSearchEnabled(false);
1676 #ifdef Q_OS_WIN
1677 const QMessageBox::StandardButton buttonPressed = QMessageBox::question(this, tr("Missing Python Runtime")
1678 , tr("Python is required to use the search engine but it does not seem to be installed.\nDo you want to install it now?")
1679 , (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
1680 if (buttonPressed == QMessageBox::Yes)
1681 installPython();
1682 #else
1683 QMessageBox::information(this, tr("Missing Python Runtime")
1684 , tr("Python is required to use the search engine but it does not seem to be installed."));
1685 #endif
1686 return;
1689 // Check version requirement
1690 if (!pyInfo.isSupportedVersion())
1692 m_ui->actionSearchWidget->setChecked(false);
1693 Preferences::instance()->setSearchEnabled(false);
1695 #ifdef Q_OS_WIN
1696 const QMessageBox::StandardButton buttonPressed = QMessageBox::question(this, tr("Old Python Runtime")
1697 , tr("Your Python version (%1) is outdated. Minimum requirement: %2.\nDo you want to install a newer version now?")
1698 .arg(pyInfo.version.toString(), u"3.5.0")
1699 , (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
1700 if (buttonPressed == QMessageBox::Yes)
1701 installPython();
1702 #else
1703 QMessageBox::information(this, tr("Old Python Runtime")
1704 , tr("Your Python version (%1) is outdated. Please upgrade to latest version for search engines to work.\nMinimum requirement: %2.")
1705 .arg(pyInfo.version.toString(), u"3.5.0"));
1706 #endif
1707 return;
1710 m_hasPython = true;
1711 m_ui->actionSearchWidget->setChecked(true);
1712 Preferences::instance()->setSearchEnabled(true);
1715 displaySearchTab(m_ui->actionSearchWidget->isChecked());
1718 /*****************************************************
1720 * HTTP Downloader *
1722 *****************************************************/
1724 // Display an input dialog to prompt user for
1725 // an url
1726 void MainWindow::on_actionDownloadFromURL_triggered()
1728 if (!m_downloadFromURLDialog)
1730 m_downloadFromURLDialog = new DownloadFromURLDialog(this);
1731 m_downloadFromURLDialog->setAttribute(Qt::WA_DeleteOnClose);
1732 connect(m_downloadFromURLDialog.data(), &DownloadFromURLDialog::urlsReadyToBeDownloaded, this, &MainWindow::downloadFromURLList);
1733 m_downloadFromURLDialog->open();
1737 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1738 void MainWindow::handleUpdateCheckFinished(ProgramUpdater *updater, const bool invokedByUser)
1740 m_ui->actionCheckForUpdates->setEnabled(true);
1741 m_ui->actionCheckForUpdates->setText(tr("&Check for Updates"));
1742 m_ui->actionCheckForUpdates->setToolTip(tr("Check for program updates"));
1744 const auto cleanup = [this, updater]()
1746 if (m_programUpdateTimer)
1747 m_programUpdateTimer->start();
1748 updater->deleteLater();
1751 const QString newVersion = updater->getNewVersion();
1752 if (!newVersion.isEmpty())
1754 const QString msg {tr("A new version is available.") + u"<br/>"
1755 + tr("Do you want to download %1?").arg(newVersion) + u"<br/><br/>"
1756 + u"<a href=\"https://www.qbittorrent.org/news.php\">%1</a>"_qs.arg(tr("Open changelog..."))};
1757 auto *msgBox = new QMessageBox {QMessageBox::Question, tr("qBittorrent Update Available"), msg
1758 , (QMessageBox::Yes | QMessageBox::No), this};
1759 msgBox->setAttribute(Qt::WA_DeleteOnClose);
1760 msgBox->setAttribute(Qt::WA_ShowWithoutActivating);
1761 msgBox->setDefaultButton(QMessageBox::Yes);
1762 msgBox->setWindowModality(Qt::NonModal);
1763 connect(msgBox, &QMessageBox::buttonClicked, this, [msgBox, updater](QAbstractButton *button)
1765 if (msgBox->buttonRole(button) == QMessageBox::YesRole)
1767 updater->updateProgram();
1770 connect(msgBox, &QDialog::finished, this, cleanup);
1771 msgBox->show();
1773 else
1775 if (invokedByUser)
1777 auto *msgBox = new QMessageBox {QMessageBox::Information, u"qBittorrent"_qs
1778 , tr("No updates available.\nYou are already using the latest version.")
1779 , QMessageBox::Ok, this};
1780 msgBox->setAttribute(Qt::WA_DeleteOnClose);
1781 msgBox->setWindowModality(Qt::NonModal);
1782 connect(msgBox, &QDialog::finished, this, cleanup);
1783 msgBox->show();
1785 else
1787 cleanup();
1791 #endif
1793 void MainWindow::toggleAlternativeSpeeds()
1795 BitTorrent::Session *const session = BitTorrent::Session::instance();
1796 session->setAltGlobalSpeedLimitEnabled(!session->isAltGlobalSpeedLimitEnabled());
1799 void MainWindow::on_actionDonateMoney_triggered()
1801 QDesktopServices::openUrl(QUrl(u"https://www.qbittorrent.org/donate"_qs));
1804 void MainWindow::showConnectionSettings()
1806 on_actionOptions_triggered();
1807 m_options->showConnectionTab();
1810 void MainWindow::minimizeWindow()
1812 setWindowState(windowState() | Qt::WindowMinimized);
1815 void MainWindow::on_actionExecutionLogs_triggered(bool checked)
1817 if (checked)
1819 Q_ASSERT(!m_executionLog);
1820 m_executionLog = new ExecutionLogWidget(executionLogMsgTypes(), m_tabs);
1821 #ifdef Q_OS_MACOS
1822 m_tabs->addTab(m_executionLog, tr("Execution Log"));
1823 #else
1824 const int indexTab = m_tabs->addTab(m_executionLog, tr("Execution Log"));
1825 m_tabs->setTabIcon(indexTab, UIThemeManager::instance()->getIcon(u"help-contents"_qs));
1826 #endif
1828 else
1830 delete m_executionLog;
1833 m_ui->actionNormalMessages->setEnabled(checked);
1834 m_ui->actionInformationMessages->setEnabled(checked);
1835 m_ui->actionWarningMessages->setEnabled(checked);
1836 m_ui->actionCriticalMessages->setEnabled(checked);
1837 setExecutionLogEnabled(checked);
1840 void MainWindow::on_actionNormalMessages_triggered(const bool checked)
1842 if (!m_executionLog)
1843 return;
1845 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::NORMAL, checked);
1846 setExecutionLogMsgTypes(flags);
1849 void MainWindow::on_actionInformationMessages_triggered(const bool checked)
1851 if (!m_executionLog)
1852 return;
1854 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::INFO, checked);
1855 setExecutionLogMsgTypes(flags);
1858 void MainWindow::on_actionWarningMessages_triggered(const bool checked)
1860 if (!m_executionLog)
1861 return;
1863 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::WARNING, checked);
1864 setExecutionLogMsgTypes(flags);
1867 void MainWindow::on_actionCriticalMessages_triggered(const bool checked)
1869 if (!m_executionLog)
1870 return;
1872 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::CRITICAL, checked);
1873 setExecutionLogMsgTypes(flags);
1876 void MainWindow::on_actionAutoExit_toggled(bool enabled)
1878 qDebug() << Q_FUNC_INFO << enabled;
1879 Preferences::instance()->setShutdownqBTWhenDownloadsComplete(enabled);
1882 void MainWindow::on_actionAutoSuspend_toggled(bool enabled)
1884 qDebug() << Q_FUNC_INFO << enabled;
1885 Preferences::instance()->setSuspendWhenDownloadsComplete(enabled);
1888 void MainWindow::on_actionAutoHibernate_toggled(bool enabled)
1890 qDebug() << Q_FUNC_INFO << enabled;
1891 Preferences::instance()->setHibernateWhenDownloadsComplete(enabled);
1894 void MainWindow::on_actionAutoShutdown_toggled(bool enabled)
1896 qDebug() << Q_FUNC_INFO << enabled;
1897 Preferences::instance()->setShutdownWhenDownloadsComplete(enabled);
1900 void MainWindow::updatePowerManagementState()
1902 const bool inhibitSuspend = (Preferences::instance()->preventFromSuspendWhenDownloading() && BitTorrent::Session::instance()->hasUnfinishedTorrents())
1903 || (Preferences::instance()->preventFromSuspendWhenSeeding() && BitTorrent::Session::instance()->hasRunningSeed());
1904 m_pwr->setActivityState(inhibitSuspend);
1907 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1908 void MainWindow::checkProgramUpdate(const bool invokedByUser)
1910 if (m_programUpdateTimer)
1911 m_programUpdateTimer->stop();
1913 m_ui->actionCheckForUpdates->setEnabled(false);
1914 m_ui->actionCheckForUpdates->setText(tr("Checking for Updates..."));
1915 m_ui->actionCheckForUpdates->setToolTip(tr("Already checking for program updates in the background"));
1917 auto *updater = new ProgramUpdater(this);
1918 connect(updater, &ProgramUpdater::updateCheckFinished
1919 , this, [this, invokedByUser, updater]()
1921 handleUpdateCheckFinished(updater, invokedByUser);
1923 updater->checkForUpdates();
1925 #endif
1927 #ifdef Q_OS_WIN
1928 void MainWindow::installPython()
1930 setCursor(QCursor(Qt::WaitCursor));
1931 // Download python
1932 #ifdef QBT_APP_64BIT
1933 const auto installerURL = u"https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe"_qs;
1934 #else
1935 const auto installerURL = u"https://www.python.org/ftp/python/3.8.10/python-3.8.10.exe"_qs;
1936 #endif
1937 Net::DownloadManager::instance()->download(
1938 Net::DownloadRequest(installerURL).saveToFile(true)
1939 , this, &MainWindow::pythonDownloadFinished);
1942 void MainWindow::pythonDownloadFinished(const Net::DownloadResult &result)
1944 if (result.status != Net::DownloadStatus::Success)
1946 setCursor(QCursor(Qt::ArrowCursor));
1947 QMessageBox::warning(
1948 this, tr("Download error")
1949 , tr("Python setup could not be downloaded, reason: %1.\nPlease install it manually.")
1950 .arg(result.errorString));
1951 return;
1954 setCursor(QCursor(Qt::ArrowCursor));
1955 QProcess installer;
1956 qDebug("Launching Python installer in passive mode...");
1958 const Path exePath = result.filePath + u".exe";
1959 Utils::Fs::renameFile(result.filePath, exePath);
1960 installer.start(exePath.toString(), {u"/passive"_qs});
1962 // Wait for setup to complete
1963 installer.waitForFinished(10 * 60 * 1000);
1965 qDebug("Installer stdout: %s", installer.readAllStandardOutput().data());
1966 qDebug("Installer stderr: %s", installer.readAllStandardError().data());
1967 qDebug("Setup should be complete!");
1969 // Delete temp file
1970 Utils::Fs::removeFile(exePath);
1972 // Reload search engine
1973 if (Utils::ForeignApps::pythonInfo().isSupportedVersion())
1975 m_ui->actionSearchWidget->setChecked(true);
1976 displaySearchTab(true);
1979 #endif // Q_OS_WIN