mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
deck and plugin sharing
This commit is contained in:
parent
804413604c
commit
2646a40edb
9 changed files with 671 additions and 119 deletions
|
@ -26,6 +26,7 @@ def importAll():
|
|||
import update
|
||||
import utils
|
||||
import view
|
||||
import getshared
|
||||
|
||||
class DialogManager(object):
|
||||
|
||||
|
|
|
@ -4,10 +4,20 @@
|
|||
from PyQt4.QtGui import *
|
||||
from PyQt4.QtCore import *
|
||||
import anki, ankiqt
|
||||
from anki.exporting import exporters
|
||||
from anki.exporting import exporters as exporters_
|
||||
from anki.utils import parseTags
|
||||
from ankiqt import ui
|
||||
|
||||
class PackagedAnkiExporter(object):
|
||||
def __init__(self, *args):
|
||||
pass
|
||||
|
||||
def exporters():
|
||||
l = list(exporters_())
|
||||
l.insert(1, (_("Packaged Anki Deck (*.zip)"),
|
||||
PackagedAnkiExporter))
|
||||
return l
|
||||
|
||||
class ExportDialog(QDialog):
|
||||
|
||||
def __init__(self, parent):
|
||||
|
@ -35,7 +45,7 @@ class ExportDialog(QDialog):
|
|||
self.setTabOrder(self.tags,
|
||||
self.dialog.includeScheduling)
|
||||
# save button
|
||||
b = QPushButton(_("Export to..."))
|
||||
b = QPushButton(_("Export..."))
|
||||
self.dialog.buttonBox.addButton(b, QDialogButtonBox.AcceptRole)
|
||||
|
||||
def exporterChanged(self, idx):
|
||||
|
@ -50,6 +60,9 @@ class ExportDialog(QDialog):
|
|||
self.dialog.includeTags.hide()
|
||||
|
||||
def accept(self):
|
||||
if isinstance(self.exporter, PackagedAnkiExporter):
|
||||
self.parent.onShare(parseTags(unicode(self.tags.text())))
|
||||
return QDialog.accept(self)
|
||||
file = ui.utils.getSaveFile(self, _("Choose file to export to"), "export",
|
||||
self.exporter.key, self.exporter.ext)
|
||||
self.hide()
|
||||
|
|
210
ankiqt/ui/getshared.py
Normal file
210
ankiqt/ui/getshared.py
Normal file
|
@ -0,0 +1,210 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
|
||||
|
||||
from PyQt4.QtGui import *
|
||||
from PyQt4.QtCore import *
|
||||
from PyQt4.QtNetwork import *
|
||||
import ankiqt, simplejson, time, cStringIO, zipfile, tempfile, os, re
|
||||
from ankiqt.ui.utils import saveGeom, restoreGeom, showInfo
|
||||
from anki.utils import fmtTimeSpan
|
||||
|
||||
R_ID = 0
|
||||
R_USERNAME = 1
|
||||
R_TITLE = 2
|
||||
R_DESCRIPTION = 3
|
||||
R_TAGS = 4
|
||||
R_VERSION = 5
|
||||
R_FACTS = 6
|
||||
R_SIZE = 7
|
||||
R_COUNT = 8
|
||||
R_MODIFIED = 9
|
||||
R_FNAME = 10
|
||||
|
||||
class GetShared(QDialog):
|
||||
|
||||
def __init__(self, parent, type):
|
||||
QDialog.__init__(self, parent, Qt.Window)
|
||||
self.parent = parent
|
||||
self.form = ankiqt.forms.getshared.Ui_Dialog()
|
||||
self.form.setupUi(self)
|
||||
restoreGeom(self, "getshared")
|
||||
self.setupTable()
|
||||
self.onChangeType(type)
|
||||
self.ok = False
|
||||
if type == 0:
|
||||
self.setWindowTitle(_("Download Shared Deck"))
|
||||
else:
|
||||
self.setWindowTitle(_("Download Shared Plugin"))
|
||||
self.exec_()
|
||||
|
||||
def setupTable(self):
|
||||
self.connect(
|
||||
self.form.table, SIGNAL("currentCellChanged(int,int,int,int)"),
|
||||
self.onCellChanged)
|
||||
self.form.table.verticalHeader().setDefaultSectionSize(
|
||||
self.parent.config['editLineSize'])
|
||||
self.form.search.setText("search not yet implemented")
|
||||
|
||||
def fetchData(self):
|
||||
h = QHttp(self)
|
||||
h.connect(h, SIGNAL("requestFinished(int,bool)"), self.onReqFin)
|
||||
h.setHost("anki.ichi2.net")
|
||||
#h.setHost("localhost", 8001)
|
||||
self.conId = h.get("/file/search?t=%d" % self.type)
|
||||
self.http = h
|
||||
self.parent.setProgressParent(self)
|
||||
self.parent.startProgress()
|
||||
|
||||
def onReqFin(self, id, err):
|
||||
"List fetched."
|
||||
if id != self.conId:
|
||||
return
|
||||
self.parent.finishProgress()
|
||||
self.parent.setProgressParent(None)
|
||||
if err:
|
||||
showInfo(_("Unable to connect to server."), parent=self)
|
||||
self.close()
|
||||
return
|
||||
data = self.http.readAll()
|
||||
self.allList = simplejson.loads(unicode(data))
|
||||
self.typeChanged()
|
||||
self.limit()
|
||||
|
||||
def limit(self):
|
||||
self.curList = self.allList
|
||||
self.redraw()
|
||||
|
||||
def redraw(self):
|
||||
self.form.table.setSortingEnabled(False)
|
||||
self.form.table.setRowCount(len(self.curList))
|
||||
self.items = {}
|
||||
if self.type == 0:
|
||||
cols = (R_TITLE, R_FACTS, R_MODIFIED)
|
||||
else:
|
||||
cols = (R_TITLE, R_MODIFIED)
|
||||
for rc, r in enumerate(self.curList):
|
||||
for cc, c in enumerate(cols):
|
||||
if c == R_MODIFIED:
|
||||
txt = time.strftime("%m/%Y", time.localtime(r[c]))
|
||||
else:
|
||||
txt = unicode(r[c])
|
||||
item = QTableWidgetItem(txt)
|
||||
self.items[item] = r
|
||||
self.form.table.setItem(rc, cc, item)
|
||||
self.form.table.setSortingEnabled(True)
|
||||
self.form.table.selectRow(0)
|
||||
|
||||
def onCellChanged(self, row, col, x, y):
|
||||
ci = self.form.table.currentItem()
|
||||
if not ci:
|
||||
self.form.bottomLabel.setText(_("Nothing selected."))
|
||||
return
|
||||
r = self.items[ci]
|
||||
self.curRow = r
|
||||
self.form.bottomLabel.setText(_("""\
|
||||
<b>Title</b>: %(title)s<br>
|
||||
<b>Tags</b>: %(tags)s<br>
|
||||
<b>Size</b>: %(size)0.2fKB<br>
|
||||
<b>Uploader</b>: %(author)s<br>
|
||||
<b>Downloads</b>: %(count)s<br>
|
||||
<b>Description</b>:<br>%(description)s""") % {
|
||||
'title': r[R_TITLE],
|
||||
'tags': r[R_TAGS],
|
||||
'size': r[R_SIZE] / 1024.0,
|
||||
'author': r[R_USERNAME],
|
||||
'count': r[R_COUNT],
|
||||
'description': r[R_DESCRIPTION].replace("\n", "<br>"),
|
||||
})
|
||||
self.form.scrollAreaWidgetContents.adjustSize()
|
||||
self.form.scrollArea.setWidget(self.form.scrollAreaWidgetContents)
|
||||
|
||||
def onChangeType(self, type):
|
||||
self.type = type
|
||||
self.fetchData()
|
||||
|
||||
def typeChanged(self):
|
||||
self.form.table.clear()
|
||||
if self.type == 0:
|
||||
self.form.table.setColumnCount(3)
|
||||
self.form.table.setHorizontalHeaderLabels([
|
||||
_("Title"), _("Facts"), _("Modified")])
|
||||
else:
|
||||
self.form.table.setColumnCount(2)
|
||||
self.form.table.setHorizontalHeaderLabels([
|
||||
_("Title"), _("Modified")])
|
||||
self.form.table.horizontalHeader().setResizeMode(
|
||||
0, QHeaderView.Stretch)
|
||||
self.form.table.verticalHeader().hide()
|
||||
self.form.table.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
|
||||
def accept(self):
|
||||
h = QHttp(self)
|
||||
h.connect(h, SIGNAL("requestFinished(int,bool)"), self.onReqFin2)
|
||||
h.setHost("anki.ichi2.net")
|
||||
#h.setHost("localhost", 8001)
|
||||
self.conId = h.get("/file/get?id=%d" % self.curRow[R_ID])
|
||||
self.http = h
|
||||
self.parent.setProgressParent(self)
|
||||
self.parent.startProgress()
|
||||
|
||||
def onReqFin2(self, id, err):
|
||||
"File fetched."
|
||||
if id != self.conId:
|
||||
return
|
||||
try:
|
||||
self.parent.finishProgress()
|
||||
self.parent.setProgressParent(None)
|
||||
if err:
|
||||
showInfo(_("Unable to connect to server."), parent=self)
|
||||
self.close()
|
||||
return
|
||||
data = self.http.readAll()
|
||||
ext = os.path.splitext(self.curRow[R_FNAME])[1]
|
||||
if ext == ".zip":
|
||||
f = cStringIO.StringIO()
|
||||
f.write(data)
|
||||
z = zipfile.ZipFile(f)
|
||||
else:
|
||||
z = None
|
||||
tit = self.curRow[R_TITLE]
|
||||
tit = re.sub("[^][A-Za-z0-9 ()\-]", "", tit)
|
||||
tit = tit[0:40]
|
||||
if self.type == 0:
|
||||
# deck
|
||||
dd = self.parent.documentDir
|
||||
p = os.path.join(dd, tit + ".anki")
|
||||
if os.path.exists(p):
|
||||
tit += "%d" % time.time()
|
||||
for l in z.namelist():
|
||||
if l == "shared.anki":
|
||||
dpath = os.path.join(dd, tit + ".anki")
|
||||
open(dpath, "w").write(z.read(l))
|
||||
elif l.startswith("shared.media/"):
|
||||
try:
|
||||
os.mkdir(os.path.join(dd, tit + ".media"))
|
||||
except OSError:
|
||||
pass
|
||||
open(os.path.join(dd, tit + ".media",
|
||||
os.path.basename(l)),"w").write(z.read(l))
|
||||
self.parent.loadDeck(dpath)
|
||||
self.ok = True
|
||||
else:
|
||||
pd = self.parent.pluginsFolder()
|
||||
if z:
|
||||
raise "nyi"
|
||||
# for l in z.namelist():
|
||||
# try:
|
||||
# os.mkdir(os.path.join(pd, os.path.dirname(l)))
|
||||
# except OSError:
|
||||
# pass
|
||||
# open(os.path.join(pd, 2))
|
||||
else:
|
||||
open(os.path.join(pd, tit + ext), "w").write(data)
|
||||
showInfo(_("Plugin downloaded. Please restart Anki."),
|
||||
parent=self)
|
||||
self.ok = True
|
||||
return
|
||||
finally:
|
||||
QDialog.accept(self)
|
||||
|
|
@ -6,8 +6,8 @@ from PyQt4.QtGui import *
|
|||
from PyQt4.QtCore import *
|
||||
from PyQt4.QtWebKit import QWebPage
|
||||
|
||||
import os, sys, re, types, gettext, stat, traceback
|
||||
import shutil, time, glob, tempfile, datetime
|
||||
import os, sys, re, types, gettext, stat, traceback, inspect
|
||||
import shutil, time, glob, tempfile, datetime, zipfile
|
||||
|
||||
from PyQt4.QtCore import *
|
||||
from PyQt4.QtGui import *
|
||||
|
@ -627,50 +627,10 @@ To upgrade an old deck, download Anki 0.9.8.7."""))
|
|||
latest = self.config['recentDeckPaths'][0]
|
||||
defaultDir = os.path.dirname(latest)
|
||||
else:
|
||||
if save:
|
||||
defaultDir = unicode(os.path.expanduser("~/"),
|
||||
sys.getfilesystemencoding())
|
||||
else:
|
||||
samples = self.getSamplesDir()
|
||||
if samples:
|
||||
return samples
|
||||
return defaultDir
|
||||
|
||||
def getSamplesDir(self):
|
||||
path = os.path.join(ankiqt.runningDir, "libanki")
|
||||
if not os.path.exists(path):
|
||||
path = os.path.join(
|
||||
os.path.join(ankiqt.runningDir, ".."), "libanki")
|
||||
if not os.path.exists(path):
|
||||
path = ankiqt.runningDir
|
||||
if sys.platform.startswith("win32"):
|
||||
path = os.path.split(
|
||||
os.path.split(ankiqt.runningDir)[0])[0]
|
||||
elif sys.platform.startswith("darwin"):
|
||||
path = ankiqt.runningDir + "/../../.."
|
||||
path = os.path.join(path, "samples")
|
||||
path = os.path.normpath(path)
|
||||
if os.path.exists(path):
|
||||
if sys.platform.startswith("darwin"):
|
||||
return self.openMacSamplesDir(path)
|
||||
return path
|
||||
return ""
|
||||
|
||||
def openMacSamplesDir(self, path):
|
||||
# some versions of macosx don't allow the open dialog to point inside
|
||||
# a .App file, it seems - so we copy the files onto the desktop.
|
||||
newDir = os.path.expanduser("~/Documents/Anki 0.9 Sample Decks")
|
||||
import shutil
|
||||
if os.path.exists(newDir):
|
||||
files = os.listdir(path)
|
||||
for file in files:
|
||||
loc = os.path.join(path, file)
|
||||
if not os.path.exists(os.path.join(newDir, file)):
|
||||
shutil.copy2(loc, newDir)
|
||||
return newDir
|
||||
shutil.copytree(path, newDir)
|
||||
return newDir
|
||||
|
||||
def updateRecentFiles(self, path):
|
||||
"Add the current deck to the list of recent files."
|
||||
path = os.path.normpath(path)
|
||||
|
@ -762,6 +722,7 @@ To upgrade an old deck, download Anki 0.9.8.7."""))
|
|||
return True
|
||||
|
||||
def inMainWindow(self):
|
||||
return True
|
||||
return self.app.activeWindow() == self
|
||||
|
||||
def onNew(self, initial=False, path=None):
|
||||
|
@ -827,23 +788,27 @@ To upgrade an old deck, download Anki 0.9.8.7."""))
|
|||
self.deck = None
|
||||
self.moveToState("initial")
|
||||
|
||||
def onOpen(self, samples=False):
|
||||
def onGetSharedDeck(self):
|
||||
if not self.inMainWindow(): return
|
||||
if not self.saveAndClose(hideWelcome=True): return
|
||||
s = ui.getshared.GetShared(self, 0)
|
||||
if not s.ok:
|
||||
self.deck = None
|
||||
self.moveToState("initial")
|
||||
|
||||
def onGetSharedPlugin(self):
|
||||
if not self.inMainWindow(): return
|
||||
ui.getshared.GetShared(self, 1)
|
||||
|
||||
def onOpen(self):
|
||||
if not self.inMainWindow(): return
|
||||
key = _("Deck files (*.anki)")
|
||||
if samples: defaultDir = self.getSamplesDir()
|
||||
else: defaultDir = self.getDefaultDir()
|
||||
defaultDir = self.getDefaultDir()
|
||||
file = QFileDialog.getOpenFileName(self, _("Open deck"),
|
||||
defaultDir, key)
|
||||
file = unicode(file)
|
||||
if not file:
|
||||
return False
|
||||
if samples:
|
||||
# we need to copy into a writeable location
|
||||
d = unicode(
|
||||
os.path.join(self.documentDir, os.path.basename(file)))
|
||||
if not os.path.exists(d):
|
||||
shutil.copy(file, d)
|
||||
file = d
|
||||
ret = self.loadDeck(file, interactive=True)
|
||||
if not ret:
|
||||
if ret is None:
|
||||
|
@ -854,9 +819,6 @@ To upgrade an old deck, download Anki 0.9.8.7."""))
|
|||
self.updateRecentFiles(file)
|
||||
return True
|
||||
|
||||
def onOpenSamples(self):
|
||||
self.onOpen(samples=True)
|
||||
|
||||
def onUnsavedTimer(self):
|
||||
QToolTip.showText(
|
||||
self.mainWin.statusbar.mapToGlobal(QPoint(0, -100)),
|
||||
|
@ -952,14 +914,12 @@ your deck."""))
|
|||
def onWelcomeAnchor(self, str):
|
||||
if str == "new":
|
||||
self.onNew()
|
||||
elif str == "sample":
|
||||
self.onOpenSamples()
|
||||
elif str == "open":
|
||||
self.onOpen()
|
||||
elif str == "sample":
|
||||
self.onGetSharedDeck()
|
||||
elif str == "openrem":
|
||||
self.onOpenOnline()
|
||||
elif str == "more":
|
||||
self.onGetMoreDecks()
|
||||
if str == "addfacts":
|
||||
if not self.deck:
|
||||
self.onNew()
|
||||
|
@ -1177,7 +1137,6 @@ day = :d""", d=yesterday)
|
|||
self.deck.newCardSpacing = self.mainWin.newCardScheduling.currentIndex()
|
||||
self.deck.revCardOrder = self.mainWin.revCardOrder.currentIndex()
|
||||
self.deck.setFailedCardPolicy(self.mainWin.failedCardsOption.currentIndex())
|
||||
self.deck.updateDynamicIndices()
|
||||
self.deck.startSession()
|
||||
self.deck.flushMod()
|
||||
self.moveToState("getQuestion")
|
||||
|
@ -1403,9 +1362,6 @@ day = :d""", d=yesterday)
|
|||
def onActiveTags(self):
|
||||
ui.activetags.show(self)
|
||||
|
||||
def onGetMoreDecks(self):
|
||||
QDesktopServices.openUrl(QUrl(ankiqt.appMoreDecks))
|
||||
|
||||
# Importing & exporting
|
||||
##########################################################################
|
||||
|
||||
|
@ -1417,9 +1373,20 @@ day = :d""", d=yesterday)
|
|||
def onExport(self):
|
||||
ui.exporting.ExportDialog(self)
|
||||
|
||||
# Cramming
|
||||
# Cramming & Sharing
|
||||
##########################################################################
|
||||
|
||||
def _copyToTmpDeck(self, name="cram.anki", tags=""):
|
||||
ndir = tempfile.mkdtemp(prefix="anki")
|
||||
path = os.path.join(ndir, name)
|
||||
from anki.exporting import AnkiExporter
|
||||
e = AnkiExporter(self.deck)
|
||||
if tags:
|
||||
e.limitTags = parseTags(tags)
|
||||
path = unicode(path, sys.getfilesystemencoding())
|
||||
e.exportInto(path)
|
||||
return (e, path)
|
||||
|
||||
def onCram(self):
|
||||
if self.deck.name() == "cram":
|
||||
ui.utils.showInfo(
|
||||
|
@ -1433,14 +1400,7 @@ day = :d""", d=yesterday)
|
|||
return
|
||||
s = unicode(s)
|
||||
# open tmp deck
|
||||
ndir = tempfile.mkdtemp(prefix="anki")
|
||||
path = os.path.join(ndir, "cram.anki")
|
||||
from anki.exporting import AnkiExporter
|
||||
e = AnkiExporter(self.deck)
|
||||
if s:
|
||||
e.limitTags = parseTags(s)
|
||||
path = unicode(path, sys.getfilesystemencoding())
|
||||
e.exportInto(path)
|
||||
(e, path) = self._copyToTmpDeck(tags=s)
|
||||
if not e.exportedCards:
|
||||
ui.utils.showInfo(_("No cards matched the provided tags."))
|
||||
return
|
||||
|
@ -1484,6 +1444,79 @@ day = :d""", d=yesterday)
|
|||
self.reset()
|
||||
p.finish()
|
||||
|
||||
def onShare(self, tags):
|
||||
pwd = os.getcwd()
|
||||
# open tmp deck
|
||||
(e, path) = self._copyToTmpDeck(name="shared.anki", tags=tags)
|
||||
if not e.exportedCards:
|
||||
ui.utils.showInfo(_("No cards matched the provided tags."))
|
||||
return
|
||||
self.deck.startProgress()
|
||||
self.deck.updateProgress()
|
||||
d = DeckStorage.Deck(path)
|
||||
# reset scheduling to defaults
|
||||
d.newCardsPerDay = 20
|
||||
d.delay0 = 600
|
||||
d.delay1 = 600
|
||||
d.delay2 = 0
|
||||
d.hardIntervalMin = 0.333
|
||||
d.hardIntervalMax = 0.5
|
||||
d.midIntervalMin = 3.0
|
||||
d.midIntervalMax = 5.0
|
||||
d.easyIntervalMin = 7.0
|
||||
d.easyIntervalMax = 9.0
|
||||
d.syncName = None
|
||||
d.suspended = u"Suspended"
|
||||
self.deck.updateProgress()
|
||||
d.updateAllPriorities()
|
||||
d.utcOffset = 24
|
||||
d.flushMod()
|
||||
d.save()
|
||||
self.deck.updateProgress()
|
||||
# remove indices
|
||||
indices = d.s.column0(
|
||||
"select name from sqlite_master where type = 'index'")
|
||||
for i in indices:
|
||||
d.s.statement("drop index %s" % i)
|
||||
# and q/a cache
|
||||
d.s.statement("update cards set question = '', answer = ''")
|
||||
self.deck.updateProgress()
|
||||
d.s.statement("vacuum")
|
||||
self.deck.updateProgress()
|
||||
nfacts = d.factCount
|
||||
mdir = d.mediaDir()
|
||||
d.close()
|
||||
dir = os.path.dirname(path)
|
||||
zippath = os.path.join(dir, "shared-%d.zip" % time.time())
|
||||
# zip it up
|
||||
zip = zipfile.ZipFile(zippath, "w", zipfile.ZIP_DEFLATED)
|
||||
zip.writestr("facts", str(nfacts))
|
||||
readmep = os.path.join(dir, "README.html")
|
||||
readme = open(readmep, "w")
|
||||
readme.write('''\
|
||||
<html><body>
|
||||
This is an exported packaged deck created by Anki.<p>
|
||||
|
||||
To share this deck with other people, upload it to
|
||||
<a href="http://anki.ichi2.net/file/upload">
|
||||
http://anki.ichi2.net/file/upload</a>, or email
|
||||
it to your friends.
|
||||
</body></html>''')
|
||||
readme.close()
|
||||
zip.write(readmep, "README.txt")
|
||||
zip.write(path, "shared.anki")
|
||||
if mdir:
|
||||
for f in os.listdir(mdir):
|
||||
zip.write(os.path.join(mdir, f),
|
||||
str(os.path.join("shared.media/", f)))
|
||||
shutil.rmtree(mdir)
|
||||
self.deck.updateProgress()
|
||||
zip.close()
|
||||
os.chdir(pwd)
|
||||
os.unlink(path)
|
||||
self.deck.finishProgress()
|
||||
self.onOpenPluginFolder(dir)
|
||||
|
||||
# Reviewing and learning ahead
|
||||
##########################################################################
|
||||
|
||||
|
@ -1700,10 +1733,12 @@ day = :d""", d=yesterday)
|
|||
s = SIGNAL("triggered()")
|
||||
self.connect(m.actionNew, s, self.onNew)
|
||||
self.connect(m.actionOpenOnline, s, self.onOpenOnline)
|
||||
self.connect(m.actionDownloadSharedDeck, s, self.onGetSharedDeck)
|
||||
self.connect(m.actionDownloadSharedPlugin, s, self.onGetSharedPlugin)
|
||||
self.connect(m.actionOpen, s, self.onOpen)
|
||||
self.connect(m.actionOpenSamples, s, self.onOpenSamples)
|
||||
self.connect(m.actionSave, s, self.onSave)
|
||||
self.connect(m.actionSaveAs, s, self.onSaveAs)
|
||||
self.connect(m.actionShare, s, self.onShare)
|
||||
self.connect(m.actionClose, s, self.onClose)
|
||||
self.connect(m.actionExit, s, self, SLOT("close()"))
|
||||
self.connect(m.actionSyncdeck, s, self.syncDeck)
|
||||
|
@ -1744,7 +1779,6 @@ day = :d""", d=yesterday)
|
|||
self.connect(m.actionDisableAllPlugins, s, self.onDisableAllPlugins)
|
||||
self.connect(m.actionActiveTags, s, self.onActiveTags)
|
||||
self.connect(m.actionReleaseNotes, s, self.onReleaseNotes)
|
||||
self.connect(m.actionGetMoreDecks, s, self.onGetMoreDecks)
|
||||
self.connect(m.actionCacheLatex, s, self.onCacheLatex)
|
||||
self.connect(m.actionUncacheLatex, s, self.onUncacheLatex)
|
||||
self.connect(m.actionStudyOptions, s, self.onStudyOptions)
|
||||
|
@ -1912,6 +1946,7 @@ day = :d""", d=yesterday)
|
|||
sys.path.insert(0, plugdir)
|
||||
plugins = self.enabledPlugins()
|
||||
plugins.sort()
|
||||
self.registeredPlugins = {}
|
||||
for plugin in plugins:
|
||||
try:
|
||||
nopy = plugin.replace(".py", "")
|
||||
|
@ -1919,6 +1954,7 @@ day = :d""", d=yesterday)
|
|||
except:
|
||||
print "Error in %s" % plugin
|
||||
traceback.print_exc()
|
||||
self.checkForUpdatedPlugins()
|
||||
|
||||
def rebuildPluginsMenu(self):
|
||||
if getattr(self, "pluginActions", None) is None:
|
||||
|
@ -1950,13 +1986,15 @@ day = :d""", d=yesterday)
|
|||
return [p for p in os.listdir(self.pluginsFolder())
|
||||
if p.endswith(".py.off") or p.endswith(".py")]
|
||||
|
||||
def onOpenPluginFolder(self):
|
||||
def onOpenPluginFolder(self, path=None):
|
||||
if path is None:
|
||||
path = self.pluginsFolder()
|
||||
if sys.platform == "win32":
|
||||
# reuse our process handling code from latex
|
||||
anki.latex.call(["explorer", self.pluginsFolder().encode(
|
||||
anki.latex.call(["explorer", path.encode(
|
||||
sys.getfilesystemencoding())])
|
||||
else:
|
||||
QDesktopServices.openUrl(QUrl("file://" + self.pluginsFolder()))
|
||||
QDesktopServices.openUrl(QUrl("file://" + path))
|
||||
|
||||
def onGetPlugins(self):
|
||||
QDesktopServices.openUrl(QUrl("http://ichi2.net/anki/wiki/Plugins"))
|
||||
|
@ -1988,6 +2026,14 @@ day = :d""", d=yesterday)
|
|||
self.enablePlugin(plugin)
|
||||
self.rebuildPluginsMenu()
|
||||
|
||||
def registerPlugin(self, name, updateId):
|
||||
src = os.path.basename(inspect.getfile(inspect.currentframe(1)))
|
||||
self.registeredPlugins[src] = {'name': name,
|
||||
'id': updateId}
|
||||
|
||||
def checkForUpdatedPlugins(self):
|
||||
pass
|
||||
|
||||
# Font localisation
|
||||
##########################################################################
|
||||
|
||||
|
@ -2019,9 +2065,9 @@ day = :d""", d=yesterday)
|
|||
##########################################################################
|
||||
|
||||
def setupProgressInfo(self):
|
||||
addHook("startProgress", self.onStartProgress)
|
||||
addHook("updateProgress", self.onUpdateProgress)
|
||||
addHook("finishProgress", self.onFinishProgress)
|
||||
addHook("startProgress", self.startProgress)
|
||||
addHook("updateProgress", self.updateProgress)
|
||||
addHook("finishProgress", self.finishProgress)
|
||||
addHook("dbProgress", self.onDbProgress)
|
||||
addHook("dbFinished", self.onDbFinished)
|
||||
self.progressParent = None
|
||||
|
@ -2032,7 +2078,7 @@ day = :d""", d=yesterday)
|
|||
def setProgressParent(self, parent):
|
||||
self.progressParent = parent
|
||||
|
||||
def onStartProgress(self, max=100, min=0, title=None):
|
||||
def startProgress(self, max=0, min=0, title=None):
|
||||
if self.mainThread != QThread.currentThread():
|
||||
return
|
||||
self.setBusy()
|
||||
|
@ -2042,14 +2088,14 @@ day = :d""", d=yesterday)
|
|||
p = ui.utils.ProgressWin(parent, max, min, title)
|
||||
self.progressWins.append(p)
|
||||
|
||||
def onUpdateProgress(self, label=None, value=None):
|
||||
def updateProgress(self, label=None, value=None):
|
||||
if self.mainThread != QThread.currentThread():
|
||||
return
|
||||
if self.progressWins:
|
||||
self.progressWins[-1].update(label, value)
|
||||
self.app.processEvents()
|
||||
|
||||
def onFinishProgress(self):
|
||||
def finishProgress(self):
|
||||
if self.mainThread != QThread.currentThread():
|
||||
return
|
||||
if self.progressWins:
|
||||
|
|
|
@ -272,25 +272,18 @@ Start adding your own material.</td>
|
|||
<td valign=middle><h2><a href="welcome:open">Open Local Deck</a></h2></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<a href="welcome:openrem"><img src=":/icons/document-open-remote.png"></a>
|
||||
</td>
|
||||
<td valign=middle><h2><a href="welcome:openrem">Open Online Deck</a></h2></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td width=50>
|
||||
<a href="welcome:sample"><img src=":/icons/anki.png"></a>
|
||||
</td>
|
||||
<td valign=middle><h2><a href="welcome:sample">Open Sample Deck</a></h2></td>
|
||||
<td valign=middle><h2><a href="welcome:sample">Download Shared Deck</a></h2></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td width=50>
|
||||
<a href="welcome:more"><img src=":/icons/khtml_kget.png"></a>
|
||||
<td>
|
||||
<a href="welcome:openrem"><img src=":/icons/document-open-remote.png"></a>
|
||||
</td>
|
||||
<td valign=middle><h2><a href="welcome:more">Get More Decks</a></h2></td>
|
||||
<td valign=middle><h2><a href="welcome:openrem">Download Personal Deck</a></h2></td>
|
||||
</tr>
|
||||
|
||||
</table>"""))
|
||||
|
|
|
@ -58,6 +58,19 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0" >
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox" >
|
||||
<property name="orientation" >
|
||||
|
|
121
designer/getshared.ui
Normal file
121
designer/getshared.ui
Normal file
|
@ -0,0 +1,121 @@
|
|||
<ui version="4.0" >
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>517</width>
|
||||
<height>411</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" >
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" >
|
||||
<item>
|
||||
<widget class="QLabel" name="searchLabel" >
|
||||
<property name="text" >
|
||||
<string>Search:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="search" />
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<widget class="QTableWidget" name="table" />
|
||||
<widget class="QScrollArea" name="scrollArea" >
|
||||
<property name="frameShape" >
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow" >
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="verticalScrollBarPolicy" >
|
||||
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy" >
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>494</width>
|
||||
<height>54</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy" >
|
||||
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2" >
|
||||
<item>
|
||||
<widget class="QLabel" name="bottomLabel" >
|
||||
<property name="text" >
|
||||
<string>Loading...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons" >
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
|
@ -1174,7 +1174,6 @@
|
|||
<addaction name="actionReportbug" />
|
||||
<addaction name="actionForum" />
|
||||
<addaction name="actionReleaseNotes" />
|
||||
<addaction name="actionGetMoreDecks" />
|
||||
<addaction name="separator" />
|
||||
<addaction name="actionDonate" />
|
||||
<addaction name="actionAbout" />
|
||||
|
@ -1208,10 +1207,23 @@
|
|||
<normaloff>:/icons/document-open-recent.png</normaloff>:/icons/document-open-recent.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuDownload" >
|
||||
<property name="title" >
|
||||
<string>&Download...</string>
|
||||
</property>
|
||||
<property name="icon" >
|
||||
<iconset resource="../icons.qrc" >
|
||||
<normaloff>:/icons/document-open-remote.png</normaloff>:/icons/document-open-remote.png</iconset>
|
||||
</property>
|
||||
<addaction name="actionOpenOnline" />
|
||||
<addaction name="separator" />
|
||||
<addaction name="actionDownloadSharedDeck" />
|
||||
<addaction name="actionDownloadSharedPlugin" />
|
||||
</widget>
|
||||
<addaction name="actionNew" />
|
||||
<addaction name="actionOpen" />
|
||||
<addaction name="menuOpenRecent" />
|
||||
<addaction name="actionOpenOnline" />
|
||||
<addaction name="menuDownload" />
|
||||
<addaction name="actionImport" />
|
||||
<addaction name="separator" />
|
||||
<addaction name="actionSave" />
|
||||
|
@ -1288,7 +1300,6 @@
|
|||
<addaction name="actionDisableAllPlugins" />
|
||||
<addaction name="separator" />
|
||||
</widget>
|
||||
<addaction name="actionGetPlugins" />
|
||||
<addaction name="actionOpenPluginFolder" />
|
||||
<addaction name="separator" />
|
||||
<addaction name="menuStartup" />
|
||||
|
@ -1762,15 +1773,6 @@
|
|||
<string>Check Media Database...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionOpenOnline" >
|
||||
<property name="icon" >
|
||||
<iconset resource="../icons.qrc" >
|
||||
<normaloff>:/icons/document-open-remote.png</normaloff>:/icons/document-open-remote.png</iconset>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Open On&line...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCram" >
|
||||
<property name="icon" >
|
||||
<iconset resource="../icons.qrc" >
|
||||
|
@ -1823,15 +1825,6 @@
|
|||
<string>Active &Tags...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionGetMoreDecks" >
|
||||
<property name="icon" >
|
||||
<iconset resource="../icons.qrc" >
|
||||
<normaloff>:/icons/download.png</normaloff>:/icons/download.png</iconset>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>&Get More Decks...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionEditCurrent" >
|
||||
<property name="icon" >
|
||||
<iconset resource="../icons.qrc" >
|
||||
|
@ -1904,6 +1897,34 @@
|
|||
<string>&Record Noise Profile...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionGetShared" >
|
||||
<property name="text" >
|
||||
<string>Get Shared...</string>
|
||||
</property>
|
||||
<property name="statusTip" >
|
||||
<string>Open a pre-made deck or plugin</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionShare" >
|
||||
<property name="text" >
|
||||
<string>Share...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionOpenOnline" >
|
||||
<property name="text" >
|
||||
<string>Personal Deck</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDownloadSharedDeck" >
|
||||
<property name="text" >
|
||||
<string>Shared Deck</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionDownloadSharedPlugin" >
|
||||
<property name="text" >
|
||||
<string>Shared Plugin</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>newPerDay</tabstop>
|
||||
|
|
134
designer/share.ui
Normal file
134
designer/share.ui
Normal file
|
@ -0,0 +1,134 @@
|
|||
<ui version="4.0" >
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>517</width>
|
||||
<height>411</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>Get Shared Decks/Plugins</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" >
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" >
|
||||
<item>
|
||||
<widget class="QLabel" name="searchLabel" >
|
||||
<property name="text" >
|
||||
<string>Search:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="search" />
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2" >
|
||||
<property name="text" >
|
||||
<string>Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="type" />
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<widget class="QTableWidget" name="table" />
|
||||
<widget class="QScrollArea" name="scrollArea" >
|
||||
<property name="frameShape" >
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow" >
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="verticalScrollBarPolicy" >
|
||||
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy" >
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>494</width>
|
||||
<height>54</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy" >
|
||||
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2" >
|
||||
<item>
|
||||
<widget class="QLabel" name="bottomLabel" >
|
||||
<property name="text" >
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons" >
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
Loading…
Reference in a new issue