From 89f2da224d611c125a9940fa1089859d8b6a7844 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 1 Mar 2011 12:45:17 -0700 Subject: [PATCH] Allow drag drop of images to change cover in book details window. Fixes #9226 (Drag/drop covers into book information dialog) --- src/calibre/gui2/dialogs/book_info.py | 19 ++++- src/calibre/gui2/dialogs/book_info.ui | 7 +- src/calibre/gui2/widgets.py | 111 ++++++++++++++++---------- 3 files changed, 91 insertions(+), 46 deletions(-) diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index 4da897920c..523974e4f2 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en' import textwrap, os, re from PyQt4.Qt import QCoreApplication, SIGNAL, QModelIndex, QTimer, Qt, \ - QDialog, QPixmap, QGraphicsScene, QIcon, QSize + QDialog, QPixmap, QIcon, QSize from calibre.gui2.dialogs.book_info_ui import Ui_BookInfo from calibre.gui2 import dynamic, open_local_file, open_url @@ -14,12 +14,14 @@ from calibre import fit_image from calibre.library.comments import comments_to_html from calibre.utils.icu import sort_key + class BookInfo(QDialog, Ui_BookInfo): def __init__(self, parent, view, row, view_func): QDialog.__init__(self, parent) Ui_BookInfo.__init__(self) self.setupUi(self) + self.gui = parent self.cover_pixmap = None self.comments.sizeHint = self.comments_size_hint self.comments.page().setLinkDelegationPolicy(self.comments.page().DelegateAllLinks) @@ -38,11 +40,22 @@ class BookInfo(QDialog, Ui_BookInfo): self.connect(self.text, SIGNAL('linkActivated(QString)'), self.open_book_path) self.fit_cover.stateChanged.connect(self.toggle_cover_fit) self.cover.resizeEvent = self.cover_view_resized + self.cover.cover_changed.connect(self.cover_changed) desktop = QCoreApplication.instance().desktop() screen_height = desktop.availableGeometry().height() - 100 self.resize(self.size().width(), screen_height) + def cover_changed(self, data): + if self.current_row is not None: + id_ = self.view.model().id(self.current_row) + self.view.model().db.set_cover(id_, data) + if self.gui.cover_flow: + self.gui.cover_flow.dataChanged() + ci = self.view.currentIndex() + if ci.isValid(): + self.view.model().current_changed(ci, ci) + def link_clicked(self, url): open_url(url) @@ -83,7 +96,6 @@ class BookInfo(QDialog, Ui_BookInfo): if self.cover_pixmap is None: return self.setWindowIcon(QIcon(self.cover_pixmap)) - self.scene = QGraphicsScene() pixmap = self.cover_pixmap if self.fit_cover.isChecked(): scaled, new_width, new_height = fit_image(pixmap.width(), @@ -92,8 +104,7 @@ class BookInfo(QDialog, Ui_BookInfo): if scaled: pixmap = pixmap.scaled(new_width, new_height, Qt.KeepAspectRatio, Qt.SmoothTransformation) - self.scene.addPixmap(pixmap) - self.cover.setScene(self.scene) + self.cover.set_pixmap(pixmap) def refresh(self, row): if isinstance(row, QModelIndex): diff --git a/src/calibre/gui2/dialogs/book_info.ui b/src/calibre/gui2/dialogs/book_info.ui index 2902a2c917..412126a610 100644 --- a/src/calibre/gui2/dialogs/book_info.ui +++ b/src/calibre/gui2/dialogs/book_info.ui @@ -25,7 +25,7 @@ - + @@ -115,6 +115,11 @@ QWidget
QtWebKit/QWebView
+ + CoverView + QGraphicsView +
calibre/gui2/widgets.h
+
diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index 3622cc6c39..5cacf32bb2 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -11,9 +11,9 @@ from PyQt4.Qt import QIcon, QFont, QLabel, QListWidget, QAction, \ QPixmap, QSplitterHandle, QToolButton, \ QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, \ QRegExp, QSettings, QSize, QSplitter, \ - QPainter, QLineEdit, QComboBox, QPen, \ + QPainter, QLineEdit, QComboBox, QPen, QGraphicsScene, \ QMenu, QStringListModel, QCompleter, QStringList, \ - QTimer, QRect, QFontDatabase + QTimer, QRect, QFontDatabase, QGraphicsView from calibre.gui2 import NONE, error_dialog, pixmap_to_data, gprefs from calibre.gui2.filename_pattern_ui import Ui_Form @@ -181,22 +181,16 @@ class FormatList(QListWidget): else: return QListWidget.keyPressEvent(self, event) - -class ImageView(QWidget): - - BORDER_WIDTH = 1 - cover_changed = pyqtSignal(object) - - def __init__(self, parent=None): - QWidget.__init__(self, parent) - self._pixmap = QPixmap(self) - self.setMinimumSize(QSize(150, 200)) - self.setAcceptDrops(True) - self.draw_border = True - - # Drag 'n drop {{{ +class ImageDropMixin(object): # {{{ + ''' + Adds support for dropping images onto widgets and a contect menu for + copy/pasting images. + ''' DROPABBLE_EXTENSIONS = IMAGE_EXTENSIONS + def __init__(self): + self.setAcceptDrops(True) + @classmethod def paths_from_event(cls, event): ''' @@ -223,14 +217,58 @@ class ImageView(QWidget): pmap = QPixmap() pmap.load(path) if not pmap.isNull(): - self.setPixmap(pmap) + self.handle_image_drop(path, pmap) event.accept() - self.cover_changed.emit(open(path, 'rb').read()) break + def handle_image_drop(self, path, pmap): + self.set_pixmap(pmap) + self.cover_changed.emit(open(path, 'rb').read()) + def dragMoveEvent(self, event): event.acceptProposedAction() - # }}} + + def get_pixmap(self): + return self.pixmap() + + def set_pixmap(self, pmap): + self.setPixmap(pmap) + + def contextMenuEvent(self, ev): + cm = QMenu(self) + copy = cm.addAction(_('Copy Image')) + paste = cm.addAction(_('Paste Image')) + if not QApplication.instance().clipboard().mimeData().hasImage(): + paste.setEnabled(False) + copy.triggered.connect(self.copy_to_clipboard) + paste.triggered.connect(self.paste_from_clipboard) + cm.exec_(ev.globalPos()) + + def copy_to_clipboard(self): + QApplication.instance().clipboard().setPixmap(self.get_pixmap()) + + def paste_from_clipboard(self): + cb = QApplication.instance().clipboard() + pmap = cb.pixmap() + if pmap.isNull() and cb.supportsSelection(): + pmap = cb.pixmap(cb.Selection) + if not pmap.isNull(): + self.set_pixmap(pmap) + self.cover_changed.emit( + pixmap_to_data(pmap)) +# }}} + +class ImageView(QWidget, ImageDropMixin): + + BORDER_WIDTH = 1 + cover_changed = pyqtSignal(object) + + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self._pixmap = QPixmap(self) + self.setMinimumSize(QSize(150, 200)) + ImageDropMixin.__init__(self) + self.draw_border = True def setPixmap(self, pixmap): if not isinstance(pixmap, QPixmap): @@ -272,32 +310,23 @@ class ImageView(QWidget): p.drawRect(target) p.end() +class CoverView(QGraphicsView, ImageDropMixin): - # Clipboard copy/paste # {{{ - def contextMenuEvent(self, ev): - cm = QMenu(self) - copy = cm.addAction(_('Copy Image')) - paste = cm.addAction(_('Paste Image')) - if not QApplication.instance().clipboard().mimeData().hasImage(): - paste.setEnabled(False) - copy.triggered.connect(self.copy_to_clipboard) - paste.triggered.connect(self.paste_from_clipboard) - cm.exec_(ev.globalPos()) + cover_changed = pyqtSignal(object) - def copy_to_clipboard(self): - QApplication.instance().clipboard().setPixmap(self.pixmap()) + def __init__(self, *args, **kwargs): + QGraphicsView.__init__(self, *args, **kwargs) + ImageDropMixin.__init__(self) - def paste_from_clipboard(self): - cb = QApplication.instance().clipboard() - pmap = cb.pixmap() - if pmap.isNull() and cb.supportsSelection(): - pmap = cb.pixmap(cb.Selection) - if not pmap.isNull(): - self.setPixmap(pmap) - self.cover_changed.emit( - pixmap_to_data(pmap)) - # }}} + def get_pixmap(self): + for item in self.scene().items(): + if hasattr(item, 'pixmap'): + return item.pixmap() + def set_pixmap(self, pmap): + self.scene = QGraphicsScene() + self.scene.addPixmap(pmap) + self.setScene(self.scene) class FontFamilyModel(QAbstractListModel):