mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 22:42:25 -04:00
dragging and dropping of urls
This commit is contained in:
parent
5a2ee8f30e
commit
fadf6fd249
4 changed files with 107 additions and 87 deletions
|
@ -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() +
|
||||||
|
|
160
aqt/editor.py
160
aqt/editor.py
|
@ -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."
|
||||||
|
|
26
aqt/utils.py
26
aqt/utils.py
|
@ -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":
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue