# -*- coding: utf-8 -*- # Copyright: Damien Elmes # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html from aqt.qt import * import re, os, sys, urllib2, ctypes, simplejson, traceback, urllib2 from anki.utils import stripHTML, isWin, isMac, namedtmp from anki.sound import play from anki.hooks import runHook, runFilter from aqt.sound import getAudio from aqt.webview import AnkiWebView from aqt.utils import shortcut, showInfo, showWarning, getBase, getFile, \ openHelp import aqt import anki.js from BeautifulSoup import BeautifulSoup # fixme: when tab order returns to the webview, the previously focused field # is focused, which is not good when the user is tabbing through the dialog # fixme: set rtl in div css # fixme: commit from tag area causes error pics = ("jpg", "jpeg", "png", "tif", "tiff", "gif") audio = ("wav", "mp3", "ogg", "flac") _html = """ %s
%s
""" def _filterHTML(html): doc = BeautifulSoup(html) # filter out implicit formatting from webkit for tag in doc("span", "Apple-style-span"): tag.replaceWithChildren() for tag in doc("font", "Apple-style-span"): tag.replaceWithChildren() # turn file:/// links into relative ones for tag in doc("img"): if tag['src'].lower().startswith("file://"): tag['src'] = os.path.basename(tag['src']) # strip superfluous elements for elem in "html", "head", "body", "meta": for tag in doc(elem): tag.replaceWithChildren() html = unicode(doc) return html # caller is responsible for resetting note on reset class Editor(object): def __init__(self, mw, widget, parentWindow, addMode=False): self.mw = mw self.widget = widget self.parentWindow = parentWindow self.note = None self.stealFocus = True self.addMode = addMode self._loaded = False self._keepButtons = False self.currentField = 0 # current card, for card layout self.card = None self.setupOuter() self.setupButtons() self.setupWeb() self.setupTagsAndDeck() self.setupKeyboard() # Initial setup ############################################################ def setupOuter(self): l = QVBoxLayout() l.setMargin(0) l.setSpacing(0) self.widget.setLayout(l) self.outerLayout = l def setupWeb(self): self.web = EditorWebView(self.widget, self) self.web.allowDrops = True self.web.setBridge(self.bridge) self.outerLayout.addWidget(self.web, 1) # pick up the window colour p = self.web.palette() p.setBrush(QPalette.Base, Qt.transparent) self.web.page().setPalette(p) self.web.setAttribute(Qt.WA_OpaquePaintEvent, False) # Top buttons ###################################################################### def _addButton(self, name, func, key=None, tip=None, size=True, text="", check=False, native=False, canDisable=True): b = QPushButton(text) if check: b.connect(b, SIGNAL("clicked(bool)"), func) else: b.connect(b, SIGNAL("clicked()"), func) if size: b.setFixedHeight(20) b.setFixedWidth(20) if not native: b.setStyle(self.plastiqueStyle) b.setFocusPolicy(Qt.NoFocus) else: b.setAutoDefault(False) if not text: b.setIcon(QIcon(":/icons/%s.png" % name)) if key: b.setShortcut(QKeySequence(key)) if tip: b.setToolTip(shortcut(tip)) if check: b.setCheckable(True) self.iconsBox.addWidget(b) if canDisable: self._buttons[name] = b return b def setupButtons(self): self._buttons = {} # button styles for mac self.plastiqueStyle = QStyleFactory.create("plastique") self.widget.setStyle(self.plastiqueStyle) # icons self.iconsBox = QHBoxLayout() if not isMac: self.iconsBox.setMargin(6) else: self.iconsBox.setMargin(0) self.iconsBox.setSpacing(0) self.outerLayout.addLayout(self.iconsBox) b = self._addButton b("fields", self.onFields, "", shortcut(_("Customize Fields")), size=False, text=_("Fields..."), native=True, canDisable=False) b("layout", self.onCardLayout, _("Ctrl+L"), shortcut(_("Customize Cards (Ctrl+L)")), size=False, text=_("Cards..."), native=True, canDisable=False) # align to right self.iconsBox.addItem(QSpacerItem(20,1, QSizePolicy.Expanding)) b("text_bold", self.toggleBold, _("Ctrl+B"), _("Bold text (Ctrl+B)"), check=True) b("text_italic", self.toggleItalic, _("Ctrl+I"), _("Italic text (Ctrl+I)"), check=True) b("text_under", self.toggleUnderline, _("Ctrl+U"), _("Underline text (Ctrl+U)"), check=True) b("text_super", self.toggleSuper, _("Ctrl+="), _("Superscript (Ctrl+=)"), check=True) b("text_sub", self.toggleSub, _("Ctrl+Shift+="), _("Subscript (Ctrl+Shift+=)"), check=True) b("text_clear", self.removeFormat, _("Ctrl+R"), _("Remove formatting (Ctrl+R)")) but = b("foreground", self.onForeground, _("F7"), text=" ") but.setToolTip(_("Set foreground colour (F7)")) self.setupForegroundButton(but) but = b("cloze", self.onCloze, _("Ctrl+Shift+C"), _("Cloze deletion (Ctrl+Shift+C)"), text="[...]") but.setFixedWidth(24) s = self.clozeShortcut2 = QShortcut( QKeySequence(_("Ctrl+Alt+Shift+C")), self.parentWindow) s.connect(s, SIGNAL("activated()"), self.onCloze) # fixme: better image names b("mail-attachment", self.onAddMedia, _("F3"), _("Attach pictures/audio/video (F3)")) b("media-record", self.onRecSound, _("F5"), _("Record audio (F5)")) b("adv", self.onAdvanced, text=u"▾") s = QShortcut(QKeySequence("Ctrl+T, T"), self.widget) s.connect(s, SIGNAL("activated()"), self.insertLatex) s = QShortcut(QKeySequence("Ctrl+T, E"), self.widget) s.connect(s, SIGNAL("activated()"), self.insertLatexEqn) s = QShortcut(QKeySequence("Ctrl+T, M"), self.widget) s.connect(s, SIGNAL("activated()"), self.insertLatexMathEnv) s = QShortcut(QKeySequence("Ctrl+Shift+X"), self.widget) s.connect(s, SIGNAL("activated()"), self.onHtmlEdit) def enableButtons(self, val=True): for b in self._buttons.values(): b.setEnabled(val) def disableButtons(self): self.enableButtons(False) def onFields(self): from aqt.fields import FieldDialog self.saveNow() FieldDialog(self.mw, self.note, parent=self.parentWindow) def onCardLayout(self): from aqt.clayout import CardLayout self.saveNow() if self.card: ord = self.card.ord else: ord = 0 CardLayout(self.mw, self.note, ord=ord, parent=self.parentWindow, addMode=self.addMode) self.loadNote() # JS->Python bridge ###################################################################### def bridge(self, str): if not self.note or not runHook: # shutdown return # focus lost or key/button pressed? if str.startswith("blur") or str.startswith("key"): (type, txt) = str.split(":", 1) txt = self.mungeHTML(txt) # misbehaving apps may include a null byte in the text txt = txt.replace("\x00", "") # reverse the url quoting we added to get images to display txt = unicode(urllib2.unquote(txt.encode("utf")), "utf8") self.note.fields[self.currentField] = txt self.mw.requireReset() if not self.addMode: self.note.flush() if type == "blur": if not self._keepButtons: self.disableButtons() # run any filters if runFilter( "editFocusLost", False, self.note, self.currentField): # something updated the note; schedule reload def onUpdate(): self.loadNote() self.checkValid() self.mw.progress.timer(100, onUpdate, False) else: self.checkValid() else: runHook("editTimer", self.note) self.checkValid() # focused into field? elif str.startswith("focus"): (type, num) = str.split(":", 1) self.enableButtons() self.currentField = int(num) # state buttons changed? elif str.startswith("state"): (cmd, txt) = str.split(":", 1) r = simplejson.loads(txt) self._buttons['text_bold'].setChecked(r['bold']) self._buttons['text_italic'].setChecked(r['italic']) self._buttons['text_under'].setChecked(r['under']) self._buttons['text_super'].setChecked(r['super']) self._buttons['text_sub'].setChecked(r['sub']) elif str.startswith("dupes"): self.showDupes() else: print str def mungeHTML(self, txt): if txt == "
": txt = "" return _filterHTML(txt) # Setting/unsetting the current note ###################################################################### def _loadFinished(self, w): self._loaded = True if self.note: self.loadNote() def setNote(self, note, hide=True, focus=False): "Make NOTE the current note." self.note = note self.currentField = 0 # change timer if self.note: self.web.setHtml(_html % (getBase(self.mw.col), anki.js.jquery, (isMac or isWin) and 1 or 0, _("Show Duplicates")), loadCB=self._loadFinished) self.updateTagsAndDeck() self.updateKeyboard() else: self.hideCompleters() if hide: self.widget.hide() def loadNote(self): if self.stealFocus: field = self.currentField else: field = -1 if not self._loaded: # will be loaded when page is ready return data = [] for fld, val in self.note.items(): data.append((fld, self.mw.col.media.escapeImages(val))) self.web.eval("setFields(%s, %d);" % ( simplejson.dumps(data), field)) self.web.eval("setFonts(%s);" % ( simplejson.dumps(self.fonts()))) self.checkValid() self.widget.show() if self.stealFocus: self.web.setFocus() def focus(self): self.web.setFocus() def fonts(self): return [(f['font'], f['size'], f['rtl']) for f in self.note.model()['flds']] def saveNow(self): "Must call this before adding cards, closing dialog, etc." if not self.note: return self._keepButtons = True self.web.eval("saveField('blur');") self._keepButtons = False self.saveTagsAndDeck() def checkValid(self): cols = [] err = None for f in self.note.fields: cols.append("#fff") err = self.note.dupeOrEmpty() if err == 2: cols[0] = "#fcc" self.web.eval("showDupes();") else: self.web.eval("hideDupes();") self.web.eval("setBackgrounds(%s);" % simplejson.dumps(cols)) def showDupes(self): contents = self.note.fields[0] browser = aqt.dialogs.open("Browser", self.mw) browser.form.searchEdit.lineEdit().setText( "'note:%s' '%s:%s'" % ( self.note.model()['name'], self.note.model()['flds'][0]['name'], contents)) browser.onSearch() def fieldsAreBlank(self): if not self.note: return True for f in self.note.fields: if f: return False return True # HTML editing ###################################################################### def onHtmlEdit(self): self.saveNow() d = QDialog(self.widget) form = aqt.forms.edithtml.Ui_Dialog() form.setupUi(d) d.connect(form.buttonBox, SIGNAL("helpRequested()"), lambda: openHelp("editor")) form.textEdit.setPlainText(self.note.fields[self.currentField]) form.textEdit.moveCursor(QTextCursor.End) d.exec_() html = form.textEdit.toPlainText() # filter html through beautifulsoup so we can strip out things like a # leading html = unicode(BeautifulSoup(html)) self.note.fields[self.currentField] = html self.loadNote() # Tag & deck handling ###################################################################### def setupTagsAndDeck(self): import aqt.tagedit g = QGroupBox(self.widget) g.setFlat(True) tb = QGridLayout() tb.setSpacing(12) tb.setMargin(6) # deck if self.addMode: l = QLabel(_("Deck")) tb.addWidget(l, 0, 0) self.deck = aqt.tagedit.TagEdit(self.widget, type=1) self.deck.connect(self.deck, SIGNAL("lostFocus"), self.saveTagsAndDeck) tb.addWidget(self.deck, 0, 1) else: self.deck = None # tags l = QLabel(_("Tags")) tb.addWidget(l, 1, 0) self.tags = aqt.tagedit.TagEdit(self.widget) self.tags.connect(self.tags, SIGNAL("lostFocus"), self.saveTagsAndDeck) tb.addWidget(self.tags, 1, 1) g.setLayout(tb) self.outerLayout.addWidget(g) def updateTagsAndDeck(self): if self.tags.col != self.mw.col: if self.deck: self.deck.setCol(self.mw.col) self.tags.setCol(self.mw.col) if self.addMode: self.deck.setText(self.mw.col.decks.name(self.note.model()['did'])) self.tags.setText(self.note.stringTags().strip()) def saveTagsAndDeck(self): if not self.note: return self.note.tags = self.mw.col.tags.split(self.tags.text()) if self.addMode: name = self.deck.text() if not name.strip(): self.note.model()['did'] = 1 else: self.note.model()['did'] = self.mw.col.decks.id(name) # save tags to model m = self.note.model() m['tags'] = self.note.tags self.mw.col.models.save(m) if not self.addMode: self.note.flush() runHook("tagsUpdated", self.note) def hideCompleters(self): self.tags.hideCompleter() if self.addMode: self.deck.hideCompleter() # Format buttons ###################################################################### def toggleBold(self, bool): self._eval("setFormat('bold');") def toggleItalic(self, bool): self._eval("setFormat('italic');") def toggleUnderline(self, bool): self._eval("setFormat('underline');") def toggleSuper(self, bool): self._eval("setFormat('superscript');") def toggleSub(self, bool): self._eval("setFormat('subscript');") def removeFormat(self): self._eval("setFormat('removeFormat');") def onCloze(self): # check that the model is set up for cloze deletion if '{{cloze:' not in self.note.model()['tmpls'][0]['qfmt']: openHelp("cloze") return f = self.note.fields[self.currentField] # find the highest existing cloze m = re.findall("\{\{c(\d+)::", f) if m: next = sorted([int(x) for x in m])[-1] + 1 if self.mw.app.keyboardModifiers() & Qt.AltModifier: next -= 1 else: next = 1 self._eval("wrap('{{c%d::', '}}');" % next) def _eval(self, str): # some versions of webkit crash if we try a dom-modifying operation # before outstanding UI events have been processed self.mw.app.processEvents() self.mw.progress.timer(100, lambda: self.web.eval(str), False) # Foreground colour ###################################################################### def setupForegroundButton(self, but): self.foregroundFrame = QFrame() self.foregroundFrame.setAutoFillBackground(True) self.colourChanged() hbox = QHBoxLayout() hbox.addWidget(self.foregroundFrame) hbox.setMargin(5) but.setLayout(hbox) def _updateForegroundButton(self, txtcol): self.foregroundFrame.setPalette(QPalette(QColor(txtcol))) self.foregroundFrame.setStyleSheet("* {background-color: %s}" % txtcol) def colourChanged(self): recent = self.mw.pm.profile['recentColours'] self._updateForegroundButton(recent[-1]) def onForeground(self): self.web.eval("saveSel();") class ColourPopup(QDialog): def __init__(self, parent): QDialog.__init__(self, parent, Qt.FramelessWindowHint) def event(self, evt): if evt.type() == QEvent.WindowDeactivate: self.close() return QDialog.event(self, evt) p = ColourPopup(self.widget) p.move(self.foregroundFrame.mapToGlobal(QPoint(0,0))) g = QGridLayout(p) g.setMargin(4) g.setSpacing(0) p.setLayout(g) lastWidget = None self.colourNext = QShortcut(QKeySequence("F7"), p) p.connect(self.colourNext, SIGNAL("activated()"), self.onNextColour) self.colourChoose = QShortcut(QKeySequence("F6"), p) p.connect(self.colourChoose, SIGNAL("activated()"), self.onChooseColourKey) for n, c in enumerate(reversed(self.mw.pm.profile['recentColours'])): col = QToolButton() col.setAutoRaise(True) col.setFixedWidth(64) col.setFixedHeight(16) col.setAutoFillBackground(True) col.setPalette(QPalette(QColor(c))) col.setStyleSheet("* {background-color: %s}" % c) col.connect(col, SIGNAL("clicked()"), lambda c=c: self.onChooseColour(c)) g.addWidget(col, n, 0) if lastWidget: p.setTabOrder(lastWidget, col) lastWidget = col but = QPushButton("X") but.setFixedWidth(16) but.setFixedHeight(16) but.setAutoDefault(False) but.connect(but, SIGNAL("clicked()"), lambda c=c: self.onRemoveColour(c)) g.addWidget(but, n, 1) spc = QSpacerItem(5,10, QSizePolicy.Fixed) g.addItem(spc, n+1, 0) cb = QPushButton(_("+")) cb.setShortcut(QKeySequence("F5")) cb.connect(cb, SIGNAL("clicked()"), self.onNewColour) cb.setFixedWidth(80) cb.setFixedHeight(16) cb.setAutoDefault(False) g.addWidget(cb, n+2, 0, 1, 2) self.colourDiag = p p.show() def onRemoveColour(self, colour): recent = self.mw.pm.profile['recentColours'] recent.remove(colour) if not recent: recent.append("#000000") self.colourDiag.close() self.onForeground() self.colourChanged() def onNextColour(self): self.colourDiag.focusWidget().nextInFocusChain().setFocus() def onChooseColourKey(self): try: self.colourDiag.focusWidget().click() except: # dialog focused pass def onChooseColour(self, colour): recent = self.mw.pm.profile['recentColours'] recent.remove(colour) recent.append(colour) self._eval("restoreSel(); wrap('', '');"% colour) self.colourDiag.close() self.colourChanged() def onNewColour(self): new = QColorDialog.getColor(Qt.white, self.widget) self.widget.raise_() recent = self.mw.pm.profile['recentColours'] if new.isValid(): txtcol = unicode(new.name()) if txtcol not in recent: recent.append(txtcol) self.colourChanged() self.onChooseColour(txtcol) # Audio/video/images ###################################################################### def onAddMedia(self): key = (_("Media") + " (*.jpg *.png *.gif *.tiff *.svg *.tif *.jpeg "+ "*.mp3 *.ogg *.wav *.avi *.ogv *.mpg *.mpeg *.mov *.mp4 " + "*.mkv *.ogx *.ogv *.oga *.flv *.swf *.flac)") def accept(file): self.addMedia(file, canDelete=True) file = getFile(self.widget, _("Add Media"), accept, key, key="media") def addMedia(self, path, canDelete=False): html = self._addMedia(path, canDelete) self._eval("setFormat('inserthtml', %s);" % simplejson.dumps(html)) def _addMedia(self, path, canDelete=False): "Add to media folder and return basename." # copy to media folder name = self.mw.col.media.addFile(path) # remove original? if canDelete and self.mw.pm.profile['deleteMedia']: if os.path.abspath(name) != os.path.abspath(path): try: os.unlink(old) except: pass # return a local html link ext = name.split(".")[-1].lower() if ext in pics: return '' % name else: anki.sound.play(name) return '[sound:%s]' % name def onRecSound(self): try: file = getAudio(self.widget) except Exception, e: showWarning(_( "Couldn't record audio. Have you installed lame and sox?") + "\n\n" + unicode(e)) return self.addMedia(file) # Advanced menu ###################################################################### def onAdvanced(self): m = QMenu(self.mw) a = m.addAction(_("LaTeX")) a.setShortcut(QKeySequence("Ctrl+T, T")) a.connect(a, SIGNAL("triggered()"), self.insertLatex) a = m.addAction(_("LaTeX equation")) a.setShortcut(QKeySequence("Ctrl+T, E")) a.connect(a, SIGNAL("triggered()"), self.insertLatexEqn) a = m.addAction(_("LaTeX math env.")) a.setShortcut(QKeySequence("Ctrl+T, M")) a.connect(a, SIGNAL("triggered()"), self.insertLatexMathEnv) a = m.addAction(_("Edit HTML")) a.setShortcut(QKeySequence("Ctrl+Shift+X")) a.connect(a, SIGNAL("triggered()"), self.onHtmlEdit) m.exec_(QCursor.pos()) # LaTeX ###################################################################### def insertLatex(self): self._eval("wrap('[latex]', '[/latex]');") def insertLatexEqn(self): self._eval("wrap('[$]', '[/$]');") def insertLatexMathEnv(self): self._eval("wrap('[$$]', '[/$$]');") # Keyboard layout ###################################################################### def setupKeyboard(self): if isWin and self.mw.pm.profile['preserveKeyboard']: a = ctypes.windll.user32.ActivateKeyboardLayout a.restype = ctypes.c_void_p a.argtypes = [ctypes.c_void_p, ctypes.c_uint] g = ctypes.windll.user32.GetKeyboardLayout g.restype = ctypes.c_void_p g.argtypes = [ctypes.c_uint] else: a = g = None self.activateKeyboard = a self.getKeyboard = g def updateKeyboard(self): self.keyboardLayouts = {} def saveKeyboard(self): if not self.getKeyboard: return self.keyboardLayouts[self.currentField] = self.getKeyboard(0) def restoreKeyboard(self): if not self.getKeyboard: return if self.currentField in self.keyboardLayouts: self.activateKeyboard(self.keyboardLayouts[self.currentField], 0) # Pasting, drag & drop, and keyboard layouts ###################################################################### class EditorWebView(AnkiWebView): def __init__(self, parent, editor): AnkiWebView.__init__(self) self.editor = editor self.errtxt = _("An error occured while opening %s") self.strip = self.editor.mw.pm.profile['stripHTML'] def keyPressEvent(self, evt): self._curKey = True self.origClip = None shiftPaste = (evt.modifiers() == (Qt.ShiftModifier | Qt.ControlModifier) and evt.key() == Qt.Key_V) if evt.matches(QKeySequence.Paste) or shiftPaste: self.prepareClip(shiftPaste) if shiftPaste: self.triggerPageAction(QWebPage.Paste) QWebView.keyPressEvent(self, evt) self.restoreClip() def mouseReleaseEvent(self, evt): if not isMac and not isWin and evt.button() == Qt.MidButton: # middle click on x11; munge the clipboard before standard # handling self.prepareClip(mode=QClipboard.Selection) AnkiWebView.mouseReleaseEvent(self, evt) self.restoreClip(mode=QClipboard.Selection) else: AnkiWebView.mouseReleaseEvent(self, evt) # Buggy; disable for now. # def contextMenuEvent(self, evt): # # adjust in case the user is going to paste # self.prepareClip() # QWebView.contextMenuEvent(self, evt) # self.restoreClip() def dropEvent(self, evt): oldmime = evt.mimeData() # coming from this program? if evt.source(): if oldmime.hasHtml(): mime = QMimeData() mime.setHtml(_filterHTML(oldmime.html())) else: # old qt on linux won't give us html when dragging an image; # in that case just do the default action (which is to ignore # the drag) return AnkiWebView.dropEvent(self, evt) 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 prepareClip(self, keep=False, mode=QClipboard.Clipboard): clip = self.editor.mw.app.clipboard() mime = clip.mimeData(mode=mode) self.saveClip(mode=mode) if keep: new = QMimeData() if mime.hasHtml(): new.setHtml(_filterHTML(mime.html())) else: new.setText(mime.text()) mime = new else: mime = self._processMime(mime) clip.setMimeData(mime, mode=mode) def restoreClip(self, mode=QClipboard.Clipboard): if not self.origClip: return clip = self.editor.mw.app.clipboard() clip.setMimeData(self.origClip, mode=mode) def saveClip(self, mode): # we don't own the clipboard object, so we need to copy it mime = self.editor.mw.app.clipboard().mimeData(mode=mode) n = QMimeData() if mime.hasText(): n.setText(mime.text()) if mime.hasHtml(): n.setHtml(mime.html()) if mime.hasUrls(): n.setUrls(mime.urls()) if mime.hasImage(): n.setImageData(mime.imageData()) self.origClip = n def _processMime(self, mime): # print "html=%s image=%s urls=%s txt=%s" % ( # mime.hasHtml(), mime.hasImage(), mime.hasUrls(), mime.hasText()) # print "html", mime.html() # print "urls", mime.urls() # print "text", mime.text() if mime.hasImage(): return self._processImage(mime) elif mime.hasUrls(): return self._processUrls(mime) elif mime.hasText() and (self.strip or not mime.hasHtml()): return self._processText(mime) elif mime.hasHtml(): return self._processHtml(mime) else: # nothing return QMimeData() def _processUrls(self, mime): url = mime.urls()[0].toString() link = None for suffix in pics+audio: if url.lower().endswith(suffix): link = self._retrieveURL(url) break if not link: # not a supported media type; include link verbatim link = url mime = QMimeData() mime.setHtml(link) return mime def _processText(self, mime): txt = unicode(mime.text()) l = txt.lower() html = None # firefox on linux just gives us a url for an image if "\n" in l and (l.startswith("http://") or l.startswith("file://")): txt = txt.split("\r\n")[0] html = self._retrieveURL(txt) new = QMimeData() if html: new.setHtml(html) else: new.setText(mime.text()) return new def _processHtml(self, mime): html = mime.html() if self.strip: html = stripHTML(html) else: html = _filterHTML(html) mime = QMimeData() mime.setHtml(html) return mime def _processImage(self, mime): im = QImage(mime.imageData()) name = namedtmp("paste-%d.png" % im.cacheKey()) uname = unicode(name, sys.getfilesystemencoding()) if im.hasAlphaChannel(): im.save(uname) else: im.save(uname, None, 95) mime = QMimeData() mime.setHtml(self.editor._addMedia(uname)) return mime def _retrieveURL(self, url): # is it media? ext = url.split(".")[-1].lower() if ext not in pics and ext not in audio: return # fetch it into a temporary folder try: req = urllib2.Request(url, None, { 'User-Agent': 'Mozilla/5.0 (compatible; Anki)'}) filecontents = urllib2.urlopen(req).read() except urllib2.URLError, e: showWarning(self.errtxt % e) return path = namedtmp(os.path.basename(url)) file = open(path, "wb") file.write(filecontents) file.close() return self.editor._addMedia(path)