Allow drag drop of images to change cover in book details window. Fixes #9226 (Drag/drop covers into book information dialog)

This commit is contained in:
Kovid Goyal 2011-03-01 12:45:17 -07:00
parent 27412b5b5c
commit 89f2da224d
3 changed files with 91 additions and 46 deletions

View File

@ -6,7 +6,7 @@ __docformat__ = 'restructuredtext en'
import textwrap, os, re import textwrap, os, re
from PyQt4.Qt import QCoreApplication, SIGNAL, QModelIndex, QTimer, Qt, \ 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.dialogs.book_info_ui import Ui_BookInfo
from calibre.gui2 import dynamic, open_local_file, open_url 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.library.comments import comments_to_html
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
class BookInfo(QDialog, Ui_BookInfo): class BookInfo(QDialog, Ui_BookInfo):
def __init__(self, parent, view, row, view_func): def __init__(self, parent, view, row, view_func):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
Ui_BookInfo.__init__(self) Ui_BookInfo.__init__(self)
self.setupUi(self) self.setupUi(self)
self.gui = parent
self.cover_pixmap = None self.cover_pixmap = None
self.comments.sizeHint = self.comments_size_hint self.comments.sizeHint = self.comments_size_hint
self.comments.page().setLinkDelegationPolicy(self.comments.page().DelegateAllLinks) 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.connect(self.text, SIGNAL('linkActivated(QString)'), self.open_book_path)
self.fit_cover.stateChanged.connect(self.toggle_cover_fit) self.fit_cover.stateChanged.connect(self.toggle_cover_fit)
self.cover.resizeEvent = self.cover_view_resized self.cover.resizeEvent = self.cover_view_resized
self.cover.cover_changed.connect(self.cover_changed)
desktop = QCoreApplication.instance().desktop() desktop = QCoreApplication.instance().desktop()
screen_height = desktop.availableGeometry().height() - 100 screen_height = desktop.availableGeometry().height() - 100
self.resize(self.size().width(), screen_height) 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): def link_clicked(self, url):
open_url(url) open_url(url)
@ -83,7 +96,6 @@ class BookInfo(QDialog, Ui_BookInfo):
if self.cover_pixmap is None: if self.cover_pixmap is None:
return return
self.setWindowIcon(QIcon(self.cover_pixmap)) self.setWindowIcon(QIcon(self.cover_pixmap))
self.scene = QGraphicsScene()
pixmap = self.cover_pixmap pixmap = self.cover_pixmap
if self.fit_cover.isChecked(): if self.fit_cover.isChecked():
scaled, new_width, new_height = fit_image(pixmap.width(), scaled, new_width, new_height = fit_image(pixmap.width(),
@ -92,8 +104,7 @@ class BookInfo(QDialog, Ui_BookInfo):
if scaled: if scaled:
pixmap = pixmap.scaled(new_width, new_height, pixmap = pixmap.scaled(new_width, new_height,
Qt.KeepAspectRatio, Qt.SmoothTransformation) Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.scene.addPixmap(pixmap) self.cover.set_pixmap(pixmap)
self.cover.setScene(self.scene)
def refresh(self, row): def refresh(self, row):
if isinstance(row, QModelIndex): if isinstance(row, QModelIndex):

View File

@ -25,7 +25,7 @@
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QGraphicsView" name="cover"/> <widget class="CoverView" name="cover"/>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@ -115,6 +115,11 @@
<extends>QWidget</extends> <extends>QWidget</extends>
<header>QtWebKit/QWebView</header> <header>QtWebKit/QWebView</header>
</customwidget> </customwidget>
<customwidget>
<class>CoverView</class>
<extends>QGraphicsView</extends>
<header>calibre/gui2/widgets.h</header>
</customwidget>
</customwidgets> </customwidgets>
<resources> <resources>
<include location="../../../../resources/images.qrc"/> <include location="../../../../resources/images.qrc"/>

View File

@ -11,9 +11,9 @@ from PyQt4.Qt import QIcon, QFont, QLabel, QListWidget, QAction, \
QPixmap, QSplitterHandle, QToolButton, \ QPixmap, QSplitterHandle, QToolButton, \
QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, \ QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, \
QRegExp, QSettings, QSize, QSplitter, \ QRegExp, QSettings, QSize, QSplitter, \
QPainter, QLineEdit, QComboBox, QPen, \ QPainter, QLineEdit, QComboBox, QPen, QGraphicsScene, \
QMenu, QStringListModel, QCompleter, QStringList, \ 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 import NONE, error_dialog, pixmap_to_data, gprefs
from calibre.gui2.filename_pattern_ui import Ui_Form from calibre.gui2.filename_pattern_ui import Ui_Form
@ -181,22 +181,16 @@ class FormatList(QListWidget):
else: else:
return QListWidget.keyPressEvent(self, event) return QListWidget.keyPressEvent(self, event)
class ImageDropMixin(object): # {{{
class ImageView(QWidget): '''
Adds support for dropping images onto widgets and a contect menu for
BORDER_WIDTH = 1 copy/pasting images.
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 {{{
DROPABBLE_EXTENSIONS = IMAGE_EXTENSIONS DROPABBLE_EXTENSIONS = IMAGE_EXTENSIONS
def __init__(self):
self.setAcceptDrops(True)
@classmethod @classmethod
def paths_from_event(cls, event): def paths_from_event(cls, event):
''' '''
@ -223,14 +217,58 @@ class ImageView(QWidget):
pmap = QPixmap() pmap = QPixmap()
pmap.load(path) pmap.load(path)
if not pmap.isNull(): if not pmap.isNull():
self.setPixmap(pmap) self.handle_image_drop(path, pmap)
event.accept() event.accept()
self.cover_changed.emit(open(path, 'rb').read())
break break
def handle_image_drop(self, path, pmap):
self.set_pixmap(pmap)
self.cover_changed.emit(open(path, 'rb').read())
def dragMoveEvent(self, event): def dragMoveEvent(self, event):
event.acceptProposedAction() 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): def setPixmap(self, pixmap):
if not isinstance(pixmap, QPixmap): if not isinstance(pixmap, QPixmap):
@ -272,32 +310,23 @@ class ImageView(QWidget):
p.drawRect(target) p.drawRect(target)
p.end() p.end()
class CoverView(QGraphicsView, ImageDropMixin):
# Clipboard copy/paste # {{{ cover_changed = pyqtSignal(object)
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): def __init__(self, *args, **kwargs):
QApplication.instance().clipboard().setPixmap(self.pixmap()) QGraphicsView.__init__(self, *args, **kwargs)
ImageDropMixin.__init__(self)
def paste_from_clipboard(self): def get_pixmap(self):
cb = QApplication.instance().clipboard() for item in self.scene().items():
pmap = cb.pixmap() if hasattr(item, 'pixmap'):
if pmap.isNull() and cb.supportsSelection(): return item.pixmap()
pmap = cb.pixmap(cb.Selection)
if not pmap.isNull():
self.setPixmap(pmap)
self.cover_changed.emit(
pixmap_to_data(pmap))
# }}}
def set_pixmap(self, pmap):
self.scene = QGraphicsScene()
self.scene.addPixmap(pmap)
self.setScene(self.scene)
class FontFamilyModel(QAbstractListModel): class FontFamilyModel(QAbstractListModel):