diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index 6f1f111e2a..bbaf87cfb1 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -12,8 +12,8 @@ import time import traceback from datetime import datetime, timedelta -from PyQt4.QtCore import SIGNAL, QObject, QCoreApplication, Qt, QTimer, QThread, QDate -from PyQt4.QtGui import QPixmap, QListWidgetItem, QErrorMessage, QDialog +from PyQt4.Qt import SIGNAL, QObject, QCoreApplication, Qt, QTimer, QThread, QDate, \ + QPixmap, QListWidgetItem, QDialog, QListWidget from calibre.gui2 import qstring_to_unicode, error_dialog, file_icon_provider, \ choose_files, choose_images, ResizableDialog @@ -80,6 +80,37 @@ class Format(QListWidgetItem): QListWidgetItem.__init__(self, file_icon_provider().icon_from_ext(ext), text, parent, QListWidgetItem.UserType) +class FormatList(QListWidget): + DROPABBLE_EXTENSIONS = BOOK_EXTENSIONS + + @classmethod + def paths_from_event(cls, event): + ''' + Accept a drop event and return a list of paths that can be read from + and represent files with extensions. + ''' + if event.mimeData().hasFormat('text/uri-list'): + urls = [unicode(u.toLocalFile()) for u in event.mimeData().urls()] + urls = [u for u in urls if os.path.splitext(u)[1] and os.access(u, os.R_OK)] + return [u for u in urls if os.path.splitext(u)[1][1:].lower() in cls.DROPABBLE_EXTENSIONS] + + def dragEnterEvent(self, event): + if int(event.possibleActions() & Qt.CopyAction) + \ + int(event.possibleActions() & Qt.MoveAction) == 0: + return + paths = self.paths_from_event(event) + if paths: + event.acceptProposedAction() + + def dropEvent(self, event): + paths = self.paths_from_event(event) + event.setDropAction(Qt.CopyAction) + self.emit(SIGNAL('formats_dropped(PyQt_PyObject,PyQt_PyObject)'), + event, paths) + + def dragMoveEvent(self, event): + event.acceptProposedAction() + class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): COVER_FETCH_TIMEOUT = 240 # seconds @@ -129,16 +160,21 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): def add_format(self, x): files = choose_files(self, 'add formats dialog', - "Choose formats for " + qstring_to_unicode((self.title.text())), - [('Books', BOOK_EXTENSIONS)]) - if not files: - return - for _file in files: + _("Choose formats for ") + unicode((self.title.text())), + [(_('Books'), BOOK_EXTENSIONS)]) + self._add_formats(files) + + def _add_formats(self, paths): + added = False + if not paths: + return added + bad_perms = [] + for _file in paths: _file = os.path.abspath(_file) if not os.access(_file, os.R_OK): - QErrorMessage(self.window).showMessage("You do not have "+\ - "permission to read the file: " + _file) + bad_perms.append(_file) continue + _file = run_plugins_on_import(_file) size = os.stat(_file).st_size ext = os.path.splitext(_file)[1].lower().replace('.', '') @@ -149,6 +185,17 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): break Format(self.formats, ext, size, path=_file) self.formats_changed = True + added = True + if bad_perms: + error_dialog(self.window, _('You do not have ' + 'permission to read the following files:'), + det_msg='\n'.join(bad_perms), show=True) + + return added + + def formats_dropped(self, event, paths): + if self._add_formats(paths): + event.accept() def remove_format(self, x): rows = self.formats.selectionModel().selectedRows(0) @@ -276,6 +323,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.row = row self.cover_data = None self.formats_changed = False + self.formats.setAcceptDrops(True) self.cover_changed = False self.cpixmap = None self.cover.setAcceptDrops(True) @@ -287,6 +335,9 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.select_cover) QObject.connect(self.add_format_button, SIGNAL("clicked(bool)"), \ self.add_format) + self.connect(self.formats, + SIGNAL('formats_dropped(PyQt_PyObject,PyQt_PyObject)'), + self.formats_dropped) QObject.connect(self.remove_format_button, SIGNAL("clicked(bool)"), \ self.remove_format) QObject.connect(self.fetch_metadata_button, SIGNAL('clicked()'), diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui index 78a4815fd9..e14a8ce4f4 100644 --- a/src/calibre/gui2/dialogs/metadata_single.ui +++ b/src/calibre/gui2/dialogs/metadata_single.ui @@ -439,7 +439,7 @@ - + 0 @@ -452,6 +452,9 @@ 130 + + QAbstractItemView::DropOnly + 64 @@ -703,6 +706,11 @@ QLineEdit
widgets.h
+ + FormatList + QListWidget +
calibre/gui2/widgets.h
+
title diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index e1c9aaf9c3..190ff920ef 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -19,6 +19,7 @@ from calibre.gui2.dialogs.job_view_ui import Ui_Dialog from calibre.gui2.filename_pattern_ui import Ui_Form from calibre import fit_image from calibre.utils.fonts import fontconfig +from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks.metadata.meta import metadata_from_filename from calibre.utils.config import prefs @@ -107,6 +108,37 @@ class FilenamePattern(QWidget, Ui_Form): IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'gif', 'png', 'bmp'] +class FormatList(QListWidget): + DROPABBLE_EXTENSIONS = BOOK_EXTENSIONS + + @classmethod + def paths_from_event(cls, event): + ''' + Accept a drop event and return a list of paths that can be read from + and represent files with extensions. + ''' + if event.mimeData().hasFormat('text/uri-list'): + urls = [unicode(u.toLocalFile()) for u in event.mimeData().urls()] + urls = [u for u in urls if os.path.splitext(u)[1] and os.access(u, os.R_OK)] + return [u for u in urls if os.path.splitext(u)[1][1:].lower() in cls.DROPABBLE_EXTENSIONS] + + def dragEnterEvent(self, event): + if int(event.possibleActions() & Qt.CopyAction) + \ + int(event.possibleActions() & Qt.MoveAction) == 0: + return + paths = self.paths_from_event(event) + if paths: + event.acceptProposedAction() + + def dropEvent(self, event): + paths = self.paths_from_event(event) + event.setDropAction(Qt.CopyAction) + self.emit(SIGNAL('formats_dropped(PyQt_PyObject,PyQt_PyObject)'), + event, paths) + + def dragMoveEvent(self, event): + event.acceptProposedAction() + class ImageView(QLabel): MAX_WIDTH = 400