diff --git a/ankiqt/config.py b/ankiqt/config.py index d3d3ce92f..84cb0b812 100644 --- a/ankiqt/config.py +++ b/ankiqt/config.py @@ -86,7 +86,6 @@ class Config(dict): 'showLastCardContent': False, 'showLastCardInterval': False, 'showProgress': True, - 'showStudyOptions': False, 'showStudyScreen': True, 'showStudyStats': True, 'showTimer': True, diff --git a/ankiqt/ui/activetags.py b/ankiqt/ui/activetags.py index cb33c2b57..12e6a9745 100644 --- a/ankiqt/ui/activetags.py +++ b/ankiqt/ui/activetags.py @@ -9,53 +9,33 @@ from ankiqt.ui.utils import saveGeom, restoreGeom class ActiveTagsChooser(QDialog): - def __init__(self, parent): + def __init__(self, parent, active, inactive): QDialog.__init__(self, parent, Qt.Window) self.parent = parent self.deck = self.parent.deck + self.active = active + self.inactive = inactive self.dialog = ankiqt.forms.activetags.Ui_Dialog() self.dialog.setupUi(self) - self.selectAll = QPushButton(_("Select All")) - self.connect(self.selectAll, SIGNAL("clicked()"), self.onSelectAll) - self.dialog.buttonBox.addButton(self.selectAll, - QDialogButtonBox.ActionRole) - self.selectNone = QPushButton(_("Select None")) - self.connect(self.selectNone, SIGNAL("clicked()"), self.onSelectNone) - self.dialog.buttonBox.addButton(self.selectNone, - QDialogButtonBox.ActionRole) - self.invert = QPushButton(_("Invert")) - self.connect(self.invert, SIGNAL("clicked()"), self.onInvert) - self.dialog.buttonBox.addButton(self.invert, - QDialogButtonBox.ActionRole) self.connect(self.dialog.buttonBox, SIGNAL("helpRequested()"), self.onHelp) self.rebuildTagList() restoreGeom(self, "activeTags") - def onSelectAll(self): - self.dialog.list.selectAll() - - def onSelectNone(self): - self.dialog.list.clearSelection() - - def onInvert(self): - sm = self.dialog.list.selectionModel() - sel = sm.selection() - self.dialog.list.selectAll() - sm.select(sel, QItemSelectionModel.Deselect) - def rebuildTagList(self): - self.tags = self.deck.allTags() + usertags = self.deck.allTags() self.items = [] self.suspended = {} - alltags = [] - # get list of currently suspended - for t in parseTags(self.deck.suspended): - self.suspended[t] = 1 - if t not in self.tags: - self.tags.append(t) - # sort and remove special 'Suspended' tag - self.tags.sort() + yes = parseTags(self.deck.getVar(self.active)) + no = parseTags(self.deck.getVar(self.inactive)) + yesHash = {} + noHash = {} + for y in yes: + yesHash[y] = True + for n in no: + noHash[n] = True + groupedTags = [] + usertags.sort() # render models and templates for (type, sql, icon) in ( ("models", "select tags from models", "contents.png"), @@ -66,60 +46,74 @@ class ActiveTagsChooser(QDialog): for tag in parseTags(tags): d[tag] = 1 sortedtags = sorted(d.keys()) - alltags.extend(sortedtags) icon = QIcon(":/icons/" + icon) - for t in sortedtags: - item = QListWidgetItem(icon, t.replace("_", " ")) - self.dialog.list.addItem(item) - self.items.append(item) - idx = self.dialog.list.indexFromItem(item) - if t in self.suspended: - mode = QItemSelectionModel.Select - else: - mode = QItemSelectionModel.Deselect - self.dialog.list.selectionModel().select(idx, mode) + groupedTags.append([icon, sortedtags]) # remove from user tags - for tag in alltags: + for tag in groupedTags[0][1] + groupedTags[1][1]: try: - self.tags.remove(tag) + usertags.remove(tag) except: pass # user tags icon = QIcon(":/icons/Anki_Fact.png") - for t in self.tags: - item = QListWidgetItem(icon, t.replace("_", " ")) - self.dialog.list.addItem(item) - self.items.append(item) - idx = self.dialog.list.indexFromItem(item) - if t in self.suspended: - mode = QItemSelectionModel.Select - else: - mode = QItemSelectionModel.Deselect - self.dialog.list.selectionModel().select(idx, mode) - self.tags = alltags + self.tags + groupedTags.append([icon, usertags]) + self.tags = [] + for (icon, tags) in groupedTags: + for t in tags: + self.tags.append(t) + item = QListWidgetItem(icon, t.replace("_", " ")) + self.dialog.activeList.addItem(item) + if t in yesHash: + mode = QItemSelectionModel.Select + self.dialog.activeCheck.setChecked(True) + else: + mode = QItemSelectionModel.Deselect + idx = self.dialog.activeList.indexFromItem(item) + self.dialog.activeList.selectionModel().select(idx, mode) + # inactive + item = QListWidgetItem(icon, t.replace("_", " ")) + self.dialog.inactiveList.addItem(item) + if t in noHash: + mode = QItemSelectionModel.Select + self.dialog.inactiveCheck.setChecked(True) + else: + mode = QItemSelectionModel.Deselect + idx = self.dialog.inactiveList.indexFromItem(item) + self.dialog.inactiveList.selectionModel().select(idx, mode) def accept(self): self.hide() - self.deck.startProgress() n = 0 - suspended = [] - for item in self.items: - idx = self.dialog.list.indexFromItem(item) - if self.dialog.list.selectionModel().isSelected(idx): - suspended.append(self.tags[n]) - n += 1 - self.deck.suspended = canonifyTags(joinTags(suspended)) - self.deck.setModified() - self.deck.updateAllPriorities(partial=True, dirty=False) + yes = [] + no = [] + for c in range(self.dialog.activeList.count()): + # active + item = self.dialog.activeList.item(c) + idx = self.dialog.activeList.indexFromItem(item) + if self.dialog.activeList.selectionModel().isSelected(idx): + yes.append(self.tags[c]) + # inactive + item = self.dialog.inactiveList.item(c) + idx = self.dialog.inactiveList.indexFromItem(item) + if self.dialog.inactiveList.selectionModel().isSelected(idx): + no.append(self.tags[c]) + + if self.dialog.activeCheck.isChecked(): + self.deck.setVar(self.active, joinTags(yes)) + else: + self.deck.setVar(self.active, "") + if self.dialog.inactiveCheck.isChecked(): + self.deck.setVar(self.inactive, joinTags(no)) + else: + self.deck.setVar(self.inactive, "") self.parent.reset() saveGeom(self, "activeTags") - self.deck.finishProgress() QDialog.accept(self) def onHelp(self): QDesktopServices.openUrl(QUrl(ankiqt.appWiki + "ActiveTags")) -def show(parent): - at = ActiveTagsChooser(parent) +def show(parent, active, inactive): + at = ActiveTagsChooser(parent, active, inactive) at.exec_() diff --git a/ankiqt/ui/deckproperties.py b/ankiqt/ui/deckproperties.py index a50befe38..c7fa69abc 100644 --- a/ankiqt/ui/deckproperties.py +++ b/ankiqt/ui/deckproperties.py @@ -62,7 +62,6 @@ class DeckProperties(QDialog): self.dialog.delay2.setText(unicode(self.d.delay2)) self.dialog.collapse.setCheckState(self.d.collapseTime and Qt.Checked or Qt.Unchecked) - self.dialog.failedCardMax.setText(unicode(self.d.failedCardMax)) self.dialog.perDay.setCheckState(self.d.getBool("perDay") and Qt.Checked or Qt.Unchecked) # sources @@ -245,10 +244,6 @@ class DeckProperties(QDialog): self.updateField(self.d, 'delay1', v2) v = float(self.dialog.delay2.text()) self.updateField(self.d, 'delay2', min(v, 1)) - v = int(self.dialog.failedCardMax.text()) - if v == 1 or v < 0: - v = 2 - self.updateField(self.d, 'failedCardMax', v) except ValueError: pass try: diff --git a/ankiqt/ui/main.py b/ankiqt/ui/main.py index fb3d5f6dd..d64958d33 100755 --- a/ankiqt/ui/main.py +++ b/ankiqt/ui/main.py @@ -826,6 +826,7 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors") ########################################################################## def onClose(self): + # allow focusOut to save if self.inMainWindow() or not self.app.activeWindow(): isCram = self.isCramming() self.saveAndClose(hideWelcome=isCram) @@ -836,6 +837,8 @@ Debug info:\n%s""") % traceback.format_exc(), help="DeckErrors") def saveAndClose(self, hideWelcome=False, parent=None): "(Auto)save and close. Prompt if necessary. True if okay to proceed." + # allow any focusOut()s to run first + self.setFocus() if not parent: parent = self self.hideWelcome = hideWelcome @@ -1465,6 +1468,7 @@ later by using File>Close. ########################################################################## def setupStudyScreen(self): + self.mainWin.buttonStack.hide() self.mainWin.newCardOrder.insertItems( 0, QStringList(newCardOrderLabels().values())) self.mainWin.newCardScheduling.insertItems( @@ -1475,9 +1479,10 @@ later by using File>Close. SIGNAL("clicked()"), lambda: QDesktopServices.openUrl(QUrl( ankiqt.appWiki + "StudyOptions"))) - self.mainWin.optionsBox.setShown(False) self.connect(self.mainWin.minuteLimit, SIGNAL("textChanged(QString)"), self.onMinuteLimitChanged) + self.connect(self.mainWin.questionLimit, + SIGNAL("textChanged(QString)"), self.onQuestionLimitChanged) self.connect(self.mainWin.newPerDay, SIGNAL("textChanged(QString)"), self.onNewLimitChanged) self.connect(self.mainWin.startReviewingButton, @@ -1485,6 +1490,37 @@ later by using File>Close. self.onStartReview) self.connect(self.mainWin.newCardOrder, SIGNAL("activated(int)"), self.onNewCardOrderChanged) + self.connect(self.mainWin.advancedOptions, + SIGNAL("clicked()"), + self.onAdvancedOptions) + self.connect(self.mainWin.failedCardMax, + SIGNAL("editingFinished()"), + self.onFailedMaxChanged) + self.connect(self.mainWin.newCategories, + SIGNAL("clicked()"), self.onNewCategoriesClicked) + self.connect(self.mainWin.revCategories, + SIGNAL("clicked()"), self.onRevCategoriesClicked) + + def onNewCategoriesClicked(self): + ui.activetags.show(self, "newActive", "newInactive") + + def onRevCategoriesClicked(self): + ui.activetags.show(self, "revActive", "revInactive") + + def onFailedMaxChanged(self): + try: + v = int(self.mainWin.failedCardMax.text()) + if v == 1 or v < 0: + v = 2 + self.deck.failedCardMax = v + except ValueError: + pass + self.mainWin.failedCardMax.setText(str(self.deck.failedCardMax)) + self.deck.flushMod() + + def onAdvancedOptions(self): + self.onDeckProperties() + self.deckProperties.dialog.qtabwidget.setCurrentIndex(2) def onMinuteLimitChanged(self, qstr): try: @@ -1497,6 +1533,17 @@ later by using File>Close. self.deck.flushMod() self.updateStudyStats() + def onQuestionLimitChanged(self, qstr): + try: + val = int(self.mainWin.questionLimit.text()) + if self.deck.sessionRepLimit == val: + return + self.deck.sessionRepLimit = val + except ValueError: + pass + self.deck.flushMod() + self.updateStudyStats() + def onNewLimitChanged(self, qstr): try: val = int(self.mainWin.newPerDay.text()) @@ -1531,15 +1578,28 @@ later by using File>Close. self.deck.finishProgress() uf(self.deck, 'newCardOrder', ncOrd) + def updateActives(self): + labels = [ + u"Show All Due Cards", + u"Show Chosen Categories" + ] + if self.deck.getVar("newActive") or self.deck.getVar("newInactive"): + new = labels[1] + else: + new = labels[0] + self.mainWin.newCategoryLabel.setText(new) + if self.deck.getVar("revActive") or self.deck.getVar("revInactive"): + rev = labels[1] + else: + rev = labels[0] + self.mainWin.revCategoryLabel.setText(rev) + def updateStudyStats(self): self.deck.reset() + self.updateActives() wasReached = self.deck.sessionLimitReached() sessionColour = '%s' cardColour = '%s' - if not wasReached: - top = _("

Study Options

") - else: - top = _("

Well done!

") # top label h = {} s = self.deck.getStats() @@ -1574,15 +1634,15 @@ day = :d""", d=yesterday) anki.utils.fmtTimeSpan(ttoday, short=True, point=1)) h['timeTodayChg'] = str(anki.utils.fmtTimeSpan( tyest, short=True, point=1)) - h['cs_header'] = _("Cards/session:") - h['cd_header'] = _("Cards/day:") - h['td_header'] = _("Time/day:") - h['rd_header'] = _("Reviews due:") - h['ntod_header'] = _("New today:") - h['ntot_header'] = _("New total:") + h['cs_header'] = "" + _("Cards/session:") + "" + h['cd_header'] = "" + _("Cards/day:") + "" + h['td_header'] = "" + _("Time/day:") + "" + h['rd_header'] = "" + _("Reviews due:") + "" + h['ntod_header'] = "" + _("New today:") + "" + h['ntot_header'] = "" + _("New total:") + "" stats1 = ("""\ - + @@ -1592,7 +1652,7 @@ day = :d""", d=yesterday) stats2 = ("""\
%(cs_header)s%(repsInSesChg)s
%(cs_header)s%(repsInSesChg)s %(repsInSes)s
%(cd_header)s%(repsTodayChg)s %(repsToday)s
- +
%(rd_header)s%(ret)s
%(rd_header)s%(ret)s
%(ntod_header)s%(new)s
%(ntot_header)s%(newof)s
""") % h @@ -1603,10 +1663,11 @@ day = :d""", d=yesterday) self.haveYesterday = True stats1 = ( "%s    " % stats1) - self.mainWin.optionsLabel.setText(top + """\ + self.mainWin.optionsLabel.setText("""\

%s -
%s
""" % (stats1, stats2)) + +


%s
""" % (stats1, stats2)) h['tt_header'] = _("Session Statistics") h['cs_tip'] = _("The number of cards you studied in the current \ session (blue) and previous session (black)") @@ -1632,22 +1693,12 @@ learnt today") def showStudyScreen(self): # forget last card self.lastCard = None - self.mainWin.optionsButton.setChecked(self.config['showStudyOptions']) - self.mainWin.optionsBox.setShown(self.config['showStudyOptions']) self.switchToStudyScreen() self.updateStudyStats() - # start reviewing button - self.mainWin.buttonStack.hide() - if self.reviewingStarted: - self.mainWin.startReviewingButton.setText(_("Continue &Reviewing")) - else: - self.mainWin.startReviewingButton.setText(_("Start &Reviewing")) self.mainWin.startReviewingButton.setFocus() self.setupStudyOptions() + self.mainWin.studyOptionsFrame.setFixedWidth(300) self.mainWin.studyOptionsFrame.show() - if self.haveYesterday: - size = self.mainWin.optionsLabel.sizeHint().width() + 50 - self.mainWin.studyOptionsFrame.setFixedWidth(size) def setupStudyOptions(self): self.mainWin.newPerDay.setText(str(self.deck.newCardsPerDay)) @@ -1666,6 +1717,7 @@ learnt today") labels = failedCardOptionLabels().values()[0:-1] self.mainWin.failedCardsOption.insertItems(0, labels) self.mainWin.failedCardsOption.setCurrentIndex(self.deck.getFailedCardPolicy()) + self.mainWin.failedCardMax.setText(unicode(self.deck.failedCardMax)) def onStartReview(self): def uf(obj, field, value): @@ -1675,15 +1727,6 @@ learnt today") self.mainWin.studyOptionsFrame.hide() # make sure the size is updated before button stack shown self.app.processEvents() - self.config['showStudyOptions'] = self.mainWin.optionsButton.isChecked() - try: - uf(self.deck, 'newCardsPerDay', int(self.mainWin.newPerDay.text())) - uf(self.deck, 'sessionTimeLimit', min(float( - self.mainWin.minuteLimit.text()), 3600) * 60) - uf(self.deck, 'sessionRepLimit', - int(self.mainWin.questionLimit.text())) - except (ValueError, OverflowError): - pass uf(self.deck, 'newCardSpacing', self.mainWin.newCardScheduling.currentIndex()) uf(self.deck, 'revCardOrder', @@ -1890,9 +1933,6 @@ will be lost when you close the deck.""")) def onDonate(self): QDesktopServices.openUrl(QUrl(ankiqt.appDonate)) - def onActiveTags(self): - ui.activetags.show(self) - # Importing & exporting ########################################################################## @@ -2374,7 +2414,6 @@ Are you sure?""" % deckName), "Graphs", "Dstats", "Cstats", - "ActiveTags", "StudyOptions", ) @@ -2426,7 +2465,6 @@ Are you sure?""" % deckName), self.connect(m.actionOpenPluginFolder, s, self.onOpenPluginFolder) self.connect(m.actionEnableAllPlugins, s, self.onEnableAllPlugins) self.connect(m.actionDisableAllPlugins, s, self.onDisableAllPlugins) - self.connect(m.actionActiveTags, s, self.onActiveTags) self.connect(m.actionReleaseNotes, s, self.onReleaseNotes) self.connect(m.actionCacheLatex, s, self.onCacheLatex) self.connect(m.actionUncacheLatex, s, self.onUncacheLatex) @@ -2980,8 +3018,6 @@ Consider backing up your media directory first.""")) def changeLayoutSpacing(self): if sys.platform.startswith("darwin"): self.mainWin.studyOptionsReviewBar.setContentsMargins(0, 20, 0, 0) - self.mainWin.optionsBox.layout().setSpacing(10) - self.mainWin.optionsBox.layout().setContentsMargins(4, 10, 4, 4) # Proxy support ########################################################################## diff --git a/designer/activetags.ui b/designer/activetags.ui index c18cfac5f..ef53e5f94 100644 --- a/designer/activetags.ui +++ b/designer/activetags.ui @@ -11,27 +11,48 @@ - Inactive Tags + Selective Study - - - - Cards with any of the selected tags below will not be shown. - - - true - - - - - - QAbstractItemView::MultiSelection - - + + + + + Show only cards with these tags: + + + + + + + false + + + QAbstractItemView::MultiSelection + + + + + + + Hide cards with these tags: + + + + + + + false + + + QAbstractItemView::MultiSelection + + + + @@ -56,8 +77,8 @@ accept() - 248 - 254 + 358 + 264 157 @@ -81,5 +102,37 @@ + + activeCheck + toggled(bool) + activeList + setEnabled(bool) + + + 133 + 18 + + + 133 + 85 + + + + + inactiveCheck + toggled(bool) + inactiveList + setEnabled(bool) + + + 146 + 213 + + + 68 + 276 + + + diff --git a/designer/deckproperties.ui b/designer/deckproperties.ui index b74e5e2f3..c4166305a 100644 --- a/designer/deckproperties.ui +++ b/designer/deckproperties.ui @@ -486,40 +486,14 @@ - - - - <b>Maximum failed cards</b> - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - + <b>New day starts at</b> - + Qt::Horizontal @@ -532,7 +506,7 @@ - + @@ -565,38 +539,38 @@ - + <b>Suspend leeches</b> - + - + <b>Leech failure threshold</b> - + - + <b>Per-day scheduling</b> - + @@ -658,11 +632,10 @@ easyMin easyMax collapse - failedCardMax + perDay timeOffset suspendLeeches leechFails - perDay buttonBox diff --git a/designer/main.ui b/designer/main.ui index ce117390b..77a02599d 100644 --- a/designer/main.ui +++ b/designer/main.ui @@ -6,7 +6,7 @@ 0 0 - 617 + 605 563 @@ -774,30 +774,23 @@ - QFrame::StyledPanel + QFrame::NoFrame QFrame::Raised + + 0 + 0 - 10 + 0 - - - - - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - @@ -815,185 +808,503 @@ - - - - 400 - 16777215 - + + + QTabWidget::Rounded - - Qt::Horizontal - - - - - - - 2 - - - 6 - - + 0 - - 6 - - - - - - 140 - 0 - + + + New Cards + + + + 15 - - The <b>number of minutes in a session</b>. When a session is finished, this screen will be shown again, allowing you to start another session. Choose 0 for no limit. + + 15 - - <b>Session limit (minutes):</b> + + 15 - - 4 + + 0 - - - - - - - 0 - 0 - + + + + 2 + + + 0 + + + 0 + + + 6 + + + + + + 140 + 0 + + + + The maximum number of <b>new cards shown per day</b>. The default is 20, to ensure you don't get overwhelmed with reviews after a few days. + + + <b>New Cards/Day:</b> + + + 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + <b>Display Order:</b> + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + <b>Selective Study:</b> + + + + + + + + + &Change + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + TextLabel + + + + + + + + + Qt::Vertical + + + + 20 + 15 + + + + + + + + + Reviews + + + + 15 - - - 50 - 16777215 - + + 15 - - - - - - - 140 - 0 - + + 15 - - The maximum number of <b>new cards shown per day</b>. The default is 20, to ensure you don't get overwhelmed with reviews after a few days. + + 8 - - <b>New cards per day:</b> + + + + + + <b>Max Failed Cards:</b> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 50 + 16777215 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + <b>Display Order:</b> + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + <b>Selective Study:</b> + + + + + + + + + &Change + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + TextLabel + + + + + + + + + Qt::Vertical + + + + 20 + 15 + + + + + + + + + + &Advanced + + + + + + + + + + Timeboxing + + + + 15 - - 4 + + 5 - - - - - - - 0 - 0 - + + 15 - - - 50 - 16777215 - + + 0 - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 50 - 16777215 - - - - - - - - The <b>number of questions in a session</b>. When a session is finished, this screen will be shown again, allowing you to start another session. Choose 0 for no limit. - - - <b>Session limit (questions):</b> - - - 4 - - - - + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 140 + 0 + + + + The <b>number of minutes in a session</b>. When a session is finished, this screen will be shown again, allowing you to start another session. Choose 0 for no limit. + + + <b>Session limit (minutes):</b> + + + 4 + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + + + + + The <b>number of questions in a session</b>. When a session is finished, this screen will be shown again, allowing you to start another session. Choose 0 for no limit. + + + <b>Session limit (questions):</b> + + + 4 + + + + + + + + 0 + 0 + + + + + 50 + 16777215 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 15 + + + + + + + - - - - - 10 - - - 2 - - - 6 - - - 0 - - - 0 - - - - - - - - - - - - - - - - - 20 + 10 10 @@ -1028,28 +1339,6 @@ - - - - 0 - 26 - - - - More - - - true - - - false - - - false - - - - @@ -2715,7 +3004,7 @@ 0 0 - 617 + 605 20 @@ -2835,8 +3124,6 @@ - - @@ -3476,51 +3763,38 @@ - newPerDay - minuteLimit - questionLimit - newCardOrder - newCardScheduling - revCardOrder - failedCardsOption - startReviewingButton - optionsButton - optionsHelpButton + easeButton3 + easeButton4 learnMoreButton reviewEarlyButton finishButton downloadDeckButton newDeckButton importDeckButton + newPerDay + newCardOrder + newCardScheduling + newCategories + startReviewingButton + optionsHelpButton + failedCardMax + revCardOrder + failedCardsOption + revCategories + advancedOptions + minuteLimit + questionLimit + welcomeText + saveEditorButton easeButton1 easeButton2 - easeButton3 - easeButton4 - saveEditorButton showAnswerButton help noticeButton - welcomeText + tabWidget - - - optionsButton - toggled(bool) - optionsBox - setShown(bool) - - - 218 - 250 - - - 222 - 300 - - - - +