diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 22730ba53f..aa0948e3f8 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -613,6 +613,29 @@ class ActionSaveToDisk(InterfaceActionBase): name = 'Save To Disk' actual_plugin = 'calibre.gui2.actions.save_to_disk:SaveToDiskAction' +class ActionShowBookDetails(InterfaceActionBase): + name = 'Show Book Details' + actual_plugin = 'calibre.gui2.actions.show_book_details:ShowBookDetailsAction' + +class ActionRestart(InterfaceActionBase): + name = 'Restart' + actual_plugin = 'calibre.gui2.actions.restart:RestartAction' + +class ActionOpenFolder(InterfaceActionBase): + name = 'OpenFolder' + actual_plugin = 'calibre.gui2.actions.open:OpenFolderAction' + +class ActionSendToDevice(InterfaceActionBase): + name = 'Send To Device' + actual_plugin = 'calibre.gui2.actions.device:SendToDeviceAction' + +class ActionConnectShare(InterfaceActionBase): + name = 'Connect Share' + actual_plugin = 'calibre.gui2.actions.device:ConnectShareAction' + + plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog, ActionConvert, ActionDelete, ActionEditMetadata, ActionView, - ActionFetchNews, ActionSaveToDisk] + ActionFetchNews, ActionSaveToDisk, ActionShowBookDetails, + ActionRestart, ActionOpenFolder, ActionConnectShare, + ActionSendToDevice, ] diff --git a/src/calibre/gui2/actions/__init__.py b/src/calibre/gui2/actions/__init__.py index 4798828074..d5c6a0cf7e 100644 --- a/src/calibre/gui2/actions/__init__.py +++ b/src/calibre/gui2/actions/__init__.py @@ -32,6 +32,7 @@ class InterfaceAction(QObject): def do_genesis(self): self.Dispatcher = partial(Dispatcher, parent=self) self.create_action() + self.gui.addAction(self.qaction) self.genesis() def create_action(self, spec=None, attr='qaction'): diff --git a/src/calibre/gui2/actions/device.py b/src/calibre/gui2/actions/device.py new file mode 100644 index 0000000000..f1cfd84235 --- /dev/null +++ b/src/calibre/gui2/actions/device.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +from functools import partial + +from PyQt4.Qt import QToolButton, QMenu, pyqtSignal, QIcon + +from calibre.gui2.actions import InterfaceAction +from calibre.utils.smtp import config as email_config + +class ShareConnMenu(QMenu): # {{{ + + connect_to_folder = pyqtSignal() + connect_to_itunes = pyqtSignal() + config_email = pyqtSignal() + toggle_server = pyqtSignal() + + def __init__(self, parent=None): + QMenu.__init__(self, parent) + mitem = self.addAction(QIcon(I('devices/folder.svg')), _('Connect to folder')) + mitem.setEnabled(True) + mitem.triggered.connect(lambda x : self.connect_to_folder.emit()) + self.connect_to_folder_action = mitem + mitem = self.addAction(QIcon(I('devices/itunes.png')), + _('Connect to iTunes')) + mitem.setEnabled(True) + mitem.triggered.connect(lambda x : self.connect_to_itunes.emit()) + self.connect_to_itunes_action = mitem + self.addSeparator() + self.toggle_server_action = \ + self.addAction(QIcon(I('network-server.svg')), + _('Start Content Server')) + self.toggle_server_action.triggered.connect(lambda x: + self.toggle_server.emit()) + self.addSeparator() + + self.email_actions = [] + + def server_state_changed(self, running): + text = _('Start Content Server') + if running: + text = _('Stop Content Server') + self.toggle_server_action.setText(text) + + def build_email_entries(self, sync_menu): + from calibre.gui2.device import DeviceAction + for ac in self.email_actions: + self.removeAction(ac) + self.email_actions = [] + self.memory = [] + opts = email_config().parse() + if opts.accounts: + self.email_to_menu = QMenu(_('Email to')+'...', self) + keys = sorted(opts.accounts.keys()) + for account in keys: + formats, auto, default = opts.accounts[account] + dest = 'mail:'+account+';'+formats + action1 = DeviceAction(dest, False, False, I('mail.svg'), + _('Email to')+' '+account) + action2 = DeviceAction(dest, True, False, I('mail.svg'), + _('Email to')+' '+account+ _(' and delete from library')) + map(self.email_to_menu.addAction, (action1, action2)) + map(self.memory.append, (action1, action2)) + if default: + map(self.addAction, (action1, action2)) + map(self.email_actions.append, (action1, action2)) + self.email_to_menu.addSeparator() + action1.a_s.connect(sync_menu.action_triggered) + action2.a_s.connect(sync_menu.action_triggered) + ac = self.addMenu(self.email_to_menu) + self.email_actions.append(ac) + else: + ac = self.addAction(_('Setup email based sharing of books')) + self.email_actions.append(ac) + ac.triggered.connect(self.setup_email) + + def setup_email(self, *args): + self.config_email.emit() + + def set_state(self, device_connected): + self.connect_to_folder_action.setEnabled(not device_connected) + self.connect_to_itunes_action.setEnabled(not device_connected) + + +# }}} + +class SendToDeviceAction(InterfaceAction): + + name = 'Send To Device' + action_spec = (_('Send to device'), 'sync.svg', None, _('D')) + + def genesis(self): + self.qaction.triggered.connect(self.do_sync) + self.gui.create_device_menu() + + def location_selected(self, loc): + enabled = loc == 'library' + self.qaction.setEnabled(enabled) + + def do_sync(self, *args): + self.gui._sync_action_triggered() + + +class ConnectShareAction(InterfaceAction): + + name = 'Connect Share' + action_spec = (_('Connect/share'), 'connect_share.svg', None, None) + popup_type = QToolButton.InstantPopup + + def genesis(self): + self.share_conn_menu = ShareConnMenu(self.gui) + self.share_conn_menu.toggle_server.connect(self.toggle_content_server) + self.share_conn_menu.config_email.connect(partial( + self.gui.iactions['Preferences'].do_config, + initial_category='email')) + self.qaction.setMenu(self.share_conn_menu) + self.share_conn_menu.connect_to_folder.connect(self.gui.connect_to_folder) + self.share_conn_menu.connect_to_itunes.connect(self.gui.connect_to_itunes) + + def location_selected(self, loc): + enabled = loc == 'library' + self.qaction.setEnabled(enabled) + + def set_state(self, device_connected): + self.share_conn_menu.set_state(device_connected) + + def build_email_entries(self): + m = self.gui.iactions['Send To Device'].qaction.menu() + self.share_conn_menu.build_email_entries(m) + + def content_server_state_changed(self, running): + self.share_conn_menu.server_state_changed(running) + + def toggle_content_server(self): + if self.gui.content_server is None: + self.gui.start_content_server() + else: + self.gui.content_server.exit() + self.gui.content_server = None diff --git a/src/calibre/gui2/actions/open.py b/src/calibre/gui2/actions/open.py new file mode 100644 index 0000000000..569f037327 --- /dev/null +++ b/src/calibre/gui2/actions/open.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + + +from calibre.gui2.actions import InterfaceAction + +class OpenFolderAction(InterfaceAction): + + name = 'Open Folder' + action_spec = (_('Open containing folder'), 'document_open.svg', None, + _('O')) + + def genesis(self): + self.action_open_containing_folder.triggered.connect(self.iactions['View'].view_folder) + + def location_selected(self, loc): + enabled = loc == 'library' + self.qaction.setEnabled(enabled) + + diff --git a/src/calibre/gui2/actions/restart.py b/src/calibre/gui2/actions/restart.py new file mode 100644 index 0000000000..be940fa32e --- /dev/null +++ b/src/calibre/gui2/actions/restart.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + + +from calibre.gui2.actions import InterfaceAction + +class RestartAction(InterfaceAction): + + name = 'Restart' + action_spec = (_('&Restart'), None, None, _('Ctrl+R')) + + def genesis(self): + self.qaction.triggered.connect(self.restart) + + def restart(self, *args): + self.gui.quit(restart=True) + + diff --git a/src/calibre/gui2/actions/show_book_details.py b/src/calibre/gui2/actions/show_book_details.py new file mode 100644 index 0000000000..06c63714a7 --- /dev/null +++ b/src/calibre/gui2/actions/show_book_details.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + + +from calibre.gui2.actions import InterfaceAction +from calibre.gui2.dialogs.book_info import BookInfo +from calibre.gui2 import error_dialog + +class ShowBookDetailsAction(InterfaceAction): + + name = 'Show Book Details' + action_spec = (_('Show book details'), 'dialog_information.svg', None, + _('I')) + + def genesis(self): + self.qaction.triggered.connect(self.show_book_info) + + def show_book_info(self, *args): + if self.gui.current_view() is not self.gui.library_view: + error_dialog(self.gui, _('No detailed info available'), + _('No detailed information is available for books ' + 'on the device.')).exec_() + return + index = self.gui.library_view.currentIndex() + if index.isValid(): + BookInfo(self.gui, self.gui.library_view, index).show() + diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 1e716a85fe..a9beb317a2 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -608,8 +608,6 @@ class DeviceMixin(object): # {{{ self.device_error_dialog = error_dialog(self, _('Error'), _('Error communicating with device'), ' ') self.device_error_dialog.setModal(Qt.NonModal) - self.share_conn_menu.connect_to_folder.connect(self.connect_to_folder) - self.share_conn_menu.connect_to_itunes.connect(self.connect_to_itunes) self.emailer = Emailer() self.emailer.start() self.device_manager = DeviceManager(Dispatcher(self.device_detected), @@ -647,21 +645,18 @@ class DeviceMixin(object): # {{{ def create_device_menu(self): self._sync_menu = DeviceMenu(self) - self.share_conn_menu.build_email_entries(self._sync_menu) - self.action_sync.setMenu(self._sync_menu) + self.iactions['Send To Device'].qaction.setMenu(self._sync_menu) + self.iactions['Connect Share'].build_email_entries() self.connect(self._sync_menu, SIGNAL('sync(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'), self.dispatch_sync_event) self._sync_menu.fetch_annotations.connect( self.iactions['Fetch Annotations'].fetch_annotations) self._sync_menu.disconnect_mounted_device.connect(self.disconnect_mounted_device) + self.iactions['Connect Share'].set_state(self.device_connected) if self.device_connected: - self.share_conn_menu.connect_to_folder_action.setEnabled(False) - self.share_conn_menu.connect_to_itunes_action.setEnabled(False) self._sync_menu.disconnect_mounted_device_action.setEnabled(True) else: - self.share_conn_menu.connect_to_folder_action.setEnabled(True) - self.share_conn_menu.connect_to_itunes_action.setEnabled(True) self._sync_menu.disconnect_mounted_device_action.setEnabled(False) def device_job_exception(self, job): @@ -697,17 +692,14 @@ class DeviceMixin(object): # {{{ # Device connected {{{ def set_device_menu_items_state(self, connected): + self.iactions['Connect Share'].set_state(connected) if connected: - self.share_conn_menu.connect_to_folder_action.setEnabled(False) - self.share_conn_menu.connect_to_itunes_action.setEnabled(False) self._sync_menu.disconnect_mounted_device_action.setEnabled(True) self._sync_menu.enable_device_actions(True, self.device_manager.device.card_prefix(), self.device_manager.device) self.eject_action.setEnabled(True) else: - self.share_conn_menu.connect_to_folder_action.setEnabled(True) - self.share_conn_menu.connect_to_itunes_action.setEnabled(True) self._sync_menu.disconnect_mounted_device_action.setEnabled(False) self._sync_menu.enable_device_actions(False) self.eject_action.setEnabled(False) diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index 839bfd536c..2b89057fc4 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -306,7 +306,7 @@ class LayoutMixin(object): # {{{ def finalize_layout(self): self.status_bar.initialize(self.system_tray_icon) - self.book_details.show_book_info.connect(self.show_book_info) + self.book_details.show_book_info.connect(self.iactions['Show Book Details'].show_book_info) self.book_details.files_dropped.connect(self.iactions['Add Books'].files_dropped_on_book) self.book_details.open_containing_folder.connect(self.iactions['View'].view_folder_for_id) self.book_details.view_specific_format.connect(self.iactions['View'].view_format_by_id) diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index fcd3be398b..72d548f287 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -20,7 +20,6 @@ from calibre.gui2 import config, open_url, gprefs from calibre.gui2.widgets import ComboBoxWithHelp from calibre import human_readable from calibre.gui2.dialogs.scheduler import Scheduler -from calibre.utils.smtp import config as email_config @@ -306,77 +305,6 @@ class ToolBar(QToolBar): # {{{ class Action(QAction): pass -class ShareConnMenu(QMenu): # {{{ - - connect_to_folder = pyqtSignal() - connect_to_itunes = pyqtSignal() - config_email = pyqtSignal() - toggle_server = pyqtSignal() - - def __init__(self, parent=None): - QMenu.__init__(self, parent) - mitem = self.addAction(QIcon(I('devices/folder.svg')), _('Connect to folder')) - mitem.setEnabled(True) - mitem.triggered.connect(lambda x : self.connect_to_folder.emit()) - self.connect_to_folder_action = mitem - mitem = self.addAction(QIcon(I('devices/itunes.png')), - _('Connect to iTunes')) - mitem.setEnabled(True) - mitem.triggered.connect(lambda x : self.connect_to_itunes.emit()) - self.connect_to_itunes_action = mitem - self.addSeparator() - self.toggle_server_action = \ - self.addAction(QIcon(I('network-server.svg')), - _('Start Content Server')) - self.toggle_server_action.triggered.connect(lambda x: - self.toggle_server.emit()) - self.addSeparator() - - self.email_actions = [] - - def server_state_changed(self, running): - text = _('Start Content Server') - if running: - text = _('Stop Content Server') - self.toggle_server_action.setText(text) - - def build_email_entries(self, sync_menu): - from calibre.gui2.device import DeviceAction - for ac in self.email_actions: - self.removeAction(ac) - self.email_actions = [] - self.memory = [] - opts = email_config().parse() - if opts.accounts: - self.email_to_menu = QMenu(_('Email to')+'...', self) - keys = sorted(opts.accounts.keys()) - for account in keys: - formats, auto, default = opts.accounts[account] - dest = 'mail:'+account+';'+formats - action1 = DeviceAction(dest, False, False, I('mail.svg'), - _('Email to')+' '+account) - action2 = DeviceAction(dest, True, False, I('mail.svg'), - _('Email to')+' '+account+ _(' and delete from library')) - map(self.email_to_menu.addAction, (action1, action2)) - map(self.memory.append, (action1, action2)) - if default: - map(self.addAction, (action1, action2)) - map(self.email_actions.append, (action1, action2)) - self.email_to_menu.addSeparator() - action1.a_s.connect(sync_menu.action_triggered) - action2.a_s.connect(sync_menu.action_triggered) - ac = self.addMenu(self.email_to_menu) - self.email_actions.append(ac) - else: - ac = self.addAction(_('Setup email based sharing of books')) - self.email_actions.append(ac) - ac.triggered.connect(self.setup_email) - - def setup_email(self, *args): - self.config_email.emit() - -# }}} - class MainWindowMixin(object): def __init__(self, db): @@ -442,17 +370,11 @@ class MainWindowMixin(object): setattr(self, 'action_'+name, action) all_actions.append(action) - ac(-1, 4, 0, 'sync', _('Send to device'), 'sync.svg') ac(5, 5, 3, 'choose_library', _('%d books')%0, 'lt.png', tooltip=_('Choose calibre library to work with')) - ac(8, 8, 0, 'conn_share', _('Connect/share'), 'connect_share.svg') ac(10, 10, 3, 'help', _('Help'), 'help.svg', _('F1'), _("Browse the calibre User Manual")) ac(11, 11, 0, 'preferences', _('Preferences'), 'config.svg', _('Ctrl+P')) - ac(-1, -1, 0, 'open_containing_folder', _('Open containing folder'), - 'document_open.svg') - ac(-1, -1, 0, 'show_book_details', _('Show book details'), - 'dialog_information.svg') ac(-1, -1, 0, 'books_by_same_author', _('Books by same author'), 'user_profile.svg') ac(-1, -1, 0, 'books_in_this_series', _('Books in this series'), @@ -462,24 +384,10 @@ class MainWindowMixin(object): ac(-1, -1, 0, 'books_with_the_same_tags', _('Books with the same tags'), 'tags.svg') - self.share_conn_menu = ShareConnMenu(self) - self.share_conn_menu.toggle_server.connect(self.toggle_content_server) - self.share_conn_menu.config_email.connect(partial(self.do_config, - initial_category='email')) - self.action_conn_share.setMenu(self.share_conn_menu) self.action_help.triggered.connect(self.show_help) - self.action_open_containing_folder.setShortcut(Qt.Key_O) - self.addAction(self.action_open_containing_folder) - self.action_open_containing_folder.triggered.connect(self.iactions['View'].view_folder) - self.action_sync.setShortcut(Qt.Key_D) - self.action_sync.setEnabled(True) - self.create_device_menu() - self.action_sync.triggered.connect( - self._sync_action_triggered) - pm = QMenu() @@ -498,12 +406,5 @@ class MainWindowMixin(object): def show_help(self, *args): open_url(QUrl('http://calibre-ebook.com/user_manual')) - def content_server_state_changed(self, running): - self.share_conn_menu.server_state_changed(running) - def toggle_content_server(self): - if self.content_server is None: - self.start_content_server() - else: - self.content_server.exit() - self.content_server = None + diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 581d02f9ab..c62a2d3fc8 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -15,7 +15,7 @@ from threading import Thread from PyQt4.Qt import Qt, SIGNAL, QTimer, \ QPixmap, QMenu, QIcon, pyqtSignal, \ QDialog, \ - QSystemTrayIcon, QApplication, QKeySequence, QAction, \ + QSystemTrayIcon, QApplication, QKeySequence, \ QMessageBox, QHelpEvent from calibre import prints @@ -35,7 +35,6 @@ from calibre.gui2.layout import MainWindowMixin from calibre.gui2.device import DeviceMixin from calibre.gui2.jobs import JobManager, JobsDialog, JobsButton from calibre.gui2.dialogs.config import ConfigDialog -from calibre.gui2.dialogs.book_info import BookInfo from calibre.gui2.init import LibraryViewMixin, LayoutMixin from calibre.gui2.search_box import SearchBoxMixin, SavedSearchBoxMixin from calibre.gui2.search_restriction_mixin import SearchRestrictionMixin @@ -172,22 +171,13 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ QIcon(I('eject.svg')), _('&Eject connected device')) self.eject_action.setEnabled(False) self.addAction(self.quit_action) - self.action_restart = QAction(_('&Restart'), self) - self.addAction(self.action_restart) self.system_tray_menu.addAction(self.quit_action) self.quit_action.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_Q)) - self.action_restart.setShortcut(QKeySequence(Qt.CTRL + Qt.Key_R)) - self.action_show_book_details.setShortcut(QKeySequence(Qt.Key_I)) - self.addAction(self.action_show_book_details) 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()'), self.show_windows) - self.connect(self.action_show_book_details, - SIGNAL('triggered(bool)'), self.show_book_info) - self.connect(self.action_restart, SIGNAL('triggered()'), - self.restart) self.connect(self.system_tray_icon, SIGNAL('activated(QSystemTrayIcon::ActivationReason)'), self.system_tray_icon_activated) @@ -270,7 +260,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ from calibre.library.server import server_config self.content_server = start_threaded_server( self.library_view.model().db, server_config().parse()) - self.content_server.state_callback = Dispatcher(self.content_server_state_changed) + self.content_server.state_callback = Dispatcher( + self.iactions['Connect Share'].content_server_state_changed) self.content_server.state_callback(True) self.test_server_timer = QTimer.singleShot(10000, self.test_server) @@ -381,7 +372,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ self.content_server = d.server if self.content_server is not None: self.content_server.state_callback = \ - Dispatcher(self.content_server_state_changed) + Dispatcher(self.iactions['Connect Share'].content_server_state_changed) self.content_server.state_callback(self.content_server.is_running) if d.result() == d.Accepted: @@ -411,15 +402,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ self.scheduler.database_changed(db) prefs['library_path'] = self.library_path - def show_book_info(self, *args): - if self.current_view() is not self.library_view: - error_dialog(self, _('No detailed info available'), - _('No detailed information is available for books ' - 'on the device.')).exec_() - return - index = self.library_view.currentIndex() - if index.isValid(): - BookInfo(self, self.library_view, index).show() def location_selected(self, location): ''' @@ -434,12 +416,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ for action in self.iactions.values(): action.location_selected(location) if location == 'library': - self.action_open_containing_folder.setEnabled(True) - self.action_sync.setEnabled(True) self.search_restriction.setEnabled(True) else: - self.action_open_containing_folder.setEnabled(False) - self.action_sync.setEnabled(False) self.search_restriction.setEnabled(False) # Reset the view in case something changed while it was invisible self.current_view().reset() @@ -503,9 +481,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ dynamic.set('sort_history', self.library_view.model().sort_history) self.save_layout_state() - def restart(self): - self.quit(restart=True) - def quit(self, checked=True, restart=False): if not self.confirm_quit(): return