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)
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 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)
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")
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()
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")
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)
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()
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
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")
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()
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()
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")