mirror of
https://github.com/ankitects/anki.git
synced 2025-09-20 06:52:21 -04:00
more deck browser improvements
- shortcut keys - options menu items implemented - fixed handling of locked decks - limit accels to 35
This commit is contained in:
parent
3b085c486e
commit
2e327f4fe9
3 changed files with 111 additions and 101 deletions
|
@ -94,7 +94,7 @@ class Config(object):
|
||||||
# load/save
|
# load/save
|
||||||
def load(self):
|
def load(self):
|
||||||
path = self._dbPath()
|
path = self._dbPath()
|
||||||
self.db = DB(path, level=None, text=str)
|
self.db = DB(path, text=str)
|
||||||
self.db.executescript("""
|
self.db.executescript("""
|
||||||
create table if not exists decks (path text primary key);
|
create table if not exists decks (path text primary key);
|
||||||
create table if not exists config (conf text not null);
|
create table if not exists config (conf text not null);
|
||||||
|
|
|
@ -2,18 +2,25 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
|
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
|
||||||
|
|
||||||
import time, os, stat
|
import time, os, stat, shutil
|
||||||
from PyQt4.QtCore import *
|
from PyQt4.QtCore import *
|
||||||
from PyQt4.QtGui import *
|
from PyQt4.QtGui import *
|
||||||
from anki import Deck
|
from anki import Deck
|
||||||
from anki.utils import fmtTimeSpan
|
from anki.utils import fmtTimeSpan
|
||||||
from anki.hooks import addHook
|
from anki.hooks import addHook
|
||||||
|
import aqt
|
||||||
|
#from aqt.utils import askUser
|
||||||
|
|
||||||
_css = """
|
_css = """
|
||||||
body { background-color: #eee; }
|
body { background-color: #eee; }
|
||||||
#outer { margin-top: 1em; }
|
#outer { margin-top: 1em; }
|
||||||
.sub { color: #555; }
|
.sub { color: #555; }
|
||||||
hr { margin: 5 0 5 0; }
|
hr { margin: 5 0 5 0; }
|
||||||
|
a:hover { background-color: #aaa; }
|
||||||
|
a { color: #000; text-decoration: none; }
|
||||||
|
.num { text-align: right; padding: 0 5 0 5; }
|
||||||
|
td.opts { text-align: right; }
|
||||||
|
a.opts { font-size: 80%; padding: 2; background-color: #ccc; border-radius: 2px; }
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_body = """
|
_body = """
|
||||||
|
@ -25,6 +32,8 @@ _body = """
|
||||||
</table>
|
</table>
|
||||||
<br><br>
|
<br><br>
|
||||||
%s
|
%s
|
||||||
|
<p>
|
||||||
|
Click a deck to open it, or press the number/letter next to it.
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -37,18 +46,30 @@ class DeckBrowser(object):
|
||||||
self._decks = []
|
self._decks = []
|
||||||
addHook("deckClosing", self.onClose)
|
addHook("deckClosing", self.onClose)
|
||||||
|
|
||||||
def _bridge(self, str):
|
def _linkHandler(self, url):
|
||||||
if str == "refresh":
|
(cmd, arg) = url.split(":")
|
||||||
pass
|
if cmd == "open":
|
||||||
elif str == "full":
|
deck = self._decks[int(arg)]['path']
|
||||||
self.onFull()
|
self.mw.loadDeck(deck)
|
||||||
|
elif cmd == "opts":
|
||||||
|
self._optsForRow(int(arg))
|
||||||
|
|
||||||
def _link(self, url):
|
def _keyHandler(self, evt):
|
||||||
pass
|
txt = evt.text()
|
||||||
|
if ((txt >= "0" and txt <= "9") or
|
||||||
|
(txt >= "a" and txt <= "z")):
|
||||||
|
self._openAccel(txt)
|
||||||
|
evt.accept()
|
||||||
|
evt.ignore()
|
||||||
|
|
||||||
|
def _openAccel(self, txt):
|
||||||
|
for d in self._decks:
|
||||||
|
if d['accel'] == txt:
|
||||||
|
self.mw.loadDeck(d['path'])
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
self.web.setBridge(self._bridge)
|
self.web.setLinkHandler(self._linkHandler)
|
||||||
self.web.setLinkHandler(self._link)
|
self.mw.setKeyHandler(self._keyHandler)
|
||||||
if (time.time() - self._browserLastRefreshed >
|
if (time.time() - self._browserLastRefreshed >
|
||||||
self.mw.config['deckBrowserRefreshPeriod']):
|
self.mw.config['deckBrowserRefreshPeriod']):
|
||||||
t = time.time()
|
t = time.time()
|
||||||
|
@ -91,22 +112,18 @@ later by using File>Close.
|
||||||
|
|
||||||
def _deckRow(self, c, max, deck):
|
def _deckRow(self, c, max, deck):
|
||||||
buf = "<tr>"
|
buf = "<tr>"
|
||||||
# name and status
|
|
||||||
ok = deck['state'] == 'ok'
|
ok = deck['state'] == 'ok'
|
||||||
|
def accelName(deck):
|
||||||
|
if deck['accel']:
|
||||||
|
return "%s. " % deck['accel']
|
||||||
|
return ""
|
||||||
if ok:
|
if ok:
|
||||||
sub = _("%s ago") % fmtTimeSpan(
|
# name/link
|
||||||
time.time() - deck['mod'])
|
buf += "<td>%s<b>%s</b></td>" % (
|
||||||
elif deck['state'] == 'missing':
|
accelName(deck),
|
||||||
sub = _("(moved or removed)")
|
"<a href='open:%d'>%s</a>"%(c, deck['name']))
|
||||||
elif deck['state'] == 'corrupt':
|
|
||||||
sub = _("(corrupt)")
|
|
||||||
elif deck['state'] == 'in use':
|
|
||||||
sub = _("(already open)")
|
|
||||||
sub = "<font size=-1>%s</font>" % sub
|
|
||||||
buf += "<td><b>%s</b><br><span class=sub>%s</span></td>" % (deck['name'], sub)
|
|
||||||
if ok:
|
|
||||||
# due
|
# due
|
||||||
col = '<td><b><font color=#0000ff>%s</font></b></td>'
|
col = '<td class=num><b><font color=#0000ff>%s</font></b></td>'
|
||||||
if deck['due'] > 0:
|
if deck['due'] > 0:
|
||||||
s = col % str(deck['due'])
|
s = col % str(deck['due'])
|
||||||
else:
|
else:
|
||||||
|
@ -117,47 +134,41 @@ later by using File>Close.
|
||||||
s = str(deck['new'])
|
s = str(deck['new'])
|
||||||
else:
|
else:
|
||||||
s = ""
|
s = ""
|
||||||
buf += "<td>%s</td>" % s
|
buf += "<td class=num>%s</td>" % s
|
||||||
else:
|
else:
|
||||||
buf += "<td></td><td></td>"
|
# name/error
|
||||||
# open
|
if deck['state'] == 'missing':
|
||||||
# openButton = QPushButton(_("Open"))
|
sub = _("(moved or removed)")
|
||||||
# if c < 9:
|
elif deck['state'] == 'corrupt':
|
||||||
# if sys.platform.startswith("darwin"):
|
sub = _("(corrupt)")
|
||||||
# extra = ""
|
elif deck['state'] == 'in use':
|
||||||
# # appears to be broken on osx
|
sub = _("(already open)")
|
||||||
# #extra = _(" (Command+Option+%d)") % (c+1)
|
else:
|
||||||
# #openButton.setShortcut(_("Ctrl+Alt+%d" % (c+1)))
|
sub = "unknown"
|
||||||
# else:
|
buf += "<td>%s<b>%s</b><br><span class=sub>%s</span></td>" % (
|
||||||
# extra = _(" (Alt+%d)") % (c+1)
|
accelName(deck),
|
||||||
# openButton.setShortcut(_("Alt+%d" % (c+1)))
|
deck['name'],
|
||||||
# else:
|
sub)
|
||||||
# extra = ""
|
# no counts
|
||||||
# openButton.setToolTip(_("Open this deck%s") % extra)
|
buf += "<td colspan=2></td>"
|
||||||
# self.connect(openButton, SIGNAL("clicked()"),
|
# options
|
||||||
# lambda d=deck['path']: self.loadDeck(d))
|
buf += "<td class=opts><a class=opts href='opts:%d'>%s▼</a></td>" % (
|
||||||
# layout.addWidget(openButton, c+1, 5)
|
c, "Options")
|
||||||
# if c == 0:
|
|
||||||
# focusButton = openButton
|
|
||||||
# more
|
|
||||||
# moreButton = QPushButton(_("More"))
|
|
||||||
# moreMenu = QMenu()
|
|
||||||
# a = moreMenu.addAction(QIcon(":/icons/edit-undo.png"),
|
|
||||||
# _("Hide From List"))
|
|
||||||
# a.connect(a, SIGNAL("triggered()"),
|
|
||||||
# lambda c=c: self.onDeckBrowserForget(c))
|
|
||||||
# a = moreMenu.addAction(QIcon(":/icons/editdelete.png"),
|
|
||||||
# _("Delete"))
|
|
||||||
# a.connect(a, SIGNAL("triggered()"),
|
|
||||||
# lambda c=c: self.onDeckBrowserDelete(c))
|
|
||||||
# moreButton.setMenu(moreMenu)
|
|
||||||
# self.moreMenus.append(moreMenu)
|
|
||||||
# layout.addWidget(moreButton, c+1, 6)
|
|
||||||
buf += "</tr>"
|
buf += "</tr>"
|
||||||
if c != max:
|
if c != max:
|
||||||
buf += "<tr><td colspan=3><hr noshade></td></tr>"
|
buf += "<tr><td colspan=4><hr noshade></td></tr>"
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
|
def _optsForRow(self, n):
|
||||||
|
m = QMenu(self.mw)
|
||||||
|
# hide
|
||||||
|
a = m.addAction(QIcon(":/icons/edit-undo.png"), _("Hide From List"))
|
||||||
|
a.connect(a, SIGNAL("activated()"), lambda n=n: self._hideRow(n))
|
||||||
|
# delete
|
||||||
|
a = m.addAction(QIcon(":/icons/editdelete.png"), _("Delete"))
|
||||||
|
a.connect(a, SIGNAL("activated()"), lambda n=n: self._deleteRow(n))
|
||||||
|
m.exec_(QCursor.pos())
|
||||||
|
|
||||||
def _buttons(self):
|
def _buttons(self):
|
||||||
# refresh = QPushButton(_("Refresh"))
|
# refresh = QPushButton(_("Refresh"))
|
||||||
# refresh.setToolTip(_("Check due counts again (F5)"))
|
# refresh.setToolTip(_("Check due counts again (F5)"))
|
||||||
|
@ -248,10 +259,9 @@ later by using File>Close.
|
||||||
# some misbehaving filesystems may fail here
|
# some misbehaving filesystems may fail here
|
||||||
pass
|
pass
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
if "File is in use" in unicode(e):
|
if "locked" in unicode(e):
|
||||||
state = "in use"
|
state = "in use"
|
||||||
else:
|
else:
|
||||||
raise
|
|
||||||
state = "corrupt"
|
state = "corrupt"
|
||||||
self._decks.append({'name': base, 'state':state})
|
self._decks.append({'name': base, 'state':state})
|
||||||
if forget:
|
if forget:
|
||||||
|
@ -263,53 +273,50 @@ later by using File>Close.
|
||||||
|
|
||||||
def _reorderDecks(self):
|
def _reorderDecks(self):
|
||||||
print "reorder decks"
|
print "reorder decks"
|
||||||
return
|
# return
|
||||||
if self.mw.config['deckBrowserOrder'] == 0:
|
# if self.mw.config['deckBrowserOrder'] == 0:
|
||||||
self._decks.sort(key=itemgetter('mod'),
|
# self._decks.sort(key=itemgetter('mod'),
|
||||||
reverse=True)
|
# reverse=True)
|
||||||
else:
|
# else:
|
||||||
def custcmp(a, b):
|
# def custcmp(a, b):
|
||||||
x = cmp(not not b['due'], not not a['due'])
|
# x = cmp(not not b['due'], not not a['due'])
|
||||||
if x:
|
# if x:
|
||||||
return x
|
# return x
|
||||||
x = cmp(not not b['new'], not not a['new'])
|
# x = cmp(not not b['new'], not not a['new'])
|
||||||
if x:
|
# if x:
|
||||||
return x
|
# return x
|
||||||
return cmp(a['mod'], b['mod'])
|
# return cmp(a['mod'], b['mod'])
|
||||||
self._decks.sort(cmp=custcmp)
|
# self._decks.sort(cmp=custcmp)
|
||||||
|
# after the decks are sorted, assign shortcut keys to them
|
||||||
|
for c, d in enumerate(self._decks):
|
||||||
|
if c > 35:
|
||||||
|
d['accel'] = None
|
||||||
|
elif c < 9:
|
||||||
|
d['accel'] = str(c+1)
|
||||||
|
else:
|
||||||
|
d['accel'] = ord('a')+(c-10)
|
||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
self._browserLastRefreshed = 0
|
self._browserLastRefreshed = 0
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
def onDeckBrowserForget(self, c):
|
def _hideRow(self, c):
|
||||||
if aqt.utils.askUser(_("""\
|
if aqt.utils.askUser(_("""\
|
||||||
Hide %s from the list? You can File>Open it again later.""") %
|
Hide %s from the list? You can File>Open it again later.""") %
|
||||||
self._decks[c]['name']):
|
self._decks[c]['name']):
|
||||||
self.mw.config['recentDeckPaths'].remove(self._decks[c]['path'])
|
self.mw.config.delRecentDeck(self._decks[c]['path'])
|
||||||
del self._decks[c]
|
del self._decks[c]
|
||||||
self.doLater(100, self.showDeckBrowser)
|
self.refresh()
|
||||||
|
|
||||||
def onDeckBrowserDelete(self, c):
|
def _deleteRow(self, c):
|
||||||
deck = self._decks[c]['path']
|
|
||||||
if aqt.utils.askUser(_("""\
|
if aqt.utils.askUser(_("""\
|
||||||
Delete %s? If this deck is synchronized the online version will \
|
Delete %s? If this deck is synchronized the online version will \
|
||||||
not be touched.""") %
|
not be touched.""") % self._decks[c]['name']):
|
||||||
self._decks[c]['name']):
|
deck = self._decks[c]['path']
|
||||||
del self._decks[c]
|
|
||||||
os.unlink(deck)
|
os.unlink(deck)
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(re.sub(".anki$", ".media", deck))
|
shutil.rmtree(re.sub(".anki$", ".media", deck))
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
#self.config['recentDeckPaths'].remove(deck)
|
self.mw.config.delRecentDeck(deck)
|
||||||
self.doLater(100, self.showDeckBrowser)
|
self.refresh()
|
||||||
|
|
||||||
def onDeckBrowserForgetInaccessible(self):
|
|
||||||
self._checkDecks(forget=True)
|
|
||||||
|
|
||||||
def doLater(self, msecs, func):
|
|
||||||
timer = QTimer(self)
|
|
||||||
timer.setSingleShot(True)
|
|
||||||
timer.start(msecs)
|
|
||||||
self.connect(timer, SIGNAL("timeout()"), func)
|
|
||||||
|
|
15
aqt/main.py
15
aqt/main.py
|
@ -338,7 +338,7 @@ Please do not file a bug report with Anki.<br>""")
|
||||||
self.form.setupUi(self)
|
self.form.setupUi(self)
|
||||||
self.web = aqt.webview.AnkiWebView(self.form.centralwidget)
|
self.web = aqt.webview.AnkiWebView(self.form.centralwidget)
|
||||||
self.web.setObjectName("mainText")
|
self.web.setObjectName("mainText")
|
||||||
self.web.setFocusPolicy(Qt.ClickFocus)
|
self.web.setFocusPolicy(Qt.WheelFocus)
|
||||||
self.mainLayout = QVBoxLayout()
|
self.mainLayout = QVBoxLayout()
|
||||||
self.mainLayout.addWidget(self.web)
|
self.mainLayout.addWidget(self.web)
|
||||||
self.mainLayout.setContentsMargins(0,0,0,0)
|
self.mainLayout.setContentsMargins(0,0,0,0)
|
||||||
|
@ -378,14 +378,17 @@ Please do not file a bug report with Anki.<br>""")
|
||||||
def closeAllDeckWindows(self):
|
def closeAllDeckWindows(self):
|
||||||
aqt.dialogs.closeAll()
|
aqt.dialogs.closeAll()
|
||||||
|
|
||||||
|
def setKeyHandler(self, fn):
|
||||||
|
self._keyHandler = fn
|
||||||
|
|
||||||
def keyPressEvent(self, evt):
|
def keyPressEvent(self, evt):
|
||||||
"Show answer on RET or register answer."
|
"Show answer on RET or register answer."
|
||||||
print "keypressevent"
|
if self._keyHandler:
|
||||||
evt.ignore()
|
self._keyHandler(evt)
|
||||||
return
|
else:
|
||||||
|
evt.ignore()
|
||||||
|
|
||||||
|
def reviewKeyHandler(self, evt):
|
||||||
if evt.key() in (Qt.Key_Up,Qt.Key_Down,Qt.Key_Left,Qt.Key_Right,
|
if evt.key() in (Qt.Key_Up,Qt.Key_Down,Qt.Key_Left,Qt.Key_Right,
|
||||||
Qt.Key_PageUp,Qt.Key_PageDown):
|
Qt.Key_PageUp,Qt.Key_PageDown):
|
||||||
mf = self.bodyView.body.page().currentFrame()
|
mf = self.bodyView.body.page().currentFrame()
|
||||||
|
|
Loading…
Reference in a new issue