qBittorrent
mainwindow.cpp
Go to the documentation of this file.
1 /*
2  * Bittorrent Client using Qt and libtorrent.
3  * Copyright (C) 2006 Christophe Dumez <[email protected]>
4  *
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.
9  *
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.
14  *
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.
18  *
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.
27  */
28 
29 #include "mainwindow.h"
30 
31 #include <chrono>
32 
33 #include <QActionGroup>
34 #include <QClipboard>
35 #include <QCloseEvent>
36 #include <QDebug>
37 #include <QDesktopServices>
38 #include <QFileDialog>
39 #include <QFileSystemWatcher>
40 #include <QKeyEvent>
41 #include <QMessageBox>
42 #include <QMimeData>
43 #include <QProcess>
44 #include <QPushButton>
45 #include <QShortcut>
46 #include <QSplitter>
47 #include <QStatusBar>
48 #include <QtGlobal>
49 #include <QTimer>
50 
51 #if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) && defined(QT_DBUS_LIB)
52 #include <QDBusConnection>
53 #include "qtnotify/notifications.h"
54 #endif
55 
58 #include "base/global.h"
60 #include "base/preferences.h"
61 #include "base/rss/rss_folder.h"
62 #include "base/rss/rss_session.h"
63 #include "base/utils/foreignapps.h"
64 #include "base/utils/fs.h"
65 #include "base/utils/misc.h"
66 #include "base/utils/password.h"
67 #include "base/version.h"
68 #include "aboutdialog.h"
69 #include "addnewtorrentdialog.h"
70 #include "autoexpandabledialog.h"
71 #include "cookiesdialog.h"
72 #include "downloadfromurldialog.h"
73 #include "executionlogwidget.h"
74 #include "hidabletabwidget.h"
75 #include "lineedit.h"
76 #include "optionsdialog.h"
81 #include "rss/rsswidget.h"
82 #include "search/searchwidget.h"
83 #include "speedlimitdialog.h"
84 #include "statsdialog.h"
85 #include "statusbar.h"
86 #include "torrentcreatordialog.h"
88 #include "transferlistmodel.h"
89 #include "transferlistwidget.h"
90 #include "ui_mainwindow.h"
91 #include "uithememanager.h"
92 #include "utils.h"
93 
94 #ifdef Q_OS_MACOS
95 #include "macutilities.h"
96 #endif
97 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
98 #include "programupdater.h"
99 #endif
100 
101 using namespace std::chrono_literals;
102 
103 namespace
104 {
105 #define SETTINGS_KEY(name) "GUI/" name
106 #define EXECUTIONLOG_SETTINGS_KEY(name) (SETTINGS_KEY("Log/") name)
107 #define NOTIFICATIONS_SETTINGS_KEY(name) (SETTINGS_KEY("Notifications/") name)
108 
109  const std::chrono::seconds PREVENT_SUSPEND_INTERVAL {60};
110 #if !defined(Q_OS_MACOS)
111  const int TIME_TRAY_BALLOON = 5000;
112 #endif
113 
114  bool isTorrentLink(const QString &str)
115  {
116  return str.startsWith(QLatin1String("magnet:"), Qt::CaseInsensitive)
117  || str.endsWith(QLatin1String(C_TORRENT_FILE_EXTENSION), Qt::CaseInsensitive)
118  || (!str.startsWith(QLatin1String("file:"), Qt::CaseInsensitive)
120  }
121 }
122 
123 MainWindow::MainWindow(QWidget *parent)
124  : QMainWindow(parent)
125  , m_ui(new Ui::MainWindow)
126  , m_storeExecutionLogEnabled(EXECUTIONLOG_SETTINGS_KEY("Enabled"))
127  , m_storeDownloadTrackerFavicon(SETTINGS_KEY("DownloadTrackerFavicon"))
128  , m_storeNotificationEnabled(NOTIFICATIONS_SETTINGS_KEY("Enabled"))
129  , m_storeNotificationTorrentAdded(NOTIFICATIONS_SETTINGS_KEY("TorrentAdded"))
130  , m_storeExecutionLogTypes(EXECUTIONLOG_SETTINGS_KEY("Types"), Log::MsgType::ALL)
131 #if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) && defined(QT_DBUS_LIB)
132  , m_storeNotificationTimeOut(NOTIFICATIONS_SETTINGS_KEY("Timeout"))
133 #endif
134 {
135  m_ui->setupUi(this);
136 
137  Preferences *const pref = Preferences::instance();
138  m_uiLocked = pref->isUILocked();
139  setWindowTitle("qBittorrent " QBT_VERSION);
141  // Setting icons
142 #ifndef Q_OS_MACOS
143  const QIcon appLogo(UIThemeManager::instance()->getIcon(QLatin1String("qbittorrent"), QLatin1String("qbittorrent-tray")));
144  setWindowIcon(appLogo);
145 #endif // Q_OS_MACOS
146 
147 #if (defined(Q_OS_UNIX))
148  m_ui->actionOptions->setText(tr("Preferences"));
149 #endif
150 
152 
153  m_ui->actionOpen->setIcon(UIThemeManager::instance()->getIcon("list-add"));
154  m_ui->actionDownloadFromURL->setIcon(UIThemeManager::instance()->getIcon("insert-link"));
155  m_ui->actionSetGlobalSpeedLimits->setIcon(UIThemeManager::instance()->getIcon("speedometer"));
156  m_ui->actionCreateTorrent->setIcon(UIThemeManager::instance()->getIcon("document-edit"));
157  m_ui->actionAbout->setIcon(UIThemeManager::instance()->getIcon("help-about"));
158  m_ui->actionStatistics->setIcon(UIThemeManager::instance()->getIcon("view-statistics"));
159  m_ui->actionTopQueuePos->setIcon(UIThemeManager::instance()->getIcon("go-top"));
160  m_ui->actionIncreaseQueuePos->setIcon(UIThemeManager::instance()->getIcon("go-up"));
161  m_ui->actionDecreaseQueuePos->setIcon(UIThemeManager::instance()->getIcon("go-down"));
162  m_ui->actionBottomQueuePos->setIcon(UIThemeManager::instance()->getIcon("go-bottom"));
163  m_ui->actionDelete->setIcon(UIThemeManager::instance()->getIcon("list-remove"));
164  m_ui->actionDocumentation->setIcon(UIThemeManager::instance()->getIcon("help-contents"));
165  m_ui->actionDonateMoney->setIcon(UIThemeManager::instance()->getIcon("wallet-open"));
166  m_ui->actionExit->setIcon(UIThemeManager::instance()->getIcon("application-exit"));
167  m_ui->actionLock->setIcon(UIThemeManager::instance()->getIcon("object-locked"));
168  m_ui->actionOptions->setIcon(UIThemeManager::instance()->getIcon("configure", "preferences-system"));
169  m_ui->actionPause->setIcon(UIThemeManager::instance()->getIcon("media-playback-pause"));
170  m_ui->actionPauseAll->setIcon(UIThemeManager::instance()->getIcon("media-playback-pause"));
171  m_ui->actionStart->setIcon(UIThemeManager::instance()->getIcon("media-playback-start"));
172  m_ui->actionStartAll->setIcon(UIThemeManager::instance()->getIcon("media-playback-start"));
173  m_ui->menuAutoShutdownOnDownloadsCompletion->setIcon(UIThemeManager::instance()->getIcon("application-exit"));
174  m_ui->actionManageCookies->setIcon(UIThemeManager::instance()->getIcon("preferences-web-browser-cookies"));
175 
176  auto *lockMenu = new QMenu(this);
177  lockMenu->addAction(tr("&Set Password"), this, &MainWindow::defineUILockPassword);
178  lockMenu->addAction(tr("&Clear Password"), this, &MainWindow::clearUILockPassword);
179  m_ui->actionLock->setMenu(lockMenu);
180  connect(this, &MainWindow::systemTrayIconCreated, this, [this]()
181  {
182  m_ui->actionLock->setVisible(true);
183  });
184 
185  // Creating Bittorrent session
186  updateAltSpeedsBtn(BitTorrent::Session::instance()->isAltGlobalSpeedLimitEnabled());
187 
195 
196  qDebug("create tabWidget");
197  m_tabs = new HidableTabWidget(this);
198  connect(m_tabs.data(), &QTabWidget::currentChanged, this, &MainWindow::tabChanged);
199 
200  m_splitter = new QSplitter(Qt::Horizontal, this);
201  // vSplitter->setChildrenCollapsible(false);
202 
203  auto *hSplitter = new QSplitter(Qt::Vertical, this);
204  hSplitter->setChildrenCollapsible(false);
205  hSplitter->setFrameShape(QFrame::NoFrame);
206 
207  // Name filter
208  m_searchFilter = new LineEdit(this);
209  m_searchFilter->setPlaceholderText(tr("Filter torrent names..."));
210  m_searchFilter->setFixedWidth(Utils::Gui::scaledSize(this, 200));
211  m_searchFilter->setContextMenuPolicy(Qt::CustomContextMenu);
212  connect(m_searchFilter, &QWidget::customContextMenuRequested, this, &MainWindow::showFilterContextMenu);
213  m_searchFilterAction = m_ui->toolBar->insertWidget(m_ui->actionLock, m_searchFilter);
214 
215  QWidget *spacer = new QWidget(this);
216  spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
217  m_ui->toolBar->insertWidget(m_searchFilterAction, spacer);
218 
219  // Transfer List tab
220  m_transferListWidget = new TransferListWidget(hSplitter, this);
221  // m_transferListWidget->setStyleSheet("QTreeView {border: none;}"); // borderless
222  m_propertiesWidget = new PropertiesWidget(hSplitter);
225  hSplitter->addWidget(m_transferListWidget);
226  hSplitter->addWidget(m_propertiesWidget);
228  m_splitter->addWidget(hSplitter);
229  m_splitter->setCollapsible(0, true);
230  m_splitter->setCollapsible(1, false);
231  m_tabs->addTab(m_splitter,
232 #ifndef Q_OS_MACOS
233  UIThemeManager::instance()->getIcon("folder-remote"),
234 #endif
235  tr("Transfers"));
236 
237  connect(m_searchFilter, &LineEdit::textChanged, m_transferListWidget, &TransferListWidget::applyNameFilter);
238  connect(hSplitter, &QSplitter::splitterMoved, this, &MainWindow::writeSettings);
239  connect(m_splitter, &QSplitter::splitterMoved, this, &MainWindow::writeSettings);
244 
246  , m_transferListFiltersWidget, qOverload<const BitTorrent::Torrent *, const QString &>(&TransferListFiltersWidget::trackerSuccess));
248  , m_transferListFiltersWidget, qOverload<const BitTorrent::Torrent *, const QString &>(&TransferListFiltersWidget::trackerError));
250  , m_transferListFiltersWidget, qOverload<const BitTorrent::Torrent *, const QString &>(&TransferListFiltersWidget::trackerWarning));
251 
252 #ifdef Q_OS_MACOS
253  // Increase top spacing to avoid tab overlapping
254  m_ui->centralWidgetLayout->addSpacing(8);
255 #endif
256 
257  m_ui->centralWidgetLayout->addWidget(m_tabs);
258 
259  m_queueSeparator = m_ui->toolBar->insertSeparator(m_ui->actionTopQueuePos);
260  m_queueSeparatorMenu = m_ui->menuEdit->insertSeparator(m_ui->actionTopQueuePos);
261 
262 #ifdef Q_OS_MACOS
263  for (QAction *action : asConst(m_ui->toolBar->actions()))
264  {
265  if (action->isSeparator())
266  {
267  QWidget *spacer = new QWidget(this);
268  spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
269  spacer->setMinimumWidth(16);
270  m_ui->toolBar->insertWidget(action, spacer);
271  m_ui->toolBar->removeAction(action);
272  }
273  }
274  {
275  QWidget *spacer = new QWidget(this);
276  spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
277  spacer->setMinimumWidth(8);
278  m_ui->toolBar->insertWidget(m_ui->actionDownloadFromURL, spacer);
279  }
280  {
281  QWidget *spacer = new QWidget(this);
282  spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
283  spacer->setMinimumWidth(8);
284  m_ui->toolBar->addWidget(spacer);
285  }
286 #endif // Q_OS_MACOS
287 
288  // Transfer list slots
289  connect(m_ui->actionStart, &QAction::triggered, m_transferListWidget, &TransferListWidget::startSelectedTorrents);
290  connect(m_ui->actionStartAll, &QAction::triggered, m_transferListWidget, &TransferListWidget::resumeAllTorrents);
291  connect(m_ui->actionPause, &QAction::triggered, m_transferListWidget, &TransferListWidget::pauseSelectedTorrents);
292  connect(m_ui->actionPauseAll, &QAction::triggered, m_transferListWidget, &TransferListWidget::pauseAllTorrents);
293  connect(m_ui->actionDelete, &QAction::triggered, m_transferListWidget, &TransferListWidget::softDeleteSelectedTorrents);
294  connect(m_ui->actionTopQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::topQueuePosSelectedTorrents);
295  connect(m_ui->actionIncreaseQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::increaseQueuePosSelectedTorrents);
296  connect(m_ui->actionDecreaseQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::decreaseQueuePosSelectedTorrents);
297  connect(m_ui->actionBottomQueuePos, &QAction::triggered, m_transferListWidget, &TransferListWidget::bottomQueuePosSelectedTorrents);
298 #ifndef Q_OS_MACOS
299  connect(m_ui->actionToggleVisibility, &QAction::triggered, this, [this]() { toggleVisibility(); });
300 #endif
301  connect(m_ui->actionMinimize, &QAction::triggered, this, &MainWindow::minimizeWindow);
302  connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds);
303 
304 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
305  connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, [this]() { checkProgramUpdate(true); });
306 
307  // trigger an early check on startup
308  if (pref->isUpdateCheckEnabled())
309  checkProgramUpdate(false);
310 #else
311  m_ui->actionCheckForUpdates->setVisible(false);
312 #endif
313 
314  // Certain menu items should reside at specific places on macOS.
315  // Qt partially does it on its own, but updates and different languages require tuning.
316  m_ui->actionExit->setMenuRole(QAction::QuitRole);
317  m_ui->actionAbout->setMenuRole(QAction::AboutRole);
318  m_ui->actionCheckForUpdates->setMenuRole(QAction::ApplicationSpecificRole);
319  m_ui->actionOptions->setMenuRole(QAction::PreferencesRole);
320 
321  connect(m_ui->actionManageCookies, &QAction::triggered, this, &MainWindow::manageCookies);
322 
323  m_pwr = new PowerManagement(this);
324  m_preventTimer = new QTimer(this);
325  connect(m_preventTimer, &QTimer::timeout, this, &MainWindow::updatePowerManagementState);
326 
327  // Configure BT session according to options
328  loadPreferences();
329 
332 
333  // Accept drag 'n drops
334  setAcceptDrops(true);
336 
337 #ifdef Q_OS_MACOS
338  setUnifiedTitleAndToolBarOnMac(true);
339 #endif
340 
341  // View settings
342  m_ui->actionTopToolBar->setChecked(pref->isToolbarDisplayed());
343  m_ui->actionShowStatusbar->setChecked(pref->isStatusbarDisplayed());
344  m_ui->actionSpeedInTitleBar->setChecked(pref->speedInTitleBar());
345  m_ui->actionRSSReader->setChecked(pref->isRSSWidgetEnabled());
346  m_ui->actionSearchWidget->setChecked(pref->isSearchEnabled());
347  m_ui->actionExecutionLogs->setChecked(isExecutionLogEnabled());
348 
349  const Log::MsgTypes flags = executionLogMsgTypes();
350  m_ui->actionNormalMessages->setChecked(flags.testFlag(Log::NORMAL));
351  m_ui->actionInformationMessages->setChecked(flags.testFlag(Log::INFO));
352  m_ui->actionWarningMessages->setChecked(flags.testFlag(Log::WARNING));
353  m_ui->actionCriticalMessages->setChecked(flags.testFlag(Log::CRITICAL));
354 
355  displayRSSTab(m_ui->actionRSSReader->isChecked());
356  on_actionExecutionLogs_triggered(m_ui->actionExecutionLogs->isChecked());
357  on_actionNormalMessages_triggered(m_ui->actionNormalMessages->isChecked());
358  on_actionInformationMessages_triggered(m_ui->actionInformationMessages->isChecked());
359  on_actionWarningMessages_triggered(m_ui->actionWarningMessages->isChecked());
360  on_actionCriticalMessages_triggered(m_ui->actionCriticalMessages->isChecked());
361  if (m_ui->actionSearchWidget->isChecked())
362  QTimer::singleShot(0, this, &MainWindow::on_actionSearchWidget_triggered);
363 
364  // Auto shutdown actions
365  auto *autoShutdownGroup = new QActionGroup(this);
366  autoShutdownGroup->setExclusive(true);
367  autoShutdownGroup->addAction(m_ui->actionAutoShutdownDisabled);
368  autoShutdownGroup->addAction(m_ui->actionAutoExit);
369  autoShutdownGroup->addAction(m_ui->actionAutoShutdown);
370  autoShutdownGroup->addAction(m_ui->actionAutoSuspend);
371  autoShutdownGroup->addAction(m_ui->actionAutoHibernate);
372 #if (!defined(Q_OS_UNIX) || defined(Q_OS_MACOS)) || defined(QT_DBUS_LIB)
373  m_ui->actionAutoShutdown->setChecked(pref->shutdownWhenDownloadsComplete());
374  m_ui->actionAutoSuspend->setChecked(pref->suspendWhenDownloadsComplete());
375  m_ui->actionAutoHibernate->setChecked(pref->hibernateWhenDownloadsComplete());
376 #else
377  m_ui->actionAutoShutdown->setDisabled(true);
378  m_ui->actionAutoSuspend->setDisabled(true);
379  m_ui->actionAutoHibernate->setDisabled(true);
380 #endif
381  m_ui->actionAutoExit->setChecked(pref->shutdownqBTWhenDownloadsComplete());
382 
383  if (!autoShutdownGroup->checkedAction())
384  m_ui->actionAutoShutdownDisabled->setChecked(true);
385 
386  // Load Window state and sizes
387  readSettings();
388 
389 #ifdef Q_OS_MACOS
390  // Make sure the Window is visible if we don't have a tray icon
391  if (pref->startMinimized())
392  {
393  showMinimized();
394  }
395  else
396  {
397  show();
398  activateWindow();
399  raise();
400  }
401 #else
402  if (m_systrayIcon)
403  {
404  if (!(pref->startMinimized() || m_uiLocked))
405  {
406  show();
407  activateWindow();
408  raise();
409  }
410  else if (pref->startMinimized())
411  {
412  showMinimized();
413  if (pref->minimizeToTray())
414  {
415  hide();
416  if (!pref->minimizeToTrayNotified())
417  {
418  showNotificationBalloon(tr("qBittorrent is minimized to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
419  pref->setMinimizeToTrayNotified(true);
420  }
421  }
422  }
423  }
424  else
425  {
426  // Make sure the Window is visible if we don't have a tray icon
427  if (pref->startMinimized())
428  {
429  showMinimized();
430  }
431  else
432  {
433  show();
434  activateWindow();
435  raise();
436  }
437  }
438 #endif
439 
441 
442  // Start watching the executable for updates
443  m_executableWatcher = new QFileSystemWatcher(this);
444  connect(m_executableWatcher, &QFileSystemWatcher::fileChanged, this, &MainWindow::notifyOfUpdate);
445  m_executableWatcher->addPath(qApp->applicationFilePath());
446 
447  m_transferListWidget->setFocus();
448 
449  // Update the number of torrents (tab)
451  connect(m_transferListWidget->getSourceModel(), &QAbstractItemModel::rowsInserted, this, &MainWindow::updateNbTorrents);
452  connect(m_transferListWidget->getSourceModel(), &QAbstractItemModel::rowsRemoved, this, &MainWindow::updateNbTorrents);
453 
454  connect(pref, &Preferences::changed, this, &MainWindow::optionsSaved);
455 
456  qDebug("GUI Built");
457 #ifdef Q_OS_WIN
458  if (!pref->neverCheckFileAssoc() && (!Preferences::isTorrentFileAssocSet() || !Preferences::isMagnetLinkAssocSet()))
459  {
460  if (QMessageBox::question(this, tr("Torrent file association"),
461  tr("qBittorrent is not the default application for opening torrent files or Magnet links.\nDo you want to make qBittorrent the default application for these?"),
462  QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes)
463  {
464  Preferences::setTorrentFileAssoc(true);
465  Preferences::setMagnetLinkAssoc(true);
466  }
467  else
468  {
469  pref->setNeverCheckFileAssoc();
470  }
471  }
472 #endif
473 #ifdef Q_OS_MACOS
474  setupDockClickHandler();
476  m_trayIconMenu->setAsDockMenu();
477 #endif
478 }
479 
481 {
482  delete m_ui;
483 }
484 
486 {
488 }
489 
491 {
493 }
494 
495 Log::MsgTypes MainWindow::executionLogMsgTypes() const
496 {
498 }
499 
501 {
502  m_executionLog->setMessageTypes(value);
504 }
505 
507 {
508  return m_storeNotificationEnabled.get(true);
509 }
510 
512 {
514 }
515 
517 {
519 }
520 
522 {
524 }
525 
526 #if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) && defined(QT_DBUS_LIB)
527 int MainWindow::getNotificationTimeout() const
528 {
529  return m_storeNotificationTimeOut.get(-1);
530 }
531 
532 void MainWindow::setNotificationTimeout(const int value)
533 {
534  m_storeNotificationTimeOut = value;
535 }
536 #endif
537 
539 {
541 }
542 
544 {
547 }
548 
550 {
551  const Preferences *const pref = Preferences::instance();
552  m_toolbarMenu = new QMenu(this);
553 
554  m_ui->toolBar->setContextMenuPolicy(Qt::CustomContextMenu);
555  connect(m_ui->toolBar, &QWidget::customContextMenuRequested, this, &MainWindow::toolbarMenuRequested);
556 
557  QAction *iconsOnly = m_toolbarMenu->addAction(tr("Icons Only"), this, &MainWindow::toolbarIconsOnly);
558  QAction *textOnly = m_toolbarMenu->addAction(tr("Text Only"), this, &MainWindow::toolbarTextOnly);
559  QAction *textBesideIcons = m_toolbarMenu->addAction(tr("Text Alongside Icons"), this, &MainWindow::toolbarTextBeside);
560  QAction *textUnderIcons = m_toolbarMenu->addAction(tr("Text Under Icons"), this, &MainWindow::toolbarTextUnder);
561  QAction *followSystemStyle = m_toolbarMenu->addAction(tr("Follow System Style"), this, &MainWindow::toolbarFollowSystem);
562 
563  auto *textPositionGroup = new QActionGroup(m_toolbarMenu);
564  textPositionGroup->addAction(iconsOnly);
565  iconsOnly->setCheckable(true);
566  textPositionGroup->addAction(textOnly);
567  textOnly->setCheckable(true);
568  textPositionGroup->addAction(textBesideIcons);
569  textBesideIcons->setCheckable(true);
570  textPositionGroup->addAction(textUnderIcons);
571  textUnderIcons->setCheckable(true);
572  textPositionGroup->addAction(followSystemStyle);
573  followSystemStyle->setCheckable(true);
574 
575  const auto buttonStyle = static_cast<Qt::ToolButtonStyle>(pref->getToolbarTextPosition());
576  if ((buttonStyle >= Qt::ToolButtonIconOnly) && (buttonStyle <= Qt::ToolButtonFollowStyle))
577  m_ui->toolBar->setToolButtonStyle(buttonStyle);
578  switch (buttonStyle)
579  {
580  case Qt::ToolButtonIconOnly:
581  iconsOnly->setChecked(true);
582  break;
583  case Qt::ToolButtonTextOnly:
584  textOnly->setChecked(true);
585  break;
586  case Qt::ToolButtonTextBesideIcon:
587  textBesideIcons->setChecked(true);
588  break;
589  case Qt::ToolButtonTextUnderIcon:
590  textUnderIcons->setChecked(true);
591  break;
592  default:
593  followSystemStyle->setChecked(true);
594  }
595 }
596 
598 {
599  auto *cookieDialog = new CookiesDialog(this);
600  cookieDialog->setAttribute(Qt::WA_DeleteOnClose);
601  cookieDialog->open();
602 }
603 
604 void MainWindow::toolbarMenuRequested(const QPoint &point)
605 {
606  m_toolbarMenu->popup(m_ui->toolBar->mapToGlobal(point));
607 }
608 
610 {
611  m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
612  Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonIconOnly);
613 }
614 
616 {
617  m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextOnly);
618  Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextOnly);
619 }
620 
622 {
623  m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
624  Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextBesideIcon);
625 }
626 
628 {
629  m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
630  Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextUnderIcon);
631 }
632 
634 {
635  m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle);
636  Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonFollowStyle);
637 }
638 
640 {
641  bool ok = false;
642  const QString newPassword = AutoExpandableDialog::getText(this, tr("UI lock password")
643  , tr("Please type the UI lock password:"), QLineEdit::Password, {}, &ok);
644  if (!ok)
645  return false;
646 
647  if (newPassword.size() < 3)
648  {
649  QMessageBox::warning(this, tr("Invalid password"), tr("The password must be at least 3 characters long"));
650  return false;
651  }
652 
654  return true;
655 }
656 
658 {
659  const QMessageBox::StandardButton answer = QMessageBox::question(this, tr("Clear the password")
660  , tr("Are you sure you want to clear the password?"), (QMessageBox::Yes | QMessageBox::No), QMessageBox::No);
661  if (answer == QMessageBox::Yes)
663 }
664 
666 {
667  Preferences *const pref = Preferences::instance();
668 
669  // Check if there is a password
670  if (pref->getUILockPassword().isEmpty())
671  {
672  if (!defineUILockPassword())
673  return;
674  }
675 
676  // Lock the interface
677  m_uiLocked = true;
678  pref->setUILocked(true);
679  m_trayIconMenu->setEnabled(false);
680  hide();
681 }
682 
684 {
685  m_tabs->setTabText(m_tabs->indexOf(m_rssWidget), tr("RSS (%1)").arg(count));
686 }
687 
688 void MainWindow::displayRSSTab(bool enable)
689 {
690  if (enable)
691  {
692  // RSS tab
693  if (!m_rssWidget)
694  {
697 #ifdef Q_OS_MACOS
698  m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
699 #else
700  const int indexTab = m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
701  m_tabs->setTabIcon(indexTab, UIThemeManager::instance()->getIcon("application-rss+xml"));
702 #endif
703  }
704  }
705  else
706  {
707  delete m_rssWidget;
708  }
709 }
710 
712 {
713  const Preferences *pref = Preferences::instance();
714 
715  QMenu *menu = m_searchFilter->createStandardContextMenu();
716  menu->setAttribute(Qt::WA_DeleteOnClose);
717  menu->addSeparator();
718 
719  QAction *useRegexAct = menu->addAction(tr("Use regular expressions"));
720  useRegexAct->setCheckable(true);
721  useRegexAct->setChecked(pref->getRegexAsFilteringPatternForTransferList());
722  connect(useRegexAct, &QAction::toggled, pref, &Preferences::setRegexAsFilteringPatternForTransferList);
723  connect(useRegexAct, &QAction::toggled, this, [this]() { m_transferListWidget->applyNameFilter(m_searchFilter->text()); });
724 
725  menu->popup(QCursor::pos());
726 }
727 
729 {
731  if (enable)
732  {
733  // RSS tab
734  if (!m_searchWidget)
735  {
736  m_searchWidget = new SearchWidget(this);
737  m_tabs->insertTab(1, m_searchWidget,
738 #ifndef Q_OS_MACOS
739  UIThemeManager::instance()->getIcon("edit-find"),
740 #endif
741  tr("Search"));
742  }
743  }
744  else
745  {
746  delete m_searchWidget;
747  }
748 }
749 
751 {
752  m_searchFilter->setFocus();
753  m_searchFilter->selectAll();
754 }
755 
757 {
758  m_tabs->setTabText(0, tr("Transfers (%1)").arg(m_transferListWidget->getSourceModel()->rowCount()));
759 }
760 
762 {
763  QDesktopServices::openUrl(QUrl("http://doc.qbittorrent.org"));
764 }
765 
766 void MainWindow::tabChanged(int newTab)
767 {
768  Q_UNUSED(newTab);
769  // We cannot rely on the index newTab
770  // because the tab order is undetermined now
771  if (m_tabs->currentWidget() == m_splitter)
772  {
773  qDebug("Changed tab to transfer list, refreshing the list");
775  m_searchFilterAction->setVisible(true);
776  return;
777  }
778  m_searchFilterAction->setVisible(false);
779 
780  if (m_tabs->currentWidget() == m_searchWidget)
781  {
782  qDebug("Changed tab to search engine, giving focus to search input");
783  m_searchWidget->giveFocusToSearchInput();
784  }
785 }
786 
788 {
789  Preferences *const pref = Preferences::instance();
790  pref->setMainGeometry(saveGeometry());
791  // Splitter size
792  pref->setMainVSplitterState(m_splitter->saveState());
794 }
795 
797 {
798  writeSettings();
799 
800  // delete RSSWidget explicitly to avoid crash in
801  // handleRSSUnreadCountUpdated() at application shutdown
802  delete m_rssWidget;
803 
804  delete m_executableWatcher;
805 
806  m_preventTimer->stop();
807 
808 #if (defined(Q_OS_WIN) || defined(Q_OS_MACOS))
809  if (m_programUpdateTimer)
810  m_programUpdateTimer->stop();
811 #endif
812 
813  // remove all child widgets
814  while (auto *w = findChild<QWidget *>())
815  delete w;
816 }
817 
819 {
820  const Preferences *const pref = Preferences::instance();
821  const QByteArray mainGeo = pref->getMainGeometry();
822  if (!mainGeo.isEmpty() && restoreGeometry(mainGeo))
823  m_posInitialized = true;
824  const QByteArray splitterState = pref->getMainVSplitterState();
825  if (splitterState.isEmpty())
826  // Default sizes
827  m_splitter->setSizes({ 120, m_splitter->width() - 120 });
828  else
829  m_splitter->restoreState(splitterState);
830 }
831 
833 {
834  if (isHidden())
835  {
836  if (m_uiLocked)
837  {
838  // Ask for UI lock password
839  if (!unlockUI())
840  return;
841  }
842  show();
843  if (isMinimized())
844  showNormal();
845  }
846 
847  raise();
848  activateWindow();
849 }
850 
851 void MainWindow::addTorrentFailed(const QString &error) const
852 {
853  showNotificationBalloon(tr("Error"), tr("Failed to add torrent: %1").arg(error));
854 }
855 
856 // called when a torrent was added
857 void MainWindow::torrentNew(BitTorrent::Torrent *const torrent) const
858 {
860  showNotificationBalloon(tr("Torrent added"), tr("'%1' was added.", "e.g: xxx.avi was added.").arg(torrent->name()));
861 }
862 
863 // called when a torrent has finished
865 {
866  showNotificationBalloon(tr("Download completed"), tr("'%1' has finished downloading.", "e.g: xxx.avi has finished downloading.").arg(torrent->name()));
867 }
868 
869 // Notification when disk is full
870 void MainWindow::fullDiskError(BitTorrent::Torrent *const torrent, const QString &msg) const
871 {
872  showNotificationBalloon(tr("I/O Error", "i.e: Input/Output Error")
873  , tr("An I/O error occurred for torrent '%1'.\n Reason: %2"
874  , "e.g: An error occurred for torrent 'xxx.avi'.\n Reason: disk is full.").arg(torrent->name(), msg));
875 }
876 
878 {
879  m_ui->actionCreateTorrent->setShortcut(QKeySequence::New);
880  m_ui->actionOpen->setShortcut(QKeySequence::Open);
881  m_ui->actionDelete->setShortcut(QKeySequence::Delete);
882  m_ui->actionDelete->setShortcutContext(Qt::WidgetShortcut); // nullify its effect: delete key event is handled by respective widgets, not here
883  m_ui->actionDownloadFromURL->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_O);
884  m_ui->actionExit->setShortcut(Qt::CTRL | Qt::Key_Q);
885 #ifdef Q_OS_MACOS
886  m_ui->actionCloseWindow->setShortcut(QKeySequence::Close);
887 #else
888  m_ui->actionCloseWindow->setVisible(false);
889 #endif
890 
891  const auto *switchTransferShortcut = new QShortcut((Qt::ALT | Qt::Key_1), this);
892  connect(switchTransferShortcut, &QShortcut::activated, this, &MainWindow::displayTransferTab);
893  const auto *switchSearchShortcut = new QShortcut((Qt::ALT | Qt::Key_2), this);
894  connect(switchSearchShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displaySearchTab));
895  const auto *switchRSSShortcut = new QShortcut((Qt::ALT | Qt::Key_3), this);
896  connect(switchRSSShortcut, &QShortcut::activated, this, qOverload<>(&MainWindow::displayRSSTab));
897  const auto *switchExecutionLogShortcut = new QShortcut((Qt::ALT | Qt::Key_4), this);
898  connect(switchExecutionLogShortcut, &QShortcut::activated, this, &MainWindow::displayExecutionLogTab);
899  const auto *switchSearchFilterShortcut = new QShortcut(QKeySequence::Find, m_transferListWidget);
900  connect(switchSearchFilterShortcut, &QShortcut::activated, this, &MainWindow::focusSearchFilter);
901 
902  m_ui->actionDocumentation->setShortcut(QKeySequence::HelpContents);
903  m_ui->actionOptions->setShortcut(Qt::ALT | Qt::Key_O);
904  m_ui->actionStatistics->setShortcut(Qt::CTRL | Qt::Key_I);
905  m_ui->actionStart->setShortcut(Qt::CTRL | Qt::Key_S);
906  m_ui->actionStartAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_S);
907  m_ui->actionPause->setShortcut(Qt::CTRL | Qt::Key_P);
908  m_ui->actionPauseAll->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_P);
909  m_ui->actionBottomQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Minus);
910  m_ui->actionDecreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Minus);
911  m_ui->actionIncreaseQueuePos->setShortcut(Qt::CTRL | Qt::Key_Plus);
912  m_ui->actionTopQueuePos->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Plus);
913 #ifdef Q_OS_MACOS
914  m_ui->actionMinimize->setShortcut(Qt::CTRL + Qt::Key_M);
915  addAction(m_ui->actionMinimize);
916 #endif
917 }
918 
919 // Keyboard shortcuts slots
921 {
922  m_tabs->setCurrentWidget(m_transferListWidget);
923 }
924 
926 {
927  if (!m_searchWidget)
928  {
929  m_ui->actionSearchWidget->setChecked(true);
930  displaySearchTab(true);
931  }
932 
933  m_tabs->setCurrentWidget(m_searchWidget);
934 }
935 
937 {
938  if (!m_rssWidget)
939  {
940  m_ui->actionRSSReader->setChecked(true);
941  displayRSSTab(true);
942  }
943 
944  m_tabs->setCurrentWidget(m_rssWidget);
945 }
946 
948 {
949  if (!m_executionLog)
950  {
951  m_ui->actionExecutionLogs->setChecked(true);
953  }
954 
955  m_tabs->setCurrentWidget(m_executionLog);
956 }
957 
958 // End of keyboard shortcuts slots
959 
961 {
962  Preferences *const pref = Preferences::instance();
963  if (pref->recursiveDownloadDisabled()) return;
964 
965  const auto torrentID = torrent->id();
966 
967  QMessageBox *confirmBox = new QMessageBox(QMessageBox::Question, tr("Recursive download confirmation")
968  , tr("The torrent '%1' contains torrent files, do you want to proceed with their download?").arg(torrent->name())
969  , QMessageBox::NoButton, this);
970  confirmBox->setAttribute(Qt::WA_DeleteOnClose);
971  confirmBox->setModal(true);
972 
973  const QPushButton *yes = confirmBox->addButton(tr("Yes"), QMessageBox::YesRole);
974  /*QPushButton *no = */ confirmBox->addButton(tr("No"), QMessageBox::NoRole);
975  const QPushButton *never = confirmBox->addButton(tr("Never"), QMessageBox::NoRole);
976  connect(confirmBox, &QMessageBox::buttonClicked, this, [torrentID, yes, never](const QAbstractButton *button)
977  {
978  if (button == yes)
979  BitTorrent::Session::instance()->recursiveTorrentDownload(torrentID);
980  if (button == never)
981  Preferences::instance()->disableRecursiveDownload();
982  });
983  confirmBox->show();
984 }
985 
986 void MainWindow::handleDownloadFromUrlFailure(const QString &url, const QString &reason) const
987 {
988  // Display a message box
989  showNotificationBalloon(tr("URL download error")
990  , tr("Couldn't download file at URL '%1', reason: %2.").arg(url, reason));
991 }
992 
994 {
995  auto dialog = new SpeedLimitDialog {this};
996  dialog->setAttribute(Qt::WA_DeleteOnClose);
997  dialog->open();
998 }
999 
1000 // Necessary if we want to close the window
1001 // in one time if "close to systray" is enabled
1003 {
1004  // UI locking enforcement.
1005  if (isHidden() && m_uiLocked)
1006  // Ask for UI lock password
1007  if (!unlockUI()) return;
1008 
1009  m_forceExit = true;
1010  close();
1011 }
1012 
1013 #ifdef Q_OS_MACOS
1014 void MainWindow::on_actionCloseWindow_triggered()
1015 {
1016  // On macOS window close is basically equivalent to window hide.
1017  // If you decide to implement this functionality for other OS,
1018  // then you will also need ui lock checks like in actionExit.
1019  close();
1020 }
1021 #endif
1022 
1024 {
1025  if (isMinimized() || !isVisible())
1026  return nullptr;
1027  if (m_tabs->currentIndex() == 0)
1028  return m_transferListWidget;
1029  return m_tabs->currentWidget();
1030 }
1031 
1033 {
1034  return m_transferListWidget;
1035 }
1036 
1038 {
1039  if (m_unlockDlgShowing)
1040  return false;
1041 
1042  bool ok = false;
1043  const QString password = AutoExpandableDialog::getText(this, tr("UI lock password")
1044  , tr("Please type the UI lock password:"), QLineEdit::Password, {}, &ok);
1045  if (!ok) return false;
1046 
1047  Preferences *const pref = Preferences::instance();
1048 
1049  const QByteArray secret = pref->getUILockPassword();
1050  if (!Utils::Password::PBKDF2::verify(secret, password))
1051  {
1052  QMessageBox::warning(this, tr("Invalid password"), tr("The password is invalid"));
1053  return false;
1054  }
1055 
1056  m_uiLocked = false;
1057  pref->setUILocked(false);
1058  m_trayIconMenu->setEnabled(true);
1059  return true;
1060 }
1061 
1062 void MainWindow::notifyOfUpdate(const QString &)
1063 {
1064  // Show restart message
1065  m_statusBar->showRestartRequired();
1066  Logger::instance()->addMessage(tr("qBittorrent was just updated and needs to be restarted for the changes to be effective.")
1067  , Log::CRITICAL);
1068  // Delete the executable watcher
1069  delete m_executableWatcher;
1070  m_executableWatcher = nullptr;
1071 }
1072 
1073 #ifndef Q_OS_MACOS
1074 // Toggle Main window visibility
1075 void MainWindow::toggleVisibility(const QSystemTrayIcon::ActivationReason reason)
1076 {
1077  switch (reason)
1078  {
1079  case QSystemTrayIcon::Trigger:
1080  if (isHidden())
1081  {
1082  if (m_uiLocked && !unlockUI()) // Ask for UI lock password
1083  return;
1084 
1085  // Make sure the window is not minimized
1086  setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
1087 
1088  // Then show it
1089  show();
1090  raise();
1091  activateWindow();
1092  }
1093  else
1094  {
1095  hide();
1096  }
1097  break;
1098 
1099  default:
1100  break;
1101  }
1102 }
1103 #endif // Q_OS_MACOS
1104 
1105 // Display About Dialog
1107 {
1108  // About dialog
1109  if (m_aboutDlg)
1110  m_aboutDlg->activateWindow();
1111  else
1112  m_aboutDlg = new AboutDialog(this);
1113 }
1114 
1116 {
1117  if (m_statsDlg)
1118  m_statsDlg->activateWindow();
1119  else
1120  m_statsDlg = new StatsDialog(this);
1121 }
1122 
1123 void MainWindow::showEvent(QShowEvent *e)
1124 {
1125  qDebug("** Show Event **");
1126  e->accept();
1127 
1128  if (isVisible())
1129  {
1130  // preparations before showing the window
1131 
1134 
1135  // Make sure the window is initially centered
1136  if (!m_posInitialized)
1137  {
1138  move(Utils::Gui::screenCenter(this));
1139  m_posInitialized = true;
1140  }
1141  }
1142  else
1143  {
1144  // to avoid blank screen when restoring from tray icon
1145  show();
1146  }
1147 }
1148 
1149 void MainWindow::keyPressEvent(QKeyEvent *event)
1150 {
1151  if (event->matches(QKeySequence::Paste))
1152  {
1153  const QMimeData *mimeData {QGuiApplication::clipboard()->mimeData()};
1154 
1155  if (mimeData->hasText())
1156  {
1157  const bool useTorrentAdditionDialog {AddNewTorrentDialog::isEnabled()};
1158  const QStringList lines {mimeData->text().split('\n', Qt::SkipEmptyParts)};
1159 
1160  for (QString line : lines)
1161  {
1162  line = line.trimmed();
1163 
1164  if (!isTorrentLink(line))
1165  continue;
1166 
1167  if (useTorrentAdditionDialog)
1168  AddNewTorrentDialog::show(line, this);
1169  else
1171  }
1172 
1173  return;
1174  }
1175  }
1176 
1177  QMainWindow::keyPressEvent(event);
1178 }
1179 
1180 // Called when we close the program
1181 void MainWindow::closeEvent(QCloseEvent *e)
1182 {
1183  Preferences *const pref = Preferences::instance();
1184 #ifdef Q_OS_MACOS
1185  if (!m_forceExit)
1186  {
1187  hide();
1188  e->accept();
1189  return;
1190  }
1191 #else
1192  const bool goToSystrayOnExit = pref->closeToTray();
1193  if (!m_forceExit && m_systrayIcon && goToSystrayOnExit && !this->isHidden())
1194  {
1195  e->ignore();
1196  QTimer::singleShot(0, this, &QWidget::hide);
1197  if (!pref->closeToTrayNotified())
1198  {
1199  showNotificationBalloon(tr("qBittorrent is closed to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
1200  pref->setCloseToTrayNotified(true);
1201  }
1202  return;
1203  }
1204 #endif // Q_OS_MACOS
1205 
1207  {
1208  if (e->spontaneous() || m_forceExit)
1209  {
1210  if (!isVisible())
1211  show();
1212  QMessageBox confirmBox(QMessageBox::Question, tr("Exiting qBittorrent"),
1213  // Split it because the last sentence is used in the Web UI
1214  tr("Some files are currently transferring.") + '\n' + tr("Are you sure you want to quit qBittorrent?"),
1215  QMessageBox::NoButton, this);
1216  QPushButton *noBtn = confirmBox.addButton(tr("&No"), QMessageBox::NoRole);
1217  confirmBox.addButton(tr("&Yes"), QMessageBox::YesRole);
1218  QPushButton *alwaysBtn = confirmBox.addButton(tr("&Always Yes"), QMessageBox::YesRole);
1219  confirmBox.setDefaultButton(noBtn);
1220  confirmBox.exec();
1221  if (!confirmBox.clickedButton() || (confirmBox.clickedButton() == noBtn))
1222  {
1223  // Cancel exit
1224  e->ignore();
1225  m_forceExit = false;
1226  return;
1227  }
1228  if (confirmBox.clickedButton() == alwaysBtn)
1229  // Remember choice
1231  }
1232  }
1233 
1234  // Disable some UI to prevent user interactions
1235 #ifndef Q_OS_MACOS
1236  if (m_systrayIcon)
1237  {
1238  m_systrayIcon->setToolTip(tr("qBittorrent is shutting down..."));
1239  m_trayIconMenu->setEnabled(false);
1240  }
1241 #endif
1242 
1243  // Accept exit
1244  e->accept();
1245  qApp->exit();
1246 }
1247 
1248 // Display window to create a torrent
1250 {
1252 }
1253 
1254 void MainWindow::createTorrentTriggered(const QString &path)
1255 {
1256  if (m_createTorrentDlg)
1257  {
1258  m_createTorrentDlg->updateInputPath(path);
1259  m_createTorrentDlg->activateWindow();
1260  }
1261  else
1262  m_createTorrentDlg = new TorrentCreatorDialog(this, path);
1263 }
1264 
1265 bool MainWindow::event(QEvent *e)
1266 {
1267 #ifndef Q_OS_MACOS
1268  switch (e->type())
1269  {
1270  case QEvent::WindowStateChange:
1271  {
1272  qDebug("Window change event");
1273  // Now check to see if the window is minimised
1274  if (isMinimized())
1275  {
1276  qDebug("minimisation");
1277  Preferences *const pref = Preferences::instance();
1278  if (m_systrayIcon && pref->minimizeToTray())
1279  {
1280  qDebug() << "Has active window:" << (qApp->activeWindow() != nullptr);
1281  // Check if there is a modal window
1282  bool hasModalWindow = false;
1283  for (QWidget *widget : asConst(QApplication::allWidgets()))
1284  {
1285  if (widget->isModal())
1286  {
1287  hasModalWindow = true;
1288  break;
1289  }
1290  }
1291  // Iconify if there is no modal window
1292  if (!hasModalWindow)
1293  {
1294  qDebug("Minimize to Tray enabled, hiding!");
1295  e->ignore();
1296  QTimer::singleShot(0, this, &QWidget::hide);
1297  if (!pref->minimizeToTrayNotified())
1298  {
1299  showNotificationBalloon(tr("qBittorrent is minimized to tray"), tr("This behavior can be changed in the settings. You won't be reminded again."));
1300  pref->setMinimizeToTrayNotified(true);
1301  }
1302  return true;
1303  }
1304  }
1305  }
1306  break;
1307  }
1308  case QEvent::ToolBarChange:
1309  {
1310  qDebug("MAC: Received a toolbar change event!");
1311  bool ret = QMainWindow::event(e);
1312 
1313  qDebug("MAC: new toolbar visibility is %d", !m_ui->actionTopToolBar->isChecked());
1314  m_ui->actionTopToolBar->toggle();
1315  Preferences::instance()->setToolbarDisplayed(m_ui->actionTopToolBar->isChecked());
1316  return ret;
1317  }
1318  default:
1319  break;
1320  }
1321 #endif // Q_OS_MACOS
1322 
1323  return QMainWindow::event(e);
1324 }
1325 
1326 // action executed when a file is dropped
1327 void MainWindow::dropEvent(QDropEvent *event)
1328 {
1329  event->acceptProposedAction();
1330 
1331  // remove scheme
1332  QStringList files;
1333  if (event->mimeData()->hasUrls())
1334  {
1335  for (const QUrl &url : asConst(event->mimeData()->urls()))
1336  {
1337  if (url.isEmpty())
1338  continue;
1339 
1340  files << ((url.scheme().compare("file", Qt::CaseInsensitive) == 0)
1341  ? url.toLocalFile()
1342  : url.toString());
1343  }
1344  }
1345  else
1346  {
1347  files = event->mimeData()->text().split('\n');
1348  }
1349 
1350  // differentiate ".torrent" files/links & magnet links from others
1351  QStringList torrentFiles, otherFiles;
1352  for (const QString &file : asConst(files))
1353  {
1354  if (isTorrentLink(file))
1355  torrentFiles << file;
1356  else
1357  otherFiles << file;
1358  }
1359 
1360  // Download torrents
1361  const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
1362  for (const QString &file : asConst(torrentFiles))
1363  {
1364  if (useTorrentAdditionDialog)
1366  else
1368  }
1369  if (!torrentFiles.isEmpty()) return;
1370 
1371  // Create torrent
1372  for (const QString &file : asConst(otherFiles))
1373  {
1375 
1376  // currently only handle the first entry
1377  // this is a stub that can be expanded later to create many torrents at once
1378  break;
1379  }
1380 }
1381 
1382 // Decode if we accept drag 'n drop or not
1383 void MainWindow::dragEnterEvent(QDragEnterEvent *event)
1384 {
1385  for (const QString &mime : asConst(event->mimeData()->formats()))
1386  qDebug("mimeData: %s", mime.toLocal8Bit().data());
1387  if (event->mimeData()->hasFormat("text/plain") || event->mimeData()->hasFormat("text/uri-list"))
1388  event->acceptProposedAction();
1389 }
1390 
1391 #ifdef Q_OS_MACOS
1392 
1393 static MainWindow *dockMainWindowHandle;
1394 
1395 static bool dockClickHandler(id self, SEL cmd, ...)
1396 {
1397  Q_UNUSED(self)
1398  Q_UNUSED(cmd)
1399 
1400  if (dockMainWindowHandle && !dockMainWindowHandle->isVisible())
1401  {
1402  dockMainWindowHandle->activate();
1403  }
1404 
1405  return true;
1406 }
1407 
1408 void MainWindow::setupDockClickHandler()
1409 {
1410  dockMainWindowHandle = this;
1411  MacUtils::overrideDockClickHandler(dockClickHandler);
1412 }
1413 
1414 #endif // Q_OS_MACOS
1415 
1416 /*****************************************************
1417 * *
1418 * Torrent *
1419 * *
1420 *****************************************************/
1421 
1422 // Display a dialog to allow user to add
1423 // torrents to download list
1425 {
1426  Preferences *const pref = Preferences::instance();
1427  // Open File Open Dialog
1428  // Note: it is possible to select more than one file
1429  const QStringList pathsList =
1430  QFileDialog::getOpenFileNames(this, tr("Open Torrent Files"), pref->getMainLastDir(),
1431  tr("Torrent Files") + " (*" + C_TORRENT_FILE_EXTENSION + ')');
1432 
1433  if (pathsList.isEmpty())
1434  return;
1435 
1436  const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
1437 
1438  for (const QString &file : pathsList)
1439  {
1440  if (useTorrentAdditionDialog)
1442  else
1444  }
1445 
1446  // Save last dir to remember it
1447  QString topDir = Utils::Fs::toUniformPath(pathsList.at(0));
1448  topDir = topDir.left(topDir.lastIndexOf('/'));
1449  pref->setMainLastDir(topDir);
1450 }
1451 
1453 {
1454  if (!m_uiLocked || unlockUI())
1455  {
1456  show();
1457  activateWindow();
1458  raise();
1459  }
1460 }
1461 
1463 {
1464  Logger::instance()->addMessage(tr("Options saved."));
1465  loadPreferences();
1466 }
1467 
1469 {
1470  if (!show)
1471  {
1472  // Remove status bar
1473  setStatusBar(nullptr);
1474  }
1475  else if (!m_statusBar)
1476  {
1477  // Create status bar
1478  m_statusBar = new StatusBar;
1481  setStatusBar(m_statusBar);
1482  }
1483 }
1484 
1486 {
1487  const Preferences *pref = Preferences::instance();
1488 
1489 #ifndef Q_OS_MACOS
1490  // system tray icon
1491  if (pref->systemTrayEnabled())
1492  {
1493  if (m_systrayIcon)
1494  {
1495  // Reload systray icon
1496  m_systrayIcon->setIcon(UIThemeManager::instance()->getSystrayIcon());
1497  }
1498  else
1499  {
1500  createTrayIcon(20);
1501  }
1502  }
1503  else
1504  {
1505  delete m_systrayIcon;
1506  delete m_trayIconMenu;
1507  m_ui->actionLock->setVisible(false);
1508  }
1509 #endif
1510 
1511  // General
1512  if (pref->isToolbarDisplayed())
1513  {
1514  m_ui->toolBar->setVisible(true);
1515  }
1516  else
1517  {
1518  // Clear search filter before hiding the top toolbar
1519  m_searchFilter->clear();
1520  m_ui->toolBar->setVisible(false);
1521  }
1522 
1524 
1526  {
1527  if (!m_preventTimer->isActive())
1528  {
1531  }
1532  }
1533  else
1534  {
1535  m_preventTimer->stop();
1536  m_pwr->setActivityState(false);
1537  }
1538 
1539  m_transferListWidget->setAlternatingRowColors(pref->useAlternatingRowColors());
1540  m_propertiesWidget->getFilesList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1541  m_propertiesWidget->getTrackerList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1542  m_propertiesWidget->getPeerList()->setAlternatingRowColors(pref->useAlternatingRowColors());
1543 
1544  // Queueing System
1545  if (BitTorrent::Session::instance()->isQueueingSystemEnabled())
1546  {
1547  if (!m_ui->actionDecreaseQueuePos->isVisible())
1548  {
1550  m_ui->actionDecreaseQueuePos->setVisible(true);
1551  m_ui->actionIncreaseQueuePos->setVisible(true);
1552  m_ui->actionTopQueuePos->setVisible(true);
1553  m_ui->actionBottomQueuePos->setVisible(true);
1554 #ifndef Q_OS_MACOS
1555  m_queueSeparator->setVisible(true);
1556 #endif
1557  m_queueSeparatorMenu->setVisible(true);
1558  }
1559  }
1560  else
1561  {
1562  if (m_ui->actionDecreaseQueuePos->isVisible())
1563  {
1565  m_ui->actionDecreaseQueuePos->setVisible(false);
1566  m_ui->actionIncreaseQueuePos->setVisible(false);
1567  m_ui->actionTopQueuePos->setVisible(false);
1568  m_ui->actionBottomQueuePos->setVisible(false);
1569 #ifndef Q_OS_MACOS
1570  m_queueSeparator->setVisible(false);
1571 #endif
1572  m_queueSeparatorMenu->setVisible(false);
1573  }
1574  }
1575 
1576  // Torrent properties
1578 
1579 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1580  if (pref->isUpdateCheckEnabled())
1581  {
1582  if (!m_programUpdateTimer)
1583  {
1584  m_programUpdateTimer = new QTimer(this);
1585  m_programUpdateTimer->setInterval(24h);
1586  m_programUpdateTimer->setSingleShot(true);
1587  connect(m_programUpdateTimer, &QTimer::timeout, this, [this]() { checkProgramUpdate(false); });
1588  m_programUpdateTimer->start();
1589  }
1590  }
1591  else
1592  {
1593  delete m_programUpdateTimer;
1594  m_programUpdateTimer = nullptr;
1595  }
1596 #endif
1597 
1598  qDebug("GUI settings loaded");
1599 }
1600 
1602 {
1604 
1605  // update global information
1606 #ifdef Q_OS_MACOS
1607  if (status.payloadDownloadRate > 0)
1608  {
1609  MacUtils::setBadgeLabelText(tr("%1/s", "s is a shorthand for seconds")
1611  }
1612  else if (!MacUtils::badgeLabelText().isEmpty())
1613  {
1615  }
1616 #else
1617  if (m_systrayIcon)
1618  {
1619  const auto toolTip = QString::fromLatin1("%1\n%2").arg(
1620  tr("DL speed: %1", "e.g: Download speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true))
1621  , tr("UP speed: %1", "e.g: Upload speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadUploadRate, true)));
1622  m_systrayIcon->setToolTip(toolTip); // tray icon
1623  }
1624 #endif // Q_OS_MACOS
1625 
1627  {
1628  setWindowTitle(tr("[D: %1, U: %2] qBittorrent %3", "D = Download; U = Upload; %3 is qBittorrent version")
1631  , QBT_VERSION));
1632  }
1633 }
1634 
1635 void MainWindow::reloadTorrentStats(const QVector<BitTorrent::Torrent *> &torrents)
1636 {
1638  {
1639  if (torrents.contains(m_propertiesWidget->getCurrentTorrent()))
1641  }
1642 }
1643 
1644 void MainWindow::showNotificationBalloon(const QString &title, const QString &msg) const
1645 {
1646  if (!isNotificationsEnabled())
1647  return;
1648 
1649 #if (defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) && defined(QT_DBUS_LIB)
1650  OrgFreedesktopNotificationsInterface notifications(QLatin1String("org.freedesktop.Notifications")
1651  , QLatin1String("/org/freedesktop/Notifications")
1652  , QDBusConnection::sessionBus());
1653 
1654  // Testing for 'notifications.isValid()' isn't helpful here.
1655  // If the notification daemon is configured to run 'as needed'
1656  // the above check can be false if the daemon wasn't started
1657  // by another application. In this case DBus will be able to
1658  // start the notification daemon and complete our request. Such
1659  // a daemon is xfce4-notifyd, DBus autostarts it and after
1660  // some inactivity shuts it down. Other DEs, like GNOME, choose
1661  // to start their daemons at the session startup and have it sit
1662  // idling for the whole session.
1663  const QVariantMap hints {{QLatin1String("desktop-entry"), QLatin1String("org.qbittorrent.qBittorrent")}};
1664  QDBusPendingReply<uint> reply = notifications.Notify(QLatin1String("qBittorrent"), 0
1665  , QLatin1String("qbittorrent"), title, msg, {}, hints, getNotificationTimeout());
1666 
1667  reply.waitForFinished();
1668  if (!reply.isError())
1669  return;
1670 #elif defined(Q_OS_MACOS)
1671  MacUtils::displayNotification(title, msg);
1672 #else
1673  if (m_systrayIcon && QSystemTrayIcon::supportsMessages())
1674  m_systrayIcon->showMessage(title, msg, QSystemTrayIcon::Information, TIME_TRAY_BALLOON);
1675 #endif
1676 }
1677 
1678 /*****************************************************
1679 * *
1680 * Utils *
1681 * *
1682 *****************************************************/
1683 
1684 void MainWindow::downloadFromURLList(const QStringList &urlList)
1685 {
1686  const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
1687  for (const QString &url : urlList)
1688  {
1689  if (useTorrentAdditionDialog)
1690  AddNewTorrentDialog::show(url, this);
1691  else
1693  }
1694 }
1695 
1696 /*****************************************************
1697 * *
1698 * Options *
1699 * *
1700 *****************************************************/
1701 
1702 #ifndef Q_OS_MACOS
1703 void MainWindow::createTrayIcon(const int retries)
1704 {
1705  if (m_systrayIcon)
1706  return;
1707 
1708  if (QSystemTrayIcon::isSystemTrayAvailable())
1709  {
1710  m_systrayIcon = new QSystemTrayIcon(UIThemeManager::instance()->getSystrayIcon(), this);
1711 
1713  m_systrayIcon->setContextMenu(m_trayIconMenu);
1714 
1715  connect(m_systrayIcon, &QSystemTrayIcon::activated, this, &MainWindow::toggleVisibility);
1716  connect(m_systrayIcon, &QSystemTrayIcon::messageClicked, this, &MainWindow::balloonClicked);
1717 
1718  m_systrayIcon->show();
1719  emit systemTrayIconCreated();
1720  }
1721  else
1722  {
1723  if (retries > 0)
1724  {
1725  LogMsg("System tray icon is not available, retrying...", Log::WARNING);
1726  QTimer::singleShot(std::chrono::seconds(2), this, [this, retries]()
1727  {
1728  if (Preferences::instance()->systemTrayEnabled())
1729  createTrayIcon(retries - 1);
1730  });
1731  }
1732  else
1733  {
1734  LogMsg("System tray icon is still not available after retries. Disabling it.", Log::WARNING);
1736  }
1737  }
1738 }
1739 #endif // Q_OS_MACOS
1740 
1742 {
1743  if (m_trayIconMenu)
1744  return;
1745 
1746  m_trayIconMenu = new QMenu(this);
1747 
1748 #ifndef Q_OS_MACOS
1749  connect(m_trayIconMenu, &QMenu::aboutToShow, this, [this]()
1750  {
1751  m_ui->actionToggleVisibility->setText(isVisible() ? tr("Hide") : tr("Show"));
1752  });
1753 
1754  m_trayIconMenu->addAction(m_ui->actionToggleVisibility);
1755  m_trayIconMenu->addSeparator();
1756 #endif
1757 
1758  m_trayIconMenu->addAction(m_ui->actionOpen);
1759  m_trayIconMenu->addAction(m_ui->actionDownloadFromURL);
1760  m_trayIconMenu->addSeparator();
1761 
1762  m_trayIconMenu->addAction(m_ui->actionUseAlternativeSpeedLimits);
1763  m_trayIconMenu->addAction(m_ui->actionSetGlobalSpeedLimits);
1764  m_trayIconMenu->addSeparator();
1765 
1766  m_trayIconMenu->addAction(m_ui->actionStartAll);
1767  m_trayIconMenu->addAction(m_ui->actionPauseAll);
1768 
1769 #ifndef Q_OS_MACOS
1770  m_trayIconMenu->addSeparator();
1771  m_trayIconMenu->addAction(m_ui->actionExit);
1772 #endif
1773 
1774  if (m_uiLocked)
1775  m_trayIconMenu->setEnabled(false);
1776 }
1777 
1778 void MainWindow::updateAltSpeedsBtn(const bool alternative)
1779 {
1780  m_ui->actionUseAlternativeSpeedLimits->setChecked(alternative);
1781 }
1782 
1784 {
1785  return m_propertiesWidget;
1786 }
1787 
1788 // Display Program Options
1790 {
1791  if (m_options)
1792  m_options->activateWindow();
1793  else
1794  m_options = new OptionsDialog(this);
1795 }
1796 
1798 {
1799  const bool isVisible = static_cast<QAction *>(sender())->isChecked();
1800  m_ui->toolBar->setVisible(isVisible);
1802 }
1803 
1805 {
1806  const bool isVisible = static_cast<QAction *>(sender())->isChecked();
1808  showStatusBar(isVisible);
1809 }
1810 
1812 {
1813  m_displaySpeedInTitle = static_cast<QAction *>(sender())->isChecked();
1817  else
1818  setWindowTitle("qBittorrent " QBT_VERSION);
1819 }
1820 
1822 {
1823  Preferences::instance()->setRSSWidgetVisible(m_ui->actionRSSReader->isChecked());
1824  displayRSSTab(m_ui->actionRSSReader->isChecked());
1825 }
1826 
1828 {
1829  if (!m_hasPython && m_ui->actionSearchWidget->isChecked())
1830  {
1832 
1833  // Not installed
1834  if (!pyInfo.isValid())
1835  {
1836  m_ui->actionSearchWidget->setChecked(false);
1838 
1839 #ifdef Q_OS_WIN
1840  const QMessageBox::StandardButton buttonPressed = QMessageBox::question(this, tr("Missing Python Runtime")
1841  , tr("Python is required to use the search engine but it does not seem to be installed.\nDo you want to install it now?")
1842  , (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
1843  if (buttonPressed == QMessageBox::Yes)
1844  installPython();
1845 #else
1846  QMessageBox::information(this, tr("Missing Python Runtime")
1847  , tr("Python is required to use the search engine but it does not seem to be installed."));
1848 #endif
1849  return;
1850  }
1851 
1852  // Check version requirement
1853  if (!pyInfo.isSupportedVersion())
1854  {
1855  m_ui->actionSearchWidget->setChecked(false);
1857 
1858 #ifdef Q_OS_WIN
1859  const QMessageBox::StandardButton buttonPressed = QMessageBox::question(this, tr("Old Python Runtime")
1860  , tr("Your Python version (%1) is outdated. Minimum requirement: %2.\nDo you want to install a newer version now?")
1861  .arg(pyInfo.version, QLatin1String("3.5.0"))
1862  , (QMessageBox::Yes | QMessageBox::No), QMessageBox::Yes);
1863  if (buttonPressed == QMessageBox::Yes)
1864  installPython();
1865 #else
1866  QMessageBox::information(this, tr("Old Python Runtime")
1867  , tr("Your Python version (%1) is outdated. Please upgrade to latest version for search engines to work.\nMinimum requirement: %2.")
1868  .arg(pyInfo.version, QLatin1String("3.5.0")));
1869 #endif
1870  return;
1871  }
1872 
1873  m_hasPython = true;
1874  m_ui->actionSearchWidget->setChecked(true);
1876  }
1877 
1878  displaySearchTab(m_ui->actionSearchWidget->isChecked());
1879 }
1880 
1881 /*****************************************************
1882 * *
1883 * HTTP Downloader *
1884 * *
1885 *****************************************************/
1886 
1887 // Display an input dialog to prompt user for
1888 // an url
1890 {
1892  {
1895  }
1896 }
1897 
1898 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
1899 void MainWindow::handleUpdateCheckFinished(ProgramUpdater *updater, const bool invokedByUser)
1900 {
1901  m_ui->actionCheckForUpdates->setEnabled(true);
1902  m_ui->actionCheckForUpdates->setText(tr("&Check for Updates"));
1903  m_ui->actionCheckForUpdates->setToolTip(tr("Check for program updates"));
1904 
1905  const auto cleanup = [this, updater]()
1906  {
1907  if (m_programUpdateTimer)
1908  m_programUpdateTimer->start();
1909  updater->deleteLater();
1910  };
1911 
1912  const QString newVersion = updater->getNewVersion();
1913  if (!newVersion.isEmpty())
1914  {
1915  const QString msg {tr("A new version is available.") + "<br/>"
1916  + tr("Do you want to download %1?").arg(newVersion) + "<br/><br/>"
1917  + QString::fromLatin1("<a href=\"https://www.qbittorrent.org/news.php\">%1</a>").arg(tr("Open changelog..."))};
1918  auto *msgBox = new QMessageBox {QMessageBox::Question, tr("qBittorrent Update Available"), msg
1919  , (QMessageBox::Yes | QMessageBox::No), this};
1920  msgBox->setAttribute(Qt::WA_DeleteOnClose);
1921  msgBox->setAttribute(Qt::WA_ShowWithoutActivating);
1922  msgBox->setDefaultButton(QMessageBox::Yes);
1923  connect(msgBox, &QMessageBox::buttonClicked, this, [msgBox, updater](QAbstractButton *button)
1924  {
1925  if (msgBox->buttonRole(button) == QMessageBox::YesRole)
1926  {
1927  updater->updateProgram();
1928  }
1929  });
1930  connect(msgBox, &QDialog::finished, this, cleanup);
1931  msgBox->open();
1932  }
1933  else
1934  {
1935  if (invokedByUser)
1936  {
1937  auto *msgBox = new QMessageBox {QMessageBox::Information, QLatin1String("qBittorrent")
1938  , tr("No updates available.\nYou are already using the latest version.")
1939  , QMessageBox::Ok, this};
1940  msgBox->setAttribute(Qt::WA_DeleteOnClose);
1941  connect(msgBox, &QDialog::finished, this, cleanup);
1942  msgBox->open();
1943  }
1944  else
1945  {
1946  cleanup();
1947  }
1948  }
1949 }
1950 #endif
1951 
1953 {
1956 }
1957 
1959 {
1960  QDesktopServices::openUrl(QUrl("https://www.qbittorrent.org/donate"));
1961 }
1962 
1964 {
1966  m_options->showConnectionTab();
1967 }
1968 
1970 {
1971  setWindowState(windowState() | Qt::WindowMinimized);
1972 }
1973 
1975 {
1976  if (checked)
1977  {
1978  Q_ASSERT(!m_executionLog);
1980 #ifdef Q_OS_MACOS
1981  m_tabs->addTab(m_executionLog, tr("Execution Log"));
1982 #else
1983  const int indexTab = m_tabs->addTab(m_executionLog, tr("Execution Log"));
1984  m_tabs->setTabIcon(indexTab, UIThemeManager::instance()->getIcon("view-calendar-journal"));
1985 #endif
1986  }
1987  else
1988  {
1989  delete m_executionLog;
1990  }
1991 
1992  m_ui->actionNormalMessages->setEnabled(checked);
1993  m_ui->actionInformationMessages->setEnabled(checked);
1994  m_ui->actionWarningMessages->setEnabled(checked);
1995  m_ui->actionCriticalMessages->setEnabled(checked);
1996  setExecutionLogEnabled(checked);
1997 }
1998 
2000 {
2001  if (!m_executionLog)
2002  return;
2003 
2004  const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::NORMAL, checked);
2005  setExecutionLogMsgTypes(flags);
2006 }
2007 
2009 {
2010  if (!m_executionLog)
2011  return;
2012 
2013  const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::INFO, checked);
2014  setExecutionLogMsgTypes(flags);
2015 }
2016 
2018 {
2019  if (!m_executionLog)
2020  return;
2021 
2022  const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::WARNING, checked);
2023  setExecutionLogMsgTypes(flags);
2024 }
2025 
2027 {
2028  if (!m_executionLog)
2029  return;
2030 
2031  const Log::MsgTypes flags = executionLogMsgTypes().setFlag(Log::CRITICAL, checked);
2032  setExecutionLogMsgTypes(flags);
2033 }
2034 
2036 {
2037  qDebug() << Q_FUNC_INFO << enabled;
2039 }
2040 
2042 {
2043  qDebug() << Q_FUNC_INFO << enabled;
2045 }
2046 
2048 {
2049  qDebug() << Q_FUNC_INFO << enabled;
2051 }
2052 
2054 {
2055  qDebug() << Q_FUNC_INFO << enabled;
2057 }
2058 
2060 {
2063  m_pwr->setActivityState(inhibitSuspend);
2064 }
2065 
2066 #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
2067 void MainWindow::checkProgramUpdate(const bool invokedByUser)
2068 {
2069  if (m_programUpdateTimer)
2070  m_programUpdateTimer->stop();
2071 
2072  m_ui->actionCheckForUpdates->setEnabled(false);
2073  m_ui->actionCheckForUpdates->setText(tr("Checking for Updates..."));
2074  m_ui->actionCheckForUpdates->setToolTip(tr("Already checking for program updates in the background"));
2075 
2076  auto *updater = new ProgramUpdater(this);
2077  connect(updater, &ProgramUpdater::updateCheckFinished
2078  , this, [this, invokedByUser, updater]()
2079  {
2080  handleUpdateCheckFinished(updater, invokedByUser);
2081  });
2082  updater->checkForUpdates();
2083 }
2084 #endif
2085 
2086 #ifdef Q_OS_WIN
2087 void MainWindow::installPython()
2088 {
2089  setCursor(QCursor(Qt::WaitCursor));
2090  // Download python
2091 #ifdef QBT_APP_64BIT
2092  const QString installerURL = "https://www.python.org/ftp/python/3.8.10/python-3.8.10-amd64.exe";
2093 #else
2094  const QString installerURL = "https://www.python.org/ftp/python/3.8.10/python-3.8.10.exe";
2095 #endif
2097  Net::DownloadRequest(installerURL).saveToFile(true)
2098  , this, &MainWindow::pythonDownloadFinished);
2099 }
2100 
2101 void MainWindow::pythonDownloadFinished(const Net::DownloadResult &result)
2102 {
2103  if (result.status != Net::DownloadStatus::Success)
2104  {
2105  setCursor(QCursor(Qt::ArrowCursor));
2106  QMessageBox::warning(
2107  this, tr("Download error")
2108  , tr("Python setup could not be downloaded, reason: %1.\nPlease install it manually.")
2109  .arg(result.errorString));
2110  return;
2111  }
2112 
2113  setCursor(QCursor(Qt::ArrowCursor));
2114  QProcess installer;
2115  qDebug("Launching Python installer in passive mode...");
2116 
2117  const QString exePath = result.filePath + QLatin1String(".exe");
2118  QFile::rename(result.filePath, exePath);
2119  installer.start(Utils::Fs::toNativePath(exePath), {"/passive"});
2120 
2121  // Wait for setup to complete
2122  installer.waitForFinished(10 * 60 * 1000);
2123 
2124  qDebug("Installer stdout: %s", installer.readAllStandardOutput().data());
2125  qDebug("Installer stderr: %s", installer.readAllStandardError().data());
2126  qDebug("Setup should be complete!");
2127 
2128  // Delete temp file
2129  Utils::Fs::forceRemove(exePath);
2130 
2131  // Reload search engine
2132  if (Utils::ForeignApps::pythonInfo().isSupportedVersion())
2133  {
2134  m_ui->actionSearchWidget->setChecked(true);
2135  displaySearchTab(true);
2136  }
2137 }
2138 #endif // Q_OS_WIN
QBITTORRENT_HAS_EXECINFO_H if(NOT QBITTORRENT_HAS_EXECINFO_H) message(FATAL_ERROR "execinfo.h header file not found.\n" "Please either disable the STACKTRACE feature or use a libc that has this header file
static void show(const QString &source, const BitTorrent::AddTorrentParams &inParams, QWidget *parent)
static QString getText(QWidget *parent, const QString &title, const QString &label, QLineEdit::EchoMode mode=QLineEdit::Normal, const QString &text={}, bool *ok=nullptr, bool excludeExtension=false, Qt::InputMethodHints inputMethodHints=Qt::ImhNone)
bool isAltGlobalSpeedLimitEnabled() const
Definition: session.cpp:2726
static Session * instance()
Definition: session.cpp:997
void trackersAdded(Torrent *torrent, const QVector< TrackerEntry > &trackers)
void trackersChanged(Torrent *torrent)
void fullDiskError(Torrent *torrent, const QString &msg)
void trackersRemoved(Torrent *torrent, const QVector< TrackerEntry > &trackers)
bool hasRunningSeed() const
Definition: session.cpp:1763
void loadTorrentFailed(const QString &error)
void setAltGlobalSpeedLimitEnabled(bool enabled)
Definition: session.cpp:2731
bool hasActiveTorrents() const
Definition: session.cpp:1747
void torrentAdded(Torrent *torrent)
void trackerError(Torrent *torrent, const QString &tracker)
const SessionStatus & status() const
Definition: session.cpp:4301
bool hasUnfinishedTorrents() const
Definition: session.cpp:1755
void speedLimitModeChanged(bool alternative)
void trackerSuccess(Torrent *torrent, const QString &tracker)
void torrentFinished(Torrent *torrent)
void recursiveTorrentDownloadPossible(Torrent *torrent)
bool addTorrent(const QString &source, const AddTorrentParams &params=AddTorrentParams())
Definition: session.cpp:2007
void downloadFromUrlFailed(const QString &url, const QString &reason)
void torrentsUpdated(const QVector< Torrent * > &torrents)
void trackerWarning(Torrent *torrent, const QString &tracker)
void trackerlessStateChanged(Torrent *torrent, bool trackerless)
TorrentID id() const
Definition: torrent.cpp:54
virtual QString name() const =0
void urlsReadyToBeDownloaded(const QStringList &torrentURLs)
void addMessage(const QString &message, const Log::MsgType &type=Log::NORMAL)
Definition: logger.cpp:73
static Logger * instance()
Definition: logger.cpp:56
void handleDownloadFromUrlFailure(const QString &, const QString &) const
Definition: mainwindow.cpp:986
void showStatusBar(bool show)
void downloadFromURLList(const QStringList &urlList)
void readSettings()
Definition: mainwindow.cpp:818
QPointer< AboutDialog > m_aboutDlg
Definition: mainwindow.h:226
void showNotificationBalloon(const QString &title, const QString &msg) const
PropertiesWidget * m_propertiesWidget
Definition: mainwindow.h:238
void toggleAlternativeSpeeds()
TransferListWidget * transferListWidget() const
void minimizeWindow()
SettingValue< bool > m_storeNotificationEnabled
Definition: mainwindow.h:260
void balloonClicked()
Definition: mainwindow.cpp:832
void updateNbTorrents()
Definition: mainwindow.cpp:756
void on_actionOptions_triggered()
void on_actionAutoHibernate_toggled(bool)
SettingValue< bool > m_storeNotificationTorrentAdded
Definition: mainwindow.h:261
void on_actionNormalMessages_triggered(bool checked)
~MainWindow() override
Definition: mainwindow.cpp:480
QPointer< TorrentCreatorDialog > m_createTorrentDlg
Definition: mainwindow.h:228
void askRecursiveTorrentDownloadConfirmation(BitTorrent::Torrent *const torrent)
Definition: mainwindow.cpp:960
void on_actionAutoSuspend_toggled(bool)
void displaySearchTab()
Definition: mainwindow.cpp:925
void fullDiskError(BitTorrent::Torrent *const torrent, const QString &msg) const
Definition: mainwindow.cpp:870
void manageCookies()
Definition: mainwindow.cpp:597
void createKeyboardShortcuts()
Definition: mainwindow.cpp:877
PropertiesWidget * propertiesWidget() const
TransferListWidget * m_transferListWidget
Definition: mainwindow.h:236
void createTrayIconMenu()
void on_actionShowStatusbar_triggered()
void on_actionDonateMoney_triggered()
void finishedTorrent(BitTorrent::Torrent *const torrent) const
Definition: mainwindow.cpp:864
void on_actionOpen_triggered()
void writeSettings()
Definition: mainwindow.cpp:787
void closeEvent(QCloseEvent *) override
void toolbarMenuRequested(const QPoint &point)
Definition: mainwindow.cpp:604
void setExecutionLogEnabled(bool value)
Definition: mainwindow.cpp:490
void on_actionAbout_triggered()
void dragEnterEvent(QDragEnterEvent *event) override
Log::MsgTypes executionLogMsgTypes() const
Definition: mainwindow.cpp:495
void keyPressEvent(QKeyEvent *event) override
void setNotificationsEnabled(bool value)
Definition: mainwindow.cpp:511
bool m_posInitialized
Definition: mainwindow.h:222
void addToolbarContextMenu()
Definition: mainwindow.cpp:549
void on_actionCriticalMessages_triggered(bool checked)
Ui::MainWindow * m_ui
Definition: mainwindow.h:218
void toolbarTextOnly()
Definition: mainwindow.cpp:615
QPointer< StatusBar > m_statusBar
Definition: mainwindow.h:224
void createTorrentTriggered(const QString &path={})
SettingValue< bool > m_storeExecutionLogEnabled
Definition: mainwindow.h:258
void clearUILockPassword()
Definition: mainwindow.cpp:657
void torrentNew(BitTorrent::Torrent *const torrent) const
Definition: mainwindow.cpp:857
SettingValue< bool > m_storeDownloadTrackerFavicon
Definition: mainwindow.h:259
void on_actionAutoExit_toggled(bool)
void on_actionSearchWidget_triggered()
void toolbarIconsOnly()
Definition: mainwindow.cpp:609
bool m_forceExit
Definition: mainwindow.h:240
void on_actionDocumentation_triggered() const
Definition: mainwindow.cpp:761
void toolbarTextBeside()
Definition: mainwindow.cpp:621
void updateAltSpeedsBtn(bool alternative)
QFileSystemWatcher * m_executableWatcher
Definition: mainwindow.h:220
void on_actionTopToolBar_triggered()
void tabChanged(int newTab)
Definition: mainwindow.cpp:766
PowerManagement * m_pwr
Definition: mainwindow.h:253
void setTorrentAddedNotificationsEnabled(bool value)
Definition: mainwindow.cpp:521
bool m_unlockDlgShowing
Definition: mainwindow.h:242
QPointer< ExecutionLogWidget > m_executionLog
Definition: mainwindow.h:251
QPointer< OptionsDialog > m_options
Definition: mainwindow.h:225
QAction * m_queueSeparatorMenu
Definition: mainwindow.h:247
void showFilterContextMenu(const QPoint &)
Definition: mainwindow.cpp:711
void showConnectionSettings()
LineEdit * m_searchFilter
Definition: mainwindow.h:243
CachedSettingValue< Log::MsgTypes > m_storeExecutionLogTypes
Definition: mainwindow.h:262
bool m_displaySpeedInTitle
Definition: mainwindow.h:239
void on_actionInformationMessages_triggered(bool checked)
void updatePowerManagementState()
MainWindow(QWidget *parent=nullptr)
Definition: mainwindow.cpp:123
QPointer< QTabWidget > m_tabs
Definition: mainwindow.h:223
void addTorrentFailed(const QString &error) const
Definition: mainwindow.cpp:851
void displayExecutionLogTab()
Definition: mainwindow.cpp:947
void handleRSSUnreadCountUpdated(int count)
Definition: mainwindow.cpp:683
void reloadSessionStats()
QPointer< StatsDialog > m_statsDlg
Definition: mainwindow.h:227
void setExecutionLogMsgTypes(Log::MsgTypes value)
Definition: mainwindow.cpp:500
void cleanup()
Definition: mainwindow.cpp:796
void on_actionAutoShutdown_toggled(bool)
void toolbarTextUnder()
Definition: mainwindow.cpp:627
bool isTorrentAddedNotificationsEnabled() const
Definition: mainwindow.cpp:516
void loadPreferences()
QPointer< QSystemTrayIcon > m_systrayIcon
Definition: mainwindow.h:232
void on_actionExit_triggered()
void optionsSaved()
void displayRSSTab()
Definition: mainwindow.cpp:936
void toolbarFollowSystem()
Definition: mainwindow.cpp:633
bool m_uiLocked
Definition: mainwindow.h:241
QPointer< DownloadFromURLDialog > m_downloadFromURLDialog
Definition: mainwindow.h:229
QPointer< SearchWidget > m_searchWidget
Definition: mainwindow.h:249
void showEvent(QShowEvent *) override
bool isDownloadTrackerFavicon() const
Definition: mainwindow.cpp:538
void on_actionRSSReader_triggered()
QSplitter * m_splitter
Definition: mainwindow.h:248
QWidget * currentTabWidget() const
bool defineUILockPassword()
Definition: mainwindow.cpp:639
bool m_hasPython
Definition: mainwindow.h:255
void on_actionLock_triggered()
Definition: mainwindow.cpp:665
TransferListFiltersWidget * m_transferListFiltersWidget
Definition: mainwindow.h:237
void on_actionWarningMessages_triggered(bool checked)
void activate()
void toggleVisibility(const QSystemTrayIcon::ActivationReason reason=QSystemTrayIcon::Trigger)
QMenu * m_toolbarMenu
Definition: mainwindow.h:256
void on_actionSetGlobalSpeedLimits_triggered()
Definition: mainwindow.cpp:993
QPointer< RSSWidget > m_rssWidget
Definition: mainwindow.h:250
QAction * m_searchFilterAction
Definition: mainwindow.h:244
void displayTransferTab() const
Definition: mainwindow.cpp:920
void on_actionCreateTorrent_triggered()
QTimer * m_preventTimer
Definition: mainwindow.h:254
QAction * m_queueSeparator
Definition: mainwindow.h:246
void on_actionDownloadFromURL_triggered()
bool isExecutionLogEnabled() const
Definition: mainwindow.cpp:485
void focusSearchFilter()
Definition: mainwindow.cpp:750
void notifyOfUpdate(const QString &)
void on_actionStatistics_triggered()
bool unlockUI()
void setDownloadTrackerFavicon(bool value)
Definition: mainwindow.cpp:543
void on_actionExecutionLogs_triggered(bool checked)
QPointer< QMenu > m_trayIconMenu
Definition: mainwindow.h:234
bool event(QEvent *e) override
void dropEvent(QDropEvent *event) override
void reloadTorrentStats(const QVector< BitTorrent::Torrent * > &torrents)
void createTrayIcon(int retries)
void on_actionSpeedInTitleBar_triggered()
bool isNotificationsEnabled() const
Definition: mainwindow.cpp:506
void systemTrayIconCreated()
static bool hasSupportedScheme(const QString &url)
DownloadHandler * download(const DownloadRequest &downloadRequest)
static DownloadManager * instance()
QDBusPendingReply< uint > Notify(const QString &app_name, uint id, const QString &icon, const QString &summary, const QString &body, const QStringList &actions, const QVariantMap &hints, int timeout)
Definition: notifications.h:69
void setActivityState(bool busy)
bool recursiveDownloadDisabled() const
bool shutdownqBTWhenDownloadsComplete() const
static Preferences * instance()
bool shutdownWhenDownloadsComplete() const
void setSuspendWhenDownloadsComplete(bool suspend)
void setCloseToTrayNotified(bool b)
void setConfirmOnExit(bool confirm)
bool preventFromSuspendWhenSeeding() const
void setShutdownqBTWhenDownloadsComplete(bool shutdown)
bool useAlternatingRowColors() const
bool isStatusbarDisplayed() const
QByteArray getMainGeometry() const
void changed()
bool suspendWhenDownloadsComplete() const
bool isSearchEnabled() const
void setRegexAsFilteringPatternForTransferList(bool checked)
void setMinimizeToTrayNotified(bool b)
bool confirmOnExit() const
void setMainLastDir(const QString &path)
bool systemTrayEnabled() const
void showSpeedInTitleBar(bool show)
bool closeToTray() const
void setStatusbarDisplayed(bool displayed)
void setSystemTrayEnabled(bool enabled)
void setMainGeometry(const QByteArray &geometry)
void setUILocked(bool locked)
bool speedInTitleBar() const
void setUILockPassword(const QByteArray &password)
bool getRegexAsFilteringPatternForTransferList() const
bool isUILocked() const
bool isRSSWidgetEnabled() const
bool closeToTrayNotified() const
void setToolbarTextPosition(int position)
QByteArray getMainVSplitterState() const
void setShutdownWhenDownloadsComplete(bool shutdown)
QString getMainLastDir() const
bool isToolbarDisplayed() const
void setRSSWidgetVisible(bool enabled)
void setSearchEnabled(bool enabled)
bool minimizeToTray() const
void setToolbarDisplayed(bool displayed)
int getToolbarTextPosition() const
void setHibernateWhenDownloadsComplete(bool hibernate)
QByteArray getUILockPassword() const
bool preventFromSuspendWhenDownloading() const
bool minimizeToTrayNotified() const
void setMainVSplitterState(const QByteArray &state)
bool hibernateWhenDownloadsComplete() const
bool startMinimized() const
void checkForUpdates() const
void updateCheckFinished()
QString getNewVersion() const
void loadTorrentInfos(BitTorrent::Torrent *const torrent)
TrackerListWidget * getTrackerList() const
BitTorrent::Torrent * getCurrentTorrent() const
void loadTrackers(BitTorrent::Torrent *const torrent)
QTreeView * getFilesList() const
PeerListWidget * getPeerList() const
static Session * instance()
void unreadCountUpdated(int count)
T get(const T &defaultValue={}) const
Definition: settingvalue.h:46
void connectionButtonClicked()
void alternativeSpeedsButtonClicked()
void trackerWarning(const BitTorrent::Torrent *torrent, const QString &tracker)
void removeTrackers(const BitTorrent::Torrent *torrent, const QVector< BitTorrent::TrackerEntry > &trackers)
void trackerError(const BitTorrent::Torrent *torrent, const QString &tracker)
void changeTrackerless(const BitTorrent::Torrent *torrent, bool trackerless)
void addTrackers(const BitTorrent::Torrent *torrent, const QVector< BitTorrent::TrackerEntry > &trackers)
void trackerSuccess(const BitTorrent::Torrent *torrent, const QString &tracker)
int rowCount(const QModelIndex &parent={}) const override
TransferListModel * getSourceModel() const
void currentTorrentChanged(BitTorrent::Torrent *const torrent)
void hideQueuePosColumn(bool hide)
void applyNameFilter(const QString &name)
static UIThemeManager * instance()
const char C_TORRENT_FILE_EXTENSION[]
Definition: global.h:38
constexpr std::add_const_t< T > & asConst(T &t) noexcept
Definition: global.h:42
flag icons free of to any person obtaining a copy of this software and associated documentation files(the "Software")
void LogMsg(const QString &message, const Log::MsgType &type)
Definition: logger.cpp:125
#define NOTIFICATIONS_SETTINGS_KEY(name)
Definition: mainwindow.cpp:107
#define EXECUTIONLOG_SETTINGS_KEY(name)
Definition: mainwindow.cpp:106
#define SETTINGS_KEY(name)
Definition: mainwindow.cpp:105
Definition: filelogger.h:36
MsgType
Definition: logger.h:43
@ WARNING
Definition: logger.h:47
@ NORMAL
Definition: logger.h:45
@ CRITICAL
Definition: logger.h:48
@ INFO
Definition: logger.h:46
@ ALL
Definition: logger.h:44
void overrideDockClickHandler(bool(*dockClickHandler)(id, SEL,...))
Definition: macutilities.mm:59
void displayNotification(const QString &title, const QString &message)
Definition: macutilities.mm:85
void setBadgeLabelText(const QString &text)
QString badgeLabelText()
PythonInfo pythonInfo()
QString toUniformPath(const QString &path)
Definition: fs.cpp:69
bool forceRemove(const QString &filePath)
Definition: fs.cpp:173
QString toNativePath(const QString &path)
Definition: fs.cpp:64
T scaledSize(const QWidget *widget, const T &size)
Definition: utils.h:46
QPoint screenCenter(const QWidget *w)
Definition: utils.cpp:119
nonstd::expected< void, QString > saveToFile(const QString &path, const QByteArray &data)
Definition: io.cpp:69
QString friendlyUnit(qint64 bytes, bool isSpeed=false)
Definition: misc.cpp:261
QByteArray generate(const QString &password)
Definition: password.cpp:68
bool verify(const QByteArray &secret, const QString &password)
Definition: password.cpp:95
bool isTorrentLink(const QString &str)
Definition: mainwindow.cpp:114
const std::chrono::seconds PREVENT_SUSPEND_INTERVAL
Definition: mainwindow.cpp:109
T value(const QString &key, const T &defaultValue={})
Definition: preferences.cpp:64
action
Definition: tstool.py:143
file(GLOB QBT_TS_FILES "${qBittorrent_SOURCE_DIR}/src/lang/*.ts") set_source_files_properties($
Definition: CMakeLists.txt:5
DownloadStatus status