home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2010 November / maximum-cd-2010-11.iso / DiscContents / calibre-0.7.13.msi / file_1319 (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2010-08-06  |  23.5 KB  |  594 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, QAction, QMessageBox, QHelpEvent
  16. from calibre import prints
  17. from calibre.constants import __appname__, isosx
  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.gui2 import error_dialog, GetMetadata, open_local_file, gprefs, max_available_height, config, info_dialog, Dispatcher
  22. from calibre.gui2.cover_flow import CoverFlowMixin
  23. from calibre.gui2.widgets import ProgressIndicator
  24. from calibre.gui2.update import UpdateMixin
  25. from calibre.gui2.main_window import MainWindow
  26. from calibre.gui2.layout import MainWindowMixin
  27. from calibre.gui2.device import DeviceMixin
  28. from calibre.gui2.jobs import JobManager, JobsDialog, JobsButton
  29. from calibre.gui2.dialogs.config import ConfigDialog
  30. from calibre.gui2.dialogs.book_info import BookInfo
  31. from calibre.library.database2 import LibraryDatabase2
  32. from calibre.gui2.init import LibraryViewMixin, LayoutMixin
  33. from calibre.gui2.search_box import SearchBoxMixin, SavedSearchBoxMixin
  34. from calibre.gui2.search_restriction_mixin import SearchRestrictionMixin
  35. from calibre.gui2.tag_view import TagBrowserMixin
  36. from calibre.gui2.actions import AnnotationsAction, AddAction, DeleteAction, EditMetadataAction, SaveToDiskAction, GenerateCatalogAction, FetchNewsAction, ConvertAction, ViewAction
  37.  
  38. class Listener(Thread):
  39.     
  40.     def __init__(self, listener):
  41.         Thread.__init__(self)
  42.         self.daemon = True
  43.         self.listener = listener
  44.         self.queue = Queue()
  45.         self._run = True
  46.         self.start()
  47.  
  48.     
  49.     def run(self):
  50.         while self._run:
  51.             
  52.             try:
  53.                 conn = self.listener.accept()
  54.                 msg = conn.recv()
  55.                 self.queue.put(msg)
  56.             continue
  57.             continue
  58.             continue
  59.  
  60.  
  61.     
  62.     def close(self):
  63.         self._run = False
  64.         
  65.         try:
  66.             self.listener.close()
  67.         except:
  68.             pass
  69.  
  70.  
  71.  
  72.  
  73. class SystemTrayIcon(QSystemTrayIcon):
  74.     tooltip_requested = pyqtSignal(object)
  75.     
  76.     def __init__(self, icon, parent):
  77.         QSystemTrayIcon.__init__(self, icon, parent)
  78.  
  79.     
  80.     def event(self, ev):
  81.         if ev.type() == ev.ToolTip:
  82.             evh = QHelpEvent(ev)
  83.             self.tooltip_requested.emit((self, evh.globalPos()))
  84.             return True
  85.         return QSystemTrayIcon.event(self, ev)
  86.  
  87.  
  88.  
  89. class Main(MainWindow, MainWindowMixin, DeviceMixin, TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, SearchBoxMixin, SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin, AnnotationsAction, AddAction, DeleteAction, EditMetadataAction, SaveToDiskAction, GenerateCatalogAction, FetchNewsAction, ConvertAction, ViewAction):
  90.     
  91.     def __init__(self, opts, parent = None):
  92.         MainWindow.__init__(self, opts, parent)
  93.         self.opts = opts
  94.  
  95.     
  96.     def initialize(self, library_path, db, listener, actions):
  97.         opts = self.opts
  98.         (self.preferences_action, self.quit_action) = actions
  99.         self.library_path = library_path
  100.         self.content_server = None
  101.         self.spare_servers = []
  102.         self.must_restart_before_config = False
  103.         fontconfig = fontconfig
  104.         import calibre.utils.fonts
  105.         self.fc = fontconfig
  106.         self.listener = Listener(listener)
  107.         self.check_messages_timer = QTimer()
  108.         self.connect(self.check_messages_timer, SIGNAL('timeout()'), self.another_instance_wants_to_talk)
  109.         self.check_messages_timer.start(1000)
  110.         MainWindowMixin.__init__(self, db)
  111.         self.job_manager = JobManager()
  112.         self.jobs_dialog = JobsDialog(self, self.job_manager)
  113.         self.jobs_button = JobsButton(horizontal = True)
  114.         self.jobs_button.initialize(self.jobs_dialog, self.job_manager)
  115.         LayoutMixin.__init__(self)
  116.         DeviceMixin.__init__(self)
  117.         self.restriction_count_of_books_in_view = 0
  118.         self.restriction_count_of_books_in_library = 0
  119.         self.restriction_in_effect = False
  120.         self.progress_indicator = ProgressIndicator(self)
  121.         self.progress_indicator.pos = (0, 20)
  122.         self.verbose = opts.verbose
  123.         self.get_metadata = GetMetadata()
  124.         self.upload_memory = { }
  125.         self.delete_memory = { }
  126.         self.conversion_jobs = { }
  127.         self.persistent_files = []
  128.         self.metadata_dialogs = []
  129.         self.default_thumbnail = None
  130.         self.tb_wrapper = textwrap.TextWrapper(width = 40)
  131.         self.viewers = collections.deque()
  132.         self.system_tray_icon = SystemTrayIcon(QIcon(I('library.png')), self)
  133.         self.system_tray_icon.setToolTip('calibre')
  134.         self.system_tray_icon.tooltip_requested.connect(self.job_manager.show_tooltip)
  135.         if not config['systray_icon']:
  136.             self.system_tray_icon.hide()
  137.         else:
  138.             self.system_tray_icon.show()
  139.         self.system_tray_menu = QMenu(self)
  140.         self.restore_action = self.system_tray_menu.addAction(QIcon(I('page.svg')), _('&Restore'))
  141.         self.donate_action = self.system_tray_menu.addAction(QIcon(I('donate.svg')), _('&Donate to support calibre'))
  142.         self.donate_button.setDefaultAction(self.donate_action)
  143.         self.donate_button.setStatusTip(self.donate_button.toolTip())
  144.         self.eject_action = self.system_tray_menu.addAction(QIcon(I('eject.svg')), _('&Eject connected device'))
  145.         self.eject_action.setEnabled(False)
  146.         self.addAction(self.quit_action)
  147.         self.action_restart = QAction(_('&Restart'), self)
  148.         self.addAction(self.action_restart)
  149.         self.system_tray_menu.addAction(self.quit_action)
  150.         self.quit_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Q))
  151.         self.action_restart.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_R))
  152.         self.action_show_book_details.setShortcut(QKeySequence(Qt.Key_I))
  153.         self.addAction(self.action_show_book_details)
  154.         self.system_tray_icon.setContextMenu(self.system_tray_menu)
  155.         self.connect(self.quit_action, SIGNAL('triggered(bool)'), self.quit)
  156.         self.connect(self.donate_action, SIGNAL('triggered(bool)'), self.donate)
  157.         self.connect(self.restore_action, SIGNAL('triggered()'), self.show_windows)
  158.         self.connect(self.action_show_book_details, SIGNAL('triggered(bool)'), self.show_book_info)
  159.         self.connect(self.action_restart, SIGNAL('triggered()'), self.restart)
  160.         self.connect(self.system_tray_icon, SIGNAL('activated(QSystemTrayIcon::ActivationReason)'), self.system_tray_icon_activated)
  161.         QTimer.singleShot(1000, self.add_spare_server)
  162.         self.location_manager.location_selected.connect(self.location_selected)
  163.         self.location_manager.unmount_device.connect(self.device_manager.umount_device)
  164.         self.eject_action.triggered.connect(self.device_manager.umount_device)
  165.         UpdateMixin.__init__(self, opts)
  166.         SavedSearchBoxMixin.__init__(self, db)
  167.         SearchBoxMixin.__init__(self)
  168.         LibraryViewMixin.__init__(self, db)
  169.         self.show()
  170.         if self.system_tray_icon.isVisible() and opts.start_in_tray:
  171.             self.hide_windows()
  172.         
  173.         for t in (self.tool_bar,):
  174.             self.library_view.model().count_changed_signal.connect(t.count_changed)
  175.         
  176.         if not gprefs.get('quick_start_guide_added', False):
  177.             MetaInformation = MetaInformation
  178.             import calibre.ebooks.metadata
  179.             mi = MetaInformation(_('Calibre Quick Start Guide'), [
  180.                 'John Schember'])
  181.             mi.author_sort = 'Schember, John'
  182.             mi.comments = 'A guide to get you up and running with calibre'
  183.             mi.publisher = 'calibre'
  184.             self.library_view.model().add_books([
  185.                 P('quick_start.epub')], [
  186.                 'epub'], [
  187.                 mi])
  188.             gprefs['quick_start_guide_added'] = True
  189.             self.library_view.model().books_added(1)
  190.             if hasattr(self, 'db_images'):
  191.                 self.db_images.reset()
  192.             
  193.         
  194.         self.library_view.model().count_changed()
  195.         self.tool_bar.database_changed(self.library_view.model().db)
  196.         self.library_view.model().database_changed.connect(self.tool_bar.database_changed, type = Qt.QueuedConnection)
  197.         TagBrowserMixin.__init__(self, db)
  198.         SearchRestrictionMixin.__init__(self)
  199.         CoverFlowMixin.__init__(self)
  200.         self._calculated_available_height = min(max_available_height() - 15, self.height())
  201.         self.resize(self.width(), self._calculated_available_height)
  202.         if config['autolaunch_server']:
  203.             self.start_content_server()
  204.         
  205.         self.keyboard_interrupt.connect(self.quit, type = Qt.QueuedConnection)
  206.         AddAction.__init__(self)
  207.         self.read_settings()
  208.         self.finalize_layout()
  209.         self.donate_button.start_animation()
  210.         self.scheduler.delete_old_news.connect(self.library_view.model().delete_books_by_id, type = Qt.QueuedConnection)
  211.  
  212.     
  213.     def start_content_server(self):
  214.         start_threaded_server = start_threaded_server
  215.         import calibre.library.server.main
  216.         server_config = server_config
  217.         import calibre.library.server
  218.         self.content_server = start_threaded_server(self.library_view.model().db, server_config().parse())
  219.         self.content_server.state_callback = Dispatcher(self.content_server_state_changed)
  220.         self.content_server.state_callback(True)
  221.         self.test_server_timer = QTimer.singleShot(10000, self.test_server)
  222.  
  223.     
  224.     def resizeEvent(self, ev):
  225.         MainWindow.resizeEvent(self, ev)
  226.         self.search.setMaximumWidth(self.width() - 150)
  227.  
  228.     
  229.     def add_spare_server(self, *args):
  230.         self.spare_servers.append(Server(limit = int(config['worker_limit'] / 2)))
  231.  
  232.     
  233.     def spare_server(self):
  234.         if not hasattr(self, '__spare_server_property_limiter'):
  235.             self._Main__spare_server_property_limiter = True
  236.             return None
  237.         
  238.         try:
  239.             QTimer.singleShot(1000, self.add_spare_server)
  240.             return self.spare_servers.pop()
  241.         except:
  242.             hasattr(self, '__spare_server_property_limiter')
  243.  
  244.  
  245.     spare_server = property(spare_server)
  246.     
  247.     def no_op(self, *args):
  248.         pass
  249.  
  250.     
  251.     def system_tray_icon_activated(self, r):
  252.         if r == QSystemTrayIcon.Trigger:
  253.             if self.isVisible():
  254.                 self.hide_windows()
  255.             else:
  256.                 self.show_windows()
  257.         
  258.  
  259.     
  260.     def hide_windows(self):
  261.         for window in QApplication.topLevelWidgets():
  262.             if isinstance(window, (MainWindow, QDialog)) and window.isVisible():
  263.                 window.hide()
  264.                 setattr(window, '__systray_minimized', True)
  265.                 continue
  266.         
  267.  
  268.     
  269.     def show_windows(self):
  270.         for window in QApplication.topLevelWidgets():
  271.             if getattr(window, '__systray_minimized', False):
  272.                 window.show()
  273.                 setattr(window, '__systray_minimized', False)
  274.                 continue
  275.         
  276.  
  277.     
  278.     def test_server(self, *args):
  279.         if self.content_server is not None and self.content_server.exception is not None:
  280.             error_dialog(self, _('Failed to start content server'), unicode(self.content_server.exception)).exec_()
  281.         
  282.  
  283.     
  284.     def another_instance_wants_to_talk(self):
  285.         
  286.         try:
  287.             msg = self.listener.queue.get_nowait()
  288.         except Empty:
  289.             return None
  290.  
  291.         if msg.startswith('launched:'):
  292.             argv = eval(msg[len('launched:'):])
  293.             if len(argv) > 1:
  294.                 path = os.path.abspath(argv[1])
  295.                 if os.access(path, os.R_OK):
  296.                     self.add_filesystem_book(path)
  297.                 
  298.             
  299.             self.setWindowState(self.windowState() & ~(Qt.WindowMinimized) | Qt.WindowActive)
  300.             self.show_windows()
  301.             self.raise_()
  302.             self.activateWindow()
  303.         elif msg.startswith('refreshdb:'):
  304.             self.library_view.model().refresh()
  305.             self.library_view.model().research()
  306.         else:
  307.             print msg
  308.  
  309.     
  310.     def current_view(self):
  311.         idx = self.stack.currentIndex()
  312.         if idx == 0:
  313.             return self.library_view
  314.         if idx == 1:
  315.             return self.memory_view
  316.         if idx == 2:
  317.             return self.card_a_view
  318.         if idx == 3:
  319.             return self.card_b_view
  320.  
  321.     
  322.     def booklists(self):
  323.         return (self.memory_view.model().db, self.card_a_view.model().db, self.card_b_view.model().db)
  324.  
  325.     
  326.     def do_config(self, checked = False, initial_category = 'general'):
  327.         if self.job_manager.has_jobs():
  328.             d = error_dialog(self, _('Cannot configure'), _('Cannot configure while there are running jobs.'))
  329.             d.exec_()
  330.             return None
  331.         if self.must_restart_before_config:
  332.             d = error_dialog(self, _('Cannot configure'), _('Cannot configure before calibre is restarted.'))
  333.             d.exec_()
  334.             return None
  335.         d = ConfigDialog(self, self.library_view, server = self.content_server, initial_category = initial_category)
  336.         d.exec_()
  337.         self.content_server = d.server
  338.         if d.result() == d.Accepted:
  339.             self.read_toolbar_settings()
  340.             self.search.search_as_you_type(config['search_as_you_type'])
  341.             self.save_menu.actions()[2].setText(_('Save only %s format to disk') % prefs['output_format'].upper())
  342.             self.save_menu.actions()[3].setText(_('Save only %s format to disk in a single directory') % prefs['output_format'].upper())
  343.             self.tags_view.set_new_model()
  344.             self.tags_view.recount()
  345.             self.create_device_menu()
  346.             self.set_device_menu_items_state(bool(self.device_connected))
  347.             self.tool_bar.apply_settings()
  348.         
  349.  
  350.     
  351.     def library_moved(self, newloc):
  352.         if newloc is None:
  353.             return None
  354.         db = LibraryDatabase2(newloc)
  355.         self.library_path = newloc
  356.         self.book_on_device(None, reset = True)
  357.         db.set_book_on_device_func(self.book_on_device)
  358.         self.library_view.set_database(db)
  359.         self.tags_view.set_database(db, self.tag_match, self.sort_by)
  360.         self.library_view.model().set_book_on_device_func(self.book_on_device)
  361.         self.status_bar.clear_message()
  362.         self.search.clear_to_help()
  363.         self.saved_search.clear_to_help()
  364.         self.book_details.reset_info()
  365.         self.library_view.model().count_changed()
  366.         self.scheduler.database_changed(db)
  367.         prefs['library_path'] = self.library_path
  368.  
  369.     
  370.     def show_book_info(self, *args):
  371.         if self.current_view() is not self.library_view:
  372.             error_dialog(self, _('No detailed info available'), _('No detailed information is available for books on the device.')).exec_()
  373.             return None
  374.         index = self.library_view.currentIndex()
  375.         if index.isValid():
  376.             BookInfo(self, self.library_view, index).show()
  377.         
  378.  
  379.     
  380.     def location_selected(self, location):
  381.         if location == 'library':
  382.             pass
  383.         elif location == 'main':
  384.             pass
  385.         elif location == 'carda':
  386.             pass
  387.         
  388.         page = 3
  389.         self.stack.setCurrentIndex(page)
  390.         self.book_details.reset_info()
  391.         for x in ('tb', 'cb'):
  392.             splitter = getattr(self, x + '_splitter')
  393.             splitter.button.setEnabled(location == 'library')
  394.         
  395.         if location == 'library':
  396.             self.action_edit.setEnabled(True)
  397.             self.action_merge.setEnabled(True)
  398.             self.action_convert.setEnabled(True)
  399.             self.view_menu.actions()[1].setEnabled(True)
  400.             self.action_open_containing_folder.setEnabled(True)
  401.             self.action_sync.setEnabled(True)
  402.             self.search_restriction.setEnabled(True)
  403.             for action in list(self.delete_menu.actions())[1:]:
  404.                 action.setEnabled(True)
  405.             
  406.         else:
  407.             self.action_edit.setEnabled(False)
  408.             self.action_merge.setEnabled(False)
  409.             self.action_convert.setEnabled(False)
  410.             self.view_menu.actions()[1].setEnabled(False)
  411.             self.action_open_containing_folder.setEnabled(False)
  412.             self.action_sync.setEnabled(False)
  413.             self.search_restriction.setEnabled(False)
  414.             for action in list(self.delete_menu.actions())[1:]:
  415.                 action.setEnabled(False)
  416.             
  417.             self.current_view().reset()
  418.         self.set_number_of_books_shown()
  419.  
  420.     
  421.     def job_exception(self, job):
  422.         if not hasattr(self, '_modeless_dialogs'):
  423.             self._modeless_dialogs = []
  424.         
  425.         if self.isVisible():
  426.             for x in list(self._modeless_dialogs):
  427.                 if not x.isVisible():
  428.                     self._modeless_dialogs.remove(x)
  429.                     continue
  430.             
  431.         
  432.         
  433.         try:
  434.             if 'calibre.ebooks.DRMError' in job.details:
  435.                 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'))
  436.                 d.setModal(False)
  437.                 d.show()
  438.                 self._modeless_dialogs.append(d)
  439.                 return None
  440.             if 'calibre.web.feeds.input.RecipeDisabled' in job.details:
  441.                 msg = job.details
  442.                 msg = msg[msg.find('calibre.web.feeds.input.RecipeDisabled:'):]
  443.                 msg = msg.partition(':')[-1]
  444.                 d = error_dialog(self, _('Recipe Disabled'), '<p>%s</p>' % msg)
  445.                 d.setModal(False)
  446.                 d.show()
  447.                 self._modeless_dialogs.append(d)
  448.                 return None
  449.         except:
  450.             pass
  451.  
  452.         if job.killed:
  453.             return None
  454.         
  455.         try:
  456.             prints(job.details, file = sys.stderr)
  457.         except:
  458.             job.killed
  459.  
  460.         d = error_dialog(self, _('Conversion Error'), _('<b>Failed</b>') + ': ' + unicode(job.description), det_msg = job.details)
  461.         d.setModal(False)
  462.         d.show()
  463.         self._modeless_dialogs.append(d)
  464.  
  465.     
  466.     def read_settings(self):
  467.         geometry = config['main_window_geometry']
  468.         if geometry is not None:
  469.             self.restoreGeometry(geometry)
  470.         
  471.         self.read_toolbar_settings()
  472.         self.read_layout_settings()
  473.  
  474.     
  475.     def write_settings(self):
  476.         config.set('main_window_geometry', self.saveGeometry())
  477.         dynamic.set('sort_history', self.library_view.model().sort_history)
  478.         self.save_layout_state()
  479.  
  480.     
  481.     def restart(self):
  482.         self.quit(restart = True)
  483.  
  484.     
  485.     def quit(self, checked = True, restart = False):
  486.         if not self.confirm_quit():
  487.             return None
  488.         
  489.         try:
  490.             self.shutdown()
  491.         except:
  492.             self.confirm_quit()
  493.  
  494.         self.restart_after_quit = restart
  495.         QApplication.instance().quit()
  496.  
  497.     
  498.     def donate(self, *args):
  499.         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        '
  500.         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.')
  501.         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)
  502.         pt = PersistentTemporaryFile('_donate.htm')
  503.         pt.write(HTML.encode('utf-8'))
  504.         pt.close()
  505.         open_local_file(pt.name)
  506.  
  507.     
  508.     def confirm_quit(self):
  509.         if self.job_manager.has_jobs():
  510.             msg = _('There are active jobs. Are you sure you want to quit?')
  511.             if self.job_manager.has_device_jobs():
  512.                 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>'
  513.             
  514.             d = QMessageBox(QMessageBox.Warning, _('WARNING: Active jobs'), msg, QMessageBox.Yes | QMessageBox.No, self)
  515.             d.setIconPixmap(QPixmap(I('dialog_warning.svg')))
  516.             d.setDefaultButton(QMessageBox.No)
  517.             if d.exec_() != QMessageBox.Yes:
  518.                 return False
  519.         
  520.         return True
  521.  
  522.     
  523.     def shutdown(self, write_settings = True):
  524.         if write_settings:
  525.             self.write_settings()
  526.         
  527.         self.check_messages_timer.stop()
  528.         self.update_checker.terminate()
  529.         self.listener.close()
  530.         self.job_manager.server.close()
  531.         while self.spare_servers:
  532.             self.spare_servers.pop().close()
  533.         self.device_manager.keep_going = False
  534.         cc = self.library_view.model().cover_cache
  535.         if cc is not None:
  536.             cc.stop()
  537.         
  538.         self.hide_windows()
  539.         self.emailer.stop()
  540.         
  541.         try:
  542.             
  543.             try:
  544.                 if self.content_server is not None:
  545.                     s = self.content_server
  546.                     self.content_server = None
  547.                     s.exit()
  548.             except:
  549.                 pass
  550.  
  551.             time.sleep(2)
  552.         except KeyboardInterrupt:
  553.             pass
  554.  
  555.         self.hide_windows()
  556.         return True
  557.  
  558.     
  559.     def run_wizard(self, *args):
  560.         if self.confirm_quit():
  561.             self.run_wizard_b4_shutdown = True
  562.             self.restart_after_quit = True
  563.             
  564.             try:
  565.                 self.shutdown(write_settings = False)
  566.             except:
  567.                 pass
  568.  
  569.             QApplication.instance().quit()
  570.         
  571.  
  572.     
  573.     def closeEvent(self, e):
  574.         self.write_settings()
  575.         if self.system_tray_icon.isVisible():
  576.             if not dynamic['systray_msg'] and not isosx:
  577.                 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_()
  578.                 dynamic['systray_msg'] = True
  579.             
  580.             self.hide_windows()
  581.             e.ignore()
  582.         elif self.confirm_quit():
  583.             
  584.             try:
  585.                 self.shutdown(write_settings = False)
  586.             except:
  587.                 pass
  588.  
  589.             e.accept()
  590.         else:
  591.             e.ignore()
  592.  
  593.  
  594.