def file_save(self): stopwatch.pause() path = config()["path"] try: database().save(path) except MnemosyneError, e: self.widget.error_box(e)
def do_input(line): """ Input mode """ print("=== Input mode ===") card = ui_controller_main() card_type_by_id = dict([(card_type.id, card_type) \ for card_type in card_types()]) category_names_by_id = dict([(i, name) for (i, name) in \ enumerate(database().category_names())]) while True: # Select Card Type by user: print "Select Card Type:" print '\n'.join(["%s %s" % \ (type_id, card_type_by_id[type_id].name)\ for type_id in sorted(card_type_by_id.keys())]) while True: inp = \ raw_input(_("Enter number of Card Type or 'q' to quit ... ")) if inp in ("q", "Q"): return if inp in card_type_by_id: card_type = card_type_by_id[inp] break print(_("Input error, try again")) # Select the exist or Add the new Category print '\n'.join(["%s %s" % (cat_id, category_names_by_id[cat_id]) \ for cat_id in sorted(category_names_by_id.keys())]) inp = raw_input(_("Enter number of Category or "\ "enter new category or 'q' to quit ... ")) if inp in ("q", "Q"): return category_name = inp if inp in category_names_by_id: category_name = category_names_by_id[inp] # Enter all fields for the current type fact = {} for key, name in card_type.fields: print _("Enter field"), name inp = raw_input() if inp: fact[key] = inp # Check necesary fields and create new card for required in card_type.required_fields(): if required not in fact: print(_("Error.This card is not saved in a database !!!")) print(_("You didn't enter all necesary field(s) !!!")) break else: # Create new card card.create_new_cards(fact, card_type, 0, [category_name]) database().save(config()['path']) if raw_input(_("Do you want to add a new record? y/n ")) != "y": break
def closeEvent(self, event): try: config().save() database().backup() database().unload() except MnemosyneError, e: self.error_box(e) event.ignore()
def file_open(self): stopwatch.pause() old_path = expand_path(config()["path"]) out = self.widget.open_file_dialog(path=old_path, filter=_("Mnemosyne databases (*.mem)")) if not out: stopwatch.unpause() return try: database().unload() except MnemosyneError, e: self.widget.error_box(e) stopwatch.unpause() return
def file_save_as(self): stopwatch.pause() old_path = expand_path(config()["path"]) out = self.widget.save_file_dialog(path=old_path, filter=_("Mnemosyne databases (*.mem)")) if not out: stopwatch.unpause() return if not out.endswith(".mem"): out += ".mem" try: database().save(out) except MnemosyneError, e: self.widget.error_box(e) stopwatch.unpause() return
def reset_learning_data(self): """Used when creating a card for the first time, or when choosing 'reset learning data' on import. Last_rep and next_rep are measured in days since the creation of the database. Note that these values should be stored as float in SQL to accomodate plugins doing minute-level scheduling. Note: self.unseen is needed on top of self.acq_reps, because the initial grading of a manually added card is counted as the first repetition. An imported card has no such initial grading, and therefore we do the initial grading the first time we see it during the interactive learning process. Because of this, determining if a card has been seen during the interactive learning process can not be decided on the basis of acq_reps, but still that information is needed when randomly selecting unseen cards to learn. """ self.grade = 0 self.easiness = database().average_easiness() self.acq_reps = 0 self.ret_reps = 0 self.lapses = 0 self.acq_reps_since_lapse = 0 self.ret_reps_since_lapse = 0 self.last_rep = 0 self.next_rep = 0 self.unseen = True
def __init__(self, datadir=None, category=None, records=None): if datadir: datadir = os.path.abspath(datadir) elif os.path.exists(os.path.join(os.getcwdu(), ".mnemosyne")): datadir = os.path.abspath(os.path.join(os.getcwdu(), ".mnemosyne")) else: datadir = os.path.abspath(os.path.join(os.path.expanduser("~"), ".mnemosyne")) print 'datadir=', datadir libmnemosyne.initialise(datadir) self.card_type = FrontToBack() self.database = database() self.saved = False if records: self.records = records else: self.records = -1 if not category: category = "English-Russian" self.category = category #Category(category) self.controller = ui_controller_main()
def new_cards(self, grade): """Note that we don't rebuild revision queue afterwards, as this can cause corruption for the current card. The new cards will show up after the old queue is empty.""" try: fact_data = self.card_widget.get_data() except ValueError: return # Let the user try again to fill out the missing data. cat_names = [c.strip() for c in \ unicode(self.categories.currentText()).split(',')] card_type_name = unicode(self.card_types.currentText()) card_type = self.card_type_by_name[card_type_name] c = ui_controller_main() c.create_new_cards(fact_data, card_type, grade, cat_names) self.update_combobox(', '.join(cat_names)) database().save(config()['path']) self.card_widget.clear()
def update_status_bar(self, message=None): db = database() self.sched.setText(_("Scheduled: ") + \ str(db.scheduled_count()) + " ") self.notmem.setText(_("Not memorised: ") + \ str(db.non_memorised_count()) + " ") self.all.setText(_("All: ") \ + str(db.active_count()) + " ") if message: self.statusBar().showMessage(message)
def new_question(self, learn_ahead=False): if database().card_count() == 0: self.state = "EMPTY" self.card = None else: self.card = scheduler().get_new_question(learn_ahead) if self.card != None: self.state = "SELECT SHOW" else: self.state = "SELECT AHEAD" self.update_dialog() stopwatch.start()
def update_combobox(self, current_cat_name): no_of_categories = self.categories.count() for i in range(no_of_categories-1,-1,-1): self.categories.removeItem(i) self.categories.addItem(_("<default>")) for name in database().category_names(): if name != _("<default>"): self.categories.addItem(name) if ',' in current_cat_name: self.categories.addItem(current_cat_name) for i in range(self.categories.count()): if self.categories.itemText(i) == current_cat_name: self.categories.setCurrentIndex(i) break
def rebuild_queue(self, learn_ahead=False): self.queue = [] datab = database() if not datab.is_loaded(): return queue = CardQueue(limit=20) if learn_ahead: queue.mkgroup("learn ahead", limit=5) queue.add(datab.cards_learn_ahead(sort_key="next_rep")) else: queue.mkgroup("scheduled", limit=10, allow_dup=None) # limit amount of grade0 cards for the current group queue.setlimit(limit=config()["grade_0_items_at_once"], group="scheduled", grade=0) # add due_for_ret_rep generator queue.add(datab.cards_due_for_ret_rep(sort_key="interval")) # add final review generator, grade0 queue.add(datab.cards_due_for_final_review(grade=0)) # add final review generator, grade1 queue.add(datab.cards_due_for_final_review(grade=1)) # add new_memorizing generator, grade0 new_mem = datab.cards_new_memorising(grade=0) queue.add(new_mem) # FIXME: HOW? # queue.add(iter(2*grade_0_selected)) # queue.add(new_mem) # queue.add(new_mem) # The same for grade1 new_mem = datab.cards_new_memorising(grade=1) queue.add(new_mem) # add cards_unseen generator queue.add(datab.cards_unseen(randomise=config()["randomise_new_cards"])) # FIXME: HOW??? # grade_0_in_queue = sum(1 for i in self.queue if i.grade == 0)/2 self.queue = [card for card in queue()]
def new_question(self, learn_ahead=False): """ Print new question """ if database().card_count() == 0: raise CmdUiControllerException(_("Database is empty")) else: self.card = scheduler().get_new_question(learn_ahead) if self.card: print _("Question:"), self.card.question() else: value = raw_input(_("Learn ahead of schedule" + "? (y/N)")) if value in ("y", "Y"): print("\n") self.new_question(True) else: raise CmdUiControllerException(_("Finished"))
def file_new(self): stopwatch.pause() out = self.widget.save_file_dialog(path=config().basedir, filter=_("Mnemosyne databases (*.mem)"), caption=_("New")) if not out: stopwatch.unpause() return if not out.endswith(".mem"): out += ".mem" db = database() db.unload() db.new(out) db.load(config()["path"]) ui_controller_review().clear() ui_controller_review().update_dialog() stopwatch.unpause()
def update_related_cards(self, fact, new_fact_data, new_card_type, \ new_cat_names): # Allow this function to be overridden by a function hook. f = component_manager.get_current("function_hook", "update_related_cards") if f: return f.run() # Partial implementation. Still to do: change card type, duplicate checking. db = database() new_cards, updated_cards = fact.card_type.update_related_cards(\ new_fact_data, new_cat_names) db.update_fact(fact) for card in new_cards: db.add_card(card) for card in updated_cards: db.update_card(card) return if db.has_fact_with_data(fact_data): self.widget.information_box(\ _("Card is already in database.\nDuplicate not added."), _("OK")) return fact = Fact(fact_data, card_type) duplicates = db.duplicates_for_fact(fact) if len(duplicates) != 0: answer = self.widget.question_box(\ _("There is already data present for:\n\n") + "".join(fact[k] for k in card_type.required_fields()), _("&Merge and edit"), _("&Add as is"), _("&Do not add")) if answer == 0: # Merge and edit. merged_fact_data = copy.copy(fact.data) for duplicate in duplicates: for key in fact_data: if key not in card_type.required_fields(): merged_fact_data[key] += "/" + duplicate[key] db.delete_fact_and_related_data(duplicate) print merged_fact_data # TODO: edit merged data. #dlg = EditItemDlg(new_item, self) #dlg.exec_loop() #get fact from that if answer == 2: # Don't add. return db.add_fact(fact) for card in card_type.create_related_cards(fact, grade): db.add_card(card)
def new_question(self, learn_ahead=False): """ Create new question """ if not database().card_count(): raise HildonUiControllerException(self.w_tree, \ _("Database is empty")) card = scheduler().get_new_question(learn_ahead) if card: self.question.set_text(card.question()) else: # FIXME value = raw_input(_("Learn ahead of schedule" + "? (y/N)")) self.new_question(True) #self.question.set_text(card.question()) self.answer.set_text("") for widget in [getattr(self, "grade%i" % num) for num in range(6)]: widget.set_sensitive(False) self.get_answer.set_sensitive(True) self.card = card
def main(argv): """ Main """ opts, argv = parse_commandline(argv) # FIXME: move this to config module if opts.datadir: basedir = os.path.abspath(opts.datadir) elif os.path.exists(os.path.join(os.getcwdu(), ".pomni")): basedir = os.path.abspath(os.path.join(os.getcwdu(), ".pomni")) else: basedir = os.path.join(os.environ['HOME'], ".pomni") initialise(basedir) cdatabase = database() db_name = os.path.join(basedir, config()['path']) if os.path.exists(db_name): cdatabase.load(db_name) return ui_factory(opts.ui).start(opts.mode)
def create_new_cards(self, fact_data, card_type, grade, cat_names): """Create a new set of related cards.""" # Allow this function to be overridden by a function hook. f = component_manager.get_current("function_hook", "create_new_cards") if f: return f.run() db = database() if db.has_fact_with_data(fact_data): self.widget.information_box(\ _("Card is already in database.\nDuplicate not added."), _("OK")) return fact = Fact(fact_data, card_type) for cat_name in cat_names: fact.cat.append(db.get_or_create_category_with_name(cat_name)) duplicates = db.duplicates_for_fact(fact) if len(duplicates) != 0: answer = self.widget.question_box(\ _("There is already data present for:\n\n") + "".join(fact[k] for k in card_type.required_fields()), _("&Merge and edit"), _("&Add as is"), _("&Do not add")) if answer == 0: # Merge and edit. merged_fact_data = copy.copy(fact.data) for duplicate in duplicates: for key in fact_data: if key not in card_type.required_fields(): merged_fact_data[key] += "/" + duplicate[key] db.delete_fact_and_related_data(duplicate) print merged_fact_data # TODO: edit merged data. #dlg = EditItemDlg(new_item, self) #dlg.exec_loop() #get fact from that if answer == 2: # Don't add. return db.add_fact(fact) for card in card_type.create_related_cards(fact, grade): db.add_card(card)
def set_initial_grade(self, grade): """This is called after manually adding cards. This code is separated out from the constructor, as for imported for imported cards, there is no grading information available when there are created, and the initial grading is done the first time they are are seen in the interactive review process (by similar code in the scheduler). This initial grading is seen as the first repetition. """ db = database() sch = scheduler() self.grade = grade self.easiness = db.average_easiness() self.acq_reps = 1 self.acq_reps_since_lapse = 1 self.last_rep = db.days_since_start() new_interval = sch.calculate_initial_interval(grade) new_interval += sch.calculate_interval_noise(new_interval) self.next_rep = db.days_since_start() + new_interval
def initialise_new_empty_database(): from mnemosyne.libmnemosyne.component_manager import database filename = config()["path"] if not os.path.exists(os.path.join(config().basedir, filename)): database().new(os.path.join(config().basedir, filename))
def update_dialog(self, redraw_all=False): w = self.widget # Update title. database_name = os.path.basename(config()["path"])[:-4] title = _("Mnemosyne") + " - " + database_name w.set_window_title(title) # Update menu bar. if config()["only_editable_when_answer_shown"] == True: if self.card != None and self.state == "SELECT GRADE": w.enable_edit_current_card(True) else: w.enable_edit_current_card(False) else: if self.card != None: w.enable_edit_current_card(True) else: w.enable_edit_current_card(False) w.enable_delete_current_card(self.card != None) w.enable_edit_deck(database().card_count() > 0) # Hide/show the question and answer boxes. if self.state == "SELECT SHOW": w.question_box_visible(True) if self.card.fact_view.a_on_top_of_q: w.answer_box_visible(False) elif self.state == "SELECT GRADE": w.answer_box_visible(True) if self.card.fact_view.a_on_top_of_q: w.question_box_visible(False) else: w.question_box_visible(True) w.answer_box_visible(True) # Update question label. question_label_text = _("Question:") if self.card != None and self.card.fact.cat[0].name != _("<default>"): for c in self.card.fact.cat: question_label_text += " " + c.name w.set_question_label(question_label_text) # Update question content. if self.card == None: w.clear_question() elif self.state == "SELECT SHOW" or redraw_all == True: w.set_question(self.card.question()) # Update answer content. if self.card == None or self.state == "SELECT SHOW": w.clear_answer() else: w.set_answer(self.card.answer()) # Update 'Show answer' button. if self.state == "EMPTY": show_enabled, default, text = False, True, _("Show answer") grades_enabled = False elif self.state == "SELECT SHOW": show_enabled, default, text = True, True, _("Show answer") grades_enabled = False elif self.state == "SELECT GRADE": show_enabled, default, text = False, True, _("Show answer") grades_enabled = True elif self.state == "SELECT AHEAD": show_enabled, default, text = True, False, \ _("Learn ahead of schedule") grades_enabled = False w.update_show_button(text, default, show_enabled) # Update grade buttons. if self.card != None and self.card.grade in [0,1]: i = 0 # Acquisition phase. default_4 = False else: i = 1 # Retention phase. default_4 = True w.enable_grades(grades_enabled) if grades_enabled: w.grade_4_default(default_4) # Tooltips and texts for the grade buttons. for grade in range(0,6): # Tooltip. if self.state == "SELECT GRADE" and \ config()["show_intervals"] == "tooltips": w.set_grade_tooltip(grade, tooltip[i][grade] +\ self.next_rep_string(scheduler().process_answer(self.card, \ grade, dry_run=True))) else: w.set_grade_tooltip(grade, tooltip[i][grade]) # Button text. if self.state == "SELECT GRADE" and \ config()["show_intervals"] == "buttons": w.set_grade_text(grade, str(scheduler().process_answer(\ self.card, grade, dry_run=True))) w.set_grades_title(_("Pick days until next repetition:")) else: w.set_grade_text(grade, str(grade)) w.set_grades_title(_("Grade your answer:")) # TODO: accelerator update needed? #self.grade_buttons[grade].setAccel(QKeySequence(str(grade))) # Update status bar. ui_controller_main().widget.update_status_bar() # Run possible update code that independent of the controller state. # TODO: remove when migration is complete. w.update_dialog()
def process_answer(self, card, new_grade, dry_run=False): db = database() days_since_start = db.days_since_start() # When doing a dry run, make a copy to operate on. Note that this # leaves the original in cards and the reference in the GUI intact. if dry_run: card = copy.copy(card) # Calculate scheduled and actual interval, taking care of corner # case when learning ahead on the same day. scheduled_interval = card.next_rep - card.last_rep actual_interval = days_since_start - card.last_rep if actual_interval == 0: actual_interval = 1 # Otherwise new interval can become zero. if (card.acq_reps == 0) and (card.ret_reps == 0): # The card has not yet been given its initial grade, because it # was imported. card.easiness = db.average_easiness() card.acq_reps = 1 card.acq_reps_since_lapse = 1 new_interval = calculate_initial_interval(new_grade) # Make sure the second copy of a grade 0 card doesn't show # up again. if not dry_run and card.grade == 0 and new_grade in [2,3,4,5]: for i in self.queue: if i.id == card.id: self.queue.remove(i) break elif card.grade in [0,1] and new_grade in [0,1]: # In the acquisition phase and staying there. card.acq_reps += 1 card.acq_reps_since_lapse += 1 new_interval = 0 elif card.grade in [0,1] and new_grade in [2,3,4,5]: # In the acquisition phase and moving to the retention phase. card.acq_reps += 1 card.acq_reps_since_lapse += 1 new_interval = 1 # Make sure the second copy of a grade 0 card doesn't show # up again. if not dry_run and card.grade == 0: for i in self.queue: if i.id == card.id: self.queue.remove(i) break elif card.grade in [2,3,4,5] and new_grade in [0,1]: # In the retention phase and dropping back to the # acquisition phase. card.ret_reps += 1 card.lapses += 1 card.acq_reps_since_lapse = 0 card.ret_reps_since_lapse = 0 new_interval = 0 elif card.grade in [2,3,4,5] and new_grade in [2,3,4,5]: # In the retention phase and staying there. card.ret_reps += 1 card.ret_reps_since_lapse += 1 if actual_interval >= scheduled_interval: if new_grade == 2: card.easiness -= 0.16 if new_grade == 3: card.easiness -= 0.14 if new_grade == 5: card.easiness += 0.10 if card.easiness < 1.3: card.easiness = 1.3 new_interval = 0 if card.ret_reps_since_lapse == 1: new_interval = 6 else: if new_grade == 2 or new_grade == 3: if actual_interval <= scheduled_interval: new_interval = actual_interval * card.easiness else: new_interval = scheduled_interval if new_grade == 4: new_interval = actual_interval * card.easiness if new_grade == 5: if actual_interval < scheduled_interval: new_interval = scheduled_interval # Avoid spacing. else: new_interval = actual_interval * card.easiness # Shouldn't happen, but build in a safeguard. if new_interval == 0: print "Internal error: new interval was zero." new_interval = scheduled_interval new_interval = int(new_interval) # When doing a dry run, stop here and return the scheduled interval. if dry_run: return new_interval # Add some randomness to interval. noise = self.calculate_interval_noise(new_interval) # Update grade and interval. card.grade = new_grade card.last_rep = days_since_start card.next_rep = days_since_start + new_interval + noise card.unseen = False # Don't schedule related cards on the same day. for c in db.cards_from_fact(card.fact): if c != card and c.next_rep == card.next_rep and card.grade >= 2: card.next_rep += 1 noise += 1 db.update_card(card) # Create log entry. log().revision(card, scheduled_interval, actual_interval, new_interval, noise) return new_interval + noise
def new_card(self, card): new_interval = database().days_since_start() - card.next_rep self.logger.info("New item %s %d %d", card.id, card.grade, new_interval)
def rebuild_queue(self, learn_ahead=False): self.queue = [] db = database() if not db.is_loaded(): return # Do the cards that are scheduled for today (or are overdue), but # first do those that have the shortest interval, as being a day # late on an interval of 2 could be much worse than being a day late # on an interval of 50. self.queue = list(db.cards_due_for_ret_rep(sort_key="interval")) if len(self.queue) != 0: return # Now rememorise the cards that we got wrong during the last stage. # Concentrate on only a limited number of grade 0 cards, in order to # avoid too long intervals between revisions. If there are too few # cards in left in the queue, append more new cards to keep some # spread between these last cards. Also limit the queue size to 10 # in order not to have too large a spread. limit = config()["grade_0_items_at_once"] grade_0 = db.cards_due_for_final_review(grade=0) grade_0_selected = [] if limit != 0: for i in grade_0: for j in grade_0_selected: if i.fact == j.fact: break else: grade_0_selected.append(i) if len(grade_0_selected) == limit: break grade_1 = list(db.cards_due_for_final_review(grade=1)) self.queue += 2*grade_0_selected + grade_1 random.shuffle(self.queue) if len(grade_0_selected) == limit or len(self.queue) >= 10: return # Now do the cards which have never been committed to long-term # memory, but which we have seen before. Also limit the queue size # to 10 in order not to have too large a spread. grade_0 = db.cards_new_memorising(grade=0) grade_0_in_queue = len(grade_0_selected) grade_0_selected = [] if limit != 0: for i in grade_0: for j in grade_0_selected: if i.fact == j.fact: break else: grade_0_selected.append(i) if len(grade_0_selected) + grade_0_in_queue == limit: break grade_1 = list(db.cards_new_memorising(grade=1)) self.queue += 2*grade_0_selected + grade_1 random.shuffle(self.queue) if len(grade_0_selected) + grade_0_in_queue == limit or \ len(self.queue) >= 10: return # Now add some new cards. This is a bit inefficient at the moment as # 'unseen' is wastefully created as opposed to being a generator # expression. However, in order to use random.choice, there doesn't # seem to be another option. unseen = list(db.cards_unseen()) grade_0_in_queue = sum(1 for i in self.queue if i.grade == 0)/2 grade_0_selected = [] if limit != 0 and len(unseen) != 0: while True: if config()["randomise_new_cards"] == False: new_card = unseen[0] else: new_card = random.choice(unseen) unseen.remove(new_card) for i in grade_0_selected: if new_card.fact == i.fact: break else: grade_0_selected.append(new_card) if len(unseen) == 0 or \ len(grade_0_selected) + grade_0_in_queue == limit: self.queue += grade_0_selected return # If we get to here, there are no more scheduled cards or new cards # to learn. The user can signal that he wants to learn ahead by # calling rebuild_revision_queue with 'learn_ahead' set to True. # Don't shuffle this queue, as it's more useful to review the # earliest scheduled cards first. We only put 5 cards at the same # time into the queue, in order to save memory. # TODO: this requires the user to click 'learn ahead of schedule' # again after 5 cards. If it's possible to make this algorithm # stateless and return 1 card at the time, this will be solved # automatically. if learn_ahead == False: return else: for card in db.cards_learn_ahead(sort_key="next_rep"): self.queue.append(card) if len(self.queue) >= 5: return
def saved_database(self): self.logger.info("Saved database %d %d %d", \ database().scheduled_count(), \ database().non_memorised_count(), \ database().card_count())