dragging and dropping of urls

This commit is contained in:
Damien Elmes 2011-04-04 22:02:40 +09:00
parent 5a2ee8f30e
commit fadf6fd249
4 changed files with 107 additions and 87 deletions

View file

@ -215,7 +215,7 @@ class CardLayout(QDialog):
styles = self.model.genCSS() styles = self.model.genCSS()
self.form.preview.setHtml( self.form.preview.setHtml(
('<html><head>%s</head><body class="%s">' % ('<html><head>%s</head><body class="%s">' %
(getBase(self.deck, c), c.cssClass())) + (getBase(self.deck), c.cssClass())) +
"<style>" + styles + "</style>" + "<style>" + styles + "</style>" +
mungeQA(c.q(reload=True)) + mungeQA(c.q(reload=True)) +
self.maybeTextInput() + self.maybeTextInput() +

View file

@ -5,19 +5,18 @@
from PyQt4.QtGui import * from PyQt4.QtGui import *
from PyQt4.QtCore import * from PyQt4.QtCore import *
from PyQt4.QtSvg import * # fixme: obsolete? from PyQt4.QtSvg import * # fixme: obsolete?
from PyQt4.QtWebKit import QWebView
import re, os, sys, tempfile, urllib2, ctypes, simplejson import re, os, sys, tempfile, urllib2, ctypes, simplejson
from anki.utils import stripHTML from anki.utils import stripHTML
from anki.sound import play from anki.sound import play
from anki.hooks import addHook, removeHook, runHook, runFilter from anki.hooks import addHook, removeHook, runHook, runFilter
from aqt.sound import getAudio from aqt.sound import getAudio
from aqt.webview import AnkiWebView from aqt.webview import AnkiWebView
from aqt.utils import shortcut, showInfo from aqt.utils import shortcut, showInfo, showWarning, getBase
import anki.js import anki.js
# fixme: add code to escape from text field
_html = """ _html = """
<html><head><style> <html><head>%s<style>
.field { .field {
border: 1px solid #aaa; background:#fff; color:#000; padding: 5px; border: 1px solid #aaa; background:#fff; color:#000; padding: 5px;
} }
@ -185,7 +184,7 @@ class Editor(object):
self.outerLayout = l self.outerLayout = l
def setupWeb(self): def setupWeb(self):
self.web = AnkiWebView(self.widget) self.web = EditorWebView(self.widget, self)
self.web.allowDrops = True self.web.allowDrops = True
self.web.setBridge(self.bridge) self.web.setBridge(self.bridge)
self.outerLayout.addWidget(self.web) self.outerLayout.addWidget(self.web)
@ -194,8 +193,6 @@ class Editor(object):
p.setBrush(QPalette.Base, Qt.transparent) p.setBrush(QPalette.Base, Qt.transparent)
self.web.page().setPalette(p) self.web.page().setPalette(p)
self.web.setAttribute(Qt.WA_OpaquePaintEvent, False) self.web.setAttribute(Qt.WA_OpaquePaintEvent, False)
self.web.setHtml(_html % anki.js.all,
loadCB=self._loadFinished)
# Top buttons # Top buttons
###################################################################### ######################################################################
@ -361,7 +358,8 @@ class Editor(object):
self.changeTimer.stop() self.changeTimer.stop()
self.changeTimer = None self.changeTimer = None
if self.fact: if self.fact:
self.loadFact() self.web.setHtml(_html % (getBase(self.mw.deck), anki.js.all),
loadCB=self._loadFinished)
self.updateTags() self.updateTags()
else: else:
self.widget.hide() self.widget.hide()
@ -769,36 +767,60 @@ to enable recording.'''), parent=self.widget)
cur.setPosition(pos+4) cur.setPosition(pos+4)
w.setTextCursor(cur) w.setTextCursor(cur)
# Pasting, drag & drop, and keyboard layouts
######################################################################
class FactEdit(QTextEdit): class EditorWebView(AnkiWebView):
def __init__(self, parent, *args): pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif")
QTextEdit.__init__(self, *args) audio = ("wav", "mp3", "ogg", "flac")
self.parent = parent
self._tmpDir = None
if sys.platform.startswith("win32"):
self._ownLayout = None
def canInsertFromMimeData(self, source): def __init__(self, parent, editor):
return (source.hasUrls() or AnkiWebView.__init__(self, parent)
source.hasText() or self.editor = editor
source.hasImage() or self.__tmpDir = None
source.hasHtml()) self.errtxt = _("An error occured while opening %s")
# if sys.platform.startswith("win32"):
# self._ownLayout = None
def insertFromMimeData(self, source): # after the drop/copy, make sure data updated?
if self._insertFromMimeData(source):
self.emit(SIGNAL("lostFocus"))
def _insertFromMimeData(self, source): def keyPressEvent(self, evt):
pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif") self._curKey = True
audio = ("wav", "mp3", "ogg", "flac") return QWebView.keyPressEvent(self, evt)
errtxt = _("An error occured while opening %s")
if source.hasHtml() and "qrichtext" in unicode(source.html()): def contextMenuEvent(self, evt):
self.insertHtml(source.html()) QWebView.contextMenuEvent(self, evt)
return True
if source.hasText() and (self.mw.config['stripHTML'] or def dropEvent(self, evt):
not source.hasHtml()): oldmime = evt.mimeData()
txt = unicode(source.text()) # coming from us?
if evt.source() == self:
# if they're copying just an image, we need to turn it into html
# again
txt = ""
if not oldmime.hasHtml() and oldmime.hasUrls():
# qt gives it to us twice
txt += '<img src="%s">' % os.path.basename(
unicode(oldmime.urls()[0].toString()))
mime = QMimeData()
mime.setHtml(txt)
else:
mime = self._processMime(oldmime)
# create a new event with the new mime data
new = QDropEvent(evt.pos(), evt.possibleActions(), mime,
evt.mouseButtons(), evt.keyboardModifiers())
evt.accept()
QWebView.dropEvent(self, new)
def _processMime(self, mime):
print "html=%s image=%s urls=%s txt=%s" % (
mime.hasHtml(), mime.hasImage(), mime.hasUrls(), mime.hasText())
if mime.hasUrls():
return self._processUrls(mime)
if mime.hasText() and (self.mw.config['stripHTML'] or
not mime.hasHtml()):
txt = unicode(mime.text())
l = txt.lower() l = txt.lower()
if l.startswith("http://") or l.startswith("file://"): if l.startswith("http://") or l.startswith("file://"):
hadN = False hadN = False
@ -808,7 +830,7 @@ class FactEdit(QTextEdit):
if "\r" in txt: if "\r" in txt:
txt = txt.split("\r")[0] txt = txt.split("\r")[0]
hadN = True hadN = True
if not source.hasImage() or hadN: if not mime.hasImage() or hadN:
# firefox on linux just gives us a url # firefox on linux just gives us a url
ext = txt.split(".")[-1].lower() ext = txt.split(".")[-1].lower()
try: try:
@ -820,15 +842,15 @@ class FactEdit(QTextEdit):
self.parent._addSound(name, widget=self) self.parent._addSound(name, widget=self)
else: else:
# not image or sound, treat as plain text # not image or sound, treat as plain text
self.insertPlainText(source.text()) self.insertPlainText(mime.text())
return True return True
except urllib2.URLError, e: except urllib2.URLError, e:
ui.utils.showWarning(errtxt % e) ui.utils.showWarning(errtxt % e)
else: else:
self.insertPlainText(source.text()) self.insertPlainText(mime.text())
return True return True
if source.hasImage(): if mime.hasImage():
im = QImage(source.imageData()) im = QImage(mime.imageData())
if im.hasAlphaChannel(): if im.hasAlphaChannel():
(fd, name) = tempfile.mkstemp(prefix="paste", suffix=".png") (fd, name) = tempfile.mkstemp(prefix="paste", suffix=".png")
uname = unicode(name, sys.getfilesystemencoding()) uname = unicode(name, sys.getfilesystemencoding())
@ -839,39 +861,49 @@ class FactEdit(QTextEdit):
im.save(uname, None, 95) im.save(uname, None, 95)
self.parent._addPicture(uname, widget=self) self.parent._addPicture(uname, widget=self)
return True return True
if source.hasUrls(): if mime.hasHtml():
for url in source.urls(): self.insertHtml(self.simplifyHTML(unicode(mime.html())))
url = unicode(url.toString())
ext = url.split(".")[-1].lower()
try:
if ext in pics:
name = self._retrieveURL(url)
self.parent._addPicture(name, widget=self)
elif ext in audio:
name = self._retrieveURL(url)
self.parent._addSound(name, widget=self)
except urllib2.URLError, e:
ui.utils.showWarning(errtxt % e)
return True
if source.hasHtml():
self.insertHtml(self.simplifyHTML(unicode(source.html())))
return True return True
def _processUrls(self, mime):
links = []
for url in mime.urls():
url = unicode(url.toString())
link = self._retrieveURL(url)
if link:
links.append(link)
mime = QMimeData()
mime.setHtml("".join(links))
return mime
def _retrieveURL(self, url): def _retrieveURL(self, url):
req = urllib2.Request(url, None, { # fetch it into a temporary folder
'User-Agent': 'Mozilla/5.0 (compatible; Anki/%s)' % try:
aqt.appVersion }) req = urllib2.Request(url, None, {
filecontents = urllib2.urlopen(req).read() 'User-Agent': 'Mozilla/5.0 (compatible; Anki)'})
path = os.path.join(self.tmpDir(), os.path.basename(url)) filecontents = urllib2.urlopen(req).read()
except urllib2.URLError, e:
showWarning(self.errtxt % e)
return
path = os.path.join(self._tmpDir(), os.path.basename(url))
file = open(path, "wb") file = open(path, "wb")
file.write(filecontents) file.write(filecontents)
file.close() file.close()
return path # copy to media folder
name = self.editor.mw.deck.media.addFile(path)
print "name was", name
# return a local html link
ext = name.split(".")[-1].lower()
if ext in self.pics:
return '<img src="%s">' % name
else:
# FIXME: should also autoplay audio
return '[sound:%s]' % name
def tmpDir(self): def _tmpDir(self):
if not self._tmpDir: if not self.__tmpDir:
self._tmpDir = tempfile.mkdtemp(prefix="anki") self.__tmpDir = tempfile.mkdtemp(prefix="anki")
return self._tmpDir return self.__tmpDir
def simplifyHTML(self, html): def simplifyHTML(self, html):
"Remove all style information and P tags." "Remove all style information and P tags."

View file

@ -264,25 +264,17 @@ def applyStyles(widget):
except (IOError, OSError): except (IOError, OSError):
pass pass
def getBase(deck, card): def getBase(deck):
base = None base = None
if deck and card: mdir = deck.media.dir(create=None)
print "fixme: remote images" if isWin:
mdir = deck.media.dir() prefix = u"file:///"
if False: # deck.getBool("remoteImages") and card.fact.model.features:
pass #base = card.fact.model.features
elif mdir:
if isWin:
prefix = u"file:///"
else:
prefix = u"file://"
base = prefix + unicode(
urllib.quote(mdir.encode("utf-8")),
"utf-8") + "/"
if base:
return '<base href="%s">' % base
else: else:
return "" prefix = u"file://"
base = prefix + unicode(
urllib.quote(mdir.encode("utf-8")),
"utf-8") + "/"
return '<base href="%s">' % base
def openFolder(path): def openFolder(path):
if sys.platform == "win32": if sys.platform == "win32":

View file

@ -56,9 +56,6 @@ class AnkiWebView(QWebView):
self._curKey = None self._curKey = None
self.allowDrops = False self.allowDrops = False
def keyPressEvent(self, evt): def keyPressEvent(self, evt):
if evt.matches(QKeySequence.Copy):
self.triggerPageAction(QWebPage.Copy)
evt.accept()
self._curKey = True self._curKey = True
return QWebView.keyPressEvent(self, evt) return QWebView.keyPressEvent(self, evt)
def keyReleaseEvent(self, evt): def keyReleaseEvent(self, evt):
@ -75,8 +72,7 @@ class AnkiWebView(QWebView):
def contextMenuEvent(self, evt): def contextMenuEvent(self, evt):
QWebView.contextMenuEvent(self, evt) QWebView.contextMenuEvent(self, evt)
def dropEvent(self, evt): def dropEvent(self, evt):
if self.allowDrops: pass
QWebView.dropEvent(self, evt)
def setLinkHandler(self, handler=None): def setLinkHandler(self, handler=None):
if handler: if handler:
self.linkHandler = handler self.linkHandler = handler