14
14
from anki.utils import fmtTimeSpan, parseTags, findTag, addTags, deleteTags, \
16
16
from ankiqt.ui.utils import saveGeom, restoreGeom, saveSplitter, restoreSplitter
17
from ankiqt.ui.utils import saveHeader, restoreHeader
17
from ankiqt.ui.utils import saveHeader, restoreHeader, saveState, \
18
restoreState, applyStyles
18
19
from anki.errors import *
19
20
from anki.db import *
20
21
from anki.stats import CardStats
167
181
def updateCard(self, index):
169
183
self.cards[index.row()] = self.deck.s.first("""
170
select id, question, answer, due, reps, factId, created, modified,
171
interval, factor, priority from cards where id = :id""",
184
select id, question, answer, combinedDue, reps, factId, created, modified,
185
interval, factor, noCount, priority, (select tags from facts where
186
facts.id = cards.factId), (select created from facts where
187
facts.id = cards.factId) from cards where id = :id""",
172
188
id=self.cards[index.row()][0])
173
self.emit(SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
174
index, self.index(index.row(), 1))
189
self.emit(SIGNAL("layoutChanged()"))
176
191
# called after search changed
195
self.cards = [[x[0]] for x in self.cards]
196
self.emit(SIGNAL("layoutChanged()"))
180
199
######################################################################
221
240
return self.repsColumn(index)
222
241
elif self.sortKey == "factor":
223
242
return self.easeColumn(index)
243
elif self.sortKey == "noCount":
244
return self.noColumn(index)
245
elif self.sortKey == "fact":
246
return self.factCreatedColumn(index)
225
248
return self.nextDue(index)
243
270
return time.strftime("%Y-%m-%d", time.localtime(
244
271
self.cards[index.row()][CARD_CREATED]))
273
def factCreatedColumn(self, index):
274
return time.strftime("%Y-%m-%d", time.localtime(
275
self.cards[index.row()][CARD_FACTCREATED]))
246
277
def modifiedColumn(self, index):
247
278
return time.strftime("%Y-%m-%d", time.localtime(
248
279
self.cards[index.row()][CARD_MODIFIED]))
257
288
def easeColumn(self, index):
258
289
return "%0.2f" % self.cards[index.row()][CARD_EASE]
291
def noColumn(self, index):
292
return "%d" % self.cards[index.row()][CARD_NO]
260
294
class StatusDelegate(QItemDelegate):
262
296
def __init__(self, parent, model):
267
301
if len(self.model.cards[index.row()]) == 1:
268
302
self.model.updateCard(index)
269
303
row = self.model.cards[index.row()]
304
if row[CARD_PRIORITY] == -3:
306
if index.row() % 2 == 0:
307
brush = QBrush(QColor(COLOUR_SUSPENDED1))
309
brush = QBrush(QColor(COLOUR_SUSPENDED2))
311
painter.fillRect(option.rect, brush)
270
313
if row[CARD_PRIORITY] == 0:
272
brush = QBrush(QColor("#ffffaa"))
315
if index.row() % 2 == 0:
316
brush = QBrush(QColor(COLOUR_INACTIVE1))
318
brush = QBrush(QColor(COLOUR_INACTIVE2))
320
painter.fillRect(option.rect, brush)
322
elif "Marked" in row[CARD_TAGS]:
323
if index.row() % 2 == 0:
324
brush = QBrush(QColor(COLOUR_MARKED1))
326
brush = QBrush(QColor(COLOUR_MARKED2))
274
328
painter.fillRect(option.rect, brush)
275
329
painter.restore()
292
347
self.lastFilter = ""
293
348
self.dialog = ankiqt.forms.cardlist.Ui_MainWindow()
294
349
self.dialog.setupUi(self)
350
self.setUnifiedTitleAndToolBarOnMac(True)
295
351
restoreGeom(self, "editor")
352
restoreState(self, "editor")
296
353
restoreSplitter(self.dialog.splitter, "editor")
355
self.dialog.toolBar.setIconSize(QSize(self.config['iconSize'],
356
self.config['iconSize']))
357
self.dialog.toolBar.toggleViewAction().setText(_("Toggle Toolbar"))
297
358
# flush all changes before we load
298
359
self.deck.s.flush()
299
360
self.model = DeckModel(self.parent, self.parent.deck)
300
361
self.dialog.tableView.setSortingEnabled(False)
362
self.dialog.tableView.setShowGrid(False)
301
363
self.dialog.tableView.setModel(self.model)
302
364
self.dialog.tableView.selectionModel()
303
365
self.connect(self.dialog.tableView.selectionModel(),
304
366
SIGNAL("selectionChanged(QItemSelection,QItemSelection)"),
305
367
self.updateFilterLabel)
306
368
self.dialog.tableView.setItemDelegate(StatusDelegate(self, self.model))
307
if self.deck.getInt("reverseOrder"):
308
self.dialog.actionReverseOrder.setChecked(True)
369
self.updateSortOrder()
309
370
self.updateFont()
310
371
self.setupMenus()
311
372
self.setupFilter()
320
381
self.updateFilterLabel()
323
383
if self.parent.currentCard:
324
384
self.currentCard = self.parent.currentCard
325
self.focusCurrentCard()
326
386
if sys.platform.startswith("darwin"):
327
387
self.macCloseShortcut = QShortcut(QKeySequence("Ctrl+w"), self)
328
388
self.connect(self.macCloseShortcut, SIGNAL("activated()"),
331
def findCardInDeckModel(self, model, card):
332
for i, thisCard in enumerate(model.cards):
333
if thisCard[0] == card.id:
391
def findCardInDeckModel(self):
392
for i, thisCard in enumerate(self.model.cards):
393
if thisCard[0] == self.currentCard.id:
361
421
self.connect(self.dialog.sortBox, SIGNAL("activated(int)"),
362
422
self.sortChanged)
363
423
self.sortChanged(self.sortIndex, refresh=False)
424
self.connect(self.dialog.sortOrder, SIGNAL("clicked()"),
365
427
def drawTags(self):
366
428
self.dialog.tagList.view().setFixedWidth(200)
367
429
self.dialog.tagList.setMaxVisibleItems(30)
368
430
self.dialog.tagList.setFixedWidth(130)
369
431
self.dialog.tagList.clear()
370
alltags = [None, "Marked", "Suspended", None, None, None]
432
alltags = [None, "Marked", None, None, "Leech", None, None]
372
self.dialog.tagList.addItem(_("<Filter>"))
434
self.dialog.tagList.addItem(_("Show All Cards"))
373
435
self.dialog.tagList.addItem(QIcon(":/icons/rating.png"),
375
437
self.dialog.tagList.addItem(QIcon(":/icons/media-playback-pause.png"),
377
439
self.dialog.tagList.addItem(QIcon(":/icons/chronometer.png"),
441
self.dialog.tagList.addItem(QIcon(":/icons/emblem-important.png"),
379
443
self.dialog.tagList.addItem(QIcon(":/icons/editclear.png"),
380
444
_('No fact tags'))
381
445
self.dialog.tagList.insertSeparator(
394
458
icon = QIcon(":/icons/" + icon)
395
459
for t in sortedtags:
396
460
self.dialog.tagList.addItem(icon, t.replace("_", " "))
398
self.dialog.tagList.insertSeparator(
399
self.dialog.tagList.count())
462
self.dialog.tagList.insertSeparator(
463
self.dialog.tagList.count())
401
466
alluser = sorted(self.deck.allTags())
402
467
for tag in alltags:
431
497
self.sortIndex = 0
432
498
self.dialog.sortBox.setCurrentIndex(self.sortIndex)
500
def updateSortOrder(self):
501
if self.deck.getInt("reverseOrder"):
502
self.dialog.sortOrder.setIcon(QIcon(":/icons/view-sort-descending.png"))
504
self.dialog.sortOrder.setIcon(QIcon(":/icons/view-sort-ascending.png"))
434
506
def sortChanged(self, idx, refresh=True):
436
508
self.sortKey = "question"
466
540
def rebuildSortIndex(self, key):
468
542
"question", "answer", "created", "modified", "due", "interval",
543
"reps", "factor", "noCount"):
471
545
old = self.deck.s.scalar("select sql from sqlite_master where name = :k",
472
546
k="ix_cards_sort")
511
587
"cur": len(self.model.cards),
512
588
"tot": self.deck.cardCount,
513
589
"sel": ngettext("%d selected", "%d selected", selected) % selected
590
} + " - " + self.parent.deck.name())
592
def onEvent(self, type='field'):
517
593
if self.deck.undoAvailable():
518
594
self.dialog.actionUndo.setText(_("Undo %s") %
519
595
self.deck.undoName())
553
631
def updateSearch(self, force=True):
554
632
if self.parent.inDbHandler:
556
idx = self.dialog.tableView.currentIndex()
558
634
self.model.searchStr = unicode(self.dialog.filterEdit.text())
559
635
self.model.showMatching(force)
560
636
self.updateFilterLabel()
562
638
self.filterTimer = None
563
639
if self.model.cards:
566
640
self.dialog.cardInfoGroup.show()
567
641
self.dialog.fieldsArea.show()
569
643
self.dialog.cardInfoGroup.hide()
570
644
self.dialog.fieldsArea.hide()
571
self.dialog.tableView.selectRow(row)
572
self.dialog.tableView.scrollTo(idx, QAbstractItemView.PositionAtCenter)
645
if not self.focusCurrentCard():
647
self.dialog.tableView.selectRow(0)
573
648
if not self.model.cards:
574
649
self.editor.setFact(None)
576
651
def focusCurrentCard(self):
577
652
if self.currentCard:
578
currentCardIndex = self.findCardInDeckModel(
579
self.model, self.currentCard)
657
currentCardIndex = self.findCardInDeckModel()
580
658
if currentCardIndex >= 0:
581
659
sm = self.dialog.tableView.selectionModel()
597
677
def setupMenus(self):
679
self.connect(self.dialog.actionAddItems, SIGNAL("triggered()"), self.parent.onAddCard)
599
680
self.connect(self.dialog.actionDelete, SIGNAL("triggered()"), self.deleteCards)
600
681
self.connect(self.dialog.actionAddTag, SIGNAL("triggered()"), self.addTags)
601
682
self.connect(self.dialog.actionDeleteTag, SIGNAL("triggered()"), self.deleteTags)
603
684
self.connect(self.dialog.actionCram, SIGNAL("triggered()"), self.cram)
604
685
self.connect(self.dialog.actionAddCards, SIGNAL("triggered()"), self.addCards)
605
686
self.connect(self.dialog.actionChangeModel, SIGNAL("triggered()"), self.onChangeModel)
687
self.connect(self.dialog.actionToggleSuspend, SIGNAL("triggered(bool)"), self.onSuspend)
688
self.connect(self.dialog.actionToggleMark, SIGNAL("triggered(bool)"), self.onMark)
607
690
self.connect(self.dialog.actionFont, SIGNAL("triggered()"), self.onFont)
608
691
self.connect(self.dialog.actionUndo, SIGNAL("triggered()"), self.onUndo)
609
692
self.connect(self.dialog.actionRedo, SIGNAL("triggered()"), self.onRedo)
610
693
self.connect(self.dialog.actionInvertSelection, SIGNAL("triggered()"), self.invertSelection)
611
self.connect(self.dialog.actionReverseOrder, SIGNAL("triggered()"), self.reverseOrder)
612
694
self.connect(self.dialog.actionSelectFacts, SIGNAL("triggered()"), self.selectFacts)
613
695
self.connect(self.dialog.actionFindReplace, SIGNAL("triggered()"), self.onFindReplace)
618
700
self.connect(self.dialog.actionNextCard, SIGNAL("triggered()"), self.onNextCard)
619
701
self.connect(self.dialog.actionFind, SIGNAL("triggered()"), self.onFind)
620
702
self.connect(self.dialog.actionFact, SIGNAL("triggered()"), self.onFact)
703
self.connect(self.dialog.actionTags, SIGNAL("triggered()"), self.onTags)
704
self.connect(self.dialog.actionSort, SIGNAL("triggered()"), self.onSort)
705
self.connect(self.dialog.actionCardList, SIGNAL("triggered()"), self.onCardList)
622
707
self.connect(self.dialog.actionGuide, SIGNAL("triggered()"), self.onHelp)
623
708
runHook('editor.setupMenus', self)
634
719
self.editor.setFact(None)
635
720
self.editor.close()
636
721
saveGeom(self, "editor")
722
saveState(self, "editor")
637
723
saveHeader(self.dialog.tableView.horizontalHeader(), "editor")
639
725
ui.dialogs.close("CardList")
640
self.parent.moveToState("auto")
726
if self.parent.currentCard:
727
self.parent.moveToState("showQuestion")
729
self.parent.moveToState("auto")
641
730
self.teardownHooks()
724
816
"select id from cards where factId in (%s)" %
725
817
",".join([str(s) for s in self.selectedFacts()]))
727
def updateAfterCardChange(self, reset=False):
819
def updateAfterCardChange(self):
728
820
"Refresh info like stats on current card"
729
821
self.currentRow = self.dialog.tableView.currentIndex()
730
822
self.rowChanged(self.currentRow, None)
733
825
self.parent.moveToState("auto")
738
830
def deleteCards(self):
739
831
cards = self.selectedCards()
740
832
n = _("Delete Cards")
834
new = self.findCardInDeckModel() + 1
836
# card has been deleted
838
self.dialog.tableView.setFocus()
741
839
self.deck.setUndoStart(n)
742
840
self.deck.deleteCards(cards)
743
841
self.deck.setUndoEnd(n)
842
new = min(max(0, new), len(self.model.cards) - 1)
843
self.dialog.tableView.selectRow(new)
744
844
self.updateSearch()
745
845
self.updateAfterCardChange()
748
(tags, r) = ui.utils.getTag(self, self.deck, _("Enter tags to add:"))
847
def addTags(self, tags=None, label=None):
849
(tags, r) = ui.utils.getTag(self, self.deck, _("Enter tags to add:"))
853
label = _("Add Tags")
751
855
self.parent.setProgressParent(self)
752
self.deck.setUndoStart(n)
856
self.deck.setUndoStart(label)
753
857
self.deck.addTags(self.selectedFacts(), tags)
754
self.deck.setUndoEnd(n)
858
self.deck.setUndoEnd(label)
755
859
self.parent.setProgressParent(None)
756
860
self.updateAfterCardChange()
758
def deleteTags(self):
759
(tags, r) = ui.utils.getTag(self, self.deck, _("Enter tags to delete:"))
862
def deleteTags(self, tags=None, label=None):
864
(tags, r) = ui.utils.getTag(self, self.deck, _("Enter tags to delete:"))
868
label = _("Delete Tags")
762
870
self.parent.setProgressParent(self)
763
self.deck.setUndoStart(n)
871
self.deck.setUndoStart(label)
764
872
self.deck.deleteTags(self.selectedFacts(), tags)
765
self.deck.setUndoEnd(n)
873
self.deck.setUndoEnd(label)
766
874
self.parent.setProgressParent(None)
767
875
self.updateAfterCardChange()
877
def updateToggles(self):
878
self.dialog.actionToggleSuspend.setChecked(self.isSuspended())
879
self.dialog.actionToggleMark.setChecked(self.isMarked())
881
def isSuspended(self):
882
return self.currentCard and self.currentCard.priority == -3
884
def onSuspend(self, sus):
890
def _onSuspend(self):
892
self.parent.setProgressParent(self)
893
self.deck.setUndoStart(n)
894
self.deck.suspendCards(self.selectedCards())
895
self.deck.setUndoEnd(n)
896
self.parent.setProgressParent(None)
899
def _onUnsuspend(self):
901
self.parent.setProgressParent(self)
902
self.deck.setUndoStart(n)
903
self.deck.unsuspendCards(self.selectedCards())
904
self.deck.setUndoEnd(n)
905
self.parent.setProgressParent(None)
909
return self.currentCard and "Marked" in self.currentCard.fact.tags
911
def onMark(self, mark):
918
self.addTags(tags="Marked", label=_("Toggle Mark"))
921
self.deleteTags(tags="Marked", label=_("Toggle Mark"))
769
923
def reschedule(self):
770
924
n = _("Reschedule")
771
925
d = QDialog(self)
779
933
self.deck.resetCards(self.selectedCards())
782
min = float(str(frm.rangeMin.text()))
783
max = float(str(frm.rangeMax.text()))
936
min = float(frm.rangeMin.value())
937
max = float(frm.rangeMax.value())
784
938
except ValueError:
785
939
ui.utils.showInfo(
786
_("Please enter a valid start and end range."),
940
_("Please enter a valid range."),
789
943
self.deck.rescheduleCards(self.selectedCards(), min, max)
819
973
facts = self.deck.s.query(Fact).filter(
820
974
text("id in %s" % ids2str(sf))).order_by(Fact.created).all()
821
975
self.deck.updateProgress(_("Generating Cards..."))
822
977
for c, fact in enumerate(facts):
823
self.deck.addCards(fact, d.selectedCms)
978
ids.extend(self.deck.addCards(fact, d.selectedCms))
825
980
self.deck.updateProgress()
826
981
self.deck.flushMod()
827
self.deck.updateAllPriorities()
982
self.deck.updatePriorities(ids)
828
983
self.deck.finishProgress()
829
984
self.parent.setProgressParent(None)
830
985
self.deck.setUndoEnd(n)
866
1021
######################################################################
868
1023
def selectFacts(self):
1024
self.deck.startProgress()
869
1025
sm = self.dialog.tableView.selectionModel()
1026
sm.blockSignals(True)
870
1027
cardIds = dict([(x, 1) for x in self.selectedFactsAsCards()])
871
1028
for i, card in enumerate(self.model.cards):
872
1029
if card.id in cardIds:
873
1030
sm.select(self.model.index(i, 0),
874
1031
QItemSelectionModel.Select | QItemSelectionModel.Rows)
1033
self.deck.updateProgress()
1034
sm.blockSignals(False)
1035
self.deck.finishProgress()
1036
self.updateFilterLabel()
1037
self.updateAfterCardChange()
877
1039
def invertSelection(self):
878
1040
sm = self.dialog.tableView.selectionModel()
1209
1383
combos=self.fieldCombos,
1210
1384
new=self.targetModel.fieldModels)
1387
self.parent.deck.currentModel = self.origModel
1388
self.modelChooser.deinit()
1389
return QDialog.reject(self)
1212
1391
def accept(self):
1213
1392
saveGeom(self, "changeModel")
1393
self.parent.deck.currentModel = self.origModel
1215
1395
fmap = self.getFieldMap()
1216
1396
cmap = self.getTemplateMap()
1217
1397
if not cmap or (self.targetModel != self.oldModel and
1219
return ui.utils.showInfo(
1220
1400
_("Targets must be unique."), parent=self)
1402
if [c for c in cmap.values() if not c]:
1403
if not ui.utils.askUser(_("""\
1404
Any cards with templates mapped to nothing will be deleted.
1405
If a fact has no remaining cards, it will be lost.
1406
Are you sure you want to continue?"""), parent=self):
1408
self.modelChooser.deinit()
1221
1409
if self.targetModel == self.oldModel:
1222
1410
self.ret = (self.targetModel, None, cmap)
1223
1411
return QDialog.accept(self)