Example #1
0
    def init_layout(self):
        self.setWindowTitle(" Prestudy")

        vbox = QVBoxLayout()

        vbox.addWidget(QLabel("Select deck to add notes to:"))
        self.combo_box = QComboBox(self)
        self.combo_box.addItems(self.deck_names)
        vbox.addWidget(self.combo_box)

        vbox.addWidget(
            QLabel(
                "(Optional) Enter tag(s) to add to notes, separated by spaces:"
            ))
        self.tags_box = QLineEdit()
        vbox.addWidget(self.tags_box)

        hbox = QHBoxLayout()
        self.finish_button = QPushButton("Add Notes")
        hbox.addStretch(1)
        hbox.addWidget(self.finish_button)
        vbox.addLayout(hbox)

        self.finish_button.clicked.connect(lambda: self.add_notes_action())

        self.setLayout(vbox)
Example #2
0
    def _setupUi(self):
        flabel = QLabel("In this field:")
        self.fsel = QComboBox()
        fields = self._getFields()
        self.fsel.addItems(fields)
        self.cb = QCheckBox()
        self.cb.setText("transform to plain text")
        f_hbox = QHBoxLayout()
        f_hbox.addWidget(flabel)
        f_hbox.addWidget(self.fsel)
        f_hbox.addWidget(self.cb)
        f_hbox.setAlignment(Qt.AlignLeft)

        button_box = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
            orientation=Qt.Horizontal,
            parent=self,
        )

        bottom_hbox = QHBoxLayout()
        bottom_hbox.addWidget(button_box)

        vbox_main = QVBoxLayout()
        vbox_main.addLayout(f_hbox)
        vbox_main.addLayout(bottom_hbox)
        self.setLayout(vbox_main)
        self.setWindowTitle("Batch Clean Selected Notes")
        button_box.rejected.connect(self.reject)
        button_box.accepted.connect(self.accept)
        self.rejected.connect(self.reject)
        self.accepted.connect(self.accept)
        self.fsel.setFocus()
Example #3
0
 def rebuild_template_map(self, key=None, attr=None):
     if not key:
         key = "t"
         attr = "tmpls"
     map_widget = getattr(self, key + "widg")
     layout = getattr(self, key + "layout")
     src = self.old_model[attr]
     dst = self.targetModel[attr]
     if map_widget:
         layout.removeWidget(map_widget)
         map_widget.deleteLater()
         setattr(self, key + "MapWidget", None)
     map_widget = QWidget()
     map_widget_layout = QGridLayout()
     combos = []
     targets = [entity['name'] for entity in dst] + [_("Nothing")]
     indices = {}
     for i, entity in enumerate(src):
         map_widget_layout.addWidget(QLabel(_("Change %s to:") % entity['name']), i, 0)
         combo_box = QComboBox()
         combo_box.addItems(targets)
         idx = min(i, len(targets) - 1)
         combo_box.setCurrentIndex(idx)
         indices[combo_box] = idx
         combo_box.currentIndexChanged.connect(
             lambda entry_id: self.on_combo_changed(entry_id, combo_box, key))
         combos.append(combo_box)
         map_widget_layout.addWidget(combo_box, i, 1)
     map_widget.setLayout(map_widget_layout)
     layout.addWidget(map_widget)
     setattr(self, key + "widg", map_widget)
     setattr(self, key + "layout", layout)
     setattr(self, key + "combos", combos)
     setattr(self, key + "indices", indices)
Example #4
0
def setup_evernote(self):
    global evernote_default_deck
    global evernote_default_tag
    global evernote_tags_to_import
    global keep_evernote_tags
    global update_existing_notes

    widget = QWidget()
    layout = QVBoxLayout()

    # Default Deck
    evernote_default_deck_label = QLabel("Default Deck:")
    evernote_default_deck = QLineEdit()
    evernote_default_deck.setText(mw.col.conf.get(SETTING_DEFAULT_DECK, ""))
    layout.insertWidget(int(layout.count()) + 1, evernote_default_deck_label)
    layout.insertWidget(int(layout.count()) + 2, evernote_default_deck)
    evernote_default_deck.connect(evernote_default_deck, SIGNAL("editingFinished()"), update_evernote_default_deck)

    # Default Tag
    evernote_default_tag_label = QLabel("Default Tag:")
    evernote_default_tag = QLineEdit()
    evernote_default_tag.setText(mw.col.conf.get(SETTING_DEFAULT_TAG, ""))
    layout.insertWidget(int(layout.count()) + 1, evernote_default_tag_label)
    layout.insertWidget(int(layout.count()) + 2, evernote_default_tag)
    evernote_default_tag.connect(evernote_default_tag, SIGNAL("editingFinished()"), update_evernote_default_tag)

    # Tags to Import
    evernote_tags_to_import_label = QLabel("Tags to Import:")
    evernote_tags_to_import = QLineEdit()
    evernote_tags_to_import.setText(mw.col.conf.get(SETTING_TAGS_TO_IMPORT, ""))
    layout.insertWidget(int(layout.count()) + 1, evernote_tags_to_import_label)
    layout.insertWidget(int(layout.count()) + 2, evernote_tags_to_import)
    evernote_tags_to_import.connect(evernote_tags_to_import,
                                    SIGNAL("editingFinished()"),
                                    update_evernote_tags_to_import)

    # Keep Evernote Tags
    keep_evernote_tags = QCheckBox("Keep Evernote Tags", self)
    keep_evernote_tags.setChecked(mw.col.conf.get(SETTING_KEEP_TAGS, False))
    keep_evernote_tags.stateChanged.connect(update_evernote_keep_tags)
    layout.insertWidget(int(layout.count()) + 1, keep_evernote_tags)

    # Update Existing Notes
    update_existing_notes = QComboBox()
    update_existing_notes.addItems(["Ignore Existing Notes", "Update Existing Notes In-Place",
                                    "Delete and Re-Add Existing Notes"])
    update_existing_notes.setCurrentIndex(mw.col.conf.get(SETTING_UPDATE_EXISTING_NOTES,
                                                          UpdateExistingNotes.UpdateNotesInPlace))
    update_existing_notes.activated.connect(update_evernote_update_existing_notes)
    layout.insertWidget(int(layout.count()) + 1, update_existing_notes)

    # Vertical Spacer
    vertical_spacer = QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)
    layout.addItem(vertical_spacer)

    # Parent Widget
    widget.setLayout(layout)

    # New Tab
    self.form.tabWidget.addTab(widget, "Evernote Importer")
Example #5
0
class FinalTouchesWindow(QWidget):
    """
    Window 3/3, allows the user to set deck and tags.
    """
    def __init__(self, vocab_words: List[Term]):
        super().__init__(mw, flags=QtCore.Qt.Window)
        self.vocab_words = vocab_words
        self.init_layout()

    def init_layout(self):
        self.setWindowTitle(" Prestudy")

        vbox = QVBoxLayout()

        vbox.addWidget(QLabel("Select deck to add notes to:"))
        self.combo_box = QComboBox(self)
        self.combo_box.addItems(self.deck_names)
        vbox.addWidget(self.combo_box)

        vbox.addWidget(
            QLabel(
                "(Optional) Enter tag(s) to add to notes, separated by spaces:"
            ))
        self.tags_box = QLineEdit()
        vbox.addWidget(self.tags_box)

        hbox = QHBoxLayout()
        self.finish_button = QPushButton("Add Notes")
        hbox.addStretch(1)
        hbox.addWidget(self.finish_button)
        vbox.addLayout(hbox)

        self.finish_button.clicked.connect(lambda: self.add_notes_action())

        self.setLayout(vbox)

    @property
    def deck_names(self):
        return [d["name"] for d in self.decks]

    @property
    def decks(self):
        return sorted(list(mw.col.decks.decks.values()),
                      key=lambda d: d["name"])

    def add_notes_action(self):
        # Checkpoint so user can undo later
        mw.checkpoint("Add Prestudy Notes")

        add_notes(self.vocab_words, self.combo_box.currentText(),
                  self.tags_box.text().split())

        # Refresh main window view
        mw.reset()

        self.close()
Example #6
0
    def _ui_field_select_row(self):
        hbox = QHBoxLayout()
        hbox.setAlignment(Qt.AlignLeft)
        hbox.addWidget(QLabel("Field:"))

        model = self.browser.mw.col.getNote(self.nids[0]).model()
        field_names = self.browser.mw.col.models.fieldNames(model)

        self.field_selection = QComboBox()
        self.field_selection.addItems(field_names)
        hbox.addWidget(self.field_selection)

        return hbox
    def _ui_join_keys_row(self):
        def _fix_width(cb):
            width = cb.minimumSizeHint().width()
            cb.view().setMinimumWidth(width)

        # first row consists of join keys for notes and file
        hbox = QHBoxLayout()
        hbox.setAlignment(Qt.AlignLeft)

        # file join key
        hbox.addWidget(QLabel("File Join Key:"))
        self.file_join_key_selection = QComboBox()
        self.file_join_key_selection.addItems(self.file_field_names)
        _fix_width(self.file_join_key_selection)
        if "nid" in self.file_field_names:
            self.file_join_key_selection.setCurrentText("nid")
        else:
            self.file_join_key_selection.setCurrentText(
                self.file_field_names[0])
        self.file_join_key_selection.currentIndexChanged.connect(
            lambda _: self._combobox_changed(self.file_join_key_selection))
        hbox.addWidget(self.file_join_key_selection)

        # note join key
        hbox.addWidget(QLabel("Note Join Key:"))
        self.note_join_key_selection = QComboBox()
        expanded_note_field_names = ["nid"] + self.note_field_names
        self.note_join_key_selection.addItems(expanded_note_field_names)
        _fix_width(self.note_join_key_selection)
        self.note_join_key_selection_default_value = "nid"
        if self.file_join_key_selection.currentText(
        ) in expanded_note_field_names:
            self.note_join_key_selection.setCurrentText(
                self.file_join_key_selection.currentText())
        else:
            self.note_join_key_selection.setCurrentText(
                self.note_join_key_selection_default_value)
        self.note_join_key_selection.currentIndexChanged.connect(
            lambda _: self._combobox_changed(self.note_join_key_selection))
        hbox.addWidget(self.note_join_key_selection)

        yield hbox

        hbox = QHBoxLayout()
        hbox.addWidget(
            QLabel("Define the mapping from file fields to note fields. "
                   "Any file fields mapping to nothing will be ignored."))
        yield hbox
def setup_evernote(self):
    global evernote_default_deck
    global evernote_default_tag
    global evernote_tags_to_import
    global keep_evernote_tags
    global update_existing_notes

    widget = QWidget()
    layout = QVBoxLayout()

    # Default Deck
    evernote_default_deck_label = QLabel("Default Deck for imported Cards:")
    evernote_default_deck = QLineEdit()
    evernote_default_deck.setText(mw.col.conf.get(SETTING_DEFAULT_DECK, ""))
    layout.insertWidget(int(layout.count()) + 1, evernote_default_deck_label)
    layout.insertWidget(int(layout.count()) + 2, evernote_default_deck)
    evernote_default_deck.connect(evernote_default_deck, SIGNAL("editingFinished()"), update_evernote_default_deck)

    # Default Tag
    evernote_default_tag_label = QLabel("Default Tag for imported Cards:")
    evernote_default_tag = QLineEdit()
    evernote_default_tag.setText(mw.col.conf.get(SETTING_DEFAULT_TAG, ""))
    layout.insertWidget(int(layout.count()) + 1, evernote_default_tag_label)
    layout.insertWidget(int(layout.count()) + 2, evernote_default_tag)
    evernote_default_tag.connect(evernote_default_tag, SIGNAL("editingFinished()"), update_evernote_default_tag)

    # Tags to Import
    evernote_tags_to_import_label = QLabel("Evernote Tags to Import:")
    evernote_tags_to_import = QLineEdit()
    evernote_tags_to_import.setText(mw.col.conf.get(SETTING_TAGS_TO_IMPORT, ""))
    layout.insertWidget(int(layout.count()) + 1, evernote_tags_to_import_label)
    layout.insertWidget(int(layout.count()) + 2, evernote_tags_to_import)
    evernote_tags_to_import.connect(evernote_tags_to_import,
                                    SIGNAL("editingFinished()"),
                                    update_evernote_tags_to_import)

    # Keep Evernote Tags
    keep_evernote_tags = QCheckBox("Keep Evernote Tags", self)
    keep_evernote_tags.setChecked(mw.col.conf.get(SETTING_KEEP_TAGS, False))
    keep_evernote_tags.stateChanged.connect(update_evernote_keep_tags)
    layout.insertWidget(int(layout.count()) + 1, keep_evernote_tags)

    # Update Existing Notes
    update_existing_notes = QComboBox()
    update_existing_notes.addItems(["Ignore Existing Notes", "Update Existing Notes In-Place",
                                    "Delete and Re-Add Existing Notes"])
    update_existing_notes.setCurrentIndex(mw.col.conf.get(SETTING_UPDATE_EXISTING_NOTES,
                                                          UpdateExistingNotes.UpdateNotesInPlace))
    update_existing_notes.activated.connect(update_evernote_update_existing_notes)
    layout.insertWidget(int(layout.count()) + 1, update_existing_notes)

    # Vertical Spacer
    vertical_spacer = QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)
    layout.addItem(vertical_spacer)

    # Parent Widget
    widget.setLayout(layout)

    # New Tab
    self.form.tabWidget.addTab(widget, "Evernote2AnkiMac")
Example #9
0
def _rebuildMap(self, key):
    """
    key -- either "t" or "f", for template or fields. What to edit.

    """
    map = getattr(self, key + "widg")
    lay = getattr(self, key + "layout")
    if map:
        lay.removeWidget(map)
        map.deleteLater()
        setattr(self, key + "MapWidget", None)
    map = QWidget()
    l = QGridLayout()
    combos = []
    targets = [template['name'] for template in getattr(self, key + "dst")]
    indices = {}
    sources = getattr(self, key + "src")
    sourcesNames = [template["name"] for template in sources]
    if getUserOption("Associate to same name"):
        assoc = eltToPos(sourcesNames, targets)
    else:
        assoc = enumerate(sourcesNames)
    for indexSrc, (indexDst, templateName) in enumerate(assoc):
        l.addWidget(QLabel(_("Change %s to:") % templateName), indexSrc, 0)
        cb = QComboBox()
        cb.addItems(targets + [_("Nothing")])
        idx = min(indexDst, len(targets))
        cb.setCurrentIndex(idx)
        indices[cb] = idx
        cb.currentIndexChanged.connect(
            lambda i, cb=cb, key=key: self.onComboChanged(i, cb, key))
        combos.append(cb)
        l.addWidget(cb, indexSrc, 1)
    map.setLayout(l)
    lay.addWidget(map)
    setattr(self, key + "widg", map)
    setattr(self, key + "layout", lay)
    setattr(self, key + "combos", combos)
    setattr(self, key + "indices", indices)
 def rebuild_template_map(self, key=None, attr=None):
     if not key:
         key = "t"
         attr = "tmpls"
     map_widget = getattr(self, key + "widg")
     layout = getattr(self, key + "layout")
     src = self.old_model[attr]
     dst = self.targetModel[attr]
     if map_widget:
         layout.removeWidget(map_widget)
         map_widget.deleteLater()
         setattr(self, key + "MapWidget", None)
     map_widget = QWidget()
     map_widget_layout = QGridLayout()
     combos = []
     targets = [entity['name'] for entity in dst] + [_("Nothing")]
     indices = {}
     for i, entity in enumerate(src):
         map_widget_layout.addWidget(
             QLabel(_("Change %s to:") % entity['name']), i, 0)
         combo_box = QComboBox()
         combo_box.addItems(targets)
         idx = min(i, len(targets) - 1)
         combo_box.setCurrentIndex(idx)
         indices[combo_box] = idx
         combo_box.currentIndexChanged.connect(
             lambda entry_id: self.on_combo_changed(entry_id, combo_box, key
                                                    ))
         combos.append(combo_box)
         map_widget_layout.addWidget(combo_box, i, 1)
     map_widget.setLayout(map_widget_layout)
     layout.addWidget(map_widget)
     setattr(self, key + "widg", map_widget)
     setattr(self, key + "layout", layout)
     setattr(self, key + "combos", combos)
     setattr(self, key + "indices", indices)
Example #11
0
class BatchCleanDialog(QDialog):
    """Browser batch editing dialog"""
    def __init__(self, browser, nids):
        QDialog.__init__(self, parent=browser)
        self.browser = browser
        self.nids = nids
        self._setupUi()

    def _setupUi(self):
        flabel = QLabel("In this field:")
        self.fsel = QComboBox()
        fields = self._getFields()
        self.fsel.addItems(fields)
        self.cb = QCheckBox()
        self.cb.setText("transform to plain text")
        f_hbox = QHBoxLayout()
        f_hbox.addWidget(flabel)
        f_hbox.addWidget(self.fsel)
        f_hbox.addWidget(self.cb)
        f_hbox.setAlignment(Qt.AlignLeft)

        button_box = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel,
            orientation=Qt.Horizontal,
            parent=self,
        )

        bottom_hbox = QHBoxLayout()
        bottom_hbox.addWidget(button_box)

        vbox_main = QVBoxLayout()
        vbox_main.addLayout(f_hbox)
        vbox_main.addLayout(bottom_hbox)
        self.setLayout(vbox_main)
        self.setWindowTitle("Batch Clean Selected Notes")
        button_box.rejected.connect(self.reject)
        button_box.accepted.connect(self.accept)
        self.rejected.connect(self.reject)
        self.accepted.connect(self.accept)
        self.fsel.setFocus()

    def _getFields(self):
        nid = self.nids[0]
        mw = self.browser.mw
        model = mw.col.getNote(nid).model()
        fields = mw.col.models.fieldNames(model)
        return fields

    def reject(self):
        self.close()

    def accept(self):
        if self.cb.isChecked():
            func = stripHTML
        else:
            func = cleanHtml_regular_use
        self.browser.mw.checkpoint("batch edit")
        self.browser.mw.progress.start()
        self.browser.model.beginReset()
        fld_name = self.fsel.currentText()
        cnt = 0
        for nid in self.nids:
            note = self.browser.mw.col.getNote(nid)
            cleaned = func(note[fld_name])
            if cleaned != note[fld_name]:
                cnt += 1
                note[fld_name] = cleaned
                note.flush()
        self.browser.model.endReset()
        self.browser.mw.requireReset()
        self.browser.mw.progress.finish()
        self.browser.mw.reset()
        self.close()
        tooltip(f"{cnt} notes cleaned")

    def closeEvent(self, evt):
        evt.accept()
Example #12
0
    def setup(self):
        addonconfig = mw.addonManager.getConfig(__name__)
        config = types.SimpleNamespace(**addonconfig['defaults'])
        if addonconfig.get("_debug_time", False):
            self.timepoint = lambda c: print("%s: %0.3f" %
                                             (c, time.time() - self.time))
        else:
            self.timepoint = lambda _: None
        config.did = mw.col.conf['curDeck']

        swin = QDialog(mw)
        vl = QVBoxLayout()
        fl = QHBoxLayout()
        deckcb = QComboBox()
        deckcb.addItems(sorted(mw.col.decks.allNames()))
        deckcb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        fl.addWidget(QLabel("Deck: "))
        deckcb.setCurrentText(mw.col.decks.get(config.did)['name'])

        def change_did(deckname):
            config.did = mw.col.decks.byName(deckname)['id']

        deckcb.currentTextChanged.connect(change_did)
        fl.addWidget(deckcb)
        vl.addLayout(fl)
        frm = QGroupBox("Settings")
        vl.addWidget(frm)
        il = QVBoxLayout()
        fl = QHBoxLayout()
        field = QLineEdit()
        field.setPlaceholderText(
            "e.g. \"kanji\", \"hanzi\" or \"sentence-kanji\" (default: \"%s\")"
            % config.pattern)
        il.addWidget(
            QLabel("Pattern or Field names to search for (case insensitive):"))
        fl.addWidget(field)
        liter = QCheckBox("Match exactly")
        liter.setChecked(config.literal)
        fl.addWidget(liter)
        il.addLayout(fl)
        stint = QSpinBox()
        stint.setRange(1, 65536)
        stint.setValue(config.interval)
        il.addWidget(QLabel("Card interval considered strong:"))
        il.addWidget(stint)
        ttcol = QSpinBox()
        ttcol.setRange(1, 99)
        ttcol.setValue(config.thin)
        il.addWidget(QLabel("Number of Columns in the in-app table:"))
        il.addWidget(ttcol)
        wtcol = QSpinBox()
        wtcol.setRange(1, 99)
        wtcol.setValue(config.wide)
        il.addWidget(QLabel("Number of Columns in the exported table:"))
        il.addWidget(wtcol)
        groupby = QComboBox()
        groupby.addItems([
            *("None, sorted by " + x.pretty_value() for x in SortOrder),
            *(x.name for x in data.groups),
        ])
        groupby.setCurrentIndex(config.groupby)
        il.addWidget(QLabel("Group by:"))
        il.addWidget(groupby)
        shnew = QCheckBox("Show units not yet seen")
        shnew.setChecked(config.unseen)
        il.addWidget(shnew)
        toolt = QCheckBox("Show informational tooltips")
        toolt.setChecked(config.tooltips)
        il.addWidget(toolt)
        frm.setLayout(il)
        hl = QHBoxLayout()
        vl.addLayout(hl)
        gen = QPushButton("Generate", clicked=swin.accept)
        hl.addWidget(gen)
        cls = QPushButton("Close", clicked=swin.reject)
        hl.addWidget(cls)
        swin.setLayout(vl)
        swin.setTabOrder(gen, cls)
        swin.setTabOrder(cls, field)
        swin.setTabOrder(field, liter)
        swin.setTabOrder(liter, stint)
        swin.setTabOrder(stint, ttcol)
        swin.setTabOrder(ttcol, wtcol)
        swin.setTabOrder(wtcol, groupby)
        swin.setTabOrder(groupby, shnew)
        swin.setTabOrder(shnew, toolt)
        swin.resize(500, 400)
        if swin.exec_():
            mw.progress.start(immediate=True)
            if len(field.text().strip()) != 0:
                config.pattern = field.text().lower()
            config.pattern = config.pattern.split()
            config.literal = liter.isChecked()
            config.interval = stint.value()
            config.thin = ttcol.value()
            config.wide = wtcol.value()
            config.groupby = groupby.currentIndex()
            config.unseen = shnew.isChecked()
            config.tooltips = toolt.isChecked()
            self.makegrid(config)
            mw.progress.finish()
            self.win.show()
    def _ui_field_select_rows(self):

        # combo boxes to select mapping for remaining fields
        self.mapping_field_selections = []
        for field_name in self.file_field_names:
            # nid can only be used as a join key
            if field_name == "nid":
                continue
            hbox = QHBoxLayout()
            hbox.setAlignment(Qt.AlignLeft)
            hbox.addWidget(QLabel("{} -> ".format(field_name)))
            field_selection = QComboBox()
            field_selection.addItems([NOTHING_VALUE] + self.note_field_names)
            width = field_selection.minimumSizeHint().width()
            field_selection.view().setMinimumWidth(width)
            if field_name in self.note_field_names and \
                    field_name != self.note_join_key_selection.currentText():
                field_selection.setCurrentText(field_name)
            else:
                field_selection.setCurrentText(NOTHING_VALUE)
            field_selection.currentIndexChanged.connect(
                lambda _, fs=field_selection: self._combobox_changed(fs))
            hbox.addWidget(field_selection)
            self.mapping_field_selections.append(field_selection)
            yield hbox
Example #14
0
def setup_evernote(self):
    global evernote_default_deck
    global evernote_default_tag
    global evernote_tags_to_import
    global keep_evernote_tags
    global update_existing_notes

    widget = QWidget()
    layout = QVBoxLayout()

    # Default Deck
    evernote_default_deck_label = QLabel("Default Deck:")
    evernote_default_deck = QLineEdit()
    evernote_default_deck.setText(mw.col.conf.get(SETTING_DEFAULT_DECK, ""))
    layout.insertWidget(int(layout.count()) + 1, evernote_default_deck_label)
    layout.insertWidget(int(layout.count()) + 2, evernote_default_deck)
    evernote_default_deck.connect(evernote_default_deck, SIGNAL("editingFinished()"), update_evernote_default_deck)

    # Default Tag
    evernote_default_tag_label = QLabel("Default Tag:")
    evernote_default_tag = QLineEdit()
    evernote_default_tag.setText(mw.col.conf.get(SETTING_DEFAULT_TAG, ""))
    layout.insertWidget(int(layout.count()) + 1, evernote_default_tag_label)
    layout.insertWidget(int(layout.count()) + 2, evernote_default_tag)
    evernote_default_tag.connect(evernote_default_tag, SIGNAL("editingFinished()"), update_evernote_default_tag)

    # Tags to Import
    evernote_tags_to_import_label = QLabel("Tags to Import:")
    evernote_tags_to_import = QLineEdit()
    evernote_tags_to_import.setText(mw.col.conf.get(SETTING_TAGS_TO_IMPORT, ""))
    layout.insertWidget(int(layout.count()) + 1, evernote_tags_to_import_label)
    layout.insertWidget(int(layout.count()) + 2, evernote_tags_to_import)
    evernote_tags_to_import.connect(evernote_tags_to_import,
                                    SIGNAL("editingFinished()"),
                                    update_evernote_tags_to_import)

    # Keep Evernote Tags
    keep_evernote_tags = QCheckBox("Keep Evernote Tags", self)
    keep_evernote_tags.setChecked(mw.col.conf.get(SETTING_KEEP_TAGS, False))
    keep_evernote_tags.stateChanged.connect(update_evernote_keep_tags)
    layout.insertWidget(int(layout.count()) + 1, keep_evernote_tags)

    # Update Existing Notes
    updated_label = QLabel("Behavior if a note is already imported:")
    layout.insertWidget(int(layout.count()) + 1, updated_label)
    update_existing_notes = QComboBox()
    update_existing_notes.addItems(["Ignore (do nothing)",
                                    "Update (raise API usage, but useful if you tend to edit your cards in Evernote)",
                                    "Reset (same as Update, but cards are rescheduled)"])
    update_existing_notes.setCurrentIndex(mw.col.conf.get(SETTING_UPDATE_EXISTING_NOTES,
                                                          UpdateExistingNotes.UpdateNotesInPlace))
    update_existing_notes.activated.connect(update_evernote_update_existing_notes)
    layout.insertWidget(int(layout.count()) + 1, update_existing_notes)

    deletebutton = QPushButton(_("Reset Account"), clicked=remove_token)
    layout.insertWidget(int(layout.count()) + 1, deletebutton)


    # Vertical Spacer
    vertical_spacer = QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)
    layout.addItem(vertical_spacer)

    # Parent Widget
    widget.setLayout(layout)

    # New Tab
    self.form.tabWidget.addTab(widget, "Evernote Importer")
Example #15
0
    def setup(self):
        addonconfig = mw.addonManager.getConfig(__name__)
        config = types.SimpleNamespace(**addonconfig['defaults'])
        if addonconfig.get("_debug_time", False):
            self.timepoint = lambda c: print("%s: %0.3f" % (c, time.time()-self.time))
        else:
            self.timepoint = lambda _: None
        config.did = mw.col.conf['curDeck']

        swin = QDialog(mw)
        vl = QVBoxLayout()
        fl = QHBoxLayout()
        deckcb = QComboBox()
        deckcb.addItems(sorted(mw.col.decks.allNames()))
        deckcb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        fl.addWidget(QLabel("Deck: "))
        deckcb.setCurrentText(mw.col.decks.get(config.did)['name'])
        def change_did(deckname):
            config.did = mw.col.decks.byName(deckname)['id']
        deckcb.currentTextChanged.connect(change_did)
        fl.addWidget(deckcb)
        vl.addLayout(fl)
        frm = QGroupBox("Settings")
        vl.addWidget(frm)
        il = QVBoxLayout()
        fl = QHBoxLayout()
        field = QLineEdit()
        field.setPlaceholderText("e.g. \"kanji\" or \"sentence-kanji\" (default: \"%s\")" % config.pattern)
        il.addWidget(QLabel("Pattern or Field names to search for (case insensitive):"))
        fl.addWidget(field)
        liter = QCheckBox("Match exactly")
        liter.setChecked(config.literal)
        fl.addWidget(liter)
        il.addLayout(fl)
        stint = QSpinBox()
        stint.setRange(1, 65536)
        stint.setValue(config.interval)
        il.addWidget(QLabel("Card interval considered strong:"))
        il.addWidget(stint)
        ttcol = QSpinBox()
        ttcol.setRange(1, 99)
        ttcol.setValue(config.thin)
        il.addWidget(QLabel("Number of Columns:"))
        coll = QHBoxLayout()
        coll.addWidget(QLabel("In-app:"))
        coll.addWidget(ttcol)
        wtcol = QSpinBox()
        wtcol.setRange(1, 99)
        wtcol.setValue(config.wide)
        coll.addWidget(QLabel("Exported:"))
        coll.addWidget(wtcol)
        itcol = QCheckBox("Don't care")
        itcol.setChecked(addonconfig['defaults'].get("autothinwide", False))
        def disableEnableColumnSettings(state):
            ttcol.setEnabled(state != Qt.Checked)
            wtcol.setEnabled(state != Qt.Checked)
        itcol.stateChanged.connect(disableEnableColumnSettings)
        disableEnableColumnSettings(itcol.checkState())
        coll.addWidget(itcol)
        il.addLayout(coll)
        groupby = QComboBox()
        groupby.addItems([
            *("None, sorted by " + x.pretty_value() for x in SortOrder),
            *(x.name for x in data.groups),
        ])
        groupby.setCurrentIndex(config.groupby)
        il.addWidget(QLabel("Group by:"))
        il.addWidget(groupby)
        shnew = QCheckBox("Show units not yet seen")
        shnew.setChecked(config.unseen)
        il.addWidget(shnew)
        toolt = QCheckBox("Show informational tooltips")
        toolt.setChecked(config.tooltips)
        il.addWidget(toolt)
        frm.setLayout(il)
        hl = QHBoxLayout()
        vl.addLayout(hl)
        gen = QPushButton("Generate", clicked=swin.accept)
        hl.addWidget(gen)
        cls = QPushButton("Close", clicked=swin.reject)
        hl.addWidget(cls)
        swin.setLayout(vl)
        swin.setTabOrder(gen, cls)
        swin.setTabOrder(cls, field)
        swin.setTabOrder(field, liter)
        swin.setTabOrder(liter, stint)
        swin.setTabOrder(stint, ttcol)
        swin.setTabOrder(ttcol, wtcol)
        swin.setTabOrder(wtcol, groupby)
        swin.setTabOrder(groupby, shnew)
        swin.setTabOrder(shnew, toolt)
        swin.resize(500, 400)
        if swin.exec_():
            mw.progress.start(immediate=True)
            if len(field.text().strip()) != 0:
                config.pattern = field.text().lower()
            config.pattern = config.pattern.split()
            config.literal = liter.isChecked()
            config.interval = stint.value()
            config.thin = ttcol.value()
            config.wide = wtcol.value()
            config.groupby = groupby.currentIndex()
            config.unseen = shnew.isChecked()
            config.tooltips = toolt.isChecked()
            config.autothinwide = itcol.isChecked()
            self.makegrid(config)
            mw.progress.finish()
            self.win.show()
Example #16
0
class TextCleanerDialogBase(QDialog):
    """Base class for dialogs"""
    def __init__(self, browser, nids, description, title):
        super().__init__(parent=browser)
        self.browser = browser
        self.nids = nids
        self.description = description
        self.title = title
        self.changelog = ChangeLog()
        self._setup_ui()

    def _setup_ui(self):
        self.setWindowTitle(self.title)
        self.setMinimumWidth(600)
        self.setMinimumHeight(400)

        vbox = QVBoxLayout()
        vbox.addLayout(self._ui_top_row())
        vbox.addLayout(self._ui_field_select_row())
        vbox.addWidget(self._ui_log())
        vbox.addLayout(self._ui_bottom_row())

        self.setLayout(vbox)

    def _ui_top_row(self):
        hbox = QHBoxLayout()
        hbox.addWidget(QLabel(self.description))
        return hbox

    def _ui_field_select_row(self):
        hbox = QHBoxLayout()
        hbox.setAlignment(Qt.AlignLeft)
        hbox.addWidget(QLabel("Field:"))

        model = self.browser.mw.col.getNote(self.nids[0]).model()
        field_names = self.browser.mw.col.models.fieldNames(model)

        self.field_selection = QComboBox()
        self.field_selection.addItems(field_names)
        hbox.addWidget(self.field_selection)

        return hbox

    def _ui_log(self):
        self.log = QPlainTextEdit()
        self.log.setTabChangesFocus(False)
        self.log.setReadOnly(True)

        font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
        font.setPointSize(self.log.font().pointSize() - 2)
        self.log.setFont(font)
        return self.log

    def _ui_bottom_row(self):
        hbox = QHBoxLayout()

        buttons = QDialogButtonBox(Qt.Horizontal, self)

        # Button to check if content needs to be changed
        check_btn = buttons.addButton("&Check", QDialogButtonBox.ActionRole)
        check_btn.setToolTip("Check")
        check_btn.clicked.connect(lambda _: self.onCheck())

        # Button to generate diff of proposed content changes
        diff_btn = buttons.addButton("&Diff", QDialogButtonBox.ActionRole)
        diff_btn.setToolTip("Show diff")
        diff_btn.clicked.connect(lambda _: self.onDiff())

        # Button to make the proposed changes
        fix_btn = buttons.addButton("&Fix", QDialogButtonBox.ActionRole)
        fix_btn.setToolTip("Fix")
        fix_btn.clicked.connect(lambda _: self.onFix())

        # Button to close this dialog
        close_btn = buttons.addButton("&Close", QDialogButtonBox.RejectRole)
        close_btn.clicked.connect(self.close)

        hbox.addWidget(buttons)
        return hbox

    def clean_content(self, content, output_html_diff=False):
        raise NotImplementedError("clean_content")

    def onCheck(self):
        """Checks which notes need to be updated for the selected field"""
        append_to_log = self.log.appendPlainText

        try:
            self.log.clear()
            nids = self.nids
            field_name = self.field_selection.currentText()

            checked = 0
            need_clean = 0
            failed_notes = []
            for nid in nids:
                note = self.browser.mw.col.getNote(nid)
                if field_name in note:
                    content = note[field_name]
                    try:
                        cleaned_content = self.clean_content(content)
                        if content != cleaned_content:
                            append_to_log(
                                "Need to update note for nid {}:".format(nid))
                            append_to_log("{}\n=>\n{}\n".format(
                                content, cleaned_content))
                            need_clean += 1
                    except TextProcessingError as e:
                        failed_notes.append((nid, content, str(e)))
                    checked += 1
            if failed_notes:
                append_to_log(
                    "Found {} notes that failed to be processed:".format(
                        len(failed_notes)))
                for nid, _, exc in failed_notes:
                    append_to_log("{}: {}\n".format(nid, exc))
            append_to_log("Checked {} notes".format(checked))
            append_to_log("Found {} notes ({:.0f}%) need to be updated".format(
                need_clean,
                0 if not checked else 100.0 * need_clean / checked))
            if failed_notes:
                append_to_log(
                    "Found {} notes that failed to be processed".format(
                        len(failed_notes)))

        except Exception:
            append_to_log("Failed while checking notes:\n{}".format(
                traceback.format_exc()))

        # Ensure QPlainTextEdit refreshes (not clear why this is necessary)
        self.log.repaint()

    def onDiff(self):
        """Produces HTML diff of the updates that would be made"""
        append_to_log = self.log.appendPlainText

        lines = []
        try:
            self.log.clear()
            nids = self.nids
            field_name = self.field_selection.currentText()

            cnt = 0
            need_clean = 0
            failed_notes = []
            for nid in nids:
                note = self.browser.mw.col.getNote(nid)
                if field_name in note:
                    content = note[field_name]
                    try:
                        cleaned_content = self.clean_content(
                            content, output_html_diff=True)
                        if content != cleaned_content:
                            lines.append((nid, cleaned_content))
                            need_clean += 1
                    except TextProcessingError as e:
                        failed_notes.append((nid, content, str(e)))
                    cnt += 1
            if failed_notes:
                append_to_log(
                    "Found {} notes that failed to be processed:".format(
                        len(failed_notes)))
                for nid, _, exc in failed_notes:
                    append_to_log("{}: {}\n".format(nid, exc))
            append_to_log(
                "Checked {} notes. Found {} notes need updating.".format(
                    cnt, need_clean))
            if failed_notes:
                append_to_log(
                    "Found {} notes that failed to be processed".format(
                        len(failed_notes)))

            if len(lines) > 0:
                ext = ".html"
                default_path = QStandardPaths.writableLocation(
                    QStandardPaths.DocumentsLocation)
                path = os.path.join(default_path, f"diff{ext}")

                options = QFileDialog.Options()

                # native doesn't seem to works
                options |= QFileDialog.DontUseNativeDialog

                # we'll confirm ourselves
                options |= QFileDialog.DontConfirmOverwrite

                result = QFileDialog.getSaveFileName(self,
                                                     "Save HTML diff",
                                                     path,
                                                     f"HTML (*{ext})",
                                                     options=options)

                if not isinstance(result, tuple):
                    raise Exception("Expected a tuple from save dialog")
                file = result[0]
                if file:
                    do_save = True
                    if not file.lower().endswith(ext):
                        file += ext
                    if os.path.exists(file):
                        if not askUser(
                                "{} already exists. Are you sure you want to overwrite it?"
                                .format(file),
                                parent=self):
                            do_save = False
                    if do_save:
                        append_to_log("Saving to {}".format(file))
                        with open(file, "w", encoding="utf-8") as outf:
                            outf.write(DIFF_PRE)
                            for nid, line in lines:
                                outf.write("<p>nid {}:</p>\n".format(nid))
                                outf.write("<p>{}</p>\n".format(line))
                            outf.write(DIFF_POST)
                        append_to_log("Done")

            # Ensure QPlainTextEdit refreshes (not clear why this is necessary)
            self.log.repaint()

        except Exception:
            append_to_log("Failed while checking notes:\n{}".format(
                traceback.format_exc()))

    def onFix(self):
        """Updates the selected notes where the content needs to be updated"""
        append_to_log = self.log.appendPlainText

        try:
            self.log.clear()
            nids = self.nids
            field_name = self.field_selection.currentText()

            append_to_log("Checking how many notes need to be updated")
            checked = 0
            note_changes = []
            failed_notes = []
            for nid in nids:
                note = self.browser.mw.col.getNote(nid)
                if field_name in note:
                    content = note[field_name]
                    try:
                        cleaned_content = self.clean_content(content)
                        if content != cleaned_content:
                            note_changes.append(
                                NoteChange(nid=nid,
                                           old=content,
                                           new=cleaned_content))
                    except TextProcessingError as e:
                        failed_notes.append((nid, content, str(e)))
                    checked += 1

            if failed_notes:
                append_to_log(
                    "Found {} notes that failed to be processed:".format(
                        len(failed_notes)))
                for nid, _, exc in failed_notes:
                    append_to_log("{}: {}\n".format(nid, exc))

            append_to_log("{} of {} notes will be updated".format(
                len(note_changes), checked))

            if failed_notes:
                append_to_log(
                    "Found {} notes that failed to be processed.".format(
                        len(failed_notes)))

            self.log.repaint()

            if askUser(
                    "{} of {} notes will be updated.  Are you sure you want to do this?"
                    .format(len(note_changes), checked),
                    parent=self):

                append_to_log("Beginning update")

                self.browser.mw.checkpoint("{} ({} {})".format(
                    self.checkpoint_name, len(note_changes),
                    "notes" if len(note_changes) > 1 else "note"))
                self.browser.model.beginReset()

                cleaned = 0
                try:
                    init_ts = int(time.time() * 1000)
                    for note_change in note_changes:
                        note = self.browser.mw.col.getNote(note_change.nid)
                        content = note[field_name]

                        # content should not have changed
                        if content != note_change.old:
                            # this should never happen
                            raise NoteFixError(
                                "nid {} old and new content do not match".
                                format(note_change.nid))

                        cleaned_content = note_change.new

                        append_to_log("Updating note for nid {}:".format(nid))
                        append_to_log("{}\n=>\n{}\n".format(
                            content, cleaned_content))

                        ts = int(time.time() * 1000)

                        note[field_name] = cleaned_content
                        note.flush()

                        self.changelog.record_change(
                            self.op, init_ts,
                            ChangeLogEntry(ts=ts,
                                           nid=nid,
                                           fld=field_name,
                                           old=content,
                                           new=cleaned_content))

                        cleaned += 1

                    append_to_log("Updated {} notes ({:.0f}%)".format(
                        cleaned,
                        0 if not checked else 100.0 * cleaned / checked))

                finally:
                    if cleaned:
                        self.changelog.commit_changes()
                        self.browser.mw.requireReset()
                    self.browser.model.endReset()

            else:
                append_to_log("User aborted update")

        except Exception:
            append_to_log("Failed while checking notes:\n{}".format(
                traceback.format_exc()))

        # Ensure QPlainTextEdit refreshes (not clear why this is necessary)
        self.log.repaint()

    def close(self):
        self.changelog.close()
        super().close()
class BatchUpdateDialog(QDialog):
    """Base class for dialogs"""
    def __init__(self, browser, nids, file):
        super().__init__(parent=browser)
        self.browser = browser
        self.nids = nids
        self.title = "Batch Update Selected Notes"
        self.changelog = ChangeLog()
        self.checkpoint_name = "Batch Update"
        self.file = file

        # note field names and model id
        first_note = self.browser.mw.col.getNote(self.nids[0])
        model = first_note.model()
        self.model_id = first_note.mid
        self.note_field_names = self.browser.mw.col.models.fieldNames(model)

        # file field names
        with open(self.file, encoding="utf-8") as inf:
            reader = csv.DictReader(inf)

            # load field names as list of strings
            self.file_field_names = reader.fieldnames

        self._setup_ui()

    def _setup_ui(self):
        self.setWindowTitle(self.title)
        self.setMinimumWidth(600)
        self.setMinimumHeight(400)

        vbox = QVBoxLayout()
        for row in self._ui_join_keys_row():
            vbox.addLayout(row)
        scroll_area = QScrollArea()
        inner = QFrame(scroll_area)
        vbox_scrollable = QVBoxLayout()
        inner.setLayout(vbox_scrollable)
        for row in self._ui_field_select_rows():
            vbox_scrollable.addLayout(row)
        scroll_area.setWidget(inner)

        splitter = QSplitter()
        splitter.setOrientation(Qt.Vertical)
        splitter.addWidget(scroll_area)
        splitter.addWidget(self._ui_log())
        vbox.addWidget(splitter)
        vbox.addLayout(self._ui_bottom_row())

        self.setLayout(vbox)

    def _ui_join_keys_row(self):
        def _fix_width(cb):
            width = cb.minimumSizeHint().width()
            cb.view().setMinimumWidth(width)

        # first row consists of join keys for notes and file
        hbox = QHBoxLayout()
        hbox.setAlignment(Qt.AlignLeft)

        # file join key
        hbox.addWidget(QLabel("File Join Key:"))
        self.file_join_key_selection = QComboBox()
        self.file_join_key_selection.addItems(self.file_field_names)
        _fix_width(self.file_join_key_selection)
        if "nid" in self.file_field_names:
            self.file_join_key_selection.setCurrentText("nid")
        else:
            self.file_join_key_selection.setCurrentText(
                self.file_field_names[0])
        self.file_join_key_selection.currentIndexChanged.connect(
            lambda _: self._combobox_changed(self.file_join_key_selection))
        hbox.addWidget(self.file_join_key_selection)

        # note join key
        hbox.addWidget(QLabel("Note Join Key:"))
        self.note_join_key_selection = QComboBox()
        expanded_note_field_names = ["nid"] + self.note_field_names
        self.note_join_key_selection.addItems(expanded_note_field_names)
        _fix_width(self.note_join_key_selection)
        self.note_join_key_selection_default_value = "nid"
        if self.file_join_key_selection.currentText(
        ) in expanded_note_field_names:
            self.note_join_key_selection.setCurrentText(
                self.file_join_key_selection.currentText())
        else:
            self.note_join_key_selection.setCurrentText(
                self.note_join_key_selection_default_value)
        self.note_join_key_selection.currentIndexChanged.connect(
            lambda _: self._combobox_changed(self.note_join_key_selection))
        hbox.addWidget(self.note_join_key_selection)

        yield hbox

        hbox = QHBoxLayout()
        hbox.addWidget(
            QLabel("Define the mapping from file fields to note fields. "
                   "Any file fields mapping to nothing will be ignored."))
        yield hbox

    def _ui_field_select_rows(self):

        # combo boxes to select mapping for remaining fields
        self.mapping_field_selections = []
        for field_name in self.file_field_names:
            # nid can only be used as a join key
            if field_name == "nid":
                continue
            hbox = QHBoxLayout()
            hbox.setAlignment(Qt.AlignLeft)
            hbox.addWidget(QLabel("{} -> ".format(field_name)))
            field_selection = QComboBox()
            field_selection.addItems([NOTHING_VALUE] + self.note_field_names)
            width = field_selection.minimumSizeHint().width()
            field_selection.view().setMinimumWidth(width)
            if field_name in self.note_field_names and \
                    field_name != self.note_join_key_selection.currentText():
                field_selection.setCurrentText(field_name)
            else:
                field_selection.setCurrentText(NOTHING_VALUE)
            field_selection.currentIndexChanged.connect(
                lambda _, fs=field_selection: self._combobox_changed(fs))
            hbox.addWidget(field_selection)
            self.mapping_field_selections.append(field_selection)
            yield hbox

    def _combobox_changed(self, updated_cb):
        new_text = updated_cb.currentText()

        # We only need to check for non-nothing values, because multiple fields can
        # be set to nothing.
        if new_text != NOTHING_VALUE and updated_cb is not self.file_join_key_selection:
            # We only need to check the note mappings.  We exclude the file join key combobox.
            note_field_combos = [self.note_join_key_selection
                                 ] + self.mapping_field_selections

            for cb in note_field_combos:
                if cb is updated_cb:
                    continue
                else:
                    if cb.currentText() == new_text:
                        if cb is self.note_join_key_selection:
                            cb.setCurrentText(
                                self.note_join_key_selection_default_value)
                        else:
                            cb.setCurrentText(NOTHING_VALUE)

    def _ui_log(self):
        self.log = QPlainTextEdit()
        self.log.setTabChangesFocus(False)
        self.log.setReadOnly(True)

        font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
        font.setPointSize(self.log.font().pointSize() - 2)
        self.log.setFont(font)
        return self.log

    def _ui_bottom_row(self):
        hbox = QHBoxLayout()

        buttons = QDialogButtonBox(Qt.Horizontal, self)

        # Button to check if content needs to be changed
        check_btn = buttons.addButton("&Dry-run", QDialogButtonBox.ActionRole)
        check_btn.setToolTip("Dry-run")
        check_btn.clicked.connect(lambda _: self.onCheck(mode="dryrun"))

        # Button to diff the proposed changes
        diff_btn = buttons.addButton("&Diff", QDialogButtonBox.ActionRole)
        diff_btn.setToolTip("Diff")
        diff_btn.clicked.connect(lambda _: self.onCheck(mode="diff"))

        # Button to make the proposed changes
        fix_btn = buttons.addButton("&Update", QDialogButtonBox.ActionRole)
        fix_btn.setToolTip("Update")
        fix_btn.clicked.connect(lambda _: self.onCheck(mode="update"))

        # Button to close this dialog
        close_btn = buttons.addButton("&Close", QDialogButtonBox.RejectRole)
        close_btn.clicked.connect(self.close)

        hbox.addWidget(buttons)
        return hbox

    def onCheck(self, *, mode):
        self.log.clear()
        try:
            # Mapping from field name in file to field combo boxes for notes.
            # We need to check each of the selections for the combo boxes.
            zipped_fields = zip(
                [fn for fn in self.file_field_names if fn != "nid"],
                self.mapping_field_selections)

            # mapping from file join key name to note join key name
            file_join_key_name = self.file_join_key_selection.currentText()
            note_join_key_name = self.note_join_key_selection.currentText()

            self.log.appendPlainText(
                "Join key: File field '{}' -> Note field '{}'".format(
                    file_join_key_name, note_join_key_name))

            # Check which of the field combo boxes having a non-nothing selection and
            # build the mapping from fields in file to fields in notes.
            file_to_note_mappings = {}
            for file_field_name, note_field_cb in zipped_fields:
                note_field_name = note_field_cb.currentText()
                if note_field_name != NOTHING_VALUE:
                    file_to_note_mappings[file_field_name] = note_field_name
                    self.log.appendPlainText(
                        "File field '{}' -> Note field '{}'".format(
                            file_field_name, note_field_name))
            if not file_to_note_mappings:
                self.log.appendPlainText("ERROR: No mappings selected")
                return

            # Check which key values exist and to make sure there are no duplicate values.
            # Build a mapping form these key values to the row which contains the field values.
            file_key_to_values = {}
            duplicate_file_key_values = set()
            with open(self.file, encoding="utf-8") as inf:
                reader = csv.DictReader(inf)
                for row in reader:
                    join_key_val = row[file_join_key_name]
                    if join_key_val in file_key_to_values:
                        duplicate_file_key_values.add(join_key_val)
                    else:
                        file_key_to_values[join_key_val] = row
            if duplicate_file_key_values:
                self.log.appendPlainText(
                    "ERROR: Found {} key values for '{}' that appear more than once:"
                    .format(len(duplicate_file_key_values),
                            file_join_key_name))
                for val in duplicate_file_key_values:
                    self.log.appendPlainText(val)
                return
            self.log.appendPlainText("Found {} records for '{}' in {}".format(
                len(file_key_to_values), file_join_key_name, self.file))

            # When we aren't joining by nid, we need to create an additional mapping from the join
            # key to the nid value, because we can only look up by nid.
            note_join_key_to_nid = {}
            if note_join_key_name != "nid":
                self.log.appendPlainText(
                    "Joining to notes by '{}', so finding all values.".format(
                        note_join_key_name))
                for nid in self.nids:
                    note = self.browser.mw.col.getNote(nid)
                    if note.mid != self.model_id:
                        self.log.appendPlainText(
                            "ERROR: Note {} has different model ID {} than expected {} based on first note. "
                            .format(nid, note.mid, self.model_id) +
                            "Please only select notes of the same model.")
                        return
                    if note_join_key_name in note:
                        if note[note_join_key_name] in note_join_key_to_nid:
                            self.log.appendPlainText(
                                "ERROR: Value '{}' already exists in notes".
                                format(note[note_join_key_name]))
                            return
                        else:
                            note_join_key_to_nid[
                                note[note_join_key_name]] = nid
                    else:
                        self.log.appendPlainText(
                            "ERROR: Field '{}' not found in note {}".format(
                                note_join_key_name, nid))
                        return

            # these store the changes we will propose to make (grouped by nid)
            note_changes = defaultdict(list)

            # track join keys that were not found in notes
            missing_note_keys = set()

            # how many fields being updated are empty
            empty_note_field_count = 0
            notes_with_empty_fields = set()

            for file_key, file_values in file_key_to_values.items():

                if note_join_key_name == "nid":
                    nid = file_key
                else:
                    if file_key in note_join_key_to_nid:
                        nid = note_join_key_to_nid[file_key]
                        self.log.appendPlainText(
                            "Found note {} with value {} for '{}'".format(
                                nid, file_key, note_join_key_name))
                    else:
                        self.log.appendPlainText(
                            "Could not find note with value {} for '{}'".
                            format(file_key, note_join_key_name))
                        missing_note_keys.add(file_key)
                        continue

                self.log.appendPlainText("Checking note {}".format(nid))

                try:
                    note = self.browser.mw.col.getNote(nid)
                except TypeError:
                    self.log.appendPlainText(
                        "ERROR: Note {} was not found".format(nid))
                    return

                # Get the current values for fields we're updating in the note.
                note_values = {}
                for note_field_name in file_to_note_mappings.values():
                    if note_field_name in note:
                        note_values[note_field_name] = note[note_field_name]
                    else:
                        self.log.appendPlainText(
                            "ERROR: Field '{}' not found in note {}".format(
                                note_field_name, nid))
                        return

                # Compare the file field values to the note field values and see if anything is different
                # and therefore needs to be updated.
                for file_field_name, note_field_name in file_to_note_mappings.items(
                ):
                    file_value = file_values[file_field_name]
                    note_value = note_values[note_field_name]
                    if file_value != note_value:
                        self.log.appendPlainText(
                            "Need to update note field '{}':".format(
                                note_field_name))
                        self.log.appendPlainText("{}\n=>\n{}".format(
                            note_value or "<empty>", file_value))
                        note_changes[nid].append(
                            NoteChange(nid=nid,
                                       fld=note_field_name,
                                       old=note_value,
                                       new=file_value))
                        if not note_value:
                            empty_note_field_count += 1
                            notes_with_empty_fields.add(nid)

            if missing_note_keys:
                self.log.appendPlainText(
                    "ERROR: {} values were not found in notes for field '{}'".
                    format(len(missing_note_keys), note_join_key_name))
                return

            if note_changes:
                self.log.appendPlainText(
                    "Need to make changes to {} notes".format(
                        len(note_changes)))
                if empty_note_field_count:
                    self.log.appendPlainText(
                        "{} fields across {} notes are empty".format(
                            empty_note_field_count,
                            len(notes_with_empty_fields)))

                if mode == "dryrun":
                    # nothing to do
                    pass
                elif mode == "diff":
                    ext = ".html"
                    default_path = QStandardPaths.writableLocation(
                        QStandardPaths.DocumentsLocation)
                    path = os.path.join(default_path, f"diff{ext}")

                    options = QFileDialog.Options()

                    # native doesn't seem to works
                    options |= QFileDialog.DontUseNativeDialog

                    # we'll confirm ourselves
                    options |= QFileDialog.DontConfirmOverwrite

                    result = QFileDialog.getSaveFileName(self,
                                                         "Save HTML diff",
                                                         path,
                                                         f"HTML (*{ext})",
                                                         options=options)

                    if not isinstance(result, tuple):
                        raise Exception("Expected a tuple from save dialog")
                    file = result[0]
                    if file:
                        do_save = True
                        if not file.lower().endswith(ext):
                            file += ext
                        if os.path.exists(file):
                            if not askUser(
                                    "{} already exists. Are you sure you want to overwrite it?"
                                    .format(file),
                                    parent=self):
                                do_save = False
                        if do_save:
                            self.log.appendPlainText(
                                "Saving to {}".format(file))
                            with open(file, "w", encoding="utf-8") as outf:
                                outf.write(DIFF_PRE)
                                for nid, changes in note_changes.items():
                                    outf.write("<p>nid {}:</p>\n".format(nid))
                                    for change in changes:
                                        outf.write("<p>{}: {}</p>\n".format(
                                            change.fld,
                                            html_diff(html.escape(change.old),
                                                      html.escape(
                                                          change.new))))
                                outf.write(DIFF_POST)
                            self.log.appendPlainText("Done")
                elif mode == "update":
                    if askUser(
                            "{} notes will be updated.  Are you sure you want to do this?"
                            .format(len(note_changes)),
                            parent=self):
                        self.log.appendPlainText("Beginning update")

                        self.browser.mw.checkpoint("{} ({} {})".format(
                            self.checkpoint_name, len(note_changes),
                            "notes" if len(note_changes) > 1 else "note"))
                        self.browser.model.beginReset()
                        updated_count = 0
                        try:
                            init_ts = int(time.time() * 1000)

                            for nid, changes in note_changes.items():
                                note = self.browser.mw.col.getNote(nid)
                                for change in changes:
                                    ts = int(time.time() * 1000)
                                    note[change.fld] = change.new
                                    self.changelog.record_change(
                                        "batch_update", init_ts,
                                        ChangeLogEntry(ts=ts,
                                                       nid=nid,
                                                       fld=change.fld,
                                                       old=change.old,
                                                       new=change.new))
                                note.flush()
                                updated_count += 1
                            self.log.appendPlainText(
                                "Updated {} notes".format(updated_count))
                        finally:
                            if updated_count:
                                self.changelog.commit_changes()
                                self.browser.mw.requireReset()
                            self.browser.model.endReset()
                else:
                    self.log.appendPlainText(
                        "ERROR: Unexpected mode: {}".format(mode))
                    return
            else:
                self.log.appendPlainText("No changes need to be made")

        except Exception:
            self.log.appendPlainText("Failed during dry run:\n{}".format(
                traceback.format_exc()))

        finally:
            # Ensure QPlainTextEdit refreshes (not clear why this is necessary)
            self.log.repaint()

    def close(self):
        self.changelog.close()
        super().close()
Example #18
0
def setup_evernote(self):
    global icoEvernoteWeb
    global imgEvernoteWeb
    global elements
    global evernote_query_last_updated
    global evernote_pagination_current_page_spinner

    def update_checkbox(setting):
        if setting is DECKS.EVERNOTE_NOTEBOOK_INTEGRATION and not elements[DECKS.BASE].text():
            return
        if setting.get.startswith(QUERY.get):
            update_evernote_query_visibilities()
        setting.save(elements[setting].isChecked())
        # mw.col.conf[setting] =
        if setting is QUERY.USE_TAGS:
            update_evernote_query_visibilities()
        if setting is QUERY.LAST_UPDATED.USE:
            evernote_query_last_updated_value_set_visibilities()

    def create_checkbox(setting, label=" ", default_value=False, is_fixed_size=False, fixed_width=None):
        if isinstance(label, bool):
            default_value = label
            label = " "
        checkbox = QCheckBox(label, self)
        sval = setting.fetch()
        if not isinstance(sval, bool):
            sval = default_value
        checkbox.setChecked(sval)
        # noinspection PyUnresolvedReferences
        checkbox.stateChanged.connect(lambda: update_checkbox(setting))
        if is_fixed_size or fixed_width:
            checkbox.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
            if fixed_width:
                checkbox.setFixedWidth(fixed_width)
        elements[setting] = checkbox
        return checkbox

    def create_checked_checkbox(*a, **kw):
        kw['default_value'] = True
        return create_checkbox(*a, **kw)

    def update_text(setting, text):
        text = text.strip()
        setting.save(text)
        if setting is DECKS.BASE:
            update_anki_deck_visibilities()
        if setting.get.startswith(QUERY.get):
            if text:
                use_key = getattr(QUERY, 'USE_' + setting.label.name)
                elements[use_key].setChecked(True)
            evernote_query_text_changed()
            if setting is QUERY.SEARCH_TERMS:
                update_evernote_query_visibilities()

    def create_textbox(setting, default_value=""):
        textbox = QLineEdit()
        textbox.setText(setting.fetch(default_value))
        textbox.connect(textbox,
                        SIGNAL("textEdited(QString)"),
                        lambda text: update_text(setting, text))
        elements[setting] = textbox
        return textbox

    def add_query_row(setting, is_checked=False, **kw):
        try:
            default_value = setting.val
        except:
            default_value = ''
        row_label = ' '.join(x.capitalize() for x in setting.replace('_', ' ').split())
        hbox = QHBoxLayout()
        hbox.addWidget(create_checkbox(getattr(QUERY, 'USE_' + setting),
                       default_value=is_checked, **kw))
        hbox.addWidget(create_textbox(getattr(QUERY, setting), default_value))
        form.addRow(row_label, hbox)

    def gen_qt_hr():
        vbox = QVBoxLayout()
        hr = QFrame()
        hr.setAutoFillBackground(True)
        hr.setFrameShape(QFrame.HLine)
        hr.setStyleSheet("QFrame { background-color: #0060bf; color: #0060bf; }")
        hr.setFixedHeight(2)
        vbox.addWidget(hr)
        vbox.addSpacing(4)
        return vbox

    # Begin setup_evernote()
    widget = QWidget()
    layout = QVBoxLayout()
    elements = {}
    rm_log_path('Dicts\\')
    evernote_query_last_updated = DictCaseInsensitive()


    ########################## QUERY ##########################
    ##################### QUERY: TEXTBOXES ####################
    group = QGroupBox("EVERNOTE SEARCH OPTIONS:")
    group.setStyleSheet('QGroupBox{    font-size: 10px;    font-weight: bold;  color: rgb(105, 170, 53);}')
    form = QFormLayout()

    form.addRow(gen_qt_hr())

    # Show Generated Evernote Query Button
    button_show_generated_evernote_query = QPushButton(icoEvernoteWeb, "Show Full Query", self)
    button_show_generated_evernote_query.setAutoDefault(False)
    button_show_generated_evernote_query.connect(button_show_generated_evernote_query,
                                                 SIGNAL("clicked()"),
                                                 handle_show_generated_evernote_query)


    # Add Form Row for Match Any Terms
    hbox = QHBoxLayout()
    hbox.addWidget(create_checked_checkbox(QUERY.ANY, "     Match Any Terms", is_fixed_size=True))
    hbox.addWidget(button_show_generated_evernote_query)
    form.addRow("<b>Search Parameters:</b>", hbox)

    # Add Form Rows for Evernote Query Textboxes
    for el in QUERY_TEXTBOXES:
        add_query_row(el, 'TAGS' in el)

    ################### QUERY: LAST UPDATED ###################
    # Evernote Query: Last Updated Type
    evernote_query_last_updated.type = QComboBox()
    evernote_query_last_updated.type.setStyleSheet(' QComboBox { color: rgb(45, 79, 201); font-weight: bold; } ')
    evernote_query_last_updated.type.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
    evernote_query_last_updated.type.addItems([u"Δ Day", u"Δ Week", u"Δ Month", u"Δ Year", "Date", "+ Time"])
    evernote_query_last_updated.type.setCurrentIndex(QUERY.LAST_UPDATED.TYPE.fetch(EvernoteQueryLocationType.RelativeDay))
    evernote_query_last_updated.type.activated.connect(update_evernote_query_last_updated_type)


    # Evernote Query: Last Updated Type: Relative Date
    evernote_query_last_updated.value.relative.spinner = EvernoteQueryLocationValueQSpinBox()
    evernote_query_last_updated.value.relative.spinner.setVisible(False)
    evernote_query_last_updated.value.relative.spinner.setStyleSheet(
        " QSpinBox, EvernoteQueryLocationValueQSpinBox { font-weight: bold;  color: rgb(173, 0, 0); } ")
    evernote_query_last_updated.value.relative.spinner.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
    evernote_query_last_updated.value.relative.spinner.connect(evernote_query_last_updated.value.relative.spinner,
                                                               SIGNAL("valueChanged(int)"),
                                                               update_evernote_query_last_updated_value_relative_spinner)

    # Evernote Query: Last Updated Type: Absolute Date
    evernote_query_last_updated.value.absolute.date = QDateEdit()
    evernote_query_last_updated.value.absolute.date.setDisplayFormat('M/d/yy')
    evernote_query_last_updated.value.absolute.date.setCalendarPopup(True)
    evernote_query_last_updated.value.absolute.date.setVisible(False)
    evernote_query_last_updated.value.absolute.date.setStyleSheet(
        "QDateEdit { font-weight: bold;  color: rgb(173, 0, 0); } ")
    evernote_query_last_updated.value.absolute.date.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
    evernote_query_last_updated.value.absolute.date.connect(evernote_query_last_updated.value.absolute.date,
                                                            SIGNAL("dateChanged(QDate)"),
                                                            update_evernote_query_last_updated_value_absolute_date)

    # Evernote Query: Last Updated Type: Absolute DateTime
    evernote_query_last_updated.value.absolute.datetime = QDateTimeEdit()
    evernote_query_last_updated.value.absolute.datetime.setDisplayFormat('M/d/yy h:mm AP')
    evernote_query_last_updated.value.absolute.datetime.setCalendarPopup(True)
    evernote_query_last_updated.value.absolute.datetime.setVisible(False)
    evernote_query_last_updated.value.absolute.datetime.setStyleSheet(
        "QDateTimeEdit { font-weight: bold;  color: rgb(173, 0, 0); } ")
    evernote_query_last_updated.value.absolute.datetime.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
    evernote_query_last_updated.value.absolute.datetime.connect(evernote_query_last_updated.value.absolute.datetime,
                                                                SIGNAL("dateTimeChanged(QDateTime)"),
                                                                update_evernote_query_last_updated_value_absolute_datetime)



    # Evernote Query: Last Updated Type: Absolute Time
    evernote_query_last_updated.value.absolute.time = QTimeEdit()
    evernote_query_last_updated.value.absolute.time.setDisplayFormat('h:mm AP')
    evernote_query_last_updated.value.absolute.time.setVisible(False)
    evernote_query_last_updated.value.absolute.time.setStyleSheet(
        "QTimeEdit { font-weight: bold;  color: rgb(143, 0, 30); } ")
    evernote_query_last_updated.value.absolute.time.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
    evernote_query_last_updated.value.absolute.time.connect(evernote_query_last_updated.value.absolute.time,
                                                            SIGNAL("timeChanged(QTime)"),
                                                            update_evernote_query_last_updated_value_absolute_time)

    # Create HBox for Separated Date & Time
    hbox_datetime = QHBoxLayout()
    hbox_datetime.addWidget(evernote_query_last_updated.value.absolute.date)
    hbox_datetime.addWidget(evernote_query_last_updated.value.absolute.time)

    # Evernote Query: Last Updated Type
    evernote_query_last_updated.value.stacked_layout = QStackedLayout()
    evernote_query_last_updated.value.stacked_layout.addWidget(evernote_query_last_updated.value.relative.spinner)
    evernote_query_last_updated.value.stacked_layout.addItem(hbox_datetime)

    # Add Form Row for Evernote Query: Last Updated
    hbox = QHBoxLayout()
    label = QLabel("Last Updated: ")
    label.setMinimumWidth(SETTINGS.FORM.LABEL_MINIMUM_WIDTH.val)
    hbox.addWidget(create_checkbox(QUERY.LAST_UPDATED.USE, is_fixed_size=True))
    hbox.addWidget(evernote_query_last_updated.type)
    hbox.addWidget(evernote_query_last_updated.value.relative.spinner)
    hbox.addWidget(evernote_query_last_updated.value.absolute.date)
    hbox.addWidget(evernote_query_last_updated.value.absolute.time)
    form.addRow(label, hbox)

    # Add Horizontal Row Separator
    form.addRow(gen_qt_hr())

    ############################ PAGINATION ##########################
    # Evernote Pagination: Current Page
    evernote_pagination_current_page_spinner = QSpinBox()
    evernote_pagination_current_page_spinner.setStyleSheet("QSpinBox { font-weight: bold;  color: rgb(173, 0, 0);  } ")
    evernote_pagination_current_page_spinner.setPrefix("PAGE: ")
    evernote_pagination_current_page_spinner.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
    evernote_pagination_current_page_spinner.setValue(EVERNOTE.PAGINATION_CURRENT_PAGE.fetch(1))
    evernote_pagination_current_page_spinner.connect(evernote_pagination_current_page_spinner,
                                                     SIGNAL("valueChanged(int)"),
                                                     update_evernote_pagination_current_page_spinner)

    # Evernote Pagination: Automation
    hbox = QHBoxLayout()
    hbox.addWidget(create_checked_checkbox(EVERNOTE.AUTO_PAGING, "     Automate", fixed_width=105))
    hbox.addWidget(evernote_pagination_current_page_spinner)

    # Add Form Row for Evernote Pagination
    form.addRow("<b>Pagination:</b>", hbox)

    # Add Query Form to Group Box
    group.setLayout(form)

    # Add Query Group Box to Main Layout
    layout.addWidget(group)

    ########################## DECK ##########################
    # Setup Group Box and Form
    group = QGroupBox("ANKI NOTE OPTIONS:")
    group.setStyleSheet('QGroupBox{    font-size: 10px;    font-weight: bold;  color: rgb(105, 170, 53);}')
    form = QFormLayout()

    # Add Horizontal Row Separator
    form.addRow(gen_qt_hr())

    # Add Form Row for Default Anki Deck
    hbox = QHBoxLayout()
    hbox.insertSpacing(0, 33)
    hbox.addWidget(create_textbox(DECKS.BASE, DECKS.BASE_DEFAULT_VALUE))
    label_deck = QLabel("<b>Anki Deck:</b>")
    label_deck.setMinimumWidth(SETTINGS.FORM.LABEL_MINIMUM_WIDTH.val)
    form.addRow(label_deck, hbox)

    # Add Form Row for Evernote Notebook Integration
    label_deck = QLabel("Evernote Notebook:")
    label_deck.setMinimumWidth(SETTINGS.FORM.LABEL_MINIMUM_WIDTH.val)
    form.addRow("", create_checked_checkbox(DECKS.EVERNOTE_NOTEBOOK_INTEGRATION, "      Append Evernote Notebook"))

    # Add Horizontal Row Separator
    form.addRow(gen_qt_hr())

    ############################ TAGS ##########################
    # Add Form Row for Evernote Tag Options
    label = QLabel("<b>Evernote Tags:</b>")
    label.setMinimumWidth(SETTINGS.FORM.LABEL_MINIMUM_WIDTH.val)

    # Tags: Save To Anki Note
    form.addRow(label, create_checkbox(TAGS.KEEP_TAGS, "     Save To Anki Note", TAGS.KEEP_TAGS_DEFAULT_VALUE))
    hbox = QHBoxLayout()
    hbox.insertSpacing(0, 33)
    hbox.addWidget(create_textbox(TAGS.TO_DELETE))

    # Tags: Tags To Delete
    form.addRow("Tags to Delete:", hbox)
    form.addRow(" ", create_checkbox(TAGS.DELETE_EVERNOTE_QUERY_TAGS, "     Also Delete Search Tags"))

    # Add Horizontal Row Separator
    form.addRow(gen_qt_hr())

    ############################ NOTE UPDATING ##########################
    # Note Update Method
    update_existing_notes = QComboBox()
    update_existing_notes.setStyleSheet(
        ' QComboBox { color: #3b679e; font-weight: bold; } QComboBoxItem { color: #A40F2D; font-weight: bold; } ')
    update_existing_notes.addItems(["Ignore Existing Notes", "Update In-Place",
                                    "Delete and Re-Add"])
    sval = ANKI.UPDATE_EXISTING_NOTES.fetch()
    if not isinstance(sval, int):
        sval = ANKI.UPDATE_EXISTING_NOTES.val
    update_existing_notes.setCurrentIndex(sval)
    update_existing_notes.activated.connect(update_update_existing_notes)

    # Add Form Row for Note Update Method
    hbox = QHBoxLayout()
    hbox.insertSpacing(0, 33)
    hbox.addWidget(update_existing_notes)
    form.addRow("<b>Note Updating:</b>", hbox)

    # Add Note Update Method Form to Group Box
    group.setLayout(form)

    # Add Note Update Method Group Box to Main Layout
    layout.addWidget(group)

    ######################### UPDATE VISIBILITIES #######################
    # Update Visibilities of Anki Deck Options
    update_anki_deck_visibilities()

    # Update Visibilities of Query Options
    evernote_query_text_changed()
    update_evernote_query_visibilities()

    ######################## ADD TO SETTINGS PANEL ######################
    # Vertical Spacer
    vertical_spacer = QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)
    layout.addItem(vertical_spacer)

    # Parent Widget
    widget.setLayout(layout)

    # New Tab
    self.form.tabWidget.addTab(widget, "Anknotes")