Fix transfer list tab hotkey
[qBittorrent.git] / src / gui / mainwindow.cpp
blobf94b809972564cb97033f61d52d937b7c6cdaeb6
1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2022 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 <QtGlobal>
34 #include <algorithm>
35 #include <chrono>
37 #if defined(Q_OS_WIN)
38 #include <Windows.h>
39 #include <versionhelpers.h> // must follow after Windows.h
40 #endif
42 #include <QActionGroup>
43 #include <QClipboard>
44 #include <QCloseEvent>
45 #include <QComboBox>
46 #include <QDebug>
47 #include <QDesktopServices>
48 #include <QFileDialog>
49 #include <QFileSystemWatcher>
50 #include <QKeyEvent>
51 #include <QLabel>
52 #include <QMessageBox>
53 #include <QMetaObject>
54 #include <QMimeData>
55 #include <QProcess>
56 #include <QPushButton>
57 #include <QShortcut>
58 #include <QSplitter>
59 #include <QStatusBar>
60 #include <QtGlobal>
61 #include <QTimer>
63 #include "base/bittorrent/session.h"
64 #include "base/bittorrent/sessionstatus.h"
65 #include "base/global.h"
66 #include "base/net/downloadmanager.h"
67 #include "base/path.h"
68 #include "base/preferences.h"
69 #include "base/rss/rss_folder.h"
70 #include "base/rss/rss_session.h"
71 #include "base/utils/foreignapps.h"
72 #include "base/utils/fs.h"
73 #include "base/utils/misc.h"
74 #include "base/utils/password.h"
75 #include "base/version.h"
76 #include "aboutdialog.h"
77 #include "addnewtorrentdialog.h"
78 #include "autoexpandabledialog.h"
79 #include "cookiesdialog.h"
80 #include "desktopintegration.h"
81 #include "downloadfromurldialog.h"
82 #include "executionlogwidget.h"
83 #include "hidabletabwidget.h"
84 #include "lineedit.h"
85 #include "optionsdialog.h"
86 #include "powermanagement/powermanagement.h"
87 #include "properties/peerlistwidget.h"
88 #include "properties/propertieswidget.h"
89 #include "properties/trackerlistwidget.h"
90 #include "rss/rsswidget.h"
91 #include "search/searchwidget.h"
92 #include "speedlimitdialog.h"
93 #include "statsdialog.h"
94 #include "statusbar.h"
95 #include "torrentcreatordialog.h"
96 #include "transferlistfilterswidget.h"
97 #include "transferlistmodel.h"
98 #include "transferlistwidget.h"
99 #include "ui_mainwindow.h"
100 #include "uithememanager.h"
101 #include "utils.h"
103 #ifdef Q_OS_MACOS
104 #include "macutilities.h"
105 #endif
106 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
107 #include "programupdater.h"
108 #endif
110 using namespace std::chrono_literals;
112 namespace
114 #define SETTINGS_KEY(name) u"GUI/" name
115 #define EXECUTIONLOG_SETTINGS_KEY(name) (SETTINGS_KEY(u"Log/"_s) name)
117 const std::chrono::seconds PREVENT_SUSPEND_INTERVAL {60};
119 bool isTorrentLink(const QString &str)
121 return str.startsWith(u"magnet:", Qt::CaseInsensitive)
122 || str.endsWith(TORRENT_FILE_EXTENSION, Qt::CaseInsensitive)
123 || (!str.startsWith(u"file:", Qt::CaseInsensitive)
124 && Net::DownloadManager::hasSupportedScheme(str));
128 MainWindow::MainWindow(IGUIApplication *app, WindowState initialState)
129 : GUIApplicationComponent(app)
130 , m_ui(new Ui::MainWindow)
131 , m_storeExecutionLogEnabled(EXECUTIONLOG_SETTINGS_KEY(u"Enabled"_s))
132 , m_storeDownloadTrackerFavicon(SETTINGS_KEY(u"DownloadTrackerFavicon"_s))
133 , m_storeExecutionLogTypes(EXECUTIONLOG_SETTINGS_KEY(u"Types"_s), Log::MsgType::ALL)
135 m_ui->setupUi(this);
137 Preferences *const pref = Preferences::instance();
138 m_uiLocked = pref->isUILocked();
139 setWindowTitle(QStringLiteral("qBittorrent " QBT_VERSION));
140 m_displaySpeedInTitle = pref->speedInTitleBar();
141 // Setting icons
142 #ifndef Q_OS_MACOS
143 setWindowIcon(UIThemeManager::instance()->getIcon(u"qbittorrent"_s));
144 #endif // Q_OS_MACOS
146 #if (defined(Q_OS_UNIX))
147 m_ui->actionOptions->setText(tr("Preferences"));
148 #endif
150 addToolbarContextMenu();
152 m_ui->actionOpen->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_s));
153 m_ui->actionDownloadFromURL->setIcon(UIThemeManager::instance()->getIcon(u"insert-link"_s));
154 m_ui->actionSetGlobalSpeedLimits->setIcon(UIThemeManager::instance()->getIcon(u"speedometer"_s));
155 m_ui->actionCreateTorrent->setIcon(UIThemeManager::instance()->getIcon(u"torrent-creator"_s, u"document-edit"_s));
156 m_ui->actionAbout->setIcon(UIThemeManager::instance()->getIcon(u"help-about"_s));
157 m_ui->actionStatistics->setIcon(UIThemeManager::instance()->getIcon(u"view-statistics"_s));
158 m_ui->actionTopQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-top"_s));
159 m_ui->actionIncreaseQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-up"_s));
160 m_ui->actionDecreaseQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-down"_s));
161 m_ui->actionBottomQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-bottom"_s));
162 m_ui->actionDelete->setIcon(UIThemeManager::instance()->getIcon(u"list-remove"_s));
163 m_ui->actionDocumentation->setIcon(UIThemeManager::instance()->getIcon(u"help-contents"_s));
164 m_ui->actionDonateMoney->setIcon(UIThemeManager::instance()->getIcon(u"wallet-open"_s));
165 m_ui->actionExit->setIcon(UIThemeManager::instance()->getIcon(u"application-exit"_s));
166 m_ui->actionLock->setIcon(UIThemeManager::instance()->getIcon(u"object-locked"_s));
167 m_ui->actionOptions->setIcon(UIThemeManager::instance()->getIcon(u"configure"_s, u"preferences-system"_s));
168 m_ui->actionPause->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_s, u"media-playback-pause"_s));
169 m_ui->actionPauseAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_s, u"media-playback-pause"_s));
170 m_ui->actionStart->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_s, u"media-playback-start"_s));
171 m_ui->actionStartAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_s, u"media-playback-start"_s));
172 m_ui->menuAutoShutdownOnDownloadsCompletion->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_s, u"application-exit"_s));
173 m_ui->actionManageCookies->setIcon(UIThemeManager::instance()->getIcon(u"browser-cookies"_s, u"preferences-web-browser-cookies"_s));
174 m_ui->menuLog->setIcon(UIThemeManager::instance()->getIcon(u"help-contents"_s));
175 m_ui->actionCheckForUpdates->setIcon(UIThemeManager::instance()->getIcon(u"view-refresh"_s));
177 auto *lockMenu = new QMenu(this);
178 lockMenu->addAction(tr("&Set Password"), this, &MainWindow::defineUILockPassword);
179 lockMenu->addAction(tr("&Clear Password"), this, &MainWindow::clearUILockPassword);
180 m_ui->actionLock->setMenu(lockMenu);
182 // Creating Bittorrent session
183 updateAltSpeedsBtn(BitTorrent::Session::instance()->isAltGlobalSpeedLimitEnabled());
185 connect(BitTorrent::Session::instance(), &BitTorrent::Session::speedLimitModeChanged, this, &MainWindow::updateAltSpeedsBtn);
186 connect(BitTorrent::Session::instance(), &BitTorrent::Session::recursiveTorrentDownloadPossible, this, &MainWindow::askRecursiveTorrentDownloadConfirmation);
188 qDebug("create tabWidget");
189 m_tabs = new HidableTabWidget(this);
190 connect(m_tabs.data(), &QTabWidget::currentChanged, this, &MainWindow::tabChanged);
192 m_splitter = new QSplitter(Qt::Horizontal, this);
193 // vSplitter->setChildrenCollapsible(false);
195 auto *hSplitter = new QSplitter(Qt::Vertical, this);
196 hSplitter->setChildrenCollapsible(false);
197 hSplitter->setFrameShape(QFrame::NoFrame);
199 // Torrent filter
200 m_columnFilterEdit = new LineEdit;
201 m_columnFilterEdit->setPlaceholderText(tr("Filter torrents..."));
202 m_columnFilterEdit->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
203 m_columnFilterEdit->setFixedWidth(200);
204 m_columnFilterEdit->setContextMenuPolicy(Qt::CustomContextMenu);
205 connect(m_columnFilterEdit, &QWidget::customContextMenuRequested, this, &MainWindow::showFilterContextMenu);
206 auto *columnFilterLabel = new QLabel(tr("Filter by:"));
207 m_columnFilterComboBox = new QComboBox;
208 QHBoxLayout *columnFilterLayout = new QHBoxLayout(m_columnFilterWidget);
209 columnFilterLayout->setContentsMargins(0, 0, 0, 0);
210 auto *columnFilterSpacer = new QWidget(this);
211 columnFilterSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
212 columnFilterLayout->addWidget(columnFilterSpacer);
213 columnFilterLayout->addWidget(m_columnFilterEdit);
214 columnFilterLayout->addWidget(columnFilterLabel, 0);
215 columnFilterLayout->addWidget(m_columnFilterComboBox, 0);
216 m_columnFilterWidget = new QWidget(this);
217 m_columnFilterWidget->setLayout(columnFilterLayout);
218 m_columnFilterAction = m_ui->toolBar->insertWidget(m_ui->actionLock, m_columnFilterWidget);
220 auto *spacer = new QWidget(this);
221 spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
222 m_ui->toolBar->insertWidget(m_columnFilterAction, spacer);
224 // Transfer List tab
225 m_transferListWidget = new TransferListWidget(hSplitter, this);
226 m_propertiesWidget = new PropertiesWidget(hSplitter);
227 connect(m_transferListWidget, &TransferListWidget::currentTorrentChanged, m_propertiesWidget, &PropertiesWidget::loadTorrentInfos);
228 hSplitter->addWidget(m_transferListWidget);
229 hSplitter->addWidget(m_propertiesWidget);
230 m_splitter->addWidget(hSplitter);
231 m_splitter->setCollapsible(0, false);
232 m_tabs->addTab(m_splitter,
233 #ifndef Q_OS_MACOS
234 UIThemeManager::instance()->getIcon(u"folder-remote"_s),
235 #endif
236 tr("Transfers"));
237 // Filter types
238 const QVector<TransferListModel::Column> filterTypes = {TransferListModel::Column::TR_NAME, TransferListModel::Column::TR_SAVE_PATH};
239 for (const TransferListModel::Column type : filterTypes)
241 const QString typeName = m_transferListWidget->getSourceModel()->headerData(type, Qt::Horizontal, Qt::DisplayRole).value<QString>();
242 m_columnFilterComboBox->addItem(typeName, type);
244 connect(m_columnFilterComboBox, &QComboBox::currentIndexChanged, this, &MainWindow::applyTransferListFilter);
245 connect(m_columnFilterEdit, &LineEdit::textChanged, this, &MainWindow::applyTransferListFilter);
246 connect(hSplitter, &QSplitter::splitterMoved, this, &MainWindow::saveSettings);
247 connect(m_splitter, &QSplitter::splitterMoved, this, &MainWindow::saveSplitterSettings);
248 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersChanged, m_propertiesWidget, &PropertiesWidget::loadTrackers);
250 #ifdef Q_OS_MACOS
251 // Increase top spacing to avoid tab overlapping
252 m_ui->centralWidgetLayout->addSpacing(8);
253 #endif
255 m_ui->centralWidgetLayout->addWidget(m_tabs);
257 m_queueSeparator = m_ui->toolBar->insertSeparator(m_ui->actionTopQueuePos);
258 m_queueSeparatorMenu = m_ui->menuEdit->insertSeparator(m_ui->actionTopQueuePos);
260 #ifdef Q_OS_MACOS
261 for (QAction *action : asConst(m_ui->toolBar->actions()))
263 if (action->isSeparator())
265 QWidget *spacer = new QWidget(this);
266 spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
267 spacer->setMinimumWidth(16);
268 m_ui->toolBar->insertWidget(action, spacer);
269 m_ui->toolBar->removeAction(action);
273 QWidget *spacer = new QWidget(this);
274 spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
275 spacer->setMinimumWidth(8);
276 m_ui->toolBar->insertWidget(m_ui->actionDownloadFromURL, spacer);
279 QWidget *spacer = new QWidget(this);
280 spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
281 spacer->setMinimumWidth(8);
282 m_ui->toolBar->addWidget(spacer);
284 #endif // Q_OS_MACOS
286 // Transfer list slots
287 connect(m_ui->actionStart, &QAction::triggered, m_transferListWidget, &TransferListWidget::startSelectedTorrents);
288 connect(m_ui->actionStartAll, &QAction::triggered, m_transferListWidget, &TransferListWidget::resumeAllTorrents);
289 connect(m_ui->actionPause, &QAction::triggered, m_transferListWidget, &TransferListWidget::pauseSelectedTorrents);
290 connect(m_ui->actionPauseAll, &QAction::triggered, m_transferListWidget, &TransferListWidget::pauseAllTorrents);
291 connect(m_ui->actionDelete, &QAction::triggered, m_transferListWidget, &TransferListWidget::softDeleteSelectedTorrents);
292 connect(m_ui->actionTopQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::topQueuePosSelectedTorrents);
293 connect(m_ui->actionIncreaseQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::increaseQueuePosSelectedTorrents);
294 connect(m_ui->actionDecreaseQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::decreaseQueuePosSelectedTorrents);
295 connect(m_ui->actionBottomQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::bottomQueuePosSelectedTorrents);
296 #ifndef Q_OS_MACOS
297 connect(m_ui->actionToggleVisibility, &QAction::triggered, this, &MainWindow::toggleVisibility);
298 #endif
299 connect(m_ui->actionMinimize, &QAction::triggered, this, &MainWindow::minimizeWindow);
300 connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds);
302 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
303 connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, [this]() { checkProgramUpdate(true); });
305 // trigger an early check on startup
306 if (pref->isUpdateCheckEnabled())
307 checkProgramUpdate(false);
308 #else
309 m_ui->actionCheckForUpdates->setVisible(false);
310 #endif
312 // Certain menu items should reside at specific places on macOS.
313 // Qt partially does it on its own, but updates and different languages require tuning.
314 m_ui->actionExit->setMenuRole(QAction::QuitRole);
315 m_ui->actionAbout->setMenuRole(QAction::AboutRole);
316 m_ui->actionCheckForUpdates->setMenuRole(QAction::ApplicationSpecificRole);
317 m_ui->actionOptions->setMenuRole(QAction::PreferencesRole);
319 connect(m_ui->actionManageCookies, &QAction::triggered, this, &MainWindow::manageCookies);
321 // Initialise system sleep inhibition timer
322 m_pwr = new PowerManagement(this);
323 m_preventTimer = new QTimer(this);
324 connect(m_preventTimer, &QTimer::timeout, this, &MainWindow::updatePowerManagementState);
325 m_preventTimer->start(PREVENT_SUSPEND_INTERVAL);
327 // Configure BT session according to options
328 loadPreferences();
330 connect(BitTorrent::Session::instance(), &BitTorrent::Session::statsUpdated, this, &MainWindow::reloadSessionStats);
331 connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsUpdated, this, &MainWindow::reloadTorrentStats);
333 // Accept drag 'n drops
334 setAcceptDrops(true);
335 createKeyboardShortcuts();
337 #ifdef Q_OS_MACOS
338 setUnifiedTitleAndToolBarOnMac(true);
339 #endif
341 // View settings
342 m_ui->actionTopToolBar->setChecked(pref->isToolbarDisplayed());
343 m_ui->actionShowStatusbar->setChecked(pref->isStatusbarDisplayed());
344 m_ui->actionSpeedInTitleBar->setChecked(pref->speedInTitleBar());
345 m_ui->actionRSSReader->setChecked(pref->isRSSWidgetEnabled());
346 m_ui->actionSearchWidget->setChecked(pref->isSearchEnabled());
347 m_ui->actionExecutionLogs->setChecked(isExecutionLogEnabled());
349 const Log::MsgTypes flags = executionLogMsgTypes();
350 m_ui->actionNormalMessages->setChecked(flags.testFlag(Log::NORMAL));
351 m_ui->actionInformationMessages->setChecked(flags.testFlag(Log::INFO));
352 m_ui->actionWarningMessages->setChecked(flags.testFlag(Log::WARNING));
353 m_ui->actionCriticalMessages->setChecked(flags.testFlag(Log::CRITICAL));
355 displayRSSTab(m_ui->actionRSSReader->isChecked());
356 on_actionExecutionLogs_triggered(m_ui->actionExecutionLogs->isChecked());
357 on_actionNormalMessages_triggered(m_ui->actionNormalMessages->isChecked());
358 on_actionInformationMessages_triggered(m_ui->actionInformationMessages->isChecked());
359 on_actionWarningMessages_triggered(m_ui->actionWarningMessages->isChecked());
360 on_actionCriticalMessages_triggered(m_ui->actionCriticalMessages->isChecked());
361 if (m_ui->actionSearchWidget->isChecked())
362 QMetaObject::invokeMethod(this, &MainWindow::on_actionSearchWidget_triggered, Qt::QueuedConnection);
363 // Auto shutdown actions
364 auto *autoShutdownGroup = new QActionGroup(this);
365 autoShutdownGroup->setExclusive(true);
366 autoShutdownGroup->addAction(m_ui->actionAutoShutdownDisabled);
367 autoShutdownGroup->addAction(m_ui->actionAutoExit);
368 autoShutdownGroup->addAction(m_ui->actionAutoShutdown);
369 autoShutdownGroup->addAction(m_ui->actionAutoSuspend);
370 autoShutdownGroup->addAction(m_ui->actionAutoHibernate);
371 #if (!defined(Q_OS_UNIX) || defined(Q_OS_MACOS)) || defined(QBT_USES_DBUS)
372 m_ui->actionAutoShutdown->setChecked(pref->shutdownWhenDownloadsComplete());
373 m_ui->actionAutoSuspend->setChecked(pref->suspendWhenDownloadsComplete());
374 m_ui->actionAutoHibernate->setChecked(pref->hibernateWhenDownloadsComplete());
375 #else
376 m_ui->actionAutoShutdown->setDisabled(true);
377 m_ui->actionAutoSuspend->setDisabled(true);
378 m_ui->actionAutoHibernate->setDisabled(true);
379 #endif
380 m_ui->actionAutoExit->setChecked(pref->shutdownqBTWhenDownloadsComplete());
382 if (!autoShutdownGroup->checkedAction())
383 m_ui->actionAutoShutdownDisabled->setChecked(true);
385 // Load Window state and sizes
386 loadSettings();
388 app->desktopIntegration()->setMenu(createDesktopIntegrationMenu());
389 #ifndef Q_OS_MACOS
390 m_ui->actionLock->setVisible(app->desktopIntegration()->isActive());
391 connect(app->desktopIntegration(), &DesktopIntegration::stateChanged, this, [this, app]()
393 m_ui->actionLock->setVisible(app->desktopIntegration()->isActive());
395 #endif
396 connect(app->desktopIntegration(), &DesktopIntegration::notificationClicked, this, &MainWindow::desktopNotificationClicked);
397 connect(app->desktopIntegration(), &DesktopIntegration::activationRequested, this, [this]()
399 #ifdef Q_OS_MACOS
400 if (!isVisible())
401 activate();
402 #else
403 toggleVisibility();
404 #endif
407 #ifdef Q_OS_MACOS
408 if (initialState == WindowState::Normal)
410 show();
411 activateWindow();
412 raise();
414 else
416 // Make sure the Window is visible if we don't have a tray icon
417 showMinimized();
419 #else
420 if (app->desktopIntegration()->isActive())
422 if ((initialState == WindowState::Normal) && !m_uiLocked)
424 show();
425 activateWindow();
426 raise();
428 else if (initialState == WindowState::Minimized)
430 showMinimized();
431 if (pref->minimizeToTray())
433 hide();
434 if (!pref->minimizeToTrayNotified())
436 app->desktopIntegration()->showNotification(tr("qBittorrent is minimized to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
437 pref->setMinimizeToTrayNotified(true);
442 else
444 // Make sure the Window is visible if we don't have a tray icon
445 if (initialState != WindowState::Normal)
447 showMinimized();
449 else
451 show();
452 activateWindow();
453 raise();
456 #endif
458 m_propertiesWidget->readSettings();
460 const bool isFiltersSidebarVisible = pref->isFiltersSidebarVisible();
461 m_ui->actionShowFiltersSidebar->setChecked(isFiltersSidebarVisible);
462 if (isFiltersSidebarVisible)
464 showFiltersSidebar(true);
466 else
468 m_transferListWidget->applyStatusFilter(pref->getTransSelFilter());
469 m_transferListWidget->applyCategoryFilter(QString());
470 m_transferListWidget->applyTagFilter(QString());
471 m_transferListWidget->applyTrackerFilterAll();
474 // Start watching the executable for updates
475 m_executableWatcher = new QFileSystemWatcher(this);
476 connect(m_executableWatcher, &QFileSystemWatcher::fileChanged, this, &MainWindow::notifyOfUpdate);
477 m_executableWatcher->addPath(qApp->applicationFilePath());
479 m_transferListWidget->setFocus();
481 // Update the number of torrents (tab)
482 updateNbTorrents();
483 connect(m_transferListWidget->getSourceModel(), &QAbstractItemModel::rowsInserted, this, &MainWindow::updateNbTorrents);
484 connect(m_transferListWidget->getSourceModel(), &QAbstractItemModel::rowsRemoved, this, &MainWindow::updateNbTorrents);
486 connect(pref, &Preferences::changed, this, &MainWindow::optionsSaved);
488 qDebug("GUI Built");
491 MainWindow::~MainWindow()
493 delete m_ui;
496 bool MainWindow::isExecutionLogEnabled() const
498 return m_storeExecutionLogEnabled;
501 void MainWindow::setExecutionLogEnabled(const bool value)
503 m_storeExecutionLogEnabled = value;
506 Log::MsgTypes MainWindow::executionLogMsgTypes() const
508 return m_storeExecutionLogTypes;
511 void MainWindow::setExecutionLogMsgTypes(const Log::MsgTypes value)
513 m_executionLog->setMessageTypes(value);
514 m_storeExecutionLogTypes = value;
517 bool MainWindow::isDownloadTrackerFavicon() const
519 return m_storeDownloadTrackerFavicon;
522 void MainWindow::setDownloadTrackerFavicon(const bool value)
524 if (m_transferListFiltersWidget)
525 m_transferListFiltersWidget->setDownloadTrackerFavicon(value);
526 m_storeDownloadTrackerFavicon = value;
529 void MainWindow::addToolbarContextMenu()
531 const Preferences *const pref = Preferences::instance();
532 m_toolbarMenu = new QMenu(this);
534 m_ui->toolBar->setContextMenuPolicy(Qt::CustomContextMenu);
535 connect(m_ui->toolBar, &QWidget::customContextMenuRequested, this, &MainWindow::toolbarMenuRequested);
537 QAction *iconsOnly = m_toolbarMenu->addAction(tr("Icons Only"), this, &MainWindow::toolbarIconsOnly);
538 QAction *textOnly = m_toolbarMenu->addAction(tr("Text Only"), this, &MainWindow::toolbarTextOnly);
539 QAction *textBesideIcons = m_toolbarMenu->addAction(tr("Text Alongside Icons"), this, &MainWindow::toolbarTextBeside);
540 QAction *textUnderIcons = m_toolbarMenu->addAction(tr("Text Under Icons"), this, &MainWindow::toolbarTextUnder);
541 QAction *followSystemStyle = m_toolbarMenu->addAction(tr("Follow System Style"), this, &MainWindow::toolbarFollowSystem);
543 auto *textPositionGroup = new QActionGroup(m_toolbarMenu);
544 textPositionGroup->addAction(iconsOnly);
545 iconsOnly->setCheckable(true);
546 textPositionGroup->addAction(textOnly);
547 textOnly->setCheckable(true);
548 textPositionGroup->addAction(textBesideIcons);
549 textBesideIcons->setCheckable(true);
550 textPositionGroup->addAction(textUnderIcons);
551 textUnderIcons->setCheckable(true);
552 textPositionGroup->addAction(followSystemStyle);
553 followSystemStyle->setCheckable(true);
555 const auto buttonStyle = static_cast<Qt::ToolButtonStyle>(pref->getToolbarTextPosition());
556 if ((buttonStyle >= Qt::ToolButtonIconOnly) && (buttonStyle <= Qt::ToolButtonFollowStyle))
557 m_ui->toolBar->setToolButtonStyle(buttonStyle);
558 switch (buttonStyle)
560 case Qt::ToolButtonIconOnly:
561 iconsOnly->setChecked(true);
562 break;
563 case Qt::ToolButtonTextOnly:
564 textOnly->setChecked(true);
565 break;
566 case Qt::ToolButtonTextBesideIcon:
567 textBesideIcons->setChecked(true);
568 break;
569 case Qt::ToolButtonTextUnderIcon:
570 textUnderIcons->setChecked(true);
571 break;
572 default:
573 followSystemStyle->setChecked(true);
577 void MainWindow::manageCookies()
579 auto *cookieDialog = new CookiesDialog(this);
580 cookieDialog->setAttribute(Qt::WA_DeleteOnClose);
581 cookieDialog->open();
584 void MainWindow::toolbarMenuRequested()
586 m_toolbarMenu->popup(QCursor::pos());
589 void MainWindow::toolbarIconsOnly()
591 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
592 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonIconOnly);
595 void MainWindow::toolbarTextOnly()
597 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextOnly);
598 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextOnly);
601 void MainWindow::toolbarTextBeside()
603 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
604 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextBesideIcon);
607 void MainWindow::toolbarTextUnder()
609 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
610 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextUnderIcon);
613 void MainWindow::toolbarFollowSystem()
615 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle);
616 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonFollowStyle);
619 bool MainWindow::defineUILockPassword()
621 bool ok = false;
622 const QString newPassword = AutoExpandableDialog::getText(this, tr("UI lock password")
623 , tr("Please type the UI lock password:"), QLineEdit::Password, {}, &ok);
624 if (!ok)
625 return false;
627 if (newPassword.size() < 3)
629 QMessageBox::warning(this, tr("Invalid password"), tr("The password must be at least 3 characters long"));
630 return false;
633 Preferences::instance()->setUILockPassword(Utils::Password::PBKDF2::generate(newPassword));
634 return true;
637 void MainWindow::clearUILockPassword()
639 const QMessageBox::StandardButton answer = QMessageBox::question(this, tr("Clear the password")
640 , tr("Are you sure you want to clear the password?"), (QMessageBox::Yes | QMessageBox::No), QMessageBox::No);
641 if (answer == QMessageBox::Yes)
642 Preferences::instance()->setUILockPassword({});
645 void MainWindow::on_actionLock_triggered()
647 Preferences *const pref = Preferences::instance();
649 // Check if there is a password
650 if (pref->getUILockPassword().isEmpty())
652 if (!defineUILockPassword())
653 return;
656 // Lock the interface
657 m_uiLocked = true;
658 pref->setUILocked(true);
659 app()->desktopIntegration()->menu()->setEnabled(false);
660 hide();
663 void MainWindow::handleRSSUnreadCountUpdated(int count)
665 m_tabs->setTabText(m_tabs->indexOf(m_rssWidget), tr("RSS (%1)").arg(count));
668 void MainWindow::displayRSSTab(bool enable)
670 if (enable)
672 // RSS tab
673 if (!m_rssWidget)
675 m_rssWidget = new RSSWidget(m_tabs);
676 connect(m_rssWidget.data(), &RSSWidget::unreadCountUpdated, this, &MainWindow::handleRSSUnreadCountUpdated);
677 #ifdef Q_OS_MACOS
678 m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
679 #else
680 const int indexTab = m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
681 m_tabs->setTabIcon(indexTab, UIThemeManager::instance()->getIcon(u"application-rss"_s));
682 #endif
685 else
687 delete m_rssWidget;
691 void MainWindow::showFilterContextMenu()
693 const Preferences *pref = Preferences::instance();
695 QMenu *menu = m_columnFilterEdit->createStandardContextMenu();
696 menu->setAttribute(Qt::WA_DeleteOnClose);
697 menu->addSeparator();
699 QAction *useRegexAct = menu->addAction(tr("Use regular expressions"));
700 useRegexAct->setCheckable(true);
701 useRegexAct->setChecked(pref->getRegexAsFilteringPatternForTransferList());
702 connect(useRegexAct, &QAction::toggled, pref, &Preferences::setRegexAsFilteringPatternForTransferList);
703 connect(useRegexAct, &QAction::toggled, this, &MainWindow::applyTransferListFilter);
705 menu->popup(QCursor::pos());
708 void MainWindow::displaySearchTab(bool enable)
710 Preferences::instance()->setSearchEnabled(enable);
711 if (enable)
713 // RSS tab
714 if (!m_searchWidget)
716 m_searchWidget = new SearchWidget(app(), this);
717 m_tabs->insertTab(1, m_searchWidget,
718 #ifndef Q_OS_MACOS
719 UIThemeManager::instance()->getIcon(u"edit-find"_s),
720 #endif
721 tr("Search"));
724 else
726 delete m_searchWidget;
730 void MainWindow::focusSearchFilter()
732 m_columnFilterEdit->setFocus();
733 m_columnFilterEdit->selectAll();
736 void MainWindow::updateNbTorrents()
738 m_tabs->setTabText(0, tr("Transfers (%1)").arg(m_transferListWidget->getSourceModel()->rowCount()));
741 void MainWindow::on_actionDocumentation_triggered() const
743 QDesktopServices::openUrl(QUrl(u"https://doc.qbittorrent.org"_s));
746 void MainWindow::tabChanged(int newTab)
748 Q_UNUSED(newTab);
749 // We cannot rely on the index newTab
750 // because the tab order is undetermined now
751 if (m_tabs->currentWidget() == m_splitter)
753 qDebug("Changed tab to transfer list, refreshing the list");
754 m_propertiesWidget->loadDynamicData();
755 m_columnFilterAction->setVisible(true);
756 return;
758 m_columnFilterAction->setVisible(false);
760 if (m_tabs->currentWidget() == m_searchWidget)
762 qDebug("Changed tab to search engine, giving focus to search input");
763 m_searchWidget->giveFocusToSearchInput();
767 void MainWindow::saveSettings() const
769 auto *pref = Preferences::instance();
770 pref->setMainGeometry(saveGeometry());
771 m_propertiesWidget->saveSettings();
774 void MainWindow::saveSplitterSettings() const
776 if (!m_transferListFiltersWidget)
777 return;
779 auto *pref = Preferences::instance();
780 pref->setFiltersSidebarWidth(m_splitter->sizes()[0]);
783 void MainWindow::cleanup()
785 saveSettings();
786 saveSplitterSettings();
788 // delete RSSWidget explicitly to avoid crash in
789 // handleRSSUnreadCountUpdated() at application shutdown
790 delete m_rssWidget;
792 delete m_executableWatcher;
794 m_preventTimer->stop();
796 #if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
797 if (m_programUpdateTimer)
798 m_programUpdateTimer->stop();
799 #endif
801 // remove all child widgets
802 while (auto *w = findChild<QWidget *>())
803 delete w;
806 void MainWindow::loadSettings()
808 const auto *pref = Preferences::instance();
810 if (const QByteArray mainGeo = pref->getMainGeometry();
811 !mainGeo.isEmpty() && restoreGeometry(mainGeo))
813 m_posInitialized = true;
817 void MainWindow::desktopNotificationClicked()
819 if (isHidden())
821 if (m_uiLocked)
823 // Ask for UI lock password
824 if (!unlockUI())
825 return;
827 show();
828 if (isMinimized())
829 showNormal();
832 raise();
833 activateWindow();
836 void MainWindow::createKeyboardShortcuts()
838 m_ui->actionCreateTorrent->setShortcut(QKeySequence::New);
839 m_ui->actionOpen->setShortcut(QKeySequence::Open);
840 m_ui->actionDelete->setShortcut(QKeySequence::Delete);
841 m_ui->actionDelete->setShortcutContext(Qt::WidgetShortcut); // nullify its effect: delete key event is handled by respective widgets, not here
842 m_ui->actionDownloadFromURL->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_O);
843 m_ui->actionExit->setShortcut(Qt::CTRL | Qt::Key_Q);
844 #ifdef Q_OS_MACOS
845 m_ui->actionCloseWindow->setShortcut(QKeySequence::Close);
846 #else
847 m_ui->actionCloseWindow->setVisible(false);
848 #endif
850 const auto *switchTransferShortcut = new QShortcut((Qt::ALT | Qt::Key_1), this);
851 connect(switchTransferShortcut, &QShortcut::activated, this, &MainWindow::displayTransferTab);
852 const auto *switchSearchShortcut = new QShortcut((Qt::ALT | Qt::Key_2), this);
853 connect(switchSearchShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displaySearchTab));
854 const auto *switchRSSShortcut = new QShortcut((Qt::ALT | Qt::Key_3), this);
855 connect(switchRSSShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displayRSSTab));
856 const auto *switchExecutionLogShortcut = new QShortcut((Qt::ALT | Qt::Key_4), this);
857 connect(switchExecutionLogShortcut, &QShortcut::activated, this, &MainWindow::displayExecutionLogTab);
858 const auto *switchSearchFilterShortcut = new QShortcut(QKeySequence::Find, m_transferListWidget);
859 connect(switchSearchFilterShortcut, &QShortcut::activated, this, &MainWindow::focusSearchFilter);
861 m_ui->actionDocumentation->setShortcut(QKeySequence::HelpContents);
862 m_ui->actionOptions->setShortcut(Qt::ALT | Qt::Key_O);
863 m_ui->actionStatistics->setShortcut(Qt::CTRL | Qt::Key_I);
864 m_ui->actionStart->setShortcut(Qt::CTRL | Qt::Key_S);
865 m_ui->actionStartAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_S);
866 m_ui->actionPause->setShortcut(Qt::CTRL | Qt::Key_P);
867 m_ui->actionPauseAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_P);
868 m_ui->actionBottomQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Minus);
869 m_ui->actionDecreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Minus);
870 m_ui->actionIncreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Plus);
871 m_ui->actionTopQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Plus);
872 #ifdef Q_OS_MACOS
873 m_ui->actionMinimize->setShortcut(Qt::CTRL + Qt::Key_M);
874 addAction(m_ui->actionMinimize);
875 #endif
878 // Keyboard shortcuts slots
879 void MainWindow::displayTransferTab() const
881 m_tabs->setCurrentWidget(m_splitter);
884 void MainWindow::displaySearchTab()
886 if (!m_searchWidget)
888 m_ui->actionSearchWidget->setChecked(true);
889 displaySearchTab(true);
892 m_tabs->setCurrentWidget(m_searchWidget);
895 void MainWindow::displayRSSTab()
897 if (!m_rssWidget)
899 m_ui->actionRSSReader->setChecked(true);
900 displayRSSTab(true);
903 m_tabs->setCurrentWidget(m_rssWidget);
906 void MainWindow::displayExecutionLogTab()
908 if (!m_executionLog)
910 m_ui->actionExecutionLogs->setChecked(true);
911 on_actionExecutionLogs_triggered(true);
914 m_tabs->setCurrentWidget(m_executionLog);
917 // End of keyboard shortcuts slots
919 void MainWindow::askRecursiveTorrentDownloadConfirmation(const BitTorrent::Torrent *torrent)
921 if (!Preferences::instance()->isRecursiveDownloadEnabled())
922 return;
924 const auto torrentID = torrent->id();
926 QMessageBox *confirmBox = new QMessageBox(QMessageBox::Question, tr("Recursive download confirmation")
927 , tr("The torrent '%1' contains .torrent files, do you want to proceed with their downloads?").arg(torrent->name())
928 , (QMessageBox::Yes | QMessageBox::No | QMessageBox::NoToAll), this);
929 confirmBox->setAttribute(Qt::WA_DeleteOnClose);
931 const QAbstractButton *yesButton = confirmBox->button(QMessageBox::Yes);
932 QAbstractButton *neverButton = confirmBox->button(QMessageBox::NoToAll);
933 neverButton->setText(tr("Never"));
935 connect(confirmBox, &QMessageBox::buttonClicked, this
936 , [torrentID, yesButton, neverButton](const QAbstractButton *button)
938 if (button == yesButton)
940 BitTorrent::Session::instance()->recursiveTorrentDownload(torrentID);
942 else if (button == neverButton)
944 Preferences::instance()->setRecursiveDownloadEnabled(false);
947 confirmBox->open();
950 void MainWindow::on_actionSetGlobalSpeedLimits_triggered()
952 auto *dialog = new SpeedLimitDialog {this};
953 dialog->setAttribute(Qt::WA_DeleteOnClose);
954 dialog->open();
957 // Necessary if we want to close the window
958 // in one time if "close to systray" is enabled
959 void MainWindow::on_actionExit_triggered()
961 // UI locking enforcement.
962 if (isHidden() && m_uiLocked)
963 // Ask for UI lock password
964 if (!unlockUI()) return;
966 m_forceExit = true;
967 close();
970 #ifdef Q_OS_MACOS
971 void MainWindow::on_actionCloseWindow_triggered()
973 // On macOS window close is basically equivalent to window hide.
974 // If you decide to implement this functionality for other OS,
975 // then you will also need ui lock checks like in actionExit.
976 close();
978 #endif
980 QWidget *MainWindow::currentTabWidget() const
982 if (isMinimized() || !isVisible())
983 return nullptr;
984 if (m_tabs->currentIndex() == 0)
985 return m_transferListWidget;
986 return m_tabs->currentWidget();
989 TransferListWidget *MainWindow::transferListWidget() const
991 return m_transferListWidget;
994 bool MainWindow::unlockUI()
996 if (m_unlockDlgShowing)
997 return false;
999 bool ok = false;
1000 const QString password = AutoExpandableDialog::getText(this, tr("UI lock password")
1001 , tr("Please type the UI lock password:"), QLineEdit::Password, {}, &ok);
1002 if (!ok) return false;
1004 Preferences *const pref = Preferences::instance();
1006 const QByteArray secret = pref->getUILockPassword();
1007 if (!Utils::Password::PBKDF2::verify(secret, password))
1009 QMessageBox::warning(this, tr("Invalid password"), tr("The password is invalid"));
1010 return false;
1013 m_uiLocked = false;
1014 pref->setUILocked(false);
1015 app()->desktopIntegration()->menu()->setEnabled(true);
1016 return true;
1019 void MainWindow::notifyOfUpdate(const QString &)
1021 // Show restart message
1022 m_statusBar->showRestartRequired();
1023 LogMsg(tr("qBittorrent was just updated and needs to be restarted for the changes to be effective.")
1024 , Log::CRITICAL);
1025 // Delete the executable watcher
1026 delete m_executableWatcher;
1027 m_executableWatcher = nullptr;
1030 #ifndef Q_OS_MACOS
1031 // Toggle Main window visibility
1032 void MainWindow::toggleVisibility()
1034 if (isHidden())
1036 if (m_uiLocked && !unlockUI()) // Ask for UI lock password
1037 return;
1039 // Make sure the window is not minimized
1040 setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
1042 // Then show it
1043 show();
1044 raise();
1045 activateWindow();
1047 else
1049 hide();
1052 #endif // Q_OS_MACOS
1054 // Display About Dialog
1055 void MainWindow::on_actionAbout_triggered()
1057 // About dialog
1058 if (m_aboutDlg)
1060 m_aboutDlg->activateWindow();
1062 else
1064 m_aboutDlg = new AboutDialog(this);
1065 m_aboutDlg->setAttribute(Qt::WA_DeleteOnClose);
1066 m_aboutDlg->show();
1070 void MainWindow::on_actionStatistics_triggered()
1072 if (m_statsDlg)
1074 m_statsDlg->activateWindow();
1076 else
1078 m_statsDlg = new StatsDialog(this);
1079 m_statsDlg->setAttribute(Qt::WA_DeleteOnClose);
1080 m_statsDlg->show();
1084 void MainWindow::showEvent(QShowEvent *e)
1086 qDebug("** Show Event **");
1087 e->accept();
1089 if (isVisible())
1091 // preparations before showing the window
1093 if (currentTabWidget() == m_transferListWidget)
1094 m_propertiesWidget->loadDynamicData();
1096 // Make sure the window is initially centered
1097 if (!m_posInitialized)
1099 move(Utils::Gui::screenCenter(this));
1100 m_posInitialized = true;
1103 else
1105 // to avoid blank screen when restoring from tray icon
1106 show();
1110 void MainWindow::keyPressEvent(QKeyEvent *event)
1112 if (event->matches(QKeySequence::Paste))
1114 const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData();
1116 if (mimeData->hasText())
1118 const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
1119 const QStringList lines = mimeData->text().split(u'\n', Qt::SkipEmptyParts);
1121 for (QString line : lines)
1123 line = line.trimmed();
1125 if (!isTorrentLink(line))
1126 continue;
1128 if (useTorrentAdditionDialog)
1129 AddNewTorrentDialog::show(line, this);
1130 else
1131 BitTorrent::Session::instance()->addTorrent(line);
1134 return;
1138 QMainWindow::keyPressEvent(event);
1141 // Called when we close the program
1142 void MainWindow::closeEvent(QCloseEvent *e)
1144 Preferences *const pref = Preferences::instance();
1145 #ifdef Q_OS_MACOS
1146 if (!m_forceExit)
1148 hide();
1149 e->accept();
1150 return;
1152 #else
1153 const bool goToSystrayOnExit = pref->closeToTray();
1154 if (!m_forceExit && app()->desktopIntegration()->isActive() && goToSystrayOnExit && !this->isHidden())
1156 e->ignore();
1157 QMetaObject::invokeMethod(this, &QWidget::hide, Qt::QueuedConnection);
1158 if (!pref->closeToTrayNotified())
1160 app()->desktopIntegration()->showNotification(tr("qBittorrent is closed to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
1161 pref->setCloseToTrayNotified(true);
1163 return;
1165 #endif // Q_OS_MACOS
1167 const QVector<BitTorrent::Torrent *> allTorrents = BitTorrent::Session::instance()->torrents();
1168 const bool hasActiveTorrents = std::any_of(allTorrents.cbegin(), allTorrents.cend(), [](BitTorrent::Torrent *torrent)
1170 return torrent->isActive();
1172 if (pref->confirmOnExit() && hasActiveTorrents)
1174 if (e->spontaneous() || m_forceExit)
1176 if (!isVisible())
1177 show();
1178 QMessageBox confirmBox(QMessageBox::Question, tr("Exiting qBittorrent"),
1179 // Split it because the last sentence is used in the Web UI
1180 tr("Some files are currently transferring.") + u'\n' + tr("Are you sure you want to quit qBittorrent?"),
1181 QMessageBox::NoButton, this);
1182 QPushButton *noBtn = confirmBox.addButton(tr("&No"), QMessageBox::NoRole);
1183 confirmBox.addButton(tr("&Yes"), QMessageBox::YesRole);
1184 QPushButton *alwaysBtn = confirmBox.addButton(tr("&Always Yes"), QMessageBox::YesRole);
1185 confirmBox.setDefaultButton(noBtn);
1186 confirmBox.exec();
1187 if (!confirmBox.clickedButton() || (confirmBox.clickedButton() == noBtn))
1189 // Cancel exit
1190 e->ignore();
1191 m_forceExit = false;
1192 return;
1194 if (confirmBox.clickedButton() == alwaysBtn)
1195 // Remember choice
1196 Preferences::instance()->setConfirmOnExit(false);
1200 // Accept exit
1201 e->accept();
1202 qApp->exit();
1205 // Display window to create a torrent
1206 void MainWindow::on_actionCreateTorrent_triggered()
1208 createTorrentTriggered({});
1211 void MainWindow::createTorrentTriggered(const Path &path)
1213 if (m_createTorrentDlg)
1215 m_createTorrentDlg->updateInputPath(path);
1216 m_createTorrentDlg->activateWindow();
1218 else
1220 m_createTorrentDlg = new TorrentCreatorDialog(this, path);
1221 m_createTorrentDlg->setAttribute(Qt::WA_DeleteOnClose);
1222 m_createTorrentDlg->show();
1226 bool MainWindow::event(QEvent *e)
1228 #ifndef Q_OS_MACOS
1229 switch (e->type())
1231 case QEvent::WindowStateChange:
1232 qDebug("Window change event");
1233 // Now check to see if the window is minimised
1234 if (isMinimized())
1236 qDebug("minimisation");
1237 Preferences *const pref = Preferences::instance();
1238 if (app()->desktopIntegration()->isActive() && pref->minimizeToTray())
1240 qDebug() << "Has active window:" << (qApp->activeWindow() != nullptr);
1241 // Check if there is a modal window
1242 const QWidgetList allWidgets = QApplication::allWidgets();
1243 const bool hasModalWindow = std::any_of(allWidgets.cbegin(), allWidgets.cend()
1244 , [](const QWidget *widget) { return widget->isModal(); });
1245 // Iconify if there is no modal window
1246 if (!hasModalWindow)
1248 qDebug("Minimize to Tray enabled, hiding!");
1249 e->ignore();
1250 QMetaObject::invokeMethod(this, &QWidget::hide, Qt::QueuedConnection);
1251 if (!pref->minimizeToTrayNotified())
1253 app()->desktopIntegration()->showNotification(tr("qBittorrent is minimized to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
1254 pref->setMinimizeToTrayNotified(true);
1256 return true;
1260 break;
1261 case QEvent::ToolBarChange:
1263 qDebug("MAC: Received a toolbar change event!");
1264 const bool ret = QMainWindow::event(e);
1266 qDebug("MAC: new toolbar visibility is %d", !m_ui->actionTopToolBar->isChecked());
1267 m_ui->actionTopToolBar->toggle();
1268 Preferences::instance()->setToolbarDisplayed(m_ui->actionTopToolBar->isChecked());
1269 return ret;
1271 default:
1272 break;
1274 #endif // Q_OS_MACOS
1276 return QMainWindow::event(e);
1279 // action executed when a file is dropped
1280 void MainWindow::dropEvent(QDropEvent *event)
1282 event->acceptProposedAction();
1284 // remove scheme
1285 QStringList files;
1286 if (event->mimeData()->hasUrls())
1288 for (const QUrl &url : asConst(event->mimeData()->urls()))
1290 if (url.isEmpty())
1291 continue;
1293 files << ((url.scheme().compare(u"file", Qt::CaseInsensitive) == 0)
1294 ? url.toLocalFile()
1295 : url.toString());
1298 else
1300 files = event->mimeData()->text().split(u'\n');
1303 // differentiate ".torrent" files/links & magnet links from others
1304 QStringList torrentFiles, otherFiles;
1305 for (const QString &file : asConst(files))
1307 if (isTorrentLink(file))
1308 torrentFiles << file;
1309 else
1310 otherFiles << file;
1313 // Download torrents
1314 const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
1315 for (const QString &file : asConst(torrentFiles))
1317 if (useTorrentAdditionDialog)
1318 AddNewTorrentDialog::show(file, this);
1319 else
1320 BitTorrent::Session::instance()->addTorrent(file);
1322 if (!torrentFiles.isEmpty()) return;
1324 // Create torrent
1325 for (const QString &file : asConst(otherFiles))
1327 createTorrentTriggered(Path(file));
1329 // currently only handle the first entry
1330 // this is a stub that can be expanded later to create many torrents at once
1331 break;
1335 // Decode if we accept drag 'n drop or not
1336 void MainWindow::dragEnterEvent(QDragEnterEvent *event)
1338 for (const QString &mime : asConst(event->mimeData()->formats()))
1339 qDebug("mimeData: %s", mime.toLocal8Bit().data());
1341 if (event->mimeData()->hasFormat(u"text/plain"_s) || event->mimeData()->hasFormat(u"text/uri-list"_s))
1342 event->acceptProposedAction();
1345 /*****************************************************
1347 * Torrent *
1349 *****************************************************/
1351 // Display a dialog to allow user to add
1352 // torrents to download list
1353 void MainWindow::on_actionOpen_triggered()
1355 Preferences *const pref = Preferences::instance();
1356 // Open File Open Dialog
1357 // Note: it is possible to select more than one file
1358 const QStringList pathsList =
1359 QFileDialog::getOpenFileNames(this, tr("Open Torrent Files"), pref->getMainLastDir().data(),
1360 tr("Torrent Files") + u" (*" + TORRENT_FILE_EXTENSION + u')');
1362 if (pathsList.isEmpty())
1363 return;
1365 const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
1367 for (const QString &file : pathsList)
1369 if (useTorrentAdditionDialog)
1370 AddNewTorrentDialog::show(file, this);
1371 else
1372 BitTorrent::Session::instance()->addTorrent(file);
1375 // Save last dir to remember it
1376 const Path topDir {pathsList.at(0)};
1377 const Path parentDir = topDir.parentPath();
1378 pref->setMainLastDir(parentDir.isEmpty() ? topDir : parentDir);
1381 void MainWindow::activate()
1383 if (!m_uiLocked || unlockUI())
1385 show();
1386 activateWindow();
1387 raise();
1391 void MainWindow::optionsSaved()
1393 LogMsg(tr("Options saved."));
1394 loadPreferences();
1397 void MainWindow::showStatusBar(bool show)
1399 if (!show)
1401 // Remove status bar
1402 setStatusBar(nullptr);
1404 else if (!m_statusBar)
1406 // Create status bar
1407 m_statusBar = new StatusBar;
1408 connect(m_statusBar.data(), &StatusBar::connectionButtonClicked, this, &MainWindow::showConnectionSettings);
1409 connect(m_statusBar.data(), &StatusBar::alternativeSpeedsButtonClicked, this, &MainWindow::toggleAlternativeSpeeds);
1410 setStatusBar(m_statusBar);
1414 void MainWindow::showFiltersSidebar(const bool show)
1416 if (show && !m_transferListFiltersWidget)
1418 m_transferListFiltersWidget = new TransferListFiltersWidget(m_splitter, m_transferListWidget, isDownloadTrackerFavicon());
1419 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersAdded, m_transferListFiltersWidget, &TransferListFiltersWidget::addTrackers);
1420 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersRemoved, m_transferListFiltersWidget, &TransferListFiltersWidget::removeTrackers);
1421 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersChanged, m_transferListFiltersWidget, &TransferListFiltersWidget::refreshTrackers);
1422 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerlessStateChanged, m_transferListFiltersWidget, &TransferListFiltersWidget::changeTrackerless);
1423 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerEntriesUpdated, m_transferListFiltersWidget, &TransferListFiltersWidget::trackerEntriesUpdated);
1425 m_splitter->insertWidget(0, m_transferListFiltersWidget);
1426 m_splitter->setCollapsible(0, true);
1427 // From https://doc.qt.io/qt-5/qsplitter.html#setSizes:
1428 // Instead, any additional/missing space is distributed amongst the widgets
1429 // according to the relative weight of the sizes.
1430 m_splitter->setStretchFactor(0, 0);
1431 m_splitter->setStretchFactor(1, 1);
1432 m_splitter->setSizes({Preferences::instance()->getFiltersSidebarWidth()});
1434 else if (!show && m_transferListFiltersWidget)
1436 saveSplitterSettings();
1437 delete m_transferListFiltersWidget;
1438 m_transferListFiltersWidget = nullptr;
1442 void MainWindow::loadPreferences()
1444 const Preferences *pref = Preferences::instance();
1446 // General
1447 if (pref->isToolbarDisplayed())
1449 m_ui->toolBar->setVisible(true);
1451 else
1453 // Clear search filter before hiding the top toolbar
1454 m_columnFilterEdit->clear();
1455 m_ui->toolBar->setVisible(false);
1458 showStatusBar(pref->isStatusbarDisplayed());
1460 updatePowerManagementState();
1462 m_transferListWidget->setAlternatingRowColors(pref->useAlternatingRowColors());
1463 m_propertiesWidget->getFilesList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1464 m_propertiesWidget->getTrackerList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1465 m_propertiesWidget->getPeerList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1467 // Queueing System
1468 if (BitTorrent::Session::instance()->isQueueingSystemEnabled())
1470 if (!m_ui->actionDecreaseQueuePos->isVisible())
1472 m_transferListWidget->hideQueuePosColumn(false);
1473 m_ui->actionDecreaseQueuePos->setVisible(true);
1474 m_ui->actionIncreaseQueuePos->setVisible(true);
1475 m_ui->actionTopQueuePos->setVisible(true);
1476 m_ui->actionBottomQueuePos->setVisible(true);
1477 #ifndef Q_OS_MACOS
1478 m_queueSeparator->setVisible(true);
1479 #endif
1480 m_queueSeparatorMenu->setVisible(true);
1483 else
1485 if (m_ui->actionDecreaseQueuePos->isVisible())
1487 m_transferListWidget->hideQueuePosColumn(true);
1488 m_ui->actionDecreaseQueuePos->setVisible(false);
1489 m_ui->actionIncreaseQueuePos->setVisible(false);
1490 m_ui->actionTopQueuePos->setVisible(false);
1491 m_ui->actionBottomQueuePos->setVisible(false);
1492 #ifndef Q_OS_MACOS
1493 m_queueSeparator->setVisible(false);
1494 #endif
1495 m_queueSeparatorMenu->setVisible(false);
1499 // Torrent properties
1500 m_propertiesWidget->reloadPreferences();
1502 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1503 if (pref->isUpdateCheckEnabled())
1505 if (!m_programUpdateTimer)
1507 m_programUpdateTimer = new QTimer(this);
1508 m_programUpdateTimer->setInterval(24h);
1509 m_programUpdateTimer->setSingleShot(true);
1510 connect(m_programUpdateTimer, &QTimer::timeout, this, [this]() { checkProgramUpdate(false); });
1511 m_programUpdateTimer->start();
1514 else
1516 delete m_programUpdateTimer;
1517 m_programUpdateTimer = nullptr;
1519 #endif
1521 qDebug("GUI settings loaded");
1524 void MainWindow::reloadSessionStats()
1526 const BitTorrent::SessionStatus &status = BitTorrent::Session::instance()->status();
1528 // update global information
1529 #ifdef Q_OS_MACOS
1530 if (status.payloadDownloadRate > 0)
1532 MacUtils::setBadgeLabelText(tr("%1/s", "s is a shorthand for seconds")
1533 .arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate)));
1535 else if (!MacUtils::badgeLabelText().isEmpty())
1537 MacUtils::setBadgeLabelText({});
1539 #else
1540 const auto toolTip = u"%1\n%2"_s.arg(
1541 tr("DL speed: %1", "e.g: Download speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true))
1542 , tr("UP speed: %1", "e.g: Upload speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadUploadRate, true)));
1543 app()->desktopIntegration()->setToolTip(toolTip); // tray icon
1544 #endif // Q_OS_MACOS
1546 if (m_displaySpeedInTitle)
1548 setWindowTitle(tr("[D: %1, U: %2] qBittorrent %3", "D = Download; U = Upload; %3 is qBittorrent version")
1549 .arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true)
1550 , Utils::Misc::friendlyUnit(status.payloadUploadRate, true)
1551 , QStringLiteral(QBT_VERSION)));
1555 void MainWindow::reloadTorrentStats(const QVector<BitTorrent::Torrent *> &torrents)
1557 if (currentTabWidget() == m_transferListWidget)
1559 if (torrents.contains(m_propertiesWidget->getCurrentTorrent()))
1560 m_propertiesWidget->loadDynamicData();
1564 /*****************************************************
1566 * Utils *
1568 *****************************************************/
1570 void MainWindow::downloadFromURLList(const QStringList &urlList)
1572 const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
1573 for (const QString &url : urlList)
1575 if (useTorrentAdditionDialog)
1576 AddNewTorrentDialog::show(url, this);
1577 else
1578 BitTorrent::Session::instance()->addTorrent(url);
1582 /*****************************************************
1584 * Options *
1586 *****************************************************/
1588 QMenu *MainWindow::createDesktopIntegrationMenu()
1590 auto *menu = new QMenu;
1592 #ifndef Q_OS_MACOS
1593 connect(menu, &QMenu::aboutToShow, this, [this]()
1595 m_ui->actionToggleVisibility->setText(isVisible() ? tr("Hide") : tr("Show"));
1598 menu->addAction(m_ui->actionToggleVisibility);
1599 menu->addSeparator();
1600 #endif
1602 menu->addAction(m_ui->actionOpen);
1603 menu->addAction(m_ui->actionDownloadFromURL);
1604 menu->addSeparator();
1606 menu->addAction(m_ui->actionUseAlternativeSpeedLimits);
1607 menu->addAction(m_ui->actionSetGlobalSpeedLimits);
1608 menu->addSeparator();
1610 menu->addAction(m_ui->actionStartAll);
1611 menu->addAction(m_ui->actionPauseAll);
1613 #ifndef Q_OS_MACOS
1614 menu->addSeparator();
1615 menu->addAction(m_ui->actionExit);
1616 #endif
1618 if (m_uiLocked)
1619 menu->setEnabled(false);
1621 return menu;
1624 void MainWindow::updateAltSpeedsBtn(const bool alternative)
1626 m_ui->actionUseAlternativeSpeedLimits->setChecked(alternative);
1629 PropertiesWidget *MainWindow::propertiesWidget() const
1631 return m_propertiesWidget;
1634 // Display Program Options
1635 void MainWindow::on_actionOptions_triggered()
1637 if (m_options)
1639 m_options->activateWindow();
1641 else
1643 m_options = new OptionsDialog(app(), this);
1644 m_options->setAttribute(Qt::WA_DeleteOnClose);
1645 m_options->open();
1649 void MainWindow::on_actionTopToolBar_triggered()
1651 const bool isVisible = static_cast<QAction *>(sender())->isChecked();
1652 m_ui->toolBar->setVisible(isVisible);
1653 Preferences::instance()->setToolbarDisplayed(isVisible);
1656 void MainWindow::on_actionShowStatusbar_triggered()
1658 const bool isVisible = static_cast<QAction *>(sender())->isChecked();
1659 Preferences::instance()->setStatusbarDisplayed(isVisible);
1660 showStatusBar(isVisible);
1663 void MainWindow::on_actionShowFiltersSidebar_triggered(const bool checked)
1665 Preferences *const pref = Preferences::instance();
1666 pref->setFiltersSidebarVisible(checked);
1667 showFiltersSidebar(checked);
1670 void MainWindow::on_actionSpeedInTitleBar_triggered()
1672 m_displaySpeedInTitle = static_cast<QAction *>(sender())->isChecked();
1673 Preferences::instance()->showSpeedInTitleBar(m_displaySpeedInTitle);
1674 if (m_displaySpeedInTitle)
1675 reloadSessionStats();
1676 else
1677 setWindowTitle(QStringLiteral("qBittorrent " QBT_VERSION));
1680 void MainWindow::on_actionRSSReader_triggered()
1682 Preferences::instance()->setRSSWidgetVisible(m_ui->actionRSSReader->isChecked());
1683 displayRSSTab(m_ui->actionRSSReader->isChecked());
1686 void MainWindow::on_actionSearchWidget_triggered()
1688 if (!m_hasPython && m_ui->actionSearchWidget->isChecked())
1690 const Utils::ForeignApps::PythonInfo pyInfo = Utils::ForeignApps::pythonInfo();
1692 // Not installed
1693 if (!pyInfo.isValid())
1695 m_ui->actionSearchWidget->setChecked(false);
1696 Preferences::instance()->setSearchEnabled(false);
1698 #ifdef Q_OS_WIN
1699 const QMessageBox::StandardButton buttonPressed = QMessageBox::question(this, tr("Missing Python Runtime")
1700 , tr("Python is required to use the search engine but it does not seem to be installed.\nDo you want to install it now?")
1701 , (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
1702 if (buttonPressed == QMessageBox::Yes)
1703 installPython();
1704 #else
1705 QMessageBox::information(this, tr("Missing Python Runtime")
1706 , tr("Python is required to use the search engine but it does not seem to be installed."));
1707 #endif
1708 return;
1711 // Check version requirement
1712 if (!pyInfo.isSupportedVersion())
1714 m_ui->actionSearchWidget->setChecked(false);
1715 Preferences::instance()->setSearchEnabled(false);
1717 #ifdef Q_OS_WIN
1718 const QMessageBox::StandardButton buttonPressed = QMessageBox::question(this, tr("Old Python Runtime")
1719 , tr("Your Python version (%1) is outdated. Minimum requirement: %2.\nDo you want to install a newer version now?")
1720 .arg(pyInfo.version.toString(), u"3.5.0")
1721 , (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
1722 if (buttonPressed == QMessageBox::Yes)
1723 installPython();
1724 #else
1725 QMessageBox::information(this, tr("Old Python Runtime")
1726 , tr("Your Python version (%1) is outdated. Please upgrade to latest version for search engines to work.\nMinimum requirement: %2.")
1727 .arg(pyInfo.version.toString(), u"3.5.0"));
1728 #endif
1729 return;
1732 m_hasPython = true;
1733 m_ui->actionSearchWidget->setChecked(true);
1734 Preferences::instance()->setSearchEnabled(true);
1737 displaySearchTab(m_ui->actionSearchWidget->isChecked());
1740 /*****************************************************
1742 * HTTP Downloader *
1744 *****************************************************/
1746 // Display an input dialog to prompt user for
1747 // an url
1748 void MainWindow::on_actionDownloadFromURL_triggered()
1750 if (!m_downloadFromURLDialog)
1752 m_downloadFromURLDialog = new DownloadFromURLDialog(this);
1753 m_downloadFromURLDialog->setAttribute(Qt::WA_DeleteOnClose);
1754 connect(m_downloadFromURLDialog.data(), &DownloadFromURLDialog::urlsReadyToBeDownloaded, this, &MainWindow::downloadFromURLList);
1755 m_downloadFromURLDialog->open();
1759 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1760 void MainWindow::handleUpdateCheckFinished(ProgramUpdater *updater, const bool invokedByUser)
1762 m_ui->actionCheckForUpdates->setEnabled(true);
1763 m_ui->actionCheckForUpdates->setText(tr("&Check for Updates"));
1764 m_ui->actionCheckForUpdates->setToolTip(tr("Check for program updates"));
1766 const auto cleanup = [this, updater]()
1768 if (m_programUpdateTimer)
1769 m_programUpdateTimer->start();
1770 updater->deleteLater();
1773 const QString newVersion = updater->getNewVersion();
1774 if (!newVersion.isEmpty())
1776 const QString msg {tr("A new version is available.") + u"<br/>"
1777 + tr("Do you want to download %1?").arg(newVersion) + u"<br/><br/>"
1778 + u"<a href=\"https://www.qbittorrent.org/news.php\">%1</a>"_s.arg(tr("Open changelog..."))};
1779 auto *msgBox = new QMessageBox {QMessageBox::Question, tr("qBittorrent Update Available"), msg
1780 , (QMessageBox::Yes | QMessageBox::No), this};
1781 msgBox->setAttribute(Qt::WA_DeleteOnClose);
1782 msgBox->setAttribute(Qt::WA_ShowWithoutActivating);
1783 msgBox->setDefaultButton(QMessageBox::Yes);
1784 msgBox->setWindowModality(Qt::NonModal);
1785 connect(msgBox, &QMessageBox::buttonClicked, this, [msgBox, updater](QAbstractButton *button)
1787 if (msgBox->buttonRole(button) == QMessageBox::YesRole)
1789 updater->updateProgram();
1792 connect(msgBox, &QDialog::finished, this, cleanup);
1793 msgBox->show();
1795 else
1797 if (invokedByUser)
1799 auto *msgBox = new QMessageBox {QMessageBox::Information, u"qBittorrent"_s
1800 , tr("No updates available.\nYou are already using the latest version.")
1801 , QMessageBox::Ok, this};
1802 msgBox->setAttribute(Qt::WA_DeleteOnClose);
1803 msgBox->setWindowModality(Qt::NonModal);
1804 connect(msgBox, &QDialog::finished, this, cleanup);
1805 msgBox->show();
1807 else
1809 cleanup();
1813 #endif
1815 void MainWindow::toggleAlternativeSpeeds()
1817 BitTorrent::Session *const session = BitTorrent::Session::instance();
1818 session->setAltGlobalSpeedLimitEnabled(!session->isAltGlobalSpeedLimitEnabled());
1821 void MainWindow::on_actionDonateMoney_triggered()
1823 QDesktopServices::openUrl(QUrl(u"https://www.qbittorrent.org/donate"_s));
1826 void MainWindow::showConnectionSettings()
1828 on_actionOptions_triggered();
1829 m_options->showConnectionTab();
1832 void MainWindow::minimizeWindow()
1834 setWindowState(windowState() | Qt::WindowMinimized);
1837 void MainWindow::on_actionExecutionLogs_triggered(bool checked)
1839 if (checked)
1841 Q_ASSERT(!m_executionLog);
1842 m_executionLog = new ExecutionLogWidget(executionLogMsgTypes(), m_tabs);
1843 #ifdef Q_OS_MACOS
1844 m_tabs->addTab(m_executionLog, tr("Execution Log"));
1845 #else
1846 const int indexTab = m_tabs->addTab(m_executionLog, tr("Execution Log"));
1847 m_tabs->setTabIcon(indexTab, UIThemeManager::instance()->getIcon(u"help-contents"_s));
1848 #endif
1850 else
1852 delete m_executionLog;
1855 m_ui->actionNormalMessages->setEnabled(checked);
1856 m_ui->actionInformationMessages->setEnabled(checked);
1857 m_ui->actionWarningMessages->setEnabled(checked);
1858 m_ui->actionCriticalMessages->setEnabled(checked);
1859 setExecutionLogEnabled(checked);
1862 void MainWindow::on_actionNormalMessages_triggered(const bool checked)
1864 if (!m_executionLog)
1865 return;
1867 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::NORMAL, checked);
1868 setExecutionLogMsgTypes(flags);
1871 void MainWindow::on_actionInformationMessages_triggered(const bool checked)
1873 if (!m_executionLog)
1874 return;
1876 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::INFO, checked);
1877 setExecutionLogMsgTypes(flags);
1880 void MainWindow::on_actionWarningMessages_triggered(const bool checked)
1882 if (!m_executionLog)
1883 return;
1885 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::WARNING, checked);
1886 setExecutionLogMsgTypes(flags);
1889 void MainWindow::on_actionCriticalMessages_triggered(const bool checked)
1891 if (!m_executionLog)
1892 return;
1894 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::CRITICAL, checked);
1895 setExecutionLogMsgTypes(flags);
1898 void MainWindow::on_actionAutoExit_toggled(bool enabled)
1900 qDebug() << Q_FUNC_INFO << enabled;
1901 Preferences::instance()->setShutdownqBTWhenDownloadsComplete(enabled);
1904 void MainWindow::on_actionAutoSuspend_toggled(bool enabled)
1906 qDebug() << Q_FUNC_INFO << enabled;
1907 Preferences::instance()->setSuspendWhenDownloadsComplete(enabled);
1910 void MainWindow::on_actionAutoHibernate_toggled(bool enabled)
1912 qDebug() << Q_FUNC_INFO << enabled;
1913 Preferences::instance()->setHibernateWhenDownloadsComplete(enabled);
1916 void MainWindow::on_actionAutoShutdown_toggled(bool enabled)
1918 qDebug() << Q_FUNC_INFO << enabled;
1919 Preferences::instance()->setShutdownWhenDownloadsComplete(enabled);
1922 void MainWindow::updatePowerManagementState()
1924 const bool preventFromSuspendWhenDownloading = Preferences::instance()->preventFromSuspendWhenDownloading();
1925 const bool preventFromSuspendWhenSeeding = Preferences::instance()->preventFromSuspendWhenSeeding();
1927 const QVector<BitTorrent::Torrent *> allTorrents = BitTorrent::Session::instance()->torrents();
1928 const bool inhibitSuspend = std::any_of(allTorrents.cbegin(), allTorrents.cend(), [&](const BitTorrent::Torrent *torrent)
1930 if (preventFromSuspendWhenDownloading && (!torrent->isFinished() && !torrent->isPaused() && !torrent->isErrored() && torrent->hasMetadata()))
1931 return true;
1933 if (preventFromSuspendWhenSeeding && (torrent->isFinished() && !torrent->isPaused()))
1934 return true;
1936 return torrent->isMoving();
1938 m_pwr->setActivityState(inhibitSuspend);
1941 void MainWindow::applyTransferListFilter()
1943 m_transferListWidget->applyFilter(m_columnFilterEdit->text(), m_columnFilterComboBox->currentData().value<TransferListModel::Column>());
1946 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1947 void MainWindow::checkProgramUpdate(const bool invokedByUser)
1949 if (m_programUpdateTimer)
1950 m_programUpdateTimer->stop();
1952 m_ui->actionCheckForUpdates->setEnabled(false);
1953 m_ui->actionCheckForUpdates->setText(tr("Checking for Updates..."));
1954 m_ui->actionCheckForUpdates->setToolTip(tr("Already checking for program updates in the background"));
1956 auto *updater = new ProgramUpdater(this);
1957 connect(updater, &ProgramUpdater::updateCheckFinished
1958 , this, [this, invokedByUser, updater]()
1960 handleUpdateCheckFinished(updater, invokedByUser);
1962 updater->checkForUpdates();
1964 #endif
1966 #ifdef Q_OS_WIN
1967 void MainWindow::installPython()
1969 setCursor(QCursor(Qt::WaitCursor));
1970 // Download python
1971 #ifdef QBT_APP_64BIT
1972 const auto installerURL = ::IsWindows8OrGreater()
1973 ? u"https://www.python.org/ftp/python/3.10.11/python-3.10.11-amd64.exe"_s
1974 : u"https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe"_s;
1975 #else
1976 const auto installerURL = ::IsWindows8OrGreater()
1977 ? u"https://www.python.org/ftp/python/3.10.11/python-3.10.11.exe"_s
1978 : u"https://www.python.org/ftp/python/3.8.10/python-3.8.10.exe"_s;
1979 #endif
1980 Net::DownloadManager::instance()->download(
1981 Net::DownloadRequest(installerURL).saveToFile(true)
1982 , Preferences::instance()->useProxyForGeneralPurposes()
1983 , this, &MainWindow::pythonDownloadFinished);
1986 void MainWindow::pythonDownloadFinished(const Net::DownloadResult &result)
1988 if (result.status != Net::DownloadStatus::Success)
1990 setCursor(QCursor(Qt::ArrowCursor));
1991 QMessageBox::warning(
1992 this, tr("Download error")
1993 , tr("Python setup could not be downloaded, reason: %1.\nPlease install it manually.")
1994 .arg(result.errorString));
1995 return;
1998 setCursor(QCursor(Qt::ArrowCursor));
1999 QProcess installer;
2000 qDebug("Launching Python installer in passive mode...");
2002 const Path exePath = result.filePath + u".exe";
2003 Utils::Fs::renameFile(result.filePath, exePath);
2004 installer.start(exePath.toString(), {u"/passive"_s});
2006 // Wait for setup to complete
2007 installer.waitForFinished(10 * 60 * 1000);
2009 qDebug("Installer stdout: %s", installer.readAllStandardOutput().data());
2010 qDebug("Installer stderr: %s", installer.readAllStandardError().data());
2011 qDebug("Setup should be complete!");
2013 // Delete temp file
2014 Utils::Fs::removeFile(exePath);
2016 // Reload search engine
2017 if (Utils::ForeignApps::pythonInfo().isSupportedVersion())
2019 m_ui->actionSearchWidget->setChecked(true);
2020 displaySearchTab(true);
2023 #endif // Q_OS_WIN