diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 2dafee08c1..92566d0046 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -5,7 +5,8 @@ from xml.parsers.expat import ExpatError from functools import partial from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, QUrl, QTimer from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox, \ - QToolButton, QDialog, QDesktopServices, QFileDialog + QToolButton, QDialog, QDesktopServices, QFileDialog, \ + QSystemTrayIcon, QApplication from PyQt4.QtSvg import QSvgRenderer from calibre import __version__, __appname__, islinux, sanitize_file_name, \ @@ -20,12 +21,12 @@ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \ pixmap_to_data, choose_dir, ORG_NAME, \ set_sidebar_directories, Dispatcher, \ SingleApplication, Application, available_height, \ - max_available_height, config + max_available_height, config, info_dialog from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror from calibre.library.database import LibraryDatabase from calibre.gui2.dialogs.scheduler import Scheduler from calibre.gui2.update import CheckForUpdates -from calibre.gui2.main_window import MainWindow, option_parser +from calibre.gui2.main_window import MainWindow, option_parser as _option_parser from calibre.gui2.main_ui import Ui_MainWindow from calibre.gui2.device import DeviceManager from calibre.gui2.status import StatusBar @@ -91,7 +92,24 @@ class Main(MainWindow, Ui_MainWindow): self.device_connected = False self.viewers = collections.deque() self.content_server = None + self.system_tray_icon = QSystemTrayIcon(QIcon(':/library'), self) + if opts.no_systray: + self.system_tray_icon.hide() + else: + self.system_tray_icon.show() + self.system_tray_menu = QMenu() + self.restore_action = self.system_tray_menu.addAction(QIcon(':/images/page.svg'), _('&Restore')) + self.donate_action = self.system_tray_menu.addAction(QIcon(':/images/donate.svg'), _('&Donate')) + self.quit_action = self.system_tray_menu.addAction(QIcon(':/images/window-close.svg'), _('&Quit')) + self.system_tray_icon.setContextMenu(self.system_tray_menu) + self.connect(self.quit_action, SIGNAL('triggered(bool)'), self.quit) + self.connect(self.donate_action, SIGNAL('triggered(bool)'), self.donate) + self.connect(self.restore_action, SIGNAL('triggered(bool)'), lambda c : self.show()) + def sta(r): + if r == QSystemTrayIcon.Trigger: + self.hide() if self.isVisible() else self.show() + self.connect(self.system_tray_icon, SIGNAL('activated(QSystemTrayIcon::ActivationReason)'), sta) ####################### Location View ######################## QObject.connect(self.location_view, SIGNAL('location_selected(PyQt_PyObject)'), self.location_selected) @@ -109,7 +127,7 @@ class Main(MainWindow, Ui_MainWindow): self.update_found) self.update_checker.start() ####################### Status Bar ##################### - self.status_bar = StatusBar(self.jobs_dialog) + self.status_bar = StatusBar(self.jobs_dialog, self.system_tray_icon) self.setStatusBar(self.status_bar) QObject.connect(self.job_manager, SIGNAL('job_added(int)'), self.status_bar.job_added, Qt.QueuedConnection) @@ -1238,21 +1256,52 @@ in which you want to store your books files. Any existing books will be automati self.library_view.write_settings() if self.device_connected: self.memory_view.write_settings() - - def closeEvent(self, e): - msg = 'There are active jobs. Are you sure you want to quit?' + + def quit(self, checked): + if self.shutdown(): + QApplication.instance().quit() + + def donate(self): + BUTTON = ''' +
+ + + + +
+ ''' + MSG = _('is the result of the efforts of many volunteers from all over the world. If you find it useful, please consider donating to support its development.') + HTML = ''' + + + Donate to support calibre + + +
calibre
+

Calibre %s

+ %s + + + '''%(MSG, BUTTON) + pt = PersistentTemporaryFile('_donate.htm') + pt.write(HTML) + pt.close() + QDesktopServices.openUrl(QUrl.fromLocalFile(pt.name)) + + + def shutdown(self): + msg = _('There are active jobs. Are you sure you want to quit?') if self.job_manager.has_device_jobs(): - msg = '

'+__appname__ + ' is communicating with the device!
'+\ - 'Quitting may cause corruption on the device.
'+\ - 'Are you sure you want to quit?' + msg = '

'+__appname__ + _(''' is communicating with the device!
+ 'Quitting may cause corruption on the device.
+ 'Are you sure you want to quit?''')+'

' if self.job_manager.has_jobs(): - d = QMessageBox(QMessageBox.Warning, 'WARNING: Active jobs', msg, + d = QMessageBox(QMessageBox.Warning, _('WARNING: Active jobs'), msg, QMessageBox.Yes|QMessageBox.No, self) d.setIconPixmap(QPixmap(':/images/dialog_warning.svg')) d.setDefaultButton(QMessageBox.No) if d.exec_() != QMessageBox.Yes: - e.ignore() - return + return False self.job_manager.terminate_all_jobs() self.write_settings() @@ -1269,7 +1318,22 @@ in which you want to store your books files. Any existing books will be automati time.sleep(2) except KeyboardInterrupt: pass - e.accept() + self.hide() + return True + + + def closeEvent(self, e): + if 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, choose Quit in the context menu of the system tray.')).exec_() + dynamic['systray_msg'] = True + self.hide() + e.ignore() + else: + if self.shutdown(): + e.accept() + else: + e.ignore() def update_found(self, version): os = 'windows' if iswindows else 'osx' if isosx else 'linux' @@ -1286,21 +1350,27 @@ in which you want to store your books files. Any existing books will be automati dynamic.set('update to version %s'%version, False) -def main(args=sys.argv): - from calibre.utils.lock import singleinstance - - pid = os.fork() if False and islinux else -1 - if pid <= 0: - parser = option_parser('''\ +def option_parser(): + parser = _option_parser('''\ %prog [opts] [path_to_ebook] Launch the main calibre Graphical User Interface and optionally add the ebook at path_to_ebook to the database. ''') - parser.add_option('--with-library', default=None, action='store', - help=_('Use the library located at the specified path.')) - parser.add_option('-v', '--verbose', default=0, action='count', - help=_('Log debugging information to console')) + parser.add_option('--with-library', default=None, action='store', + help=_('Use the library located at the specified path.')) + parser.add_option('-v', '--verbose', default=0, action='count', + help=_('Log debugging information to console')) + parser.add_option('--no-systray', default=False, action='store_true', + help=_('Disable system tray icon')) + return parser + +def main(args=sys.argv): + from calibre.utils.lock import singleinstance + + pid = os.fork() if False and islinux else -1 + if pid <= 0: + parser = option_parser() opts, args = parser.parse_args(args) if opts.with_library is not None and os.path.isdir(opts.with_library): prefs.set('library_path', opts.with_library) diff --git a/src/calibre/gui2/status.py b/src/calibre/gui2/status.py index 67b06ebfe3..d6321fbf43 100644 --- a/src/calibre/gui2/status.py +++ b/src/calibre/gui2/status.py @@ -164,8 +164,9 @@ class TagViewButton(QToolButton): class StatusBar(QStatusBar): - def __init__(self, jobs_dialog): + def __init__(self, jobs_dialog, systray=None): QStatusBar.__init__(self) + self.systray = systray self.movie_button = MovieButton(QMovie(':/images/jobs-animated.mng'), jobs_dialog) self.cover_flow_button = CoverFlowButton() self.tag_view_button = TagViewButton() @@ -179,6 +180,11 @@ class StatusBar(QStatusBar): def reset_info(self): self.book_info.show_data({}) + + def showMessage(self, msg, timeout=0): + if self.systray is not None: + self.systray.showMessage('calibre', msg, self.systray.Information, 10000) + return QStatusBar.showMessage(self, msg, timeout) def jobs(self): src = qstring_to_unicode(self.movie_button.jobs.text()) diff --git a/src/calibre/linux.py b/src/calibre/linux.py index 94c306db43..88b2a52ca7 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -183,7 +183,8 @@ def setup_completion(fatal_errors): from calibre.ebooks.odt.to_oeb import option_parser as odt2oeb from calibre.ebooks.epub.from_feeds import option_parser as feeds2epub from calibre.ebooks.epub.from_any import option_parser as any2epub - from calibre.ebooks.epub.from_comic import option_parser as comic2epub + from calibre.ebooks.epub.from_comic import option_parser as comic2epub + from calibre.gui2.main import option_parser as guiop any_formats = ['epub', 'htm', 'html', 'xhtml', 'xhtm', 'rar', 'zip', 'txt', 'lit', 'rtf', 'pdf', 'prc', 'mobi', 'fb2', 'odt'] f = open_file('/etc/bash_completion.d/libprs500') @@ -204,6 +205,7 @@ def setup_completion(fatal_errors): f.write(opts_and_exts('fb22lrf', htmlop, ['fb2'])) f.write(opts_and_exts('pdf2lrf', htmlop, ['pdf'])) f.write(opts_and_exts('any2lrf', htmlop, any_formats)) + f.write(opts_and_exts('calibre', guiop, any_formats)) f.write(opts_and_exts('any2lrf', any2epub, any_formats)) f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf'])) f.write(opts_and_exts('lrf-meta', metaop, ['lrf']))