Use tray icon from system theme only if option is set
[qBittorrent.git] / src / gui / mainwindow.cpp
blobd9e7dab1c93e85de7639203fd3ab7a6e0756a59b
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 <algorithm>
33 #include <chrono>
35 #include <QActionGroup>
36 #include <QClipboard>
37 #include <QCloseEvent>
38 #include <QComboBox>
39 #include <QDebug>
40 #include <QDesktopServices>
41 #include <QFileDialog>
42 #include <QFileSystemWatcher>
43 #include <QKeyEvent>
44 #include <QLabel>
45 #include <QMessageBox>
46 #include <QMetaObject>
47 #include <QMimeData>
48 #include <QProcess>
49 #include <QPushButton>
50 #include <QShortcut>
51 #include <QSplitter>
52 #include <QStatusBar>
53 #include <QtGlobal>
54 #include <QTimer>
56 #include "base/bittorrent/session.h"
57 #include "base/bittorrent/sessionstatus.h"
58 #include "base/global.h"
59 #include "base/net/downloadmanager.h"
60 #include "base/path.h"
61 #include "base/preferences.h"
62 #include "base/rss/rss_folder.h"
63 #include "base/rss/rss_session.h"
64 #include "base/utils/foreignapps.h"
65 #include "base/utils/fs.h"
66 #include "base/utils/misc.h"
67 #include "base/utils/password.h"
68 #include "base/version.h"
69 #include "aboutdialog.h"
70 #include "addnewtorrentdialog.h"
71 #include "autoexpandabledialog.h"
72 #include "cookiesdialog.h"
73 #include "desktopintegration.h"
74 #include "downloadfromurldialog.h"
75 #include "executionlogwidget.h"
76 #include "hidabletabwidget.h"
77 #include "lineedit.h"
78 #include "optionsdialog.h"
79 #include "powermanagement/powermanagement.h"
80 #include "properties/peerlistwidget.h"
81 #include "properties/propertieswidget.h"
82 #include "properties/trackerlistwidget.h"
83 #include "rss/rsswidget.h"
84 #include "search/searchwidget.h"
85 #include "speedlimitdialog.h"
86 #include "statsdialog.h"
87 #include "statusbar.h"
88 #include "torrentcreatordialog.h"
89 #include "transferlistfilterswidget.h"
90 #include "transferlistmodel.h"
91 #include "transferlistwidget.h"
92 #include "ui_mainwindow.h"
93 #include "uithememanager.h"
94 #include "utils.h"
96 #ifdef Q_OS_MACOS
97 #include "macutilities.h"
98 #endif
99 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
100 #include "programupdater.h"
101 #endif
103 using namespace std::chrono_literals;
105 namespace
107 #define SETTINGS_KEY(name) u"GUI/" name
108 #define EXECUTIONLOG_SETTINGS_KEY(name) (SETTINGS_KEY(u"Log/"_qs) name)
110 const std::chrono::seconds PREVENT_SUSPEND_INTERVAL {60};
112 bool isTorrentLink(const QString &str)
114 return str.startsWith(u"magnet:", Qt::CaseInsensitive)
115 || str.endsWith(TORRENT_FILE_EXTENSION, Qt::CaseInsensitive)
116 || (!str.startsWith(u"file:", Qt::CaseInsensitive)
117 && Net::DownloadManager::hasSupportedScheme(str));
121 MainWindow::MainWindow(IGUIApplication *app, WindowState initialState)
122 : GUIApplicationComponent(app)
123 , m_ui(new Ui::MainWindow)
124 , m_storeExecutionLogEnabled(EXECUTIONLOG_SETTINGS_KEY(u"Enabled"_qs))
125 , m_storeDownloadTrackerFavicon(SETTINGS_KEY(u"DownloadTrackerFavicon"_qs))
126 , m_storeExecutionLogTypes(EXECUTIONLOG_SETTINGS_KEY(u"Types"_qs), Log::MsgType::ALL)
128 m_ui->setupUi(this);
130 Preferences *const pref = Preferences::instance();
131 m_uiLocked = pref->isUILocked();
132 setWindowTitle(QStringLiteral("qBittorrent " QBT_VERSION));
133 m_displaySpeedInTitle = pref->speedInTitleBar();
134 // Setting icons
135 #ifndef Q_OS_MACOS
136 setWindowIcon(UIThemeManager::instance()->getIcon(u"qbittorrent"_qs));
137 #endif // Q_OS_MACOS
139 #if (defined(Q_OS_UNIX))
140 m_ui->actionOptions->setText(tr("Preferences"));
141 #endif
143 addToolbarContextMenu();
145 m_ui->actionOpen->setIcon(UIThemeManager::instance()->getIcon(u"list-add"_qs));
146 m_ui->actionDownloadFromURL->setIcon(UIThemeManager::instance()->getIcon(u"insert-link"_qs));
147 m_ui->actionSetGlobalSpeedLimits->setIcon(UIThemeManager::instance()->getIcon(u"speedometer"_qs));
148 m_ui->actionCreateTorrent->setIcon(UIThemeManager::instance()->getIcon(u"torrent-creator"_qs, u"document-edit"_qs));
149 m_ui->actionAbout->setIcon(UIThemeManager::instance()->getIcon(u"help-about"_qs));
150 m_ui->actionStatistics->setIcon(UIThemeManager::instance()->getIcon(u"view-statistics"_qs));
151 m_ui->actionTopQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-top"_qs));
152 m_ui->actionIncreaseQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-up"_qs));
153 m_ui->actionDecreaseQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-down"_qs));
154 m_ui->actionBottomQueuePos->setIcon(UIThemeManager::instance()->getIcon(u"go-bottom"_qs));
155 m_ui->actionDelete->setIcon(UIThemeManager::instance()->getIcon(u"list-remove"_qs));
156 m_ui->actionDocumentation->setIcon(UIThemeManager::instance()->getIcon(u"help-contents"_qs));
157 m_ui->actionDonateMoney->setIcon(UIThemeManager::instance()->getIcon(u"wallet-open"_qs));
158 m_ui->actionExit->setIcon(UIThemeManager::instance()->getIcon(u"application-exit"_qs));
159 m_ui->actionLock->setIcon(UIThemeManager::instance()->getIcon(u"object-locked"_qs));
160 m_ui->actionOptions->setIcon(UIThemeManager::instance()->getIcon(u"configure"_qs, u"preferences-system"_qs));
161 m_ui->actionPause->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs));
162 m_ui->actionPauseAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-stop"_qs, u"media-playback-pause"_qs));
163 m_ui->actionStart->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs));
164 m_ui->actionStartAll->setIcon(UIThemeManager::instance()->getIcon(u"torrent-start"_qs, u"media-playback-start"_qs));
165 m_ui->menuAutoShutdownOnDownloadsCompletion->setIcon(UIThemeManager::instance()->getIcon(u"task-complete"_qs, u"application-exit"_qs));
166 m_ui->actionManageCookies->setIcon(UIThemeManager::instance()->getIcon(u"browser-cookies"_qs, u"preferences-web-browser-cookies"_qs));
167 m_ui->menuLog->setIcon(UIThemeManager::instance()->getIcon(u"help-contents"_qs));
168 m_ui->actionCheckForUpdates->setIcon(UIThemeManager::instance()->getIcon(u"view-refresh"_qs));
170 auto *lockMenu = new QMenu(this);
171 lockMenu->addAction(tr("&Set Password"), this, &MainWindow::defineUILockPassword);
172 lockMenu->addAction(tr("&Clear Password"), this, &MainWindow::clearUILockPassword);
173 m_ui->actionLock->setMenu(lockMenu);
175 // Creating Bittorrent session
176 updateAltSpeedsBtn(BitTorrent::Session::instance()->isAltGlobalSpeedLimitEnabled());
178 connect(BitTorrent::Session::instance(), &BitTorrent::Session::speedLimitModeChanged, this, &MainWindow::updateAltSpeedsBtn);
179 connect(BitTorrent::Session::instance(), &BitTorrent::Session::recursiveTorrentDownloadPossible, this, &MainWindow::askRecursiveTorrentDownloadConfirmation);
181 qDebug("create tabWidget");
182 m_tabs = new HidableTabWidget(this);
183 connect(m_tabs.data(), &QTabWidget::currentChanged, this, &MainWindow::tabChanged);
185 m_splitter = new QSplitter(Qt::Horizontal, this);
186 // vSplitter->setChildrenCollapsible(false);
188 auto *hSplitter = new QSplitter(Qt::Vertical, this);
189 hSplitter->setChildrenCollapsible(false);
190 hSplitter->setFrameShape(QFrame::NoFrame);
192 // Torrent filter
193 m_columnFilterEdit = new LineEdit;
194 m_columnFilterEdit->setPlaceholderText(tr("Filter torrents..."));
195 m_columnFilterEdit->setFixedWidth(200);
196 m_columnFilterEdit->setContextMenuPolicy(Qt::CustomContextMenu);
197 connect(m_columnFilterEdit, &QWidget::customContextMenuRequested, this, &MainWindow::showFilterContextMenu);
198 auto *columnFilterLabel = new QLabel(tr("Filter by:"));
199 m_columnFilterComboBox = new QComboBox;
200 QHBoxLayout *columnFilterLayout = new QHBoxLayout(m_columnFilterWidget);
201 columnFilterLayout->setContentsMargins(0, 0, 0, 0);
202 auto *columnFilterSpacer = new QWidget(this);
203 columnFilterSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
204 columnFilterLayout->addWidget(columnFilterSpacer);
205 columnFilterLayout->addWidget(m_columnFilterEdit);
206 columnFilterLayout->addWidget(columnFilterLabel, 0);
207 columnFilterLayout->addWidget(m_columnFilterComboBox, 0);
208 m_columnFilterWidget = new QWidget(this);
209 m_columnFilterWidget->setLayout(columnFilterLayout);
210 m_columnFilterAction = m_ui->toolBar->insertWidget(m_ui->actionLock, m_columnFilterWidget);
212 auto *spacer = new QWidget(this);
213 spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
214 m_ui->toolBar->insertWidget(m_columnFilterAction, spacer);
216 // Transfer List tab
217 m_transferListWidget = new TransferListWidget(hSplitter, this);
218 // m_transferListWidget->setStyleSheet("QTreeView {border: none;}"); // borderless
219 m_propertiesWidget = new PropertiesWidget(hSplitter);
220 connect(m_transferListWidget, &TransferListWidget::currentTorrentChanged, m_propertiesWidget, &PropertiesWidget::loadTorrentInfos);
221 hSplitter->addWidget(m_transferListWidget);
222 hSplitter->addWidget(m_propertiesWidget);
223 m_splitter->addWidget(hSplitter);
224 m_splitter->setCollapsible(0, false);
225 m_tabs->addTab(m_splitter,
226 #ifndef Q_OS_MACOS
227 UIThemeManager::instance()->getIcon(u"folder-remote"_qs),
228 #endif
229 tr("Transfers"));
230 // Filter types
231 const QVector<TransferListModel::Column> filterTypes = {TransferListModel::Column::TR_NAME, TransferListModel::Column::TR_SAVE_PATH};
232 for (const TransferListModel::Column type : filterTypes)
234 const QString typeName = m_transferListWidget->getSourceModel()->headerData(type, Qt::Horizontal, Qt::DisplayRole).value<QString>();
235 m_columnFilterComboBox->addItem(typeName, type);
237 connect(m_columnFilterComboBox, &QComboBox::currentIndexChanged, this, &MainWindow::applyTransferListFilter);
238 connect(m_columnFilterEdit, &LineEdit::textChanged, this, &MainWindow::applyTransferListFilter);
239 connect(hSplitter, &QSplitter::splitterMoved, this, &MainWindow::saveSettings);
240 connect(m_splitter, &QSplitter::splitterMoved, this, &MainWindow::saveSplitterSettings);
241 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersChanged, m_propertiesWidget, &PropertiesWidget::loadTrackers);
243 #ifdef Q_OS_MACOS
244 // Increase top spacing to avoid tab overlapping
245 m_ui->centralWidgetLayout->addSpacing(8);
246 #endif
248 m_ui->centralWidgetLayout->addWidget(m_tabs);
250 m_queueSeparator = m_ui->toolBar->insertSeparator(m_ui->actionTopQueuePos);
251 m_queueSeparatorMenu = m_ui->menuEdit->insertSeparator(m_ui->actionTopQueuePos);
253 #ifdef Q_OS_MACOS
254 for (QAction *action : asConst(m_ui->toolBar->actions()))
256 if (action->isSeparator())
258 QWidget *spacer = new QWidget(this);
259 spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
260 spacer->setMinimumWidth(16);
261 m_ui->toolBar->insertWidget(action, spacer);
262 m_ui->toolBar->removeAction(action);
266 QWidget *spacer = new QWidget(this);
267 spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
268 spacer->setMinimumWidth(8);
269 m_ui->toolBar->insertWidget(m_ui->actionDownloadFromURL, spacer);
272 QWidget *spacer = new QWidget(this);
273 spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
274 spacer->setMinimumWidth(8);
275 m_ui->toolBar->addWidget(spacer);
277 #endif // Q_OS_MACOS
279 // Transfer list slots
280 connect(m_ui->actionStart, &QAction::triggered, m_transferListWidget, &TransferListWidget::startSelectedTorrents);
281 connect(m_ui->actionStartAll, &QAction::triggered, m_transferListWidget, &TransferListWidget::resumeAllTorrents);
282 connect(m_ui->actionPause, &QAction::triggered, m_transferListWidget, &TransferListWidget::pauseSelectedTorrents);
283 connect(m_ui->actionPauseAll, &QAction::triggered, m_transferListWidget, &TransferListWidget::pauseAllTorrents);
284 connect(m_ui->actionDelete, &QAction::triggered, m_transferListWidget, &TransferListWidget::softDeleteSelectedTorrents);
285 connect(m_ui->actionTopQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::topQueuePosSelectedTorrents);
286 connect(m_ui->actionIncreaseQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::increaseQueuePosSelectedTorrents);
287 connect(m_ui->actionDecreaseQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::decreaseQueuePosSelectedTorrents);
288 connect(m_ui->actionBottomQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::bottomQueuePosSelectedTorrents);
289 #ifndef Q_OS_MACOS
290 connect(m_ui->actionToggleVisibility, &QAction::triggered, this, &MainWindow::toggleVisibility);
291 #endif
292 connect(m_ui->actionMinimize, &QAction::triggered, this, &MainWindow::minimizeWindow);
293 connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds);
295 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
296 connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, [this]() { checkProgramUpdate(true); });
298 // trigger an early check on startup
299 if (pref->isUpdateCheckEnabled())
300 checkProgramUpdate(false);
301 #else
302 m_ui->actionCheckForUpdates->setVisible(false);
303 #endif
305 // Certain menu items should reside at specific places on macOS.
306 // Qt partially does it on its own, but updates and different languages require tuning.
307 m_ui->actionExit->setMenuRole(QAction::QuitRole);
308 m_ui->actionAbout->setMenuRole(QAction::AboutRole);
309 m_ui->actionCheckForUpdates->setMenuRole(QAction::ApplicationSpecificRole);
310 m_ui->actionOptions->setMenuRole(QAction::PreferencesRole);
312 connect(m_ui->actionManageCookies, &QAction::triggered, this, &MainWindow::manageCookies);
314 m_pwr = new PowerManagement(this);
315 m_preventTimer = new QTimer(this);
316 connect(m_preventTimer, &QTimer::timeout, this, &MainWindow::updatePowerManagementState);
318 // Configure BT session according to options
319 loadPreferences();
321 connect(BitTorrent::Session::instance(), &BitTorrent::Session::statsUpdated, this, &MainWindow::reloadSessionStats);
322 connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsUpdated, this, &MainWindow::reloadTorrentStats);
324 // Accept drag 'n drops
325 setAcceptDrops(true);
326 createKeyboardShortcuts();
328 #ifdef Q_OS_MACOS
329 setUnifiedTitleAndToolBarOnMac(true);
330 #endif
332 // View settings
333 m_ui->actionTopToolBar->setChecked(pref->isToolbarDisplayed());
334 m_ui->actionShowStatusbar->setChecked(pref->isStatusbarDisplayed());
335 m_ui->actionSpeedInTitleBar->setChecked(pref->speedInTitleBar());
336 m_ui->actionRSSReader->setChecked(pref->isRSSWidgetEnabled());
337 m_ui->actionSearchWidget->setChecked(pref->isSearchEnabled());
338 m_ui->actionExecutionLogs->setChecked(isExecutionLogEnabled());
340 const Log::MsgTypes flags = executionLogMsgTypes();
341 m_ui->actionNormalMessages->setChecked(flags.testFlag(Log::NORMAL));
342 m_ui->actionInformationMessages->setChecked(flags.testFlag(Log::INFO));
343 m_ui->actionWarningMessages->setChecked(flags.testFlag(Log::WARNING));
344 m_ui->actionCriticalMessages->setChecked(flags.testFlag(Log::CRITICAL));
346 displayRSSTab(m_ui->actionRSSReader->isChecked());
347 on_actionExecutionLogs_triggered(m_ui->actionExecutionLogs->isChecked());
348 on_actionNormalMessages_triggered(m_ui->actionNormalMessages->isChecked());
349 on_actionInformationMessages_triggered(m_ui->actionInformationMessages->isChecked());
350 on_actionWarningMessages_triggered(m_ui->actionWarningMessages->isChecked());
351 on_actionCriticalMessages_triggered(m_ui->actionCriticalMessages->isChecked());
352 if (m_ui->actionSearchWidget->isChecked())
353 QMetaObject::invokeMethod(this, &MainWindow::on_actionSearchWidget_triggered, Qt::QueuedConnection);
354 // Auto shutdown actions
355 auto *autoShutdownGroup = new QActionGroup(this);
356 autoShutdownGroup->setExclusive(true);
357 autoShutdownGroup->addAction(m_ui->actionAutoShutdownDisabled);
358 autoShutdownGroup->addAction(m_ui->actionAutoExit);
359 autoShutdownGroup->addAction(m_ui->actionAutoShutdown);
360 autoShutdownGroup->addAction(m_ui->actionAutoSuspend);
361 autoShutdownGroup->addAction(m_ui->actionAutoHibernate);
362 #if (!defined(Q_OS_UNIX) || defined(Q_OS_MACOS)) || defined(QT_DBUS_LIB)
363 m_ui->actionAutoShutdown->setChecked(pref->shutdownWhenDownloadsComplete());
364 m_ui->actionAutoSuspend->setChecked(pref->suspendWhenDownloadsComplete());
365 m_ui->actionAutoHibernate->setChecked(pref->hibernateWhenDownloadsComplete());
366 #else
367 m_ui->actionAutoShutdown->setDisabled(true);
368 m_ui->actionAutoSuspend->setDisabled(true);
369 m_ui->actionAutoHibernate->setDisabled(true);
370 #endif
371 m_ui->actionAutoExit->setChecked(pref->shutdownqBTWhenDownloadsComplete());
373 if (!autoShutdownGroup->checkedAction())
374 m_ui->actionAutoShutdownDisabled->setChecked(true);
376 // Load Window state and sizes
377 loadSettings();
379 app->desktopIntegration()->setMenu(createDesktopIntegrationMenu());
380 #ifndef Q_OS_MACOS
381 m_ui->actionLock->setVisible(app->desktopIntegration()->isActive());
382 connect(app->desktopIntegration(), &DesktopIntegration::stateChanged, this, [this, app]()
384 m_ui->actionLock->setVisible(app->desktopIntegration()->isActive());
386 #endif
387 connect(app->desktopIntegration(), &DesktopIntegration::notificationClicked, this, &MainWindow::desktopNotificationClicked);
388 connect(app->desktopIntegration(), &DesktopIntegration::activationRequested, this, [this]()
390 #ifdef Q_OS_MACOS
391 if (!isVisible())
392 activate();
393 #else
394 toggleVisibility();
395 #endif
398 #ifdef Q_OS_MACOS
399 if (initialState == WindowState::Normal)
401 show();
402 activateWindow();
403 raise();
405 else
407 // Make sure the Window is visible if we don't have a tray icon
408 showMinimized();
410 #else
411 if (app->desktopIntegration()->isActive())
413 if ((initialState == WindowState::Normal) && !m_uiLocked)
415 show();
416 activateWindow();
417 raise();
419 else if (initialState == WindowState::Minimized)
421 showMinimized();
422 if (pref->minimizeToTray())
424 hide();
425 if (!pref->minimizeToTrayNotified())
427 app->desktopIntegration()->showNotification(tr("qBittorrent is minimized to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
428 pref->setMinimizeToTrayNotified(true);
433 else
435 // Make sure the Window is visible if we don't have a tray icon
436 if (initialState != WindowState::Normal)
438 showMinimized();
440 else
442 show();
443 activateWindow();
444 raise();
447 #endif
449 m_propertiesWidget->readSettings();
451 const bool isFiltersSidebarVisible = pref->isFiltersSidebarVisible();
452 m_ui->actionShowFiltersSidebar->setChecked(isFiltersSidebarVisible);
453 if (isFiltersSidebarVisible)
455 showFiltersSidebar(true);
457 else
459 m_transferListWidget->applyStatusFilter(pref->getTransSelFilter());
460 m_transferListWidget->applyCategoryFilter(QString());
461 m_transferListWidget->applyTagFilter(QString());
462 m_transferListWidget->applyTrackerFilterAll();
465 // Start watching the executable for updates
466 m_executableWatcher = new QFileSystemWatcher(this);
467 connect(m_executableWatcher, &QFileSystemWatcher::fileChanged, this, &MainWindow::notifyOfUpdate);
468 m_executableWatcher->addPath(qApp->applicationFilePath());
470 m_transferListWidget->setFocus();
472 // Update the number of torrents (tab)
473 updateNbTorrents();
474 connect(m_transferListWidget->getSourceModel(), &QAbstractItemModel::rowsInserted, this, &MainWindow::updateNbTorrents);
475 connect(m_transferListWidget->getSourceModel(), &QAbstractItemModel::rowsRemoved, this, &MainWindow::updateNbTorrents);
477 connect(pref, &Preferences::changed, this, &MainWindow::optionsSaved);
479 qDebug("GUI Built");
482 MainWindow::~MainWindow()
484 delete m_ui;
487 bool MainWindow::isExecutionLogEnabled() const
489 return m_storeExecutionLogEnabled;
492 void MainWindow::setExecutionLogEnabled(const bool value)
494 m_storeExecutionLogEnabled = value;
497 Log::MsgTypes MainWindow::executionLogMsgTypes() const
499 return m_storeExecutionLogTypes;
502 void MainWindow::setExecutionLogMsgTypes(const Log::MsgTypes value)
504 m_executionLog->setMessageTypes(value);
505 m_storeExecutionLogTypes = value;
508 bool MainWindow::isDownloadTrackerFavicon() const
510 return m_storeDownloadTrackerFavicon;
513 void MainWindow::setDownloadTrackerFavicon(const bool value)
515 if (m_transferListFiltersWidget)
516 m_transferListFiltersWidget->setDownloadTrackerFavicon(value);
517 m_storeDownloadTrackerFavicon = value;
520 void MainWindow::addToolbarContextMenu()
522 const Preferences *const pref = Preferences::instance();
523 m_toolbarMenu = new QMenu(this);
525 m_ui->toolBar->setContextMenuPolicy(Qt::CustomContextMenu);
526 connect(m_ui->toolBar, &QWidget::customContextMenuRequested, this, &MainWindow::toolbarMenuRequested);
528 QAction *iconsOnly = m_toolbarMenu->addAction(tr("Icons Only"), this, &MainWindow::toolbarIconsOnly);
529 QAction *textOnly = m_toolbarMenu->addAction(tr("Text Only"), this, &MainWindow::toolbarTextOnly);
530 QAction *textBesideIcons = m_toolbarMenu->addAction(tr("Text Alongside Icons"), this, &MainWindow::toolbarTextBeside);
531 QAction *textUnderIcons = m_toolbarMenu->addAction(tr("Text Under Icons"), this, &MainWindow::toolbarTextUnder);
532 QAction *followSystemStyle = m_toolbarMenu->addAction(tr("Follow System Style"), this, &MainWindow::toolbarFollowSystem);
534 auto *textPositionGroup = new QActionGroup(m_toolbarMenu);
535 textPositionGroup->addAction(iconsOnly);
536 iconsOnly->setCheckable(true);
537 textPositionGroup->addAction(textOnly);
538 textOnly->setCheckable(true);
539 textPositionGroup->addAction(textBesideIcons);
540 textBesideIcons->setCheckable(true);
541 textPositionGroup->addAction(textUnderIcons);
542 textUnderIcons->setCheckable(true);
543 textPositionGroup->addAction(followSystemStyle);
544 followSystemStyle->setCheckable(true);
546 const auto buttonStyle = static_cast<Qt::ToolButtonStyle>(pref->getToolbarTextPosition());
547 if ((buttonStyle >= Qt::ToolButtonIconOnly) && (buttonStyle <= Qt::ToolButtonFollowStyle))
548 m_ui->toolBar->setToolButtonStyle(buttonStyle);
549 switch (buttonStyle)
551 case Qt::ToolButtonIconOnly:
552 iconsOnly->setChecked(true);
553 break;
554 case Qt::ToolButtonTextOnly:
555 textOnly->setChecked(true);
556 break;
557 case Qt::ToolButtonTextBesideIcon:
558 textBesideIcons->setChecked(true);
559 break;
560 case Qt::ToolButtonTextUnderIcon:
561 textUnderIcons->setChecked(true);
562 break;
563 default:
564 followSystemStyle->setChecked(true);
568 void MainWindow::manageCookies()
570 auto *cookieDialog = new CookiesDialog(this);
571 cookieDialog->setAttribute(Qt::WA_DeleteOnClose);
572 cookieDialog->open();
575 void MainWindow::toolbarMenuRequested()
577 m_toolbarMenu->popup(QCursor::pos());
580 void MainWindow::toolbarIconsOnly()
582 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
583 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonIconOnly);
586 void MainWindow::toolbarTextOnly()
588 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextOnly);
589 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextOnly);
592 void MainWindow::toolbarTextBeside()
594 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
595 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextBesideIcon);
598 void MainWindow::toolbarTextUnder()
600 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
601 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextUnderIcon);
604 void MainWindow::toolbarFollowSystem()
606 m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle);
607 Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonFollowStyle);
610 bool MainWindow::defineUILockPassword()
612 bool ok = false;
613 const QString newPassword = AutoExpandableDialog::getText(this, tr("UI lock password")
614 , tr("Please type the UI lock password:"), QLineEdit::Password, {}, &ok);
615 if (!ok)
616 return false;
618 if (newPassword.size() < 3)
620 QMessageBox::warning(this, tr("Invalid password"), tr("The password must be at least 3 characters long"));
621 return false;
624 Preferences::instance()->setUILockPassword(Utils::Password::PBKDF2::generate(newPassword));
625 return true;
628 void MainWindow::clearUILockPassword()
630 const QMessageBox::StandardButton answer = QMessageBox::question(this, tr("Clear the password")
631 , tr("Are you sure you want to clear the password?"), (QMessageBox::Yes | QMessageBox::No), QMessageBox::No);
632 if (answer == QMessageBox::Yes)
633 Preferences::instance()->setUILockPassword({});
636 void MainWindow::on_actionLock_triggered()
638 Preferences *const pref = Preferences::instance();
640 // Check if there is a password
641 if (pref->getUILockPassword().isEmpty())
643 if (!defineUILockPassword())
644 return;
647 // Lock the interface
648 m_uiLocked = true;
649 pref->setUILocked(true);
650 app()->desktopIntegration()->menu()->setEnabled(false);
651 hide();
654 void MainWindow::handleRSSUnreadCountUpdated(int count)
656 m_tabs->setTabText(m_tabs->indexOf(m_rssWidget), tr("RSS (%1)").arg(count));
659 void MainWindow::displayRSSTab(bool enable)
661 if (enable)
663 // RSS tab
664 if (!m_rssWidget)
666 m_rssWidget = new RSSWidget(m_tabs);
667 connect(m_rssWidget.data(), &RSSWidget::unreadCountUpdated, this, &MainWindow::handleRSSUnreadCountUpdated);
668 #ifdef Q_OS_MACOS
669 m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
670 #else
671 const int indexTab = m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
672 m_tabs->setTabIcon(indexTab, UIThemeManager::instance()->getIcon(u"application-rss"_qs));
673 #endif
676 else
678 delete m_rssWidget;
682 void MainWindow::showFilterContextMenu()
684 const Preferences *pref = Preferences::instance();
686 QMenu *menu = m_columnFilterEdit->createStandardContextMenu();
687 menu->setAttribute(Qt::WA_DeleteOnClose);
688 menu->addSeparator();
690 QAction *useRegexAct = menu->addAction(tr("Use regular expressions"));
691 useRegexAct->setCheckable(true);
692 useRegexAct->setChecked(pref->getRegexAsFilteringPatternForTransferList());
693 connect(useRegexAct, &QAction::toggled, pref, &Preferences::setRegexAsFilteringPatternForTransferList);
694 connect(useRegexAct, &QAction::toggled, this, &MainWindow::applyTransferListFilter);
696 menu->popup(QCursor::pos());
699 void MainWindow::displaySearchTab(bool enable)
701 Preferences::instance()->setSearchEnabled(enable);
702 if (enable)
704 // RSS tab
705 if (!m_searchWidget)
707 m_searchWidget = new SearchWidget(app(), this);
708 m_tabs->insertTab(1, m_searchWidget,
709 #ifndef Q_OS_MACOS
710 UIThemeManager::instance()->getIcon(u"edit-find"_qs),
711 #endif
712 tr("Search"));
715 else
717 delete m_searchWidget;
721 void MainWindow::focusSearchFilter()
723 m_columnFilterEdit->setFocus();
724 m_columnFilterEdit->selectAll();
727 void MainWindow::updateNbTorrents()
729 m_tabs->setTabText(0, tr("Transfers (%1)").arg(m_transferListWidget->getSourceModel()->rowCount()));
732 void MainWindow::on_actionDocumentation_triggered() const
734 QDesktopServices::openUrl(QUrl(u"http://doc.qbittorrent.org"_qs));
737 void MainWindow::tabChanged(int newTab)
739 Q_UNUSED(newTab);
740 // We cannot rely on the index newTab
741 // because the tab order is undetermined now
742 if (m_tabs->currentWidget() == m_splitter)
744 qDebug("Changed tab to transfer list, refreshing the list");
745 m_propertiesWidget->loadDynamicData();
746 m_columnFilterAction->setVisible(true);
747 return;
749 m_columnFilterAction->setVisible(false);
751 if (m_tabs->currentWidget() == m_searchWidget)
753 qDebug("Changed tab to search engine, giving focus to search input");
754 m_searchWidget->giveFocusToSearchInput();
758 void MainWindow::saveSettings() const
760 auto *pref = Preferences::instance();
761 pref->setMainGeometry(saveGeometry());
762 m_propertiesWidget->saveSettings();
765 void MainWindow::saveSplitterSettings() const
767 if (!m_transferListFiltersWidget)
768 return;
770 auto *pref = Preferences::instance();
771 pref->setFiltersSidebarWidth(m_splitter->sizes()[0]);
774 void MainWindow::cleanup()
776 saveSettings();
777 saveSplitterSettings();
779 // delete RSSWidget explicitly to avoid crash in
780 // handleRSSUnreadCountUpdated() at application shutdown
781 delete m_rssWidget;
783 delete m_executableWatcher;
785 m_preventTimer->stop();
787 #if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
788 if (m_programUpdateTimer)
789 m_programUpdateTimer->stop();
790 #endif
792 // remove all child widgets
793 while (auto *w = findChild<QWidget *>())
794 delete w;
797 void MainWindow::loadSettings()
799 const auto *pref = Preferences::instance();
801 if (const QByteArray mainGeo = pref->getMainGeometry();
802 !mainGeo.isEmpty() && restoreGeometry(mainGeo))
804 m_posInitialized = true;
808 void MainWindow::desktopNotificationClicked()
810 if (isHidden())
812 if (m_uiLocked)
814 // Ask for UI lock password
815 if (!unlockUI())
816 return;
818 show();
819 if (isMinimized())
820 showNormal();
823 raise();
824 activateWindow();
827 void MainWindow::createKeyboardShortcuts()
829 m_ui->actionCreateTorrent->setShortcut(QKeySequence::New);
830 m_ui->actionOpen->setShortcut(QKeySequence::Open);
831 m_ui->actionDelete->setShortcut(QKeySequence::Delete);
832 m_ui->actionDelete->setShortcutContext(Qt::WidgetShortcut); // nullify its effect: delete key event is handled by respective widgets, not here
833 m_ui->actionDownloadFromURL->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_O);
834 m_ui->actionExit->setShortcut(Qt::CTRL | Qt::Key_Q);
835 #ifdef Q_OS_MACOS
836 m_ui->actionCloseWindow->setShortcut(QKeySequence::Close);
837 #else
838 m_ui->actionCloseWindow->setVisible(false);
839 #endif
841 const auto *switchTransferShortcut = new QShortcut((Qt::ALT | Qt::Key_1), this);
842 connect(switchTransferShortcut, &QShortcut::activated, this, &MainWindow::displayTransferTab);
843 const auto *switchSearchShortcut = new QShortcut((Qt::ALT | Qt::Key_2), this);
844 connect(switchSearchShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displaySearchTab));
845 const auto *switchRSSShortcut = new QShortcut((Qt::ALT | Qt::Key_3), this);
846 connect(switchRSSShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displayRSSTab));
847 const auto *switchExecutionLogShortcut = new QShortcut((Qt::ALT | Qt::Key_4), this);
848 connect(switchExecutionLogShortcut, &QShortcut::activated, this, &MainWindow::displayExecutionLogTab);
849 const auto *switchSearchFilterShortcut = new QShortcut(QKeySequence::Find, m_transferListWidget);
850 connect(switchSearchFilterShortcut, &QShortcut::activated, this, &MainWindow::focusSearchFilter);
852 m_ui->actionDocumentation->setShortcut(QKeySequence::HelpContents);
853 m_ui->actionOptions->setShortcut(Qt::ALT | Qt::Key_O);
854 m_ui->actionStatistics->setShortcut(Qt::CTRL | Qt::Key_I);
855 m_ui->actionStart->setShortcut(Qt::CTRL | Qt::Key_S);
856 m_ui->actionStartAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_S);
857 m_ui->actionPause->setShortcut(Qt::CTRL | Qt::Key_P);
858 m_ui->actionPauseAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_P);
859 m_ui->actionBottomQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Minus);
860 m_ui->actionDecreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Minus);
861 m_ui->actionIncreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Plus);
862 m_ui->actionTopQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Plus);
863 #ifdef Q_OS_MACOS
864 m_ui->actionMinimize->setShortcut(Qt::CTRL + Qt::Key_M);
865 addAction(m_ui->actionMinimize);
866 #endif
869 // Keyboard shortcuts slots
870 void MainWindow::displayTransferTab() const
872 m_tabs->setCurrentWidget(m_transferListWidget);
875 void MainWindow::displaySearchTab()
877 if (!m_searchWidget)
879 m_ui->actionSearchWidget->setChecked(true);
880 displaySearchTab(true);
883 m_tabs->setCurrentWidget(m_searchWidget);
886 void MainWindow::displayRSSTab()
888 if (!m_rssWidget)
890 m_ui->actionRSSReader->setChecked(true);
891 displayRSSTab(true);
894 m_tabs->setCurrentWidget(m_rssWidget);
897 void MainWindow::displayExecutionLogTab()
899 if (!m_executionLog)
901 m_ui->actionExecutionLogs->setChecked(true);
902 on_actionExecutionLogs_triggered(true);
905 m_tabs->setCurrentWidget(m_executionLog);
908 // End of keyboard shortcuts slots
910 void MainWindow::askRecursiveTorrentDownloadConfirmation(const BitTorrent::Torrent *torrent)
912 if (!Preferences::instance()->isRecursiveDownloadEnabled())
913 return;
915 const auto torrentID = torrent->id();
917 QMessageBox *confirmBox = new QMessageBox(QMessageBox::Question, tr("Recursive download confirmation")
918 , tr("The torrent '%1' contains .torrent files, do you want to proceed with their downloads?").arg(torrent->name())
919 , (QMessageBox::Yes | QMessageBox::No | QMessageBox::NoToAll), this);
920 confirmBox->setAttribute(Qt::WA_DeleteOnClose);
922 const QAbstractButton *yesButton = confirmBox->button(QMessageBox::Yes);
923 QAbstractButton *neverButton = confirmBox->button(QMessageBox::NoToAll);
924 neverButton->setText(tr("Never"));
926 connect(confirmBox, &QMessageBox::buttonClicked, this
927 , [torrentID, yesButton, neverButton](const QAbstractButton *button)
929 if (button == yesButton)
931 BitTorrent::Session::instance()->recursiveTorrentDownload(torrentID);
933 else if (button == neverButton)
935 Preferences::instance()->setRecursiveDownloadEnabled(false);
938 confirmBox->open();
941 void MainWindow::on_actionSetGlobalSpeedLimits_triggered()
943 auto dialog = new SpeedLimitDialog {this};
944 dialog->setAttribute(Qt::WA_DeleteOnClose);
945 dialog->open();
948 // Necessary if we want to close the window
949 // in one time if "close to systray" is enabled
950 void MainWindow::on_actionExit_triggered()
952 // UI locking enforcement.
953 if (isHidden() && m_uiLocked)
954 // Ask for UI lock password
955 if (!unlockUI()) return;
957 m_forceExit = true;
958 close();
961 #ifdef Q_OS_MACOS
962 void MainWindow::on_actionCloseWindow_triggered()
964 // On macOS window close is basically equivalent to window hide.
965 // If you decide to implement this functionality for other OS,
966 // then you will also need ui lock checks like in actionExit.
967 close();
969 #endif
971 QWidget *MainWindow::currentTabWidget() const
973 if (isMinimized() || !isVisible())
974 return nullptr;
975 if (m_tabs->currentIndex() == 0)
976 return m_transferListWidget;
977 return m_tabs->currentWidget();
980 TransferListWidget *MainWindow::transferListWidget() const
982 return m_transferListWidget;
985 bool MainWindow::unlockUI()
987 if (m_unlockDlgShowing)
988 return false;
990 bool ok = false;
991 const QString password = AutoExpandableDialog::getText(this, tr("UI lock password")
992 , tr("Please type the UI lock password:"), QLineEdit::Password, {}, &ok);
993 if (!ok) return false;
995 Preferences *const pref = Preferences::instance();
997 const QByteArray secret = pref->getUILockPassword();
998 if (!Utils::Password::PBKDF2::verify(secret, password))
1000 QMessageBox::warning(this, tr("Invalid password"), tr("The password is invalid"));
1001 return false;
1004 m_uiLocked = false;
1005 pref->setUILocked(false);
1006 app()->desktopIntegration()->menu()->setEnabled(true);
1007 return true;
1010 void MainWindow::notifyOfUpdate(const QString &)
1012 // Show restart message
1013 m_statusBar->showRestartRequired();
1014 LogMsg(tr("qBittorrent was just updated and needs to be restarted for the changes to be effective.")
1015 , Log::CRITICAL);
1016 // Delete the executable watcher
1017 delete m_executableWatcher;
1018 m_executableWatcher = nullptr;
1021 #ifndef Q_OS_MACOS
1022 // Toggle Main window visibility
1023 void MainWindow::toggleVisibility()
1025 if (isHidden())
1027 if (m_uiLocked && !unlockUI()) // Ask for UI lock password
1028 return;
1030 // Make sure the window is not minimized
1031 setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
1033 // Then show it
1034 show();
1035 raise();
1036 activateWindow();
1038 else
1040 hide();
1043 #endif // Q_OS_MACOS
1045 // Display About Dialog
1046 void MainWindow::on_actionAbout_triggered()
1048 // About dialog
1049 if (m_aboutDlg)
1051 m_aboutDlg->activateWindow();
1053 else
1055 m_aboutDlg = new AboutDialog(this);
1056 m_aboutDlg->setAttribute(Qt::WA_DeleteOnClose);
1057 m_aboutDlg->show();
1061 void MainWindow::on_actionStatistics_triggered()
1063 if (m_statsDlg)
1065 m_statsDlg->activateWindow();
1067 else
1069 m_statsDlg = new StatsDialog(this);
1070 m_statsDlg->setAttribute(Qt::WA_DeleteOnClose);
1071 m_statsDlg->show();
1075 void MainWindow::showEvent(QShowEvent *e)
1077 qDebug("** Show Event **");
1078 e->accept();
1080 if (isVisible())
1082 // preparations before showing the window
1084 if (currentTabWidget() == m_transferListWidget)
1085 m_propertiesWidget->loadDynamicData();
1087 // Make sure the window is initially centered
1088 if (!m_posInitialized)
1090 move(Utils::Gui::screenCenter(this));
1091 m_posInitialized = true;
1094 else
1096 // to avoid blank screen when restoring from tray icon
1097 show();
1101 void MainWindow::keyPressEvent(QKeyEvent *event)
1103 if (event->matches(QKeySequence::Paste))
1105 const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData();
1107 if (mimeData->hasText())
1109 const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
1110 const QStringList lines = mimeData->text().split(u'\n', Qt::SkipEmptyParts);
1112 for (QString line : lines)
1114 line = line.trimmed();
1116 if (!isTorrentLink(line))
1117 continue;
1119 if (useTorrentAdditionDialog)
1120 AddNewTorrentDialog::show(line, this);
1121 else
1122 BitTorrent::Session::instance()->addTorrent(line);
1125 return;
1129 QMainWindow::keyPressEvent(event);
1132 // Called when we close the program
1133 void MainWindow::closeEvent(QCloseEvent *e)
1135 Preferences *const pref = Preferences::instance();
1136 #ifdef Q_OS_MACOS
1137 if (!m_forceExit)
1139 hide();
1140 e->accept();
1141 return;
1143 #else
1144 const bool goToSystrayOnExit = pref->closeToTray();
1145 if (!m_forceExit && app()->desktopIntegration()->isActive() && goToSystrayOnExit && !this->isHidden())
1147 e->ignore();
1148 QMetaObject::invokeMethod(this, &QWidget::hide, Qt::QueuedConnection);
1149 if (!pref->closeToTrayNotified())
1151 app()->desktopIntegration()->showNotification(tr("qBittorrent is closed to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
1152 pref->setCloseToTrayNotified(true);
1154 return;
1156 #endif // Q_OS_MACOS
1158 const QVector<BitTorrent::Torrent *> allTorrents = BitTorrent::Session::instance()->torrents();
1159 const bool hasActiveTorrents = std::any_of(allTorrents.cbegin(), allTorrents.cend(), [](BitTorrent::Torrent *torrent)
1161 return torrent->isActive();
1163 if (pref->confirmOnExit() && hasActiveTorrents)
1165 if (e->spontaneous() || m_forceExit)
1167 if (!isVisible())
1168 show();
1169 QMessageBox confirmBox(QMessageBox::Question, tr("Exiting qBittorrent"),
1170 // Split it because the last sentence is used in the Web UI
1171 tr("Some files are currently transferring.") + u'\n' + tr("Are you sure you want to quit qBittorrent?"),
1172 QMessageBox::NoButton, this);
1173 QPushButton *noBtn = confirmBox.addButton(tr("&No"), QMessageBox::NoRole);
1174 confirmBox.addButton(tr("&Yes"), QMessageBox::YesRole);
1175 QPushButton *alwaysBtn = confirmBox.addButton(tr("&Always Yes"), QMessageBox::YesRole);
1176 confirmBox.setDefaultButton(noBtn);
1177 confirmBox.exec();
1178 if (!confirmBox.clickedButton() || (confirmBox.clickedButton() == noBtn))
1180 // Cancel exit
1181 e->ignore();
1182 m_forceExit = false;
1183 return;
1185 if (confirmBox.clickedButton() == alwaysBtn)
1186 // Remember choice
1187 Preferences::instance()->setConfirmOnExit(false);
1191 // Accept exit
1192 e->accept();
1193 qApp->exit();
1196 // Display window to create a torrent
1197 void MainWindow::on_actionCreateTorrent_triggered()
1199 createTorrentTriggered({});
1202 void MainWindow::createTorrentTriggered(const Path &path)
1204 if (m_createTorrentDlg)
1206 m_createTorrentDlg->updateInputPath(path);
1207 m_createTorrentDlg->activateWindow();
1209 else
1211 m_createTorrentDlg = new TorrentCreatorDialog(this, path);
1212 m_createTorrentDlg->setAttribute(Qt::WA_DeleteOnClose);
1213 m_createTorrentDlg->show();
1217 bool MainWindow::event(QEvent *e)
1219 #ifndef Q_OS_MACOS
1220 switch (e->type())
1222 case QEvent::WindowStateChange:
1223 qDebug("Window change event");
1224 // Now check to see if the window is minimised
1225 if (isMinimized())
1227 qDebug("minimisation");
1228 Preferences *const pref = Preferences::instance();
1229 if (app()->desktopIntegration()->isActive() && pref->minimizeToTray())
1231 qDebug() << "Has active window:" << (qApp->activeWindow() != nullptr);
1232 // Check if there is a modal window
1233 const QWidgetList allWidgets = QApplication::allWidgets();
1234 const bool hasModalWindow = std::any_of(allWidgets.cbegin(), allWidgets.cend()
1235 , [](const QWidget *widget) { return widget->isModal(); });
1236 // Iconify if there is no modal window
1237 if (!hasModalWindow)
1239 qDebug("Minimize to Tray enabled, hiding!");
1240 e->ignore();
1241 QMetaObject::invokeMethod(this, &QWidget::hide, Qt::QueuedConnection);
1242 if (!pref->minimizeToTrayNotified())
1244 app()->desktopIntegration()->showNotification(tr("qBittorrent is minimized to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
1245 pref->setMinimizeToTrayNotified(true);
1247 return true;
1251 break;
1252 case QEvent::ToolBarChange:
1254 qDebug("MAC: Received a toolbar change event!");
1255 const bool ret = QMainWindow::event(e);
1257 qDebug("MAC: new toolbar visibility is %d", !m_ui->actionTopToolBar->isChecked());
1258 m_ui->actionTopToolBar->toggle();
1259 Preferences::instance()->setToolbarDisplayed(m_ui->actionTopToolBar->isChecked());
1260 return ret;
1262 default:
1263 break;
1265 #endif // Q_OS_MACOS
1267 return QMainWindow::event(e);
1270 // action executed when a file is dropped
1271 void MainWindow::dropEvent(QDropEvent *event)
1273 event->acceptProposedAction();
1275 // remove scheme
1276 QStringList files;
1277 if (event->mimeData()->hasUrls())
1279 for (const QUrl &url : asConst(event->mimeData()->urls()))
1281 if (url.isEmpty())
1282 continue;
1284 files << ((url.scheme().compare(u"file", Qt::CaseInsensitive) == 0)
1285 ? url.toLocalFile()
1286 : url.toString());
1289 else
1291 files = event->mimeData()->text().split(u'\n');
1294 // differentiate ".torrent" files/links & magnet links from others
1295 QStringList torrentFiles, otherFiles;
1296 for (const QString &file : asConst(files))
1298 if (isTorrentLink(file))
1299 torrentFiles << file;
1300 else
1301 otherFiles << file;
1304 // Download torrents
1305 const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
1306 for (const QString &file : asConst(torrentFiles))
1308 if (useTorrentAdditionDialog)
1309 AddNewTorrentDialog::show(file, this);
1310 else
1311 BitTorrent::Session::instance()->addTorrent(file);
1313 if (!torrentFiles.isEmpty()) return;
1315 // Create torrent
1316 for (const QString &file : asConst(otherFiles))
1318 createTorrentTriggered(Path(file));
1320 // currently only handle the first entry
1321 // this is a stub that can be expanded later to create many torrents at once
1322 break;
1326 // Decode if we accept drag 'n drop or not
1327 void MainWindow::dragEnterEvent(QDragEnterEvent *event)
1329 for (const QString &mime : asConst(event->mimeData()->formats()))
1330 qDebug("mimeData: %s", mime.toLocal8Bit().data());
1332 if (event->mimeData()->hasFormat(u"text/plain"_qs) || event->mimeData()->hasFormat(u"text/uri-list"_qs))
1333 event->acceptProposedAction();
1336 /*****************************************************
1338 * Torrent *
1340 *****************************************************/
1342 // Display a dialog to allow user to add
1343 // torrents to download list
1344 void MainWindow::on_actionOpen_triggered()
1346 Preferences *const pref = Preferences::instance();
1347 // Open File Open Dialog
1348 // Note: it is possible to select more than one file
1349 const QStringList pathsList =
1350 QFileDialog::getOpenFileNames(this, tr("Open Torrent Files"), pref->getMainLastDir().data(),
1351 tr("Torrent Files") + u" (*" + TORRENT_FILE_EXTENSION + u')');
1353 if (pathsList.isEmpty())
1354 return;
1356 const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
1358 for (const QString &file : pathsList)
1360 if (useTorrentAdditionDialog)
1361 AddNewTorrentDialog::show(file, this);
1362 else
1363 BitTorrent::Session::instance()->addTorrent(file);
1366 // Save last dir to remember it
1367 const Path topDir {pathsList.at(0)};
1368 const Path parentDir = topDir.parentPath();
1369 pref->setMainLastDir(parentDir.isEmpty() ? topDir : parentDir);
1372 void MainWindow::activate()
1374 if (!m_uiLocked || unlockUI())
1376 show();
1377 activateWindow();
1378 raise();
1382 void MainWindow::optionsSaved()
1384 LogMsg(tr("Options saved."));
1385 loadPreferences();
1388 void MainWindow::showStatusBar(bool show)
1390 if (!show)
1392 // Remove status bar
1393 setStatusBar(nullptr);
1395 else if (!m_statusBar)
1397 // Create status bar
1398 m_statusBar = new StatusBar;
1399 connect(m_statusBar.data(), &StatusBar::connectionButtonClicked, this, &MainWindow::showConnectionSettings);
1400 connect(m_statusBar.data(), &StatusBar::alternativeSpeedsButtonClicked, this, &MainWindow::toggleAlternativeSpeeds);
1401 setStatusBar(m_statusBar);
1405 void MainWindow::showFiltersSidebar(const bool show)
1407 if (show && !m_transferListFiltersWidget)
1409 m_transferListFiltersWidget = new TransferListFiltersWidget(m_splitter, m_transferListWidget, isDownloadTrackerFavicon());
1410 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersAdded, m_transferListFiltersWidget, &TransferListFiltersWidget::addTrackers);
1411 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersRemoved, m_transferListFiltersWidget, &TransferListFiltersWidget::removeTrackers);
1412 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersChanged, m_transferListFiltersWidget, &TransferListFiltersWidget::refreshTrackers);
1413 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerlessStateChanged, m_transferListFiltersWidget, &TransferListFiltersWidget::changeTrackerless);
1414 connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerEntriesUpdated, m_transferListFiltersWidget, &TransferListFiltersWidget::trackerEntriesUpdated);
1416 m_splitter->insertWidget(0, m_transferListFiltersWidget);
1417 m_splitter->setCollapsible(0, true);
1418 // From https://doc.qt.io/qt-5/qsplitter.html#setSizes:
1419 // Instead, any additional/missing space is distributed amongst the widgets
1420 // according to the relative weight of the sizes.
1421 m_splitter->setStretchFactor(0, 0);
1422 m_splitter->setStretchFactor(1, 1);
1423 m_splitter->setSizes({Preferences::instance()->getFiltersSidebarWidth()});
1425 else if (!show && m_transferListFiltersWidget)
1427 saveSplitterSettings();
1428 delete m_transferListFiltersWidget;
1429 m_transferListFiltersWidget = nullptr;
1433 void MainWindow::loadPreferences()
1435 const Preferences *pref = Preferences::instance();
1437 // General
1438 if (pref->isToolbarDisplayed())
1440 m_ui->toolBar->setVisible(true);
1442 else
1444 // Clear search filter before hiding the top toolbar
1445 m_columnFilterEdit->clear();
1446 m_ui->toolBar->setVisible(false);
1449 showStatusBar(pref->isStatusbarDisplayed());
1451 if (pref->preventFromSuspendWhenDownloading() || pref->preventFromSuspendWhenSeeding())
1453 if (!m_preventTimer->isActive())
1455 updatePowerManagementState();
1456 m_preventTimer->start(PREVENT_SUSPEND_INTERVAL);
1459 else
1461 m_preventTimer->stop();
1462 m_pwr->setActivityState(false);
1465 m_transferListWidget->setAlternatingRowColors(pref->useAlternatingRowColors());
1466 m_propertiesWidget->getFilesList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1467 m_propertiesWidget->getTrackerList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1468 m_propertiesWidget->getPeerList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1470 // Queueing System
1471 if (BitTorrent::Session::instance()->isQueueingSystemEnabled())
1473 if (!m_ui->actionDecreaseQueuePos->isVisible())
1475 m_transferListWidget->hideQueuePosColumn(false);
1476 m_ui->actionDecreaseQueuePos->setVisible(true);
1477 m_ui->actionIncreaseQueuePos->setVisible(true);
1478 m_ui->actionTopQueuePos->setVisible(true);
1479 m_ui->actionBottomQueuePos->setVisible(true);
1480 #ifndef Q_OS_MACOS
1481 m_queueSeparator->setVisible(true);
1482 #endif
1483 m_queueSeparatorMenu->setVisible(true);
1486 else
1488 if (m_ui->actionDecreaseQueuePos->isVisible())
1490 m_transferListWidget->hideQueuePosColumn(true);
1491 m_ui->actionDecreaseQueuePos->setVisible(false);
1492 m_ui->actionIncreaseQueuePos->setVisible(false);
1493 m_ui->actionTopQueuePos->setVisible(false);
1494 m_ui->actionBottomQueuePos->setVisible(false);
1495 #ifndef Q_OS_MACOS
1496 m_queueSeparator->setVisible(false);
1497 #endif
1498 m_queueSeparatorMenu->setVisible(false);
1502 // Torrent properties
1503 m_propertiesWidget->reloadPreferences();
1505 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1506 if (pref->isUpdateCheckEnabled())
1508 if (!m_programUpdateTimer)
1510 m_programUpdateTimer = new QTimer(this);
1511 m_programUpdateTimer->setInterval(24h);
1512 m_programUpdateTimer->setSingleShot(true);
1513 connect(m_programUpdateTimer, &QTimer::timeout, this, [this]() { checkProgramUpdate(false); });
1514 m_programUpdateTimer->start();
1517 else
1519 delete m_programUpdateTimer;
1520 m_programUpdateTimer = nullptr;
1522 #endif
1524 qDebug("GUI settings loaded");
1527 void MainWindow::reloadSessionStats()
1529 const BitTorrent::SessionStatus &status = BitTorrent::Session::instance()->status();
1531 // update global information
1532 #ifdef Q_OS_MACOS
1533 if (status.payloadDownloadRate > 0)
1535 MacUtils::setBadgeLabelText(tr("%1/s", "s is a shorthand for seconds")
1536 .arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate)));
1538 else if (!MacUtils::badgeLabelText().isEmpty())
1540 MacUtils::setBadgeLabelText({});
1542 #else
1543 const auto toolTip = u"%1\n%2"_qs.arg(
1544 tr("DL speed: %1", "e.g: Download speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true))
1545 , tr("UP speed: %1", "e.g: Upload speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadUploadRate, true)));
1546 app()->desktopIntegration()->setToolTip(toolTip); // tray icon
1547 #endif // Q_OS_MACOS
1549 if (m_displaySpeedInTitle)
1551 setWindowTitle(tr("[D: %1, U: %2] qBittorrent %3", "D = Download; U = Upload; %3 is qBittorrent version")
1552 .arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true)
1553 , Utils::Misc::friendlyUnit(status.payloadUploadRate, true)
1554 , QStringLiteral(QBT_VERSION)));
1558 void MainWindow::reloadTorrentStats(const QVector<BitTorrent::Torrent *> &torrents)
1560 if (currentTabWidget() == m_transferListWidget)
1562 if (torrents.contains(m_propertiesWidget->getCurrentTorrent()))
1563 m_propertiesWidget->loadDynamicData();
1567 /*****************************************************
1569 * Utils *
1571 *****************************************************/
1573 void MainWindow::downloadFromURLList(const QStringList &urlList)
1575 const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
1576 for (const QString &url : urlList)
1578 if (useTorrentAdditionDialog)
1579 AddNewTorrentDialog::show(url, this);
1580 else
1581 BitTorrent::Session::instance()->addTorrent(url);
1585 /*****************************************************
1587 * Options *
1589 *****************************************************/
1591 QMenu *MainWindow::createDesktopIntegrationMenu()
1593 auto *menu = new QMenu;
1595 #ifndef Q_OS_MACOS
1596 connect(menu, &QMenu::aboutToShow, this, [this]()
1598 m_ui->actionToggleVisibility->setText(isVisible() ? tr("Hide") : tr("Show"));
1601 menu->addAction(m_ui->actionToggleVisibility);
1602 menu->addSeparator();
1603 #endif
1605 menu->addAction(m_ui->actionOpen);
1606 menu->addAction(m_ui->actionDownloadFromURL);
1607 menu->addSeparator();
1609 menu->addAction(m_ui->actionUseAlternativeSpeedLimits);
1610 menu->addAction(m_ui->actionSetGlobalSpeedLimits);
1611 menu->addSeparator();
1613 menu->addAction(m_ui->actionStartAll);
1614 menu->addAction(m_ui->actionPauseAll);
1616 #ifndef Q_OS_MACOS
1617 menu->addSeparator();
1618 menu->addAction(m_ui->actionExit);
1619 #endif
1621 if (m_uiLocked)
1622 menu->setEnabled(false);
1624 return menu;
1627 void MainWindow::updateAltSpeedsBtn(const bool alternative)
1629 m_ui->actionUseAlternativeSpeedLimits->setChecked(alternative);
1632 PropertiesWidget *MainWindow::propertiesWidget() const
1634 return m_propertiesWidget;
1637 // Display Program Options
1638 void MainWindow::on_actionOptions_triggered()
1640 if (m_options)
1642 m_options->activateWindow();
1644 else
1646 m_options = new OptionsDialog(app(), this);
1647 m_options->setAttribute(Qt::WA_DeleteOnClose);
1648 m_options->open();
1652 void MainWindow::on_actionTopToolBar_triggered()
1654 const bool isVisible = static_cast<QAction *>(sender())->isChecked();
1655 m_ui->toolBar->setVisible(isVisible);
1656 Preferences::instance()->setToolbarDisplayed(isVisible);
1659 void MainWindow::on_actionShowStatusbar_triggered()
1661 const bool isVisible = static_cast<QAction *>(sender())->isChecked();
1662 Preferences::instance()->setStatusbarDisplayed(isVisible);
1663 showStatusBar(isVisible);
1666 void MainWindow::on_actionShowFiltersSidebar_triggered(const bool checked)
1668 Preferences *const pref = Preferences::instance();
1669 pref->setFiltersSidebarVisible(checked);
1670 showFiltersSidebar(checked);
1673 void MainWindow::on_actionSpeedInTitleBar_triggered()
1675 m_displaySpeedInTitle = static_cast<QAction *>(sender())->isChecked();
1676 Preferences::instance()->showSpeedInTitleBar(m_displaySpeedInTitle);
1677 if (m_displaySpeedInTitle)
1678 reloadSessionStats();
1679 else
1680 setWindowTitle(QStringLiteral("qBittorrent " QBT_VERSION));
1683 void MainWindow::on_actionRSSReader_triggered()
1685 Preferences::instance()->setRSSWidgetVisible(m_ui->actionRSSReader->isChecked());
1686 displayRSSTab(m_ui->actionRSSReader->isChecked());
1689 void MainWindow::on_actionSearchWidget_triggered()
1691 if (!m_hasPython && m_ui->actionSearchWidget->isChecked())
1693 const Utils::ForeignApps::PythonInfo pyInfo = Utils::ForeignApps::pythonInfo();
1695 // Not installed
1696 if (!pyInfo.isValid())
1698 m_ui->actionSearchWidget->setChecked(false);
1699 Preferences::instance()->setSearchEnabled(false);
1701 #ifdef Q_OS_WIN
1702 const QMessageBox::StandardButton buttonPressed = QMessageBox::question(this, tr("Missing Python Runtime")
1703 , tr("Python is required to use the search engine but it does not seem to be installed.\nDo you want to install it now?")
1704 , (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
1705 if (buttonPressed == QMessageBox::Yes)
1706 installPython();
1707 #else
1708 QMessageBox::information(this, tr("Missing Python Runtime")
1709 , tr("Python is required to use the search engine but it does not seem to be installed."));
1710 #endif
1711 return;
1714 // Check version requirement
1715 if (!pyInfo.isSupportedVersion())
1717 m_ui->actionSearchWidget->setChecked(false);
1718 Preferences::instance()->setSearchEnabled(false);
1720 #ifdef Q_OS_WIN
1721 const QMessageBox::StandardButton buttonPressed = QMessageBox::question(this, tr("Old Python Runtime")
1722 , tr("Your Python version (%1) is outdated. Minimum requirement: %2.\nDo you want to install a newer version now?")
1723 .arg(pyInfo.version.toString(), u"3.5.0")
1724 , (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
1725 if (buttonPressed == QMessageBox::Yes)
1726 installPython();
1727 #else
1728 QMessageBox::information(this, tr("Old Python Runtime")
1729 , tr("Your Python version (%1) is outdated. Please upgrade to latest version for search engines to work.\nMinimum requirement: %2.")
1730 .arg(pyInfo.version.toString(), u"3.5.0"));
1731 #endif
1732 return;
1735 m_hasPython = true;
1736 m_ui->actionSearchWidget->setChecked(true);
1737 Preferences::instance()->setSearchEnabled(true);
1740 displaySearchTab(m_ui->actionSearchWidget->isChecked());
1743 /*****************************************************
1745 * HTTP Downloader *
1747 *****************************************************/
1749 // Display an input dialog to prompt user for
1750 // an url
1751 void MainWindow::on_actionDownloadFromURL_triggered()
1753 if (!m_downloadFromURLDialog)
1755 m_downloadFromURLDialog = new DownloadFromURLDialog(this);
1756 m_downloadFromURLDialog->setAttribute(Qt::WA_DeleteOnClose);
1757 connect(m_downloadFromURLDialog.data(), &DownloadFromURLDialog::urlsReadyToBeDownloaded, this, &MainWindow::downloadFromURLList);
1758 m_downloadFromURLDialog->open();
1762 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1763 void MainWindow::handleUpdateCheckFinished(ProgramUpdater *updater, const bool invokedByUser)
1765 m_ui->actionCheckForUpdates->setEnabled(true);
1766 m_ui->actionCheckForUpdates->setText(tr("&Check for Updates"));
1767 m_ui->actionCheckForUpdates->setToolTip(tr("Check for program updates"));
1769 const auto cleanup = [this, updater]()
1771 if (m_programUpdateTimer)
1772 m_programUpdateTimer->start();
1773 updater->deleteLater();
1776 const QString newVersion = updater->getNewVersion();
1777 if (!newVersion.isEmpty())
1779 const QString msg {tr("A new version is available.") + u"<br/>"
1780 + tr("Do you want to download %1?").arg(newVersion) + u"<br/><br/>"
1781 + u"<a href=\"https://www.qbittorrent.org/news.php\">%1</a>"_qs.arg(tr("Open changelog..."))};
1782 auto *msgBox = new QMessageBox {QMessageBox::Question, tr("qBittorrent Update Available"), msg
1783 , (QMessageBox::Yes | QMessageBox::No), this};
1784 msgBox->setAttribute(Qt::WA_DeleteOnClose);
1785 msgBox->setAttribute(Qt::WA_ShowWithoutActivating);
1786 msgBox->setDefaultButton(QMessageBox::Yes);
1787 msgBox->setWindowModality(Qt::NonModal);
1788 connect(msgBox, &QMessageBox::buttonClicked, this, [msgBox, updater](QAbstractButton *button)
1790 if (msgBox->buttonRole(button) == QMessageBox::YesRole)
1792 updater->updateProgram();
1795 connect(msgBox, &QDialog::finished, this, cleanup);
1796 msgBox->show();
1798 else
1800 if (invokedByUser)
1802 auto *msgBox = new QMessageBox {QMessageBox::Information, u"qBittorrent"_qs
1803 , tr("No updates available.\nYou are already using the latest version.")
1804 , QMessageBox::Ok, this};
1805 msgBox->setAttribute(Qt::WA_DeleteOnClose);
1806 msgBox->setWindowModality(Qt::NonModal);
1807 connect(msgBox, &QDialog::finished, this, cleanup);
1808 msgBox->show();
1810 else
1812 cleanup();
1816 #endif
1818 void MainWindow::toggleAlternativeSpeeds()
1820 BitTorrent::Session *const session = BitTorrent::Session::instance();
1821 session->setAltGlobalSpeedLimitEnabled(!session->isAltGlobalSpeedLimitEnabled());
1824 void MainWindow::on_actionDonateMoney_triggered()
1826 QDesktopServices::openUrl(QUrl(u"https://www.qbittorrent.org/donate"_qs));
1829 void MainWindow::showConnectionSettings()
1831 on_actionOptions_triggered();
1832 m_options->showConnectionTab();
1835 void MainWindow::minimizeWindow()
1837 setWindowState(windowState() | Qt::WindowMinimized);
1840 void MainWindow::on_actionExecutionLogs_triggered(bool checked)
1842 if (checked)
1844 Q_ASSERT(!m_executionLog);
1845 m_executionLog = new ExecutionLogWidget(executionLogMsgTypes(), m_tabs);
1846 #ifdef Q_OS_MACOS
1847 m_tabs->addTab(m_executionLog, tr("Execution Log"));
1848 #else
1849 const int indexTab = m_tabs->addTab(m_executionLog, tr("Execution Log"));
1850 m_tabs->setTabIcon(indexTab, UIThemeManager::instance()->getIcon(u"help-contents"_qs));
1851 #endif
1853 else
1855 delete m_executionLog;
1858 m_ui->actionNormalMessages->setEnabled(checked);
1859 m_ui->actionInformationMessages->setEnabled(checked);
1860 m_ui->actionWarningMessages->setEnabled(checked);
1861 m_ui->actionCriticalMessages->setEnabled(checked);
1862 setExecutionLogEnabled(checked);
1865 void MainWindow::on_actionNormalMessages_triggered(const bool checked)
1867 if (!m_executionLog)
1868 return;
1870 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::NORMAL, checked);
1871 setExecutionLogMsgTypes(flags);
1874 void MainWindow::on_actionInformationMessages_triggered(const bool checked)
1876 if (!m_executionLog)
1877 return;
1879 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::INFO, checked);
1880 setExecutionLogMsgTypes(flags);
1883 void MainWindow::on_actionWarningMessages_triggered(const bool checked)
1885 if (!m_executionLog)
1886 return;
1888 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::WARNING, checked);
1889 setExecutionLogMsgTypes(flags);
1892 void MainWindow::on_actionCriticalMessages_triggered(const bool checked)
1894 if (!m_executionLog)
1895 return;
1897 const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::CRITICAL, checked);
1898 setExecutionLogMsgTypes(flags);
1901 void MainWindow::on_actionAutoExit_toggled(bool enabled)
1903 qDebug() << Q_FUNC_INFO << enabled;
1904 Preferences::instance()->setShutdownqBTWhenDownloadsComplete(enabled);
1907 void MainWindow::on_actionAutoSuspend_toggled(bool enabled)
1909 qDebug() << Q_FUNC_INFO << enabled;
1910 Preferences::instance()->setSuspendWhenDownloadsComplete(enabled);
1913 void MainWindow::on_actionAutoHibernate_toggled(bool enabled)
1915 qDebug() << Q_FUNC_INFO << enabled;
1916 Preferences::instance()->setHibernateWhenDownloadsComplete(enabled);
1919 void MainWindow::on_actionAutoShutdown_toggled(bool enabled)
1921 qDebug() << Q_FUNC_INFO << enabled;
1922 Preferences::instance()->setShutdownWhenDownloadsComplete(enabled);
1925 void MainWindow::updatePowerManagementState()
1927 const QVector<BitTorrent::Torrent *> allTorrents = BitTorrent::Session::instance()->torrents();
1928 const bool hasUnfinishedTorrents = std::any_of(allTorrents.cbegin(), allTorrents.cend(), [](const BitTorrent::Torrent *torrent)
1930 return (!torrent->isFinished() && !torrent->isPaused() && !torrent->isErrored() && torrent->hasMetadata());
1932 const bool hasRunningSeed = std::any_of(allTorrents.cbegin(), allTorrents.cend(), [](const BitTorrent::Torrent *torrent)
1934 return (torrent->isFinished() && !torrent->isPaused());
1936 const bool inhibitSuspend = (Preferences::instance()->preventFromSuspendWhenDownloading() && hasUnfinishedTorrents)
1937 || (Preferences::instance()->preventFromSuspendWhenSeeding() && hasRunningSeed);
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 = u"https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe"_qs;
1973 #else
1974 const auto installerURL = u"https://www.python.org/ftp/python/3.8.10/python-3.8.10.exe"_qs;
1975 #endif
1976 Net::DownloadManager::instance()->download(
1977 Net::DownloadRequest(installerURL).saveToFile(true)
1978 , Preferences::instance()->useProxyForGeneralPurposes()
1979 , this, &MainWindow::pythonDownloadFinished);
1982 void MainWindow::pythonDownloadFinished(const Net::DownloadResult &result)
1984 if (result.status != Net::DownloadStatus::Success)
1986 setCursor(QCursor(Qt::ArrowCursor));
1987 QMessageBox::warning(
1988 this, tr("Download error")
1989 , tr("Python setup could not be downloaded, reason: %1.\nPlease install it manually.")
1990 .arg(result.errorString));
1991 return;
1994 setCursor(QCursor(Qt::ArrowCursor));
1995 QProcess installer;
1996 qDebug("Launching Python installer in passive mode...");
1998 const Path exePath = result.filePath + u".exe";
1999 Utils::Fs::renameFile(result.filePath, exePath);
2000 installer.start(exePath.toString(), {u"/passive"_qs});
2002 // Wait for setup to complete
2003 installer.waitForFinished(10 * 60 * 1000);
2005 qDebug("Installer stdout: %s", installer.readAllStandardOutput().data());
2006 qDebug("Installer stderr: %s", installer.readAllStandardError().data());
2007 qDebug("Setup should be complete!");
2009 // Delete temp file
2010 Utils::Fs::removeFile(exePath);
2012 // Reload search engine
2013 if (Utils::ForeignApps::pythonInfo().isSupportedVersion())
2015 m_ui->actionSearchWidget->setChecked(true);
2016 displaySearchTab(true);
2019 #endif // Q_OS_WIN