mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Improved support for dragging and dropping cover images directly from web browsers into calibre. You can drop the images onto the cover in calibre and it will be replaced. Drag and drop implementations in various browsers/OSes are very flaky, so your mileage may vary, depending on the site you are dragigng from, the browser you are using and your operating system. If it doesn't work you can alway right click and Copy Image, then right click and paste it in calibre
This commit is contained in:
parent
1920f3e7bd
commit
62105efcc9
@ -204,15 +204,29 @@ class AddAction(InterfaceAction):
|
||||
to_device = self.gui.stack.currentIndex() != 0
|
||||
self._add_books(paths, to_device)
|
||||
|
||||
def files_dropped_on_book(self, event, paths):
|
||||
def remote_file_dropped_on_book(self, url, fname):
|
||||
if self.gui.current_view() is not self.gui.library_view:
|
||||
return
|
||||
db = self.gui.library_view.model().db
|
||||
current_idx = self.gui.library_view.currentIndex()
|
||||
if not current_idx.isValid(): return
|
||||
cid = db.id(current_idx.row())
|
||||
from calibre.gui2.dnd import DownloadDialog
|
||||
d = DownloadDialog(url, fname, self.gui)
|
||||
d.start_download()
|
||||
if d.err is None:
|
||||
self.files_dropped_on_book(None, [d.fpath], cid=cid)
|
||||
|
||||
def files_dropped_on_book(self, event, paths, cid=None):
|
||||
accept = False
|
||||
if self.gui.current_view() is not self.gui.library_view:
|
||||
return
|
||||
db = self.gui.library_view.model().db
|
||||
cover_changed = False
|
||||
current_idx = self.gui.library_view.currentIndex()
|
||||
if not current_idx.isValid(): return
|
||||
cid = db.id(current_idx.row())
|
||||
if cid is None:
|
||||
if not current_idx.isValid(): return
|
||||
cid = db.id(current_idx.row()) if cid is None else cid
|
||||
for path in paths:
|
||||
ext = os.path.splitext(path)[1].lower()
|
||||
if ext:
|
||||
@ -227,8 +241,9 @@ class AddAction(InterfaceAction):
|
||||
elif ext in BOOK_EXTENSIONS:
|
||||
db.add_format_with_hooks(cid, ext, path, index_is_id=True)
|
||||
accept = True
|
||||
if accept:
|
||||
if accept and event is not None:
|
||||
event.accept()
|
||||
if current_idx.isValid():
|
||||
self.gui.library_view.model().current_changed(current_idx, current_idx)
|
||||
if cover_changed:
|
||||
if self.gui.cover_flow:
|
||||
|
@ -5,7 +5,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, collections, sys
|
||||
import collections, sys
|
||||
from Queue import Queue
|
||||
|
||||
from PyQt4.Qt import QPixmap, QSize, QWidget, Qt, pyqtSignal, QUrl, \
|
||||
@ -14,7 +14,8 @@ from PyQt4.Qt import QPixmap, QSize, QWidget, Qt, pyqtSignal, QUrl, \
|
||||
from PyQt4.QtWebKit import QWebView
|
||||
|
||||
from calibre import fit_image, prepare_string_for_xml
|
||||
from calibre.gui2.widgets import IMAGE_EXTENSIONS
|
||||
from calibre.gui2.dnd import dnd_has_image, dnd_get_image, dnd_get_files, \
|
||||
IMAGE_EXTENSIONS, dnd_has_extension
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.constants import preferred_encoding
|
||||
from calibre.library.comments import comments_to_html
|
||||
@ -165,11 +166,12 @@ class CoverView(QWidget): # {{{
|
||||
def copy_to_clipboard(self):
|
||||
QApplication.instance().clipboard().setPixmap(self.pixmap)
|
||||
|
||||
def paste_from_clipboard(self):
|
||||
cb = QApplication.instance().clipboard()
|
||||
pmap = cb.pixmap()
|
||||
if pmap.isNull() and cb.supportsSelection():
|
||||
pmap = cb.pixmap(cb.Selection)
|
||||
def paste_from_clipboard(self, pmap=None):
|
||||
if not isinstance(pmap, QPixmap):
|
||||
cb = QApplication.instance().clipboard()
|
||||
pmap = cb.pixmap()
|
||||
if pmap.isNull() and cb.supportsSelection():
|
||||
pmap = cb.pixmap(cb.Selection)
|
||||
if not pmap.isNull():
|
||||
self.pixmap = pmap
|
||||
self.do_layout()
|
||||
@ -226,6 +228,7 @@ class BookInfo(QWebView):
|
||||
self._link_clicked = False
|
||||
self.setAttribute(Qt.WA_OpaquePaintEvent, False)
|
||||
palette = self.palette()
|
||||
self.setAcceptDrops(False)
|
||||
palette.setBrush(QPalette.Base, Qt.transparent)
|
||||
self.page().setPalette(palette)
|
||||
|
||||
@ -388,36 +391,50 @@ class BookDetails(QWidget): # {{{
|
||||
show_book_info = pyqtSignal()
|
||||
open_containing_folder = pyqtSignal(int)
|
||||
view_specific_format = pyqtSignal(int, object)
|
||||
|
||||
# Drag 'n drop {{{
|
||||
DROPABBLE_EXTENSIONS = IMAGE_EXTENSIONS+BOOK_EXTENSIONS
|
||||
remote_file_dropped = pyqtSignal(object, object)
|
||||
files_dropped = pyqtSignal(object, object)
|
||||
cover_changed = pyqtSignal(object, object)
|
||||
|
||||
# application/x-moz-file-promise-url
|
||||
@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]
|
||||
# Drag 'n drop {{{
|
||||
DROPABBLE_EXTENSIONS = IMAGE_EXTENSIONS+BOOK_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:
|
||||
md = event.mimeData()
|
||||
if dnd_has_extension(md, self.DROPABBLE_EXTENSIONS) or \
|
||||
dnd_has_image(md):
|
||||
event.acceptProposedAction()
|
||||
|
||||
def dropEvent(self, event):
|
||||
paths = self.paths_from_event(event)
|
||||
event.setDropAction(Qt.CopyAction)
|
||||
self.files_dropped.emit(event, paths)
|
||||
md = event.mimeData()
|
||||
|
||||
x, y = dnd_get_image(md)
|
||||
if x is not None:
|
||||
# We have an image, set cover
|
||||
event.accept()
|
||||
if y is None:
|
||||
# Local image
|
||||
self.cover_view.paste_from_clipboard(x)
|
||||
else:
|
||||
self.remote_file_dropped.emit(x, y)
|
||||
# We do not support setting cover *and* adding formats for
|
||||
# a remote drop, anyway, so return
|
||||
return
|
||||
|
||||
# Now look for ebook files
|
||||
urls, filenames = dnd_get_files(md, BOOK_EXTENSIONS)
|
||||
if not urls:
|
||||
# Nothing found
|
||||
return
|
||||
|
||||
if not filenames:
|
||||
# Local files
|
||||
self.files_dropped.emit(event, urls)
|
||||
else:
|
||||
# Remote files, use the first file
|
||||
self.remote_file_dropped.emit(urls[0], filenames[0])
|
||||
event.accept()
|
||||
|
||||
|
||||
def dragMoveEvent(self, event):
|
||||
event.acceptProposedAction()
|
||||
|
325
src/calibre/gui2/dnd.py
Normal file
325
src/calibre/gui2/dnd.py
Normal file
@ -0,0 +1,325 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import posixpath, os, urllib, re
|
||||
from urlparse import urlparse, urlunparse
|
||||
from threading import Thread
|
||||
from Queue import Queue, Empty
|
||||
|
||||
from PyQt4.Qt import QPixmap, Qt, QDialog, QLabel, QVBoxLayout, \
|
||||
QDialogButtonBox, QProgressBar, QTimer
|
||||
|
||||
from calibre.constants import DEBUG, iswindows
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre import browser, as_unicode, prints
|
||||
from calibre.gui2 import error_dialog
|
||||
|
||||
IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'gif', 'png', 'bmp']
|
||||
|
||||
class Worker(Thread): # {{{
|
||||
|
||||
def __init__(self, url, fpath, rq):
|
||||
Thread.__init__(self)
|
||||
self.url, self.fpath = url, fpath
|
||||
self.daemon = True
|
||||
self.rq = rq
|
||||
self.err = self.tb = None
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
br = browser()
|
||||
br.retrieve(self.url, self.fpath, self.callback)
|
||||
except Exception, e:
|
||||
self.err = as_unicode(e)
|
||||
import traceback
|
||||
self.tb = traceback.format_exc()
|
||||
|
||||
def callback(self, a, b, c):
|
||||
self.rq.put((a, b, c))
|
||||
# }}}
|
||||
|
||||
class DownloadDialog(QDialog): # {{{
|
||||
|
||||
def __init__(self, url, fname, parent):
|
||||
QDialog.__init__(self, parent)
|
||||
self.setWindowTitle(_('Download %s')%fname)
|
||||
self.l = QVBoxLayout(self)
|
||||
self.purl = urlparse(url)
|
||||
self.msg = QLabel(_('Downloading <b>%s</b> from %s')%(fname,
|
||||
self.purl.netloc))
|
||||
self.msg.setWordWrap(True)
|
||||
self.l.addWidget(self.msg)
|
||||
self.pb = QProgressBar(self)
|
||||
self.pb.setMinimum(0)
|
||||
self.pb.setMaximum(0)
|
||||
self.l.addWidget(self.pb)
|
||||
self.bb = QDialogButtonBox(QDialogButtonBox.Cancel, Qt.Horizontal, self)
|
||||
self.l.addWidget(self.bb)
|
||||
self.bb.rejected.connect(self.reject)
|
||||
sz = self.sizeHint()
|
||||
self.resize(max(sz.width(), 400), sz.height())
|
||||
|
||||
fpath = PersistentTemporaryFile(os.path.splitext(fname)[1])
|
||||
fpath.close()
|
||||
self.fpath = fpath.name
|
||||
|
||||
self.worker = Worker(url, self.fpath, Queue())
|
||||
self.rejected = False
|
||||
|
||||
def reject(self):
|
||||
self.rejected = True
|
||||
QDialog.reject(self)
|
||||
|
||||
def start_download(self):
|
||||
self.worker.start()
|
||||
QTimer.singleShot(50, self.update)
|
||||
self.exec_()
|
||||
if self.worker.err is not None:
|
||||
error_dialog(self.parent(), _('Download failed'),
|
||||
_('Failed to download from %r with error: %s')%(
|
||||
self.worker.url, self.worker.err),
|
||||
det_msg=self.worker.tb, show=True)
|
||||
|
||||
def update(self):
|
||||
if self.rejected:
|
||||
return
|
||||
|
||||
try:
|
||||
progress = self.worker.rq.get_nowait()
|
||||
except Empty:
|
||||
pass
|
||||
else:
|
||||
self.update_pb(progress)
|
||||
|
||||
if not self.worker.is_alive():
|
||||
return self.accept()
|
||||
QTimer.singleShot(50, self.update)
|
||||
|
||||
def update_pb(self, progress):
|
||||
transferred, block_size, total = progress
|
||||
if total == -1:
|
||||
self.pb.setMaximum(0)
|
||||
self.pb.setMinimum(0)
|
||||
self.pb.setValue(0)
|
||||
else:
|
||||
so_far = transferred * block_size
|
||||
self.pb.setMaximum(max(total, so_far))
|
||||
self.pb.setValue(so_far)
|
||||
|
||||
@property
|
||||
def err(self):
|
||||
return self.worker.err
|
||||
|
||||
# }}}
|
||||
|
||||
def dnd_has_image(md):
|
||||
return md.hasImage()
|
||||
|
||||
def data_as_string(f, md):
|
||||
raw = bytes(md.data(f))
|
||||
if '/x-moz' in f:
|
||||
try:
|
||||
raw = raw.decode('utf-16')
|
||||
except:
|
||||
pass
|
||||
return raw
|
||||
|
||||
def dnd_has_extension(md, extensions):
|
||||
if DEBUG:
|
||||
prints('Debugging DND event')
|
||||
for f in md.formats():
|
||||
f = unicode(f)
|
||||
prints(f, repr(data_as_string(f, md))[:300], '\n')
|
||||
print ()
|
||||
if has_firefox_ext(md, extensions):
|
||||
return True
|
||||
if md.hasUrls():
|
||||
urls = [unicode(u.toString()) for u in
|
||||
md.urls()]
|
||||
purls = [urlparse(u) for u in urls]
|
||||
if DEBUG:
|
||||
prints('URLS:', urls)
|
||||
prints('Paths:', [u2p(x) for x in purls])
|
||||
|
||||
exts = frozenset([posixpath.splitext(u.path)[1][1:].lower() for u in
|
||||
purls])
|
||||
return bool(exts.intersection(frozenset(extensions)))
|
||||
return False
|
||||
|
||||
def u2p(url):
|
||||
path = url.path
|
||||
if iswindows:
|
||||
if path.startswith('/'):
|
||||
path = path[1:]
|
||||
ans = path.replace('/', os.sep)
|
||||
if os.path.exists(ans):
|
||||
return ans
|
||||
# Try unquoting the URL
|
||||
return urllib.unquote(ans)
|
||||
|
||||
def dnd_get_image(md, image_exts=IMAGE_EXTENSIONS):
|
||||
'''
|
||||
Get the image in the QMimeData object md.
|
||||
|
||||
:return: None, None if no image is found
|
||||
QPixmap, None if an image is found, the pixmap is guaranteed not
|
||||
null
|
||||
url, filename if a URL that points to an image is found
|
||||
'''
|
||||
if dnd_has_image(md):
|
||||
for x in md.formats():
|
||||
x = unicode(x)
|
||||
if x.startswith('image/'):
|
||||
cdata = bytes(md.data(x))
|
||||
pmap = QPixmap()
|
||||
pmap.loadFromData(cdata)
|
||||
if not pmap.isNull():
|
||||
return pmap, None
|
||||
break
|
||||
|
||||
# No image, look for a URL pointing to an image
|
||||
if md.hasUrls():
|
||||
urls = [unicode(u.toString()) for u in
|
||||
md.urls()]
|
||||
purls = [urlparse(u) for u in urls]
|
||||
# First look for a local file
|
||||
images = [u2p(x) for x in purls if x.scheme in ('', 'file') and
|
||||
posixpath.splitext(urllib.unquote(x.path))[1][1:].lower() in
|
||||
image_exts]
|
||||
images = [x for x in images if os.path.exists(x)]
|
||||
p = QPixmap()
|
||||
for path in images:
|
||||
try:
|
||||
with open(path, 'rb') as f:
|
||||
p.loadFromData(f.read())
|
||||
except:
|
||||
continue
|
||||
if not p.isNull():
|
||||
return p, None
|
||||
|
||||
# No local images, look for remote ones
|
||||
|
||||
# First, see if this is from Firefox
|
||||
rurl, fname = get_firefox_rurl(md, image_exts)
|
||||
|
||||
if rurl and fname:
|
||||
return rurl, fname
|
||||
# Look through all remaining URLs
|
||||
remote_urls = [x for x in purls if x.scheme in ('http', 'https',
|
||||
'ftp') and posixpath.splitext(x.path)[1][1:].lower() in image_exts]
|
||||
if remote_urls:
|
||||
rurl = remote_urls[0]
|
||||
fname = posixpath.basename(urllib.unquote(rurl.path))
|
||||
return urlunparse(rurl), fname
|
||||
|
||||
return None, None
|
||||
|
||||
def dnd_get_files(md, exts):
|
||||
'''
|
||||
Get the file in the QMimeData object md with an extension that is one of
|
||||
the extensions in exts.
|
||||
|
||||
:return: None, None if no file is found
|
||||
[paths], None if a local file is found
|
||||
[urls], [filenames] if URLs that point to a files are found
|
||||
'''
|
||||
# Look for a URL pointing to a file
|
||||
if md.hasUrls():
|
||||
urls = [unicode(u.toString()) for u in
|
||||
md.urls()]
|
||||
purls = [urlparse(u) for u in urls]
|
||||
# First look for a local file
|
||||
local_files = [u2p(x) for x in purls if x.scheme in ('', 'file') and
|
||||
posixpath.splitext(urllib.unquote(x.path))[1][1:].lower() in
|
||||
exts]
|
||||
local_files = [x for x in local_files if os.path.exists(x)]
|
||||
if local_files:
|
||||
return local_files, None
|
||||
|
||||
# No local files, look for remote ones
|
||||
|
||||
# First, see if this is from Firefox
|
||||
rurl, fname = get_firefox_rurl(md, exts)
|
||||
if rurl and fname:
|
||||
return [rurl], [fname]
|
||||
|
||||
# Look through all remaining URLs
|
||||
remote_urls = [x for x in purls if x.scheme in ('http', 'https',
|
||||
'ftp') and posixpath.splitext(x.path)[1][1:].lower() in exts]
|
||||
if remote_urls:
|
||||
filenames = [posixpath.basename(urllib.unquote(rurl.path)) for rurl in
|
||||
remote_urls]
|
||||
return [urlunparse(x) for x in remote_urls], filenames
|
||||
|
||||
return None, None
|
||||
|
||||
def _get_firefox_pair(md, exts, url, fname):
|
||||
url = bytes(md.data(url)).decode('utf-16')
|
||||
fname = bytes(md.data(fname)).decode('utf-16')
|
||||
while url.endswith('\x00'):
|
||||
url = url[:-1]
|
||||
while fname.endswith('\x00'):
|
||||
fname = fname[:-1]
|
||||
if not url or not fname:
|
||||
return None, None
|
||||
ext = posixpath.splitext(fname)[1][1:].lower()
|
||||
# Weird firefox bug on linux
|
||||
ext = {'jpe':'jpg', 'epu':'epub', 'mob':'mobi'}.get(ext, ext)
|
||||
fname = os.path.splitext(fname)[0] + '.' + ext
|
||||
if DEBUG:
|
||||
prints('Firefox file promise:', url, fname)
|
||||
if ext not in exts:
|
||||
fname = url = None
|
||||
return url, fname
|
||||
|
||||
|
||||
def get_firefox_rurl(md, exts):
|
||||
formats = frozenset([unicode(x) for x in md.formats()])
|
||||
url = fname = None
|
||||
if 'application/x-moz-file-promise-url' in formats and \
|
||||
'application/x-moz-file-promise-dest-filename' in formats:
|
||||
try:
|
||||
url, fname = _get_firefox_pair(md, exts,
|
||||
'application/x-moz-file-promise-url',
|
||||
'application/x-moz-file-promise-dest-filename')
|
||||
except:
|
||||
if DEBUG:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
if url is None and 'text/x-moz-url-data' in formats and \
|
||||
'text/x-moz-url-desc' in formats:
|
||||
try:
|
||||
url, fname = _get_firefox_pair(md, exts,
|
||||
'text/x-moz-url-data', 'text/x-moz-url-desc')
|
||||
except:
|
||||
if DEBUG:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if url is None and '_NETSCAPE_URL' in formats:
|
||||
try:
|
||||
raw = bytes(md.data('_NETSCAPE_URL'))
|
||||
raw = raw.decode('utf-8')
|
||||
lines = raw.splitlines()
|
||||
if len(lines) > 1 and re.match(r'[a-z]+://', lines[1]) is None:
|
||||
url, fname = lines[:2]
|
||||
ext = posixpath.splitext(fname)[1][1:].lower()
|
||||
if ext not in exts:
|
||||
fname = url = None
|
||||
except:
|
||||
if DEBUG:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
if DEBUG:
|
||||
prints('Firefox rurl:', url, fname)
|
||||
return url, fname
|
||||
|
||||
def has_firefox_ext(md, exts):
|
||||
return bool(get_firefox_rurl(md, exts)[0])
|
||||
|
@ -264,6 +264,9 @@ class LayoutMixin(object): # {{{
|
||||
self.book_details.files_dropped.connect(self.iactions['Add Books'].files_dropped_on_book)
|
||||
self.book_details.cover_changed.connect(self.bd_cover_changed,
|
||||
type=Qt.QueuedConnection)
|
||||
self.book_details.remote_file_dropped.connect(
|
||||
self.iactions['Add Books'].remote_file_dropped_on_book,
|
||||
type=Qt.QueuedConnection)
|
||||
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)
|
||||
|
||||
|
@ -3,7 +3,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
'''
|
||||
Miscellaneous widgets used in the GUI
|
||||
'''
|
||||
import re, os, traceback
|
||||
import re, traceback
|
||||
|
||||
from PyQt4.Qt import QIcon, QFont, QLabel, QListWidget, QAction, \
|
||||
QListWidgetItem, QTextCharFormat, QApplication, \
|
||||
@ -22,6 +22,8 @@ from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.ebooks.metadata.meta import metadata_from_filename
|
||||
from calibre.utils.config import prefs, XMLConfig, tweaks
|
||||
from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator
|
||||
from calibre.gui2.dnd import dnd_has_image, dnd_get_image, dnd_get_files, \
|
||||
IMAGE_EXTENSIONS, dnd_has_extension, DownloadDialog
|
||||
|
||||
history = XMLConfig('history')
|
||||
|
||||
@ -141,36 +143,35 @@ class FilenamePattern(QWidget, Ui_Form):
|
||||
return pat
|
||||
|
||||
|
||||
IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'gif', 'png', 'bmp']
|
||||
|
||||
class FormatList(QListWidget):
|
||||
DROPABBLE_EXTENSIONS = BOOK_EXTENSIONS
|
||||
formats_dropped = pyqtSignal(object, object)
|
||||
delete_format = pyqtSignal()
|
||||
|
||||
@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:
|
||||
md = event.mimeData()
|
||||
if dnd_has_extension(md, self.DROPABBLE_EXTENSIONS):
|
||||
event.acceptProposedAction()
|
||||
|
||||
def dropEvent(self, event):
|
||||
paths = self.paths_from_event(event)
|
||||
event.setDropAction(Qt.CopyAction)
|
||||
self.formats_dropped.emit(event, paths)
|
||||
md = event.mimeData()
|
||||
# Now look for ebook files
|
||||
urls, filenames = dnd_get_files(md, self.DROPABBLE_EXTENSIONS)
|
||||
if not urls:
|
||||
# Nothing found
|
||||
return
|
||||
|
||||
if not filenames:
|
||||
# Local files
|
||||
self.formats_dropped.emit(event, urls)
|
||||
else:
|
||||
# Remote files, use the first file
|
||||
d = DownloadDialog(urls[0], filenames[0], self)
|
||||
d.start_download()
|
||||
if d.err is None:
|
||||
self.formats_dropped.emit(event, [d.fpath])
|
||||
|
||||
|
||||
def dragMoveEvent(self, event):
|
||||
event.acceptProposedAction()
|
||||
@ -183,7 +184,7 @@ class FormatList(QListWidget):
|
||||
|
||||
class ImageDropMixin(object): # {{{
|
||||
'''
|
||||
Adds support for dropping images onto widgets and a contect menu for
|
||||
Adds support for dropping images onto widgets and a context menu for
|
||||
copy/pasting images.
|
||||
'''
|
||||
DROPABBLE_EXTENSIONS = IMAGE_EXTENSIONS
|
||||
@ -191,39 +192,36 @@ class ImageDropMixin(object): # {{{
|
||||
def __init__(self):
|
||||
self.setAcceptDrops(True)
|
||||
|
||||
@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:
|
||||
md = event.mimeData()
|
||||
if dnd_has_extension(md, self.DROPABBLE_EXTENSIONS) or \
|
||||
dnd_has_image(md):
|
||||
event.acceptProposedAction()
|
||||
|
||||
def dropEvent(self, event):
|
||||
paths = self.paths_from_event(event)
|
||||
event.setDropAction(Qt.CopyAction)
|
||||
for path in paths:
|
||||
pmap = QPixmap()
|
||||
pmap.load(path)
|
||||
if not pmap.isNull():
|
||||
self.handle_image_drop(path, pmap)
|
||||
event.accept()
|
||||
break
|
||||
md = event.mimeData()
|
||||
|
||||
def handle_image_drop(self, path, pmap):
|
||||
x, y = dnd_get_image(md)
|
||||
if x is not None:
|
||||
# We have an image, set cover
|
||||
event.accept()
|
||||
if y is None:
|
||||
# Local image
|
||||
self.handle_image_drop(x)
|
||||
else:
|
||||
# Remote files, use the first file
|
||||
d = DownloadDialog(x, y, self)
|
||||
d.start_download()
|
||||
if d.err is None:
|
||||
pmap = QPixmap()
|
||||
pmap.loadFromData(open(d.fpath, 'rb').read())
|
||||
if not pmap.isNull():
|
||||
self.handle_image_drop(pmap)
|
||||
|
||||
def handle_image_drop(self, pmap):
|
||||
self.set_pixmap(pmap)
|
||||
self.cover_changed.emit(open(path, 'rb').read())
|
||||
self.cover_changed.emit(pixmap_to_data(pmap))
|
||||
|
||||
def dragMoveEvent(self, event):
|
||||
event.acceptProposedAction()
|
||||
|
Loading…
x
Reference in New Issue
Block a user