diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 090e2cc99..3137057ca 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,12 +17,21 @@ if (GUI) if (WIN32) list (APPEND QBT_QT_COMPONENTS WinExtras) endif(WIN32) + if (APPLE) + list (APPEND QBT_GUI_OPTIONAL_LINK_LIBRARIES objc) + list (APPEND QBT_QT_COMPONENTS MacExtras) + endif (APPLE) endif (GUI) if (DBUS) list (APPEND QBT_QT_COMPONENTS DBus) endif (DBUS) find_package(Qt5 5.5.1 COMPONENTS ${QBT_QT_COMPONENTS} REQUIRED) +if (GUI AND APPLE) + # Fix MOC inability to detect macOS. This seems to only affect cmake. + # Relevant issue: https://bugreports.qt.io/browse/QTBUG-58325 + set(CMAKE_AUTOMOC_MOC_OPTIONS ${CMAKE_AUTOMOC_MOC_OPTIONS} -DQ_OS_MAC) +endif () set(CMAKE_AUTOMOC True) list(APPEND CMAKE_AUTORCC_OPTIONS -compress 9 -threshold 5) diff --git a/src/app/main.cpp b/src/app/main.cpp index 4484a67e6..b42fec742 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -208,14 +208,15 @@ int main(int argc, char *argv[]) #endif #if defined(Q_OS_MAC) -{ - // Since Apple made difficult for users to set PATH, we set here for convenience. - // Users are supposed to install Homebrew Python for search function. - // For more info see issue #5571. - QByteArray path = "/usr/local/bin:"; - path += qgetenv("PATH"); - qputenv("PATH", path.constData()); -} + // Since Apple made difficult for users to set PATH, we set here for convenience. + // Users are supposed to install Homebrew Python for search function. + // For more info see issue #5571. + QByteArray path = "/usr/local/bin:"; + path += qgetenv("PATH"); + qputenv("PATH", path.constData()); + + // On OS X the standard is to not show icons in the menus + app->setAttribute(Qt::AA_DontShowIconsInMenus); #endif #ifndef DISABLE_GUI diff --git a/src/base/preferences.cpp b/src/base/preferences.cpp index 9011eb649..6de309161 100644 --- a/src/base/preferences.cpp +++ b/src/base/preferences.cpp @@ -161,6 +161,9 @@ void Preferences::setHideZeroComboValues(int n) setValue("Preferences/General/HideZeroComboValues", n); } +// In Mac OS X the dock is sufficient for our needs so we disable the sys tray functionality. +// See extensive discussion in https://github.com/qbittorrent/qBittorrent/pull/3018 +#ifndef Q_OS_MAC bool Preferences::systrayIntegration() const { return value("Preferences/General/SystrayEnabled", true).toBool(); @@ -171,44 +174,45 @@ void Preferences::setSystrayIntegration(bool enabled) setValue("Preferences/General/SystrayEnabled", enabled); } -bool Preferences::isToolbarDisplayed() const +bool Preferences::minimizeToTray() const { - return value("Preferences/General/ToolbarDisplayed", true).toBool(); + return value("Preferences/General/MinimizeToTray", false).toBool(); } -void Preferences::setToolbarDisplayed(bool displayed) +void Preferences::setMinimizeToTray(bool b) { - setValue("Preferences/General/ToolbarDisplayed", displayed); + setValue("Preferences/General/MinimizeToTray", b); } -bool Preferences::isStatusbarDisplayed() const +bool Preferences::closeToTray() const { - return value("Preferences/General/StatusbarDisplayed", true).toBool(); + return value("Preferences/General/CloseToTray", true).toBool(); } -void Preferences::setStatusbarDisplayed(bool displayed) +void Preferences::setCloseToTray(bool b) { - setValue("Preferences/General/StatusbarDisplayed", displayed); + setValue("Preferences/General/CloseToTray", b); } +#endif -bool Preferences::minimizeToTray() const +bool Preferences::isToolbarDisplayed() const { - return value("Preferences/General/MinimizeToTray", false).toBool(); + return value("Preferences/General/ToolbarDisplayed", true).toBool(); } -void Preferences::setMinimizeToTray(bool b) +void Preferences::setToolbarDisplayed(bool displayed) { - setValue("Preferences/General/MinimizeToTray", b); + setValue("Preferences/General/ToolbarDisplayed", displayed); } -bool Preferences::closeToTray() const +bool Preferences::isStatusbarDisplayed() const { - return value("Preferences/General/CloseToTray", true).toBool(); + return value("Preferences/General/StatusbarDisplayed", true).toBool(); } -void Preferences::setCloseToTray(bool b) +void Preferences::setStatusbarDisplayed(bool displayed) { - setValue("Preferences/General/CloseToTray", b); + setValue("Preferences/General/StatusbarDisplayed", displayed); } bool Preferences::startMinimized() const @@ -1084,6 +1088,7 @@ void Preferences::setConfirmRemoveAllTags(bool enabled) setValue("Preferences/Advanced/confirmRemoveAllTags", enabled); } +#ifndef Q_OS_MAC TrayIcon::Style Preferences::trayIconStyle() const { return TrayIcon::Style(value("Preferences/Advanced/TrayIconStyle", TrayIcon::NORMAL).toInt()); @@ -1093,6 +1098,7 @@ void Preferences::setTrayIconStyle(TrayIcon::Style style) { setValue("Preferences/Advanced/TrayIconStyle", style); } +#endif // Stuff that don't appear in the Options GUI but are saved // in the same file. diff --git a/src/base/preferences.h b/src/base/preferences.h index 118c8edf4..fe021bc78 100644 --- a/src/base/preferences.h +++ b/src/base/preferences.h @@ -115,16 +115,10 @@ public: void setHideZeroValues(bool b); int getHideZeroComboValues() const; void setHideZeroComboValues(int n); - bool systrayIntegration() const; - void setSystrayIntegration(bool enabled); - bool isToolbarDisplayed() const; - void setToolbarDisplayed(bool displayed); bool isStatusbarDisplayed() const; void setStatusbarDisplayed(bool displayed); - bool minimizeToTray() const; - void setMinimizeToTray(bool b); - bool closeToTray() const; - void setCloseToTray(bool b); + bool isToolbarDisplayed() const; + void setToolbarDisplayed(bool displayed); bool startMinimized() const; void setStartMinimized(bool b); bool isSplashScreenDisabled() const; @@ -264,8 +258,16 @@ public: void setConfirmTorrentRecheck(bool enabled); bool confirmRemoveAllTags() const; void setConfirmRemoveAllTags(bool enabled); +#ifndef Q_OS_MAC + bool systrayIntegration() const; + void setSystrayIntegration(bool enabled); + bool minimizeToTray() const; + void setMinimizeToTray(bool b); + bool closeToTray() const; + void setCloseToTray(bool b); TrayIcon::Style trayIconStyle() const; void setTrayIconStyle(TrayIcon::Style style); +#endif // Stuff that don't appear in the Options GUI but are saved // in the same file. diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 4d3bb152d..3b9f18838 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -153,3 +153,7 @@ target_link_libraries(qbt_gui qbt_lineedit qbt_powermanagement qbt_rss qbt_prope if(WIN32) target_link_libraries(qbt_gui Qt5::WinExtras) endif(WIN32) + +if (APPLE) + target_link_libraries(qbt_gui Qt5::MacExtras) +endif() diff --git a/src/gui/addnewtorrentdialog.cpp b/src/gui/addnewtorrentdialog.cpp index d875fd059..d32bac4c6 100644 --- a/src/gui/addnewtorrentdialog.cpp +++ b/src/gui/addnewtorrentdialog.cpp @@ -214,7 +214,11 @@ void AddNewTorrentDialog::show(QString source, const BitTorrent::AddTorrentParam ok = dlg->loadTorrent(source); if (ok) +#ifdef Q_OS_MAC + dlg->exec(); +#else dlg->open(); +#endif else delete dlg; } diff --git a/src/gui/categoryfilterwidget.cpp b/src/gui/categoryfilterwidget.cpp index 3fe43ab4b..5422388b6 100644 --- a/src/gui/categoryfilterwidget.cpp +++ b/src/gui/categoryfilterwidget.cpp @@ -71,8 +71,11 @@ CategoryFilterWidget::CategoryFilterWidget(QWidget *parent) setUniformRowHeights(true); setHeaderHidden(true); setIconSize(Utils::Misc::smallIconSize()); -#if defined(Q_OS_MAC) +#ifdef Q_OS_MAC setAttribute(Qt::WA_MacShowFocusRect, false); + m_defaultIndentation = indentation(); + if (!BitTorrent::Session::instance()->isSubcategoriesEnabled()) + setIndentation(0); #endif setContextMenuPolicy(Qt::CustomContextMenu); sortByColumn(0, Qt::AscendingOrder); @@ -154,6 +157,12 @@ void CategoryFilterWidget::showMenu(QPoint) void CategoryFilterWidget::callUpdateGeometry() { +#ifdef Q_OS_MAC + if (!BitTorrent::Session::instance()->isSubcategoriesEnabled()) + setIndentation(0); + else + setIndentation(m_defaultIndentation); +#endif updateGeometry(); } diff --git a/src/gui/categoryfilterwidget.h b/src/gui/categoryfilterwidget.h index 3cfe64e2f..9c6df0e59 100644 --- a/src/gui/categoryfilterwidget.h +++ b/src/gui/categoryfilterwidget.h @@ -57,4 +57,8 @@ private: QSize minimumSizeHint() const override; void rowsInserted(const QModelIndex &parent, int start, int end) override; QString askCategoryName(); + +#ifdef Q_OS_MAC + int m_defaultIndentation; +#endif }; diff --git a/src/gui/executionlog.cpp b/src/gui/executionlog.cpp index a5b619a32..b286f0ed4 100644 --- a/src/gui/executionlog.cpp +++ b/src/gui/executionlog.cpp @@ -47,8 +47,10 @@ ExecutionLog::ExecutionLog(QWidget *parent, const Log::MsgTypes &types) m_msgList = new LogListWidget(MAX_LOG_MESSAGES, Log::MsgTypes(types)); +#ifndef Q_OS_MAC ui->tabConsole->setTabIcon(0, GuiIconProvider::instance()->getIcon("view-calendar-journal")); ui->tabConsole->setTabIcon(1, GuiIconProvider::instance()->getIcon("view-filter")); +#endif ui->tabGeneral->layout()->addWidget(m_msgList); ui->tabBan->layout()->addWidget(m_peerList); diff --git a/src/gui/hidabletabwidget.h b/src/gui/hidabletabwidget.h index 654556f9a..9263fac46 100644 --- a/src/gui/hidabletabwidget.h +++ b/src/gui/hidabletabwidget.h @@ -43,13 +43,22 @@ public: } protected: - void tabInserted(int index) +#ifdef Q_OS_MAC + void paintEvent(QPaintEvent* event) override + { + // Hide the pane for macintosh style + if (!style()->inherits("QMacStyle")) + QTabWidget::paintEvent(event); + } +#endif + + void tabInserted(int index) override { QTabWidget::tabInserted(index); tabBar()->setVisible(count() != 1); } - void tabRemoved(int index) + void tabRemoved(int index) override { //QTabWidget::tabInserted(index); QTabWidget::tabRemoved(index); diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index d6e6717ca..8c36288e0 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -30,6 +30,13 @@ #include "mainwindow.h" +#ifdef Q_OS_MAC +#include +#include +#include +#include +#endif + #include #if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) && defined(QT_DBUS_LIB) #include @@ -149,12 +156,18 @@ MainWindow::MainWindow(QWidget *parent) setWindowTitle("qBittorrent " QBT_VERSION); m_displaySpeedInTitle = pref->speedInTitleBar(); // Setting icons -#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) +#ifndef Q_OS_MAC +#ifdef Q_OS_UNIX if (Preferences::instance()->useSystemIconTheme()) setWindowIcon(QIcon::fromTheme("qbittorrent", QIcon(":/icons/skin/qbittorrent32.png"))); else -#endif +#endif // Q_OS_UNIX setWindowIcon(QIcon(":/icons/skin/qbittorrent32.png")); +#endif // Q_OS_MAC + +#if (defined(Q_OS_UNIX)) + m_ui->actionOptions->setText(tr("Preferences")); +#endif addToolbarContextMenu(); @@ -234,7 +247,11 @@ MainWindow::MainWindow(QWidget *parent) m_splitter->addWidget(hSplitter); m_splitter->setCollapsible(0, true); m_splitter->setCollapsible(1, false); - m_tabs->addTab(m_splitter, GuiIconProvider::instance()->getIcon("folder-remote"), tr("Transfers")); + m_tabs->addTab(m_splitter, +#ifndef Q_OS_MAC + GuiIconProvider::instance()->getIcon("folder-remote"), +#endif + tr("Transfers")); connect(m_searchFilter, SIGNAL(textChanged(QString)), m_transferListWidget, SLOT(applyNameFilter(QString))); connect(hSplitter, SIGNAL(splitterMoved(int,int)), this, SLOT(writeSettings())); @@ -247,10 +264,39 @@ MainWindow::MainWindow(QWidget *parent) connect(BitTorrent::Session::instance(), SIGNAL(trackerError(BitTorrent::TorrentHandle * const,const QString&)), m_transferListFiltersWidget, SLOT(trackerError(BitTorrent::TorrentHandle * const,const QString&))); connect(BitTorrent::Session::instance(), SIGNAL(trackerWarning(BitTorrent::TorrentHandle * const,const QString&)), m_transferListFiltersWidget, SLOT(trackerWarning(BitTorrent::TorrentHandle * const,const QString&))); +#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, SIGNAL(triggered()), m_transferListWidget, SLOT(startSelectedTorrents())); @@ -262,7 +308,9 @@ MainWindow::MainWindow(QWidget *parent) connect(m_ui->actionIncreasePriority, SIGNAL(triggered()), m_transferListWidget, SLOT(increasePrioSelectedTorrents())); connect(m_ui->actionDecreasePriority, SIGNAL(triggered()), m_transferListWidget, SLOT(decreasePrioSelectedTorrents())); connect(m_ui->actionBottomPriority, SIGNAL(triggered()), m_transferListWidget, SLOT(bottomPrioSelectedTorrents())); +#ifndef Q_OS_MAC connect(m_ui->actionToggleVisibility, SIGNAL(triggered()), this, SLOT(toggleVisibility())); +#endif connect(m_ui->actionMinimize, SIGNAL(triggered()), SLOT(minimizeWindow())); connect(m_ui->actionUseAlternativeSpeedLimits, &QAction::triggered, this, &MainWindow::toggleAlternativeSpeeds); @@ -343,6 +391,7 @@ MainWindow::MainWindow(QWidget *parent) // Load Window state and sizes readSettings(); +#ifndef Q_OS_MAC if (m_systrayIcon) { if (!(pref->startMinimized() || m_uiLocked)) { show(); @@ -356,6 +405,7 @@ MainWindow::MainWindow(QWidget *parent) } } else { +#endif // Make sure the Window is visible if we don't have a tray icon if (pref->startMinimized()) { showMinimized(); @@ -365,7 +415,9 @@ MainWindow::MainWindow(QWidget *parent) activateWindow(); raise(); } +#ifndef Q_OS_MAC } +#endif m_propertiesWidget->readSettings(); @@ -398,6 +450,7 @@ MainWindow::MainWindow(QWidget *parent) } #endif #ifdef Q_OS_MAC + setupDockClickHandler(); qt_mac_set_dock_menu(trayIconMenu()); #endif } @@ -616,7 +669,9 @@ void MainWindow::displayRSSTab(bool enable) m_rssWidget = new RSSWidget(m_tabs); connect(m_rssWidget.data(), &RSSWidget::unreadCountUpdated, this, &MainWindow::handleRSSUnreadCountUpdated); int indexTab = m_tabs->addTab(m_rssWidget, tr("RSS (%1)").arg(RSS::Session::instance()->rootFolder()->unreadCount())); +#ifndef Q_OS_MAC m_tabs->setTabIcon(indexTab, GuiIconProvider::instance()->getIcon("application-rss+xml")); +#endif } } else if (m_rssWidget) { @@ -631,7 +686,11 @@ void MainWindow::displaySearchTab(bool enable) // RSS tab if (!m_searchWidget) { m_searchWidget = new SearchWidget(this); - m_tabs->insertTab(1, m_searchWidget, GuiIconProvider::instance()->getIcon("edit-find"), tr("Search")); + m_tabs->insertTab(1, m_searchWidget, +#ifndef Q_OS_MAC + GuiIconProvider::instance()->getIcon("edit-find"), +#endif + tr("Search")); } } else if (m_searchWidget) { @@ -693,8 +752,10 @@ void MainWindow::cleanup() 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)) @@ -955,6 +1016,7 @@ void MainWindow::notifyOfUpdate(QString) m_executableWatcher = 0; } +#ifndef Q_OS_MAC // Toggle Main window visibility void MainWindow::toggleVisibility(const QSystemTrayIcon::ActivationReason reason) { @@ -983,6 +1045,7 @@ void MainWindow::toggleVisibility(const QSystemTrayIcon::ActivationReason reason break; } } +#endif // Display About Dialog void MainWindow::on_actionAbout_triggered() @@ -1022,12 +1085,20 @@ void MainWindow::showEvent(QShowEvent *e) 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 if (pref->confirmOnExit() && BitTorrent::Session::instance()->hasActiveTorrents()) { if (e->spontaneous() || m_forceExit) { @@ -1059,9 +1130,11 @@ void MainWindow::closeEvent(QCloseEvent *e) delete m_searchWidget; hide(); +#ifndef Q_OS_MAC // Hide tray icon if (m_systrayIcon) m_systrayIcon->hide(); +#endif // Accept exit e->accept(); qApp->exit(); @@ -1085,6 +1158,7 @@ void MainWindow::createTorrentTriggered(const QString &path) bool MainWindow::event(QEvent *e) { +#ifndef Q_OS_MAC switch (e->type()) { case QEvent::WindowStateChange: { qDebug("Window change event"); @@ -1112,7 +1186,6 @@ bool MainWindow::event(QEvent *e) } break; } -#ifdef Q_OS_MAC case QEvent::ToolBarChange: { qDebug("MAC: Received a toolbar change event!"); bool ret = QMainWindow::event(e); @@ -1122,10 +1195,10 @@ bool MainWindow::event(QEvent *e) Preferences::instance()->setToolbarDisplayed(m_ui->actionTopToolBar->isChecked()); return ret; } -#endif default: break; } +#endif return QMainWindow::event(e); } @@ -1191,6 +1264,53 @@ void MainWindow::dragEnterEvent(QDragEnterEvent *event) event->acceptProposedAction(); } +#ifdef Q_OS_MAC + +static MainWindow *dockMainWindowHandle; + +static bool dockClickHandler(id self, SEL cmd, ...) +{ + Q_UNUSED(self) + Q_UNUSED(cmd) + + qDebug("Dock icon clicked!"); + + if (dockMainWindowHandle && !dockMainWindowHandle->isVisible()) { + dockMainWindowHandle->activate(); + } + + // Return NO (false) to suppress the default OS X actions + return false; +} + +void MainWindow::setupDockClickHandler() +{ + Class cls = objc_getClass("NSApplication"); + objc_object *appInst = objc_msgSend(reinterpret_cast(cls), sel_registerName("sharedApplication")); + + if (!appInst) + return; + + dockMainWindowHandle = this; + objc_object* delegate = objc_msgSend(appInst, sel_registerName("delegate")); + Class delClass = reinterpret_cast(objc_msgSend(delegate, sel_registerName("class"))); + SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:"); + if (class_getInstanceMethod(delClass, shouldHandle)) { + if (class_replaceMethod(delClass, shouldHandle, reinterpret_cast(dockClickHandler), "B@:")) + qDebug("Registered dock click handler (replaced original method)"); + else + qWarning("Failed to replace method for dock click handler"); + } + else { + if (class_addMethod(delClass, shouldHandle, reinterpret_cast(dockClickHandler), "B@:")) + qDebug("Registered dock click handler"); + else + qWarning("Failed to register dock click handler"); + } +} + +#endif + /***************************************************** * * * Torrent * @@ -1258,6 +1378,7 @@ void MainWindow::loadPreferences(bool configureSession) { Logger::instance()->addMessage(tr("Options were saved successfully.")); const Preferences *const pref = Preferences::instance(); +#ifndef Q_OS_MAC const bool newSystrayIntegration = pref->systrayIntegration(); m_ui->actionLock->setVisible(newSystrayIntegration); if (newSystrayIntegration != (m_systrayIcon != 0)) { @@ -1288,6 +1409,7 @@ void MainWindow::loadPreferences(bool configureSession) // Reload systray icon if (newSystrayIntegration && m_systrayIcon) m_systrayIcon->setIcon(getSystrayIcon()); +#endif // General if (pref->isToolbarDisplayed()) { m_ui->toolBar->setVisible(true); @@ -1321,7 +1443,9 @@ void MainWindow::loadPreferences(bool configureSession) 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); } } @@ -1332,7 +1456,9 @@ void MainWindow::loadPreferences(bool configureSession) 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); } } @@ -1379,8 +1505,9 @@ void MainWindow::updateGUI() const BitTorrent::SessionStatus &status = BitTorrent::Session::instance()->status(); // update global informations +#ifndef Q_OS_MAC if (m_systrayIcon) { -#if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) +#ifdef Q_OS_UNIX QString html = "
"; html += "qBittorrent"; html += "
"; @@ -1395,9 +1522,16 @@ void MainWindow::updateGUI() 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 +#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") @@ -1430,9 +1564,10 @@ void MainWindow::showNotificationBaloon(QString title, QString msg) const reply.waitForFinished(); if (!reply.isError()) return; -#endif +#elif (!defined(Q_OS_MAC)) if (m_systrayIcon && QSystemTrayIcon::supportsMessages()) m_systrayIcon->showMessage(title, msg, QSystemTrayIcon::Information, TIME_TRAY_BALLOON); +#endif } /***************************************************** @@ -1462,6 +1597,7 @@ void MainWindow::downloadFromURLList(const QStringList &urlList) * * *****************************************************/ +#ifndef Q_OS_MAC void MainWindow::createSystrayDelayed() { static int timeout = 20; @@ -1488,24 +1624,34 @@ void MainWindow::createSystrayDelayed() } } -void MainWindow::updateAltSpeedsBtn(bool alternative) +void MainWindow::updateTrayIconMenu() { - m_ui->actionUseAlternativeSpeedLimits->setChecked(alternative); + m_ui->actionToggleVisibility->setText(isVisible() ? tr("Hide") : tr("Show")); } -void MainWindow::updateTrayIconMenu() +void MainWindow::createTrayIcon() { - m_ui->actionToggleVisibility->setText(isVisible() ? tr("Hide") : tr("Show")); + // Tray icon + m_systrayIcon = new QSystemTrayIcon(getSystrayIcon(), this); + + m_systrayIcon->setContextMenu(trayIconMenu()); + connect(m_systrayIcon, SIGNAL(messageClicked()), this, SLOT(balloonClicked())); + // End of Icon Menu + connect(m_systrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(toggleVisibility(QSystemTrayIcon::ActivationReason))); + m_systrayIcon->show(); } +#endif QMenu *MainWindow::trayIconMenu() { if (m_trayIconMenu) return m_trayIconMenu; m_trayIconMenu = new QMenu(this); +#ifndef Q_OS_MAC connect(m_trayIconMenu, SIGNAL(aboutToShow()), SLOT(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(); @@ -1518,29 +1664,24 @@ QMenu *MainWindow::trayIconMenu() 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; } -PropertiesWidget *MainWindow::propertiesWidget() const +void MainWindow::updateAltSpeedsBtn(bool alternative) { - return m_propertiesWidget; + m_ui->actionUseAlternativeSpeedLimits->setChecked(alternative); } -void MainWindow::createTrayIcon() +PropertiesWidget *MainWindow::propertiesWidget() const { - // Tray icon - m_systrayIcon = new QSystemTrayIcon(getSystrayIcon(), this); - - m_systrayIcon->setContextMenu(trayIconMenu()); - connect(m_systrayIcon, SIGNAL(messageClicked()), this, SLOT(balloonClicked())); - // End of Icon Menu - connect(m_systrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(toggleVisibility(QSystemTrayIcon::ActivationReason))); - m_systrayIcon->show(); + return m_propertiesWidget; } // Display Program Options @@ -1726,7 +1867,9 @@ void MainWindow::on_actionExecutionLogs_triggered(bool checked) Q_ASSERT(!m_executionLog); m_executionLog = new ExecutionLog(m_tabs, static_cast(executionLogMsgTypes())); int indexTab = m_tabs->addTab(m_executionLog, tr("Execution Log")); +#ifndef Q_OS_MAC m_tabs->setTabIcon(indexTab, GuiIconProvider::instance()->getIcon("view-calendar-journal")); +#endif } else if (m_executionLog) { delete m_executionLog; @@ -1808,6 +1951,7 @@ void MainWindow::checkForActiveTorrents() m_pwr->setActivityState(BitTorrent::Session::instance()->hasActiveTorrents()); } +#ifndef Q_OS_MAC QIcon MainWindow::getSystrayIcon() const { #if (defined(Q_OS_UNIX) && !defined(Q_OS_MAC)) @@ -1843,6 +1987,7 @@ QIcon MainWindow::getSystrayIcon() const icon.addFile(":/icons/skin/qbittorrent32.png", QSize(32, 32)); return icon; } +#endif #if defined(Q_OS_WIN) || defined(Q_OS_MAC) void MainWindow::checkProgramUpdate() diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index da9b73881..39ba2011e 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -32,9 +32,12 @@ #define MAINWINDOW_H #include -#include #include +#ifndef Q_OS_MAC +#include +#endif + class QCloseEvent; class QFileSystemWatcher; class QShortcut; @@ -102,15 +105,11 @@ public: void showNotificationBaloon(QString title, QString msg) const; private slots: - void toggleVisibility(const QSystemTrayIcon::ActivationReason reason = QSystemTrayIcon::Trigger); - void balloonClicked(); void writeSettings(); void readSettings(); - void createTrayIcon(); void fullDiskError(BitTorrent::TorrentHandle *const torrent, QString msg) const; void handleDownloadFromUrlFailure(QString, QString) const; - void createSystrayDelayed(); void tabChanged(int newTab); void defineUILockPassword(); void clearUILockPassword(); @@ -118,7 +117,6 @@ private slots: void notifyOfUpdate(QString); void showConnectionSettings(); void minimizeWindow(); - void updateTrayIconMenu(); // Keyboard shortcuts void createKeyboardShortcuts(); void displayTransferTab() const; @@ -191,7 +189,15 @@ private slots: void toolbarFollowSystem(); private: +#ifdef Q_OS_MAC + void setupDockClickHandler(); +#else + void toggleVisibility(const QSystemTrayIcon::ActivationReason reason = QSystemTrayIcon::Trigger); + void createTrayIcon(); + void createSystrayDelayed(); + void updateTrayIconMenu(); QIcon getSystrayIcon() const; +#endif #ifdef Q_OS_WIN bool addPythonPathToEnv(); void installPython(); @@ -221,8 +227,10 @@ private: QPointer m_statsDlg; QPointer m_createTorrentDlg; QPointer m_downloadFromURLDialog; +#ifndef Q_OS_MAC QPointer m_systrayIcon; QPointer m_systrayCreator; +#endif QPointer m_trayIconMenu; TransferListWidget *m_transferListWidget; TransferListFiltersWidget *m_transferListFiltersWidget; diff --git a/src/gui/optionsdlg.cpp b/src/gui/optionsdlg.cpp index 6bf9829f1..7d0891166 100644 --- a/src/gui/optionsdlg.cpp +++ b/src/gui/optionsdlg.cpp @@ -81,6 +81,10 @@ OptionsDialog::OptionsDialog(QWidget *parent) setAttribute(Qt::WA_DeleteOnClose); setModal(true); +#if (defined(Q_OS_UNIX)) + setWindowTitle(tr("Preferences")); +#endif + // Icons m_ui->tabSelection->item(TAB_UI)->setIcon(GuiIconProvider::instance()->getIcon("preferences-desktop")); m_ui->tabSelection->item(TAB_BITTORRENT)->setIcon(GuiIconProvider::instance()->getIcon("preferences-system-network")); @@ -95,7 +99,8 @@ OptionsDialog::OptionsDialog(QWidget *parent) #endif m_ui->tabSelection->item(TAB_ADVANCED)->setIcon(GuiIconProvider::instance()->getIcon("preferences-other")); for (int i = 0; i < m_ui->tabSelection->count(); ++i) { - m_ui->tabSelection->item(i)->setSizeHint(QSize(std::numeric_limits::max(), 64)); // uniform size for all icons + // uniform size for all icons + m_ui->tabSelection->item(i)->setSizeHint(QSize(std::numeric_limits::max(), 62)); } m_ui->IpFilterRefreshBtn->setIcon(GuiIconProvider::instance()->getIcon("view-refresh")); @@ -143,6 +148,9 @@ OptionsDialog::OptionsDialog(QWidget *parent) // Load options loadOptions(); +#ifdef Q_OS_MAC + m_ui->checkShowSystray->setVisible(false); +#else // Disable systray integration if it is not supported by the system if (!QSystemTrayIcon::isSystemTrayAvailable()) { m_ui->checkShowSystray->setChecked(false); @@ -150,6 +158,7 @@ OptionsDialog::OptionsDialog(QWidget *parent) m_ui->label_trayIconStyle->setVisible(false); m_ui->comboTrayIcon->setVisible(false); } +#endif #if defined(QT_NO_OPENSSL) m_ui->checkWebUiHttps->setVisible(false); @@ -479,11 +488,13 @@ void OptionsDialog::saveOptions() pref->setAlternatingRowColors(m_ui->checkAltRowColors->isChecked()); pref->setHideZeroValues(m_ui->checkHideZero->isChecked()); pref->setHideZeroComboValues(m_ui->comboHideZero->currentIndex()); +#ifndef Q_OS_MAC pref->setSystrayIntegration(systrayIntegration()); pref->setTrayIconStyle(TrayIcon::Style(m_ui->comboTrayIcon->currentIndex())); pref->setCloseToTray(closeToTray()); pref->setMinimizeToTray(minimizeToTray()); pref->setStartMinimized(startMinimized()); +#endif pref->setSplashScreenDisabled(isSlashScreenDisabled()); pref->setConfirmOnExit(m_ui->checkProgramExitConfirm->isChecked()); pref->setDontConfirmAutoExit(!m_ui->checkProgramAutoExitConfirm->isChecked()); @@ -699,12 +710,14 @@ void OptionsDialog::loadOptions() m_ui->checkProgramExitConfirm->setChecked(pref->confirmOnExit()); m_ui->checkProgramAutoExitConfirm->setChecked(!pref->dontConfirmAutoExit()); +#ifndef Q_OS_MAC m_ui->checkShowSystray->setChecked(pref->systrayIntegration()); if (m_ui->checkShowSystray->isChecked()) { m_ui->checkMinimizeToSysTray->setChecked(pref->minimizeToTray()); m_ui->checkCloseToSystray->setChecked(pref->closeToTray()); m_ui->comboTrayIcon->setCurrentIndex(pref->trayIconStyle()); } +#endif m_ui->checkPreventFromSuspend->setChecked(pref->preventFromSuspend()); @@ -1067,18 +1080,6 @@ int OptionsDialog::getMaxActiveTorrents() const return m_ui->spinMaxActiveTorrents->value(); } -bool OptionsDialog::minimizeToTray() const -{ - if (!m_ui->checkShowSystray->isChecked()) return false; - return m_ui->checkMinimizeToSysTray->isChecked(); -} - -bool OptionsDialog::closeToTray() const -{ - if (!m_ui->checkShowSystray->isChecked()) return false; - return m_ui->checkCloseToSystray->isChecked(); -} - bool OptionsDialog::isQueueingSystemEnabled() const { return m_ui->checkEnableQueueing->isChecked(); @@ -1128,12 +1129,26 @@ bool OptionsDialog::startMinimized() const return m_ui->checkStartMinimized->isChecked(); } +#ifndef Q_OS_MAC bool OptionsDialog::systrayIntegration() const { if (!QSystemTrayIcon::isSystemTrayAvailable()) return false; return m_ui->checkShowSystray->isChecked(); } +bool OptionsDialog::minimizeToTray() const +{ + if (!m_ui->checkShowSystray->isChecked()) return false; + return m_ui->checkMinimizeToSysTray->isChecked(); +} + +bool OptionsDialog::closeToTray() const +{ + if (!m_ui->checkShowSystray->isChecked()) return false; + return m_ui->checkCloseToSystray->isChecked(); +} +#endif + // Return Share ratio qreal OptionsDialog::getMaxRatio() const { diff --git a/src/gui/optionsdlg.h b/src/gui/optionsdlg.h index 666490dc4..dc0cf9a59 100644 --- a/src/gui/optionsdlg.h +++ b/src/gui/optionsdlg.h @@ -114,9 +114,11 @@ private: static QString languageToLocalizedString(const QLocale &locale); // General options QString getLocale() const; +#ifndef Q_OS_MAC bool systrayIntegration() const; bool minimizeToTray() const; bool closeToTray() const; +#endif bool startMinimized() const; bool isSlashScreenDisabled() const; bool preventFromSuspend() const; diff --git a/src/gui/properties/proptabbar.cpp b/src/gui/properties/proptabbar.cpp index 66914a3c9..b2edf10f3 100644 --- a/src/gui/properties/proptabbar.cpp +++ b/src/gui/properties/proptabbar.cpp @@ -43,34 +43,58 @@ PropTabBar::PropTabBar(QWidget *parent) : setSpacing(3); m_btnGroup = new QButtonGroup(this); // General tab - QPushButton *main_infos_button = new QPushButton(GuiIconProvider::instance()->getIcon("document-properties"), tr("General"), parent); + QPushButton *main_infos_button = new QPushButton( +#ifndef Q_OS_MAC + GuiIconProvider::instance()->getIcon("document-properties"), +#endif + tr("General"), parent); main_infos_button->setShortcut(Qt::ALT + Qt::Key_G); addWidget(main_infos_button); m_btnGroup->addButton(main_infos_button, MAIN_TAB); // Trackers tab - QPushButton *trackers_button = new QPushButton(GuiIconProvider::instance()->getIcon("network-server"), tr("Trackers"), parent); + QPushButton *trackers_button = new QPushButton( +#ifndef Q_OS_MAC + GuiIconProvider::instance()->getIcon("network-server"), +#endif + tr("Trackers"), parent); trackers_button->setShortcut(Qt::ALT + Qt::Key_C); addWidget(trackers_button); m_btnGroup->addButton(trackers_button, TRACKERS_TAB); // Peers tab - QPushButton *peers_button = new QPushButton(GuiIconProvider::instance()->getIcon("edit-find-user"), tr("Peers"), parent); + QPushButton *peers_button = new QPushButton( +#ifndef Q_OS_MAC + GuiIconProvider::instance()->getIcon("edit-find-user"), +#endif + tr("Peers"), parent); peers_button->setShortcut(Qt::ALT + Qt::Key_R); addWidget(peers_button); m_btnGroup->addButton(peers_button, PEERS_TAB); // URL seeds tab - QPushButton *urlseeds_button = new QPushButton(GuiIconProvider::instance()->getIcon("network-server"), tr("HTTP Sources"), parent); + QPushButton *urlseeds_button = new QPushButton( +#ifndef Q_OS_MAC + GuiIconProvider::instance()->getIcon("network-server"), +#endif + tr("HTTP Sources"), parent); urlseeds_button->setShortcut(Qt::ALT + Qt::Key_B); addWidget(urlseeds_button); m_btnGroup->addButton(urlseeds_button, URLSEEDS_TAB); // Files tab - QPushButton *files_button = new QPushButton(GuiIconProvider::instance()->getIcon("inode-directory"), tr("Content"), parent); + QPushButton *files_button = new QPushButton( +#ifndef Q_OS_MAC + GuiIconProvider::instance()->getIcon("inode-directory"), +#endif + tr("Content"), parent); files_button->setShortcut(Qt::ALT + Qt::Key_Z); addWidget(files_button); m_btnGroup->addButton(files_button, FILES_TAB); // Spacer addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed)); // Speed tab - QPushButton *speed_button = new QPushButton(GuiIconProvider::instance()->getIcon("office-chart-line"), tr("Speed"), parent); + QPushButton *speed_button = new QPushButton( +#ifndef Q_OS_MAC + GuiIconProvider::instance()->getIcon("office-chart-line"), +#endif + tr("Speed"), parent); speed_button->setShortcut(Qt::ALT + Qt::Key_D); addWidget(speed_button); m_btnGroup->addButton(speed_button, SPEED_TAB); diff --git a/src/gui/rss/rsswidget.cpp b/src/gui/rss/rsswidget.cpp index d2524bc87..ef6dec28a 100644 --- a/src/gui/rss/rsswidget.cpp +++ b/src/gui/rss/rsswidget.cpp @@ -72,10 +72,12 @@ RSSWidget::RSSWidget(QWidget *parent) m_ui->actionRename->setIcon(GuiIconProvider::instance()->getIcon("edit-rename")); m_ui->actionUpdate->setIcon(GuiIconProvider::instance()->getIcon("view-refresh")); m_ui->actionUpdateAllFeeds->setIcon(GuiIconProvider::instance()->getIcon("view-refresh")); +#ifndef Q_OS_MAC m_ui->newFeedButton->setIcon(GuiIconProvider::instance()->getIcon("list-add")); m_ui->markReadButton->setIcon(GuiIconProvider::instance()->getIcon("mail-mark-read")); m_ui->updateAllButton->setIcon(GuiIconProvider::instance()->getIcon("view-refresh")); m_ui->rssDownloaderBtn->setIcon(GuiIconProvider::instance()->getIcon("download")); +#endif m_articleListWidget = new ArticleListWidget(m_ui->splitterMain); m_ui->splitterMain->insertWidget(0, m_articleListWidget); diff --git a/src/gui/search/searchwidget.cpp b/src/gui/search/searchwidget.cpp index b04c77587..433d870bb 100644 --- a/src/gui/search/searchwidget.cpp +++ b/src/gui/search/searchwidget.cpp @@ -99,12 +99,19 @@ SearchWidget::SearchWidget(MainWindow *mainWindow) << "

" << flush; m_ui->m_searchPattern->setToolTip(searchPatternHint); +#ifndef Q_OS_MAC // Icons m_ui->searchButton->setIcon(GuiIconProvider::instance()->getIcon("edit-find")); m_ui->downloadButton->setIcon(GuiIconProvider::instance()->getIcon("download")); m_ui->goToDescBtn->setIcon(GuiIconProvider::instance()->getIcon("application-x-mswinurl")); m_ui->pluginsButton->setIcon(GuiIconProvider::instance()->getIcon("preferences-system-network")); m_ui->copyURLBtn->setIcon(GuiIconProvider::instance()->getIcon("edit-copy")); +#else + // On macOS the icons overlap the text otherwise + QSize iconSize = m_ui->tabWidget->iconSize(); + iconSize.setWidth(iconSize.width() + 16); + m_ui->tabWidget->setIconSize(iconSize); +#endif connect(m_ui->tabWidget, &QTabWidget::tabCloseRequested, this, &SearchWidget::closeTab); m_searchEngine = new SearchEngine; diff --git a/src/gui/statusbar.cpp b/src/gui/statusbar.cpp index 9d26923b9..20927f928 100644 --- a/src/gui/statusbar.cpp +++ b/src/gui/statusbar.cpp @@ -45,7 +45,11 @@ StatusBar::StatusBar(QWidget *parent) : QStatusBar(parent) { +#ifndef Q_OS_MAC + // Redefining global stylesheet breaks certain elements on mac like tabs. + // Qt checks whether the stylesheet class inherts("QMacStyle") and this becomes false. qApp->setStyleSheet("QStatusBar::item { border-width: 0; }"); +#endif BitTorrent::Session *const session = BitTorrent::Session::instance(); connect(session, &BitTorrent::Session::speedLimitModeChanged, this, &StatusBar::updateAltSpeedsBtn); @@ -108,16 +112,24 @@ StatusBar::StatusBar(QWidget *parent) QFrame *statusSep1 = new QFrame(this); statusSep1->setFrameStyle(QFrame::VLine); +#ifndef Q_OS_MAC statusSep1->setFrameShadow(QFrame::Raised); +#endif QFrame *statusSep2 = new QFrame(this); statusSep2->setFrameStyle(QFrame::VLine); +#ifndef Q_OS_MAC statusSep2->setFrameShadow(QFrame::Raised); +#endif QFrame *statusSep3 = new QFrame(this); statusSep3->setFrameStyle(QFrame::VLine); +#ifndef Q_OS_MAC statusSep3->setFrameShadow(QFrame::Raised); +#endif QFrame *statusSep4 = new QFrame(this); statusSep4->setFrameStyle(QFrame::VLine); +#ifndef Q_OS_MAC statusSep4->setFrameShadow(QFrame::Raised); +#endif layout->addWidget(m_DHTLbl); layout->addWidget(statusSep1); layout->addWidget(m_connecStatusLblIcon); diff --git a/src/gui/tagfilterwidget.cpp b/src/gui/tagfilterwidget.cpp index 06612c319..591d8b427 100644 --- a/src/gui/tagfilterwidget.cpp +++ b/src/gui/tagfilterwidget.cpp @@ -73,6 +73,7 @@ TagFilterWidget::TagFilterWidget(QWidget *parent) setIconSize(Utils::Misc::smallIconSize()); #if defined(Q_OS_MAC) setAttribute(Qt::WA_MacShowFocusRect, false); + setIndentation(0); #endif setContextMenuPolicy(Qt::CustomContextMenu); sortByColumn(0, Qt::AscendingOrder); diff --git a/src/src.pro b/src/src.pro index b27bde7e5..c3b2c0ec9 100644 --- a/src/src.pro +++ b/src/src.pro @@ -23,6 +23,8 @@ nogui { DEFINES += DISABLE_GUI TARGET = qbittorrent-nox } else { + macx: QT += macextras + macx: LIBS += -lobjc QT += xml concurrent svg widgets CONFIG(static) { DEFINES += QBT_STATIC_QT