/* * Bittorrent Client using Qt4 and libtorrent. * Copyright (C) 2006 Christophe Dumez * * 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. * * Contact : chris@qbittorrent.org */ #include #if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) && defined(QT_DBUS_LIB) #include #include "notifications.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "mainwindow.h" #include "transferlistwidget.h" #include "base/utils/misc.h" #include "torrentcreatordlg.h" #include "downloadfromurldlg.h" #include "addnewtorrentdialog.h" #include "search/searchwidget.h" #include "rss_imp.h" #include "base/bittorrent/session.h" #include "base/bittorrent/sessionstatus.h" #include "base/bittorrent/torrenthandle.h" #include "about_imp.h" #include "trackerlogin.h" #include "options_imp.h" #include "speedlimitdlg.h" #include "base/preferences.h" #include "trackerlist.h" #include "peerlistwidget.h" #include "transferlistfilterswidget.h" #include "propertieswidget.h" #include "statusbar.h" #include "hidabletabwidget.h" #include "torrentimportdlg.h" #include "torrentmodel.h" #include "executionlog.h" #include "guiiconprovider.h" #include "base/logger.h" #include "autoexpandabledialog.h" #ifdef Q_OS_MAC void qt_mac_set_dock_menu(QMenu *menu); #endif #include "lineedit.h" #include "application.h" #if defined(Q_OS_WIN) || defined(Q_OS_MAC) #include "programupdater.h" #endif #include "powermanagement.h" #ifdef Q_OS_WIN #include "base/net/downloadmanager.h" #include "base/net/downloadhandler.h" #endif #define TIME_TRAY_BALLOON 5000 #define PREVENT_SUSPEND_INTERVAL 60000 /***************************************************** * * * GUI * * * *****************************************************/ // Constructor MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , m_posInitialized(false) , force_exit(false) , unlockDlgShowing(false) , has_python(false) { setupUi(this); Preferences* const pref = Preferences::instance(); ui_locked = pref->isUILocked(); setWindowTitle(QString("qBittorrent %1").arg(QString::fromUtf8(VERSION))); displaySpeedInTitle = pref->speedInTitleBar(); // Setting icons #if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) if (Preferences::instance()->useSystemIconTheme()) setWindowIcon(QIcon::fromTheme("qbittorrent", QIcon(QString::fromUtf8(":/icons/skin/qbittorrent32.png")))); else #endif setWindowIcon(QIcon(QString::fromUtf8(":/icons/skin/qbittorrent32.png"))); addToolbarContextMenu(); actionOpen->setIcon(GuiIconProvider::instance()->getIcon("list-add")); actionDownload_from_URL->setIcon(GuiIconProvider::instance()->getIcon("insert-link")); actionSet_upload_limit->setIcon(QIcon(QString::fromUtf8(":/icons/skin/seeding.png"))); actionSet_download_limit->setIcon(QIcon(QString::fromUtf8(":/icons/skin/download.png"))); actionSet_global_upload_limit->setIcon(QIcon(QString::fromUtf8(":/icons/skin/seeding.png"))); actionSet_global_download_limit->setIcon(QIcon(QString::fromUtf8(":/icons/skin/download.png"))); actionCreate_torrent->setIcon(GuiIconProvider::instance()->getIcon("document-edit")); actionAbout->setIcon(GuiIconProvider::instance()->getIcon("help-about")); actionStatistics->setIcon(GuiIconProvider::instance()->getIcon("view-statistics")); actionDecreasePriority->setIcon(GuiIconProvider::instance()->getIcon("go-down")); actionBottomPriority->setIcon(GuiIconProvider::instance()->getIcon("go-bottom")); actionDelete->setIcon(GuiIconProvider::instance()->getIcon("list-remove")); actionDocumentation->setIcon(GuiIconProvider::instance()->getIcon("help-contents")); actionDonate_money->setIcon(GuiIconProvider::instance()->getIcon("wallet-open")); actionExit->setIcon(GuiIconProvider::instance()->getIcon("application-exit")); actionIncreasePriority->setIcon(GuiIconProvider::instance()->getIcon("go-up")); actionTopPriority->setIcon(GuiIconProvider::instance()->getIcon("go-top")); actionLock_qBittorrent->setIcon(GuiIconProvider::instance()->getIcon("object-locked")); actionOptions->setIcon(GuiIconProvider::instance()->getIcon("preferences-system")); actionPause->setIcon(GuiIconProvider::instance()->getIcon("media-playback-pause")); actionPause_All->setIcon(GuiIconProvider::instance()->getIcon("media-playback-pause")); actionStart->setIcon(GuiIconProvider::instance()->getIcon("media-playback-start")); actionStart_All->setIcon(GuiIconProvider::instance()->getIcon("media-playback-start")); action_Import_Torrent->setIcon(GuiIconProvider::instance()->getIcon("document-import")); menuAuto_Shutdown_on_downloads_completion->setIcon(GuiIconProvider::instance()->getIcon("application-exit")); QMenu *startAllMenu = new QMenu(this); startAllMenu->addAction(actionStart_All); actionStart->setMenu(startAllMenu); QMenu *pauseAllMenu = new QMenu(this); pauseAllMenu->addAction(actionPause_All); actionPause->setMenu(pauseAllMenu); QMenu *lockMenu = new QMenu(this); QAction *defineUiLockPasswdAct = lockMenu->addAction(tr("&Set Password")); connect(defineUiLockPasswdAct, SIGNAL(triggered()), this, SLOT(defineUILockPassword())); QAction *clearUiLockPasswdAct = lockMenu->addAction(tr("&Clear Password")); connect(clearUiLockPasswdAct, SIGNAL(triggered()), this, SLOT(clearUILockPassword())); actionLock_qBittorrent->setMenu(lockMenu); // Creating Bittorrent session connect(BitTorrent::Session::instance(), SIGNAL(fullDiskError(BitTorrent::TorrentHandle *const, QString)), this, SLOT(fullDiskError(BitTorrent::TorrentHandle *const, QString))); connect(BitTorrent::Session::instance(), SIGNAL(addTorrentFailed(const QString &)), this, SLOT(addTorrentFailed(const QString &))); connect(BitTorrent::Session::instance(), SIGNAL(torrentFinished(BitTorrent::TorrentHandle *const)), this, SLOT(finishedTorrent(BitTorrent::TorrentHandle *const))); connect(BitTorrent::Session::instance(), SIGNAL(trackerAuthenticationRequired(BitTorrent::TorrentHandle *const)), this, SLOT(trackerAuthenticationRequired(BitTorrent::TorrentHandle *const))); connect(BitTorrent::Session::instance(), SIGNAL(downloadFromUrlFailed(QString, QString)), this, SLOT(handleDownloadFromUrlFailure(QString, QString))); connect(BitTorrent::Session::instance(), SIGNAL(speedLimitModeChanged(bool)), this, SLOT(updateAltSpeedsBtn(bool))); connect(BitTorrent::Session::instance(), SIGNAL(recursiveTorrentDownloadPossible(BitTorrent::TorrentHandle *const)), this, SLOT(askRecursiveTorrentDownloadConfirmation(BitTorrent::TorrentHandle *const))); qDebug("create tabWidget"); tabs = new HidableTabWidget(this); connect(tabs, SIGNAL(currentChanged(int)), this, SLOT(tab_changed(int))); vSplitter = new QSplitter(Qt::Horizontal, this); //vSplitter->setChildrenCollapsible(false); hSplitter = new QSplitter(Qt::Vertical, this); hSplitter->setChildrenCollapsible(false); hSplitter->setFrameShape(QFrame::NoFrame); // Name filter search_filter = new LineEdit(this); searchFilterAct = toolBar->insertWidget(actionLock_qBittorrent, search_filter); search_filter->setPlaceholderText(tr("Filter torrent list...")); search_filter->setFixedWidth(200); QWidget *spacer = new QWidget(this); spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); toolBar->insertWidget(searchFilterAct, spacer); // Transfer List tab transferList = new TransferListWidget(hSplitter, this); //transferList->setStyleSheet("QTreeView {border: none;}"); // borderless properties = new PropertiesWidget(hSplitter, this, transferList); transferListFilters = new TransferListFiltersWidget(vSplitter, transferList); hSplitter->addWidget(transferList); hSplitter->addWidget(properties); vSplitter->addWidget(transferListFilters); vSplitter->addWidget(hSplitter); vSplitter->setCollapsible(0, true); vSplitter->setCollapsible(1, false); tabs->addTab(vSplitter, GuiIconProvider::instance()->getIcon("folder-remote"), tr("Transfers")); connect(search_filter, SIGNAL(textChanged(QString)), transferList, SLOT(applyNameFilter(QString))); connect(hSplitter, SIGNAL(splitterMoved(int, int)), this, SLOT(writeSettings())); connect(vSplitter, SIGNAL(splitterMoved(int, int)), this, SLOT(writeSettings())); connect(BitTorrent::Session::instance(), SIGNAL(trackersChanged(BitTorrent::TorrentHandle *const)), properties, SLOT(loadTrackers(BitTorrent::TorrentHandle *const))); connect(BitTorrent::Session::instance(), SIGNAL(trackersAdded(BitTorrent::TorrentHandle *const, const QList &)), transferListFilters, SLOT(addTrackers(BitTorrent::TorrentHandle *const, const QList &))); connect(BitTorrent::Session::instance(), SIGNAL(trackersRemoved(BitTorrent::TorrentHandle *const, const QList &)), transferListFilters, SLOT(removeTrackers(BitTorrent::TorrentHandle *const, const QList &))); connect(BitTorrent::Session::instance(), SIGNAL(trackerlessStateChanged(BitTorrent::TorrentHandle *const, bool)), transferListFilters, SLOT(changeTrackerless(BitTorrent::TorrentHandle *const, bool))); connect(BitTorrent::Session::instance(), SIGNAL(trackerSuccess(BitTorrent::TorrentHandle *const, const QString &)), transferListFilters, SLOT(trackerSuccess(BitTorrent::TorrentHandle *const, const QString &))); connect(BitTorrent::Session::instance(), SIGNAL(trackerError(BitTorrent::TorrentHandle *const, const QString &)), transferListFilters, SLOT(trackerError(BitTorrent::TorrentHandle *const, const QString &))); connect(BitTorrent::Session::instance(), SIGNAL(trackerWarning(BitTorrent::TorrentHandle *const, const QString &)), transferListFilters, SLOT(trackerWarning(BitTorrent::TorrentHandle *const, const QString &))); centralWidgetLayout->addWidget(tabs); prioSeparator = toolBar->insertSeparator(actionTopPriority); prioSeparatorMenu = menu_Edit->insertSeparator(actionTopPriority); // Transfer list slots connect(actionStart, SIGNAL(triggered()), transferList, SLOT(startSelectedTorrents())); connect(actionStart_All, SIGNAL(triggered()), transferList, SLOT(resumeAllTorrents())); connect(actionPause, SIGNAL(triggered()), transferList, SLOT(pauseSelectedTorrents())); connect(actionPause_All, SIGNAL(triggered()), transferList, SLOT(pauseAllTorrents())); connect(actionDelete, SIGNAL(triggered()), transferList, SLOT(deleteSelectedTorrents())); connect(actionTopPriority, SIGNAL(triggered()), transferList, SLOT(topPrioSelectedTorrents())); connect(actionIncreasePriority, SIGNAL(triggered()), transferList, SLOT(increasePrioSelectedTorrents())); connect(actionDecreasePriority, SIGNAL(triggered()), transferList, SLOT(decreasePrioSelectedTorrents())); connect(actionBottomPriority, SIGNAL(triggered()), transferList, SLOT(bottomPrioSelectedTorrents())); connect(actionToggleVisibility, SIGNAL(triggered()), this, SLOT(toggleVisibility())); connect(actionMinimize, SIGNAL(triggered()), SLOT(minimizeWindow())); #if defined(Q_OS_WIN) || defined(Q_OS_MAC) programUpdateTimer.setInterval(60 * 60 * 1000); programUpdateTimer.setSingleShot(true); connect(&programUpdateTimer, SIGNAL(timeout()), SLOT(checkProgramUpdate())); connect(actionCheck_for_updates, SIGNAL(triggered()), SLOT(checkProgramUpdate())); #else actionCheck_for_updates->setVisible(false); #endif m_pwr = new PowerManagement(this); preventTimer = new QTimer(this); connect(preventTimer, SIGNAL(timeout()), SLOT(checkForActiveTorrents())); // Configure BT session according to options loadPreferences(false); connect(BitTorrent::Session::instance(), SIGNAL(torrentsUpdated()), this, SLOT(updateGUI())); // Accept drag 'n drops setAcceptDrops(true); createKeyboardShortcuts(); // Create status bar status_bar = new StatusBar(QMainWindow::statusBar()); connect(status_bar->connectionStatusButton(), SIGNAL(clicked()), SLOT(showConnectionSettings())); connect(actionUse_alternative_speed_limits, SIGNAL(triggered()), status_bar, SLOT(toggleAlternativeSpeeds())); #ifdef Q_OS_MAC setUnifiedTitleAndToolBarOnMac(true); #endif // View settings actionTop_tool_bar->setChecked(pref->isToolbarDisplayed()); actionSpeed_in_title_bar->setChecked(pref->speedInTitleBar()); actionRSS_Reader->setChecked(pref->isRSSEnabled()); actionSearch_engine->setChecked(pref->isSearchEnabled()); actionExecution_Logs->setChecked(pref->isExecutionLogEnabled()); displayRSSTab(actionRSS_Reader->isChecked()); on_actionExecution_Logs_triggered(actionExecution_Logs->isChecked()); if (actionSearch_engine->isChecked()) QTimer::singleShot(0, this, SLOT(on_actionSearch_engine_triggered())); // Auto shutdown actions QActionGroup *autoShutdownGroup = new QActionGroup(this); autoShutdownGroup->setExclusive(true); autoShutdownGroup->addAction(actionAutoShutdown_Disabled); autoShutdownGroup->addAction(actionAutoExit_qBittorrent); autoShutdownGroup->addAction(actionAutoShutdown_system); autoShutdownGroup->addAction(actionAutoSuspend_system); autoShutdownGroup->addAction(actionAutoHibernate_system); #if (!defined(Q_OS_UNIX) || defined(Q_OS_MAC)) || defined(QT_DBUS_LIB) actionAutoShutdown_system->setChecked(pref->shutdownWhenDownloadsComplete()); actionAutoSuspend_system->setChecked(pref->suspendWhenDownloadsComplete()); actionAutoHibernate_system->setChecked(pref->hibernateWhenDownloadsComplete()); #else actionAutoShutdown_system->setDisabled(true); actionAutoSuspend_system->setDisabled(true); actionAutoHibernate_system->setDisabled(true); #endif actionAutoExit_qBittorrent->setChecked(pref->shutdownqBTWhenDownloadsComplete()); if (!autoShutdownGroup->checkedAction()) actionAutoShutdown_Disabled->setChecked(true); // Load Window state and sizes readSettings(); if (systrayIcon) { if (!(pref->startMinimized() || ui_locked)) { show(); activateWindow(); raise(); } else if (pref->startMinimized()) { showMinimized(); if (pref->minimizeToTray()) hide(); } } else { // Make sure the Window is visible if we don't have a tray icon if (pref->startMinimized()) { showMinimized(); } else { show(); activateWindow(); raise(); } } properties->readSettings(); // Start watching the executable for updates executable_watcher = new QFileSystemWatcher(this); connect(executable_watcher, SIGNAL(fileChanged(QString)), this, SLOT(notifyOfUpdate(QString))); executable_watcher->addPath(qApp->applicationFilePath()); transferList->setFocus(); // Update the number of torrents (tab) updateNbTorrents(); connect(transferList->getSourceModel(), SIGNAL(rowsInserted(QModelIndex, int, int)), this, SLOT(updateNbTorrents())); connect(transferList->getSourceModel(), SIGNAL(rowsRemoved(QModelIndex, int, int)), this, SLOT(updateNbTorrents())); qDebug("GUI Built"); #ifdef Q_OS_WIN if (!pref->neverCheckFileAssoc() && (!Preferences::isTorrentFileAssocSet() || !Preferences::isMagnetLinkAssocSet())) { if (QMessageBox::question(0, 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 qt_mac_set_dock_menu(getTrayIconMenu()); #endif } MainWindow::~MainWindow() { #ifdef Q_OS_MAC // Workaround to avoid bug http://bugreports.qt.nokia.com/browse/QTBUG-7305 setUnifiedTitleAndToolBarOnMac(false); #endif } void MainWindow::addToolbarContextMenu() { const Preferences* const pref = Preferences::instance(); toolbarMenu = new QMenu(this); toolBar->setContextMenuPolicy(Qt::CustomContextMenu); connect(toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolbarMenuRequested(QPoint))); QAction *iconsOnly = new QAction(tr("Icons Only"), toolbarMenu); connect(iconsOnly, SIGNAL(triggered()), this, SLOT(toolbarIconsOnly())); QAction *textOnly = new QAction(tr("Text Only"), toolbarMenu); connect(textOnly, SIGNAL(triggered()), this, SLOT(toolbarTextOnly())); QAction *textBesideIcons = new QAction(tr("Text Alongside Icons"), toolbarMenu); connect(textBesideIcons, SIGNAL(triggered()), this, SLOT(toolbarTextBeside())); QAction *textUnderIcons = new QAction(tr("Text Under Icons"), toolbarMenu); connect(textUnderIcons, SIGNAL(triggered()), this, SLOT(toolbarTextUnder())); QAction *followSystemStyle = new QAction(tr("Follow System Style"), toolbarMenu); connect(followSystemStyle, SIGNAL(triggered()), this, SLOT(toolbarFollowSystem())); toolbarMenu->addAction(iconsOnly); toolbarMenu->addAction(textOnly); toolbarMenu->addAction(textBesideIcons); toolbarMenu->addAction(textUnderIcons); toolbarMenu->addAction(followSystemStyle); QActionGroup *textPositionGroup = new QActionGroup(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(pref->getToolbarTextPosition()); if (buttonStyle >= Qt::ToolButtonIconOnly && buttonStyle <= Qt::ToolButtonFollowStyle) 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::toolbarMenuRequested(QPoint point) { toolbarMenu->exec(toolBar->mapToGlobal(point)); } void MainWindow::toolbarIconsOnly() { toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonIconOnly); } void MainWindow::toolbarTextOnly() { toolBar->setToolButtonStyle(Qt::ToolButtonTextOnly); Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextOnly); } void MainWindow::toolbarTextBeside() { toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextBesideIcon); } void MainWindow::toolbarTextUnder() { toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonTextUnderIcon); } void MainWindow::toolbarFollowSystem() { toolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle); Preferences::instance()->setToolbarTextPosition(Qt::ToolButtonFollowStyle); } void MainWindow::defineUILockPassword() { QString old_pass_md5 = Preferences::instance()->getUILockPasswordMD5(); if (old_pass_md5.isNull()) old_pass_md5 = ""; bool ok = false; QString new_clear_password = AutoExpandableDialog::getText(this, tr("UI lock password"), tr("Please type the UI lock password:"), QLineEdit::Password, old_pass_md5, &ok); if (ok) { new_clear_password = new_clear_password.trimmed(); if (new_clear_password.size() < 3) { QMessageBox::warning(this, tr("Invalid password"), tr("The password should contain at least 3 characters")); return; } if (new_clear_password != old_pass_md5) Preferences::instance()->setUILockPassword(new_clear_password); 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_qBittorrent_triggered() { Preferences* const pref = Preferences::instance(); // Check if there is a password if (pref->getUILockPasswordMD5().isEmpty()) { // Ask for a password bool ok = false; QString clear_password = AutoExpandableDialog::getText(this, tr("UI lock password"), tr("Please type the UI lock password:"), QLineEdit::Password, "", &ok); if (!ok) return; pref->setUILockPassword(clear_password); } // Lock the interface ui_locked = true; pref->setUILocked(true); myTrayIconMenu->setEnabled(false); hide(); } void MainWindow::displayRSSTab(bool enable) { if (enable) { // RSS tab if (!rssWidget) { rssWidget = new RSSImp(tabs); connect(rssWidget, SIGNAL(updateRSSCount(int)), this, SLOT(updateRSSTabLabel(int))); int index_tab = tabs->addTab(rssWidget, tr("RSS (%1)").arg(0)); tabs->setTabIcon(index_tab, GuiIconProvider::instance()->getIcon("application-rss+xml")); } } else if (rssWidget) { delete rssWidget; } } void MainWindow::updateRSSTabLabel(int count) { tabs->setTabText(tabs->indexOf(rssWidget), tr("RSS (%1)").arg(count)); } void MainWindow::displaySearchTab(bool enable) { Preferences::instance()->setSearchEnabled(enable); if (enable) { // RSS tab if (!searchEngine) { searchEngine = new SearchWidget(this); tabs->insertTab(1, searchEngine, GuiIconProvider::instance()->getIcon("edit-find"), tr("Search")); } } else if (searchEngine) { delete searchEngine; } } void MainWindow::updateNbTorrents() { tabs->setTabText(0, tr("Transfers (%1)").arg(transferList->getSourceModel()->rowCount())); } void MainWindow::on_actionDocumentation_triggered() const { QDesktopServices::openUrl(QUrl(QString::fromUtf8("http://doc.qbittorrent.org"))); } void MainWindow::tab_changed(int new_tab) { Q_UNUSED(new_tab); // We cannot rely on the index new_tab // because the tab order is undetermined now if (tabs->currentWidget() == vSplitter) { qDebug("Changed tab to transfer list, refreshing the list"); properties->loadDynamicData(); searchFilterAct->setVisible(true); return; } else { searchFilterAct->setVisible(false); } if (tabs->currentWidget() == searchEngine) { qDebug("Changed tab to search engine, giving focus to search input"); searchEngine->giveFocusToSearchInput(); } } void MainWindow::writeSettings() { Preferences* const pref = Preferences::instance(); pref->setMainGeometry(saveGeometry()); // Splitter size pref->setMainVSplitterState(vSplitter->saveState()); properties->saveSettings(); } void MainWindow::cleanup() { writeSettings(); delete executable_watcher; if (systrayCreator) systrayCreator->stop(); if (preventTimer) preventTimer->stop(); #if (defined(Q_OS_WIN) || defined(Q_OS_MAC)) programUpdateTimer.stop(); #endif delete search_filter; delete searchFilterAct; delete tabs; // this seems enough to also delete all contained widgets delete status_bar; delete m_pwr; delete toolbarMenu; } void MainWindow::readSettings() { const Preferences* const pref = Preferences::instance(); const QByteArray mainGeo = pref->getMainGeometry(); if (!mainGeo.isEmpty()) if (restoreGeometry(mainGeo)) m_posInitialized = true; const QByteArray splitterState = pref->getMainVSplitterState(); if (splitterState.isEmpty()) // Default sizes vSplitter->setSizes(QList() << 120 << vSplitter->width() - 120); else vSplitter->restoreState(splitterState); } void MainWindow::balloonClicked() { if (isHidden()) { if (ui_locked) { // 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 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()).arg(msg)); } void MainWindow::createKeyboardShortcuts() { actionCreate_torrent->setShortcut(QKeySequence(QString::fromUtf8("Ctrl+N"))); actionOpen->setShortcut(QKeySequence(QString::fromUtf8("Ctrl+O"))); actionDownload_from_URL->setShortcut(QKeySequence(QString::fromUtf8("Ctrl+Shift+O"))); actionExit->setShortcut(QKeySequence(QString::fromUtf8("Ctrl+Q"))); QShortcut *switchTransferShortcut = new QShortcut(QKeySequence("Alt+1"), this); connect(switchTransferShortcut, SIGNAL(activated()), this, SLOT(displayTransferTab())); QShortcut *switchSearchShortcut = new QShortcut(QKeySequence("Alt+2"), this); connect(switchSearchShortcut, SIGNAL(activated()), this, SLOT(displaySearchTab())); QShortcut *switchSearchShortcut2 = new QShortcut(QKeySequence("Ctrl+F"), this); connect(switchSearchShortcut2, SIGNAL(activated()), this, SLOT(displaySearchTab())); QShortcut *switchRSSShortcut = new QShortcut(QKeySequence("Alt+3"), this); connect(switchRSSShortcut, SIGNAL(activated()), this, SLOT(displayRSSTab())); actionDocumentation->setShortcut(QKeySequence("F1")); actionOptions->setShortcut(QKeySequence(QString::fromUtf8("Alt+O"))); actionStart->setShortcut(QKeySequence(QString::fromUtf8("Ctrl+S"))); actionStart_All->setShortcut(QKeySequence(QString::fromUtf8("Ctrl+Shift+S"))); actionPause->setShortcut(QKeySequence(QString::fromUtf8("Ctrl+P"))); actionPause_All->setShortcut(QKeySequence(QString::fromUtf8("Ctrl+Shift+P"))); actionBottomPriority->setShortcut(QKeySequence(QString::fromUtf8("Ctrl+Shift+-"))); actionDecreasePriority->setShortcut(QKeySequence(QString::fromUtf8("Ctrl+-"))); actionIncreasePriority->setShortcut(QKeySequence(QString::fromUtf8("Ctrl++"))); actionTopPriority->setShortcut(QKeySequence(QString::fromUtf8("Ctrl+Shift++"))); #ifdef Q_OS_MAC actionMinimize->setShortcut(QKeySequence(QString::fromUtf8("Ctrl+M"))); addAction(actionMinimize); #endif } // Keyboard shortcuts slots void MainWindow::displayTransferTab() const { tabs->setCurrentWidget(transferList); } void MainWindow::displaySearchTab() const { if (searchEngine) tabs->setCurrentWidget(searchEngine); } void MainWindow::displayRSSTab() const { if (rssWidget) tabs->setCurrentWidget(rssWidget); } // End of keyboard shortcuts slots void MainWindow::askRecursiveTorrentDownloadConfirmation(BitTorrent::TorrentHandle *const torrent) { Preferences* const pref = Preferences::instance(); if (pref->recursiveDownloadDisabled()) return; // Get Torrent name QString torrent_name = torrent->name(); QMessageBox confirmBox(QMessageBox::Question, tr("Recursive download confirmation"), tr("The torrent '%1' contains torrent files, do you want to proceed with their download?").arg(torrent_name)); QPushButton *yes = confirmBox.addButton(tr("Yes"), QMessageBox::YesRole); /*QPushButton *no = */ confirmBox.addButton(tr("No"), QMessageBox::NoRole); QPushButton *never = confirmBox.addButton(tr("Never"), QMessageBox::NoRole); confirmBox.exec(); if (confirmBox.clickedButton() == yes) BitTorrent::Session::instance()->recursiveTorrentDownload(torrent->hash()); else if (confirmBox.clickedButton() == never) pref->disableRecursiveDownload(); } 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).arg(reason)); } void MainWindow::on_actionSet_global_upload_limit_triggered() { qDebug() << Q_FUNC_INFO; bool ok; int cur_limit = BitTorrent::Session::instance()->uploadRateLimit(); const long new_limit = SpeedLimitDialog::askSpeedLimit(&ok, tr("Global Upload Speed Limit"), cur_limit); if (ok) { qDebug("Setting global upload rate limit to %.1fKb/s", new_limit / 1024.); BitTorrent::Session::instance()->setUploadRateLimit(new_limit); if (new_limit <= 0) Preferences::instance()->setGlobalUploadLimit(-1); else Preferences::instance()->setGlobalUploadLimit(new_limit / 1024.); } } void MainWindow::on_actionSet_global_download_limit_triggered() { qDebug() << Q_FUNC_INFO; bool ok; int cur_limit = BitTorrent::Session::instance()->downloadRateLimit(); const long new_limit = SpeedLimitDialog::askSpeedLimit(&ok, tr("Global Download Speed Limit"), cur_limit); if (ok) { qDebug("Setting global download rate limit to %.1fKb/s", new_limit / 1024.); BitTorrent::Session::instance()->setDownloadRateLimit(new_limit); if (new_limit <= 0) Preferences::instance()->setGlobalDownloadLimit(-1); else Preferences::instance()->setGlobalDownloadLimit(new_limit / 1024.); } } // 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() && ui_locked) { // Ask for UI lock password if (!unlockUI()) return; } force_exit = true; close(); } QWidget* MainWindow::getCurrentTabWidget() const { if (isMinimized() || !isVisible()) return 0; if (tabs->currentIndex() == 0) return transferList; return tabs->currentWidget(); } void MainWindow::setTabText(int index, QString text) const { tabs->setTabText(index, text); } bool MainWindow::unlockUI() { if (unlockDlgShowing) return false; else unlockDlgShowing = true; bool ok = false; QString clear_password = AutoExpandableDialog::getText(this, tr("UI lock password"), tr("Please type the UI lock password:"), QLineEdit::Password, "", &ok); unlockDlgShowing = false; if (!ok) return false; Preferences* const pref = Preferences::instance(); QString real_pass_md5 = pref->getUILockPasswordMD5(); QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(clear_password.toLocal8Bit()); QString password_md5 = md5.result().toHex(); if (real_pass_md5 == password_md5) { ui_locked = false; pref->setUILocked(false); myTrayIconMenu->setEnabled(true); return true; } QMessageBox::warning(this, tr("Invalid password"), tr("The password is invalid")); return false; } void MainWindow::notifyOfUpdate(QString) { // Show restart message status_bar->showRestartRequired(); // Delete the executable watcher delete executable_watcher; executable_watcher = 0; } // Toggle Main window visibility void MainWindow::toggleVisibility(QSystemTrayIcon::ActivationReason e) { if (e == QSystemTrayIcon::Trigger || e == QSystemTrayIcon::DoubleClick) { if (isHidden()) { if (ui_locked) { // Ask for UI lock password if (!unlockUI()) return; } // Make sure the window is not minimized setWindowState(windowState() & (~Qt::WindowMinimized | Qt::WindowActive)); // Then show it show(); raise(); activateWindow(); } else { hide(); } } } // Display About Dialog void MainWindow::on_actionAbout_triggered() { //About dialog if (aboutDlg) aboutDlg->setFocus(); else aboutDlg = new about(this); } void MainWindow::on_actionStatistics_triggered() { if (statsDlg) statsDlg->setFocus(); else statsDlg = new StatsDialog(this); } void MainWindow::showEvent(QShowEvent *e) { qDebug("** Show Event **"); if (getCurrentTabWidget() == transferList) properties->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(); const bool goToSystrayOnExit = pref->closeToTray(); if (!force_exit && systrayIcon && goToSystrayOnExit && !this->isHidden()) { hide(); e->accept(); return; } if (pref->confirmOnExit() && BitTorrent::Session::instance()->hasActiveTorrents()) { if (e->spontaneous() || force_exit) { if (!isVisible()) show(); QMessageBox confirmBox(QMessageBox::Question, tr("Exiting qBittorrent"), tr("Some files are currently transferring.\nAre 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(); force_exit = false; return; } if (confirmBox.clickedButton() == alwaysBtn) { // Remember choice Preferences::instance()->setConfirmOnExit(false); } } } //abort search if any if (searchEngine) delete searchEngine; hide(); // Hide tray icon if (systrayIcon) systrayIcon->hide(); // Accept exit e->accept(); qApp->exit(); } // Display window to create a torrent void MainWindow::on_actionCreate_torrent_triggered() { if (createTorrentDlg) createTorrentDlg->setFocus(); else createTorrentDlg = new TorrentCreatorDlg(this); } bool MainWindow::event(QEvent * e) { switch(e->type()) { case QEvent::WindowStateChange: { qDebug("Window change event"); //Now check to see if the window is minimised if (isMinimized()) { qDebug("minimisation"); if (systrayIcon && Preferences::instance()->minimizeToTray()) { qDebug("Has active window: %d", (int)(qApp->activeWindow() != 0)); // Check if there is a modal window bool has_modal_window = false; foreach (QWidget *widget, QApplication::allWidgets()) { if (widget->isModal()) { has_modal_window = true; break; } } // Iconify if there is no modal window if (!has_modal_window) { qDebug("Minimize to Tray enabled, hiding!"); e->ignore(); QTimer::singleShot(0, this, SLOT(hide())); return true; } } } break; } #ifdef Q_OS_MAC case QEvent::ToolBarChange: { qDebug("MAC: Received a toolbar change event!"); bool ret = QMainWindow::event(e); qDebug("MAC: new toolbar visibility is %d", !actionTop_tool_bar->isChecked()); actionTop_tool_bar->toggle(); Preferences::instance()->setToolbarDisplayed(actionTop_tool_bar->isChecked()); return ret; } #endif default: break; } return QMainWindow::event(e); } // Action executed when a file is dropped void MainWindow::dropEvent(QDropEvent *event) { event->acceptProposedAction(); QStringList files; if (event->mimeData()->hasUrls()) { const QList urls = event->mimeData()->urls(); foreach (const QUrl &url, urls) { if (!url.isEmpty()) { if (url.scheme().compare("file", Qt::CaseInsensitive) == 0) files << url.toLocalFile(); else files << url.toString(); } } } else { files = event->mimeData()->text().split(QString::fromUtf8("\n")); } // Add file to download list const bool useTorrentAdditionDialog = Preferences::instance()->useAdditionDialog(); foreach (QString file, files) { qDebug("Dropped file %s on download list", qPrintable(file)); if (useTorrentAdditionDialog) AddNewTorrentDialog::show(file, this); else BitTorrent::Session::instance()->addTorrent(file); } } // 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(QString::fromUtf8("text/plain")) || event->mimeData()->hasFormat(QString::fromUtf8("text/uri-list"))) event->acceptProposedAction(); } /***************************************************** * * * 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(0, tr("Open Torrent Files"), pref->getMainLastDir(), tr("Torrent Files") + QString::fromUtf8(" (*.torrent)")); const bool useTorrentAdditionDialog = Preferences::instance()->useAdditionDialog(); if (!pathsList.isEmpty()) { foreach (QString file, pathsList) { qDebug("Dropped file %s on download list", qPrintable(file)); if (useTorrentAdditionDialog) AddNewTorrentDialog::show(file, this); else BitTorrent::Session::instance()->addTorrent(file); } // Save last dir to remember it QStringList top_dir = Utils::Fs::fromNativePath(pathsList.at(0)).split("/"); top_dir.removeLast(); pref->setMainLastDir(Utils::Fs::fromNativePath(top_dir.join("/"))); } } void MainWindow::activate() { if (!ui_locked || unlockUI()) { show(); activateWindow(); raise(); } } void MainWindow::optionsSaved() { loadPreferences(); } // Load program preferences void MainWindow::loadPreferences(bool configure_session) { Logger::instance()->addMessage(tr("Options were saved successfully.")); const Preferences* const pref = Preferences::instance(); const bool newSystrayIntegration = pref->systrayIntegration(); actionLock_qBittorrent->setVisible(newSystrayIntegration); if (newSystrayIntegration != (systrayIcon != 0)) { if (newSystrayIntegration) { // create the trayicon if (!QSystemTrayIcon::isSystemTrayAvailable()) { if (!configure_session) { // Program startup systrayCreator = new QTimer(this); connect(systrayCreator, SIGNAL(timeout()), this, SLOT(createSystrayDelayed())); systrayCreator->setSingleShot(true); 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 systrayIcon; delete myTrayIconMenu; } } // Reload systray icon if (newSystrayIntegration && systrayIcon) systrayIcon->setIcon(getSystrayIcon()); // General if (pref->isToolbarDisplayed()) { toolBar->setVisible(true); } else { // Clear search filter before hiding the top toolbar search_filter->clear(); toolBar->setVisible(false); } if (pref->preventFromSuspend()) { preventTimer->start(PREVENT_SUSPEND_INTERVAL); } else { preventTimer->stop(); m_pwr->setActivityState(false); } transferList->setAlternatingRowColors(pref->useAlternatingRowColors()); properties->getFilesList()->setAlternatingRowColors(pref->useAlternatingRowColors()); properties->getTrackerList()->setAlternatingRowColors(pref->useAlternatingRowColors()); properties->getPeerList()->setAlternatingRowColors(pref->useAlternatingRowColors()); // Queueing System if (pref->isQueueingSystemEnabled()) { if (!actionDecreasePriority->isVisible()) { transferList->hidePriorityColumn(false); actionDecreasePriority->setVisible(true); actionIncreasePriority->setVisible(true); actionTopPriority->setVisible(true); actionBottomPriority->setVisible(true); prioSeparator->setVisible(true); prioSeparatorMenu->setVisible(true); } } else { if (actionDecreasePriority->isVisible()) { transferList->hidePriorityColumn(true); actionDecreasePriority->setVisible(false); actionIncreasePriority->setVisible(false); actionTopPriority->setVisible(false); actionBottomPriority->setVisible(false); prioSeparator->setVisible(false); prioSeparatorMenu->setVisible(false); } } // Torrent properties properties->reloadPreferences(); #if defined(Q_OS_WIN) || defined(Q_OS_MAC) if (pref->isUpdateCheckEnabled()) checkProgramUpdate(); else programUpdateTimer.stop(); #endif qDebug("GUI settings loaded"); } void MainWindow::addUnauthenticatedTracker(const QPair &tracker) { // Trackers whose authentication was cancelled if (unauthenticated_trackers.indexOf(tracker) < 0) unauthenticated_trackers << tracker; } // Called when a tracker requires authentication void MainWindow::trackerAuthenticationRequired(BitTorrent::TorrentHandle *const torrent) { if (unauthenticated_trackers.indexOf(qMakePair(torrent, torrent->currentTracker())) < 0) // Tracker login new trackerLogin(this, torrent); } // Check connection status and display right icon void MainWindow::updateGUI() { BitTorrent::SessionStatus status = BitTorrent::Session::instance()->status(); // update global informations if (systrayIcon) { #if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) QString html = "
"; html += "qBittorrent"; html += "
"; html += "
"; html += " " + tr("DL speed: %1", "e.g: Download speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate(), true)); html += "
"; html += "
"; html += " " + tr("UP speed: %1", "e.g: Upload speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadUploadRate(), true)); html += "
"; #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 systrayIcon->setToolTip(html); // tray icon } if (displaySpeedInTitle) setWindowTitle(tr("[D: %1, U: %2] qBittorrent %3", "D = Download; U = Upload; %3 is qBittorrent version").arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate(), true)).arg(Utils::Misc::friendlyUnit(status.payloadUploadRate(), true)).arg(QString::fromUtf8(VERSION))); } void MainWindow::showNotificationBaloon(QString title, QString msg) const { if (!Preferences::instance()->useProgramNotification()) 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 reply = notifications.Notify("qBittorrent", 0, "qbittorrent", title, msg, QStringList(), hints, -1); reply.waitForFinished(); if (!reply.isError()) return; #endif if (systrayIcon && QSystemTrayIcon::supportsMessages()) systrayIcon->showMessage(title, msg, QSystemTrayIcon::Information, TIME_TRAY_BALLOON); } /***************************************************** * * * Utils * * * *****************************************************/ void MainWindow::downloadFromURLList(const QStringList& url_list) { const bool useTorrentAdditionDialog = Preferences::instance()->useAdditionDialog(); foreach (QString url, url_list) { if ((url.size() == 40 && !url.contains(QRegExp("[^0-9A-Fa-f]"))) || (url.size() == 32 && !url.contains(QRegExp("[^2-7A-Za-z]")))) url = "magnet:?xt=urn:btih:" + url; if (useTorrentAdditionDialog) AddNewTorrentDialog::show(url, this); else BitTorrent::Session::instance()->addTorrent(url); } } /***************************************************** * * * Options * * * *****************************************************/ void MainWindow::createSystrayDelayed() { static int timeout = 20; if (QSystemTrayIcon::isSystemTrayAvailable()) { // Ok, systray integration is now supported // Create systray icon createTrayIcon(); delete systrayCreator; } else { if (timeout) { // Retry a bit later systrayCreator->start(2000); --timeout; } else { // Timed out, apparently system really does not // support systray icon delete systrayCreator; // Disable it in program preferences to // avoid trying at each startup Preferences::instance()->setSystrayIntegration(false); } } } void MainWindow::updateAltSpeedsBtn(bool alternative) { actionUse_alternative_speed_limits->setChecked(alternative); } void MainWindow::updateTrayIconMenu() { actionToggleVisibility->setText(isVisible() ? tr("Hide") : tr("Show")); } QMenu* MainWindow::getTrayIconMenu() { if (myTrayIconMenu) return myTrayIconMenu; // Tray icon Menu myTrayIconMenu = new QMenu(this); connect(myTrayIconMenu, SIGNAL(aboutToShow()), SLOT(updateTrayIconMenu())); myTrayIconMenu->addAction(actionToggleVisibility); myTrayIconMenu->addSeparator(); myTrayIconMenu->addAction(actionOpen); myTrayIconMenu->addAction(actionDownload_from_URL); myTrayIconMenu->addSeparator(); const bool isAltBWEnabled = Preferences::instance()->isAltBandwidthEnabled(); updateAltSpeedsBtn(isAltBWEnabled); actionUse_alternative_speed_limits->setChecked(isAltBWEnabled); myTrayIconMenu->addAction(actionUse_alternative_speed_limits); myTrayIconMenu->addAction(actionSet_global_download_limit); myTrayIconMenu->addAction(actionSet_global_upload_limit); myTrayIconMenu->addSeparator(); myTrayIconMenu->addAction(actionStart_All); myTrayIconMenu->addAction(actionPause_All); myTrayIconMenu->addSeparator(); myTrayIconMenu->addAction(actionExit); if (ui_locked) myTrayIconMenu->setEnabled(false); return myTrayIconMenu; } void MainWindow::createTrayIcon() { // Tray icon systrayIcon = new QSystemTrayIcon(getSystrayIcon(), this); systrayIcon->setContextMenu(getTrayIconMenu()); connect(systrayIcon, SIGNAL(messageClicked()), this, SLOT(balloonClicked())); // End of Icon Menu connect(systrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(toggleVisibility(QSystemTrayIcon::ActivationReason))); systrayIcon->show(); } // Display Program Options void MainWindow::on_actionOptions_triggered() { if (options) { // Get focus options->setFocus(); } else { options = new options_imp(this); connect(options, SIGNAL(status_changed()), this, SLOT(optionsSaved())); } } void MainWindow::on_actionTop_tool_bar_triggered() { bool is_visible = static_cast(sender())->isChecked(); toolBar->setVisible(is_visible); Preferences::instance()->setToolbarDisplayed(is_visible); } void MainWindow::on_actionSpeed_in_title_bar_triggered() { displaySpeedInTitle = static_cast(sender())->isChecked(); Preferences::instance()->showSpeedInTitleBar(displaySpeedInTitle); if (displaySpeedInTitle) updateGUI(); else setWindowTitle(QString("qBittorrent %1").arg(QString::fromUtf8(VERSION))); } void MainWindow::on_actionRSS_Reader_triggered() { Preferences::instance()->setRSSEnabled(actionRSS_Reader->isChecked()); displayRSSTab(actionRSS_Reader->isChecked()); } void MainWindow::on_actionSearch_engine_triggered() { if (!has_python && actionSearch_engine->isChecked()) { int pythonVersion = Utils::Misc::pythonVersion(); // Check if python is already in PATH if (pythonVersion > 0) Logger::instance()->addMessage(tr("Python found in %1").arg("PATH"), Log::INFO); // Prevent translators from messing with PATH #ifdef Q_OS_WIN else if (addPythonPathToEnv()) pythonVersion = Utils::Misc::pythonVersion(); #endif bool res = false; if ((pythonVersion == 2) || (pythonVersion == 3)) { // Check python minimum requirement: 2.7.0/3.3.0 QString version = Utils::Misc::pythonVersionComplete(); QStringList splitted = version.split('.'); if (splitted.size() > 1) { int middleVer = splitted.at(1).toInt(); if ((pythonVersion == 2 && middleVer < 7) || (pythonVersion == 3 && middleVer < 3)) { QMessageBox::information(this, tr("Old Python Interpreter"), tr("Your Python version %1 is outdated. Please upgrade to latest version for search engines to work. Minimum requirement: 2.7.0/3.3.0.").arg(version)); actionSearch_engine->setChecked(false); Preferences::instance()->setSearchEnabled(false); return; } else { res = true; } } else { QMessageBox::information(this, tr("Undetermined Python version"), tr("Couldn't determine your Python version (%1). Search engine disabled.").arg(version)); actionSearch_engine->setChecked(false); Preferences::instance()->setSearchEnabled(false); return; } } if (res) { has_python = 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(); actionSearch_engine->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 actionSearch_engine->setChecked(false); Preferences::instance()->setSearchEnabled(false); return; } } displaySearchTab(actionSearch_engine->isChecked()); } void MainWindow::on_action_Import_Torrent_triggered() { TorrentImportDlg::importTorrent(); } /***************************************************** * * * HTTP Downloader * * * *****************************************************/ // Display an input dialog to prompt user for // an url void MainWindow::on_actionDownload_from_URL_triggered() { if (!downloadFromURLDialog) { downloadFromURLDialog = new downloadFromURL(this); connect(downloadFromURLDialog, SIGNAL(urlsReadyToBeDownloaded(QStringList)), this, SLOT(downloadFromURLList(QStringList))); } } #if defined(Q_OS_WIN) || defined(Q_OS_MAC) void MainWindow::handleUpdateCheckFinished(bool update_available, QString new_version, bool invokedByUser) { QMessageBox::StandardButton answer = QMessageBox::Yes; if (update_available) { answer = QMessageBox::question(this, tr("qBittorrent Update Available"), tr("A new version is available.\nUpdate to version %1?").arg(new_version), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (answer == QMessageBox::Yes) { // The user want to update, let's download the update ProgramUpdater* updater = dynamic_cast(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(); actionCheck_for_updates->setEnabled(true); actionCheck_for_updates->setText(tr("&Check for Updates")); actionCheck_for_updates->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) programUpdateTimer.start(); } #endif void MainWindow::on_actionDonate_money_triggered() { QDesktopServices::openUrl(QUrl("http://www.qbittorrent.org/donate")); } void MainWindow::showConnectionSettings() { on_actionOptions_triggered(); options->showConnectionTab(); } void MainWindow::minimizeWindow() { setWindowState(windowState() ^ Qt::WindowMinimized); } void MainWindow::on_actionExecution_Logs_triggered(bool checked) { if (checked) { Q_ASSERT(!m_executionLog); m_executionLog = new ExecutionLog(tabs); int index_tab = tabs->addTab(m_executionLog, tr("Execution Log")); tabs->setTabIcon(index_tab, GuiIconProvider::instance()->getIcon("view-calendar-journal")); } else if (m_executionLog) { delete m_executionLog; } Preferences::instance()->setExecutionLogEnabled(checked); } void MainWindow::on_actionAutoExit_qBittorrent_toggled(bool enabled) { qDebug() << Q_FUNC_INFO << enabled; Preferences::instance()->setShutdownqBTWhenDownloadsComplete(enabled); } void MainWindow::on_actionAutoSuspend_system_toggled(bool enabled) { qDebug() << Q_FUNC_INFO << enabled; Preferences::instance()->setSuspendWhenDownloadsComplete(enabled); } void MainWindow::on_actionAutoHibernate_system_toggled(bool enabled) { qDebug() << Q_FUNC_INFO << enabled; Preferences::instance()->setHibernateWhenDownloadsComplete(enabled); } void MainWindow::on_actionAutoShutdown_system_toggled(bool enabled) { qDebug() << Q_FUNC_INFO << enabled; Preferences::instance()->setShutdownWhenDownloadsComplete(enabled); } void MainWindow::checkForActiveTorrents() { m_pwr->setActivityState(BitTorrent::Session::instance()->hasActiveTorrents()); } QIcon MainWindow::getSystrayIcon() const { TrayIcon::Style style = Preferences::instance()->trayIconStyle(); switch(style) { case TrayIcon::MONO_DARK: return QIcon(":/icons/skin/qbittorrent_mono_dark.png"); case TrayIcon::MONO_LIGHT: return QIcon(":/icons/skin/qbittorrent_mono_light.png"); default: break; } QIcon icon; #if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) if (Preferences::instance()->useSystemIconTheme()) icon = QIcon::fromTheme("qbittorrent"); #endif if (icon.isNull()) { icon.addFile(":/icons/skin/qbittorrent22.png", QSize(22, 22)); icon.addFile(":/icons/skin/qbittorrent16.png", QSize(16, 16)); icon.addFile(":/icons/skin/qbittorrent32.png", QSize(32, 32)); } return icon; } #if defined(Q_OS_WIN) || defined(Q_OS_MAC) void MainWindow::checkProgramUpdate() { programUpdateTimer.stop(); // If the user had clicked the menu item actionCheck_for_updates->setEnabled(false); actionCheck_for_updates->setText(tr("Checking for Updates...")); actionCheck_for_updates->setToolTip(tr("Already checking for program updates in the background")); bool invokedByUser = actionCheck_for_updates == qobject_cast(sender()); ProgramUpdater *updater = new ProgramUpdater(this, invokedByUser); connect(updater, SIGNAL(updateCheckFinished(bool, QString, bool)), SLOT(handleUpdateCheckFinished(bool, QString, bool))); updater->checkForUpdates(); } #endif #ifdef Q_OS_WIN bool MainWindow::addPythonPathToEnv() { if (has_python) return true; QString python_path = Preferences::getPythonPath(); if (!python_path.isEmpty()) { Logger::instance()->addMessage(tr("Python found in '%1'").arg(Utils::Fs::toNativePath(python_path)), Log::INFO); // Add it to PATH envvar QString path_envar = QString::fromLocal8Bit(qgetenv("PATH").constData()); if (path_envar.isNull()) path_envar = ""; path_envar = python_path + ";" + path_envar; qDebug("New PATH envvar is: %s", qPrintable(path_envar)); qputenv("PATH", Utils::Fs::toNativePath(path_envar).toLocal8Bit()); return true; } return false; } void MainWindow::installPython() { setCursor(QCursor(Qt::WaitCursor)); // Download python Net::DownloadHandler *handler = Net::DownloadManager::instance()->downloadUrl("https://www.python.org/ftp/python/3.4.3/python-3.4.3.msi", true); connect(handler, SIGNAL(downloadFinished(QString, QString)), this, SLOT(pythonDownloadSuccess(QString, QString))); connect(handler, SIGNAL(downloadFailed(QString, QString)), this, SLOT(pythonDownloadFailure(QString, QString))); } void MainWindow::pythonDownloadSuccess(const QString &url, const QString &filePath) { Q_UNUSED(url) setCursor(QCursor(Qt::ArrowCursor)); QFile::rename(filePath, filePath + ".msi"); QProcess installer; qDebug("Launching Python installer in passive mode..."); installer.start("msiexec.exe /passive /i " + Utils::Fs::toNativePath(filePath) + ".msi"); // Wait for setup to complete installer.waitForFinished(); qDebug("Installer stdout: %s", installer.readAllStandardOutput().data()); qDebug("Installer stderr: %s", installer.readAllStandardError().data()); qDebug("Setup should be complete!"); // Delete temp file Utils::Fs::forceRemove(filePath); // Reload search engine has_python = addPythonPathToEnv(); if (has_python) { // Make it print the version to Log Utils::Misc::pythonVersion(); actionSearch_engine->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