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