Esempio n. 1
0
    def deactivate(self):
        db = self.database()
        # Check if we are allowed to deactivate a card type.
        can_deactivate = True
        if db and db.is_loaded():
            for component in self.instantiated_components:
                if component.component_type == "card_type":
                    if self.database().has_clones(component):
                        can_deactivate = False
                    for card_type in self.database().card_types_in_use():
                        if issubclass(card_type.__class__,
                                      component.__class__):
                            can_deactivate = False
                            break
                    if can_deactivate == False:
                        self.main_widget().show_error(_("Cannot deactivate") \
                            + " '" + component.name + "'. " + \
_("There are cards with this card type (or a clone of it) in the database."))
                        return False
                    for criterion in db.criteria():
                        criterion.card_type_deleted(component)
                        db.update_criterion(criterion)
        # Deactivate and unregister components.
        for component in self.instantiated_components:
            component.deactivate()
            self.component_manager.unregister(component)
        for component in self.registered_components:
            self.component_manager.unregister(component)
        # If we are shutting down and the database is gone, don't worry about
        # side effects.
        if not self.database():
            return True
        # Make necessary side effects happen.
        if self.review_reset_needed:
            self.controller().reset_study_mode()
            self.log().started_scheduler()
        # Use names instead of instances here in order to survive pickling.
        if self.__class__.__name__ in self.config()["active_plugins"]:
            self.config()["active_plugins"].remove(self.__class__.__name__)
        self.instantiated_components = []
        self.registered_components = []
        return True
 def import_logs(self, filename):
     w = self.main_widget()
     db = self.database()
     w.set_progress_text(_("Importing history..."))
     log_dir = os.path.join(os.path.dirname(filename), "history")
     if not os.path.exists(log_dir):
         w.close_progress()
         w.show_information(_("No history found to import."))
         return
     # The events that we import from the science logs obviously should not
     # be reexported to these logs (this is true for both the archived logs
     # and log.txt). So, before the import, we flush the SQL logs to the
     # science logs, and after the import we edit the partership index to
     # skip these entries.
     db.dump_to_science_log()
     # Manage database indexes.
     db.before_1x_log_import()
     filenames = [os.path.join(log_dir, logname) for logname in \
         sorted(os.listdir(log_dir)) if logname.endswith(".bz2")]
     # log.txt can also contain data we need to import, especially on the
     # initial upgrade from 1.x. 'ids_to_parse' will make sure we only pick
     # up the relevant events. (If we do the importing after having used
     # 2.x for a while, there could be duplicate load events, etc, but these
     # don't matter.)
     filenames.append(os.path.join(os.path.dirname(filename), "log.txt"))
     w.set_progress_range(len(filenames))
     ignored_files = []
     parser = ScienceLogParser(self.database(),
                               ids_to_parse=self.items_by_id,
                               machine_id=self.config().machine_id())
     for filename in filenames:
         try:
             parser.parse(filename)
         except:
             ignored_files.append(filename)
         w.increase_progress(1)
     if ignored_files:
         w.show_information(_("Ignoring unparsable files:<br/>") +\
             '<br/>'.join(ignored_files))
     # Manage database indexes.
     db.after_1x_log_import()
     db.skip_science_log()
Esempio n. 3
0
 def accept(self):
     filename = self.filename_box.text()
     if not filename:
         return QtWidgets.QDialog.accept(self)
     if not filename.endswith(self.format().extension):
         filename += self.format().extension
     self.config()["export_format"] = str(type(self.format()))
     result = self.format().do_export(filename)
     if result != -1:  # Cancelled.
         self.main_widget().show_information(_("Done!"))
     QtWidgets.QDialog.accept(self)
Esempio n. 4
0
 def update_title(self):
     title = _("Mnemosyne")
     db = self.database()
     database_name = db.display_name()
     if database_name and database_name != db.default_name:
         title += " - " + database_name
     if db.current_criterion() and \
         db.current_criterion().name and \
         db.current_criterion().name != db.default_criterion_name:
         title += " - " + db.current_criterion().name
     self.main_widget().set_window_title(title)
Esempio n. 5
0
 def data(self, index, role):
     if role == QtCore.Qt.DisplayRole:
         row, column = index.row(), index.column()
         card_type = self.card_types[row]
         if column == 0:
             if not card_type.id.startswith("7") and ":" in card_type.id:
                 return "%s (%s)" % (_(card_type.name),
                         _(card_type.__class__.__bases__[0].name))
             else:
                 return card_type.name
         elif column == 1:
             language_id = self.config().card_type_property(\
                 "language_id", card_type, default="")
             return "" if not language_id else \
                    self.language_with_id(language_id).name
         elif column == 2:
             key = self.config().card_type_property(\
                 "foreign_fact_key", card_type, default="")
             return "" if not key else \
                    card_type.name_for_fact_key(key)
 def accept(self):
     self.correspondence.clear()
     for old_fact_key, old_fact_key_name in \
         self.old_card_type.fact_keys_and_names:
         new_fact_key_name = \
             self.comboboxes[old_fact_key].currentText()
         if new_fact_key_name != _("<none>"):
             new_fact_key = \
                  self.new_card_type.fact_key_with_name(new_fact_key_name)
             self.correspondence[old_fact_key] = new_fact_key
     QtWidgets.QDialog.accept(self)
class ScheduledForgottenNew(StudyMode):

    id = "ScheduledForgottenNew"
    name = _("Scheduled -> forgotten -> new")
    menu_weight = 1
    Scheduler = SM2Mnemosyne
    ReviewController = SM2Controller

    def activate(self):
        self.activate_components()
        self.review_controller().reset(new_only=False)
Esempio n. 8
0
 def activate(self):
     if not hasattr(self, "supported_API_level") or \
         self.supported_API_level < self.current_API_level:
         self.main_widget().show_error(_("Plugin '" + self.name +\
             "' needs to be upgraded. Please contact its author."))
         return
     # Don't activate a plugin twice.
     if self.instantiated_components or self.registered_components:
         return
     # See if we need to reset the review process.
     self.review_reset_needed = False
     for component in self.components:
         if component.component_type in \
             ["scheduler", "review_controller", "review_widget"]:
             self.review_reset_needed = True
     # Register all our regular components. Instantiate them if needed.
     for component in self.components:
         if component.instantiate == Component.IMMEDIATELY:
             component = component(component_manager=self.component_manager)
             self.component_manager.register(component)
             component.activate()
             self.instantiated_components.append(component)
         else:
             self.component_manager.register(component)
             self.registered_components.append(component)
     # Register gui components for certain classes.
     for key, value in self.gui_for_component.items():
         component_name = key
         assert type(component_name) == str
         for gui_module_name, gui_class_name in value:
             gui_class = getattr(\
                 importlib.import_module(gui_module_name), gui_class_name)
             self.component_manager.add_gui_to_component(\
                 component_name, gui_class)
     # Make necessary side effects happen.
     for component in self.components:
         if component.used_for == "configuration_defaults":
             component(self.component_manager).run()
     db = self.database()
     if db and db.is_loaded():
         for component in self.instantiated_components:
             if component.component_type == "card_type":
                 for criterion in db.criteria():
                     criterion.active_card_type_added(component)
                     db.update_criterion(criterion)
     if self.database().is_loaded() and self.review_reset_needed:
         self.controller().reset_study_mode()
         # We need to log 'started_scheduler' events manually and not
         # from e.g. the 'activate' function of the scheduler because
         # during startup, the database is not yet loaded when the
         # scheduler gets activated.
         self.log().started_scheduler()
     # Use names instead of instances here in order to survive pickling.
     self.config()["active_plugins"].add(self.__class__.__name__)
Esempio n. 9
0
 def run(self):
     try:
         translation = self.translator.translate(\
             self.card_type, self.foreign_text, self.target_language_id)
         if translation:
             self.finished_signal.emit(translation)
         else:
             self.error_signal.emit(
                 _("Could not contact Google servers..."))
     except Exception as e:
         self.error_signal.emit(str(e) + "\n" + traceback_string())
Esempio n. 10
0
class CramRecent(StudyMode):

    id = "CramRecent"
    name = _("Cram recently learned new cards")
    menu_weight = 4
    Scheduler = Cramming
    ReviewController = SM2ControllerCramming

    def activate(self):
        self.activate_components()
        self.review_controller().reset(new_only=True)
Esempio n. 11
0
 def delete_card_type(self, card_type):
     if not self.database().is_user_card_type(card_type) or \
         self.database().is_in_use(card_type):
         self.main_widget().show_error(\
 _("Card type %s is in use or is a system card type, cannot delete it.") \
 % (card_type.name, ))
         return
     if self.database().has_clones(card_type):
         self.main_widget().show_error(\
 _("Card type %s has clones, cannot delete it.") \
 % (card_type.name, ))
         return
     fact_views = card_type.fact_views
     self.config().delete_card_type_properties(card_type)
     self.database().delete_card_type(card_type)
     # Correct ordering for the sync protocol is deleting the fact views
     # last.
     for fact_view in fact_views:
         self.database().delete_fact_view(fact_view)
     self.database().save()
Esempio n. 12
0
class NewOnly(StudyMode):

    id = "NewOnly"
    name = _("Study new unlearned cards only")
    menu_weight = 2
    Scheduler = SM2Mnemosyne
    ReviewController = SM2Controller

    def activate(self):
        self.activate_components()
        self.review_controller().reset(new_only=True)
Esempio n. 13
0
class CramAll(StudyMode):

    id = "CramAll"
    name = _("Cram all cards")
    menu_weight = 3
    Scheduler = Cramming
    ReviewController = SM2ControllerCramming

    def activate(self):
        self.activate_components()
        self.review_controller().reset(new_only=False)
Esempio n. 14
0
class DecoratedVocabulary(Vocabulary):

    id = "3_decorated"
    name = _("Vocabulary (decorated)")

    # The keys we inherit from Vocabulary, we just override the FactViews.

    # Recognition.
    v1 = FactView(_("Recognition"), "3::1")
    v1.q_fact_keys = ["f"]
    v1.a_fact_keys = ["p_1", "m_1", "n"]
    v1.q_fact_key_decorators = {"f": "What is the translation of ${f}?"}

    # Production.
    v2 = FactView(_("Production"), "3::2")
    v2.q_fact_keys = ["m_1"]
    v2.a_fact_keys = ["f", "p_1", "n"]
    v2.q_fact_key_decorators = {"m_1": "How do you say ${m_1}?"}

    fact_views = [v1, v2]
Esempio n. 15
0
    def find_duplicates(self):
        self.stopwatch().pause()
        self.flush_sync_server()
        if self.config()["find_duplicates_help_shown"] == False:
            self.main_widget().show_information(\
_("This will tag all the cards in a given card type which have the same question. That way you can reformulate them to make the answer unambiguous. Note that this will not tag duplicates in different card types, e.g. card types for 'French' and 'Spanish'."))
            self.config()["find_duplicates_help_shown"] = True
        self.database().tag_all_duplicates()
        review_controller = self.review_controller()
        review_controller.reset_but_try_to_keep_current_card()
        review_controller.update_status_bar_counters()
        self.stopwatch().unpause()
Esempio n. 16
0
 def show_statistics(self, variant):
     self.activate()
     if not self.page.y:
         self.display_message(_("No stats available."))
         return
     self.axes.bar(self.page.x,
                   self.page.y,
                   width=0.7,
                   align="center",
                   color="blue",
                   alpha=0.75)
     self.axes.set_title(_("Number of cards"))
     self.axes.set_xlabel(_("Grades"))
     self.axes.set_xticks(self.page.x)
     self.axes.set_xticklabels([_("Not seen")] + list(range(0, 6)))
     xmin, xmax = min(self.page.x), max(self.page.x)
     self.axes.set_xlim(xmin=xmin - 0.5, xmax=xmax + 0.5)
     self.axes.set_ylim(ymax=int(max(self.page.y) * 1.1) + 1)
     from matplotlib.ticker import FuncFormatter
     self.axes.yaxis.set_major_formatter(FuncFormatter(self.integers_only))
     self._add_numbers_to_bars()
Esempio n. 17
0
 def __init__(self, statistics_page, page_index, **kwds):
     super().__init__(**kwds)
     self.statistics_page = statistics_page
     self.page_index = page_index
     self.vbox_layout = QtWidgets.QVBoxLayout(self)
     self.combobox = QtWidgets.QComboBox(self)
     self.variant_ids = []
     self.variant_widgets = []
     self.current_variant_widget = None
     variants = statistics_page.variants
     if not variants:
         variants = [(0, _("Default"))]
     for variant_id, variant_name in variants:
         self.variant_ids.append(variant_id)
         self.variant_widgets.append(None)
         self.combobox.addItem(_(variant_name))
     if len(self.variant_ids) <= 1 or \
        self.statistics_page.show_variants_in_combobox == False:
         self.combobox.hide()
     self.vbox_layout.addWidget(self.combobox)
     self.combobox.currentIndexChanged.connect(self.display_variant)
Esempio n. 18
0
    def process_latex_img_tag(self, latex_command):
        """Transform the latex tags to image tags."""

        img_file = self.create_latex_img_file(latex_command)
        if not img_file:
            return "<b>" + \
            _("Problem with latex. Are latex and dvipng installed?") + "</b>"
        # Note that we leave the the expanding of paths to the expand_paths
        # plugin, as during export, we should not do this and we disable the
        # expand_paths plugin. This means however that the expand_paths plugin
        # should always run at the end.
        return "<img src=\"" + img_file + "\" align=middle>"
Esempio n. 19
0
 def preview(self):
     card_type = self.affected_card_types[0]
     for render_chain in self.component_manager.all("render_chain"):
         render_chain.renderer_for_card_type(card_type).update(card_type)
     fact_data = {}
     for fact_key, fact_key_name in card_type.fact_keys_and_names:
         fact_data[fact_key] = _(fact_key_name)
         # Tmp hack for cloze.
         if fact_key == "text":
             fact_data[fact_key] = "[" + _(fact_key_name) + "]"
     fact = Fact(fact_data)
     cards = card_type.create_sister_cards(fact)
     # Tmp hack for Anki
     for card in cards:
         card.extra_data["ord"] = 1
     tag_text = ""
     dlg = PreviewCardsDlg(cards,
                           tag_text,
                           component_manager=self.component_manager,
                           parent=self)
     dlg.exec_()
Esempio n. 20
0
 def activate_saved_plugins(self):
     for plugin in self.config()["active_plugins"]:
         try:
             for p in self.plugins():
                 if plugin == p.__class__.__name__:
                     p.activate()
                     break
         except:
             from mnemosyne.libmnemosyne.gui_translator import _
             msg = _("Error when running plugin:") \
                   + "\n" + traceback_string()
             self.main_widget().show_error(msg)
Esempio n. 21
0
    def show_answer(self):
        if self._state == "SELECT AHEAD":
            if self.config()["warned_about_learning_ahead"] == False:
                self.main_widget().show_information(\
_("Use 'Learn ahead of schedule' sparingly. For cramming before an exam, it's much better to use the cramming scheduler plugin"))
                self.config()["warned_about_learning_ahead"] = True
            self.learning_ahead = True
            self.show_new_question()
        else:
            self.stopwatch().stop()
            self._state = "SELECT GRADE"
        self.update_dialog()
Esempio n. 22
0
    def last_rep_to_interval_string(self, last_rep, now=None):
        """Converts next_rep to a string like 'yesterday', '2 weeks ago', ...

        """

        if now is None:
            now = time.time()
        # To perform the calculation, we need to 'snap' the two timestamps
        # to midnight UTC before calculating the interval.
        now = self.midnight_UTC(\
            now - self.config()["day_starts_at"] * HOUR)
        last_rep = self.midnight_UTC(\
            last_rep - self.config()["day_starts_at"] * HOUR)
        interval_days = (last_rep - now) / DAY
        if interval_days > -1:
            return _("today")
        elif interval_days > -2:
            return _("yesterday")
        elif interval_days > -31:
            return str(int(-interval_days)) + " " + _("days ago")
        elif interval_days > -62:
            return _("1 month ago")
        elif interval_days > -365:
            interval_months = int(-interval_days / 31.)
            return str(interval_months) + " " + _("months ago")
        else:
            interval_years = -interval_days / 365.
            return "%.1f " % interval_years + _("years ago")
Esempio n. 23
0
class Map(CardType):

    id = "4"
    name = _("Map")

    # List and name the keys.
    fact_keys_and_names = [("loc", _("Location")), ("blank", _("Blank map")),
                           ("marked", _("Marked map"))]

    # Recognition.
    v1 = FactView(_("Recognition"), "4.1")
    v1.q_fact_keys = ["_", "marked"]
    v1.a_fact_keys = ["loc", "marked"]
    v1.a_on_top_of_q = True

    # Production.
    v2 = FactView(_("Production"), "4.2")
    v2.q_fact_keys = ["loc", "blank"]
    v2.a_fact_keys = ["loc", "marked"]
    v2.a_on_top_of_q = True

    fact_views = [v1, v2]
    unique_fact_keys = ["loc"]
    required_fact_keys = ["loc", "blank", "marked"]

    def fact_data(self, card):
        _fact_data = copy.copy(card.fact.data)
        _fact_data["_"] = "&nbsp;"  # Insert a blank line to improve layout.
        return _fact_data

    def fact_key_format_proxies(self):
        return {"loc": "loc", "blank": "blank", "marked": "marked", "_": "loc"}
 def context_menu(self, point):
     menu = QtWidgets.QMenu(self)
     edit_action = QtWidgets.QAction(_("&Edit"), menu)
     edit_action.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_E)
     edit_action.triggered.connect(self.menu_edit)
     menu.addAction(edit_action)
     preview_action = QtWidgets.QAction(_("&Preview"), menu)
     preview_action.setShortcut(QtCore.Qt.CTRL + QtCore.Qt.Key_P)
     preview_action.triggered.connect(self.menu_preview)
     menu.addAction(preview_action)
     delete_action = QtWidgets.QAction(_("&Delete"), menu)
     delete_action.setShortcut(QtGui.QKeySequence.Delete)
     delete_action.triggered.connect(self.menu_delete)
     menu.addAction(delete_action)
     menu.addSeparator()
     change_card_type_action = QtWidgets.QAction(_("Change card &type"),
                                                 menu)
     change_card_type_action.triggered.connect(self.menu_change_card_type)
     menu.addAction(change_card_type_action)
     menu.addSeparator()
     add_tags_action = QtWidgets.QAction(_("&Add tags"), menu)
     add_tags_action.triggered.connect(self.menu_add_tags)
     menu.addAction(add_tags_action)
     remove_tags_action = QtWidgets.QAction(_("&Remove tags"), menu)
     remove_tags_action.triggered.connect(self.menu_remove_tags)
     menu.addAction(remove_tags_action)
     indexes = self.table.selectionModel().selectedRows()
     if len(indexes) > 1:
         edit_action.setEnabled(False)
         preview_action.setEnabled(False)
     if len(indexes) >= 1:
         menu.exec_(self.table.mapToGlobal(point))
Esempio n. 25
0
 def _rebuild(self):
     for key in dict(self):
         del self[key]
     self["__ALL__"] = []
     self.display_name_for_node = {"__ALL__": _("All tags")}
     self.card_count_for_node = {}
     self.tag_for_node = {}
     # Preprocess tag names such that each tag results in a leaf of the
     # tree, i.e. if you have tags like "A::B" and "A", rename the latter
     # to "A::Untagged".
     tags = self.database().tags()
     tag_names = [tag.name for tag in tags]
     preprocessed_tag_name_for = {}
     for tag in tags:
         preprocessed_tag_name_for[tag] = tag.name
         for other_tag_name in tag_names:
             if other_tag_name.startswith(tag.name + "::") \
                 and other_tag_name != tag.name:
                 preprocessed_tag_name_for[tag] = \
                     tag.name + "::" + _("Untagged")
                 break
     # Build the actual tag tree.
     for tag in tags:
         name = preprocessed_tag_name_for[tag]
         self.tag_for_node[name] = tag
         parent = "__ALL__"
         partial_tag = ""
         for node in name.split("::"):
             if partial_tag:
                 partial_tag += "::"
             partial_tag += node
             if not partial_tag in self.display_name_for_node:
                 self[parent].append(partial_tag)
                 self[partial_tag] = []
                 self.display_name_for_node[partial_tag] = \
                     node.replace("::" + _("Untagged"), "")
             parent = partial_tag
     if "__UNTAGGED__" in self.display_name_for_node:
         self.display_name_for_node["__UNTAGGED__"] = _("Untagged")
Esempio n. 26
0
 def download_translation(self):
     self.last_foreign_text = self.foreign_text.toPlainText()
     if not self.last_foreign_text:
         return
     # Note that we need to save the QtThread as a class variable,
     # otherwise it will get garbage collected.
     self.download_thread = DownloadThread(self.translator, self.card_type,
                                           self.last_foreign_text,
                                           self.target_language_id)
     self.download_thread.finished_signal.connect(self.show_translation)
     self.download_thread.error_signal.connect(self.show_error)
     self.main_widget().set_progress_text(_("Downloading..."))
     self.download_thread.start()
Esempio n. 27
0
    def show_statistics(self, variant):
        self.activate()
        # Determine variant-dependent formatting.
        if not self.page.y:
            self.display_message(_("No stats available."))
            return
        ticklabels_neg = lambda i, j, k: ["%d" % x for x in range(i, j, k)]
        if hasattr(self.page, "LAST_WEEK") and \
            variant == self.page.LAST_WEEK:
            xticks = list(range(-7, 1, 1))
            xticklabels = ticklabels_neg(-7, 1, 1)
        elif hasattr(self.page, "LAST_MONTH") and \
            variant == self.page.LAST_MONTH:
            xticks = list(range(-30, -4, 5)) + [0]
            xticklabels = ticklabels_neg(-30, -4, 5) + ["0"]

        elif hasattr(self.page, "LAST_3_MONTHS") and \
            variant == self.page.LAST_3_MONTHS:
            xticks = list(range(-90, -9, 10)) + [0]
            xticklabels = ticklabels_neg(-90, -9, 10) + ["0"]

        elif hasattr(self.page, "LAST_6_MONTHS") and \
            variant == self.page.LAST_6_MONTHS:
            xticks = list(range(-180, -19, 20)) + [0]
            xticklabels = ticklabels_neg(-180, -19, 20) + ["0"]
        elif hasattr(self.page, "LAST_YEAR") and \
            variant == self.page.LAST_YEAR:
            xticks = list(range(-360, -59, 60)) + [0]
            xticklabels = ticklabels_neg(-360, -59, 60) + ["0"]
        else:
            raise AttributeError("Invalid variant")
        # Plot data.
        self.axes.plot(self.page.x, self.page.y)
        self.axes.set_title(self.title)
        self.axes.set_xlabel(_("Days"))
        self.axes.set_xticks(xticks)
        self.axes.set_xticklabels(xticklabels)
        xmin, xmax = min(self.page.x), max(self.page.x)
        self.axes.set_xlim(xmin=xmin - 0.5, xmax=xmax + 0.5)
Esempio n. 28
0
class CuecardWcu(FileFormat, MediaPreprocessor):

    description = _("Cuecard .wcu")
    extension = ".wcu"
    filename_filter = _("Cuecard files (*.wcu *.WCU)")
    import_possible = True
    export_possible = False

    def __init__(self, component_manager):
        FileFormat.__init__(self, component_manager)
        MediaPreprocessor.__init__(self, component_manager)

    def do_import(self, filename, extra_tag_names=""):
        FileFormat.do_import(self, filename, extra_tag_names)
        w = self.main_widget()
        try:
            tree = cElementTree.parse(filename)
        except cElementTree.ParseError as e:
            w.show_error(_("Unable to parse file:") + str(e))
            return
        card_type = self.card_type_with_id("1")
        tag_names = [tag_name.strip() for \
            tag_name in extra_tag_names.split(",") if tag_name.strip()]
        for element in tree.getroot().findall("Card"):
            fact_data = {
                "f": element.attrib["Question"],
                "b": element.attrib["Answer"]
            }
            self.preprocess_media(fact_data, tag_names)
            card = self.controller().create_new_cards(
                fact_data,
                card_type,
                grade=-1,
                tag_names=tag_names,
                check_for_duplicates=False,
                save=False)[0]
            if _("MISSING_MEDIA") in tag_names:
                tag_names.remove(_("MISSING_MEDIA"))
        self.warned_about_missing_media = False
Esempio n. 29
0
    def show_save_file_as_dialog(self):
        self.stopwatch().pause()
        if self.config()["single_database_help_shown"] == False:
            self.main_widget().show_information(_(self.single_database_help))
            self.config()["single_database_help_shown"] = True
        self.flush_sync_server()
        suffix = self.database().suffix
        old_path = expand_path(self.config()["last_database"],
                               self.config().data_dir)
        old_media_dir = self.database().media_dir()
        filename = self.main_widget().get_filename_to_save(
            path=old_path, filter=_("Mnemosyne databases") + " (*%s)" % suffix)
        if not filename:
            self.stopwatch().unpause()
            return
        if filename.endswith("config.db"):
            self.main_widget().show_information(\
_("The configuration database cannot be used to store cards."))
            self.stopwatch().unpause()
            return
        if not filename.endswith(suffix):
            filename += suffix
        try:
            self.database().save(filename)
            new_media_dir = self.database().media_dir()
            if old_media_dir == new_media_dir:
                return
            import shutil
            if os.path.exists(new_media_dir):
                shutil.rmtree(new_media_dir)
            shutil.copytree(old_media_dir, new_media_dir)
            self.log().saved_database()
        except RuntimeError as error:
            self.main_widget().show_error(str(error))
            self.stopwatch().unpause()
            return
        self.review_controller().update_dialog()
        self.update_title()
        self.stopwatch().unpause()
 def menu_delete(self):
     answer = self.main_widget().show_question\
         (_("Go ahead with delete? Sister cards will be deleted as well."),
         _("&OK"), _("&Cancel"), "")
     if answer == 1:  # Cancel.
         return
     _fact_ids = set()
     for index in self.table.selectionModel().selectedRows():
         _fact_id_index = index.model().index(\
             index.row(), _FACT_ID, index.parent())
         _fact_id = index.model().data(_fact_id_index)
         _fact_ids.add(_fact_id)
     facts = []
     for _fact_id in _fact_ids:
         facts.append(self.database().fact(_fact_id, is_id_internal=True))
     self.unload_qt_database()
     self.saved_selection = []
     self.controller().delete_facts_and_their_cards(facts)
     self.card_type_tree_wdgt.rebuild()
     self.tag_tree_wdgt.rebuild()
     self.load_qt_database()
     self.display_card_table()