From 00461b3719d25af63d3eb683e7a8223cfed8e7d3 Mon Sep 17 00:00:00 2001 From: Ryan Pearl Date: Sun, 19 Feb 2017 11:31:32 -0800 Subject: [PATCH] Rearrangement --- CatchupPlugin.py | 296 +++++++++++++++++++++++++++++++++++++++++++- README.md | 4 +- catchup/__init__.py | 294 ------------------------------------------- 3 files changed, 296 insertions(+), 298 deletions(-) delete mode 100644 catchup/__init__.py diff --git a/CatchupPlugin.py b/CatchupPlugin.py index fd97318..c63b208 100644 --- a/CatchupPlugin.py +++ b/CatchupPlugin.py @@ -1,2 +1,296 @@ -from catchup import main +from aqt import mw +from aqt.qt import * +from aqt.utils import showInfo + +# list of functions that this exposes: + +#1) suspend all overdue cards (tag them) +#2) unsuspend oldest n days + +#configuration +# +# chosen tag for overdue cards controlled by this app + +class CatchupSettings(QDialog): + def add_labeled_widget(self, label, control, small=False): + if not isinstance(label, QLabel): + label = QLabel(label) + self._num_lines += 1 + width = 1 if small else 2 + self.grid.addWidget( label, self._num_lines, 0, 1, 1) + self.grid.addWidget(control, self._num_lines, 1, 1, width) + return control + + def add_rule(self, start=0, width=3): + frame = QFrame() + frame.setFrameShape(QFrame.HLine) + frame.setFrameShadow(QFrame.Sunken) + self._num_lines += 1 + self.grid.addWidget(frame, self._num_lines, start, 1, width) + + def add_empty(self): + self.add_label("") + + def add_widget(self, control, left=True, small=False): + start = 0 if left else 1 + width = 1 if small else 3-start + self._num_lines += 1 + self.grid.addWidget(control, self._num_lines, start, 1, width) + return control + + def add_label(self, label): + ql = QLabel(label) + font = QFont() + font.setBold(True) + ql.setFont(font) + return self.add_widget(ql) + + def load_conf(self): + if 'catchup' not in mw.col.conf: + mw.col.conf['catchup'] = { + 'query': '', + 'unsuspend_query': '', + 'tag': 'catchup', + 'numdays': 1, + 'show_stats': False, + } + mw.col.setMod() + mw.reset() + + def display_conf(self): + config = mw.col.conf['catchup'] + self.ctl_query.setText(config['query']) + self.ctl_tag.setText(config['tag']) + self.ctl_numdays.setValue(config['numdays']) + self.ctl_unsuspend_query.setText(config['unsuspend_query']) + self.ctl_show_stats.setChecked(config['show_stats']) + + def on_accept(self): + if not self.ctl_tag.text(): + showInfo('You must specify a tag') + return + config = mw.col.conf['catchup'] + + old_tag = config['tag'] + + config['query'] = self.ctl_query.text() + config['unsuspend_query'] = self.ctl_unsuspend_query.text() + config['tag'] = self.ctl_tag.text() + config['numdays'] = self.ctl_numdays.value() + config['show_stats'] = self.ctl_show_stats.isChecked() + + cids = mw.col.findCards('tag:%s' % (old_tag,)) + for cid in cids: + card = mw.col.getCard(cid) + note = card.note() + note.delTag(old_tag) + note.addTag(config['tag']) + note.flush() + + mw.col.setMod() + mw.reset() + self.set_clean_config() + + def on_reject(self): + self.display_conf() + self.set_clean_config() + mw.reset() + + def do_suspend(self): + config = mw.col.conf['catchup'] + query = 'is:due' + if config['query']: + query += ' (%s)' % (config['query']) + + cids = mw.col.findCards(query) + for cid in cids: + card = mw.col.getCard(cid) + note = card.note() + note.addTag(config['tag']) + note.flush() + mw.col.sched.suspendCards(cids) + self.update_stats() + showInfo('%d cards suspended' % len(cids)) + + def get_tagged_cids(self, query=None): + config = mw.col.conf['catchup'] + find_query = 'tag:%s is:suspended' % (config['tag'],) + if query: + find_query += ' (%s)' % (query,) + cids = mw.col.findCards(find_query, order='c.type, c.due') + + return cids + + def _fixup_tags(self, unsuspended): + config = mw.col.conf['catchup'] + cids = self.get_tagged_cids() + for cid in cids: + if cid in unsuspended: + continue + card = mw.col.getCard(cid) + note = card.note() + note.addTag(config['tag']) + note.flush() + + def do_unsuspend(self): + config = mw.col.conf['catchup'] + + query = '-is:new '+config['unsuspend_query'] + + cids = self.get_tagged_cids(query=query) + + unsuspended = [] + + days = set() + + for cid in cids: + card = mw.col.getCard(cid) + days.add(card.due) + if len(days) > config['numdays']: + break + note = card.note() + note.delTag(config['tag']) + note.flush() + unsuspended.append(cid) + mw.col.sched.unsuspendCards(unsuspended) + + self._fixup_tags(unsuspended) + + self.update_stats() + showInfo('%d cards unsuspended' % len(unsuspended)) + + def do_unsuspend_new(self): + config = mw.col.conf['catchup'] + cids = self.get_tagged_cids(query='is:new') + unsuspended = [] + for cid in cids: + card = mw.col.getCard(cid) + note = card.note() + note.delTag(config['tag']) + note.flush() + unsuspended.append(cid) + mw.col.sched.unsuspendCards(unsuspended) + self._fixup_tags(unsuspended) + + self.update_stats() + showInfo('%d cards unsuspended' % len(unsuspended)) + + + def set_dirty_config(self): + self.ctl_suspend.setEnabled(False) + self.ctl_unsuspend.setEnabled(False) + + def set_clean_config(self): + self.ctl_suspend.setEnabled(True) + self.ctl_unsuspend.setEnabled(True) + + def toggle_stats(self): + config = mw.col.conf['catchup'] + config['show_stats'] = self.ctl_show_stats.isChecked() + mw.col.setMod() + mw.reset() + self.update_stats() + + def update_stats(self): + config = mw.col.conf['catchup'] + if config['show_stats']: + days = set() + + all_cids = self.get_tagged_cids() + new_behind = 0 + + for cid in all_cids: + card = mw.col.getCard(cid) + if card.type == 2: # review + days.add(card.due) + else: + new_behind += 1 + total_behind = len(all_cids) + review_behind = total_behind - new_behind + self.days_behind_label.setText("Days behind") + self.days_behind.setText("%d" % len(days)) + self.cards_behind_label.setText("Cards behind") + self.cards_behind.setText("New/Learn: %d\t In review: %d\t Total %d" % + (new_behind, review_behind, total_behind) + ) + else: + self.days_behind_label.setText("Intentionally left blank") + self.days_behind.setText("") + self.cards_behind_label.setText("") + self.cards_behind.setText("") + + def __init__(self): + QDialog.__init__(self, parent=mw) + self.load_conf() + self.grid = QGridLayout() + self.grid.setSpacing(10) + self._num_lines = 0 + + self.add_label("Settings") + self.ctl_query = self.add_labeled_widget("Suspend filter", QLineEdit()) + self.ctl_tag = self.add_labeled_widget("Catchup tag", QLineEdit()) + self.add_empty() + self.ctl_unsuspend_query = self.add_labeled_widget("Unsuspend filter", QLineEdit()) + self.ctl_numdays = self.add_labeled_widget("Number of days to unsuspend", QSpinBox(), small=True) + + self.ctl_save = self.add_widget(QPushButton("Save settings"), small=True) + self.ctl_cancel = self.add_widget(QPushButton("Undo"), small=True) + + self.ctl_save.clicked.connect(self.on_accept) + self.ctl_cancel.clicked.connect(self.on_reject) + + self.add_rule() + self.stats_label = self.add_label("Stats") + self.ctl_show_stats = self.add_labeled_widget('Show stats', QCheckBox()) + + self.days_behind_label = QLabel("Days behind") + self.cards_behind_label = QLabel("Cards behind") + + self.days_behind = self.add_labeled_widget(self.days_behind_label, QLabel("")) + self.cards_behind = self.add_labeled_widget(self.cards_behind_label, QLabel("")) + self.update_stats() + + self.add_rule() + + self.add_label("Actions") + self.ctl_suspend = self.add_widget(QPushButton("Suspend overdue cards"), small=True) + self.ctl_unsuspend = self.add_widget(QPushButton("Unsuspend earliest cards"), small=True) + self.ctl_unsuspend_new = self.add_widget(QPushButton("Unsuspend new/learn cards"), small=True) + + self.ctl_suspend.clicked.connect(self.do_suspend) + self.ctl_unsuspend.clicked.connect(self.do_unsuspend) + self.ctl_unsuspend_new.clicked.connect(self.do_unsuspend_new) + + self.add_rule() + self.add_widget(QLabel('Check the README for instructions and examples')) + self.ctl_close = self.add_widget(QPushButton("Close"), small=True) + self.ctl_close.clicked.connect(self.close) + self.ctl_close.setDefault(True) + + + layout_main = QVBoxLayout() + layout_main.addLayout(self.grid) + + self.display_conf() + + self.ctl_query.textEdited.connect(self.set_dirty_config) + self.ctl_unsuspend_query.textEdited.connect(self.set_dirty_config) + self.ctl_tag.textEdited.connect(self.set_dirty_config) + self.ctl_numdays.valueChanged.connect(self.set_dirty_config) + + self.ctl_show_stats.stateChanged.connect(self.toggle_stats) + + self.setLayout(layout_main) + self.setMinimumWidth(512) + self.setWindowTitle('CatchUp') + +def on_catchup_settings(): + dialog = CatchupSettings() + dialog.exec_() + +def main(): + action = QAction("&CatchUp", mw) + action.triggered.connect(on_catchup_settings) + mw.form.menuTools.addAction(action) + main() diff --git a/README.md b/README.md index e959d5a..76f15b0 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,7 @@ This is somewhat antithetical to the Idea of Anki, because you're supposed to do You could! But I'm lazy. You could do all of this manually, with just suspend and tagging; it wouldn't be very complicated, it would just be fiddly and repetitive. This streamlines it. ## Installation -To install CatchUp, download the [latest .zip release](https://github.com/rpearl/anki-catchup/releases/latest). Extract it and copy `CatchupPlugin.py` and the `catchup `directory to your Anki add-ons directory. Your folder structure should look like this: - - `Documents/Anki/addons/CatchupPlugin.py` - - `Documents/Anki/addons/catchup/*allFilesAndDirectories*` +To install CatchUp, download the [latest .zip release](https://github.com/rpearl/anki-catchup/releases/latest). Extract it and copy `CatchupPlugin.py` to your Anki add-ons directory. After restarting Anki you should see an entry called CatchUp under the Tools menu. diff --git a/catchup/__init__.py b/catchup/__init__.py deleted file mode 100644 index c4c1316..0000000 --- a/catchup/__init__.py +++ /dev/null @@ -1,294 +0,0 @@ -from aqt import mw -from aqt.qt import * -from aqt.utils import showInfo - -# list of functions that this exposes: - -#1) suspend all overdue cards (tag them) -#2) unsuspend oldest n days - -#configuration -# -# chosen tag for overdue cards controlled by this app - -class CatchupSettings(QDialog): - def add_labeled_widget(self, label, control, small=False): - if not isinstance(label, QLabel): - label = QLabel(label) - self._num_lines += 1 - width = 1 if small else 2 - self.grid.addWidget( label, self._num_lines, 0, 1, 1) - self.grid.addWidget(control, self._num_lines, 1, 1, width) - return control - - def add_rule(self, start=0, width=3): - frame = QFrame() - frame.setFrameShape(QFrame.HLine) - frame.setFrameShadow(QFrame.Sunken) - self._num_lines += 1 - self.grid.addWidget(frame, self._num_lines, start, 1, width) - - def add_empty(self): - self.add_label("") - - def add_widget(self, control, left=True, small=False): - start = 0 if left else 1 - width = 1 if small else 3-start - self._num_lines += 1 - self.grid.addWidget(control, self._num_lines, start, 1, width) - return control - - def add_label(self, label): - ql = QLabel(label) - font = QFont() - font.setBold(True) - ql.setFont(font) - return self.add_widget(ql) - - def load_conf(self): - if 'catchup' not in mw.col.conf: - mw.col.conf['catchup'] = { - 'query': '', - 'unsuspend_query': '', - 'tag': 'catchup', - 'numdays': 1, - 'show_stats': False, - } - mw.col.setMod() - mw.reset() - - def display_conf(self): - config = mw.col.conf['catchup'] - self.ctl_query.setText(config['query']) - self.ctl_tag.setText(config['tag']) - self.ctl_numdays.setValue(config['numdays']) - self.ctl_unsuspend_query.setText(config['unsuspend_query']) - self.ctl_show_stats.setChecked(config['show_stats']) - - def on_accept(self): - if not self.ctl_tag.text(): - showInfo('You must specify a tag') - return - config = mw.col.conf['catchup'] - - old_tag = config['tag'] - - config['query'] = self.ctl_query.text() - config['unsuspend_query'] = self.ctl_unsuspend_query.text() - config['tag'] = self.ctl_tag.text() - config['numdays'] = self.ctl_numdays.value() - config['show_stats'] = self.ctl_show_stats.isChecked() - - cids = mw.col.findCards('tag:%s' % (old_tag,)) - for cid in cids: - card = mw.col.getCard(cid) - note = card.note() - note.delTag(old_tag) - note.addTag(config['tag']) - note.flush() - - mw.col.setMod() - mw.reset() - self.set_clean_config() - - def on_reject(self): - self.display_conf() - self.set_clean_config() - mw.reset() - - def do_suspend(self): - config = mw.col.conf['catchup'] - query = 'is:due' - if config['query']: - query += ' (%s)' % (config['query']) - - cids = mw.col.findCards(query) - for cid in cids: - card = mw.col.getCard(cid) - note = card.note() - note.addTag(config['tag']) - note.flush() - mw.col.sched.suspendCards(cids) - self.update_stats() - showInfo('%d cards suspended' % len(cids)) - - def get_tagged_cids(self, query=None): - config = mw.col.conf['catchup'] - find_query = 'tag:%s is:suspended' % (config['tag'],) - if query: - find_query += ' (%s)' % (query,) - cids = mw.col.findCards(find_query, order='c.type, c.due') - - return cids - - def _fixup_tags(self, unsuspended): - config = mw.col.conf['catchup'] - cids = self.get_tagged_cids() - for cid in cids: - if cid in unsuspended: - continue - card = mw.col.getCard(cid) - note = card.note() - note.addTag(config['tag']) - note.flush() - - def do_unsuspend(self): - config = mw.col.conf['catchup'] - - query = '-is:new '+config['unsuspend_query'] - - cids = self.get_tagged_cids(query=query) - - unsuspended = [] - - days = set() - - for cid in cids: - card = mw.col.getCard(cid) - days.add(card.due) - if len(days) > config['numdays']: - break - note = card.note() - note.delTag(config['tag']) - note.flush() - unsuspended.append(cid) - mw.col.sched.unsuspendCards(unsuspended) - - self._fixup_tags(unsuspended) - - self.update_stats() - showInfo('%d cards unsuspended' % len(unsuspended)) - - def do_unsuspend_new(self): - config = mw.col.conf['catchup'] - cids = self.get_tagged_cids(query='is:new') - unsuspended = [] - for cid in cids: - card = mw.col.getCard(cid) - note = card.note() - note.delTag(config['tag']) - note.flush() - unsuspended.append(cid) - mw.col.sched.unsuspendCards(unsuspended) - self._fixup_tags(unsuspended) - - self.update_stats() - showInfo('%d cards unsuspended' % len(unsuspended)) - - - def set_dirty_config(self): - self.ctl_suspend.setEnabled(False) - self.ctl_unsuspend.setEnabled(False) - - def set_clean_config(self): - self.ctl_suspend.setEnabled(True) - self.ctl_unsuspend.setEnabled(True) - - def toggle_stats(self): - config = mw.col.conf['catchup'] - config['show_stats'] = self.ctl_show_stats.isChecked() - mw.col.setMod() - mw.reset() - self.update_stats() - - def update_stats(self): - config = mw.col.conf['catchup'] - if config['show_stats']: - days = set() - - all_cids = self.get_tagged_cids() - new_behind = 0 - - for cid in all_cids: - card = mw.col.getCard(cid) - if card.type == 2: # review - days.add(card.due) - else: - new_behind += 1 - total_behind = len(all_cids) - review_behind = total_behind - new_behind - self.days_behind_label.setText("Days behind") - self.days_behind.setText("%d" % len(days)) - self.cards_behind_label.setText("Cards behind") - self.cards_behind.setText("New/Learn: %d\t In review: %d\t Total %d" % - (new_behind, review_behind, total_behind) - ) - else: - self.days_behind_label.setText("Intentionally left blank") - self.days_behind.setText("") - self.cards_behind_label.setText("") - self.cards_behind.setText("") - - def __init__(self): - QDialog.__init__(self, parent=mw) - self.load_conf() - self.grid = QGridLayout() - self.grid.setSpacing(10) - self._num_lines = 0 - - self.add_label("Settings") - self.ctl_query = self.add_labeled_widget("Suspend filter", QLineEdit()) - self.ctl_tag = self.add_labeled_widget("Catchup tag", QLineEdit()) - self.add_empty() - self.ctl_unsuspend_query = self.add_labeled_widget("Unsuspend filter", QLineEdit()) - self.ctl_numdays = self.add_labeled_widget("Number of days to unsuspend", QSpinBox(), small=True) - - self.ctl_save = self.add_widget(QPushButton("Save settings"), small=True) - self.ctl_cancel = self.add_widget(QPushButton("Undo"), small=True) - - self.ctl_save.clicked.connect(self.on_accept) - self.ctl_cancel.clicked.connect(self.on_reject) - - self.add_rule() - self.stats_label = self.add_label("Stats") - self.ctl_show_stats = self.add_labeled_widget('Show stats', QCheckBox()) - - self.days_behind_label = QLabel("Days behind") - self.cards_behind_label = QLabel("Cards behind") - - self.days_behind = self.add_labeled_widget(self.days_behind_label, QLabel("")) - self.cards_behind = self.add_labeled_widget(self.cards_behind_label, QLabel("")) - self.update_stats() - - self.add_rule() - - self.add_label("Actions") - self.ctl_suspend = self.add_widget(QPushButton("Suspend overdue cards"), small=True) - self.ctl_unsuspend = self.add_widget(QPushButton("Unsuspend earliest cards"), small=True) - self.ctl_unsuspend_new = self.add_widget(QPushButton("Unsuspend new/learn cards"), small=True) - - self.ctl_suspend.clicked.connect(self.do_suspend) - self.ctl_unsuspend.clicked.connect(self.do_unsuspend) - self.ctl_unsuspend_new.clicked.connect(self.do_unsuspend_new) - - self.add_rule() - self.add_widget(QLabel('Check the README for instructions and examples')) - self.ctl_close = self.add_widget(QPushButton("Close"), small=True) - self.ctl_close.clicked.connect(self.close) - self.ctl_close.setDefault(True) - - - layout_main = QVBoxLayout() - layout_main.addLayout(self.grid) - - self.display_conf() - - self.ctl_query.textEdited.connect(self.set_dirty_config) - self.ctl_unsuspend_query.textEdited.connect(self.set_dirty_config) - self.ctl_tag.textEdited.connect(self.set_dirty_config) - self.ctl_numdays.valueChanged.connect(self.set_dirty_config) - - self.ctl_show_stats.stateChanged.connect(self.toggle_stats) - - self.setLayout(layout_main) - self.setMinimumWidth(512) - self.setWindowTitle('CatchUp') - -def on_catchup_settings(): - dialog = CatchupSettings() - dialog.exec_() - -def main(): - action = QAction("&CatchUp", mw) - action.triggered.connect(on_catchup_settings) - mw.form.menuTools.addAction(action)