new recent decks system, improve deck browser

This commit is contained in:
Damien Elmes 2009-06-06 00:21:29 +09:00
parent 89783c3e17
commit fbfbda1a58
3 changed files with 159 additions and 82 deletions

View file

@ -96,6 +96,7 @@ class Config(dict):
'proxyUser': '', 'proxyUser': '',
'proxyPass': '', 'proxyPass': '',
'loadLastDeck': False, 'loadLastDeck': False,
'deckBrowserRefreshPeriod': 3600,
} }
for (k,v) in fields.items(): for (k,v) in fields.items():
if not self.has_key(k): if not self.has_key(k):

View file

@ -240,7 +240,6 @@ Please do not file a bug report with Anki.<br>""")
self.currentCard = None self.currentCard = None
self.lastCard = None self.lastCard = None
self.editor.deck = self.deck self.editor.deck = self.deck
self.updateRecentFilesMenu()
if self.deck: if self.deck:
self.enableDeckMenuItems() self.enableDeckMenuItems()
self.updateViews(state) self.updateViews(state)
@ -270,7 +269,6 @@ Please do not file a bug report with Anki.<br>""")
self.help.hide() self.help.hide()
self.currentCard = None self.currentCard = None
self.lastCard = None self.lastCard = None
self.updateRecentFilesMenu()
self.disableDeckMenuItems() self.disableDeckMenuItems()
# hide all deck-associated dialogs # hide all deck-associated dialogs
self.closeAllDeckWindows() self.closeAllDeckWindows()
@ -709,33 +707,35 @@ To upgrade an old deck, download Anki 0.9.8.7."""))
self.config['recentDeckPaths'].insert(0, path) self.config['recentDeckPaths'].insert(0, path)
del self.config['recentDeckPaths'][20:] del self.config['recentDeckPaths'][20:]
self.config.save() self.config.save()
self.updateRecentFilesMenu()
def updateRecentFilesMenu(self): def onSwitchToDeck(self):
self.config['recentDeckPaths'] = [ diag = QDialog(self)
p for p in self.config['recentDeckPaths'] diag.setWindowTitle(_("Open Recent Deck"))
if os.path.exists(p)] vbox = QVBoxLayout()
if not self.config['recentDeckPaths']: combo = QComboBox()
self.mainWin.menuOpenRecent.setEnabled(False) self.switchDecks = (
return [(os.path.basename(x).replace(".anki", ""), x)
self.mainWin.menuOpenRecent.setEnabled(True) for x in self.config['recentDeckPaths']
self.mainWin.menuOpenRecent.clear() if not self.deck or self.deck.path != x])
n = 1 self.switchDecks.sort()
for file in self.config['recentDeckPaths']: combo.addItems(QStringList([x[0] for x in self.switchDecks]))
a = QAction(self) self.connect(combo, SIGNAL("activated(int)"),
if sys.platform.startswith("darwin"): self.onSwitchActivated)
a.setShortcut(_("Ctrl+Alt+%d" % n)) vbox.addWidget(combo)
else: bbox = QDialogButtonBox(QDialogButtonBox.Cancel)
a.setShortcut(_("Alt+%d" % n)) self.connect(bbox, SIGNAL("rejected()"),
a.setText(os.path.basename(file)) lambda: self.switchDeckDiag.close())
a.setStatusTip(os.path.abspath(file)) vbox.addWidget(bbox)
self.connect(a, SIGNAL("triggered()"), diag.setLayout(vbox)
lambda n=n: self.loadRecent(n-1)) diag.show()
self.mainWin.menuOpenRecent.addAction(a) combo.showPopup()
n += 1 combo.setFocus()
self.switchDeckDiag = diag
diag.exec_()
def loadRecent(self, n): def onSwitchActivated(self, idx):
self.loadDeck(self.config['recentDeckPaths'][n]) self.switchDeckDiag.close()
self.loadDeck(self.switchDecks[idx][1])
# New files, loading & saving # New files, loading & saving
########################################################################## ##########################################################################
@ -758,6 +758,12 @@ To upgrade an old deck, download Anki 0.9.8.7."""))
if self.deck is not None: if self.deck is not None:
if self.deck.reviewEarly: if self.deck.reviewEarly:
self.deck.resetAfterReviewEarly() self.deck.resetAfterReviewEarly()
# update counts
for d in self.browserDecks:
if d['path'] == self.deck.path:
d['due'] = self.deck.failedSoonCount + self.deck.revCount
d['new'] = self.deck.newCountToday
d['mod'] = self.deck.modified
if self.deck.modifiedSinceSave(): if self.deck.modifiedSinceSave():
if (self.deck.path is None or if (self.deck.path is None or
(not self.config['saveOnClose'] and (not self.config['saveOnClose'] and
@ -807,6 +813,7 @@ To upgrade an old deck, download Anki 0.9.8.7."""))
self.deck.initUndo() self.deck.initUndo()
self.deck.addModel(BasicModel()) self.deck.addModel(BasicModel())
self.deck.save() self.deck.save()
self.browserLastRefreshed = 0
self.moveToState("initial") self.moveToState("initial")
def ensureSyncParams(self): def ensureSyncParams(self):
@ -858,11 +865,13 @@ To upgrade an old deck, download Anki 0.9.8.7."""))
if self.syncDeck(onlyMerge=True, reload=2, interactive=False): if self.syncDeck(onlyMerge=True, reload=2, interactive=False):
return return
self.deck = None self.deck = None
self.browserLastRefreshed = 0
self.moveToState("initial") self.moveToState("initial")
def onGetSharedDeck(self): def onGetSharedDeck(self):
if not self.inMainWindow(): return if not self.inMainWindow(): return
ui.getshared.GetShared(self, 0) ui.getshared.GetShared(self, 0)
self.browserLastRefreshed = 0
def onGetSharedPlugin(self): def onGetSharedPlugin(self):
if not self.inMainWindow(): return if not self.inMainWindow(): return
@ -885,6 +894,7 @@ To upgrade an old deck, download Anki 0.9.8.7."""))
return False return False
else: else:
self.updateRecentFiles(file) self.updateRecentFiles(file)
self.browserLastRefreshed = 0
return True return True
def showToolTip(self, msg): def showToolTip(self, msg):
@ -955,6 +965,7 @@ your deck."""))
self.deck.initUndo() self.deck.initUndo()
self.updateTitleBar() self.updateTitleBar()
self.updateRecentFiles(self.deck.path) self.updateRecentFiles(self.deck.path)
self.browserLastRefreshed = 0
self.moveToState("initial") self.moveToState("initial")
return file return file
@ -971,32 +982,13 @@ your deck."""))
self.connect(self.mainWin.importDeckButton, self.connect(self.mainWin.importDeckButton,
SIGNAL("clicked()"), SIGNAL("clicked()"),
self.onImport) self.onImport)
self.browserLastRefreshed = 0
self.browserDecks = []
def showDeckBrowser(self): def refreshBrowserDecks(self):
import sip self.browserDecks = []
focusButton = None if not self.config['recentDeckPaths']:
self.switchToDecksScreen() return
if self.mainWin.decksFrame.layout():
while 1:
obj = self.mainWin.decksFrame.layout().takeAt(0)
if not obj:
break
if "QLabel" in repr(obj.widget()):
sip.delete(obj.widget())
else:
obj.widget().deleteLater()
sip.delete(obj)
sip.delete(self.mainWin.decksFrame.layout())
layout = QGridLayout()
self.deckBrowserDecks = []
if self.config['recentDeckPaths']:
layout.addWidget(QLabel(_("<b>Deck</b>")), 0, 0)
l = QLabel(_("<b>Due</b>"))
l.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
layout.addWidget(l, 0, 1)
l = QLabel(_("<b>New</b>"))
l.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
layout.addWidget(l, 0, 2)
toRemove = [] toRemove = []
if ui.splash.finished: if ui.splash.finished:
self.startProgress(max=len(self.config['recentDeckPaths'])) self.startProgress(max=len(self.config['recentDeckPaths']))
@ -1012,35 +1004,104 @@ your deck."""))
except: except:
toRemove.append(d) toRemove.append(d)
continue continue
self.deckBrowserDecks.append(d) self.browserDecks.append({
# name & stats 'path': d,
n = deck.name() 'name': deck.name(),
'due': deck.failedSoonCount + deck.revCount,
'new': deck.newCountToday,
'mod': deck.modified,
})
deck.close()
for d in toRemove:
self.config['recentDeckPaths'].remove(d)
self.config.save()
if ui.splash.finished:
self.finishProgress()
self.browserLastRefreshed = time.time()
def reorderBrowserDecks(self):
h = {}
for d in self.browserDecks:
h[d['path']] = d
self.browserDecks = []
for path in self.config['recentDeckPaths']:
try:
self.browserDecks.append(h[path])
except:
pass
def forceBrowserRefresh(self):
self.browserLastRefreshed = 0
self.showDeckBrowser()
def showDeckBrowser(self):
import sip
focusButton = None
self.switchToDecksScreen()
# remove all widgets from layout & layout itself
if self.mainWin.decksFrame.layout():
while 1:
obj = self.mainWin.decksFrame.layout().takeAt(0)
if not obj:
break
if "QLabel" in repr(obj.widget()):
sip.delete(obj.widget())
else:
obj.widget().deleteLater()
sip.delete(obj)
sip.delete(self.mainWin.decksFrame.layout())
# build new layout
layout = QGridLayout()
if (time.time() - self.browserLastRefreshed >
self.config['deckBrowserRefreshPeriod']):
self.refreshBrowserDecks()
else:
self.reorderBrowserDecks()
if self.browserDecks:
layout.addWidget(QLabel(_("<b>Deck</b>")), 0, 0)
l = QLabel(_("<b>Due<br>Today</b>"))
l.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
layout.addWidget(l, 0, 1)
l = QLabel(_("<b>New<br>Today</b>"))
l.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
layout.addWidget(l, 0, 2)
# space
layout.addWidget(QLabel(" " * 4), 0, 3)
for c, deck in enumerate(self.browserDecks):
# name
n = deck['name']
if len(n) > 30: if len(n) > 30:
n = n[:30] + "..." n = n[:30] + "..."
layout.addWidget(QLabel(n), c+1, 0) mod = _("%s ago") % anki.utils.fmtTimeSpan(
l = QLabel("<b>" + str(deck.failedSoonCount + deck.revCount) + time.time() - deck['mod'])
"</b>") layout.addWidget(QLabel(
"%d. <b>%s</b><br>&nbsp;&nbsp;&nbsp;&nbsp;<i>%s</i>" %
(c+1, n, mod)), c+1, 0)
# due
col = '<b><font color=#0000ff>%s</font></b>'
if deck['due'] > 0:
s = col % str(deck['due'])
else:
s = ""
l = QLabel(s)
l.setMinimumWidth(50) l.setMinimumWidth(50)
l.setAlignment(Qt.AlignRight | Qt.AlignVCenter) l.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
layout.addWidget(l, c+1, 1) layout.addWidget(l, c+1, 1)
l = QLabel(str(deck.newCount)) # new
if deck['new']:
s = str(deck['new'])
else:
s = ""
l = QLabel(s)
l.setMinimumWidth(50) l.setMinimumWidth(50)
l.setAlignment(Qt.AlignRight | Qt.AlignVCenter) l.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
layout.addWidget(l, c+1, 2) layout.addWidget(l, c+1, 2)
# open # open
openButton = QPushButton(_("Open")) openButton = QPushButton(_("Open"))
extra = "" openButton.setToolTip(_("Open this deck"))
if c < 9:
extra = " (Alt+%d)" % (c+1)
openButton.setToolTip(_("Open this deck%s") % extra)
if c < 9:
if sys.platform.startswith("darwin"):
openButton.setShortcut(_("Ctrl+Alt+%d" % (c+1)))
else:
openButton.setShortcut(_("Alt+%d" % (c+1)))
self.connect(openButton, SIGNAL("clicked()"), self.connect(openButton, SIGNAL("clicked()"),
lambda d=d: self.loadDeck(d)) lambda d=deck['path']: self.loadDeck(d))
layout.addWidget(openButton, c+1, 3) layout.addWidget(openButton, c+1, 4)
if c == 0: if c == 0:
focusButton = openButton focusButton = openButton
# more # more
@ -1053,19 +1114,24 @@ your deck."""))
_("Forget removes the deck from the list without deleting.")) _("Forget removes the deck from the list without deleting."))
self.connect(moreButton, SIGNAL("currentIndexChanged(int)"), self.connect(moreButton, SIGNAL("currentIndexChanged(int)"),
lambda idx, c=c: self.onDeckBrowserMore(idx, c)) lambda idx, c=c: self.onDeckBrowserMore(idx, c))
layout.addWidget(moreButton, c+1, 4) layout.addWidget(moreButton, c+1, 5)
deck.close()
refresh = QPushButton(_("Refresh")) refresh = QPushButton(_("Refresh"))
refresh.setToolTip(_("Check due counts again (Ctrl+r)"))
refresh.setShortcut(_("Ctrl+r"))
self.connect(refresh, SIGNAL("clicked()"), self.connect(refresh, SIGNAL("clicked()"),
self.showDeckBrowser) self.forceBrowserRefresh)
layout.addWidget(refresh, c+2, 3) layout.addWidget(refresh, c+2, 4)
for d in toRemove:
self.config['recentDeckPaths'].remove(d)
if ui.splash.finished:
self.finishProgress()
else: else:
layout.addWidget(QLabel(_( l = QLabel(_("""\
"Welcome to Anki! Click 'Download Deck' to get started.")), 0, 0) <br>
<font size=+1>
Welcome to Anki! Click <b>download deck</b> to get started. You can return here
later by clicking on the left-pointing arrow on the toolbar.
</font>
<br>
"""))
l.setWordWrap(True)
layout.addWidget(l, 0, 0)
self.mainWin.decksFrame.setLayout(layout) self.mainWin.decksFrame.setLayout(layout)
self.app.processEvents() self.app.processEvents()
if focusButton: if focusButton:
@ -1076,11 +1142,13 @@ your deck."""))
return return
elif idx == 1: elif idx == 1:
# forget # forget
self.config['recentDeckPaths'].remove(self.deckBrowserDecks[c]) self.config['recentDeckPaths'].remove(self.browserDecks[c]['path'])
del self.browserDecks[c]
self.showDeckBrowser() self.showDeckBrowser()
elif idx == 2: elif idx == 2:
# delete # delete
deck = self.deckBrowserDecks[c] deck = self.browserDecks[c]['path']
del self.browserDecks[c]
if ui.utils.askUser(_("Delete %s?") % os.path.basename(deck)): if ui.utils.askUser(_("Delete %s?") % os.path.basename(deck)):
os.unlink(deck) os.unlink(deck)
self.config['recentDeckPaths'].remove(deck) self.config['recentDeckPaths'].remove(deck)
@ -2065,6 +2133,7 @@ Couldn't contact Anki Online. Please check your internet connection.""")
self.connect(m.actionOpenOnline, s, self.onOpenOnline) self.connect(m.actionOpenOnline, s, self.onOpenOnline)
self.connect(m.actionDownloadSharedDeck, s, self.onGetSharedDeck) self.connect(m.actionDownloadSharedDeck, s, self.onGetSharedDeck)
self.connect(m.actionDownloadSharedPlugin, s, self.onGetSharedPlugin) self.connect(m.actionDownloadSharedPlugin, s, self.onGetSharedPlugin)
self.connect(m.actionOpenRecent, s, self.onSwitchToDeck)
self.connect(m.actionOpen, s, self.onOpen) self.connect(m.actionOpen, s, self.onOpen)
self.connect(m.actionSave, s, self.onSave) self.connect(m.actionSave, s, self.onSave)
self.connect(m.actionSaveAs, s, self.onSaveAs) self.connect(m.actionSaveAs, s, self.onSaveAs)

View file

@ -1501,14 +1501,9 @@
<addaction name="actionDownloadSharedDeck" /> <addaction name="actionDownloadSharedDeck" />
<addaction name="actionDownloadSharedPlugin" /> <addaction name="actionDownloadSharedPlugin" />
</widget> </widget>
<widget class="QMenu" name="menuOpenRecent" >
<property name="title" >
<string>Open &amp;Recent</string>
</property>
</widget>
<addaction name="actionNew" /> <addaction name="actionNew" />
<addaction name="actionOpen" /> <addaction name="actionOpen" />
<addaction name="menuOpenRecent" /> <addaction name="actionOpenRecent" />
<addaction name="menuDownload" /> <addaction name="menuDownload" />
<addaction name="actionImport" /> <addaction name="actionImport" />
<addaction name="separator" /> <addaction name="separator" />
@ -2315,6 +2310,18 @@
<string>123</string> <string>123</string>
</property> </property>
</action> </action>
<action name="actionOpenRecent" >
<property name="icon" >
<iconset resource="../icons.qrc" >
<normaloff>:/icons/document-open-recent.png</normaloff>:/icons/document-open-recent.png</iconset>
</property>
<property name="text" >
<string>Open Recent...</string>
</property>
<property name="shortcut" >
<string>Ctrl+Shift+R</string>
</property>
</action>
</widget> </widget>
<tabstops> <tabstops>
<tabstop>newPerDay</tabstop> <tabstop>newPerDay</tabstop>