diff --git a/aqt/config.py b/aqt/config.py index 1ffcfb1bb..65d790552 100644 --- a/aqt/config.py +++ b/aqt/config.py @@ -94,7 +94,7 @@ class Config(object): # load/save def load(self): path = self._dbPath() - self.db = DB(path, level=None, text=str) + self.db = DB(path, text=str) self.db.executescript(""" create table if not exists decks (path text primary key); create table if not exists config (conf text not null); diff --git a/aqt/deckbrowser.py b/aqt/deckbrowser.py index 3d68b30cb..71e2064a7 100644 --- a/aqt/deckbrowser.py +++ b/aqt/deckbrowser.py @@ -2,18 +2,25 @@ # -*- coding: utf-8 -*- # 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.QtGui import * from anki import Deck from anki.utils import fmtTimeSpan from anki.hooks import addHook +import aqt +#from aqt.utils import askUser _css = """ body { background-color: #eee; } #outer { margin-top: 1em; } .sub { color: #555; } 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 = """ @@ -25,6 +32,8 @@ _body = """

%s +

+Click a deck to open it, or press the number/letter next to it. """ @@ -37,18 +46,30 @@ class DeckBrowser(object): self._decks = [] addHook("deckClosing", self.onClose) - def _bridge(self, str): - if str == "refresh": - pass - elif str == "full": - self.onFull() + def _linkHandler(self, url): + (cmd, arg) = url.split(":") + if cmd == "open": + deck = self._decks[int(arg)]['path'] + self.mw.loadDeck(deck) + elif cmd == "opts": + self._optsForRow(int(arg)) - def _link(self, url): - pass + def _keyHandler(self, evt): + 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): - self.web.setBridge(self._bridge) - self.web.setLinkHandler(self._link) + self.web.setLinkHandler(self._linkHandler) + self.mw.setKeyHandler(self._keyHandler) if (time.time() - self._browserLastRefreshed > self.mw.config['deckBrowserRefreshPeriod']): t = time.time() @@ -91,22 +112,18 @@ later by using File>Close. def _deckRow(self, c, max, deck): buf = "" - # name and status ok = deck['state'] == 'ok' + def accelName(deck): + if deck['accel']: + return "%s. " % deck['accel'] + return "" if ok: - sub = _("%s ago") % fmtTimeSpan( - time.time() - deck['mod']) - elif deck['state'] == 'missing': - sub = _("(moved or removed)") - elif deck['state'] == 'corrupt': - sub = _("(corrupt)") - elif deck['state'] == 'in use': - sub = _("(already open)") - sub = "%s" % sub - buf += "%s
%s" % (deck['name'], sub) - if ok: + # name/link + buf += "%s%s" % ( + accelName(deck), + "%s"%(c, deck['name'])) # due - col = '%s' + col = '%s' if deck['due'] > 0: s = col % str(deck['due']) else: @@ -117,47 +134,41 @@ later by using File>Close. s = str(deck['new']) else: s = "" - buf += "%s" % s + buf += "%s" % s else: - buf += "" - # open - # openButton = QPushButton(_("Open")) - # if c < 9: - # if sys.platform.startswith("darwin"): - # extra = "" - # # appears to be broken on osx - # #extra = _(" (Command+Option+%d)") % (c+1) - # #openButton.setShortcut(_("Ctrl+Alt+%d" % (c+1))) - # else: - # extra = _(" (Alt+%d)") % (c+1) - # openButton.setShortcut(_("Alt+%d" % (c+1))) - # else: - # extra = "" - # openButton.setToolTip(_("Open this deck%s") % extra) - # self.connect(openButton, SIGNAL("clicked()"), - # lambda d=deck['path']: self.loadDeck(d)) - # layout.addWidget(openButton, c+1, 5) - # 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) + # name/error + if deck['state'] == 'missing': + sub = _("(moved or removed)") + elif deck['state'] == 'corrupt': + sub = _("(corrupt)") + elif deck['state'] == 'in use': + sub = _("(already open)") + else: + sub = "unknown" + buf += "%s%s
%s" % ( + accelName(deck), + deck['name'], + sub) + # no counts + buf += "" + # options + buf += "%s▼" % ( + c, "Options") buf += "" if c != max: - buf += "


" + 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): # refresh = QPushButton(_("Refresh")) # refresh.setToolTip(_("Check due counts again (F5)")) @@ -248,10 +259,9 @@ later by using File>Close. # some misbehaving filesystems may fail here pass except Exception, e: - if "File is in use" in unicode(e): + if "locked" in unicode(e): state = "in use" else: - raise state = "corrupt" self._decks.append({'name': base, 'state':state}) if forget: @@ -263,53 +273,50 @@ later by using File>Close. def _reorderDecks(self): print "reorder decks" - return - if self.mw.config['deckBrowserOrder'] == 0: - self._decks.sort(key=itemgetter('mod'), - reverse=True) - else: - def custcmp(a, b): - x = cmp(not not b['due'], not not a['due']) - if x: - return x - x = cmp(not not b['new'], not not a['new']) - if x: - return x - return cmp(a['mod'], b['mod']) - self._decks.sort(cmp=custcmp) + # return + # if self.mw.config['deckBrowserOrder'] == 0: + # self._decks.sort(key=itemgetter('mod'), + # reverse=True) + # else: + # def custcmp(a, b): + # x = cmp(not not b['due'], not not a['due']) + # if x: + # return x + # x = cmp(not not b['new'], not not a['new']) + # if x: + # return x + # return cmp(a['mod'], b['mod']) + # 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): self._browserLastRefreshed = 0 self.show() - def onDeckBrowserForget(self, c): + def _hideRow(self, c): if aqt.utils.askUser(_("""\ Hide %s from the list? You can File>Open it again later.""") % 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] - self.doLater(100, self.showDeckBrowser) + self.refresh() - def onDeckBrowserDelete(self, c): - deck = self._decks[c]['path'] + def _deleteRow(self, c): if aqt.utils.askUser(_("""\ Delete %s? If this deck is synchronized the online version will \ -not be touched.""") % - self._decks[c]['name']): - del self._decks[c] +not be touched.""") % self._decks[c]['name']): + deck = self._decks[c]['path'] os.unlink(deck) try: shutil.rmtree(re.sub(".anki$", ".media", deck)) except OSError: pass - #self.config['recentDeckPaths'].remove(deck) - self.doLater(100, self.showDeckBrowser) - - 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) + self.mw.config.delRecentDeck(deck) + self.refresh() diff --git a/aqt/main.py b/aqt/main.py index 6d0d0cff3..f3bb64b38 100755 --- a/aqt/main.py +++ b/aqt/main.py @@ -338,7 +338,7 @@ Please do not file a bug report with Anki.
""") self.form.setupUi(self) self.web = aqt.webview.AnkiWebView(self.form.centralwidget) self.web.setObjectName("mainText") - self.web.setFocusPolicy(Qt.ClickFocus) + self.web.setFocusPolicy(Qt.WheelFocus) self.mainLayout = QVBoxLayout() self.mainLayout.addWidget(self.web) self.mainLayout.setContentsMargins(0,0,0,0) @@ -378,14 +378,17 @@ Please do not file a bug report with Anki.
""") def closeAllDeckWindows(self): aqt.dialogs.closeAll() - - + def setKeyHandler(self, fn): + self._keyHandler = fn def keyPressEvent(self, evt): "Show answer on RET or register answer." - print "keypressevent" - evt.ignore() - return + if self._keyHandler: + self._keyHandler(evt) + else: + evt.ignore() + + def reviewKeyHandler(self, evt): if evt.key() in (Qt.Key_Up,Qt.Key_Down,Qt.Key_Left,Qt.Key_Right, Qt.Key_PageUp,Qt.Key_PageDown): mf = self.bodyView.body.page().currentFrame()