mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-01-08 16:17:24 +03:00
48cd993c92
"Active torrents" is a somewhat unintuitive concept as a basis for preventing sleep, as torrents can become active or inactive on the network at any time. This brings some predictability to the inhibit sleep option, and will inhibit sleep as long as there are unpaused downloads or uploads, regardless of network activity. Closes #1696, #4592, #4655, #7019, #7159, #7452
2109 lines
77 KiB
C++
2109 lines
77 KiB
C++
/*
|
|
* Bittorrent Client using Qt and libtorrent.
|
|
* Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* In addition, as a special exception, the copyright holders give permission to
|
|
* link this program with the OpenSSL project's "OpenSSL" library (or with
|
|
* modified versions of it that use the same license as the "OpenSSL" library),
|
|
* and distribute the linked executables. You must obey the GNU General Public
|
|
* License in all respects for all of the code used other than "OpenSSL". If you
|
|
* modify file(s), you may extend this exception to your version of the file(s),
|
|
* but you are not obligated to do so. If you do not wish to do so, delete this
|
|
* exception statement from your version.
|
|
*/
|
|
|
|
#include "mainwindow.h"
|
|
|
|
#include <QClipboard>
|
|
#include <QCloseEvent>
|
|
#include <QCryptographicHash>
|
|
#include <QDebug>
|
|
#include <QDesktopServices>
|
|
#include <QFileDialog>
|
|
#include <QFileSystemWatcher>
|
|
#include <QMessageBox>
|
|
#include <QMimeData>
|
|
#include <QProcess>
|
|
#include <QPushButton>
|
|
#include <QRegularExpression>
|
|
#include <QScrollBar>
|
|
#include <QShortcut>
|
|
#include <QSplitter>
|
|
#include <QStatusBar>
|
|
#include <QSysInfo>
|
|
#include <QtGlobal>
|
|
#include <QTimer>
|
|
|
|
#ifdef Q_OS_MAC
|
|
#include <QtMac>
|
|
#include <QtMacExtras>
|
|
#endif
|
|
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) && defined(QT_DBUS_LIB)
|
|
#include <QDBusConnection>
|
|
#include "notifications.h"
|
|
#endif
|
|
|
|
#include "base/bittorrent/session.h"
|
|
#include "base/bittorrent/sessionstatus.h"
|
|
#include "base/bittorrent/torrenthandle.h"
|
|
#include "base/global.h"
|
|
#include "base/logger.h"
|
|
#include "base/preferences.h"
|
|
#include "base/rss/rss_folder.h"
|
|
#include "base/rss/rss_session.h"
|
|
#include "base/settingsstorage.h"
|
|
#include "base/utils/foreignapps.h"
|
|
#include "base/utils/fs.h"
|
|
#include "base/utils/misc.h"
|
|
#include "aboutdialog.h"
|
|
#include "addnewtorrentdialog.h"
|
|
#include "application.h"
|
|
#include "autoexpandabledialog.h"
|
|
#include "cookiesdialog.h"
|
|
#include "downloadfromurldialog.h"
|
|
#include "executionlogwidget.h"
|
|
#include "guiiconprovider.h"
|
|
#include "hidabletabwidget.h"
|
|
#include "lineedit.h"
|
|
#include "optionsdialog.h"
|
|
#include "peerlistwidget.h"
|
|
#include "powermanagement.h"
|
|
#include "propertieswidget.h"
|
|
#include "rss/rsswidget.h"
|
|
#include "search/searchwidget.h"
|
|
#include "speedlimitdialog.h"
|
|
#include "statsdialog.h"
|
|
#include "statusbar.h"
|
|
#include "torrentcreatordialog.h"
|
|
#include "transferlistmodel.h"
|
|
#include "trackerlistwidget.h"
|
|
#include "transferlistfilterswidget.h"
|
|
#include "transferlistwidget.h"
|
|
#include "ui_mainwindow.h"
|
|
#include "utils.h"
|
|
|
|
#ifdef Q_OS_WIN
|
|
#include "base/net/downloadhandler.h"
|
|
#include "base/net/downloadmanager.h"
|
|
#endif
|
|
#ifdef Q_OS_MAC
|
|
#include "macutilities.h"
|
|
#endif
|
|
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
|
#include "programupdater.h"
|
|
#endif
|
|
#if LIBTORRENT_VERSION_NUM < 10100
|
|
#include "trackerlogindialog.h"
|
|
#endif
|
|
|
|
#ifdef Q_OS_MAC
|
|
void qt_mac_set_dock_menu(QMenu *menu);
|
|
#endif
|
|
|
|
#define TIME_TRAY_BALLOON 5000
|
|
#define PREVENT_SUSPEND_INTERVAL 60000
|
|
|
|
namespace
|
|
{
|
|
#define SETTINGS_KEY(name) "GUI/" name
|
|
|
|
// ExecutionLog properties keys
|
|
#define EXECUTIONLOG_SETTINGS_KEY(name) QStringLiteral(SETTINGS_KEY("Log/") name)
|
|
const QString KEY_EXECUTIONLOG_ENABLED = EXECUTIONLOG_SETTINGS_KEY("Enabled");
|
|
const QString KEY_EXECUTIONLOG_TYPES = EXECUTIONLOG_SETTINGS_KEY("Types");
|
|
|
|
// Notifications properties keys
|
|
#define NOTIFICATIONS_SETTINGS_KEY(name) QStringLiteral(SETTINGS_KEY("Notifications/") name)
|
|
const QString KEY_NOTIFICATIONS_ENABLED = NOTIFICATIONS_SETTINGS_KEY("Enabled");
|
|
const QString KEY_NOTIFICATIONS_TORRENTADDED = NOTIFICATIONS_SETTINGS_KEY("TorrentAdded");
|
|
|
|
// Misc
|
|
const QString KEY_DOWNLOAD_TRACKER_FAVICON = NOTIFICATIONS_SETTINGS_KEY("DownloadTrackerFavicon");
|
|
|
|
// just a shortcut
|
|
inline SettingsStorage *settings()
|
|
{
|
|
return SettingsStorage::instance();
|
|
}
|
|
}
|
|
|
|
MainWindow::MainWindow(QWidget *parent)
|
|
: QMainWindow(parent)
|
|
, m_ui(new Ui::MainWindow)
|
|
, m_posInitialized(false)
|
|
, m_forceExit(false)
|
|
, m_unlockDlgShowing(false)
|
|
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
|
, m_wasUpdateCheckEnabled(false)
|
|
#endif
|
|
, m_hasPython(false)
|
|
{
|
|
m_ui->setupUi(this);
|
|
|
|
Preferences *const pref = Preferences::instance();
|
|
m_uiLocked = pref->isUILocked();
|
|
setWindowTitle("qBittorrent " QBT_VERSION);
|
|
m_displaySpeedInTitle = pref->speedInTitleBar();
|
|
// Setting icons
|
|
#ifndef Q_OS_MAC
|
|
#ifdef Q_OS_UNIX
|
|
const QIcon appLogo = Preferences::instance()->useSystemIconTheme()
|
|
? QIcon::fromTheme("qbittorrent", QIcon(":/icons/skin/qbittorrent-tray.svg"))
|
|
: QIcon(":/icons/skin/qbittorrent-tray.svg");
|
|
#else
|
|
const QIcon appLogo(":/icons/skin/qbittorrent-tray.svg");
|
|
#endif // Q_OS_UNIX
|
|
setWindowIcon(appLogo);
|
|
#endif // Q_OS_MAC
|
|
|
|
#if (defined(Q_OS_UNIX))
|
|
m_ui->actionOptions->setText(tr("Preferences"));
|
|
#endif
|
|
|
|
addToolbarContextMenu();
|
|
|
|
m_ui->actionOpen->setIcon(GuiIconProvider::instance()->getIcon("list-add"));
|
|
m_ui->actionDownloadFromURL->setIcon(GuiIconProvider::instance()->getIcon("insert-link"));
|
|
m_ui->actionSetUploadLimit->setIcon(GuiIconProvider::instance()->getIcon("kt-set-max-upload-speed"));
|
|
m_ui->actionSetDownloadLimit->setIcon(GuiIconProvider::instance()->getIcon("kt-set-max-download-speed"));
|
|
m_ui->actionSetGlobalUploadLimit->setIcon(GuiIconProvider::instance()->getIcon("kt-set-max-upload-speed"));
|
|
m_ui->actionSetGlobalDownloadLimit->setIcon(GuiIconProvider::instance()->getIcon("kt-set-max-download-speed"));
|
|
m_ui->actionCreateTorrent->setIcon(GuiIconProvider::instance()->getIcon("document-edit"));
|
|
m_ui->actionAbout->setIcon(GuiIconProvider::instance()->getIcon("help-about"));
|
|
m_ui->actionStatistics->setIcon(GuiIconProvider::instance()->getIcon("view-statistics"));
|
|
m_ui->actionDecreasePriority->setIcon(GuiIconProvider::instance()->getIcon("go-down"));
|
|
m_ui->actionBottomPriority->setIcon(GuiIconProvider::instance()->getIcon("go-bottom"));
|
|
m_ui->actionDelete->setIcon(GuiIconProvider::instance()->getIcon("list-remove"));
|
|
m_ui->actionDocumentation->setIcon(GuiIconProvider::instance()->getIcon("help-contents"));
|
|
m_ui->actionDonateMoney->setIcon(GuiIconProvider::instance()->getIcon("wallet-open"));
|
|
m_ui->actionExit->setIcon(GuiIconProvider::instance()->getIcon("application-exit"));
|
|
m_ui->actionIncreasePriority->setIcon(GuiIconProvider::instance()->getIcon("go-up"));
|
|
m_ui->actionTopPriority->setIcon(GuiIconProvider::instance()->getIcon("go-top"));
|
|
m_ui->actionLock->setIcon(GuiIconProvider::instance()->getIcon("object-locked"));
|
|
m_ui->actionOptions->setIcon(GuiIconProvider::instance()->getIcon("configure", "preferences-system"));
|
|
m_ui->actionPause->setIcon(GuiIconProvider::instance()->getIcon("media-playback-pause"));
|
|
m_ui->actionPauseAll->setIcon(GuiIconProvider::instance()->getIcon("media-playback-pause"));
|
|
m_ui->actionStart->setIcon(GuiIconProvider::instance()->getIcon("media-playback-start"));
|
|
m_ui->actionStartAll->setIcon(GuiIconProvider::instance()->getIcon("media-playback-start"));
|
|
m_ui->menuAutoShutdownOnDownloadsCompletion->setIcon(GuiIconProvider::instance()->getIcon("application-exit"));
|
|
m_ui->actionManageCookies->setIcon(GuiIconProvider::instance()->getIcon("preferences-web-browser-cookies"));
|
|
|
|
QMenu *lockMenu = new QMenu(this);
|
|
QAction *defineUiLockPasswdAct = lockMenu->addAction(tr("&Set Password"));
|
|
connect(defineUiLockPasswdAct, &QAction::triggered, this, &MainWindow::defineUILockPassword);
|
|
QAction *clearUiLockPasswdAct = lockMenu->addAction(tr("&Clear Password"));
|
|
connect(clearUiLockPasswdAct, &QAction::triggered, this, &MainWindow::clearUILockPassword);
|
|
m_ui->actionLock->setMenu(lockMenu);
|
|
|
|
// Creating Bittorrent session
|
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::fullDiskError, this, &MainWindow::fullDiskError);
|
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::addTorrentFailed, this, &MainWindow::addTorrentFailed);
|
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentNew,this, &MainWindow::torrentNew);
|
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentFinished, this, &MainWindow::finishedTorrent);
|
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerAuthenticationRequired, this, &MainWindow::trackerAuthenticationRequired);
|
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::downloadFromUrlFailed, this, &MainWindow::handleDownloadFromUrlFailure);
|
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::speedLimitModeChanged, this, &MainWindow::updateAltSpeedsBtn);
|
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::recursiveTorrentDownloadPossible, this, &MainWindow::askRecursiveTorrentDownloadConfirmation);
|
|
|
|
qDebug("create tabWidget");
|
|
m_tabs = new HidableTabWidget(this);
|
|
connect(m_tabs.data(), &QTabWidget::currentChanged, this, &MainWindow::tabChanged);
|
|
|
|
m_splitter = new QSplitter(Qt::Horizontal, this);
|
|
// vSplitter->setChildrenCollapsible(false);
|
|
|
|
QSplitter *hSplitter = new QSplitter(Qt::Vertical, this);
|
|
hSplitter->setChildrenCollapsible(false);
|
|
hSplitter->setFrameShape(QFrame::NoFrame);
|
|
|
|
// Name filter
|
|
m_searchFilter = new LineEdit(this);
|
|
m_searchFilterAction = m_ui->toolBar->insertWidget(m_ui->actionLock, m_searchFilter);
|
|
m_searchFilter->setPlaceholderText(tr("Filter torrent list..."));
|
|
m_searchFilter->setFixedWidth(Utils::Gui::scaledSize(this, 200));
|
|
|
|
QWidget *spacer = new QWidget(this);
|
|
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
m_ui->toolBar->insertWidget(m_searchFilterAction, spacer);
|
|
|
|
// Transfer List tab
|
|
m_transferListWidget = new TransferListWidget(hSplitter, this);
|
|
// transferList->setStyleSheet("QTreeView {border: none;}"); // borderless
|
|
m_propertiesWidget = new PropertiesWidget(hSplitter, this, m_transferListWidget);
|
|
m_transferListFiltersWidget = new TransferListFiltersWidget(m_splitter, m_transferListWidget);
|
|
m_transferListFiltersWidget->setDownloadTrackerFavicon(isDownloadTrackerFavicon());
|
|
hSplitter->addWidget(m_transferListWidget);
|
|
hSplitter->addWidget(m_propertiesWidget);
|
|
m_splitter->addWidget(m_transferListFiltersWidget);
|
|
m_splitter->addWidget(hSplitter);
|
|
m_splitter->setCollapsible(0, true);
|
|
m_splitter->setCollapsible(1, false);
|
|
m_tabs->addTab(m_splitter,
|
|
#ifndef Q_OS_MAC
|
|
GuiIconProvider::instance()->getIcon("folder-remote"),
|
|
#endif
|
|
tr("Transfers"));
|
|
|
|
connect(m_searchFilter, &LineEdit::textChanged, m_transferListWidget, &TransferListWidget::applyNameFilter);
|
|
connect(hSplitter, &QSplitter::splitterMoved, this, &MainWindow::writeSettings);
|
|
connect(m_splitter, &QSplitter::splitterMoved, this, &MainWindow::writeSettings);
|
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersChanged, m_propertiesWidget, &PropertiesWidget::loadTrackers);
|
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersAdded, m_transferListFiltersWidget, &TransferListFiltersWidget::addTrackers);
|
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackersRemoved, m_transferListFiltersWidget, &TransferListFiltersWidget::removeTrackers);
|
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerlessStateChanged, m_transferListFiltersWidget, &TransferListFiltersWidget::changeTrackerless);
|
|
|
|
using Func = void (TransferListFiltersWidget::*)(BitTorrent::TorrentHandle *const, const QString &);
|
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerSuccess, m_transferListFiltersWidget, static_cast<Func>(&TransferListFiltersWidget::trackerSuccess));
|
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerError, m_transferListFiltersWidget, static_cast<Func>(&TransferListFiltersWidget::trackerError));
|
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::trackerWarning, m_transferListFiltersWidget, static_cast<Func>(&TransferListFiltersWidget::trackerWarning));
|
|
|
|
#ifdef Q_OS_MAC
|
|
// Increase top spacing to avoid tab overlapping
|
|
m_ui->centralWidgetLayout->addSpacing(8);
|
|
#endif
|
|
|
|
m_ui->centralWidgetLayout->addWidget(m_tabs);
|
|
|
|
m_prioSeparator = m_ui->toolBar->insertSeparator(m_ui->actionTopPriority);
|
|
m_prioSeparatorMenu = m_ui->menuEdit->insertSeparator(m_ui->actionTopPriority);
|
|
|
|
#ifdef Q_OS_MAC
|
|
foreach (QAction *action, m_ui->toolBar->actions()) {
|
|
if (action->isSeparator()) {
|
|
QWidget *spacer = new QWidget(this);
|
|
spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
spacer->setMinimumWidth(16);
|
|
m_ui->toolBar->insertWidget(action, spacer);
|
|
m_ui->toolBar->removeAction(action);
|
|
}
|
|
}
|
|
{
|
|
QWidget *spacer = new QWidget(this);
|
|
spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
spacer->setMinimumWidth(8);
|
|
m_ui->toolBar->insertWidget(m_ui->actionDownloadFromURL, spacer);
|
|
}
|
|
{
|
|
QWidget *spacer = new QWidget(this);
|
|
spacer->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
spacer->setMinimumWidth(8);
|
|
m_ui->toolBar->addWidget(spacer);
|
|
}
|
|
#endif
|
|
|
|
// Transfer list slots
|
|
connect(m_ui->actionStart, &QAction::triggered, m_transferListWidget, &TransferListWidget::startSelectedTorrents);
|
|
connect(m_ui->actionStartAll, &QAction::triggered, m_transferListWidget, &TransferListWidget::resumeAllTorrents);
|
|
connect(m_ui->actionPause, &QAction::triggered, m_transferListWidget, &TransferListWidget::pauseSelectedTorrents);
|
|
connect(m_ui->actionPauseAll, &QAction::triggered, m_transferListWidget, &TransferListWidget::pauseAllTorrents);
|
|
connect(m_ui->actionDelete, &QAction::triggered, m_transferListWidget, &TransferListWidget::softDeleteSelectedTorrents);
|
|
connect(m_ui->actionTopPriority, &QAction::triggered, m_transferListWidget, &TransferListWidget::topPrioSelectedTorrents);
|
|
connect(m_ui->actionIncreasePriority, &QAction::triggered, m_transferListWidget, &TransferListWidget::increasePrioSelectedTorrents);
|
|
connect(m_ui->actionDecreasePriority, &QAction::triggered, m_transferListWidget, &TransferListWidget::decreasePrioSelectedTorrents);
|
|
connect(m_ui->actionBottomPriority, &QAction::triggered, m_transferListWidget, &TransferListWidget::bottomPrioSelectedTorrents);
|
|
#ifndef Q_OS_MAC
|
|
connect(m_ui->actionToggleVisibility, &QAction::triggered, this, [this]() { toggleVisibility(); });
|
|
#endif
|
|
connect(m_ui->actionMinimize, &QAction::triggered, this, &MainWindow::minimizeWindow);
|
|
connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds);
|
|
|
|
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
|
m_programUpdateTimer = new QTimer(this);
|
|
m_programUpdateTimer->setInterval(60 * 60 * 1000);
|
|
m_programUpdateTimer->setSingleShot(true);
|
|
connect(m_programUpdateTimer, &QTimer::timeout, this, &MainWindow::checkProgramUpdate);
|
|
connect(m_ui->actionCheckForUpdates, &QAction::triggered, this, &MainWindow::checkProgramUpdate);
|
|
#else
|
|
m_ui->actionCheckForUpdates->setVisible(false);
|
|
#endif
|
|
|
|
// Certain menu items should reside at specific places on macOS.
|
|
// Qt partially does it on its own, but updates and different languages require tuning.
|
|
m_ui->actionExit->setMenuRole(QAction::QuitRole);
|
|
m_ui->actionAbout->setMenuRole(QAction::AboutRole);
|
|
m_ui->actionCheckForUpdates->setMenuRole(QAction::ApplicationSpecificRole);
|
|
m_ui->actionOptions->setMenuRole(QAction::PreferencesRole);
|
|
|
|
connect(m_ui->actionManageCookies, &QAction::triggered, this, &MainWindow::manageCookies);
|
|
|
|
m_pwr = new PowerManagement(this);
|
|
m_preventTimer = new QTimer(this);
|
|
connect(m_preventTimer, &QTimer::timeout, this, &MainWindow::updatePowerManagementState);
|
|
|
|
// Configure BT session according to options
|
|
loadPreferences(false);
|
|
|
|
connect(BitTorrent::Session::instance(), &BitTorrent::Session::torrentsUpdated, this, &MainWindow::updateGUI);
|
|
|
|
// Accept drag 'n drops
|
|
setAcceptDrops(true);
|
|
createKeyboardShortcuts();
|
|
|
|
#ifdef Q_OS_MAC
|
|
setUnifiedTitleAndToolBarOnMac(true);
|
|
#endif
|
|
|
|
// View settings
|
|
m_ui->actionTopToolBar->setChecked(pref->isToolbarDisplayed());
|
|
m_ui->actionShowStatusbar->setChecked(pref->isStatusbarDisplayed());
|
|
m_ui->actionSpeedInTitleBar->setChecked(pref->speedInTitleBar());
|
|
m_ui->actionRSSReader->setChecked(pref->isRSSWidgetEnabled());
|
|
m_ui->actionSearchWidget->setChecked(pref->isSearchEnabled());
|
|
m_ui->actionExecutionLogs->setChecked(isExecutionLogEnabled());
|
|
|
|
Log::MsgTypes flags(executionLogMsgTypes());
|
|
m_ui->actionNormalMessages->setChecked(flags & Log::NORMAL);
|
|
m_ui->actionInformationMessages->setChecked(flags & Log::INFO);
|
|
m_ui->actionWarningMessages->setChecked(flags & Log::WARNING);
|
|
m_ui->actionCriticalMessages->setChecked(flags & Log::CRITICAL);
|
|
|
|
displayRSSTab(m_ui->actionRSSReader->isChecked());
|
|
on_actionExecutionLogs_triggered(m_ui->actionExecutionLogs->isChecked());
|
|
on_actionNormalMessages_triggered(m_ui->actionNormalMessages->isChecked());
|
|
on_actionInformationMessages_triggered(m_ui->actionInformationMessages->isChecked());
|
|
on_actionWarningMessages_triggered(m_ui->actionWarningMessages->isChecked());
|
|
on_actionCriticalMessages_triggered(m_ui->actionCriticalMessages->isChecked());
|
|
if (m_ui->actionSearchWidget->isChecked())
|
|
QTimer::singleShot(0, this, &MainWindow::on_actionSearchWidget_triggered);
|
|
|
|
// Auto shutdown actions
|
|
QActionGroup *autoShutdownGroup = new QActionGroup(this);
|
|
autoShutdownGroup->setExclusive(true);
|
|
autoShutdownGroup->addAction(m_ui->actionAutoShutdownDisabled);
|
|
autoShutdownGroup->addAction(m_ui->actionAutoExit);
|
|
autoShutdownGroup->addAction(m_ui->actionAutoShutdown);
|
|
autoShutdownGroup->addAction(m_ui->actionAutoSuspend);
|
|
autoShutdownGroup->addAction(m_ui->actionAutoHibernate);
|
|
#if (!defined(Q_OS_UNIX) || defined(Q_OS_MAC)) || defined(QT_DBUS_LIB)
|
|
m_ui->actionAutoShutdown->setChecked(pref->shutdownWhenDownloadsComplete());
|
|
m_ui->actionAutoSuspend->setChecked(pref->suspendWhenDownloadsComplete());
|
|
m_ui->actionAutoHibernate->setChecked(pref->hibernateWhenDownloadsComplete());
|
|
#else
|
|
m_ui->actionAutoShutdown->setDisabled(true);
|
|
m_ui->actionAutoSuspend->setDisabled(true);
|
|
m_ui->actionAutoHibernate->setDisabled(true);
|
|
#endif
|
|
m_ui->actionAutoExit->setChecked(pref->shutdownqBTWhenDownloadsComplete());
|
|
|
|
if (!autoShutdownGroup->checkedAction())
|
|
m_ui->actionAutoShutdownDisabled->setChecked(true);
|
|
|
|
// Load Window state and sizes
|
|
readSettings();
|
|
|
|
#ifndef Q_OS_MAC
|
|
if (m_systrayIcon) {
|
|
if (!(pref->startMinimized() || m_uiLocked)) {
|
|
show();
|
|
activateWindow();
|
|
raise();
|
|
}
|
|
else if (pref->startMinimized()) {
|
|
showMinimized();
|
|
if (pref->minimizeToTray())
|
|
hide();
|
|
}
|
|
}
|
|
else {
|
|
#endif
|
|
// Make sure the Window is visible if we don't have a tray icon
|
|
if (pref->startMinimized()) {
|
|
showMinimized();
|
|
}
|
|
else {
|
|
show();
|
|
activateWindow();
|
|
raise();
|
|
}
|
|
#ifndef Q_OS_MAC
|
|
}
|
|
#endif
|
|
|
|
m_propertiesWidget->readSettings();
|
|
|
|
// Start watching the executable for updates
|
|
m_executableWatcher = new QFileSystemWatcher(this);
|
|
connect(m_executableWatcher, &QFileSystemWatcher::fileChanged, this, &MainWindow::notifyOfUpdate);
|
|
m_executableWatcher->addPath(qApp->applicationFilePath());
|
|
|
|
m_transferListWidget->setFocus();
|
|
|
|
// Update the number of torrents (tab)
|
|
updateNbTorrents();
|
|
connect(m_transferListWidget->getSourceModel(), &QAbstractItemModel::rowsInserted, this, &MainWindow::updateNbTorrents);
|
|
connect(m_transferListWidget->getSourceModel(), &QAbstractItemModel::rowsRemoved, this, &MainWindow::updateNbTorrents);
|
|
|
|
connect(pref, &Preferences::changed, this, &MainWindow::optionsSaved);
|
|
|
|
qDebug("GUI Built");
|
|
#ifdef Q_OS_WIN
|
|
if (!pref->neverCheckFileAssoc() && (!Preferences::isTorrentFileAssocSet() || !Preferences::isMagnetLinkAssocSet())) {
|
|
if (QMessageBox::question(this, tr("Torrent file association"),
|
|
tr("qBittorrent is not the default application to open torrent files or Magnet links.\nDo you want to associate qBittorrent to torrent files and Magnet links?"),
|
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) {
|
|
Preferences::setTorrentFileAssoc(true);
|
|
Preferences::setMagnetLinkAssoc(true);
|
|
}
|
|
else {
|
|
pref->setNeverCheckFileAssoc();
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef Q_OS_MAC
|
|
setupDockClickHandler();
|
|
qt_mac_set_dock_menu(trayIconMenu());
|
|
#endif
|
|
}
|
|
|
|
MainWindow::~MainWindow()
|
|
{
|
|
delete m_ui;
|
|
}
|
|
|
|
bool MainWindow::isExecutionLogEnabled() const
|
|
{
|
|
return settings()->loadValue(KEY_EXECUTIONLOG_ENABLED, false).toBool();
|
|
}
|
|
|
|
void MainWindow::setExecutionLogEnabled(bool value)
|
|
{
|
|
settings()->storeValue(KEY_EXECUTIONLOG_ENABLED, value);
|
|
}
|
|
|
|
int MainWindow::executionLogMsgTypes() const
|
|
{
|
|
// as default value we need all the bits set
|
|
// -1 is considered the portable way to achieve that
|
|
return settings()->loadValue(KEY_EXECUTIONLOG_TYPES, -1).toInt();
|
|
}
|
|
|
|
void MainWindow::setExecutionLogMsgTypes(const int value)
|
|
{
|
|
m_executionLog->showMsgTypes(static_cast<Log::MsgTypes>(value));
|
|
settings()->storeValue(KEY_EXECUTIONLOG_TYPES, value);
|
|
}
|
|
|
|
bool MainWindow::isNotificationsEnabled() const
|
|
{
|
|
return settings()->loadValue(KEY_NOTIFICATIONS_ENABLED, true).toBool();
|
|
}
|
|
|
|
void MainWindow::setNotificationsEnabled(bool value)
|
|
{
|
|
settings()->storeValue(KEY_NOTIFICATIONS_ENABLED, value);
|
|
}
|
|
|
|
bool MainWindow::isTorrentAddedNotificationsEnabled() const
|
|
{
|
|
return settings()->loadValue(KEY_NOTIFICATIONS_TORRENTADDED, false).toBool();
|
|
}
|
|
|
|
void MainWindow::setTorrentAddedNotificationsEnabled(bool value)
|
|
{
|
|
settings()->storeValue(KEY_NOTIFICATIONS_TORRENTADDED, value);
|
|
}
|
|
|
|
bool MainWindow::isDownloadTrackerFavicon() const
|
|
{
|
|
return settings()->loadValue(KEY_DOWNLOAD_TRACKER_FAVICON, true).toBool();
|
|
}
|
|
|
|
void MainWindow::setDownloadTrackerFavicon(bool value)
|
|
{
|
|
m_transferListFiltersWidget->setDownloadTrackerFavicon(value);
|
|
settings()->storeValue(KEY_DOWNLOAD_TRACKER_FAVICON, value);
|
|
}
|
|
|
|
void MainWindow::addToolbarContextMenu()
|
|
{
|
|
const Preferences *const pref = Preferences::instance();
|
|
m_toolbarMenu = new QMenu(this);
|
|
|
|
m_ui->toolBar->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
connect(m_ui->toolBar, &QWidget::customContextMenuRequested, this, &MainWindow::toolbarMenuRequested);
|
|
|
|
QAction *iconsOnly = new QAction(tr("Icons Only"), m_toolbarMenu);
|
|
connect(iconsOnly, &QAction::triggered, this, &MainWindow::toolbarIconsOnly);
|
|
QAction *textOnly = new QAction(tr("Text Only"), m_toolbarMenu);
|
|
connect(textOnly, &QAction::triggered, this, &MainWindow::toolbarTextOnly);
|
|
QAction *textBesideIcons = new QAction(tr("Text Alongside Icons"), m_toolbarMenu);
|
|
connect(textBesideIcons, &QAction::triggered, this, &MainWindow::toolbarTextBeside);
|
|
QAction *textUnderIcons = new QAction(tr("Text Under Icons"), m_toolbarMenu);
|
|
connect(textUnderIcons, &QAction::triggered, this, &MainWindow::toolbarTextUnder);
|
|
QAction *followSystemStyle = new QAction(tr("Follow System Style"), m_toolbarMenu);
|
|
connect(followSystemStyle, &QAction::triggered, this, &MainWindow::toolbarFollowSystem);
|
|
m_toolbarMenu->addAction(iconsOnly);
|
|
m_toolbarMenu->addAction(textOnly);
|
|
m_toolbarMenu->addAction(textBesideIcons);
|
|
m_toolbarMenu->addAction(textUnderIcons);
|
|
m_toolbarMenu->addAction(followSystemStyle);
|
|
QActionGroup *textPositionGroup = new QActionGroup(m_toolbarMenu);
|
|
textPositionGroup->addAction(iconsOnly);
|
|
iconsOnly->setCheckable(true);
|
|
textPositionGroup->addAction(textOnly);
|
|
textOnly->setCheckable(true);
|
|
textPositionGroup->addAction(textBesideIcons);
|
|
textBesideIcons->setCheckable(true);
|
|
textPositionGroup->addAction(textUnderIcons);
|
|
textUnderIcons->setCheckable(true);
|
|
textPositionGroup->addAction(followSystemStyle);
|
|
followSystemStyle->setCheckable(true);
|
|
|
|
const Qt::ToolButtonStyle buttonStyle = static_cast<Qt::ToolButtonStyle>(pref->getToolbarTextPosition());
|
|
if ((buttonStyle >= Qt::ToolButtonIconOnly) && (buttonStyle <= Qt::ToolButtonFollowStyle))
|
|
m_ui->toolBar->setToolButtonStyle(buttonStyle);
|
|
switch (buttonStyle) {
|
|
case Qt::ToolButtonIconOnly:
|
|
iconsOnly->setChecked(true);
|
|
break;
|
|
case Qt::ToolButtonTextOnly:
|
|
textOnly->setChecked(true);
|
|
break;
|
|
case Qt::ToolButtonTextBesideIcon:
|
|
textBesideIcons->setChecked(true);
|
|
break;
|
|
case Qt::ToolButtonTextUnderIcon:
|
|
textUnderIcons->setChecked(true);
|
|
break;
|
|
default:
|
|
followSystemStyle->setChecked(true);
|
|
}
|
|
}
|
|
|
|
void MainWindow::manageCookies()
|
|
{
|
|
CookiesDialog(this).exec();
|
|
}
|
|
|
|
void MainWindow::toolbarMenuRequested(QPoint point)
|
|
{
|
|
m_toolbarMenu->exec(m_ui->toolBar->mapToGlobal(point));
|
|
}
|
|
|
|
void MainWindow::toolbarIconsOnly()
|
|
{
|
|
m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly);
|
|
Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonIconOnly);
|
|
}
|
|
|
|
void MainWindow::toolbarTextOnly()
|
|
{
|
|
m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextOnly);
|
|
Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextOnly);
|
|
}
|
|
|
|
void MainWindow::toolbarTextBeside()
|
|
{
|
|
m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
|
Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextBesideIcon);
|
|
}
|
|
|
|
void MainWindow::toolbarTextUnder()
|
|
{
|
|
m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
|
|
Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextUnderIcon);
|
|
}
|
|
|
|
void MainWindow::toolbarFollowSystem()
|
|
{
|
|
m_ui->toolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle);
|
|
Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonFollowStyle);
|
|
}
|
|
|
|
void MainWindow::defineUILockPassword()
|
|
{
|
|
QString oldPassMd5 = Preferences::instance()->getUILockPasswordMD5();
|
|
if (oldPassMd5.isNull())
|
|
oldPassMd5 = "";
|
|
|
|
bool ok = false;
|
|
QString newClearPassword = AutoExpandableDialog::getText(this, tr("UI lock password"), tr("Please type the UI lock password:"), QLineEdit::Password, oldPassMd5, &ok);
|
|
if (ok) {
|
|
newClearPassword = newClearPassword.trimmed();
|
|
if (newClearPassword.size() < 3) {
|
|
QMessageBox::warning(this, tr("Invalid password"), tr("The password should contain at least 3 characters"));
|
|
}
|
|
else {
|
|
if (newClearPassword != oldPassMd5)
|
|
Preferences::instance()->setUILockPassword(newClearPassword);
|
|
QMessageBox::information(this, tr("Password update"), tr("The UI lock password has been successfully updated"));
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::clearUILockPassword()
|
|
{
|
|
QMessageBox::StandardButton answer = QMessageBox::question(this, tr("Clear the password"), tr("Are you sure you want to clear the password?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
|
if (answer == QMessageBox::Yes)
|
|
Preferences::instance()->clearUILockPassword();
|
|
}
|
|
|
|
void MainWindow::on_actionLock_triggered()
|
|
{
|
|
Preferences *const pref = Preferences::instance();
|
|
// Check if there is a password
|
|
if (pref->getUILockPasswordMD5().isEmpty()) {
|
|
// Ask for a password
|
|
bool ok = false;
|
|
QString clearPassword = AutoExpandableDialog::getText(this, tr("UI lock password"), tr("Please type the UI lock password:"), QLineEdit::Password, "", &ok);
|
|
if (!ok) return;
|
|
pref->setUILockPassword(clearPassword);
|
|
}
|
|
// Lock the interface
|
|
m_uiLocked = true;
|
|
pref->setUILocked(true);
|
|
m_trayIconMenu->setEnabled(false);
|
|
hide();
|
|
}
|
|
|
|
void MainWindow::handleRSSUnreadCountUpdated(int count)
|
|
{
|
|
m_tabs->setTabText(m_tabs->indexOf(m_rssWidget), tr("RSS (%1)").arg(count));
|
|
}
|
|
|
|
void MainWindow::displayRSSTab(bool enable)
|
|
{
|
|
if (enable) {
|
|
// RSS tab
|
|
if (!m_rssWidget) {
|
|
m_rssWidget = new RSSWidget(m_tabs);
|
|
connect(m_rssWidget.data(), &RSSWidget::unreadCountUpdated, this, &MainWindow::handleRSSUnreadCountUpdated);
|
|
#ifdef Q_OS_MAC
|
|
m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
|
|
#else
|
|
const int indexTab = m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount()));
|
|
m_tabs->setTabIcon(indexTab, GuiIconProvider::instance()->getIcon("application-rss+xml"));
|
|
#endif
|
|
}
|
|
}
|
|
else if (m_rssWidget) {
|
|
delete m_rssWidget;
|
|
}
|
|
}
|
|
|
|
void MainWindow::displaySearchTab(bool enable)
|
|
{
|
|
Preferences::instance()->setSearchEnabled(enable);
|
|
if (enable) {
|
|
// RSS tab
|
|
if (!m_searchWidget) {
|
|
m_searchWidget = new SearchWidget(this);
|
|
m_tabs->insertTab(1, m_searchWidget,
|
|
#ifndef Q_OS_MAC
|
|
GuiIconProvider::instance()->getIcon("edit-find"),
|
|
#endif
|
|
tr("Search"));
|
|
}
|
|
}
|
|
else if (m_searchWidget) {
|
|
delete m_searchWidget;
|
|
}
|
|
}
|
|
|
|
void MainWindow::focusSearchFilter()
|
|
{
|
|
m_searchFilter->setFocus();
|
|
m_searchFilter->selectAll();
|
|
}
|
|
|
|
void MainWindow::updateNbTorrents()
|
|
{
|
|
m_tabs->setTabText(0, tr("Transfers (%1)").arg(m_transferListWidget->getSourceModel()->rowCount()));
|
|
}
|
|
|
|
void MainWindow::on_actionDocumentation_triggered() const
|
|
{
|
|
QDesktopServices::openUrl(QUrl("http://doc.qbittorrent.org"));
|
|
}
|
|
|
|
void MainWindow::tabChanged(int newTab)
|
|
{
|
|
Q_UNUSED(newTab);
|
|
// We cannot rely on the index newTab
|
|
// because the tab order is undetermined now
|
|
if (m_tabs->currentWidget() == m_splitter) {
|
|
qDebug("Changed tab to transfer list, refreshing the list");
|
|
m_propertiesWidget->loadDynamicData();
|
|
m_searchFilterAction->setVisible(true);
|
|
return;
|
|
}
|
|
else {
|
|
m_searchFilterAction->setVisible(false);
|
|
}
|
|
if (m_tabs->currentWidget() == m_searchWidget) {
|
|
qDebug("Changed tab to search engine, giving focus to search input");
|
|
m_searchWidget->giveFocusToSearchInput();
|
|
}
|
|
}
|
|
|
|
void MainWindow::writeSettings()
|
|
{
|
|
Preferences *const pref = Preferences::instance();
|
|
pref->setMainGeometry(saveGeometry());
|
|
// Splitter size
|
|
pref->setMainVSplitterState(m_splitter->saveState());
|
|
m_propertiesWidget->saveSettings();
|
|
}
|
|
|
|
void MainWindow::cleanup()
|
|
{
|
|
writeSettings();
|
|
|
|
// delete RSSWidget explicitly to avoid crash in
|
|
// handleRSSUnreadCountUpdated() at application shutdown
|
|
delete m_rssWidget;
|
|
|
|
delete m_executableWatcher;
|
|
#ifndef Q_OS_MAC
|
|
if (m_systrayCreator)
|
|
m_systrayCreator->stop();
|
|
#endif
|
|
if (m_preventTimer)
|
|
m_preventTimer->stop();
|
|
#if (defined(Q_OS_WIN) || defined(Q_OS_MAC))
|
|
m_programUpdateTimer->stop();
|
|
#endif
|
|
|
|
delete m_searchFilterAction;
|
|
|
|
// remove all child widgets
|
|
while (QWidget *w = findChild<QWidget * >())
|
|
delete w;
|
|
}
|
|
|
|
void MainWindow::readSettings()
|
|
{
|
|
const Preferences *const pref = Preferences::instance();
|
|
const QByteArray mainGeo = pref->getMainGeometry();
|
|
if (!mainGeo.isEmpty() && restoreGeometry(mainGeo))
|
|
m_posInitialized = true;
|
|
const QByteArray splitterState = pref->getMainVSplitterState();
|
|
if (splitterState.isEmpty())
|
|
// Default sizes
|
|
m_splitter->setSizes({ 120, m_splitter->width() - 120 });
|
|
else
|
|
m_splitter->restoreState(splitterState);
|
|
}
|
|
|
|
void MainWindow::balloonClicked()
|
|
{
|
|
if (isHidden()) {
|
|
if (m_uiLocked) {
|
|
// Ask for UI lock password
|
|
if (!unlockUI())
|
|
return;
|
|
}
|
|
show();
|
|
if (isMinimized())
|
|
showNormal();
|
|
}
|
|
|
|
raise();
|
|
activateWindow();
|
|
}
|
|
|
|
void MainWindow::addTorrentFailed(const QString &error) const
|
|
{
|
|
showNotificationBaloon(tr("Error"), tr("Failed to add torrent: %1").arg(error));
|
|
}
|
|
|
|
// called when a torrent was added
|
|
void MainWindow::torrentNew(BitTorrent::TorrentHandle *const torrent) const
|
|
{
|
|
if (isTorrentAddedNotificationsEnabled())
|
|
showNotificationBaloon(tr("Torrent added"), tr("'%1' was added.", "e.g: xxx.avi was added.").arg(torrent->name()));
|
|
}
|
|
|
|
// called when a torrent has finished
|
|
void MainWindow::finishedTorrent(BitTorrent::TorrentHandle *const torrent) const
|
|
{
|
|
showNotificationBaloon(tr("Download completion"), tr("'%1' has finished downloading.", "e.g: xxx.avi has finished downloading.").arg(torrent->name()));
|
|
}
|
|
|
|
// Notification when disk is full
|
|
void MainWindow::fullDiskError(BitTorrent::TorrentHandle *const torrent, QString msg) const
|
|
{
|
|
showNotificationBaloon(tr("I/O Error", "i.e: Input/Output Error")
|
|
, tr("An I/O error occurred for torrent '%1'.\n Reason: %2"
|
|
, "e.g: An error occurred for torrent 'xxx.avi'.\n Reason: disk is full.").arg(torrent->name(), msg));
|
|
}
|
|
|
|
void MainWindow::createKeyboardShortcuts()
|
|
{
|
|
m_ui->actionCreateTorrent->setShortcut(QKeySequence::New);
|
|
m_ui->actionOpen->setShortcut(QKeySequence::Open);
|
|
m_ui->actionDelete->setShortcut(QKeySequence::Delete);
|
|
m_ui->actionDelete->setShortcutContext(Qt::WidgetShortcut); // nullify its effect: delete key event is handled by respective widgets, not here
|
|
m_ui->actionDownloadFromURL->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_O);
|
|
m_ui->actionExit->setShortcut(Qt::CTRL + Qt::Key_Q);
|
|
#ifdef Q_OS_MAC
|
|
m_ui->actionCloseWindow->setShortcut(QKeySequence::Close);
|
|
#else
|
|
m_ui->actionCloseWindow->setVisible(false);
|
|
#endif
|
|
|
|
QShortcut *switchTransferShortcut = new QShortcut(Qt::ALT + Qt::Key_1, this);
|
|
connect(switchTransferShortcut, &QShortcut::activated, this, &MainWindow::displayTransferTab);
|
|
|
|
using Func = void (MainWindow::*)();
|
|
QShortcut *switchSearchShortcut = new QShortcut(Qt::ALT + Qt::Key_2, this);
|
|
connect(switchSearchShortcut, &QShortcut::activated, this, static_cast<Func>(&MainWindow::displaySearchTab));
|
|
QShortcut *switchRSSShortcut = new QShortcut(Qt::ALT + Qt::Key_3, this);
|
|
connect(switchRSSShortcut, &QShortcut::activated, this, static_cast<Func>(&MainWindow::displayRSSTab));
|
|
QShortcut *switchExecutionLogShortcut = new QShortcut(Qt::ALT + Qt::Key_4, this);
|
|
connect(switchExecutionLogShortcut, &QShortcut::activated, this, &MainWindow::displayExecutionLogTab);
|
|
QShortcut *switchSearchFilterShortcut = new QShortcut(QKeySequence::Find, this);
|
|
connect(switchSearchFilterShortcut, &QShortcut::activated, this, &MainWindow::focusSearchFilter);
|
|
|
|
m_ui->actionDocumentation->setShortcut(QKeySequence::HelpContents);
|
|
m_ui->actionOptions->setShortcut(Qt::ALT + Qt::Key_O);
|
|
m_ui->actionStart->setShortcut(Qt::CTRL + Qt::Key_S);
|
|
m_ui->actionStartAll->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_S);
|
|
m_ui->actionPause->setShortcut(Qt::CTRL + Qt::Key_P);
|
|
m_ui->actionPauseAll->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_P);
|
|
m_ui->actionBottomPriority->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Minus);
|
|
m_ui->actionDecreasePriority->setShortcut(Qt::CTRL + Qt::Key_Minus);
|
|
m_ui->actionIncreasePriority->setShortcut(Qt::CTRL + Qt::Key_Plus);
|
|
m_ui->actionTopPriority->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Plus);
|
|
#ifdef Q_OS_MAC
|
|
m_ui->actionMinimize->setShortcut(Qt::CTRL + Qt::Key_M);
|
|
addAction(m_ui->actionMinimize);
|
|
#endif
|
|
}
|
|
|
|
// Keyboard shortcuts slots
|
|
void MainWindow::displayTransferTab() const
|
|
{
|
|
m_tabs->setCurrentWidget(m_transferListWidget);
|
|
}
|
|
|
|
void MainWindow::displaySearchTab()
|
|
{
|
|
if (!m_searchWidget) {
|
|
m_ui->actionSearchWidget->setChecked(true);
|
|
displaySearchTab(true);
|
|
}
|
|
|
|
m_tabs->setCurrentWidget(m_searchWidget);
|
|
}
|
|
|
|
void MainWindow::displayRSSTab()
|
|
{
|
|
if (!m_rssWidget) {
|
|
m_ui->actionRSSReader->setChecked(true);
|
|
displayRSSTab(true);
|
|
}
|
|
|
|
m_tabs->setCurrentWidget(m_rssWidget);
|
|
}
|
|
|
|
void MainWindow::displayExecutionLogTab()
|
|
{
|
|
if (!m_executionLog) {
|
|
m_ui->actionExecutionLogs->setChecked(true);
|
|
on_actionExecutionLogs_triggered(true);
|
|
}
|
|
|
|
m_tabs->setCurrentWidget(m_executionLog);
|
|
}
|
|
|
|
// End of keyboard shortcuts slots
|
|
|
|
void MainWindow::askRecursiveTorrentDownloadConfirmation(BitTorrent::TorrentHandle *const torrent)
|
|
{
|
|
Preferences *const pref = Preferences::instance();
|
|
if (pref->recursiveDownloadDisabled()) return;
|
|
|
|
const auto torrentHash = torrent->hash();
|
|
|
|
QMessageBox *confirmBox = new QMessageBox(QMessageBox::Question, tr("Recursive download confirmation")
|
|
, tr("The torrent '%1' contains torrent files, do you want to proceed with their download?").arg(torrent->name())
|
|
, QMessageBox::NoButton, this);
|
|
confirmBox->setAttribute(Qt::WA_DeleteOnClose);
|
|
confirmBox->setModal(true);
|
|
|
|
const QPushButton *yes = confirmBox->addButton(tr("Yes"), QMessageBox::YesRole);
|
|
/*QPushButton *no = */ confirmBox->addButton(tr("No"), QMessageBox::NoRole);
|
|
const QPushButton *never = confirmBox->addButton(tr("Never"), QMessageBox::NoRole);
|
|
connect(confirmBox, &QMessageBox::buttonClicked, this, [torrentHash, yes, never](const QAbstractButton *button)
|
|
{
|
|
if (button == yes)
|
|
BitTorrent::Session::instance()->recursiveTorrentDownload(torrentHash);
|
|
if (button == never)
|
|
Preferences::instance()->disableRecursiveDownload();
|
|
});
|
|
confirmBox->show();
|
|
}
|
|
|
|
void MainWindow::handleDownloadFromUrlFailure(QString url, QString reason) const
|
|
{
|
|
// Display a message box
|
|
showNotificationBaloon(tr("URL download error")
|
|
, tr("Couldn't download file at URL '%1', reason: %2.").arg(url, reason));
|
|
}
|
|
|
|
void MainWindow::on_actionSetGlobalUploadLimit_triggered()
|
|
{
|
|
qDebug() << Q_FUNC_INFO;
|
|
|
|
BitTorrent::Session *const session = BitTorrent::Session::instance();
|
|
bool ok = false;
|
|
const long newLimit = SpeedLimitDialog::askSpeedLimit(
|
|
this, &ok, tr("Global Upload Speed Limit"), session->uploadSpeedLimit());
|
|
|
|
if (ok) {
|
|
qDebug("Setting global upload rate limit to %.1fKb/s", newLimit / 1024.);
|
|
session->setUploadSpeedLimit(newLimit);
|
|
}
|
|
}
|
|
|
|
void MainWindow::on_actionSetGlobalDownloadLimit_triggered()
|
|
{
|
|
qDebug() << Q_FUNC_INFO;
|
|
|
|
BitTorrent::Session *const session = BitTorrent::Session::instance();
|
|
bool ok = false;
|
|
const long newLimit = SpeedLimitDialog::askSpeedLimit(
|
|
this, &ok, tr("Global Download Speed Limit"), session->downloadSpeedLimit());
|
|
|
|
if (ok) {
|
|
qDebug("Setting global download rate limit to %.1fKb/s", newLimit / 1024.);
|
|
session->setDownloadSpeedLimit(newLimit);
|
|
}
|
|
}
|
|
|
|
// Necessary if we want to close the window
|
|
// in one time if "close to systray" is enabled
|
|
void MainWindow::on_actionExit_triggered()
|
|
{
|
|
// UI locking enforcement.
|
|
if (isHidden() && m_uiLocked)
|
|
// Ask for UI lock password
|
|
if (!unlockUI()) return;
|
|
|
|
m_forceExit = true;
|
|
close();
|
|
}
|
|
|
|
#ifdef Q_OS_MAC
|
|
void MainWindow::on_actionCloseWindow_triggered()
|
|
{
|
|
// On macOS window close is basically equivalent to window hide.
|
|
// If you decide to implement this functionality for other OS,
|
|
// then you will also need ui lock checks like in actionExit.
|
|
close();
|
|
}
|
|
#endif
|
|
|
|
QWidget *MainWindow::currentTabWidget() const
|
|
{
|
|
if (isMinimized() || !isVisible())
|
|
return 0;
|
|
if (m_tabs->currentIndex() == 0)
|
|
return m_transferListWidget;
|
|
return m_tabs->currentWidget();
|
|
}
|
|
|
|
TransferListWidget *MainWindow::transferListWidget() const
|
|
{
|
|
return m_transferListWidget;
|
|
}
|
|
|
|
bool MainWindow::unlockUI()
|
|
{
|
|
if (m_unlockDlgShowing)
|
|
return false;
|
|
else
|
|
m_unlockDlgShowing = true;
|
|
|
|
bool ok = false;
|
|
QString clearPassword = AutoExpandableDialog::getText(this, tr("UI lock password"), tr("Please type the UI lock password:"), QLineEdit::Password, "", &ok);
|
|
m_unlockDlgShowing = false;
|
|
if (!ok) return false;
|
|
|
|
Preferences *const pref = Preferences::instance();
|
|
QString realPassMd5 = pref->getUILockPasswordMD5();
|
|
QCryptographicHash md5(QCryptographicHash::Md5);
|
|
md5.addData(clearPassword.toLocal8Bit());
|
|
QString passwordMd5 = md5.result().toHex();
|
|
if (realPassMd5 == passwordMd5) {
|
|
m_uiLocked = false;
|
|
pref->setUILocked(false);
|
|
m_trayIconMenu->setEnabled(true);
|
|
return true;
|
|
}
|
|
QMessageBox::warning(this, tr("Invalid password"), tr("The password is invalid"));
|
|
return false;
|
|
}
|
|
|
|
void MainWindow::notifyOfUpdate(QString)
|
|
{
|
|
// Show restart message
|
|
m_statusBar->showRestartRequired();
|
|
Logger::instance()->addMessage(tr("qBittorrent was just updated and needs to be restarted for the changes to be effective.")
|
|
, Log::CRITICAL);
|
|
// Delete the executable watcher
|
|
delete m_executableWatcher;
|
|
m_executableWatcher = nullptr;
|
|
}
|
|
|
|
#ifndef Q_OS_MAC
|
|
// Toggle Main window visibility
|
|
void MainWindow::toggleVisibility(const QSystemTrayIcon::ActivationReason reason)
|
|
{
|
|
switch (reason) {
|
|
case QSystemTrayIcon::Trigger:
|
|
if (isHidden()) {
|
|
if (m_uiLocked && !unlockUI()) // Ask for UI lock password
|
|
return;
|
|
|
|
// Make sure the window is not minimized
|
|
setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
|
|
|
|
// Then show it
|
|
show();
|
|
raise();
|
|
activateWindow();
|
|
}
|
|
else {
|
|
hide();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Display About Dialog
|
|
void MainWindow::on_actionAbout_triggered()
|
|
{
|
|
// About dialog
|
|
if (m_aboutDlg)
|
|
m_aboutDlg->activateWindow();
|
|
else
|
|
m_aboutDlg = new AboutDialog(this);
|
|
}
|
|
|
|
void MainWindow::on_actionStatistics_triggered()
|
|
{
|
|
if (m_statsDlg)
|
|
m_statsDlg->activateWindow();
|
|
else
|
|
m_statsDlg = new StatsDialog(this);
|
|
}
|
|
|
|
void MainWindow::showEvent(QShowEvent *e)
|
|
{
|
|
qDebug("** Show Event **");
|
|
|
|
if (currentTabWidget() == m_transferListWidget)
|
|
m_propertiesWidget->loadDynamicData();
|
|
|
|
e->accept();
|
|
|
|
// Make sure the window is initially centered
|
|
if (!m_posInitialized) {
|
|
move(Utils::Misc::screenCenter(this));
|
|
m_posInitialized = true;
|
|
}
|
|
}
|
|
|
|
// Called when we close the program
|
|
void MainWindow::closeEvent(QCloseEvent *e)
|
|
{
|
|
Preferences *const pref = Preferences::instance();
|
|
#ifdef Q_OS_MAC
|
|
if (!m_forceExit) {
|
|
hide();
|
|
e->accept();
|
|
return;
|
|
}
|
|
#else
|
|
const bool goToSystrayOnExit = pref->closeToTray();
|
|
if (!m_forceExit && m_systrayIcon && goToSystrayOnExit && !this->isHidden()) {
|
|
hide();
|
|
e->accept();
|
|
return;
|
|
}
|
|
#endif // Q_OS_MAC
|
|
|
|
if (pref->confirmOnExit() && BitTorrent::Session::instance()->hasActiveTorrents()) {
|
|
if (e->spontaneous() || m_forceExit) {
|
|
if (!isVisible())
|
|
show();
|
|
QMessageBox confirmBox(QMessageBox::Question, tr("Exiting qBittorrent"),
|
|
// Split it because the last sentence is used in the Web UI
|
|
tr("Some files are currently transferring.") + '\n' + tr("Are you sure you want to quit qBittorrent?"),
|
|
QMessageBox::NoButton, this);
|
|
QPushButton *noBtn = confirmBox.addButton(tr("&No"), QMessageBox::NoRole);
|
|
confirmBox.addButton(tr("&Yes"), QMessageBox::YesRole);
|
|
QPushButton *alwaysBtn = confirmBox.addButton(tr("&Always Yes"), QMessageBox::YesRole);
|
|
confirmBox.setDefaultButton(noBtn);
|
|
confirmBox.exec();
|
|
if (!confirmBox.clickedButton() || (confirmBox.clickedButton() == noBtn)) {
|
|
// Cancel exit
|
|
e->ignore();
|
|
m_forceExit = false;
|
|
return;
|
|
}
|
|
if (confirmBox.clickedButton() == alwaysBtn)
|
|
// Remember choice
|
|
Preferences::instance()->setConfirmOnExit(false);
|
|
}
|
|
}
|
|
|
|
// abort search if any
|
|
if (m_searchWidget)
|
|
delete m_searchWidget;
|
|
|
|
hide();
|
|
#ifndef Q_OS_MAC
|
|
// Hide tray icon
|
|
if (m_systrayIcon)
|
|
m_systrayIcon->hide();
|
|
#endif
|
|
// Accept exit
|
|
e->accept();
|
|
qApp->exit();
|
|
}
|
|
|
|
// Display window to create a torrent
|
|
void MainWindow::on_actionCreateTorrent_triggered()
|
|
{
|
|
createTorrentTriggered();
|
|
}
|
|
|
|
void MainWindow::createTorrentTriggered(const QString &path)
|
|
{
|
|
if (m_createTorrentDlg) {
|
|
m_createTorrentDlg->updateInputPath(path);
|
|
m_createTorrentDlg->activateWindow();
|
|
}
|
|
else
|
|
m_createTorrentDlg = new TorrentCreatorDialog(this, path);
|
|
}
|
|
|
|
bool MainWindow::event(QEvent *e)
|
|
{
|
|
#ifndef Q_OS_MAC
|
|
switch (e->type()) {
|
|
case QEvent::WindowStateChange: {
|
|
qDebug("Window change event");
|
|
// Now check to see if the window is minimised
|
|
if (isMinimized()) {
|
|
qDebug("minimisation");
|
|
if (m_systrayIcon && Preferences::instance()->minimizeToTray()) {
|
|
qDebug() << "Has active window:" << (qApp->activeWindow() != nullptr);
|
|
// Check if there is a modal window
|
|
bool hasModalWindow = false;
|
|
foreach (QWidget *widget, QApplication::allWidgets()) {
|
|
if (widget->isModal()) {
|
|
hasModalWindow = true;
|
|
break;
|
|
}
|
|
}
|
|
// Iconify if there is no modal window
|
|
if (!hasModalWindow) {
|
|
qDebug("Minimize to Tray enabled, hiding!");
|
|
e->ignore();
|
|
QTimer::singleShot(0, this, &QWidget::hide);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case QEvent::ToolBarChange: {
|
|
qDebug("MAC: Received a toolbar change event!");
|
|
bool ret = QMainWindow::event(e);
|
|
|
|
qDebug("MAC: new toolbar visibility is %d", !m_ui->actionTopToolBar->isChecked());
|
|
m_ui->actionTopToolBar->toggle();
|
|
Preferences::instance()->setToolbarDisplayed(m_ui->actionTopToolBar->isChecked());
|
|
return ret;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
return QMainWindow::event(e);
|
|
}
|
|
|
|
// action executed when a file is dropped
|
|
void MainWindow::dropEvent(QDropEvent *event)
|
|
{
|
|
event->acceptProposedAction();
|
|
|
|
// remove scheme
|
|
QStringList files;
|
|
if (event->mimeData()->hasUrls()) {
|
|
foreach (const QUrl &url, event->mimeData()->urls()) {
|
|
if (url.isEmpty())
|
|
continue;
|
|
|
|
files << ((url.scheme().compare("file", Qt::CaseInsensitive) == 0)
|
|
? url.toLocalFile()
|
|
: url.toString());
|
|
}
|
|
}
|
|
else {
|
|
files = event->mimeData()->text().split('\n');
|
|
}
|
|
|
|
// differentiate ".torrent" files/links & magnet links from others
|
|
QStringList torrentFiles, otherFiles;
|
|
foreach (const QString &file, files) {
|
|
const bool isTorrentLink = (file.startsWith("magnet:", Qt::CaseInsensitive)
|
|
|| file.endsWith(C_TORRENT_FILE_EXTENSION, Qt::CaseInsensitive)
|
|
|| Utils::Misc::isUrl(file));
|
|
if (isTorrentLink)
|
|
torrentFiles << file;
|
|
else
|
|
otherFiles << file;
|
|
}
|
|
|
|
// Download torrents
|
|
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
|
|
foreach (const QString &file, torrentFiles) {
|
|
if (useTorrentAdditionDialog)
|
|
AddNewTorrentDialog::show(file, this);
|
|
else
|
|
BitTorrent::Session::instance()->addTorrent(file);
|
|
}
|
|
if (!torrentFiles.isEmpty()) return;
|
|
|
|
// Create torrent
|
|
foreach (const QString &file, otherFiles) {
|
|
createTorrentTriggered(file);
|
|
|
|
// currently only hande the first entry
|
|
// this is a stub that can be expanded later to create many torrents at once
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Decode if we accept drag 'n drop or not
|
|
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
|
|
{
|
|
foreach (const QString &mime, event->mimeData()->formats())
|
|
qDebug("mimeData: %s", mime.toLocal8Bit().data());
|
|
if (event->mimeData()->hasFormat("text/plain") || event->mimeData()->hasFormat("text/uri-list"))
|
|
event->acceptProposedAction();
|
|
}
|
|
|
|
#ifdef Q_OS_MAC
|
|
|
|
static MainWindow *dockMainWindowHandle;
|
|
|
|
static bool dockClickHandler(id self, SEL cmd, ...)
|
|
{
|
|
Q_UNUSED(self)
|
|
Q_UNUSED(cmd)
|
|
|
|
if (dockMainWindowHandle && !dockMainWindowHandle->isVisible()) {
|
|
dockMainWindowHandle->activate();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void MainWindow::setupDockClickHandler()
|
|
{
|
|
dockMainWindowHandle = this;
|
|
MacUtils::overrideDockClickHandler(dockClickHandler);
|
|
}
|
|
|
|
#endif
|
|
|
|
/*****************************************************
|
|
* *
|
|
* Torrent *
|
|
* *
|
|
*****************************************************/
|
|
|
|
// Display a dialog to allow user to add
|
|
// torrents to download list
|
|
void MainWindow::on_actionOpen_triggered()
|
|
{
|
|
Preferences *const pref = Preferences::instance();
|
|
// Open File Open Dialog
|
|
// Note: it is possible to select more than one file
|
|
const QStringList pathsList =
|
|
QFileDialog::getOpenFileNames(this, tr("Open Torrent Files"), pref->getMainLastDir(),
|
|
tr("Torrent Files") + " (*" + C_TORRENT_FILE_EXTENSION + ')');
|
|
|
|
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
|
|
if (!pathsList.isEmpty()) {
|
|
foreach (QString file, pathsList) {
|
|
qDebug("Dropped file %s on download list", qUtf8Printable(file));
|
|
if (useTorrentAdditionDialog)
|
|
AddNewTorrentDialog::show(file, this);
|
|
else
|
|
BitTorrent::Session::instance()->addTorrent(file);
|
|
}
|
|
|
|
// Save last dir to remember it
|
|
QStringList topDir = Utils::Fs::fromNativePath(pathsList.at(0)).split('/');
|
|
topDir.removeLast();
|
|
pref->setMainLastDir(Utils::Fs::fromNativePath(topDir.join('/')));
|
|
}
|
|
}
|
|
|
|
void MainWindow::activate()
|
|
{
|
|
if (!m_uiLocked || unlockUI()) {
|
|
show();
|
|
activateWindow();
|
|
raise();
|
|
}
|
|
}
|
|
|
|
void MainWindow::optionsSaved()
|
|
{
|
|
loadPreferences();
|
|
}
|
|
|
|
void MainWindow::showStatusBar(bool show)
|
|
{
|
|
if (!show) {
|
|
// Remove status bar
|
|
setStatusBar(nullptr);
|
|
}
|
|
else if (!m_statusBar) {
|
|
// Create status bar
|
|
m_statusBar = new StatusBar;
|
|
connect(m_statusBar.data(), &StatusBar::connectionButtonClicked, this, &MainWindow::showConnectionSettings);
|
|
connect(m_statusBar.data(), &StatusBar::alternativeSpeedsButtonClicked, this, &MainWindow::toggleAlternativeSpeeds);
|
|
setStatusBar(m_statusBar);
|
|
}
|
|
}
|
|
|
|
void MainWindow::loadPreferences(bool configureSession)
|
|
{
|
|
Logger::instance()->addMessage(tr("Options were saved successfully."));
|
|
const Preferences *const pref = Preferences::instance();
|
|
#ifdef Q_OS_MAC
|
|
Q_UNUSED(configureSession);
|
|
#else
|
|
const bool newSystrayIntegration = pref->systrayIntegration();
|
|
m_ui->actionLock->setVisible(newSystrayIntegration);
|
|
if (newSystrayIntegration != (m_systrayIcon != 0)) {
|
|
if (newSystrayIntegration) {
|
|
// create the trayicon
|
|
if (!QSystemTrayIcon::isSystemTrayAvailable()) {
|
|
if (!configureSession) { // Program startup
|
|
m_systrayCreator = new QTimer(this);
|
|
connect(m_systrayCreator.data(), &QTimer::timeout, this, &MainWindow::createSystrayDelayed);
|
|
m_systrayCreator->setSingleShot(true);
|
|
m_systrayCreator->start(2000);
|
|
qDebug("Info: System tray is unavailable, trying again later.");
|
|
}
|
|
else {
|
|
qDebug("Warning: System tray is unavailable.");
|
|
}
|
|
}
|
|
else {
|
|
createTrayIcon();
|
|
}
|
|
}
|
|
else {
|
|
// Destroy trayicon
|
|
delete m_systrayIcon;
|
|
delete m_trayIconMenu;
|
|
}
|
|
}
|
|
// Reload systray icon
|
|
if (newSystrayIntegration && m_systrayIcon)
|
|
m_systrayIcon->setIcon(getSystrayIcon());
|
|
#endif
|
|
// General
|
|
if (pref->isToolbarDisplayed()) {
|
|
m_ui->toolBar->setVisible(true);
|
|
}
|
|
else {
|
|
// Clear search filter before hiding the top toolbar
|
|
m_searchFilter->clear();
|
|
m_ui->toolBar->setVisible(false);
|
|
}
|
|
|
|
showStatusBar(pref->isStatusbarDisplayed());
|
|
|
|
if ((pref->preventFromSuspendWhenDownloading() || pref->preventFromSuspendWhenSeeding()) && !m_preventTimer->isActive()) {
|
|
m_preventTimer->start(PREVENT_SUSPEND_INTERVAL);
|
|
}
|
|
else {
|
|
m_preventTimer->stop();
|
|
m_pwr->setActivityState(false);
|
|
}
|
|
|
|
m_transferListWidget->setAlternatingRowColors(pref->useAlternatingRowColors());
|
|
m_propertiesWidget->getFilesList()->setAlternatingRowColors(pref->useAlternatingRowColors());
|
|
m_propertiesWidget->getTrackerList()->setAlternatingRowColors(pref->useAlternatingRowColors());
|
|
m_propertiesWidget->getPeerList()->setAlternatingRowColors(pref->useAlternatingRowColors());
|
|
|
|
// Queueing System
|
|
if (BitTorrent::Session::instance()->isQueueingSystemEnabled()) {
|
|
if (!m_ui->actionDecreasePriority->isVisible()) {
|
|
m_transferListWidget->hidePriorityColumn(false);
|
|
m_ui->actionDecreasePriority->setVisible(true);
|
|
m_ui->actionIncreasePriority->setVisible(true);
|
|
m_ui->actionTopPriority->setVisible(true);
|
|
m_ui->actionBottomPriority->setVisible(true);
|
|
#ifndef Q_OS_MAC
|
|
m_prioSeparator->setVisible(true);
|
|
#endif
|
|
m_prioSeparatorMenu->setVisible(true);
|
|
}
|
|
}
|
|
else {
|
|
if (m_ui->actionDecreasePriority->isVisible()) {
|
|
m_transferListWidget->hidePriorityColumn(true);
|
|
m_ui->actionDecreasePriority->setVisible(false);
|
|
m_ui->actionIncreasePriority->setVisible(false);
|
|
m_ui->actionTopPriority->setVisible(false);
|
|
m_ui->actionBottomPriority->setVisible(false);
|
|
#ifndef Q_OS_MAC
|
|
m_prioSeparator->setVisible(false);
|
|
#endif
|
|
m_prioSeparatorMenu->setVisible(false);
|
|
}
|
|
}
|
|
|
|
// Torrent properties
|
|
m_propertiesWidget->reloadPreferences();
|
|
|
|
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
|
if (pref->isUpdateCheckEnabled() && !m_wasUpdateCheckEnabled) {
|
|
m_wasUpdateCheckEnabled = true;
|
|
checkProgramUpdate();
|
|
}
|
|
else if (!pref->isUpdateCheckEnabled() && m_wasUpdateCheckEnabled) {
|
|
m_wasUpdateCheckEnabled = false;
|
|
m_programUpdateTimer->stop();
|
|
}
|
|
#endif
|
|
|
|
qDebug("GUI settings loaded");
|
|
}
|
|
|
|
void MainWindow::addUnauthenticatedTracker(const QPair<BitTorrent::TorrentHandle *, QString> &tracker)
|
|
{
|
|
// Trackers whose authentication was cancelled
|
|
if (m_unauthenticatedTrackers.indexOf(tracker) < 0)
|
|
m_unauthenticatedTrackers << tracker;
|
|
}
|
|
|
|
// Called when a tracker requires authentication
|
|
void MainWindow::trackerAuthenticationRequired(BitTorrent::TorrentHandle *const torrent)
|
|
{
|
|
#if LIBTORRENT_VERSION_NUM < 10100
|
|
if (m_unauthenticatedTrackers.indexOf(qMakePair(torrent, torrent->currentTracker())) < 0)
|
|
// Tracker login
|
|
new TrackerLoginDialog(this, torrent);
|
|
#else
|
|
Q_UNUSED(torrent);
|
|
#endif
|
|
}
|
|
|
|
// Check connection status and display right icon
|
|
void MainWindow::updateGUI()
|
|
{
|
|
const BitTorrent::SessionStatus &status = BitTorrent::Session::instance()->status();
|
|
|
|
// update global information
|
|
#ifndef Q_OS_MAC
|
|
if (m_systrayIcon) {
|
|
#ifdef Q_OS_UNIX
|
|
QString html = "<div style='background-color: #678db2; color: #fff;height: 18px; font-weight: bold; margin-bottom: 5px;'>";
|
|
html += "qBittorrent";
|
|
html += "</div>";
|
|
html += "<div style='vertical-align: baseline; height: 18px;'>";
|
|
html += "<img src=':/icons/skin/download.png' height='14'/> " + tr("DL speed: %1", "e.g: Download speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true));
|
|
html += "</div>";
|
|
html += "<div style='vertical-align: baseline; height: 18px;'>";
|
|
html += "<img src=':/icons/skin/seeding.png' height='14'/> " + tr("UP speed: %1", "e.g: Upload speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadUploadRate, true));
|
|
html += "</div>";
|
|
#else
|
|
// OSes such as Windows do not support html here
|
|
QString html = tr("DL speed: %1", "e.g: Download speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true));
|
|
html += '\n';
|
|
html += tr("UP speed: %1", "e.g: Upload speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadUploadRate, true));
|
|
#endif // Q_OS_UNIX
|
|
m_systrayIcon->setToolTip(html); // tray icon
|
|
}
|
|
#else
|
|
if (status.payloadDownloadRate > 0)
|
|
QtMac::setBadgeLabelText(tr("%1/s", "s is a shorthand for seconds")
|
|
.arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate)));
|
|
else if (!QtMac::badgeLabelText().isEmpty())
|
|
QtMac::setBadgeLabelText("");
|
|
#endif // Q_OS_MAC
|
|
|
|
if (m_displaySpeedInTitle) {
|
|
setWindowTitle(tr("[D: %1, U: %2] qBittorrent %3", "D = Download; U = Upload; %3 is qBittorrent version")
|
|
.arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true)
|
|
, Utils::Misc::friendlyUnit(status.payloadUploadRate, true)
|
|
, QBT_VERSION));
|
|
}
|
|
}
|
|
|
|
void MainWindow::showNotificationBaloon(QString title, QString msg) const
|
|
{
|
|
if (!isNotificationsEnabled()) return;
|
|
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) && defined(QT_DBUS_LIB)
|
|
org::freedesktop::Notifications notifications("org.freedesktop.Notifications",
|
|
"/org/freedesktop/Notifications",
|
|
QDBusConnection::sessionBus());
|
|
// Testing for 'notifications.isValid()' isn't helpful here.
|
|
// If the notification daemon is configured to run 'as needed'
|
|
// the above check can be false if the daemon wasn't started
|
|
// by another application. In this case DBus will be able to
|
|
// start the notification daemon and complete our request. Such
|
|
// a daemon is xfce4-notifyd, DBus autostarts it and after
|
|
// some inactivity shuts it down. Other DEs, like GNOME, choose
|
|
// to start their daemons at the session startup and have it sit
|
|
// idling for the whole session.
|
|
QVariantMap hints;
|
|
hints["desktop-entry"] = "qBittorrent";
|
|
QDBusPendingReply<uint> reply = notifications.Notify("qBittorrent", 0, "qbittorrent", title,
|
|
msg, QStringList(), hints, -1);
|
|
reply.waitForFinished();
|
|
if (!reply.isError())
|
|
return;
|
|
#elif defined(Q_OS_MAC)
|
|
MacUtils::displayNotification(title, msg);
|
|
#else
|
|
if (m_systrayIcon && QSystemTrayIcon::supportsMessages())
|
|
m_systrayIcon->showMessage(title, msg, QSystemTrayIcon::Information, TIME_TRAY_BALLOON);
|
|
#endif
|
|
}
|
|
|
|
/*****************************************************
|
|
* *
|
|
* Utils *
|
|
* *
|
|
*****************************************************/
|
|
|
|
void MainWindow::downloadFromURLList(const QStringList &urlList)
|
|
{
|
|
const bool useTorrentAdditionDialog = AddNewTorrentDialog::isEnabled();
|
|
foreach (QString url, urlList) {
|
|
if (((url.size() == 40) && !url.contains(QRegularExpression("[^0-9A-Fa-f]")))
|
|
|| ((url.size() == 32) && !url.contains(QRegularExpression("[^2-7A-Za-z]"))))
|
|
url = "magnet:?xt=urn:btih:" + url;
|
|
|
|
if (useTorrentAdditionDialog)
|
|
AddNewTorrentDialog::show(url, this);
|
|
else
|
|
BitTorrent::Session::instance()->addTorrent(url);
|
|
}
|
|
}
|
|
|
|
/*****************************************************
|
|
* *
|
|
* Options *
|
|
* *
|
|
*****************************************************/
|
|
|
|
#ifndef Q_OS_MAC
|
|
void MainWindow::createSystrayDelayed()
|
|
{
|
|
static int timeout = 20;
|
|
if (QSystemTrayIcon::isSystemTrayAvailable()) {
|
|
// Ok, systray integration is now supported
|
|
// Create systray icon
|
|
createTrayIcon();
|
|
delete m_systrayCreator;
|
|
}
|
|
else {
|
|
if (timeout) {
|
|
// Retry a bit later
|
|
m_systrayCreator->start(2000);
|
|
--timeout;
|
|
}
|
|
else {
|
|
// Timed out, apparently system really does not
|
|
// support systray icon
|
|
delete m_systrayCreator;
|
|
// Disable it in program preferences to
|
|
// avoid trying at each startup
|
|
Preferences::instance()->setSystrayIntegration(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::updateTrayIconMenu()
|
|
{
|
|
m_ui->actionToggleVisibility->setText(isVisible() ? tr("Hide") : tr("Show"));
|
|
}
|
|
|
|
void MainWindow::createTrayIcon()
|
|
{
|
|
// Tray icon
|
|
m_systrayIcon = new QSystemTrayIcon(getSystrayIcon(), this);
|
|
|
|
m_systrayIcon->setContextMenu(trayIconMenu());
|
|
connect(m_systrayIcon.data(), &QSystemTrayIcon::messageClicked, this, &MainWindow::balloonClicked);
|
|
// End of Icon Menu
|
|
connect(m_systrayIcon.data(), &QSystemTrayIcon::activated, this, &MainWindow::toggleVisibility);
|
|
m_systrayIcon->show();
|
|
}
|
|
#endif // Q_OS_MAC
|
|
|
|
QMenu *MainWindow::trayIconMenu()
|
|
{
|
|
if (m_trayIconMenu) return m_trayIconMenu;
|
|
|
|
m_trayIconMenu = new QMenu(this);
|
|
#ifndef Q_OS_MAC
|
|
connect(m_trayIconMenu.data(), &QMenu::aboutToShow, this, &MainWindow::updateTrayIconMenu);
|
|
m_trayIconMenu->addAction(m_ui->actionToggleVisibility);
|
|
m_trayIconMenu->addSeparator();
|
|
#endif
|
|
m_trayIconMenu->addAction(m_ui->actionOpen);
|
|
m_trayIconMenu->addAction(m_ui->actionDownloadFromURL);
|
|
m_trayIconMenu->addSeparator();
|
|
const bool isAltBWEnabled = BitTorrent::Session::instance()->isAltGlobalSpeedLimitEnabled();
|
|
updateAltSpeedsBtn(isAltBWEnabled);
|
|
m_ui->actionUseAlternativeSpeedLimits->setChecked(isAltBWEnabled);
|
|
m_trayIconMenu->addAction(m_ui->actionUseAlternativeSpeedLimits);
|
|
m_trayIconMenu->addAction(m_ui->actionSetGlobalDownloadLimit);
|
|
m_trayIconMenu->addAction(m_ui->actionSetGlobalUploadLimit);
|
|
m_trayIconMenu->addSeparator();
|
|
m_trayIconMenu->addAction(m_ui->actionStartAll);
|
|
m_trayIconMenu->addAction(m_ui->actionPauseAll);
|
|
#ifndef Q_OS_MAC
|
|
m_trayIconMenu->addSeparator();
|
|
m_trayIconMenu->addAction(m_ui->actionExit);
|
|
#endif
|
|
if (m_uiLocked)
|
|
m_trayIconMenu->setEnabled(false);
|
|
|
|
return m_trayIconMenu;
|
|
}
|
|
|
|
void MainWindow::updateAltSpeedsBtn(bool alternative)
|
|
{
|
|
m_ui->actionUseAlternativeSpeedLimits->setChecked(alternative);
|
|
}
|
|
|
|
PropertiesWidget *MainWindow::propertiesWidget() const
|
|
{
|
|
return m_propertiesWidget;
|
|
}
|
|
|
|
// Display Program Options
|
|
void MainWindow::on_actionOptions_triggered()
|
|
{
|
|
if (m_options)
|
|
m_options->activateWindow();
|
|
else
|
|
m_options = new OptionsDialog(this);
|
|
}
|
|
|
|
void MainWindow::on_actionTopToolBar_triggered()
|
|
{
|
|
const bool isVisible = static_cast<QAction*>(sender())->isChecked();
|
|
m_ui->toolBar->setVisible(isVisible);
|
|
Preferences::instance()->setToolbarDisplayed(isVisible);
|
|
}
|
|
|
|
void MainWindow::on_actionShowStatusbar_triggered()
|
|
{
|
|
const bool isVisible = static_cast<QAction*>(sender())->isChecked();
|
|
Preferences::instance()->setStatusbarDisplayed(isVisible);
|
|
showStatusBar(isVisible);
|
|
}
|
|
|
|
void MainWindow::on_actionSpeedInTitleBar_triggered()
|
|
{
|
|
m_displaySpeedInTitle = static_cast<QAction * >(sender())->isChecked();
|
|
Preferences::instance()->showSpeedInTitleBar(m_displaySpeedInTitle);
|
|
if (m_displaySpeedInTitle)
|
|
updateGUI();
|
|
else
|
|
setWindowTitle("qBittorrent " QBT_VERSION);
|
|
}
|
|
|
|
void MainWindow::on_actionRSSReader_triggered()
|
|
{
|
|
Preferences::instance()->setRSSWidgetVisible(m_ui->actionRSSReader->isChecked());
|
|
displayRSSTab(m_ui->actionRSSReader->isChecked());
|
|
}
|
|
|
|
void MainWindow::on_actionSearchWidget_triggered()
|
|
{
|
|
if (!m_hasPython && m_ui->actionSearchWidget->isChecked()) {
|
|
int majorVersion = Utils::ForeignApps::pythonInfo().version.majorNumber();
|
|
|
|
// Check if python is already in PATH
|
|
if (majorVersion > 0) {
|
|
// Prevent translators from messing with PATH
|
|
Logger::instance()->addMessage(tr("Python found in %1: %2", "Python found in PATH: /usr/local/bin:/usr/bin:/etc/bin")
|
|
.arg("PATH", qgetenv("PATH").constData()), Log::INFO);
|
|
}
|
|
#ifdef Q_OS_WIN
|
|
else if (addPythonPathToEnv()) {
|
|
majorVersion = Utils::ForeignApps::pythonInfo().version.majorNumber();
|
|
}
|
|
#endif
|
|
else {
|
|
QMessageBox::information(this, tr("Undetermined Python version"), tr("Couldn't determine your Python version. Search engine disabled."));
|
|
m_ui->actionSearchWidget->setChecked(false);
|
|
Preferences::instance()->setSearchEnabled(false);
|
|
return;
|
|
}
|
|
|
|
bool res = false;
|
|
|
|
if ((majorVersion == 2) || (majorVersion == 3)) {
|
|
// Check Python minimum requirement: 2.7.9 / 3.3.0
|
|
using Version = Utils::ForeignApps::PythonInfo::Version;
|
|
const Version pyVersion = Utils::ForeignApps::pythonInfo().version;
|
|
|
|
if (((majorVersion == 2) && (pyVersion < Version {2, 7, 9}))
|
|
|| ((majorVersion == 3) && (pyVersion < Version {3, 3, 0}))) {
|
|
QMessageBox::information(this, tr("Old Python Interpreter"), tr("Your Python version (%1) is outdated. Please upgrade to latest version for search engines to work.\nMinimum requirement: 2.7.9 / 3.3.0.").arg(pyVersion));
|
|
m_ui->actionSearchWidget->setChecked(false);
|
|
Preferences::instance()->setSearchEnabled(false);
|
|
return;
|
|
}
|
|
|
|
res = true;
|
|
}
|
|
|
|
if (res) {
|
|
m_hasPython = true;
|
|
}
|
|
#ifdef Q_OS_WIN
|
|
else if (QMessageBox::question(this, tr("Missing Python Interpreter"),
|
|
tr("Python is required to use the search engine but it does not seem to be installed.\nDo you want to install it now?"),
|
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) {
|
|
// Download and Install Python
|
|
installPython();
|
|
m_ui->actionSearchWidget->setChecked(false);
|
|
Preferences::instance()->setSearchEnabled(false);
|
|
return;
|
|
}
|
|
#endif
|
|
else {
|
|
#ifndef Q_OS_WIN
|
|
QMessageBox::information(this, tr("Missing Python Interpreter"), tr("Python is required to use the search engine but it does not seem to be installed."));
|
|
#endif
|
|
m_ui->actionSearchWidget->setChecked(false);
|
|
Preferences::instance()->setSearchEnabled(false);
|
|
return;
|
|
}
|
|
}
|
|
displaySearchTab(m_ui->actionSearchWidget->isChecked());
|
|
}
|
|
|
|
/*****************************************************
|
|
* *
|
|
* HTTP Downloader *
|
|
* *
|
|
*****************************************************/
|
|
|
|
// Display an input dialog to prompt user for
|
|
// an url
|
|
void MainWindow::on_actionDownloadFromURL_triggered()
|
|
{
|
|
if (!m_downloadFromURLDialog) {
|
|
m_downloadFromURLDialog = new DownloadFromURLDialog(this);
|
|
connect(m_downloadFromURLDialog.data(), &DownloadFromURLDialog::urlsReadyToBeDownloaded, this, &MainWindow::downloadFromURLList);
|
|
}
|
|
}
|
|
|
|
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
|
|
|
void MainWindow::handleUpdateCheckFinished(bool updateAvailable, QString newVersion, bool invokedByUser)
|
|
{
|
|
QMessageBox::StandardButton answer = QMessageBox::Yes;
|
|
if (updateAvailable) {
|
|
answer = QMessageBox::question(this, tr("qBittorrent Update Available")
|
|
, tr("A new version is available.") + "<br/>"
|
|
+ tr("Do you want to download %1?").arg(newVersion) + "<br/><br/>"
|
|
+ QString("<a href=\"https://www.qbittorrent.org/news.php\">%1</a>").arg(tr("Open changelog..."))
|
|
, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
|
if (answer == QMessageBox::Yes) {
|
|
// The user want to update, let's download the update
|
|
ProgramUpdater *updater = dynamic_cast<ProgramUpdater * >(sender());
|
|
updater->updateProgram();
|
|
}
|
|
}
|
|
else if (invokedByUser) {
|
|
QMessageBox::information(this, tr("Already Using the Latest qBittorrent Version"),
|
|
tr("No updates available.\nYou are already using the latest version."));
|
|
}
|
|
sender()->deleteLater();
|
|
m_ui->actionCheckForUpdates->setEnabled(true);
|
|
m_ui->actionCheckForUpdates->setText(tr("&Check for Updates"));
|
|
m_ui->actionCheckForUpdates->setToolTip(tr("Check for program updates"));
|
|
// Don't bother the user again in this session if he chose to ignore the update
|
|
if (Preferences::instance()->isUpdateCheckEnabled() && (answer == QMessageBox::Yes))
|
|
m_programUpdateTimer->start();
|
|
}
|
|
#endif
|
|
|
|
void MainWindow::toggleAlternativeSpeeds()
|
|
{
|
|
BitTorrent::Session *const session = BitTorrent::Session::instance();
|
|
session->setAltGlobalSpeedLimitEnabled(!session->isAltGlobalSpeedLimitEnabled());
|
|
}
|
|
|
|
void MainWindow::on_actionDonateMoney_triggered()
|
|
{
|
|
QDesktopServices::openUrl(QUrl("https://www.qbittorrent.org/donate"));
|
|
}
|
|
|
|
void MainWindow::showConnectionSettings()
|
|
{
|
|
on_actionOptions_triggered();
|
|
m_options->showConnectionTab();
|
|
}
|
|
|
|
void MainWindow::minimizeWindow()
|
|
{
|
|
setWindowState(windowState() | Qt::WindowMinimized);
|
|
}
|
|
|
|
void MainWindow::on_actionExecutionLogs_triggered(bool checked)
|
|
{
|
|
if (checked) {
|
|
Q_ASSERT(!m_executionLog);
|
|
m_executionLog = new ExecutionLogWidget(m_tabs, static_cast<Log::MsgType>(executionLogMsgTypes()));
|
|
#ifdef Q_OS_MAC
|
|
m_tabs->addTab(m_executionLog, tr("Execution Log"));
|
|
#else
|
|
const int indexTab = m_tabs->addTab(m_executionLog, tr("Execution Log"));
|
|
m_tabs->setTabIcon(indexTab, GuiIconProvider::instance()->getIcon("view-calendar-journal"));
|
|
#endif
|
|
}
|
|
else if (m_executionLog) {
|
|
delete m_executionLog;
|
|
}
|
|
|
|
m_ui->actionNormalMessages->setEnabled(checked);
|
|
m_ui->actionInformationMessages->setEnabled(checked);
|
|
m_ui->actionWarningMessages->setEnabled(checked);
|
|
m_ui->actionCriticalMessages->setEnabled(checked);
|
|
setExecutionLogEnabled(checked);
|
|
}
|
|
|
|
void MainWindow::on_actionNormalMessages_triggered(bool checked)
|
|
{
|
|
if (!m_executionLog)
|
|
return;
|
|
|
|
Log::MsgTypes flags(executionLogMsgTypes());
|
|
checked ? (flags |= Log::NORMAL) : (flags &= ~Log::NORMAL);
|
|
setExecutionLogMsgTypes(flags);
|
|
}
|
|
|
|
void MainWindow::on_actionInformationMessages_triggered(bool checked)
|
|
{
|
|
if (!m_executionLog)
|
|
return;
|
|
|
|
Log::MsgTypes flags(executionLogMsgTypes());
|
|
checked ? (flags |= Log::INFO) : (flags &= ~Log::INFO);
|
|
setExecutionLogMsgTypes(flags);
|
|
}
|
|
|
|
void MainWindow::on_actionWarningMessages_triggered(bool checked)
|
|
{
|
|
if (!m_executionLog)
|
|
return;
|
|
|
|
Log::MsgTypes flags(executionLogMsgTypes());
|
|
checked ? (flags |= Log::WARNING) : (flags &= ~Log::WARNING);
|
|
setExecutionLogMsgTypes(flags);
|
|
}
|
|
|
|
void MainWindow::on_actionCriticalMessages_triggered(bool checked)
|
|
{
|
|
if (!m_executionLog)
|
|
return;
|
|
|
|
Log::MsgTypes flags(executionLogMsgTypes());
|
|
checked ? (flags |= Log::CRITICAL) : (flags &= ~Log::CRITICAL);
|
|
setExecutionLogMsgTypes(flags);
|
|
}
|
|
|
|
void MainWindow::on_actionAutoExit_toggled(bool enabled)
|
|
{
|
|
qDebug() << Q_FUNC_INFO << enabled;
|
|
Preferences::instance()->setShutdownqBTWhenDownloadsComplete(enabled);
|
|
}
|
|
|
|
void MainWindow::on_actionAutoSuspend_toggled(bool enabled)
|
|
{
|
|
qDebug() << Q_FUNC_INFO << enabled;
|
|
Preferences::instance()->setSuspendWhenDownloadsComplete(enabled);
|
|
}
|
|
|
|
void MainWindow::on_actionAutoHibernate_toggled(bool enabled)
|
|
{
|
|
qDebug() << Q_FUNC_INFO << enabled;
|
|
Preferences::instance()->setHibernateWhenDownloadsComplete(enabled);
|
|
}
|
|
|
|
void MainWindow::on_actionAutoShutdown_toggled(bool enabled)
|
|
{
|
|
qDebug() << Q_FUNC_INFO << enabled;
|
|
Preferences::instance()->setShutdownWhenDownloadsComplete(enabled);
|
|
}
|
|
|
|
void MainWindow::updatePowerManagementState()
|
|
{
|
|
const bool inhibitSuspend = (Preferences::instance()->preventFromSuspendWhenDownloading() && BitTorrent::Session::instance()->hasUnfinishedTorrents())
|
|
|| (Preferences::instance()->preventFromSuspendWhenSeeding() && BitTorrent::Session::instance()->hasRunningSeed());
|
|
m_pwr->setActivityState(inhibitSuspend);
|
|
}
|
|
|
|
#ifndef Q_OS_MAC
|
|
QIcon MainWindow::getSystrayIcon() const
|
|
{
|
|
const TrayIcon::Style style = Preferences::instance()->trayIconStyle();
|
|
// on Linux we use theme icons, and icons from resources everywhere else
|
|
#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC))
|
|
switch (style) {
|
|
case TrayIcon::NORMAL:
|
|
return QIcon::fromTheme(QLatin1String("qbittorrent-tray"));
|
|
case TrayIcon::MONO_DARK:
|
|
return QIcon::fromTheme(QLatin1String("qbittorrent-tray-dark"));
|
|
case TrayIcon::MONO_LIGHT:
|
|
return QIcon::fromTheme(QLatin1String("qbittorrent-tray-light"));
|
|
default:
|
|
break;
|
|
}
|
|
#else
|
|
switch (style) {
|
|
case TrayIcon::NORMAL:
|
|
return QIcon(QLatin1String(":/icons/skin/qbittorrent-tray.svg"));
|
|
case TrayIcon::MONO_DARK:
|
|
return QIcon(QLatin1String(":/icons/skin/qbittorrent-tray-dark.svg"));
|
|
case TrayIcon::MONO_LIGHT:
|
|
return QIcon(QLatin1String(":/icons/skin/qbittorrent-tray-light.svg"));
|
|
default:
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
// As a failsafe in case the enum is invalid
|
|
return QIcon(QLatin1String(":/icons/skin/qbittorrent-tray.svg"));
|
|
}
|
|
#endif // Q_OS_MAC
|
|
|
|
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
|
void MainWindow::checkProgramUpdate()
|
|
{
|
|
m_programUpdateTimer->stop(); // If the user had clicked the menu item
|
|
m_ui->actionCheckForUpdates->setEnabled(false);
|
|
m_ui->actionCheckForUpdates->setText(tr("Checking for Updates..."));
|
|
m_ui->actionCheckForUpdates->setToolTip(tr("Already checking for program updates in the background"));
|
|
bool invokedByUser = m_ui->actionCheckForUpdates == qobject_cast<QAction * >(sender());
|
|
ProgramUpdater *updater = new ProgramUpdater(this, invokedByUser);
|
|
connect(updater, &ProgramUpdater::updateCheckFinished, this, &MainWindow::handleUpdateCheckFinished);
|
|
updater->checkForUpdates();
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef Q_OS_WIN
|
|
bool MainWindow::addPythonPathToEnv()
|
|
{
|
|
if (m_hasPython) return true;
|
|
|
|
QString pythonPath = Preferences::getPythonPath();
|
|
if (!pythonPath.isEmpty()) {
|
|
Logger::instance()->addMessage(tr("Python found in '%1'").arg(Utils::Fs::toNativePath(pythonPath)), Log::INFO);
|
|
// Add it to PATH envvar
|
|
QString pathEnvar = QString::fromLocal8Bit(qgetenv("PATH").constData());
|
|
if (pathEnvar.isNull())
|
|
pathEnvar = "";
|
|
pathEnvar = pythonPath + ';' + pathEnvar;
|
|
qDebug("New PATH envvar is: %s", qUtf8Printable(pathEnvar));
|
|
qputenv("PATH", Utils::Fs::toNativePath(pathEnvar).toLocal8Bit());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MainWindow::installPython()
|
|
{
|
|
setCursor(QCursor(Qt::WaitCursor));
|
|
// Download python
|
|
const QString installerURL = ((QSysInfo::windowsVersion() >= QSysInfo::WV_VISTA)
|
|
? "https://www.python.org/ftp/python/3.5.2/python-3.5.2.exe"
|
|
: "https://www.python.org/ftp/python/3.4.4/python-3.4.4.msi");
|
|
Net::DownloadHandler *handler = Net::DownloadManager::instance()->download(
|
|
Net::DownloadRequest(installerURL).saveToFile(true));
|
|
|
|
using Func = void (Net::DownloadHandler::*)(const QString &, const QString &);
|
|
connect(handler, static_cast<Func>(&Net::DownloadHandler::downloadFinished), this, &MainWindow::pythonDownloadSuccess);
|
|
connect(handler, static_cast<Func>(&Net::DownloadHandler::downloadFailed), this, &MainWindow::pythonDownloadFailure);
|
|
}
|
|
|
|
void MainWindow::pythonDownloadSuccess(const QString &url, const QString &filePath)
|
|
{
|
|
Q_UNUSED(url)
|
|
setCursor(QCursor(Qt::ArrowCursor));
|
|
QProcess installer;
|
|
qDebug("Launching Python installer in passive mode...");
|
|
|
|
if (QSysInfo::windowsVersion() >= QSysInfo::WV_VISTA) {
|
|
QFile::rename(filePath, filePath + ".exe");
|
|
installer.start('"' + Utils::Fs::toNativePath(filePath) + ".exe\" /passive");
|
|
}
|
|
else {
|
|
QFile::rename(filePath, filePath + ".msi");
|
|
installer.start(Utils::Misc::windowsSystemPath() + "\\msiexec.exe /passive /i \"" + Utils::Fs::toNativePath(filePath) + ".msi\"");
|
|
}
|
|
|
|
// Wait for setup to complete
|
|
installer.waitForFinished(10 * 60 * 1000);
|
|
|
|
qDebug("Installer stdout: %s", installer.readAllStandardOutput().data());
|
|
qDebug("Installer stderr: %s", installer.readAllStandardError().data());
|
|
qDebug("Setup should be complete!");
|
|
// Delete temp file
|
|
if (QSysInfo::windowsVersion() >= QSysInfo::WV_VISTA)
|
|
Utils::Fs::forceRemove(filePath + ".exe");
|
|
else
|
|
Utils::Fs::forceRemove(filePath + ".msi");
|
|
// Reload search engine
|
|
m_hasPython = addPythonPathToEnv();
|
|
if (m_hasPython) {
|
|
// Make it print the version to Log
|
|
Utils::ForeignApps::pythonInfo();
|
|
m_ui->actionSearchWidget->setChecked(true);
|
|
displaySearchTab(true);
|
|
}
|
|
}
|
|
|
|
void MainWindow::pythonDownloadFailure(const QString &url, const QString &error)
|
|
{
|
|
Q_UNUSED(url)
|
|
setCursor(QCursor(Qt::ArrowCursor));
|
|
QMessageBox::warning(this, tr("Download error"), tr("Python setup could not be downloaded, reason: %1.\nPlease install it manually.").arg(error));
|
|
}
|
|
|
|
#endif // Q_OS_WIN
|