From b7555d7424c70916e53d4b5fc307f5fe2a63961a Mon Sep 17 00:00:00 2001 From: GRiker Date: Sat, 2 Mar 2013 05:16:16 -0700 Subject: [PATCH 1/3] Proposed changes to calibre.gui2.bars - incomplete implementation, need to discriminate which menu item received the drop. For KG review. --- src/calibre/gui2/bars.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/calibre/gui2/bars.py b/src/calibre/gui2/bars.py index 50b3f3e7f5..38643fb40f 100644 --- a/src/calibre/gui2/bars.py +++ b/src/calibre/gui2/bars.py @@ -123,6 +123,15 @@ class ToolBar(QToolBar): # {{{ md.hasFormat("application/calibre+from_device"): event.setDropAction(Qt.CopyAction) event.accept() + + elif self.added_actions: + # Give added_actions an opportunity to process the drag&drop event + # This calls the first QMenu object that accepts drops, rather than the + # specific one that was dropped on + for aa in self.added_actions: + if aa.menu() is not None and aa.menu().acceptDrops(): + aa.menu().dragEnterEvent(event) + break else: event.ignore() @@ -141,6 +150,15 @@ class ToolBar(QToolBar): # {{{ break if allowed: event.acceptProposedAction() + + elif self.added_actions: + # Give added_actions an opportunity to process the drag&drop event + # This calls the first QMenu object that accepts drops, rather than the + # specific one that was dropped on + for aa in self.added_actions: + if aa.menu() is not None and aa.menu().acceptDrops(): + aa.menu().dragMoveEvent(event) + break else: event.ignore() @@ -169,6 +187,15 @@ class ToolBar(QToolBar): # {{{ self.gui.current_view(), paths=paths) event.accept() + # Give added_actions an opportunity to process the drag&drop event + # This calls the first QMenu object that accepts drops, rather than the + # specific one that was dropped on + if self.added_actions: + for aa in self.added_actions: + if aa.menu() is not None and aa.menu().acceptDrops(): + aa.menu().dropEvent(event) + break + # }}} class MenuAction(QAction): # {{{ From 9fa3a1b5580d3e7598f1b0a0664a1c0d79a85c0f Mon Sep 17 00:00:00 2001 From: GRiker Date: Sun, 3 Mar 2013 05:28:29 -0800 Subject: [PATCH 2/3] Revisions enabling a plugin to receive drag/drop events - take 2 --- src/calibre/gui2/actions/__init__.py | 15 ++++++++ src/calibre/gui2/bars.py | 55 +++++++++++++++------------- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/src/calibre/gui2/actions/__init__.py b/src/calibre/gui2/actions/__init__.py index fa67130a1c..ef731ed0b1 100644 --- a/src/calibre/gui2/actions/__init__.py +++ b/src/calibre/gui2/actions/__init__.py @@ -101,6 +101,11 @@ class InterfaceAction(QObject): #: on calibre as a whole action_type = 'global' + #: If True, the action may inspect the event at accept_enter_event() and + #: accept_drag_move_event(), returning True or False if it wants to handle the event. + #: drop_event() will be called in the subclass from calibre.gui2.bars + accepts_drops = False + def __init__(self, parent, site_customization): QObject.__init__(self, parent) self.setObjectName(self.name) @@ -108,6 +113,15 @@ class InterfaceAction(QObject): self.site_customization = site_customization self.interface_action_base_plugin = None + def accept_enter_event(self, mime_data): + return False + + def accept_drag_move_event(self, mime_data): + return False + + def drop_event(self, event): + pass + def do_genesis(self): self.Dispatcher = partial(Dispatcher, parent=self) self.create_action() @@ -131,6 +145,7 @@ class InterfaceAction(QObject): else: action = QAction(text, self.gui) if attr == 'qaction': + action.associated_interface_action = self mt = (action.text() if self.action_menu_clone_qaction is True else unicode(self.action_menu_clone_qaction)) self.menuless_qaction = ma = QAction(action.icon(), mt, self.gui) diff --git a/src/calibre/gui2/bars.py b/src/calibre/gui2/bars.py index 38643fb40f..8267eca430 100644 --- a/src/calibre/gui2/bars.py +++ b/src/calibre/gui2/bars.py @@ -8,8 +8,8 @@ __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -from PyQt4.Qt import (QObject, QToolBar, Qt, QSize, QToolButton, QVBoxLayout, - QLabel, QWidget, QAction, QMenuBar, QMenu) +from PyQt4.Qt import (Qt, QAction, QLabel, QMenu, QMenuBar, QObject, + QToolBar, QToolButton, QSize, QVBoxLayout, QWidget) from calibre.constants import isosx from calibre.gui2 import gprefs @@ -116,29 +116,29 @@ class ToolBar(QToolBar): # {{{ ch.setPopupMode(menu_mode) return ch - #support drag&drop from/to library from/to reader/card + # support drag&drop from/to library, from/to reader/card, enabled plugins def dragEnterEvent(self, event): md = event.mimeData() if md.hasFormat("application/calibre+from_library") or \ md.hasFormat("application/calibre+from_device"): event.setDropAction(Qt.CopyAction) event.accept() - elif self.added_actions: - # Give added_actions an opportunity to process the drag&drop event - # This calls the first QMenu object that accepts drops, rather than the - # specific one that was dropped on for aa in self.added_actions: - if aa.menu() is not None and aa.menu().acceptDrops(): - aa.menu().dragEnterEvent(event) - break + if (getattr(aa.associated_interface_action, 'accepts_drops', False) and + aa.menu().geometry().contains(event.pos())): + if aa.associated_interface_action.accept_enter_event(md): + event.accept() + break + else: + event.ignore() else: event.ignore() def dragMoveEvent(self, event): allowed = False md = event.mimeData() - #Drop is only allowed in the location manager widget's different from the selected one + # Drop is only allowed in the location manager widget's different from the selected one for ac in self.location_manager.available_actions: w = self.widgetForAction(ac) if w is not None: @@ -150,21 +150,22 @@ class ToolBar(QToolBar): # {{{ break if allowed: event.acceptProposedAction() + return - elif self.added_actions: - # Give added_actions an opportunity to process the drag&drop event - # This calls the first QMenu object that accepts drops, rather than the - # specific one that was dropped on + if self.added_actions: for aa in self.added_actions: - if aa.menu() is not None and aa.menu().acceptDrops(): - aa.menu().dragMoveEvent(event) - break + if (getattr(aa.associated_interface_action, 'accepts_drops', False) and + aa.menu().geometry().contains(event.pos())): + if aa.associated_interface_action.accept_drag_move_event(md): + event.acceptProposedAction() + break + else: + event.ignore() else: event.ignore() def dropEvent(self, event): data = event.mimeData() - mime = 'application/calibre+from_library' if data.hasFormat(mime): ids = list(map(int, str(data.data(mime)).split())) @@ -178,6 +179,7 @@ class ToolBar(QToolBar): # {{{ tgt = None self.gui.sync_to_device(tgt, False, send_ids=ids) event.accept() + return mime = 'application/calibre+from_device' if data.hasFormat(mime): @@ -186,15 +188,16 @@ class ToolBar(QToolBar): # {{{ self.gui.iactions['Add Books'].add_books_from_device( self.gui.current_view(), paths=paths) event.accept() + return # Give added_actions an opportunity to process the drag&drop event - # This calls the first QMenu object that accepts drops, rather than the - # specific one that was dropped on - if self.added_actions: - for aa in self.added_actions: - if aa.menu() is not None and aa.menu().acceptDrops(): - aa.menu().dropEvent(event) - break + for aa in self.added_actions: + if (getattr(aa.associated_interface_action, 'accepts_drops', False) and + aa.menu().geometry().contains(event.pos())): + aa.associated_interface_action.drop_event(event) + event.accept() + else: + event.ignore() # }}} From 88c150fd2e7a2dea2581213793d9f8213b42d141 Mon Sep 17 00:00:00 2001 From: GRiker Date: Sun, 3 Mar 2013 06:35:38 -0800 Subject: [PATCH 3/3] Added test for no existing playlists in response to lp:1141078. Driver is unable to add books to iTunes if Books playlist doesn't already exist. --- src/calibre/devices/apple/driver.py | 87 ++++++++++++++--------------- 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 2ab9a11daa..9bb8fa014e 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -20,8 +20,7 @@ from calibre.utils.config import config_dir, dynamic, prefs from calibre.utils.date import now, parse_date from calibre.utils.zipfile import ZipFile -DEBUG = False -#DEBUG = CALIBRE_DEBUG +DEBUG = CALIBRE_DEBUG def strftime(fmt='%Y/%m/%d %H:%M:%S', dt=None): @@ -862,7 +861,6 @@ class ITUNES(DriverBase): Note that most of the initialization is necessarily performed in can_handle(), as we need to talk to iTunes to discover if there's a connected iPod ''' - if self.iTunes is None: raise OpenFeedback(self.ITUNES_SANDBOX_LOCKOUT_MESSAGE) @@ -2157,27 +2155,28 @@ class ITUNES(DriverBase): if 'iPod' in self.sources: connected_device = self.sources['iPod'] device = self.iTunes.sources[connected_device] - dev_books = None - for pl in device.playlists(): - if pl.special_kind() == appscript.k.Books: - if DEBUG: - logger().info(" Book playlist: '%s'" % (pl.name())) - dev_books = pl.file_tracks() - break - else: - logger().error(" book_playlist not found") - - for book in dev_books: - if book.kind() in self.Audiobooks: - if DEBUG: - logger().info(" ignoring '%s' of type '%s'" % (book.name(), book.kind())) + if device.playlists() is not None: + dev_books = None + for pl in device.playlists(): + if pl.special_kind() == appscript.k.Books: + if DEBUG: + logger().info(" Book playlist: '%s'" % (pl.name())) + dev_books = pl.file_tracks() + break else: - if DEBUG: - logger().info(" %-40.40s %-30.30s %-40.40s [%s]" % - (book.name(), book.artist(), book.composer(), book.kind())) - device_books.append(book) - if DEBUG: - logger().info() + logger().error(" book_playlist not found") + + for book in dev_books: + if book.kind() in self.Audiobooks: + if DEBUG: + logger().info(" ignoring '%s' of type '%s'" % (book.name(), book.kind())) + else: + if DEBUG: + logger().info(" %-40.40s %-30.30s %-40.40s [%s]" % + (book.name(), book.artist(), book.composer(), book.kind())) + device_books.append(book) + if DEBUG: + logger().info() elif iswindows: import pythoncom @@ -2187,29 +2186,29 @@ class ITUNES(DriverBase): pythoncom.CoInitialize() connected_device = self.sources['iPod'] device = self.iTunes.sources.ItemByName(connected_device) - - dev_books = None - for pl in device.Playlists: - if pl.Kind == self.PlaylistKind.index('User') and \ - pl.SpecialKind == self.PlaylistSpecialKind.index('Books'): - if DEBUG: - logger().info(" Books playlist: '%s'" % (pl.Name)) - dev_books = pl.Tracks - break - else: - if DEBUG: - logger().info(" no Books playlist found") - - for book in dev_books: - if book.KindAsString in self.Audiobooks: - if DEBUG: - logger().info(" ignoring '%s' of type '%s'" % (book.Name, book.KindAsString)) + if device.Playlists is not None: + dev_books = None + for pl in device.Playlists: + if pl.Kind == self.PlaylistKind.index('User') and \ + pl.SpecialKind == self.PlaylistSpecialKind.index('Books'): + if DEBUG: + logger().info(" Books playlist: '%s'" % (pl.Name)) + dev_books = pl.Tracks + break else: if DEBUG: - logger().info(" %-40.40s %-30.30s %-40.40s [%s]" % (book.Name, book.Artist, book.Composer, book.KindAsString)) - device_books.append(book) - if DEBUG: - logger().info() + logger().info(" no Books playlist found") + + for book in dev_books: + if book.KindAsString in self.Audiobooks: + if DEBUG: + logger().info(" ignoring '%s' of type '%s'" % (book.Name, book.KindAsString)) + else: + if DEBUG: + logger().info(" %-40.40s %-30.30s %-40.40s [%s]" % (book.Name, book.Artist, book.Composer, book.KindAsString)) + device_books.append(book) + if DEBUG: + logger().info() finally: pythoncom.CoUninitialize()