home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2011 January / maximum-cd-2011-01.iso / DiscContents / calibre-0.7.26.msi / file_1378 (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2010-10-31  |  22.6 KB  |  612 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. from __future__ import with_statement
  5. __license__ = 'GPL v3'
  6. __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
  7. __docformat__ = 'restructuredtext en'
  8. import collections
  9. import os
  10. import sys
  11. import textwrap
  12. import time
  13. from Queue import Queue, Empty
  14. from threading import Thread
  15. from PyQt4.Qt import Qt, SIGNAL, QTimer, QPixmap, QMenu, QIcon, pyqtSignal, QDialog, QSystemTrayIcon, QApplication, QKeySequence, QMessageBox, QHelpEvent
  16. from calibre import prints
  17. from calibre.constants import __appname__, isosx, DEBUG
  18. from calibre.ptempfile import PersistentTemporaryFile
  19. from calibre.utils.config import prefs, dynamic
  20. from calibre.utils.ipc.server import Server
  21. from calibre.library.database2 import LibraryDatabase2
  22. from calibre.customize.ui import interface_actions
  23. from calibre.gui2 import error_dialog, GetMetadata, open_local_file, gprefs, max_available_height, config, info_dialog, Dispatcher
  24. from calibre.gui2.cover_flow import CoverFlowMixin
  25. from calibre.gui2.widgets import ProgressIndicator
  26. from calibre.gui2.update import UpdateMixin
  27. from calibre.gui2.main_window import MainWindow
  28. from calibre.gui2.layout import MainWindowMixin
  29. from calibre.gui2.device import DeviceMixin
  30. from calibre.gui2.jobs import JobManager, JobsDialog, JobsButton
  31. from calibre.gui2.init import LibraryViewMixin, LayoutMixin
  32. from calibre.gui2.search_box import SearchBoxMixin, SavedSearchBoxMixin
  33. from calibre.gui2.search_restriction_mixin import SearchRestrictionMixin
  34. from calibre.gui2.tag_view import TagBrowserMixin
  35. from calibre.utils.ordered_dict import OrderedDict
  36.  
  37. class Listener(Thread):
  38.     
  39.     def __init__(self, listener):
  40.         Thread.__init__(self)
  41.         self.daemon = True
  42.         self.listener = listener
  43.         self.queue = Queue()
  44.         self._run = True
  45.         self.start()
  46.  
  47.     
  48.     def run(self):
  49.         if self.listener is None:
  50.             return None
  51.         while self._run:
  52.             
  53.             try:
  54.                 conn = self.listener.accept()
  55.                 msg = conn.recv()
  56.                 self.queue.put(msg)
  57.             continue
  58.             continue
  59.             continue
  60.  
  61.  
  62.     
  63.     def close(self):
  64.         self._run = False
  65.         
  66.         try:
  67.             self.listener.close()
  68.         except:
  69.             pass
  70.  
  71.  
  72.  
  73.  
  74. class SystemTrayIcon(QSystemTrayIcon):
  75.     tooltip_requested = pyqtSignal(object)
  76.     
  77.     def __init__(self, icon, parent):
  78.         QSystemTrayIcon.__init__(self, icon, parent)
  79.  
  80.     
  81.     def event(self, ev):
  82.         if ev.type() == ev.ToolTip:
  83.             evh = QHelpEvent(ev)
  84.             self.tooltip_requested.emit((self, evh.globalPos()))
  85.             return True
  86.         return QSystemTrayIcon.event(self, ev)
  87.  
  88.  
  89.  
  90. class Main(MainWindow, MainWindowMixin, DeviceMixin, TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, SearchBoxMixin, SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin):
  91.     
  92.     def __init__(self, opts, parent = None):
  93.         MainWindow.__init__(self, opts, parent)
  94.         self.opts = opts
  95.         self.device_connected = None
  96.         acmap = OrderedDict()
  97.         for action in interface_actions():
  98.             (mod, cls) = action.actual_plugin.split(':')
  99.             ac = getattr(__import__(mod, fromlist = [
  100.                 '1'], level = 0), cls)(self, action.site_customization)
  101.             if ac.name in acmap:
  102.                 if ac.priority >= acmap[ac.name].priority:
  103.                     acmap[ac.name] = ac
  104.                 
  105.             ac.priority >= acmap[ac.name].priority
  106.             acmap[ac.name] = ac
  107.         
  108.         self.iactions = acmap
  109.  
  110.     
  111.     def initialize(self, library_path, db, listener, actions, show_gui = True):
  112.         opts = self.opts
  113.         (self.preferences_action, self.quit_action) = actions
  114.         self.library_path = library_path
  115.         self.content_server = None
  116.         self.spare_servers = []
  117.         self.must_restart_before_config = False
  118.         fontconfig = fontconfig
  119.         import calibre.utils.fonts
  120.         self.fc = fontconfig
  121.         self.listener = Listener(listener)
  122.         self.check_messages_timer = QTimer()
  123.         self.connect(self.check_messages_timer, SIGNAL('timeout()'), self.another_instance_wants_to_talk)
  124.         self.check_messages_timer.start(1000)
  125.         for ac in self.iactions.values():
  126.             ac.do_genesis()
  127.         
  128.         MainWindowMixin.__init__(self, db)
  129.         self.job_manager = JobManager()
  130.         self.jobs_dialog = JobsDialog(self, self.job_manager)
  131.         self.jobs_button = JobsButton(horizontal = True, parent = self)
  132.         self.jobs_button.initialize(self.jobs_dialog, self.job_manager)
  133.         LayoutMixin.__init__(self)
  134.         DeviceMixin.__init__(self)
  135.         self.restriction_count_of_books_in_view = 0
  136.         self.restriction_count_of_books_in_library = 0
  137.         self.restriction_in_effect = False
  138.         self.progress_indicator = ProgressIndicator(self)
  139.         self.progress_indicator.pos = (0, 20)
  140.         self.verbose = opts.verbose
  141.         self.get_metadata = GetMetadata()
  142.         self.upload_memory = { }
  143.         self.metadata_dialogs = []
  144.         self.default_thumbnail = None
  145.         self.tb_wrapper = textwrap.TextWrapper(width = 40)
  146.         self.viewers = collections.deque()
  147.         self.system_tray_icon = SystemTrayIcon(QIcon(I('library.png')), self)
  148.         self.system_tray_icon.setToolTip('calibre')
  149.         self.system_tray_icon.tooltip_requested.connect(self.job_manager.show_tooltip)
  150.         if not config['systray_icon']:
  151.             self.system_tray_icon.hide()
  152.         else:
  153.             self.system_tray_icon.show()
  154.         self.system_tray_menu = QMenu(self)
  155.         self.restore_action = self.system_tray_menu.addAction(QIcon(I('page.png')), _('&Restore'))
  156.         self.donate_action = self.system_tray_menu.addAction(QIcon(I('donate.png')), _('&Donate to support calibre'))
  157.         self.donate_button.setDefaultAction(self.donate_action)
  158.         self.donate_button.setStatusTip(self.donate_button.toolTip())
  159.         self.eject_action = self.system_tray_menu.addAction(QIcon(I('eject.png')), _('&Eject connected device'))
  160.         self.eject_action.setEnabled(False)
  161.         self.addAction(self.quit_action)
  162.         self.system_tray_menu.addAction(self.quit_action)
  163.         self.quit_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Q))
  164.         self.system_tray_icon.setContextMenu(self.system_tray_menu)
  165.         self.connect(self.quit_action, SIGNAL('triggered(bool)'), self.quit)
  166.         self.connect(self.donate_action, SIGNAL('triggered(bool)'), self.donate)
  167.         self.connect(self.restore_action, SIGNAL('triggered()'), self.show_windows)
  168.         self.connect(self.system_tray_icon, SIGNAL('activated(QSystemTrayIcon::ActivationReason)'), self.system_tray_icon_activated)
  169.         QTimer.singleShot(1000, self.add_spare_server)
  170.         self.location_manager.location_selected.connect(self.location_selected)
  171.         self.location_manager.unmount_device.connect(self.device_manager.umount_device)
  172.         self.eject_action.triggered.connect(self.device_manager.umount_device)
  173.         UpdateMixin.__init__(self, opts)
  174.         SavedSearchBoxMixin.__init__(self)
  175.         SearchBoxMixin.__init__(self)
  176.         LibraryViewMixin.__init__(self, db)
  177.         if show_gui:
  178.             self.show()
  179.         
  180.         if self.system_tray_icon.isVisible() and opts.start_in_tray:
  181.             self.hide_windows()
  182.         
  183.         self.library_view.model().count_changed_signal.connect(self.iactions['Choose Library'].count_changed)
  184.         if not gprefs.get('quick_start_guide_added', False):
  185.             MetaInformation = MetaInformation
  186.             import calibre.ebooks.metadata
  187.             mi = MetaInformation(_('Calibre Quick Start Guide'), [
  188.                 'John Schember'])
  189.             mi.author_sort = 'Schember, John'
  190.             mi.comments = 'A guide to get you up and running with calibre'
  191.             mi.publisher = 'calibre'
  192.             self.library_view.model().add_books([
  193.                 P('quick_start.epub')], [
  194.                 'epub'], [
  195.                 mi])
  196.             gprefs['quick_start_guide_added'] = True
  197.             self.library_view.model().books_added(1)
  198.             if hasattr(self, 'db_images'):
  199.                 self.db_images.reset()
  200.             
  201.         
  202.         self.library_view.model().count_changed()
  203.         self.tool_bar.database_changed(self.library_view.model().db)
  204.         self.library_view.model().database_changed.connect(self.tool_bar.database_changed, type = Qt.QueuedConnection)
  205.         TagBrowserMixin.__init__(self, db)
  206.         SearchRestrictionMixin.__init__(self)
  207.         self.apply_named_search_restriction(db.prefs['gui_restriction'])
  208.         CoverFlowMixin.__init__(self)
  209.         self._calculated_available_height = min(max_available_height() - 15, self.height())
  210.         self.resize(self.width(), self._calculated_available_height)
  211.         if config['autolaunch_server']:
  212.             self.start_content_server()
  213.         
  214.         self.keyboard_interrupt.connect(self.quit, type = Qt.QueuedConnection)
  215.         self.read_settings()
  216.         self.finalize_layout()
  217.         if self.tool_bar.showing_donate:
  218.             self.donate_button.start_animation()
  219.         
  220.         self.set_window_title()
  221.         for ac in self.iactions.values():
  222.             ac.initialization_complete()
  223.         
  224.  
  225.     
  226.     def start_content_server(self):
  227.         start_threaded_server = start_threaded_server
  228.         import calibre.library.server.main
  229.         server_config = server_config
  230.         import calibre.library.server
  231.         self.content_server = start_threaded_server(self.library_view.model().db, server_config().parse())
  232.         self.content_server.state_callback = Dispatcher(self.iactions['Connect Share'].content_server_state_changed)
  233.         self.content_server.state_callback(True)
  234.         self.test_server_timer = QTimer.singleShot(10000, self.test_server)
  235.  
  236.     
  237.     def resizeEvent(self, ev):
  238.         MainWindow.resizeEvent(self, ev)
  239.         self.search.setMaximumWidth(self.width() - 150)
  240.  
  241.     
  242.     def add_spare_server(self, *args):
  243.         self.spare_servers.append(Server(limit = int(config['worker_limit'] / 2)))
  244.  
  245.     
  246.     def spare_server(self):
  247.         if not hasattr(self, '__spare_server_property_limiter'):
  248.             self._Main__spare_server_property_limiter = True
  249.             return None
  250.         
  251.         try:
  252.             QTimer.singleShot(1000, self.add_spare_server)
  253.             return self.spare_servers.pop()
  254.         except:
  255.             hasattr(self, '__spare_server_property_limiter')
  256.  
  257.  
  258.     spare_server = property(spare_server)
  259.     
  260.     def no_op(self, *args):
  261.         pass
  262.  
  263.     
  264.     def system_tray_icon_activated(self, r):
  265.         if r == QSystemTrayIcon.Trigger:
  266.             if self.isVisible():
  267.                 self.hide_windows()
  268.             else:
  269.                 self.show_windows()
  270.         
  271.  
  272.     
  273.     def hide_windows(self):
  274.         for window in QApplication.topLevelWidgets():
  275.             if isinstance(window, (MainWindow, QDialog)) and window.isVisible():
  276.                 window.hide()
  277.                 setattr(window, '__systray_minimized', True)
  278.                 continue
  279.         
  280.  
  281.     
  282.     def show_windows(self):
  283.         for window in QApplication.topLevelWidgets():
  284.             if getattr(window, '__systray_minimized', False):
  285.                 window.show()
  286.                 setattr(window, '__systray_minimized', False)
  287.                 continue
  288.         
  289.  
  290.     
  291.     def test_server(self, *args):
  292.         if self.content_server is not None and self.content_server.exception is not None:
  293.             error_dialog(self, _('Failed to start content server'), unicode(self.content_server.exception)).exec_()
  294.         
  295.  
  296.     
  297.     def another_instance_wants_to_talk(self):
  298.         
  299.         try:
  300.             msg = self.listener.queue.get_nowait()
  301.         except Empty:
  302.             return None
  303.  
  304.         if msg.startswith('launched:'):
  305.             argv = eval(msg[len('launched:'):])
  306.             if len(argv) > 1:
  307.                 path = os.path.abspath(argv[1])
  308.                 if os.access(path, os.R_OK):
  309.                     self.iactions['Add Books'].add_filesystem_book(path)
  310.                 
  311.             
  312.             self.setWindowState(self.windowState() & ~(Qt.WindowMinimized) | Qt.WindowActive)
  313.             self.show_windows()
  314.             self.raise_()
  315.             self.activateWindow()
  316.         elif msg.startswith('refreshdb:'):
  317.             self.library_view.model().refresh()
  318.             self.library_view.model().research()
  319.         else:
  320.             print msg
  321.  
  322.     
  323.     def current_view(self):
  324.         idx = self.stack.currentIndex()
  325.         if idx == 0:
  326.             return self.library_view
  327.         if idx == 1:
  328.             return self.memory_view
  329.         if idx == 2:
  330.             return self.card_a_view
  331.         if idx == 3:
  332.             return self.card_b_view
  333.  
  334.     
  335.     def booklists(self):
  336.         return (self.memory_view.model().db, self.card_a_view.model().db, self.card_b_view.model().db)
  337.  
  338.     
  339.     def library_moved(self, newloc):
  340.         if newloc is None:
  341.             return None
  342.         
  343.         try:
  344.             olddb = self.library_view.model().db
  345.         except:
  346.             newloc is None
  347.             olddb = None
  348.  
  349.         db = LibraryDatabase2(newloc)
  350.         if self.content_server is not None:
  351.             self.content_server.set_database(db)
  352.         
  353.         self.library_path = newloc
  354.         self.book_on_device(None, reset = True)
  355.         db.set_book_on_device_func(self.book_on_device)
  356.         self.library_view.set_database(db)
  357.         self.tags_view.set_database(db, self.tag_match, self.sort_by)
  358.         self.library_view.model().set_book_on_device_func(self.book_on_device)
  359.         self.status_bar.clear_message()
  360.         self.search.clear_to_help()
  361.         self.saved_search.clear_to_help()
  362.         self.book_details.reset_info()
  363.         self.library_view.model().count_changed()
  364.         prefs['library_path'] = self.library_path
  365.         db = self.library_view.model().db
  366.         for action in self.iactions.values():
  367.             action.library_changed(db)
  368.         
  369.         self.set_window_title()
  370.         self.apply_named_search_restriction('')
  371.         self.saved_searches_changed()
  372.         self.apply_named_search_restriction(db.prefs['gui_restriction'])
  373.         if olddb is not None:
  374.             
  375.             try:
  376.                 olddb.conn.close()
  377.             import traceback
  378.             traceback.print_exc()
  379.  
  380.         
  381.         if self.device_connected:
  382.             self.set_books_in_library(self.booklists(), reset = True)
  383.             self.refresh_ondevice()
  384.             self.memory_view.reset()
  385.             self.card_a_view.reset()
  386.             self.card_b_view.reset()
  387.         
  388.  
  389.     
  390.     def set_window_title(self):
  391.         self.setWindowTitle(__appname__ + u' - ||%s||' % self.iactions['Choose Library'].library_name())
  392.  
  393.     
  394.     def location_selected(self, location):
  395.         if location == 'library':
  396.             pass
  397.         elif location == 'main':
  398.             pass
  399.         elif location == 'carda':
  400.             pass
  401.         
  402.         page = 3
  403.         self.stack.setCurrentIndex(page)
  404.         self.book_details.reset_info()
  405.         for x in ('tb', 'cb'):
  406.             splitter = getattr(self, x + '_splitter')
  407.             splitter.button.setEnabled(location == 'library')
  408.         
  409.         for action in self.iactions.values():
  410.             action.location_selected(location)
  411.         
  412.         if location == 'library':
  413.             self.search_restriction.setEnabled(True)
  414.         else:
  415.             self.search_restriction.setEnabled(False)
  416.             self.current_view().reset()
  417.         self.set_number_of_books_shown()
  418.  
  419.     
  420.     def job_exception(self, job):
  421.         if not hasattr(self, '_modeless_dialogs'):
  422.             self._modeless_dialogs = []
  423.         
  424.         if self.isVisible():
  425.             for x in list(self._modeless_dialogs):
  426.                 if not x.isVisible():
  427.                     self._modeless_dialogs.remove(x)
  428.                     continue
  429.             
  430.         
  431.         
  432.         try:
  433.             if 'calibre.ebooks.DRMError' in job.details:
  434.                 d = error_dialog(self, _('Conversion Error'), _('<p>Could not convert: %s<p>It is a <a href="%s">DRM</a>ed book. You must first remove the DRM using third party tools.') % (job.description.split(':')[-1], 'http://bugs.calibre-ebook.com/wiki/DRM'))
  435.                 d.setModal(False)
  436.                 d.show()
  437.                 self._modeless_dialogs.append(d)
  438.                 return None
  439.             if 'calibre.web.feeds.input.RecipeDisabled' in job.details:
  440.                 msg = job.details
  441.                 msg = msg[msg.find('calibre.web.feeds.input.RecipeDisabled:'):]
  442.                 msg = msg.partition(':')[-1]
  443.                 d = error_dialog(self, _('Recipe Disabled'), '<p>%s</p>' % msg)
  444.                 d.setModal(False)
  445.                 d.show()
  446.                 self._modeless_dialogs.append(d)
  447.                 return None
  448.         except:
  449.             pass
  450.  
  451.         if job.killed:
  452.             return None
  453.         
  454.         try:
  455.             prints(job.details, file = sys.stderr)
  456.         except:
  457.             job.killed
  458.  
  459.         d = error_dialog(self, _('Conversion Error'), _('<b>Failed</b>') + ': ' + unicode(job.description), det_msg = job.details)
  460.         d.setModal(False)
  461.         d.show()
  462.         self._modeless_dialogs.append(d)
  463.  
  464.     
  465.     def read_settings(self):
  466.         geometry = config['main_window_geometry']
  467.         if geometry is not None:
  468.             self.restoreGeometry(geometry)
  469.         
  470.         self.read_layout_settings()
  471.  
  472.     
  473.     def write_settings(self):
  474.         config.set('main_window_geometry', self.saveGeometry())
  475.         dynamic.set('sort_history', self.library_view.model().sort_history)
  476.         self.save_layout_state()
  477.  
  478.     
  479.     def quit(self, checked = True, restart = False):
  480.         if not self.confirm_quit():
  481.             return None
  482.         
  483.         try:
  484.             self.shutdown()
  485.         except:
  486.             self.confirm_quit()
  487.  
  488.         self.restart_after_quit = restart
  489.         QApplication.instance().quit()
  490.  
  491.     
  492.     def donate(self, *args):
  493.         BUTTON = '\n        <form action="https://www.paypal.com/cgi-bin/webscr" method="post">\n            <input type="hidden" name="cmd" value="_s-xclick" />\n            <input type="hidden" name="hosted_button_id" value="3029467" />\n            <input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="Donate to support calibre development" />\n            <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1" />\n        </form>\n        '
  494.         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. Your donation helps keep calibre development going.')
  495.         HTML = u'\n        <html>\n            <head>\n                <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />\n                <title>Donate to support calibre</title>\n            </head>\n            <body style="background:white">\n                <div><a href="http://calibre-ebook.com"><img style="border:0px"\n                src="file://%s" alt="calibre" /></a></div>\n                <p>Calibre %s</p>\n                %s\n            </body>\n        </html>\n        ' % (P('content_server/calibre_banner.png').replace(os.sep, '/'), MSG, BUTTON)
  496.         pt = PersistentTemporaryFile('_donate.htm')
  497.         pt.write(HTML.encode('utf-8'))
  498.         pt.close()
  499.         open_local_file(pt.name)
  500.  
  501.     
  502.     def confirm_quit(self):
  503.         if self.job_manager.has_jobs():
  504.             msg = _('There are active jobs. Are you sure you want to quit?')
  505.             if self.job_manager.has_device_jobs():
  506.                 msg = '<p>' + __appname__ + _(' is communicating with the device!<br>\n                      Quitting may cause corruption on the device.<br>\n                      Are you sure you want to quit?') + '</p>'
  507.             
  508.             d = QMessageBox(QMessageBox.Warning, _('WARNING: Active jobs'), msg, QMessageBox.Yes | QMessageBox.No, self)
  509.             d.setIconPixmap(QPixmap(I('dialog_warning.png')))
  510.             d.setDefaultButton(QMessageBox.No)
  511.             if d.exec_() != QMessageBox.Yes:
  512.                 return False
  513.         
  514.         return True
  515.  
  516.     
  517.     def shutdown(self, write_settings = True):
  518.         
  519.         try:
  520.             db = self.library_view.model().db
  521.             cf = db.clean
  522.         except:
  523.             pass
  524.  
  525.         cf()
  526.         db.prefs['field_metadata'] = db.field_metadata.all_metadata()
  527.         db.commit_dirty_cache()
  528.         if DEBUG and db.gm_count > 0:
  529.             print 'get_metadata cache: {0:d} calls, {1:4.2f}% misses'.format(db.gm_count, db.gm_missed * 100 / db.gm_count)
  530.         
  531.         for action in self.iactions.values():
  532.             if not action.shutting_down():
  533.                 return None
  534.         
  535.         if write_settings:
  536.             self.write_settings()
  537.         
  538.         self.check_messages_timer.stop()
  539.         self.update_checker.terminate()
  540.         self.listener.close()
  541.         self.job_manager.server.close()
  542.         while self.spare_servers:
  543.             self.spare_servers.pop().close()
  544.         self.device_manager.keep_going = False
  545.         cc = self.library_view.model().cover_cache
  546.         if cc is not None:
  547.             cc.stop()
  548.         
  549.         mb = self.library_view.model().metadata_backup
  550.         if mb is not None:
  551.             mb.stop()
  552.         
  553.         self.hide_windows()
  554.         self.emailer.stop()
  555.         
  556.         try:
  557.             
  558.             try:
  559.                 if self.content_server is not None:
  560.                     s = self.content_server
  561.                     self.content_server = None
  562.                     s.exit()
  563.             except:
  564.                 pass
  565.  
  566.         except KeyboardInterrupt:
  567.             pass
  568.  
  569.         time.sleep(2)
  570.         if mb is not None:
  571.             mb.flush()
  572.         
  573.         self.hide_windows()
  574.         return True
  575.  
  576.     
  577.     def run_wizard(self, *args):
  578.         if self.confirm_quit():
  579.             self.run_wizard_b4_shutdown = True
  580.             self.restart_after_quit = True
  581.             
  582.             try:
  583.                 self.shutdown(write_settings = False)
  584.             except:
  585.                 pass
  586.  
  587.             QApplication.instance().quit()
  588.         
  589.  
  590.     
  591.     def closeEvent(self, e):
  592.         self.write_settings()
  593.         if self.system_tray_icon.isVisible():
  594.             if not dynamic['systray_msg'] and not isosx:
  595.                 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_()
  596.                 dynamic['systray_msg'] = True
  597.             
  598.             self.hide_windows()
  599.             e.ignore()
  600.         elif self.confirm_quit():
  601.             
  602.             try:
  603.                 self.shutdown(write_settings = False)
  604.             except:
  605.                 pass
  606.  
  607.             e.accept()
  608.         else:
  609.             e.ignore()
  610.  
  611.  
  612.