# Copyright: Damien Elmes # License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html from PyQt4.QtGui import * from PyQt4.QtCore import * import sys, re, time import ankiqt.forms import anki from ankiqt import ui from anki.utils import parseTags from anki.deck import newCardOrderLabels, newCardSchedulingLabels from anki.utils import hexifyID, dehexifyID tabs = ("Synchronization", "Scheduling", "Models", "Description", "Advanced") class DeckProperties(QDialog): def __init__(self, parent): QDialog.__init__(self, parent, Qt.Window) self.parent = parent self.d = parent.deck self.origMod = self.d.modified self.dialog = ankiqt.forms.deckproperties.Ui_DeckProperties() self.dialog.setupUi(self) self.dialog.newCardOrder.insertItems( 0, QStringList(newCardOrderLabels().values())) self.dialog.newCardScheduling.insertItems( 0, QStringList(newCardSchedulingLabels().values())) self.readData() self.connect(self.dialog.modelsAdd, SIGNAL("clicked()"), self.onAdd) self.connect(self.dialog.modelsEdit, SIGNAL("clicked()"), self.onEdit) self.connect(self.dialog.modelsDelete, SIGNAL("clicked()"), self.onDelete) self.connect(self.dialog.buttonBox, SIGNAL("helpRequested()"), self.helpRequested) self.connect(self.dialog.addSource, SIGNAL("clicked()"), self.onAddSource) self.connect(self.dialog.deleteSource, SIGNAL("clicked()"), self.onDeleteSource) self.show() def readData(self): # description self.dialog.deckDescription.setText(self.d.description) # syncing sn = self.d.syncName if sn: self.dialog.doSync.setCheckState(Qt.Checked) self.dialog.syncName.setText(sn) else: self.dialog.doSync.setCheckState(Qt.Unchecked) self.dialog.syncName.setText(self.d.name()) # priorities self.dialog.highPriority.setText(self.d.highPriority) self.dialog.medPriority.setText(self.d.medPriority) self.dialog.lowPriority.setText(self.d.lowPriority) self.dialog.postponing.setText(self.d.suspended) # scheduling for type in ("hard", "mid", "easy"): v = getattr(self.d, type + "IntervalMin") getattr(self.dialog, type + "Min").setText("%0.3f" % v) v = getattr(self.d, type + "IntervalMax") getattr(self.dialog, type + "Max").setText("%0.3f" % v) self.dialog.delay0.setText(unicode(self.d.delay0/60.0)) self.dialog.delay1.setText(unicode(self.d.delay1/60.0)) self.dialog.delay2.setText(unicode(self.d.delay2/60.0)) self.dialog.collapse.setCheckState(self.d.collapseTime and Qt.Checked or Qt.Unchecked) self.dialog.failedCardMax.setText(unicode(self.d.failedCardMax)) self.dialog.newCardsPerDay.setText(unicode(self.d.newCardsPerDay)) self.dialog.newCardOrder.setCurrentIndex(self.d.newCardOrder) self.dialog.newCardScheduling.setCurrentIndex(self.d.newCardSpacing) # sources self.sources = self.d.s.all("select id, name from sources") self.sourcesToRemove = [] self.drawSourcesTable() # models self.updateModelsList() # hour shift self.dialog.timeOffset.setText(str( (self.d.utcOffset - time.timezone) / 60.0 / 60.0)) def drawSourcesTable(self): self.dialog.sourcesTable.clear() self.dialog.sourcesTable.setRowCount(len(self.sources)) self.dialog.sourcesTable.setColumnCount(2) self.dialog.sourcesTable.setHorizontalHeaderLabels( QStringList([_("ID"), _("Name")])) self.dialog.sourcesTable.horizontalHeader().setResizeMode( QHeaderView.Stretch) self.dialog.sourcesTable.verticalHeader().hide() self.dialog.sourcesTable.setSelectionBehavior( QAbstractItemView.SelectRows) self.dialog.sourcesTable.setSelectionMode( QAbstractItemView.SingleSelection) self.sourceItems = [] n = 0 for (id, name) in self.sources: a = QTableWidgetItem(hexifyID(id)) b = QTableWidgetItem(name) self.sourceItems.append([a, b]) self.dialog.sourcesTable.setItem(n, 0, a) self.dialog.sourcesTable.setItem(n, 1, b) n += 1 def updateModelsList(self): self.dialog.modelsList.clear() self.models = [] for model in self.d.models: name = _("%(name)s [%(facts)d facts]") % { 'name': model.name, 'facts': self.d.modelUseCount(model), } self.models.append((name, model)) self.models.sort() for (name, model) in self.models: item = QListWidgetItem(name) self.dialog.modelsList.addItem(item) if model == self.d.currentModel: self.dialog.modelsList.setCurrentItem(item) def onAdd(self): m = ui.modelchooser.AddModel(self, self.parent).getModel() if m: self.d.addModel(m) self.updateModelsList() def onEdit(self): model = self.selectedModel() if not model: return ui.modelproperties.ModelProperties(self, model, self.parent) self.updateModelsList() def onDelete(self): model = self.selectedModel() row = self.dialog.modelsList.currentRow() if not model: return if len(self.d.models) < 2: ui.utils.showWarning(_("Please add another model first."), parent=self) return if self.d.s.scalar("select 1 from sources where id=:id", id=model.source): ui.utils.showWarning(_("This model is used by deck source:\n" "%s\nYou will need to remove the source " "first.") % hexifyID(model.source)) return count = self.d.modelUseCount(model) if count: if not ui.utils.askUser( _("This model is used by %d facts.\n" "Are you sure you want to delete it?\n" "If you delete it, these cards will be lost.") % count, parent=self): return self.d.deleteModel(model) self.updateModelsList() self.dialog.modelsList.setCurrentRow(row) def selectedModel(self): row = self.dialog.modelsList.currentRow() if row == -1: return None return self.models[self.dialog.modelsList.currentRow()][1] def updateField(self, obj, field, value): if getattr(obj, field) != value: setattr(obj, field, value) self.d.setModified() def helpRequested(self): idx = self.dialog.qtabwidget.currentIndex() QDesktopServices.openUrl(QUrl(ankiqt.appWiki + "DeckProperties#" + tabs[idx])) def onAddSource(self): (s, ret) = QInputDialog.getText(self, _("Anki"), _("Source ID:")) if not s: return rc = self.dialog.sourcesTable.rowCount() self.dialog.sourcesTable.insertRow(rc) a = QTableWidgetItem(s) b = QTableWidgetItem("") self.dialog.sourcesTable.setItem(rc, 0, a) self.dialog.sourcesTable.setItem(rc, 1, b) def onDeleteSource(self): r = self.dialog.sourcesTable.currentRow() if r == -1: return self.dialog.sourcesTable.removeRow(r) id = self.sources[r][0] self.sourcesToRemove.append(id) def reject(self): # description self.updateField(self.d, 'description', unicode(self.dialog.deckDescription.toPlainText())) # syncing if self.dialog.doSync.checkState() == Qt.Checked: self.updateField(self.d, 'syncName', unicode(self.dialog.syncName.text())) else: self.updateField(self.d, 'syncName', None) # scheduling minmax = ("Min", "Max") for type in ("hard", "mid", "easy"): v = getattr(self.dialog, type + "Min").text() try: v = float(v) except ValueError: continue self.updateField(self.d, type + "IntervalMin", v) v = getattr(self.dialog, type + "Max").text() try: v = float(v) except ValueError: continue self.updateField(self.d, type + "IntervalMax", v) try: v = float(self.dialog.delay0.text()) * 60.0 self.updateField(self.d, 'delay0', v) v = float(self.dialog.delay1.text()) * 60.0 self.updateField(self.d, 'delay1', v) v = float(self.dialog.delay2.text()) * 60.0 self.updateField(self.d, 'delay2', v) v = int(self.dialog.failedCardMax.text()) self.updateField(self.d, 'failedCardMax', v) v = int(self.dialog.newCardsPerDay.text()) self.updateField(self.d, 'newCardsPerDay', v) except ValueError: pass # hour shift try: self.updateField(self.d, 'utcOffset', float(str(self.dialog.timeOffset.text())) *60*60 + time.timezone) except: pass self.updateField(self.d, 'collapseTime', self.dialog.collapse.isChecked() and 1 or 0) self.updateField(self.d, "highPriority", unicode(self.dialog.highPriority.text())) self.updateField(self.d, "medPriority", unicode(self.dialog.medPriority.text())) self.updateField(self.d, "lowPriority", unicode(self.dialog.lowPriority.text())) self.updateField(self.d, "suspended", unicode(self.dialog.postponing.text())) # new card order self.updateField(self.d, "newCardOrder", self.dialog.newCardOrder.currentIndex()) self.updateField(self.d, "newCardSpacing", self.dialog.newCardScheduling.currentIndex()) # sources d = {} d.update(self.sources) for n in range(self.dialog.sourcesTable.rowCount()): try: id = dehexifyID(str(self.dialog.sourcesTable.item(n, 0).text())) except (ValueError,OverflowError): continue name = unicode(self.dialog.sourcesTable.item(n, 1).text()) if id in d: if d[id] == name: del d[id] continue # name changed self.d.s.statement( "update sources set name = :n where id = :id", id=id, n=name) else: self.d.s.statement(""" insert into sources values (:id, :n, :t, 0, 0)""", id=id, n=name, t=time.time()) self.d.setModified() try: del d[id] except KeyError: pass for id in self.sourcesToRemove + d.keys(): self.d.s.statement("delete from sources where id = :id", id=id) self.d.setModified() # mark deck dirty and close if self.origMod != self.d.modified: self.parent.reset() QDialog.reject(self)