calibre now has a system tray icon

This commit is contained in:
Kovid Goyal 2008-11-21 11:09:58 -08:00
parent bd1d6ca3f3
commit e9f3b6b735
3 changed files with 104 additions and 26 deletions

View File

@ -5,7 +5,8 @@ from xml.parsers.expat import ExpatError
from functools import partial from functools import partial
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, QUrl, QTimer from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, QUrl, QTimer
from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox, \ 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 PyQt4.QtSvg import QSvgRenderer
from calibre import __version__, __appname__, islinux, sanitize_file_name, \ 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, \ pixmap_to_data, choose_dir, ORG_NAME, \
set_sidebar_directories, Dispatcher, \ set_sidebar_directories, Dispatcher, \
SingleApplication, Application, available_height, \ 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.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
from calibre.library.database import LibraryDatabase from calibre.library.database import LibraryDatabase
from calibre.gui2.dialogs.scheduler import Scheduler from calibre.gui2.dialogs.scheduler import Scheduler
from calibre.gui2.update import CheckForUpdates 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.main_ui import Ui_MainWindow
from calibre.gui2.device import DeviceManager from calibre.gui2.device import DeviceManager
from calibre.gui2.status import StatusBar from calibre.gui2.status import StatusBar
@ -91,7 +92,24 @@ class Main(MainWindow, Ui_MainWindow):
self.device_connected = False self.device_connected = False
self.viewers = collections.deque() self.viewers = collections.deque()
self.content_server = None 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 ######################## ####################### Location View ########################
QObject.connect(self.location_view, SIGNAL('location_selected(PyQt_PyObject)'), QObject.connect(self.location_view, SIGNAL('location_selected(PyQt_PyObject)'),
self.location_selected) self.location_selected)
@ -109,7 +127,7 @@ class Main(MainWindow, Ui_MainWindow):
self.update_found) self.update_found)
self.update_checker.start() self.update_checker.start()
####################### Status Bar ##################### ####################### 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) self.setStatusBar(self.status_bar)
QObject.connect(self.job_manager, SIGNAL('job_added(int)'), self.status_bar.job_added, QObject.connect(self.job_manager, SIGNAL('job_added(int)'), self.status_bar.job_added,
Qt.QueuedConnection) Qt.QueuedConnection)
@ -1239,20 +1257,51 @@ in which you want to store your books files. Any existing books will be automati
if self.device_connected: if self.device_connected:
self.memory_view.write_settings() self.memory_view.write_settings()
def closeEvent(self, e): def quit(self, checked):
msg = 'There are active jobs. Are you sure you want to quit?' if self.shutdown():
QApplication.instance().quit()
def donate(self):
BUTTON = '''
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_s-xclick">
<input type="hidden" name="hosted_button_id" value="1335186">
<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="">
<img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
</form>
'''
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 = '''
<html>
<head>
<title>Donate to support calibre</title>
</head>
<body style="background:white">
<div><a href="http://calibre.kovidgoyal.net"><img style="border:0px" src="http://calibre.kovidgoyal.net/chrome/site/calibre_banner.png" alt="calibre" /></a></div>
<p>Calibre %s</p>
%s
</body>
</html>
'''%(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(): if self.job_manager.has_device_jobs():
msg = '<p>'+__appname__ + ' is communicating with the device!<br>'+\ msg = '<p>'+__appname__ + _(''' is communicating with the device!<br>
'Quitting may cause corruption on the device.<br>'+\ 'Quitting may cause corruption on the device.<br>
'Are you sure you want to quit?' 'Are you sure you want to quit?''')+'</p>'
if self.job_manager.has_jobs(): 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) QMessageBox.Yes|QMessageBox.No, self)
d.setIconPixmap(QPixmap(':/images/dialog_warning.svg')) d.setIconPixmap(QPixmap(':/images/dialog_warning.svg'))
d.setDefaultButton(QMessageBox.No) d.setDefaultButton(QMessageBox.No)
if d.exec_() != QMessageBox.Yes: if d.exec_() != QMessageBox.Yes:
e.ignore() return False
return
self.job_manager.terminate_all_jobs() self.job_manager.terminate_all_jobs()
self.write_settings() 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) time.sleep(2)
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
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 <b>Quit</b> in the context menu of the system tray.')).exec_()
dynamic['systray_msg'] = True
self.hide()
e.ignore()
else:
if self.shutdown():
e.accept() e.accept()
else:
e.ignore()
def update_found(self, version): def update_found(self, version):
os = 'windows' if iswindows else 'osx' if isosx else 'linux' os = 'windows' if iswindows else 'osx' if isosx else 'linux'
@ -1286,12 +1350,8 @@ in which you want to store your books files. Any existing books will be automati
dynamic.set('update to version %s'%version, False) dynamic.set('update to version %s'%version, False)
def main(args=sys.argv): def option_parser():
from calibre.utils.lock import singleinstance parser = _option_parser('''\
pid = os.fork() if False and islinux else -1
if pid <= 0:
parser = option_parser('''\
%prog [opts] [path_to_ebook] %prog [opts] [path_to_ebook]
Launch the main calibre Graphical User Interface and optionally add the ebook at Launch the main calibre Graphical User Interface and optionally add the ebook at
@ -1301,6 +1361,16 @@ path_to_ebook to the database.
help=_('Use the library located at the specified path.')) help=_('Use the library located at the specified path.'))
parser.add_option('-v', '--verbose', default=0, action='count', parser.add_option('-v', '--verbose', default=0, action='count',
help=_('Log debugging information to console')) 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) opts, args = parser.parse_args(args)
if opts.with_library is not None and os.path.isdir(opts.with_library): if opts.with_library is not None and os.path.isdir(opts.with_library):
prefs.set('library_path', opts.with_library) prefs.set('library_path', opts.with_library)

View File

@ -164,8 +164,9 @@ class TagViewButton(QToolButton):
class StatusBar(QStatusBar): class StatusBar(QStatusBar):
def __init__(self, jobs_dialog): def __init__(self, jobs_dialog, systray=None):
QStatusBar.__init__(self) QStatusBar.__init__(self)
self.systray = systray
self.movie_button = MovieButton(QMovie(':/images/jobs-animated.mng'), jobs_dialog) self.movie_button = MovieButton(QMovie(':/images/jobs-animated.mng'), jobs_dialog)
self.cover_flow_button = CoverFlowButton() self.cover_flow_button = CoverFlowButton()
self.tag_view_button = TagViewButton() self.tag_view_button = TagViewButton()
@ -180,6 +181,11 @@ class StatusBar(QStatusBar):
def reset_info(self): def reset_info(self):
self.book_info.show_data({}) 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): def jobs(self):
src = qstring_to_unicode(self.movie_button.jobs.text()) src = qstring_to_unicode(self.movie_button.jobs.text())
return int(re.search(r'\d+', src).group()) return int(re.search(r'\d+', src).group())

View File

@ -184,6 +184,7 @@ def setup_completion(fatal_errors):
from calibre.ebooks.epub.from_feeds import option_parser as feeds2epub 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_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', any_formats = ['epub', 'htm', 'html', 'xhtml', 'xhtm', 'rar', 'zip',
'txt', 'lit', 'rtf', 'pdf', 'prc', 'mobi', 'fb2', 'odt'] 'txt', 'lit', 'rtf', 'pdf', 'prc', 'mobi', 'fb2', 'odt']
f = open_file('/etc/bash_completion.d/libprs500') 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('fb22lrf', htmlop, ['fb2']))
f.write(opts_and_exts('pdf2lrf', htmlop, ['pdf'])) f.write(opts_and_exts('pdf2lrf', htmlop, ['pdf']))
f.write(opts_and_exts('any2lrf', htmlop, any_formats)) 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('any2lrf', any2epub, any_formats))
f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf'])) f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf']))
f.write(opts_and_exts('lrf-meta', metaop, ['lrf'])) f.write(opts_and_exts('lrf-meta', metaop, ['lrf']))