diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 6968100026..a1b8e77ccc 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -895,6 +895,8 @@ class Application(QApplication): qargs = [i.encode('utf-8') if isinstance(i, unicode) else i for i in args] self.pi = plugins['progress_indicator'][0] QApplication.__init__(self, qargs) + if islinux or isbsd: + self.setAttribute(Qt.AA_DontUseNativeMenuBar, 'CALIBRE_NO_NATIVE_MENUBAR' in os.environ) self.setup_styles(force_calibre_style) f = QFont(QApplication.font()) if (f.family(), f.pointSize()) == ('Sans Serif', 9): # Hard coded Qt settings, no user preference detected diff --git a/src/calibre/gui2/jobs.py b/src/calibre/gui2/jobs.py index 61a46bb705..f6aecc912a 100644 --- a/src/calibre/gui2/jobs.py +++ b/src/calibre/gui2/jobs.py @@ -16,6 +16,7 @@ from PyQt5.Qt import (QAbstractTableModel, QModelIndex, Qt, QHBoxLayout, QVBoxLayout, QSizePolicy, QLabel, QCoreApplication, QAction, QByteArray, QSortFilterProxyModel) +from calibre.constants import islinux, isbsd from calibre.utils.ipc.server import Server from calibre.utils.ipc.job import ParallelJob from calibre.gui2 import (Dispatcher, error_dialog, question_dialog, @@ -446,6 +447,8 @@ class DetailView(QDialog, Ui_Dialog): # {{{ class JobsButton(QFrame): # {{{ + tray_tooltip_updated = pyqtSignal(object) + def __init__(self, horizontal=False, size=48, parent=None): QFrame.__init__(self, parent) if horizontal: @@ -506,6 +509,17 @@ class JobsButton(QFrame): # {{{ src = unicode(self._jobs.text()) return int(re.search(r'\d+', src).group()) + def tray_tooltip(self, num=0): + if num == 0: + text = _('No running jobs') + elif num == 1: + text = _('One running job') + else: + text = _('%d running jobs') % num + if not (islinux or isbsd): + text = 'calibre: ' + text + return text + def job_added(self, nnum): jobs = self._jobs src = unicode(jobs.text()) @@ -513,6 +527,7 @@ class JobsButton(QFrame): # {{{ text = src.replace(str(num), str(nnum)) jobs.setText(text) self.start() + self.tray_tooltip_updated.emit(self.tray_tooltip(nnum)) def job_done(self, nnum): jobs = self._jobs @@ -522,6 +537,7 @@ class JobsButton(QFrame): # {{{ jobs.setText(text) if nnum == 0: self.no_more_jobs() + self.tray_tooltip_updated.emit(self.tray_tooltip(nnum)) def no_more_jobs(self): if self.is_running: diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index 356ca4907e..3d200b89bb 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -14,7 +14,6 @@ from PyQt5.Qt import ( QWidget, QSizePolicy, QBrush, QPixmap, QSize, QPushButton, QVBoxLayout) from calibre import human_readable -from calibre.constants import islinux, isbsd from calibre.gui2.preferences import ConfigWidgetBase, test_widget, CommaSeparatedList from calibre.gui2.preferences.look_feel_ui import Ui_Form from calibre.gui2 import config, gprefs, qt_app, open_local_file, question_dialog @@ -180,9 +179,6 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r('show_avg_rating', config) r('disable_animations', config) r('systray_icon', config, restart_required=True) - if islinux or isbsd: - self.opt_systray_icon.setEnabled(False) - self.opt_systray_icon.setText(_('System tray icon is disabled because of bugs in Qt 5')) r('show_splash_screen', gprefs) r('disable_tray_notification', config) r('use_roman_numerals_for_series_number', config) diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index d2ec5ee38a..727788726e 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -16,12 +16,12 @@ from collections import OrderedDict from io import BytesIO import apsw -from PyQt5.Qt import (Qt, QTimer, QHelpEvent, QAction, - QMenu, QIcon, pyqtSignal, QUrl, QFont, - QDialog, QSystemTrayIcon, QApplication) +from PyQt5.Qt import ( + Qt, QTimer, QAction, QMenu, QIcon, pyqtSignal, QUrl, QFont, QDialog, + QApplication, QSystemTrayIcon) from calibre import prints, force_unicode -from calibre.constants import __appname__, isosx, filesystem_encoding, DEBUG, islinux, isbsd +from calibre.constants import __appname__, isosx, filesystem_encoding, DEBUG from calibre.utils.config import prefs, dynamic from calibre.utils.ipc.server import Server from calibre.db.legacy import LibraryDatabase @@ -47,6 +47,7 @@ from calibre.gui2.auto_add import AutoAdder from calibre.gui2.proceed import ProceedQuestion from calibre.gui2.dialogs.message_box import JobError from calibre.gui2.job_indicator import Pointer +from calibre.gui2.dbus_export.widgets import factory from calibre.library import current_library_name class Listener(Thread): # {{{ @@ -80,23 +81,6 @@ class Listener(Thread): # {{{ # }}} -class SystemTrayIcon(QSystemTrayIcon): # {{{ - - tooltip_requested = pyqtSignal(object) - - def __init__(self, icon, parent): - QSystemTrayIcon.__init__(self, icon, parent) - - def event(self, ev): - if ev.type() == ev.ToolTip: - evh = QHelpEvent(ev) - self.tooltip_requested.emit( - (self, evh.globalPos())) - return True - return QSystemTrayIcon.event(self, ev) - -# }}} - _gui = None def get_gui(): @@ -270,20 +254,19 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.default_thumbnail = None self.tb_wrapper = textwrap.TextWrapper(width=40) self.viewers = collections.deque() - self.system_tray_icon = SystemTrayIcon(QIcon(I('lt.png')), self) - self.system_tray_icon.setToolTip('calibre') - self.system_tray_icon.tooltip_requested.connect( - self.job_manager.show_tooltip) - systray_ok = config['systray_icon'] and not (islinux or isbsd) - # System tray icons are broken on linux, see - # https://bugreports.qt-project.org/browse/QTBUG-31762 - if not systray_ok: - self.system_tray_icon.hide() + self.system_tray_icon = None + if config['systray_icon']: + self.system_tray_icon = factory(app_id='com.calibre-ebook.gui').create_system_tray_icon(parent=self, title='calibre') + if self.system_tray_icon is not None: + self.system_tray_icon.setIcon(QIcon(I('lt.png'))) + self.system_tray_icon.setToolTip(self.jobs_button.tray_tooltip()) + self.system_tray_icon.setVisible(True) + self.jobs_button.tray_tooltip_updated.connect(self.system_tray_icon.setToolTip) else: - self.system_tray_icon.show() + prints('Failed to create system tray icon') self.system_tray_menu = QMenu(self) - self.restore_action = self.system_tray_menu.addAction( - QIcon(I('page.png')), _('&Restore')) + self.toggle_to_tray_action = self.system_tray_menu.addAction(QIcon(I('page.png')), '') + self.toggle_to_tray_action.triggered.connect(self.system_tray_icon_activated) self.system_tray_menu.addAction(self.donate_action) self.donate_button.setDefaultAction(self.donate_action) self.donate_button.setStatusTip(self.donate_button.toolTip()) @@ -294,12 +277,11 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.system_tray_menu.addAction(self.quit_action) self.keyboard.register_shortcut('quit calibre', _('Quit calibre'), default_keys=('Ctrl+Q',), action=self.quit_action) - self.system_tray_icon.setContextMenu(self.system_tray_menu) + if self.system_tray_icon is not None: + self.system_tray_icon.setContextMenu(self.system_tray_menu) + self.system_tray_icon.activated.connect(self.system_tray_icon_activated) self.quit_action.triggered[bool].connect(self.quit) self.donate_action.triggered[bool].connect(self.donate) - self.restore_action.triggered.connect(self.show_windows) - self.system_tray_icon.activated.connect( - self.system_tray_icon_activated) self.esc_action = QAction(self) self.addAction(self.esc_action) @@ -355,7 +337,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ if splash_screen is not None: splash_screen.hide() - if self.system_tray_icon.isVisible() and opts.start_in_tray: + if self.system_tray_icon is not None and self.system_tray_icon.isVisible() and opts.start_in_tray: self.hide_windows() self.library_view.model().count_changed_signal.connect( self.iactions['Choose Library'].count_changed) @@ -441,6 +423,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.iactions['Connect Share'].check_smartdevice_menus() QTimer.singleShot(1, self.start_smartdevice) + QTimer.singleShot(100, self.update_toggle_to_tray_action) def esc(self, *args): self.clear_button.click() @@ -512,8 +495,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ def no_op(self, *args): pass - def system_tray_icon_activated(self, r): - if r == QSystemTrayIcon.Trigger: + def system_tray_icon_activated(self, r=False): + if r in (QSystemTrayIcon.Trigger, QSystemTrayIcon.MiddleClick, False): if self.isVisible(): self.hide_windows() else: @@ -533,18 +516,25 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ skip_dialog_name=skip_dialog_name, skip_dialog_skipped_value=skipped_value) + def update_toggle_to_tray_action(self, *args): + if hasattr(self, 'toggle_to_tray_action'): + self.toggle_to_tray_action.setText( + _('Hide main window') if self.isVisible() else _('Show main window')) + def hide_windows(self): for window in QApplication.topLevelWidgets(): if isinstance(window, (MainWindow, QDialog)) and \ window.isVisible(): window.hide() setattr(window, '__systray_minimized', True) + self.update_toggle_to_tray_action() def show_windows(self, *args): for window in QApplication.topLevelWidgets(): if getattr(window, '__systray_minimized', False): window.show() setattr(window, '__systray_minimized', False) + self.update_toggle_to_tray_action() def test_server(self, *args): if self.content_server is not None and \ @@ -937,7 +927,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ def closeEvent(self, e): self.write_settings() - if self.system_tray_icon.isVisible(): + if self.system_tray_icon is not None and self.system_tray_icon.isVisible(): if not dynamic['systray_msg'] and not isosx: info_dialog(self, 'calibre', 'calibre '+ _('will keep running in the system tray. To close it, '