From 83a8d5d5e1dda1c6b30fab355879ca3f5889cd07 Mon Sep 17 00:00:00 2001 From: Mizaki Date: Sat, 11 Feb 2023 01:18:56 +0000 Subject: [PATCH 01/15] Generate settings tabs for each talker --- comictaggerlib/renamewindow.py | 6 +- comictaggerlib/settingswindow.py | 150 +++++++++++--- comictaggerlib/taggerwindow.py | 4 +- comictaggerlib/ui/settingswindow.ui | 299 ++++++++++++---------------- 4 files changed, 252 insertions(+), 207 deletions(-) diff --git a/comictaggerlib/renamewindow.py b/comictaggerlib/renamewindow.py index 9bed8df..d266f8f 100644 --- a/comictaggerlib/renamewindow.py +++ b/comictaggerlib/renamewindow.py @@ -39,7 +39,7 @@ class RenameWindow(QtWidgets.QDialog): comic_archive_list: list[ComicArchive], data_style: int, config: settngs.Config[settngs.Namespace], - talker: ComicTalker, + talkers: dict[str, ComicTalker], ) -> None: super().__init__(parent) @@ -55,7 +55,7 @@ class RenameWindow(QtWidgets.QDialog): ) self.config = config - self.talker = talker + self.talkers = talkers self.comic_archive_list = comic_archive_list self.data_style = data_style self.rename_list: list[str] = [] @@ -160,7 +160,7 @@ class RenameWindow(QtWidgets.QDialog): self.twList.setSortingEnabled(True) def modify_settings(self) -> None: - settingswin = SettingsWindow(self, self.config, self.talker) + settingswin = SettingsWindow(self, self.config, self.talkers) settingswin.setModal(True) settingswin.show_rename_tab() settingswin.exec() diff --git a/comictaggerlib/settingswindow.py b/comictaggerlib/settingswindow.py index 68a046e..53c599f 100644 --- a/comictaggerlib/settingswindow.py +++ b/comictaggerlib/settingswindow.py @@ -15,6 +15,7 @@ # limitations under the License. from __future__ import annotations +import argparse import html import logging import os @@ -131,7 +132,7 @@ Spider-Geddon #1 - New Players; Check In class SettingsWindow(QtWidgets.QDialog): def __init__( - self, parent: QtWidgets.QWidget, config: settngs.Config[settngs.Namespace], talker: ComicTalker + self, parent: QtWidgets.QWidget, config: settngs.Config[settngs.Namespace], talkers: dict[str, ComicTalker] ) -> None: super().__init__(parent) @@ -142,7 +143,7 @@ class SettingsWindow(QtWidgets.QDialog): ) self.config = config - self.talker = talker + self.talkers = talkers self.name = "Settings" if platform.system() == "Windows": @@ -194,7 +195,6 @@ class SettingsWindow(QtWidgets.QDialog): self.btnBrowseRar.clicked.connect(self.select_rar) self.btnClearCache.clicked.connect(self.clear_cache) self.btnResetSettings.clicked.connect(self.reset_settings) - self.btnTestKey.clicked.connect(self.test_api_key) self.btnTemplateHelp.clicked.connect(self.show_template_help) self.cbxMoveFiles.clicked.connect(self.dir_test) self.leDirectory.textEdited.connect(self.dir_test) @@ -223,7 +223,6 @@ class SettingsWindow(QtWidgets.QDialog): self.btnRemoveValueReplacement.clicked.disconnect() self.btnResetSettings.clicked.disconnect() self.btnTemplateHelp.clicked.disconnect() - self.btnTestKey.clicked.disconnect() self.cbxChangeExtension.clicked.disconnect() self.cbxComplicatedParser.clicked.disconnect() self.cbxMoveFiles.clicked.disconnect() @@ -235,6 +234,100 @@ class SettingsWindow(QtWidgets.QDialog): self.twLiteralReplacements.cellChanged.disconnect() self.twValueReplacements.cellChanged.disconnect() + self.sources: dict = {} + self.generate_source_option_tabs() + + def generate_source_option_tabs(self) -> None: + def format_internal_name(int_name: str = "") -> str: + # Presume talker__ + int_name_split = int_name.split("_") + del int_name_split[0:3] + int_name_split[0] = int_name_split[0].capitalize() + new_name = " ".join(int_name_split) + return new_name + + # Add source sub tabs to Comic Sources tab + for talker_id, talker_obj in self.talkers.items(): + # Add source to general tab dropdown list + self.cobxInfoSource.addItem(talker_obj.name, talker_id) + + # Use a dict to make a var name from var + source_info = {} + tab_name = talker_id + source_info[tab_name] = {"tab": QtWidgets.QWidget(), "widgets": {}} + layout_grid = QtWidgets.QGridLayout() + row = 0 + + full_talker_name = "talker_" + talker_id + for option in self.config[1][full_talker_name][1].values(): + current_widget = None + if option.action is not None and isinstance(option.action, type(argparse.BooleanOptionalAction)): + # bool equals a checkbox (QCheckBox) + current_widget = QtWidgets.QCheckBox(format_internal_name(option.internal_name)) + # Set widget status + current_widget.setChecked(getattr(self.config[0], option.internal_name)) + # Add widget and span all columns + layout_grid.addWidget(current_widget, row, 0, 1, -1) + elif isinstance(option.type, type(int)): + # int equals a spinbox (QSpinBox) + lbl = QtWidgets.QLabel(option.internal_name) + # Create a label + layout_grid.addWidget(lbl, row, 0) + current_widget = QtWidgets.QSpinBox() + current_widget.setRange(0, 9999) + current_widget.setValue(getattr(self.config[0], option.internal_name)) + layout_grid.addWidget(current_widget, row, 1, alignment=QtCore.Qt.AlignLeft) + elif isinstance(option.type, type(float)): + # float equals a spinbox (QDoubleSpinBox) + lbl = QtWidgets.QLabel(format_internal_name(option.internal_name)) + # Create a label + layout_grid.addWidget(lbl, row, 0) + current_widget = QtWidgets.QDoubleSpinBox() + current_widget.setRange(0, 9999.99) + current_widget.setValue(getattr(self.config[0], option.internal_name)) + layout_grid.addWidget(current_widget, row, 1, alignment=QtCore.Qt.AlignLeft) + # type of None should be string + elif option.type is None or isinstance(option.type, type(str)): + # str equals a text field (QLineEdit) + lbl = QtWidgets.QLabel(format_internal_name(option.internal_name)) + # Create a label + layout_grid.addWidget(lbl, row, 0) + current_widget = QtWidgets.QLineEdit() + # Set widget status + current_widget.setText(getattr(self.config[0], option.internal_name)) + layout_grid.addWidget(current_widget, row, 1) + # Special case for api_key, make a test button + if option.internal_name.endswith("api_key"): + btn = QtWidgets.QPushButton("Test Key") + layout_grid.addWidget(btn, row, 2) + btn.clicked.connect(lambda state, sn=talker_id: self.test_api_key(sn)) + row += 1 + + if current_widget: + # Add tooltip text + current_widget.setToolTip(option.help) + + source_info[tab_name]["widgets"][option.internal_name] = current_widget + else: + # An empty current_widget implies an unsupported type + logger.info(f"Unsupported talker option found. Name: {option.internal_name} Type: {option.type}") + + # Add vertical spacer + vspacer = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + layout_grid.addItem(vspacer, row, 0) + # Display the new widgets + source_info[tab_name]["tab"].setLayout(layout_grid) + + # Add new sub tab to Comic Source tab + self.tTalkerTabs.addTab(source_info[tab_name]["tab"], talker_obj.name) + self.sources.update(source_info) + + # Select active source in dropdown + self.cobxInfoSource.setCurrentIndex(self.cobxInfoSource.findData(self.config[0].talker_source)) + + # Set General as start tab + self.tabWidget.setCurrentIndex(0) + def addLiteralReplacement(self) -> None: self.insertRow(self.twLiteralReplacements, self.twLiteralReplacements.rowCount(), Replacement("", "", False)) @@ -312,17 +405,11 @@ class SettingsWindow(QtWidgets.QDialog): self.cbxRemovePublisher.setChecked(self.config[0].filename_remove_publisher) self.switch_parser() - self.cbxUseSeriesStartAsVolume.setChecked(self.config[0].talker_comicvine_cv_use_series_start_as_volume) self.cbxClearFormBeforePopulating.setChecked(self.config[0].talker_clear_form_before_populating) - self.cbxRemoveHtmlTables.setChecked(self.config[0].talker_comicvine_cv_remove_html_tables) - self.cbxUseFilter.setChecked(self.config[0].talker_always_use_publisher_filter) self.cbxSortByYear.setChecked(self.config[0].talker_sort_series_by_year) self.cbxExactMatches.setChecked(self.config[0].talker_exact_series_matches_first) - self.leKey.setText(self.config[0].talker_comicvine_cv_api_key) - self.leURL.setText(self.config[0].talker_comicvine_cv_url) - self.cbxAssumeLoneCreditIsPrimary.setChecked(self.config[0].cbl_assume_lone_credit_is_primary) self.cbxCopyCharactersToTags.setChecked(self.config[0].cbl_copy_characters_to_tags) self.cbxCopyTeamsToTags.setChecked(self.config[0].cbl_copy_teams_to_tags) @@ -428,21 +515,12 @@ class SettingsWindow(QtWidgets.QDialog): self.config[0].filename_remove_fcbd = self.cbxRemoveFCBD.isChecked() self.config[0].filename_remove_publisher = self.cbxRemovePublisher.isChecked() - self.config[0].talker_comicvine_cv_use_series_start_as_volume = self.cbxUseSeriesStartAsVolume.isChecked() self.config[0].talker_clear_form_before_populating = self.cbxClearFormBeforePopulating.isChecked() - self.config[0].talker_comicvine_cv_remove_html_tables = self.cbxRemoveHtmlTables.isChecked() - self.config[0].talker_always_use_publisher_filter = self.cbxUseFilter.isChecked() self.config[0].talker_sort_series_by_year = self.cbxSortByYear.isChecked() self.config[0].talker_exact_series_matches_first = self.cbxExactMatches.isChecked() - if self.leKey.text().strip(): - self.config[0].talker_comicvine_cv_api_key = self.leKey.text().strip() - self.talker.api_key = self.config[0].talker_comicvine_cv_api_key - - if self.leURL.text().strip(): - self.config[0].talker_comicvine_cv_url = self.leURL.text().strip() - self.talker.api_url = self.config[0].talker_comicvine_cv_url + self.config[0].talker_source = str(self.cobxInfoSource.itemData(self.cobxInfoSource.currentIndex())) self.config[0].cbl_assume_lone_credit_is_primary = self.cbxAssumeLoneCreditIsPrimary.isChecked() self.config[0].cbl_copy_characters_to_tags = self.cbxCopyCharactersToTags.isChecked() @@ -464,6 +542,19 @@ class SettingsWindow(QtWidgets.QDialog): self.config[0].rename_strict = self.cbxRenameStrict.isChecked() self.config[0].rename_replacements = self.get_replacements() + # Read settings from sources tabs and generate self.settings.config data + for tab in self.sources.items(): + for name, widget in tab[1]["widgets"].items(): + widget_value = None + if isinstance(widget, (QtWidgets.QSpinBox, QtWidgets.QDoubleSpinBox)): + widget_value = widget.value() + elif isinstance(widget, QtWidgets.QLineEdit): + widget_value = widget.text().strip() + elif isinstance(widget, QtWidgets.QCheckBox): + widget_value = widget.isChecked() + + setattr(self.config[0], name, widget_value) + self.update_talkers_config() settngs.save_file(self.config, self.config[0].runtime_config.user_config_dir / "settings.json") @@ -472,8 +563,8 @@ class SettingsWindow(QtWidgets.QDialog): def update_talkers_config(self) -> None: cfg = settngs.normalize_config(self.config, True, True) - if f"talker_{self.talker.id}" in cfg[0]: - self.talker.parse_settings(cfg[0][f"talker_{self.talker.id}"]) + for talker, talker_obj in self.talkers.items(): + talker_obj.parse_settings(cfg[0][f"talker_{talker}"]) def select_rar(self) -> None: self.select_file(self.leRarExePath, "RAR") @@ -483,11 +574,20 @@ class SettingsWindow(QtWidgets.QDialog): ComicCacher(self.config[0].runtime_config.user_cache_dir, version).clear_cache() QtWidgets.QMessageBox.information(self, self.name, "Cache has been cleared.") - def test_api_key(self) -> None: - if self.talker.check_api_key(self.leKey.text().strip(), self.leURL.text().strip()): + def test_api_key(self, source_id) -> None: + # Find URL and API key + for tab in self.sources.items(): + for name, widget in tab[1]["widgets"].items(): + if tab[0] == source_id: + if name.endswith("api_key"): + key = widget.text().strip() + if name.endswith("url"): + url = widget.text().strip() + + if self.talkers[source_id].check_api_key(key, url): QtWidgets.QMessageBox.information(self, "API Key Test", "Key is valid!") else: - QtWidgets.QMessageBox.warning(self, "API Key Test", "Key is NOT valid.") + QtWidgets.QMessageBox.warning(self, "API Key Test", "Key is NOT valid!") def reset_settings(self) -> None: self.config = settngs.get_namespace(settngs.defaults(self.config[1])) diff --git a/comictaggerlib/taggerwindow.py b/comictaggerlib/taggerwindow.py index d51da19..db4deb6 100644 --- a/comictaggerlib/taggerwindow.py +++ b/comictaggerlib/taggerwindow.py @@ -1354,7 +1354,7 @@ class TaggerWindow(QtWidgets.QMainWindow): QtWidgets.QMessageBox.warning(self, self.tr("Web Link"), self.tr("Web Link is invalid.")) def show_settings(self) -> None: - settingswin = SettingsWindow(self, self.config, self.current_talker()) + settingswin = SettingsWindow(self, self.config, self.talkers) settingswin.setModal(True) settingswin.exec() settingswin.result() @@ -2047,7 +2047,7 @@ class TaggerWindow(QtWidgets.QMainWindow): if self.dirty_flag_verification( "File Rename", "If you rename files now, unsaved data in the form will be lost. Are you sure?" ): - dlg = RenameWindow(self, ca_list, self.load_data_style, self.config, self.current_talker()) + dlg = RenameWindow(self, ca_list, self.load_data_style, self.config, self.talkers) dlg.setModal(True) if dlg.exec() and self.comic_archive is not None: self.fileSelectionList.update_selected_rows() diff --git a/comictaggerlib/ui/settingswindow.ui b/comictaggerlib/ui/settingswindow.ui index 813c7a8..7607a7e 100644 --- a/comictaggerlib/ui/settingswindow.ui +++ b/comictaggerlib/ui/settingswindow.ui @@ -7,7 +7,7 @@ 0 0 702 - 513 + 559 @@ -28,7 +28,7 @@ - 0 + 3 @@ -308,75 +308,11 @@ - + - Comic Vine + Comic Sources - - - - - 0 - 0 - - - - - - - - - Use Series Start Date as Volume - - - - - - - Clear Form Before Importing Comic Vine data - - - - - - - Remove HTML tables from CV summary field - - - - - - - - 0 - 0 - - - - Qt::Horizontal - - - - - - - Initially sort Series search results by Starting Year instead of No. Issues - - - - - - - Initially show Series Name exact matches first - - - - - - - - @@ -391,100 +327,127 @@ - - - - 0 - 0 - + + + 0 - - - - - - 0 - 0 - - - - <html><head/><body><p>A personal API key from <a href="http://www.comicvine.com/api/"><span style=" text-decoration: underline; color:#0000ff;">Comic Vine</span></a> is recommended in order to search for tag data. Login (or create a new account) there to get your key, and enter it below.</p></body></html> - - - Qt::RichText - - - Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft - - - true - - - true - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse - - - - - - - Test Key - - - - - - - - 0 - 0 - - - - false - - - - - - - - 0 - 0 - - - - - 120 - 0 - - - - - 200 - 16777215 - - - - Comic Vine API Key - - - - - - - Comic Vine URL - - - - - - - + + + General + + + + + 10 + 70 + 650 + 22 + + + + Use Series Start Date as Volume + + + + + + 10 + 90 + 666 + 22 + + + + Clear Form Before Importing Comic Vine data + + + + + + 10 + 130 + 666 + 22 + + + + Initially sort Series search results by Starting Year instead of No. Issues + + + + + + 10 + 150 + 666 + 22 + + + + Initially show Series Name exact matches first + + + + + + 5 + 120 + 648 + 3 + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + + 190 + 13 + 301 + 32 + + + + + + + 15 + 13 + 171 + 32 + + + + Select Information Source: + + + + + + 5 + 60 + 648 + 3 + + + + Qt::Horizontal + + + + + + @@ -774,25 +737,16 @@ Find - - AlignCenter - Replacement - - AlignCenter - Strict Only - - AlignCenter - @@ -819,25 +773,16 @@ Find - - AlignCenter - Replacement - - AlignCenter - Strict Only - - AlignCenter - From 6a6a3320cba41a9f418021acba27b46311a3b6c3 Mon Sep 17 00:00:00 2001 From: Mizaki Date: Tue, 14 Feb 2023 01:32:56 +0000 Subject: [PATCH 02/15] Move talker settings menu generator to a separate file --- comictaggerlib/settingswindow.py | 122 ++------------ comictaggerlib/ui/talkeruigenerator.py | 213 +++++++++++++++++++++++++ comictalker/comictalker.py | 16 +- comictalker/talkers/comicvine.py | 28 +++- 4 files changed, 258 insertions(+), 121 deletions(-) create mode 100644 comictaggerlib/ui/talkeruigenerator.py diff --git a/comictaggerlib/settingswindow.py b/comictaggerlib/settingswindow.py index 53c599f..f44ddbf 100644 --- a/comictaggerlib/settingswindow.py +++ b/comictaggerlib/settingswindow.py @@ -15,7 +15,6 @@ # limitations under the License. from __future__ import annotations -import argparse import html import logging import os @@ -26,6 +25,7 @@ from typing import Any import settngs from PyQt5 import QtCore, QtGui, QtWidgets, uic +import comictaggerlib.ui.talkeruigenerator from comicapi import utils from comicapi.genericmetadata import md_test from comictaggerlib.ctversion import version @@ -190,6 +190,15 @@ class SettingsWindow(QtWidgets.QDialog): self.settings_to_form() self.rename_test() self.dir_test() + self.sources: dict = {} + self.sources = comictaggerlib.ui.talkeruigenerator.generate_source_option_tabs( + parent, self.tTalkerTabs, self.config, self.talkers + ) + # Select active source in dropdown + self.cobxInfoSource.setCurrentIndex(self.cobxInfoSource.findData(self.config[0].talker_source)) + + # Set General as start tab + self.tabWidget.setCurrentIndex(0) def connect_signals(self) -> None: self.btnBrowseRar.clicked.connect(self.select_rar) @@ -234,100 +243,6 @@ class SettingsWindow(QtWidgets.QDialog): self.twLiteralReplacements.cellChanged.disconnect() self.twValueReplacements.cellChanged.disconnect() - self.sources: dict = {} - self.generate_source_option_tabs() - - def generate_source_option_tabs(self) -> None: - def format_internal_name(int_name: str = "") -> str: - # Presume talker__ - int_name_split = int_name.split("_") - del int_name_split[0:3] - int_name_split[0] = int_name_split[0].capitalize() - new_name = " ".join(int_name_split) - return new_name - - # Add source sub tabs to Comic Sources tab - for talker_id, talker_obj in self.talkers.items(): - # Add source to general tab dropdown list - self.cobxInfoSource.addItem(talker_obj.name, talker_id) - - # Use a dict to make a var name from var - source_info = {} - tab_name = talker_id - source_info[tab_name] = {"tab": QtWidgets.QWidget(), "widgets": {}} - layout_grid = QtWidgets.QGridLayout() - row = 0 - - full_talker_name = "talker_" + talker_id - for option in self.config[1][full_talker_name][1].values(): - current_widget = None - if option.action is not None and isinstance(option.action, type(argparse.BooleanOptionalAction)): - # bool equals a checkbox (QCheckBox) - current_widget = QtWidgets.QCheckBox(format_internal_name(option.internal_name)) - # Set widget status - current_widget.setChecked(getattr(self.config[0], option.internal_name)) - # Add widget and span all columns - layout_grid.addWidget(current_widget, row, 0, 1, -1) - elif isinstance(option.type, type(int)): - # int equals a spinbox (QSpinBox) - lbl = QtWidgets.QLabel(option.internal_name) - # Create a label - layout_grid.addWidget(lbl, row, 0) - current_widget = QtWidgets.QSpinBox() - current_widget.setRange(0, 9999) - current_widget.setValue(getattr(self.config[0], option.internal_name)) - layout_grid.addWidget(current_widget, row, 1, alignment=QtCore.Qt.AlignLeft) - elif isinstance(option.type, type(float)): - # float equals a spinbox (QDoubleSpinBox) - lbl = QtWidgets.QLabel(format_internal_name(option.internal_name)) - # Create a label - layout_grid.addWidget(lbl, row, 0) - current_widget = QtWidgets.QDoubleSpinBox() - current_widget.setRange(0, 9999.99) - current_widget.setValue(getattr(self.config[0], option.internal_name)) - layout_grid.addWidget(current_widget, row, 1, alignment=QtCore.Qt.AlignLeft) - # type of None should be string - elif option.type is None or isinstance(option.type, type(str)): - # str equals a text field (QLineEdit) - lbl = QtWidgets.QLabel(format_internal_name(option.internal_name)) - # Create a label - layout_grid.addWidget(lbl, row, 0) - current_widget = QtWidgets.QLineEdit() - # Set widget status - current_widget.setText(getattr(self.config[0], option.internal_name)) - layout_grid.addWidget(current_widget, row, 1) - # Special case for api_key, make a test button - if option.internal_name.endswith("api_key"): - btn = QtWidgets.QPushButton("Test Key") - layout_grid.addWidget(btn, row, 2) - btn.clicked.connect(lambda state, sn=talker_id: self.test_api_key(sn)) - row += 1 - - if current_widget: - # Add tooltip text - current_widget.setToolTip(option.help) - - source_info[tab_name]["widgets"][option.internal_name] = current_widget - else: - # An empty current_widget implies an unsupported type - logger.info(f"Unsupported talker option found. Name: {option.internal_name} Type: {option.type}") - - # Add vertical spacer - vspacer = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - layout_grid.addItem(vspacer, row, 0) - # Display the new widgets - source_info[tab_name]["tab"].setLayout(layout_grid) - - # Add new sub tab to Comic Source tab - self.tTalkerTabs.addTab(source_info[tab_name]["tab"], talker_obj.name) - self.sources.update(source_info) - - # Select active source in dropdown - self.cobxInfoSource.setCurrentIndex(self.cobxInfoSource.findData(self.config[0].talker_source)) - - # Set General as start tab - self.tabWidget.setCurrentIndex(0) - def addLiteralReplacement(self) -> None: self.insertRow(self.twLiteralReplacements, self.twLiteralReplacements.rowCount(), Replacement("", "", False)) @@ -542,7 +457,7 @@ class SettingsWindow(QtWidgets.QDialog): self.config[0].rename_strict = self.cbxRenameStrict.isChecked() self.config[0].rename_replacements = self.get_replacements() - # Read settings from sources tabs and generate self.settings.config data + # Read settings from sources tabs and generate self.config data for tab in self.sources.items(): for name, widget in tab[1]["widgets"].items(): widget_value = None @@ -574,21 +489,6 @@ class SettingsWindow(QtWidgets.QDialog): ComicCacher(self.config[0].runtime_config.user_cache_dir, version).clear_cache() QtWidgets.QMessageBox.information(self, self.name, "Cache has been cleared.") - def test_api_key(self, source_id) -> None: - # Find URL and API key - for tab in self.sources.items(): - for name, widget in tab[1]["widgets"].items(): - if tab[0] == source_id: - if name.endswith("api_key"): - key = widget.text().strip() - if name.endswith("url"): - url = widget.text().strip() - - if self.talkers[source_id].check_api_key(key, url): - QtWidgets.QMessageBox.information(self, "API Key Test", "Key is valid!") - else: - QtWidgets.QMessageBox.warning(self, "API Key Test", "Key is NOT valid!") - def reset_settings(self) -> None: self.config = settngs.get_namespace(settngs.defaults(self.config[1])) self.settings_to_form() diff --git a/comictaggerlib/ui/talkeruigenerator.py b/comictaggerlib/ui/talkeruigenerator.py new file mode 100644 index 0000000..306df1e --- /dev/null +++ b/comictaggerlib/ui/talkeruigenerator.py @@ -0,0 +1,213 @@ +from __future__ import annotations + +import argparse +import logging + +import settngs +from PyQt5 import QtCore, QtWidgets + +from comictalker.comictalker import ComicTalker + +logger = logging.getLogger(__name__) + + +def call_check_api_key( + talker_id: str, + sources_info: dict[str, QtWidgets.QWidget], + talkers: dict[str, ComicTalker], + parent: QtWidgets.QWidget, +): + key = "" + # Find the correct widget to get the API key + for name, widget in sources_info[talker_id]["widgets"].items(): + if name.startswith("talker_" + talker_id) and name.endswith("api_key"): + key = widget.text().strip() + + if talkers[talker_id].check_api_key(key): + QtWidgets.QMessageBox.information(parent, "API Key Test", "Key is valid!") + else: + QtWidgets.QMessageBox.warning(parent, "API Key Test", "Key is NOT valid!") + + +def call_check_api_url( + talker_id: str, + sources_info: dict[str, QtWidgets.QWidget], + talkers: dict[str, ComicTalker], + parent: QtWidgets.QWidget, +): + url = "" + # Find the correct widget to get the URL key + for name, widget in sources_info[talker_id]["widgets"].items(): + if name.startswith("talker_" + talker_id) and name.endswith("url"): + url = widget.text().strip() + + if talkers[talker_id].check_api_url(url): + QtWidgets.QMessageBox.information(parent, "API Key Test", "URL is valid!") + else: + QtWidgets.QMessageBox.warning(parent, "API Key Test", "URL is NOT valid!") + + +def test_api_key( + btn: QtWidgets.QPushButton, + talker_id: str, + sources_info: dict[str, QtWidgets.QWidget], + talkers: dict[str, ComicTalker], + parent: QtWidgets.QWidget, +) -> None: + btn.clicked.connect(lambda: call_check_api_key(talker_id, sources_info, talkers, parent)) + + +def test_api_url( + btn: QtWidgets.QPushButton, + talker_id: str, + sources_info: dict[str, QtWidgets.QWidget], + talkers: dict[str, ComicTalker], + parent: QtWidgets.QWidget, +) -> None: + btn.clicked.connect(lambda: call_check_api_url(talker_id, sources_info, talkers, parent)) + + +def format_internal_name(int_name: str = "") -> str: + # Presume talker__: talker_comicvine_cv_widget_name + int_name_split = int_name.split("_") + del int_name_split[0:3] + int_name_split[0] = int_name_split[0].capitalize() + new_name = " ".join(int_name_split) + return new_name + + +def generate_checkbox( + option: settngs.Setting, value: bool, layout: QtWidgets.QGridLayout +) -> tuple[QtWidgets.QGridLayout, QtWidgets.QCheckBox]: + # bool equals a checkbox (QCheckBox) + widget = QtWidgets.QCheckBox(format_internal_name(option.internal_name)) + # Set widget status + widget.setChecked(value) + # Add tooltip text + widget.setToolTip(option.help) + # Add widget and span all columns + layout.addWidget(widget, layout.rowCount() + 1, 0, 1, -1) + + return layout, widget + + +def generate_spinbox( + option: settngs.Setting, value: int | float, layout: QtWidgets.QGridLayout +) -> tuple[QtWidgets.QGridLayout, QtWidgets.QSpinBox | QtWidgets.QDoubleSpinBox]: + if isinstance(value, int): + # int equals a spinbox (QSpinBox) + lbl = QtWidgets.QLabel(option.internal_name) + # Create a label + layout.addWidget(lbl, layout.rowCount() + 1, 0) + widget = QtWidgets.QSpinBox() + widget.setRange(0, 9999) + widget.setValue(value) + widget.setToolTip(option.help) + layout.addWidget(widget, layout.rowCount() - 1, 1, alignment=QtCore.Qt.AlignLeft) + + if isinstance(value, float): + # float equals a spinbox (QDoubleSpinBox) + lbl = QtWidgets.QLabel(format_internal_name(option.internal_name)) + # Create a label + layout.addWidget(lbl, layout.rowCount() + 1, 0) + widget = QtWidgets.QDoubleSpinBox() + widget.setRange(0, 9999.99) + widget.setValue(value) + widget.setToolTip(option.help) + layout.addWidget(widget, layout.rowCount() - 1, 1, alignment=QtCore.Qt.AlignLeft) + + return layout, widget + + +def generate_textbox( + option: settngs.Setting, value: str, layout: QtWidgets.QGridLayout +) -> tuple[QtWidgets.QGridLayout, QtWidgets.QLineEdit, QtWidgets.QPushButton]: + btn = None + # str equals a text field (QLineEdit) + lbl = QtWidgets.QLabel(format_internal_name(option.internal_name)) + # Create a label + layout.addWidget(lbl, layout.rowCount() + 1, 0) + widget = QtWidgets.QLineEdit() + widget.setObjectName(option.internal_name) + # Set widget status + widget.setText(value) + widget.setToolTip(option.help) + layout.addWidget(widget, layout.rowCount() - 1, 1) + # Special case for api_key, make a test button + if option.internal_name.endswith("api_key"): + btn = QtWidgets.QPushButton("Test Key") + layout.addWidget(btn, layout.rowCount() - 1, 2) + + if option.internal_name.endswith("url"): + btn = QtWidgets.QPushButton("Test URL") + layout.addWidget(btn, layout.rowCount() - 1, 2) + + return layout, widget, btn + + +def generate_source_option_tabs( + parent: QtWidgets.QWidget, + tabs: QtWidgets.QTabWidget, + config: settngs.Config[settngs.Namespace], + talkers: dict[str, ComicTalker], +) -> dict[str, QtWidgets.QWidget]: + """ + Generate GUI tabs and settings for talkers + """ + + sources: dict = {} + + # Add source sub tabs to Comic Sources tab + for talker_id, talker_obj in talkers.items(): + # Add source to general tab dropdown list + tabs.findChildren(QtWidgets.QComboBox, "cobxInfoSource")[0].addItem(talker_obj.name, talker_id) + + # Use a dict to make a var name from var + source_info = {} + tab_name = talker_id + source_info[tab_name] = {"tab": QtWidgets.QWidget(), "widgets": {}} + layout_grid = QtWidgets.QGridLayout() + + for option in config[1][f"talker_{talker_id}"][1].values(): + current_widget = None + if option.action is not None and ( + isinstance(option.action, type(argparse.BooleanOptionalAction)) + or option.action == "store_true" + or option.action == "store_false" + ): + layout_grid, current_widget = generate_checkbox( + option, getattr(config[0], option.internal_name), layout_grid + ) + source_info[tab_name]["widgets"][option.internal_name] = current_widget + elif isinstance(option.type, type(int)) or isinstance(option.type, type(float)): + layout_grid, current_widget = generate_spinbox( + option, getattr(config[0], option.internal_name), layout_grid + ) + source_info[tab_name]["widgets"][option.internal_name] = current_widget + # option.type of None should be string + elif option.type is None or isinstance(option.type, type(str)): + layout_grid, current_widget, btn = generate_textbox( + option, getattr(config[0], option.internal_name), layout_grid + ) + source_info[tab_name]["widgets"][option.internal_name] = current_widget + + if option.internal_name.endswith("key"): + # Attach test api function to button. A better way? + test_api_key(btn, talker_id, source_info, talkers, parent) + if option.internal_name.endswith("url"): + # Attach test api function to button. A better way? + test_api_url(btn, talker_id, source_info, talkers, parent) + else: + logger.debug(f"Unsupported talker option found. Name: {option.internal_name} Type: {option.type}") + + # Add vertical spacer + vspacer = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + layout_grid.addItem(vspacer, layout_grid.rowCount() + 1, 0) + # Display the new widgets + source_info[tab_name]["tab"].setLayout(layout_grid) + + # Add new sub tab to Comic Source tab + tabs.addTab(source_info[tab_name]["tab"], talker_obj.name) + sources.update(source_info) + + return sources diff --git a/comictalker/comictalker.py b/comictalker/comictalker.py index 88c078d..9f32c1f 100644 --- a/comictalker/comictalker.py +++ b/comictalker/comictalker.py @@ -118,7 +118,10 @@ class ComicTalker: self.api_url: str = "" def register_settings(self, parser: settngs.Manager) -> None: - """Allows registering settings using the settngs package with an argparse like interface""" + """ + Allows registering settings using the settngs package with an argparse like interface + NOTE: The order used will be reflected within the settings menu + """ return None def parse_settings(self, settings: dict[str, Any]) -> dict[str, Any]: @@ -128,10 +131,15 @@ class ComicTalker: """ return settings - def check_api_key(self, key: str, url: str) -> bool: + def check_api_key(self, key: str) -> bool: """ - This function should return true if the given api key and url are valid. - If the Talker does not use an api key it should validate that the url works. + This function should return true if the given api key is valid. + """ + raise NotImplementedError + + def check_api_url(self, url: str) -> bool: + """ + This function should return true if the given url is valid. """ raise NotImplementedError diff --git a/comictalker/talkers/comicvine.py b/comictalker/talkers/comicvine.py index f3d4d29..fa9dc66 100644 --- a/comictalker/talkers/comicvine.py +++ b/comictalker/talkers/comicvine.py @@ -181,8 +181,6 @@ class ComicVineTalker(ComicTalker): self.wait_on_ratelimit_time: int = 20 def register_settings(self, parser: settngs.Manager) -> None: - parser.add_setting("--cv-api-key", help="Use the given Comic Vine API Key.") - parser.add_setting("--cv-url", help="Use the given Comic Vine URL.") parser.add_setting("--cv-use-series-start-as-volume", default=False, action=argparse.BooleanOptionalAction) parser.add_setting("--cv-wait-on-ratelimit", default=False, action=argparse.BooleanOptionalAction) parser.add_setting( @@ -191,6 +189,8 @@ class ComicVineTalker(ComicTalker): action=argparse.BooleanOptionalAction, help="Removes html tables instead of converting them to text.", ) + parser.add_setting("--cv-api-key", help="Use the given Comic Vine API Key.") + parser.add_setting("--cv-url", help="Use the given Comic Vine URL.") def parse_settings(self, settings: dict[str, Any]) -> dict[str, Any]: if settings["cv_api_key"]: @@ -208,7 +208,23 @@ class ComicVineTalker(ComicTalker): self.remove_html_tables = settings["cv_remove_html_tables"] return settngs - def check_api_key(self, key: str, url: str) -> bool: + def check_api_key(self, key: str) -> bool: + url = self.api_url + try: + test_url = urljoin(url, "issue/1/") + + cv_response: CVResult = requests.get( + test_url, + headers={"user-agent": "comictagger/" + self.version}, + params={"api_key": key, "format": "json", "field_list": "name"}, + ).json() + + # Bogus request, but if the key is wrong, you get error 100: "Invalid API Key" + return cv_response["status_code"] != 100 + except Exception: + return False + + def check_api_url(self, url: str) -> bool: if not url: url = self.api_url try: @@ -221,11 +237,11 @@ class ComicVineTalker(ComicTalker): cv_response: CVResult = requests.get( test_url, headers={"user-agent": "comictagger/" + self.version}, - params={"api_key": key, "format": "json", "field_list": "name"}, + params={"api_key": self.api_key, "format": "json", "field_list": "name"}, ).json() - # Bogus request, but if the key is wrong, you get error 100: "Invalid API Key" - return cv_response["status_code"] != 100 + # Bogus request, but if the url is correct, you get error 102: "Error in URL Format" + return cv_response["status_code"] == 102 except Exception: return False From 2fde11a704ca4c2c91770771f42a77188ecc1543 Mon Sep 17 00:00:00 2001 From: Mizaki Date: Tue, 14 Feb 2023 01:47:32 +0000 Subject: [PATCH 03/15] Test for menu generator format_internal_name --- tests/talker_test.py | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 tests/talker_test.py diff --git a/tests/talker_test.py b/tests/talker_test.py new file mode 100644 index 0000000..c7ced2c --- /dev/null +++ b/tests/talker_test.py @@ -0,0 +1,7 @@ +from __future__ import annotations + +import comictaggerlib.ui.talkeruigenerator + + +def test_format_internal_name(): + assert comictaggerlib.ui.talkeruigenerator.format_internal_name("talker_comicvine_cv_test_name") == "Test name" From 02fd8beda8a70fe99970da7a73eba599840d180d Mon Sep 17 00:00:00 2001 From: Mizaki Date: Sat, 18 Feb 2023 01:15:46 +0000 Subject: [PATCH 04/15] Use None as parent for api and url message boxes Rename test_api_key and test_api_url to api_key_btn_connect and api_url_btn_connect Make separate function to set form values, called in settings_to_form Change isinstance to is Call findChildren only once --- comictaggerlib/settingswindow.py | 16 +++--- comictaggerlib/ui/talkeruigenerator.py | 69 ++++++++++++++------------ 2 files changed, 46 insertions(+), 39 deletions(-) diff --git a/comictaggerlib/settingswindow.py b/comictaggerlib/settingswindow.py index f44ddbf..40b288a 100644 --- a/comictaggerlib/settingswindow.py +++ b/comictaggerlib/settingswindow.py @@ -186,16 +186,13 @@ class SettingsWindow(QtWidgets.QDialog): self.leRenameTemplate.setToolTip(f"
{html.escape(template_tooltip)}
") self.rename_error: Exception | None = None + self.sources: dict = comictaggerlib.ui.talkeruigenerator.generate_source_option_tabs( + self.tTalkerTabs, self.config, self.talkers + ) self.connect_signals() self.settings_to_form() self.rename_test() self.dir_test() - self.sources: dict = {} - self.sources = comictaggerlib.ui.talkeruigenerator.generate_source_option_tabs( - parent, self.tTalkerTabs, self.config, self.talkers - ) - # Select active source in dropdown - self.cobxInfoSource.setCurrentIndex(self.cobxInfoSource.findData(self.config[0].talker_source)) # Set General as start tab self.tabWidget.setCurrentIndex(0) @@ -351,6 +348,13 @@ class SettingsWindow(QtWidgets.QDialog): table.removeRow(i) for row, replacement in enumerate(replacments): self.insertRow(table, row, replacement) + + # Set talker values + comictaggerlib.ui.talkeruigenerator.settings_to_talker_form(self.sources, self.config) + + # Select active source in dropdown + self.cobxInfoSource.setCurrentIndex(self.cobxInfoSource.findData(self.config[0].talker_source)) + self.connect_signals() def get_replacements(self) -> Replacements: diff --git a/comictaggerlib/ui/talkeruigenerator.py b/comictaggerlib/ui/talkeruigenerator.py index 306df1e..edc9e4b 100644 --- a/comictaggerlib/ui/talkeruigenerator.py +++ b/comictaggerlib/ui/talkeruigenerator.py @@ -15,7 +15,6 @@ def call_check_api_key( talker_id: str, sources_info: dict[str, QtWidgets.QWidget], talkers: dict[str, ComicTalker], - parent: QtWidgets.QWidget, ): key = "" # Find the correct widget to get the API key @@ -24,16 +23,15 @@ def call_check_api_key( key = widget.text().strip() if talkers[talker_id].check_api_key(key): - QtWidgets.QMessageBox.information(parent, "API Key Test", "Key is valid!") + QtWidgets.QMessageBox.information(None, "API Key Test", "Key is valid!") else: - QtWidgets.QMessageBox.warning(parent, "API Key Test", "Key is NOT valid!") + QtWidgets.QMessageBox.warning(None, "API Key Test", "Key is NOT valid!") def call_check_api_url( talker_id: str, sources_info: dict[str, QtWidgets.QWidget], talkers: dict[str, ComicTalker], - parent: QtWidgets.QWidget, ): url = "" # Find the correct widget to get the URL key @@ -42,29 +40,27 @@ def call_check_api_url( url = widget.text().strip() if talkers[talker_id].check_api_url(url): - QtWidgets.QMessageBox.information(parent, "API Key Test", "URL is valid!") + QtWidgets.QMessageBox.information(None, "API Key Test", "URL is valid!") else: - QtWidgets.QMessageBox.warning(parent, "API Key Test", "URL is NOT valid!") + QtWidgets.QMessageBox.warning(None, "API Key Test", "URL is NOT valid!") -def test_api_key( +def api_key_btn_connect( btn: QtWidgets.QPushButton, talker_id: str, sources_info: dict[str, QtWidgets.QWidget], talkers: dict[str, ComicTalker], - parent: QtWidgets.QWidget, ) -> None: - btn.clicked.connect(lambda: call_check_api_key(talker_id, sources_info, talkers, parent)) + btn.clicked.connect(lambda: call_check_api_key(talker_id, sources_info, talkers)) -def test_api_url( +def api_url_btn_connect( btn: QtWidgets.QPushButton, talker_id: str, sources_info: dict[str, QtWidgets.QWidget], talkers: dict[str, ComicTalker], - parent: QtWidgets.QWidget, ) -> None: - btn.clicked.connect(lambda: call_check_api_url(talker_id, sources_info, talkers, parent)) + btn.clicked.connect(lambda: call_check_api_url(talker_id, sources_info, talkers)) def format_internal_name(int_name: str = "") -> str: @@ -77,12 +73,10 @@ def format_internal_name(int_name: str = "") -> str: def generate_checkbox( - option: settngs.Setting, value: bool, layout: QtWidgets.QGridLayout + option: settngs.Setting, layout: QtWidgets.QGridLayout ) -> tuple[QtWidgets.QGridLayout, QtWidgets.QCheckBox]: # bool equals a checkbox (QCheckBox) widget = QtWidgets.QCheckBox(format_internal_name(option.internal_name)) - # Set widget status - widget.setChecked(value) # Add tooltip text widget.setToolTip(option.help) # Add widget and span all columns @@ -101,7 +95,6 @@ def generate_spinbox( layout.addWidget(lbl, layout.rowCount() + 1, 0) widget = QtWidgets.QSpinBox() widget.setRange(0, 9999) - widget.setValue(value) widget.setToolTip(option.help) layout.addWidget(widget, layout.rowCount() - 1, 1, alignment=QtCore.Qt.AlignLeft) @@ -112,7 +105,6 @@ def generate_spinbox( layout.addWidget(lbl, layout.rowCount() + 1, 0) widget = QtWidgets.QDoubleSpinBox() widget.setRange(0, 9999.99) - widget.setValue(value) widget.setToolTip(option.help) layout.addWidget(widget, layout.rowCount() - 1, 1, alignment=QtCore.Qt.AlignLeft) @@ -120,7 +112,7 @@ def generate_spinbox( def generate_textbox( - option: settngs.Setting, value: str, layout: QtWidgets.QGridLayout + option: settngs.Setting, layout: QtWidgets.QGridLayout ) -> tuple[QtWidgets.QGridLayout, QtWidgets.QLineEdit, QtWidgets.QPushButton]: btn = None # str equals a text field (QLineEdit) @@ -129,8 +121,6 @@ def generate_textbox( layout.addWidget(lbl, layout.rowCount() + 1, 0) widget = QtWidgets.QLineEdit() widget.setObjectName(option.internal_name) - # Set widget status - widget.setText(value) widget.setToolTip(option.help) layout.addWidget(widget, layout.rowCount() - 1, 1) # Special case for api_key, make a test button @@ -145,8 +135,23 @@ def generate_textbox( return layout, widget, btn +def settings_to_talker_form(sources: dict[str, QtWidgets.QWidget], config: settngs.Config[settngs.Namespace]): + for talker in sources.items(): + for name, widget in talker[1]["widgets"].items(): + value = getattr(config[0], name) + value_type = type(value) + try: + if value_type is str: + widget.setText(value) + if value_type is int or value_type is float: + widget.setValue(value) + if value_type is bool: + widget.setChecked(value) + except Exception: + logger.debug("Failed to set value of %s", name) + + def generate_source_option_tabs( - parent: QtWidgets.QWidget, tabs: QtWidgets.QTabWidget, config: settngs.Config[settngs.Namespace], talkers: dict[str, ComicTalker], @@ -156,11 +161,12 @@ def generate_source_option_tabs( """ sources: dict = {} + cobxInfoSource = tabs.findChildren(QtWidgets.QComboBox, "cobxInfoSource")[0] # Add source sub tabs to Comic Sources tab for talker_id, talker_obj in talkers.items(): # Add source to general tab dropdown list - tabs.findChildren(QtWidgets.QComboBox, "cobxInfoSource")[0].addItem(talker_obj.name, talker_id) + cobxInfoSource.addItem(talker_obj.name, talker_id) # Use a dict to make a var name from var source_info = {} @@ -171,32 +177,29 @@ def generate_source_option_tabs( for option in config[1][f"talker_{talker_id}"][1].values(): current_widget = None if option.action is not None and ( - isinstance(option.action, type(argparse.BooleanOptionalAction)) + option.action is argparse.BooleanOptionalAction + or option.action is bool or option.action == "store_true" or option.action == "store_false" ): - layout_grid, current_widget = generate_checkbox( - option, getattr(config[0], option.internal_name), layout_grid - ) + layout_grid, current_widget = generate_checkbox(option, layout_grid) source_info[tab_name]["widgets"][option.internal_name] = current_widget - elif isinstance(option.type, type(int)) or isinstance(option.type, type(float)): + elif option.type is int or option.type is float: layout_grid, current_widget = generate_spinbox( option, getattr(config[0], option.internal_name), layout_grid ) source_info[tab_name]["widgets"][option.internal_name] = current_widget # option.type of None should be string - elif option.type is None or isinstance(option.type, type(str)): - layout_grid, current_widget, btn = generate_textbox( - option, getattr(config[0], option.internal_name), layout_grid - ) + elif option.type is None or option.type is str: + layout_grid, current_widget, btn = generate_textbox(option, layout_grid) source_info[tab_name]["widgets"][option.internal_name] = current_widget if option.internal_name.endswith("key"): # Attach test api function to button. A better way? - test_api_key(btn, talker_id, source_info, talkers, parent) + api_key_btn_connect(btn, talker_id, source_info, talkers) if option.internal_name.endswith("url"): # Attach test api function to button. A better way? - test_api_url(btn, talker_id, source_info, talkers, parent) + api_url_btn_connect(btn, talker_id, source_info, talkers) else: logger.debug(f"Unsupported talker option found. Name: {option.internal_name} Type: {option.type}") From a24bd1c7196d674644780a6bf623849c7bc4e71e Mon Sep 17 00:00:00 2001 From: Mizaki Date: Sat, 18 Feb 2023 17:16:56 +0000 Subject: [PATCH 05/15] Generate talker general tab programatically. Move search options to search tab. --- comictaggerlib/ctsettings/file.py | 74 +++++----- comictaggerlib/settingswindow.py | 28 ++-- comictaggerlib/ui/settingswindow.ui | 197 ++++++------------------- comictaggerlib/ui/talkeruigenerator.py | 27 +++- 4 files changed, 114 insertions(+), 212 deletions(-) diff --git a/comictaggerlib/ctsettings/file.py b/comictaggerlib/ctsettings/file.py index 3d780f5..7fd8140 100644 --- a/comictaggerlib/ctsettings/file.py +++ b/comictaggerlib/ctsettings/file.py @@ -46,6 +46,43 @@ def identifier(parser: settngs.Manager) -> None: action=AppendAction, help="When enabled filters the listed publishers from all search results", ) + parser.add_setting("--series-match-search-thresh", default=90, type=int) + parser.add_setting( + "--clear-metadata", + default=True, + help="Clears all existing metadata during import, default is to merges metadata.\nMay be used in conjunction with -o, -f and -m.\n\n", + dest="clear_metadata_on_import", + action=argparse.BooleanOptionalAction, + ) + parser.add_setting( + "-a", + "--auto-imprint", + action=argparse.BooleanOptionalAction, + default=False, + help="Enables the auto imprint functionality.\ne.g. if the publisher is set to 'vertigo' it\nwill be updated to 'DC Comics' and the imprint\nproperty will be set to 'Vertigo'.\n\n", + ) + + parser.add_setting( + "--sort-series-by-year", default=True, action=argparse.BooleanOptionalAction, help="Sorts series by year" + ) + parser.add_setting( + "--exact-series-matches-first", + default=True, + action=argparse.BooleanOptionalAction, + help="Puts series that are an exact match at the top of the list", + ) + parser.add_setting( + "--always-use-publisher-filter", + default=False, + action=argparse.BooleanOptionalAction, + help="Enables the publisher filter", + ) + parser.add_setting( + "--clear-form-before-populating", + default=False, + action=argparse.BooleanOptionalAction, + help="Clears all existing metadata when applying metadata from comic source", + ) def dialog(parser: settngs.Manager) -> None: @@ -86,43 +123,6 @@ def filename(parser: settngs.Manager) -> None: def talker(parser: settngs.Manager) -> None: # General settings for talkers parser.add_setting("--source", default="comicvine", help="Use a specified source by source ID") - parser.add_setting("--series-match-search-thresh", default=90, type=int) - parser.add_setting( - "--clear-metadata", - default=True, - help="Clears all existing metadata during import, default is to merges metadata.\nMay be used in conjunction with -o, -f and -m.\n\n", - dest="clear_metadata_on_import", - action=argparse.BooleanOptionalAction, - ) - parser.add_setting( - "-a", - "--auto-imprint", - action=argparse.BooleanOptionalAction, - default=False, - help="Enables the auto imprint functionality.\ne.g. if the publisher is set to 'vertigo' it\nwill be updated to 'DC Comics' and the imprint\nproperty will be set to 'Vertigo'.\n\n", - ) - - parser.add_setting( - "--sort-series-by-year", default=True, action=argparse.BooleanOptionalAction, help="Sorts series by year" - ) - parser.add_setting( - "--exact-series-matches-first", - default=True, - action=argparse.BooleanOptionalAction, - help="Puts series that are an exact match at the top of the list", - ) - parser.add_setting( - "--always-use-publisher-filter", - default=False, - action=argparse.BooleanOptionalAction, - help="Enables the publisher filter", - ) - parser.add_setting( - "--clear-form-before-populating", - default=False, - action=argparse.BooleanOptionalAction, - help="Clears all existing metadata when applying metadata from comic source", - ) def cbl(parser: settngs.Manager) -> None: diff --git a/comictaggerlib/settingswindow.py b/comictaggerlib/settingswindow.py index 40b288a..a5558cf 100644 --- a/comictaggerlib/settingswindow.py +++ b/comictaggerlib/settingswindow.py @@ -306,7 +306,7 @@ class SettingsWindow(QtWidgets.QDialog): else: self.leRarExePath.setEnabled(False) self.sbNameMatchIdentifyThresh.setValue(self.config[0].identifier_series_match_identify_thresh) - self.sbNameMatchSearchThresh.setValue(self.config[0].talker_series_match_search_thresh) + self.sbNameMatchSearchThresh.setValue(self.config[0].identifier_series_match_search_thresh) self.tePublisherFilter.setPlainText("\n".join(self.config[0].identifier_publisher_filter)) self.cbxCheckForNewVersion.setChecked(self.config[0].general_check_for_new_version) @@ -317,10 +317,10 @@ class SettingsWindow(QtWidgets.QDialog): self.cbxRemovePublisher.setChecked(self.config[0].filename_remove_publisher) self.switch_parser() - self.cbxClearFormBeforePopulating.setChecked(self.config[0].talker_clear_form_before_populating) - self.cbxUseFilter.setChecked(self.config[0].talker_always_use_publisher_filter) - self.cbxSortByYear.setChecked(self.config[0].talker_sort_series_by_year) - self.cbxExactMatches.setChecked(self.config[0].talker_exact_series_matches_first) + self.cbxClearFormBeforePopulating.setChecked(self.config[0].identifier_clear_form_before_populating) + self.cbxUseFilter.setChecked(self.config[0].identifier_always_use_publisher_filter) + self.cbxSortByYear.setChecked(self.config[0].identifier_sort_series_by_year) + self.cbxExactMatches.setChecked(self.config[0].identifier_exact_series_matches_first) self.cbxAssumeLoneCreditIsPrimary.setChecked(self.config[0].cbl_assume_lone_credit_is_primary) self.cbxCopyCharactersToTags.setChecked(self.config[0].cbl_copy_characters_to_tags) @@ -352,9 +352,6 @@ class SettingsWindow(QtWidgets.QDialog): # Set talker values comictaggerlib.ui.talkeruigenerator.settings_to_talker_form(self.sources, self.config) - # Select active source in dropdown - self.cobxInfoSource.setCurrentIndex(self.cobxInfoSource.findData(self.config[0].talker_source)) - self.connect_signals() def get_replacements(self) -> Replacements: @@ -424,7 +421,7 @@ class SettingsWindow(QtWidgets.QDialog): self.config[0].general_check_for_new_version = self.cbxCheckForNewVersion.isChecked() self.config[0].identifier_series_match_identify_thresh = self.sbNameMatchIdentifyThresh.value() - self.config[0].talker_series_match_search_thresh = self.sbNameMatchSearchThresh.value() + self.config[0].identifier_series_match_search_thresh = self.sbNameMatchSearchThresh.value() self.config[0].identifier_publisher_filter = [ x.strip() for x in str(self.tePublisherFilter.toPlainText()).splitlines() if x.strip() ] @@ -434,12 +431,10 @@ class SettingsWindow(QtWidgets.QDialog): self.config[0].filename_remove_fcbd = self.cbxRemoveFCBD.isChecked() self.config[0].filename_remove_publisher = self.cbxRemovePublisher.isChecked() - self.config[0].talker_clear_form_before_populating = self.cbxClearFormBeforePopulating.isChecked() - self.config[0].talker_always_use_publisher_filter = self.cbxUseFilter.isChecked() - self.config[0].talker_sort_series_by_year = self.cbxSortByYear.isChecked() - self.config[0].talker_exact_series_matches_first = self.cbxExactMatches.isChecked() - - self.config[0].talker_source = str(self.cobxInfoSource.itemData(self.cobxInfoSource.currentIndex())) + self.config[0].identifier_clear_form_before_populating = self.cbxClearFormBeforePopulating.isChecked() + self.config[0].identifier_always_use_publisher_filter = self.cbxUseFilter.isChecked() + self.config[0].identifier_sort_series_by_year = self.cbxSortByYear.isChecked() + self.config[0].identifier_exact_series_matches_first = self.cbxExactMatches.isChecked() self.config[0].cbl_assume_lone_credit_is_primary = self.cbxAssumeLoneCreditIsPrimary.isChecked() self.config[0].cbl_copy_characters_to_tags = self.cbxCopyCharactersToTags.isChecked() @@ -471,6 +466,9 @@ class SettingsWindow(QtWidgets.QDialog): widget_value = widget.text().strip() elif isinstance(widget, QtWidgets.QCheckBox): widget_value = widget.isChecked() + # The talker source dropdown + elif isinstance(widget, QtWidgets.QComboBox): + widget_value = widget.itemData(widget.currentIndex()) setattr(self.config[0], name, widget_value) diff --git a/comictaggerlib/ui/settingswindow.ui b/comictaggerlib/ui/settingswindow.ui index 7607a7e..c1485c9 100644 --- a/comictaggerlib/ui/settingswindow.ui +++ b/comictaggerlib/ui/settingswindow.ui @@ -136,24 +136,14 @@ Searching - - - - <html><head/><body><p>These settings are for the automatic issue identifier which searches online for matches. </p><p>Hover the mouse over an entry field for more info.</p></body></html> - - - true - - - - - + + Qt::Horizontal - + QFormLayout::AllNonFixedFieldsGrow @@ -252,6 +242,44 @@ + + + + <html><head/><body><p>These settings are for the automatic issue identifier which searches online for matches. </p><p>Hover the mouse over an entry field for more info.</p></body></html> + + + true + + + + + + + Qt::Horizontal + + + + + + + Initially show Series Name exact matches first + + + + + + + Initially sort Series search results by Starting Year instead of No. Issues + + + + + + + Clear Form Before Importing Comic Vine data + + + @@ -313,154 +341,13 @@ Comic Sources - - - - - 0 - 0 - - - - Qt::Horizontal - - - - 0 + -1 - - - General - - - - - 10 - 70 - 650 - 22 - - - - Use Series Start Date as Volume - - - - - - 10 - 90 - 666 - 22 - - - - Clear Form Before Importing Comic Vine data - - - - - - 10 - 130 - 666 - 22 - - - - Initially sort Series search results by Starting Year instead of No. Issues - - - - - - 10 - 150 - 666 - 22 - - - - Initially show Series Name exact matches first - - - - - - 5 - 120 - 648 - 3 - - - - - 0 - 0 - - - - Qt::Horizontal - - - - - - 190 - 13 - 301 - 32 - - - - - - - 15 - 13 - 171 - 32 - - - - Select Information Source: - - - - - - 5 - 60 - 648 - 3 - - - - Qt::Horizontal - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - diff --git a/comictaggerlib/ui/talkeruigenerator.py b/comictaggerlib/ui/talkeruigenerator.py index edc9e4b..3a5c236 100644 --- a/comictaggerlib/ui/talkeruigenerator.py +++ b/comictaggerlib/ui/talkeruigenerator.py @@ -142,7 +142,11 @@ def settings_to_talker_form(sources: dict[str, QtWidgets.QWidget], config: settn value_type = type(value) try: if value_type is str: - widget.setText(value) + # Special case for general dropdown box + if name == "talker_source": + widget.setCurrentIndex(widget.findData(config[0].talker_source)) + else: + widget.setText(value) if value_type is int or value_type is float: widget.setValue(value) if value_type is bool: @@ -161,15 +165,28 @@ def generate_source_option_tabs( """ sources: dict = {} - cobxInfoSource = tabs.findChildren(QtWidgets.QComboBox, "cobxInfoSource")[0] + # Use a dict to make a var name from var + source_info = {} + + # Add General tab + source_info["general"] = {"tab": QtWidgets.QWidget(), "widgets": {}} + general_layout = QtWidgets.QGridLayout() + lbl_info = QtWidgets.QLabel("Information Source:") + cbx_info = QtWidgets.QComboBox() + general_layout.addWidget(lbl_info, 0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) + general_layout.addWidget(cbx_info, 0, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) + vspacer = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + general_layout.addItem(vspacer, 1, 0) + source_info["general"]["widgets"]["talker_source"] = cbx_info + + source_info["general"]["tab"].setLayout(general_layout) + tabs.addTab(source_info["general"]["tab"], "General") # Add source sub tabs to Comic Sources tab for talker_id, talker_obj in talkers.items(): # Add source to general tab dropdown list - cobxInfoSource.addItem(talker_obj.name, talker_id) + source_info["general"]["widgets"]["talker_source"].addItem(talker_obj.name, talker_id) - # Use a dict to make a var name from var - source_info = {} tab_name = talker_id source_info[tab_name] = {"tab": QtWidgets.QWidget(), "widgets": {}} layout_grid = QtWidgets.QGridLayout() From fefb3ce6cda98727a19427ed5edb297a9c6fe8e5 Mon Sep 17 00:00:00 2001 From: Mizaki Date: Sun, 19 Feb 2023 23:33:22 +0000 Subject: [PATCH 06/15] Remove general tab from talker tab and use base tab from settings window. Additional clean up. --- comictaggerlib/settingswindow.py | 19 +-- comictaggerlib/ui/settingswindow.ui | 10 +- comictaggerlib/ui/talkeruigenerator.py | 160 +++++++++++++------------ tests/talker_test.py | 13 +- 4 files changed, 101 insertions(+), 101 deletions(-) diff --git a/comictaggerlib/settingswindow.py b/comictaggerlib/settingswindow.py index a5558cf..888e440 100644 --- a/comictaggerlib/settingswindow.py +++ b/comictaggerlib/settingswindow.py @@ -187,7 +187,7 @@ class SettingsWindow(QtWidgets.QDialog): self.rename_error: Exception | None = None self.sources: dict = comictaggerlib.ui.talkeruigenerator.generate_source_option_tabs( - self.tTalkerTabs, self.config, self.talkers + self.tComicTalkers, self.config, self.talkers ) self.connect_signals() self.settings_to_form() @@ -456,21 +456,8 @@ class SettingsWindow(QtWidgets.QDialog): self.config[0].rename_strict = self.cbxRenameStrict.isChecked() self.config[0].rename_replacements = self.get_replacements() - # Read settings from sources tabs and generate self.config data - for tab in self.sources.items(): - for name, widget in tab[1]["widgets"].items(): - widget_value = None - if isinstance(widget, (QtWidgets.QSpinBox, QtWidgets.QDoubleSpinBox)): - widget_value = widget.value() - elif isinstance(widget, QtWidgets.QLineEdit): - widget_value = widget.text().strip() - elif isinstance(widget, QtWidgets.QCheckBox): - widget_value = widget.isChecked() - # The talker source dropdown - elif isinstance(widget, QtWidgets.QComboBox): - widget_value = widget.itemData(widget.currentIndex()) - - setattr(self.config[0], name, widget_value) + # Read settings from talker tabs + comictaggerlib.ui.talkeruigenerator.form_settings_to_config(self.sources, self.config) self.update_talkers_config() diff --git a/comictaggerlib/ui/settingswindow.ui b/comictaggerlib/ui/settingswindow.ui index c1485c9..82b1129 100644 --- a/comictaggerlib/ui/settingswindow.ui +++ b/comictaggerlib/ui/settingswindow.ui @@ -340,15 +340,7 @@ Comic Sources - - - - - -1 - - - - + diff --git a/comictaggerlib/ui/talkeruigenerator.py b/comictaggerlib/ui/talkeruigenerator.py index 3a5c236..15aed6a 100644 --- a/comictaggerlib/ui/talkeruigenerator.py +++ b/comictaggerlib/ui/talkeruigenerator.py @@ -63,79 +63,72 @@ def api_url_btn_connect( btn.clicked.connect(lambda: call_check_api_url(talker_id, sources_info, talkers)) -def format_internal_name(int_name: str = "") -> str: - # Presume talker__: talker_comicvine_cv_widget_name - int_name_split = int_name.split("_") - del int_name_split[0:3] +def format_internal_name(dest_name: str) -> str: + int_name_split = dest_name.split("_") + del int_name_split[0:1] int_name_split[0] = int_name_split[0].capitalize() new_name = " ".join(int_name_split) + return new_name -def generate_checkbox( - option: settngs.Setting, layout: QtWidgets.QGridLayout -) -> tuple[QtWidgets.QGridLayout, QtWidgets.QCheckBox]: - # bool equals a checkbox (QCheckBox) - widget = QtWidgets.QCheckBox(format_internal_name(option.internal_name)) - # Add tooltip text +def generate_checkbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> QtWidgets.QCheckBox: + widget = QtWidgets.QCheckBox(format_internal_name(option.dest)) widget.setToolTip(option.help) - # Add widget and span all columns - layout.addWidget(widget, layout.rowCount() + 1, 0, 1, -1) + layout.addWidget(widget, layout.rowCount(), 0, 1, -1) - return layout, widget + return widget -def generate_spinbox( - option: settngs.Setting, value: int | float, layout: QtWidgets.QGridLayout -) -> tuple[QtWidgets.QGridLayout, QtWidgets.QSpinBox | QtWidgets.QDoubleSpinBox]: - if isinstance(value, int): - # int equals a spinbox (QSpinBox) - lbl = QtWidgets.QLabel(option.internal_name) - # Create a label - layout.addWidget(lbl, layout.rowCount() + 1, 0) - widget = QtWidgets.QSpinBox() - widget.setRange(0, 9999) - widget.setToolTip(option.help) - layout.addWidget(widget, layout.rowCount() - 1, 1, alignment=QtCore.Qt.AlignLeft) +def generate_spinbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> QtWidgets.QSpinBox: + row = layout.rowCount() + lbl = QtWidgets.QLabel(format_internal_name(option.dest)) + layout.addWidget(lbl, row, 0) + widget = QtWidgets.QSpinBox() + widget.setRange(0, 9999) + widget.setToolTip(option.help) + layout.addWidget(widget, row, 1, alignment=QtCore.Qt.AlignLeft) - if isinstance(value, float): - # float equals a spinbox (QDoubleSpinBox) - lbl = QtWidgets.QLabel(format_internal_name(option.internal_name)) - # Create a label - layout.addWidget(lbl, layout.rowCount() + 1, 0) - widget = QtWidgets.QDoubleSpinBox() - widget.setRange(0, 9999.99) - widget.setToolTip(option.help) - layout.addWidget(widget, layout.rowCount() - 1, 1, alignment=QtCore.Qt.AlignLeft) + return widget - return layout, widget + +def generate_doublespinbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> QtWidgets.QDoubleSpinBox: + row = layout.rowCount() + lbl = QtWidgets.QLabel(format_internal_name(option.dest)) + layout.addWidget(lbl, row, 0) + widget = QtWidgets.QDoubleSpinBox() + widget.setRange(0, 9999.99) + widget.setToolTip(option.help) + layout.addWidget(widget, row, 1, alignment=QtCore.Qt.AlignLeft) + + return widget def generate_textbox( option: settngs.Setting, layout: QtWidgets.QGridLayout -) -> tuple[QtWidgets.QGridLayout, QtWidgets.QLineEdit, QtWidgets.QPushButton]: +) -> tuple[QtWidgets.QLineEdit, QtWidgets.QPushButton]: btn = None - # str equals a text field (QLineEdit) - lbl = QtWidgets.QLabel(format_internal_name(option.internal_name)) - # Create a label - layout.addWidget(lbl, layout.rowCount() + 1, 0) + row = layout.rowCount() + lbl = QtWidgets.QLabel(format_internal_name(option.dest)) + layout.addWidget(lbl, row, 0) widget = QtWidgets.QLineEdit() widget.setObjectName(option.internal_name) widget.setToolTip(option.help) - layout.addWidget(widget, layout.rowCount() - 1, 1) + layout.addWidget(widget, row, 1) + # Special case for api_key, make a test button if option.internal_name.endswith("api_key"): btn = QtWidgets.QPushButton("Test Key") - layout.addWidget(btn, layout.rowCount() - 1, 2) + layout.addWidget(btn, row, 2) if option.internal_name.endswith("url"): btn = QtWidgets.QPushButton("Test URL") - layout.addWidget(btn, layout.rowCount() - 1, 2) + layout.addWidget(btn, row, 2) - return layout, widget, btn + return widget, btn -def settings_to_talker_form(sources: dict[str, QtWidgets.QWidget], config: settngs.Config[settngs.Namespace]): +def settings_to_talker_form(sources: dict[str, QtWidgets.QWidget], config: settngs.Config[settngs.Namespace]) -> None: for talker in sources.items(): for name, widget in talker[1]["widgets"].items(): value = getattr(config[0], name) @@ -155,8 +148,25 @@ def settings_to_talker_form(sources: dict[str, QtWidgets.QWidget], config: settn logger.debug("Failed to set value of %s", name) +def form_settings_to_config(sources: dict[str, QtWidgets.QWidget], config: settngs.Config[settngs.Namespace]) -> None: + for tab in sources.items(): + for name, widget in tab[1]["widgets"].items(): + widget_value = None + if isinstance(widget, (QtWidgets.QSpinBox, QtWidgets.QDoubleSpinBox)): + widget_value = widget.value() + elif isinstance(widget, QtWidgets.QLineEdit): + widget_value = widget.text().strip() + elif isinstance(widget, QtWidgets.QCheckBox): + widget_value = widget.isChecked() + # The talker source dropdown + elif isinstance(widget, QtWidgets.QComboBox): + widget_value = widget.itemData(widget.currentIndex()) + + setattr(config[0], name, widget_value) + + def generate_source_option_tabs( - tabs: QtWidgets.QTabWidget, + comic_talker_tab: QtWidgets.QWidget, config: settngs.Config[settngs.Namespace], talkers: dict[str, ComicTalker], ) -> dict[str, QtWidgets.QWidget]: @@ -165,30 +175,32 @@ def generate_source_option_tabs( """ sources: dict = {} - # Use a dict to make a var name from var - source_info = {} - # Add General tab - source_info["general"] = {"tab": QtWidgets.QWidget(), "widgets": {}} - general_layout = QtWidgets.QGridLayout() + # Tab comes with a QVBoxLayout + comic_talker_tab_layout = comic_talker_tab.layout() + + talker_layout = QtWidgets.QGridLayout() lbl_info = QtWidgets.QLabel("Information Source:") cbx_info = QtWidgets.QComboBox() - general_layout.addWidget(lbl_info, 0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) - general_layout.addWidget(cbx_info, 0, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) - vspacer = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - general_layout.addItem(vspacer, 1, 0) - source_info["general"]["widgets"]["talker_source"] = cbx_info + line = QtWidgets.QFrame() + line.setFrameShape(QtWidgets.QFrame.HLine) + line.setFrameShadow(QtWidgets.QFrame.Sunken) + talker_tabs = QtWidgets.QTabWidget() - source_info["general"]["tab"].setLayout(general_layout) - tabs.addTab(source_info["general"]["tab"], "General") + talker_layout.addWidget(lbl_info, 0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) + talker_layout.addWidget(cbx_info, 0, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) + talker_layout.addWidget(line, 1, 0, 1, -1) + talker_layout.addWidget(talker_tabs, 2, 0, 1, -1) + + comic_talker_tab_layout.addLayout(talker_layout) # Add source sub tabs to Comic Sources tab for talker_id, talker_obj in talkers.items(): # Add source to general tab dropdown list - source_info["general"]["widgets"]["talker_source"].addItem(talker_obj.name, talker_id) + cbx_info.addItem(talker_obj.name, talker_id) tab_name = talker_id - source_info[tab_name] = {"tab": QtWidgets.QWidget(), "widgets": {}} + sources[tab_name] = {"tab": QtWidgets.QWidget(), "widgets": {}} layout_grid = QtWidgets.QGridLayout() for option in config[1][f"talker_{talker_id}"][1].values(): @@ -199,24 +211,25 @@ def generate_source_option_tabs( or option.action == "store_true" or option.action == "store_false" ): - layout_grid, current_widget = generate_checkbox(option, layout_grid) - source_info[tab_name]["widgets"][option.internal_name] = current_widget - elif option.type is int or option.type is float: - layout_grid, current_widget = generate_spinbox( - option, getattr(config[0], option.internal_name), layout_grid - ) - source_info[tab_name]["widgets"][option.internal_name] = current_widget + current_widget = generate_checkbox(option, layout_grid) + sources[tab_name]["widgets"][option.internal_name] = current_widget + elif option.type is int: + current_widget = generate_spinbox(option, layout_grid) + sources[tab_name]["widgets"][option.internal_name] = current_widget + elif option.type is float: + current_widget = generate_doublespinbox(option, layout_grid) + sources[tab_name]["widgets"][option.internal_name] = current_widget # option.type of None should be string elif option.type is None or option.type is str: - layout_grid, current_widget, btn = generate_textbox(option, layout_grid) - source_info[tab_name]["widgets"][option.internal_name] = current_widget + current_widget, btn = generate_textbox(option, layout_grid) + sources[tab_name]["widgets"][option.internal_name] = current_widget if option.internal_name.endswith("key"): # Attach test api function to button. A better way? - api_key_btn_connect(btn, talker_id, source_info, talkers) + api_key_btn_connect(btn, talker_id, sources, talkers) if option.internal_name.endswith("url"): # Attach test api function to button. A better way? - api_url_btn_connect(btn, talker_id, source_info, talkers) + api_url_btn_connect(btn, talker_id, sources, talkers) else: logger.debug(f"Unsupported talker option found. Name: {option.internal_name} Type: {option.type}") @@ -224,10 +237,9 @@ def generate_source_option_tabs( vspacer = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) layout_grid.addItem(vspacer, layout_grid.rowCount() + 1, 0) # Display the new widgets - source_info[tab_name]["tab"].setLayout(layout_grid) + sources[tab_name]["tab"].setLayout(layout_grid) # Add new sub tab to Comic Source tab - tabs.addTab(source_info[tab_name]["tab"], talker_obj.name) - sources.update(source_info) + talker_tabs.addTab(sources[tab_name]["tab"], talker_obj.name) return sources diff --git a/tests/talker_test.py b/tests/talker_test.py index c7ced2c..c305a52 100644 --- a/tests/talker_test.py +++ b/tests/talker_test.py @@ -1,7 +1,16 @@ from __future__ import annotations +import pytest + import comictaggerlib.ui.talkeruigenerator +test_names = [ + ("cv_test_name", "Test name"), + ("cv2_test_name", "Test name"), +] -def test_format_internal_name(): - assert comictaggerlib.ui.talkeruigenerator.format_internal_name("talker_comicvine_cv_test_name") == "Test name" + +@pytest.mark.parametrize("int_name, expected", test_names) +def test_format_internal_name(int_name, expected): + results = comictaggerlib.ui.talkeruigenerator.format_internal_name(int_name) + assert results == expected From bd5e23f93f32e2c256a8f6c5326c85897d3eddfb Mon Sep 17 00:00:00 2001 From: Mizaki Date: Mon, 20 Feb 2023 00:44:51 +0000 Subject: [PATCH 07/15] Add another test case for format_internal_name --- tests/talker_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/talker_test.py b/tests/talker_test.py index c305a52..5b81534 100644 --- a/tests/talker_test.py +++ b/tests/talker_test.py @@ -7,6 +7,7 @@ import comictaggerlib.ui.talkeruigenerator test_names = [ ("cv_test_name", "Test name"), ("cv2_test_name", "Test name"), + ("mu_use_this_test", "Use this test"), ] From f439797b03faf49b2f8dfacd188436f11dd75f56 Mon Sep 17 00:00:00 2001 From: Mizaki Date: Mon, 20 Feb 2023 18:45:39 +0000 Subject: [PATCH 08/15] Use new display_name from settngs. Add source combobox getting and setting and add to sources dict of widgets. --- comictaggerlib/ui/talkeruigenerator.py | 56 ++++++++++++-------------- comictalker/talkers/comicvine.py | 23 ++++++++--- requirements.txt | 2 +- tests/talker_test.py | 17 -------- 4 files changed, 44 insertions(+), 54 deletions(-) delete mode 100644 tests/talker_test.py diff --git a/comictaggerlib/ui/talkeruigenerator.py b/comictaggerlib/ui/talkeruigenerator.py index 15aed6a..581778d 100644 --- a/comictaggerlib/ui/talkeruigenerator.py +++ b/comictaggerlib/ui/talkeruigenerator.py @@ -63,17 +63,8 @@ def api_url_btn_connect( btn.clicked.connect(lambda: call_check_api_url(talker_id, sources_info, talkers)) -def format_internal_name(dest_name: str) -> str: - int_name_split = dest_name.split("_") - del int_name_split[0:1] - int_name_split[0] = int_name_split[0].capitalize() - new_name = " ".join(int_name_split) - - return new_name - - def generate_checkbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> QtWidgets.QCheckBox: - widget = QtWidgets.QCheckBox(format_internal_name(option.dest)) + widget = QtWidgets.QCheckBox(option.display_name) widget.setToolTip(option.help) layout.addWidget(widget, layout.rowCount(), 0, 1, -1) @@ -82,7 +73,7 @@ def generate_checkbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> def generate_spinbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> QtWidgets.QSpinBox: row = layout.rowCount() - lbl = QtWidgets.QLabel(format_internal_name(option.dest)) + lbl = QtWidgets.QLabel(option.display_name) layout.addWidget(lbl, row, 0) widget = QtWidgets.QSpinBox() widget.setRange(0, 9999) @@ -94,7 +85,7 @@ def generate_spinbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> def generate_doublespinbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> QtWidgets.QDoubleSpinBox: row = layout.rowCount() - lbl = QtWidgets.QLabel(format_internal_name(option.dest)) + lbl = QtWidgets.QLabel(option.display_name) layout.addWidget(lbl, row, 0) widget = QtWidgets.QDoubleSpinBox() widget.setRange(0, 9999.99) @@ -109,7 +100,7 @@ def generate_textbox( ) -> tuple[QtWidgets.QLineEdit, QtWidgets.QPushButton]: btn = None row = layout.rowCount() - lbl = QtWidgets.QLabel(format_internal_name(option.dest)) + lbl = QtWidgets.QLabel(option.display_name) layout.addWidget(lbl, row, 0) widget = QtWidgets.QLineEdit() widget.setObjectName(option.internal_name) @@ -129,17 +120,16 @@ def generate_textbox( def settings_to_talker_form(sources: dict[str, QtWidgets.QWidget], config: settngs.Config[settngs.Namespace]) -> None: - for talker in sources.items(): + # Set the active talker in sources combo box + sources["talker_source"].setCurrentIndex(sources["talker_source"].findData(config[0].talker_source)) + + for talker in sources["tabs"].items(): for name, widget in talker[1]["widgets"].items(): value = getattr(config[0], name) value_type = type(value) try: if value_type is str: - # Special case for general dropdown box - if name == "talker_source": - widget.setCurrentIndex(widget.findData(config[0].talker_source)) - else: - widget.setText(value) + widget.setText(value) if value_type is int or value_type is float: widget.setValue(value) if value_type is bool: @@ -149,7 +139,10 @@ def settings_to_talker_form(sources: dict[str, QtWidgets.QWidget], config: settn def form_settings_to_config(sources: dict[str, QtWidgets.QWidget], config: settngs.Config[settngs.Namespace]) -> None: - for tab in sources.items(): + # Source combo box value + config[0].talker_source = sources["talker_source"].itemData(sources["talker_source"].currentIndex()) + + for tab in sources["tabs"].items(): for name, widget in tab[1]["widgets"].items(): widget_value = None if isinstance(widget, (QtWidgets.QSpinBox, QtWidgets.QDoubleSpinBox)): @@ -158,9 +151,6 @@ def form_settings_to_config(sources: dict[str, QtWidgets.QWidget], config: settn widget_value = widget.text().strip() elif isinstance(widget, QtWidgets.QCheckBox): widget_value = widget.isChecked() - # The talker source dropdown - elif isinstance(widget, QtWidgets.QComboBox): - widget_value = widget.itemData(widget.currentIndex()) setattr(config[0], name, widget_value) @@ -174,7 +164,8 @@ def generate_source_option_tabs( Generate GUI tabs and settings for talkers """ - sources: dict = {} + # Store all widgets as to allow easier access to their values vs. using findChildren etc. on the tab widget + sources: dict = {"tabs": {}} # Tab comes with a QVBoxLayout comic_talker_tab_layout = comic_talker_tab.layout() @@ -194,13 +185,16 @@ def generate_source_option_tabs( comic_talker_tab_layout.addLayout(talker_layout) + # Add cbx_info combobox to sources for getting and setting talker + sources["talker_source"] = cbx_info + # Add source sub tabs to Comic Sources tab for talker_id, talker_obj in talkers.items(): # Add source to general tab dropdown list cbx_info.addItem(talker_obj.name, talker_id) tab_name = talker_id - sources[tab_name] = {"tab": QtWidgets.QWidget(), "widgets": {}} + sources["tabs"][tab_name] = {"tab": QtWidgets.QWidget(), "widgets": {}} layout_grid = QtWidgets.QGridLayout() for option in config[1][f"talker_{talker_id}"][1].values(): @@ -212,17 +206,17 @@ def generate_source_option_tabs( or option.action == "store_false" ): current_widget = generate_checkbox(option, layout_grid) - sources[tab_name]["widgets"][option.internal_name] = current_widget + sources["tabs"][tab_name]["widgets"][option.internal_name] = current_widget elif option.type is int: current_widget = generate_spinbox(option, layout_grid) - sources[tab_name]["widgets"][option.internal_name] = current_widget + sources["tabs"][tab_name]["widgets"][option.internal_name] = current_widget elif option.type is float: current_widget = generate_doublespinbox(option, layout_grid) - sources[tab_name]["widgets"][option.internal_name] = current_widget + sources["tabs"][tab_name]["widgets"][option.internal_name] = current_widget # option.type of None should be string elif option.type is None or option.type is str: current_widget, btn = generate_textbox(option, layout_grid) - sources[tab_name]["widgets"][option.internal_name] = current_widget + sources["tabs"][tab_name]["widgets"][option.internal_name] = current_widget if option.internal_name.endswith("key"): # Attach test api function to button. A better way? @@ -237,9 +231,9 @@ def generate_source_option_tabs( vspacer = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) layout_grid.addItem(vspacer, layout_grid.rowCount() + 1, 0) # Display the new widgets - sources[tab_name]["tab"].setLayout(layout_grid) + sources["tabs"][tab_name]["tab"].setLayout(layout_grid) # Add new sub tab to Comic Source tab - talker_tabs.addTab(sources[tab_name]["tab"], talker_obj.name) + talker_tabs.addTab(sources["tabs"][tab_name]["tab"], talker_obj.name) return sources diff --git a/comictalker/talkers/comicvine.py b/comictalker/talkers/comicvine.py index fa9dc66..282c636 100644 --- a/comictalker/talkers/comicvine.py +++ b/comictalker/talkers/comicvine.py @@ -181,16 +181,29 @@ class ComicVineTalker(ComicTalker): self.wait_on_ratelimit_time: int = 20 def register_settings(self, parser: settngs.Manager) -> None: - parser.add_setting("--cv-use-series-start-as-volume", default=False, action=argparse.BooleanOptionalAction) - parser.add_setting("--cv-wait-on-ratelimit", default=False, action=argparse.BooleanOptionalAction) + parser.add_setting( + "--cv-use-series-start-as-volume", + default=False, + action=argparse.BooleanOptionalAction, + display_name="Use series start as volume", + help="Use the series start year as the volume number", + ) + parser.add_setting( + "--cv-wait-on-ratelimit", + default=False, + action=argparse.BooleanOptionalAction, + display_name="Wait on ratelimit", + help="Wait when the rate limit is hit", + ) parser.add_setting( "--cv-remove-html-tables", default=False, action=argparse.BooleanOptionalAction, - help="Removes html tables instead of converting them to text.", + display_name="Remove HTML tables", + help="Removes html tables instead of converting them to text", ) - parser.add_setting("--cv-api-key", help="Use the given Comic Vine API Key.") - parser.add_setting("--cv-url", help="Use the given Comic Vine URL.") + parser.add_setting("--cv-api-key", display_name="API Key", help="Use the given Comic Vine API Key") + parser.add_setting("--cv-url", display_name="URL", help="Use the given Comic Vine URL") def parse_settings(self, settings: dict[str, Any]) -> dict[str, Any]: if settings["cv_api_key"]: diff --git a/requirements.txt b/requirements.txt index a6aefcd..7a936ec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ pycountry #pyicu; sys_platform == 'linux' or sys_platform == 'darwin' rapidfuzz>=2.12.0 requests==2.* -settngs==0.5.0 +settngs==0.6.2 text2digits typing_extensions wordninja diff --git a/tests/talker_test.py b/tests/talker_test.py deleted file mode 100644 index 5b81534..0000000 --- a/tests/talker_test.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import annotations - -import pytest - -import comictaggerlib.ui.talkeruigenerator - -test_names = [ - ("cv_test_name", "Test name"), - ("cv2_test_name", "Test name"), - ("mu_use_this_test", "Use this test"), -] - - -@pytest.mark.parametrize("int_name, expected", test_names) -def test_format_internal_name(int_name, expected): - results = comictaggerlib.ui.talkeruigenerator.format_internal_name(int_name) - assert results == expected From 59893b1d1cd6b3c83215f43640b496b9517098c8 Mon Sep 17 00:00:00 2001 From: Mizaki Date: Tue, 21 Feb 2023 00:38:13 +0000 Subject: [PATCH 09/15] Fix optoin.type ifs --- comictaggerlib/ui/talkeruigenerator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/comictaggerlib/ui/talkeruigenerator.py b/comictaggerlib/ui/talkeruigenerator.py index 581778d..a5cd24a 100644 --- a/comictaggerlib/ui/talkeruigenerator.py +++ b/comictaggerlib/ui/talkeruigenerator.py @@ -201,7 +201,7 @@ def generate_source_option_tabs( current_widget = None if option.action is not None and ( option.action is argparse.BooleanOptionalAction - or option.action is bool + or option.type is bool or option.action == "store_true" or option.action == "store_false" ): @@ -214,7 +214,7 @@ def generate_source_option_tabs( current_widget = generate_doublespinbox(option, layout_grid) sources["tabs"][tab_name]["widgets"][option.internal_name] = current_widget # option.type of None should be string - elif option.type is None or option.type is str: + elif (option.type is None and option.action is None) or option.type is str: current_widget, btn = generate_textbox(option, layout_grid) sources["tabs"][tab_name]["widgets"][option.internal_name] = current_widget From 316bd52f21125d006e67d616c473e6f78fc2f1f5 Mon Sep 17 00:00:00 2001 From: Mizaki Date: Tue, 21 Feb 2023 00:42:11 +0000 Subject: [PATCH 10/15] Use currentData for combo box --- comictaggerlib/ui/talkeruigenerator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comictaggerlib/ui/talkeruigenerator.py b/comictaggerlib/ui/talkeruigenerator.py index a5cd24a..1f2a36a 100644 --- a/comictaggerlib/ui/talkeruigenerator.py +++ b/comictaggerlib/ui/talkeruigenerator.py @@ -140,7 +140,7 @@ def settings_to_talker_form(sources: dict[str, QtWidgets.QWidget], config: settn def form_settings_to_config(sources: dict[str, QtWidgets.QWidget], config: settngs.Config[settngs.Namespace]) -> None: # Source combo box value - config[0].talker_source = sources["talker_source"].itemData(sources["talker_source"].currentIndex()) + config[0].talker_source = sources["talker_source"].currentData() for tab in sources["tabs"].items(): for name, widget in tab[1]["widgets"].items(): From aba59bdbfe6aea0a2ef1f9757dd0406bffc84a6d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Feb 2023 00:43:46 +0000 Subject: [PATCH 11/15] docs(contributor): contrib-readme-action has updated readme --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c276be0..f8378ca 100644 --- a/README.md +++ b/README.md @@ -83,10 +83,10 @@ choco install comictagger - - fcanc + + mizaki
- fcanc + mizaki
@@ -96,19 +96,19 @@ choco install comictagger lordwelch - - - mizaki -
- mizaki -
- MichaelFitzurka
MichaelFitzurka
+ + + + fcanc +
+ fcanc +
From 5b5a483e25a447dcdbdf92b41ae66ef674fbdbd3 Mon Sep 17 00:00:00 2001 From: Mizaki Date: Tue, 21 Feb 2023 00:58:13 +0000 Subject: [PATCH 12/15] Fix api key test button generation --- comictaggerlib/ui/talkeruigenerator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/comictaggerlib/ui/talkeruigenerator.py b/comictaggerlib/ui/talkeruigenerator.py index 1f2a36a..9966d3f 100644 --- a/comictaggerlib/ui/talkeruigenerator.py +++ b/comictaggerlib/ui/talkeruigenerator.py @@ -108,7 +108,7 @@ def generate_textbox( layout.addWidget(widget, row, 1) # Special case for api_key, make a test button - if option.internal_name.endswith("api_key"): + if option.internal_name.endswith("key"): btn = QtWidgets.QPushButton("Test Key") layout.addWidget(btn, row, 2) From 118429f84c599ab1a1fee2b866fb07f5d8a5cadb Mon Sep 17 00:00:00 2001 From: Mizaki Date: Thu, 23 Feb 2023 00:42:48 +0000 Subject: [PATCH 13/15] Change source term to metadata Generate API text field in their own function API tests return string message of result Add help to text field lables --- comictaggerlib/ui/settingswindow.ui | 2 +- comictaggerlib/ui/talkeruigenerator.py | 138 +++++++++++-------------- comictalker/comictalker.py | 4 +- comictalker/talkers/comicvine.py | 13 ++- 4 files changed, 75 insertions(+), 82 deletions(-) diff --git a/comictaggerlib/ui/settingswindow.ui b/comictaggerlib/ui/settingswindow.ui index 82b1129..ee7d80d 100644 --- a/comictaggerlib/ui/settingswindow.ui +++ b/comictaggerlib/ui/settingswindow.ui @@ -338,7 +338,7 @@
- Comic Sources + Metadata Sources diff --git a/comictaggerlib/ui/talkeruigenerator.py b/comictaggerlib/ui/talkeruigenerator.py index 9966d3f..cc785e8 100644 --- a/comictaggerlib/ui/talkeruigenerator.py +++ b/comictaggerlib/ui/talkeruigenerator.py @@ -2,6 +2,7 @@ from __future__ import annotations import argparse import logging +from functools import partial import settngs from PyQt5 import QtCore, QtWidgets @@ -11,56 +12,53 @@ from comictalker.comictalker import ComicTalker logger = logging.getLogger(__name__) -def call_check_api_key( +def generate_api_widgets( talker_id: str, - sources_info: dict[str, QtWidgets.QWidget], + sources: dict[str, QtWidgets.QWidget], + config: settngs.Config[settngs.Namespace], + layout: QtWidgets.QGridLayout, talkers: dict[str, ComicTalker], ): - key = "" - # Find the correct widget to get the API key - for name, widget in sources_info[talker_id]["widgets"].items(): - if name.startswith("talker_" + talker_id) and name.endswith("api_key"): - key = widget.text().strip() + # *args enforces keyword arguments and allows position arguments to be ignored + def call_check_api(*args, le_url: QtWidgets.QLineEdit, le_key: QtWidgets.QLineEdit, talker_id: str): + url = "" + key = "" + if le_key is not None: + key = le_key.text().strip() + if le_url is not None: + url = le_url.text().strip() + QtWidgets.QMessageBox.information(None, "API Test", talkers[talker_id].check_api_key(url, key)) - if talkers[talker_id].check_api_key(key): - QtWidgets.QMessageBox.information(None, "API Key Test", "Key is valid!") - else: - QtWidgets.QMessageBox.warning(None, "API Key Test", "Key is NOT valid!") + # get the actual config objects in case they have overwritten the default + talker_key = config[1][f"talker_{talker_id}"][1][f"{talker_id}_key"] + talker_url = config[1][f"talker_{talker_id}"][1][f"{talker_id}_url"] + btn_test_row = None + le_key = None + le_url = None + # only file settings are saved + if talker_key.file: + # record the current row so we know where to add the button + btn_test_row = layout.rowCount() + le_key = generate_textbox(talker_key, layout) + # To enable setting and getting + sources["tabs"][talker_id]["widgets"][f"talker_{talker_id}_{talker_id}_key"] = le_key -def call_check_api_url( - talker_id: str, - sources_info: dict[str, QtWidgets.QWidget], - talkers: dict[str, ComicTalker], -): - url = "" - # Find the correct widget to get the URL key - for name, widget in sources_info[talker_id]["widgets"].items(): - if name.startswith("talker_" + talker_id) and name.endswith("url"): - url = widget.text().strip() + # only file settings are saved + if talker_url.file: + # record the current row so we know where to add the button + # We overwrite so that the default will be next to the url text box + btn_test_row = layout.rowCount() + le_url = generate_textbox(talker_url, layout) + # To enable setting and getting + sources["tabs"][talker_id]["widgets"][f"talker_{talker_id}_{talker_id}_url"] = le_url - if talkers[talker_id].check_api_url(url): - QtWidgets.QMessageBox.information(None, "API Key Test", "URL is valid!") - else: - QtWidgets.QMessageBox.warning(None, "API Key Test", "URL is NOT valid!") - - -def api_key_btn_connect( - btn: QtWidgets.QPushButton, - talker_id: str, - sources_info: dict[str, QtWidgets.QWidget], - talkers: dict[str, ComicTalker], -) -> None: - btn.clicked.connect(lambda: call_check_api_key(talker_id, sources_info, talkers)) - - -def api_url_btn_connect( - btn: QtWidgets.QPushButton, - talker_id: str, - sources_info: dict[str, QtWidgets.QWidget], - talkers: dict[str, ComicTalker], -) -> None: - btn.clicked.connect(lambda: call_check_api_url(talker_id, sources_info, talkers)) + # The button row was recorded so we add it + if btn_test_row is not None: + btn = QtWidgets.QPushButton("Test API") + layout.addWidget(btn, btn_test_row, 2) + # partial is used as connect will pass in event information + btn.clicked.connect(partial(call_check_api, le_url=le_url, le_key=le_key, talker_id=talker_id)) def generate_checkbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> QtWidgets.QCheckBox: @@ -95,33 +93,21 @@ def generate_doublespinbox(option: settngs.Setting, layout: QtWidgets.QGridLayou return widget -def generate_textbox( - option: settngs.Setting, layout: QtWidgets.QGridLayout -) -> tuple[QtWidgets.QLineEdit, QtWidgets.QPushButton]: - btn = None +def generate_textbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> QtWidgets.QLineEdit: row = layout.rowCount() lbl = QtWidgets.QLabel(option.display_name) layout.addWidget(lbl, row, 0) widget = QtWidgets.QLineEdit() - widget.setObjectName(option.internal_name) + lbl.setToolTip(option.help) widget.setToolTip(option.help) layout.addWidget(widget, row, 1) - # Special case for api_key, make a test button - if option.internal_name.endswith("key"): - btn = QtWidgets.QPushButton("Test Key") - layout.addWidget(btn, row, 2) - - if option.internal_name.endswith("url"): - btn = QtWidgets.QPushButton("Test URL") - layout.addWidget(btn, row, 2) - - return widget, btn + return widget def settings_to_talker_form(sources: dict[str, QtWidgets.QWidget], config: settngs.Config[settngs.Namespace]) -> None: - # Set the active talker in sources combo box - sources["talker_source"].setCurrentIndex(sources["talker_source"].findData(config[0].talker_source)) + # Set the active talker via id in sources combo box + sources["cbx_select_talker"].setCurrentIndex(sources["cbx_select_talker"].findData(config[0].talker_source)) for talker in sources["tabs"].items(): for name, widget in talker[1]["widgets"].items(): @@ -140,7 +126,7 @@ def settings_to_talker_form(sources: dict[str, QtWidgets.QWidget], config: settn def form_settings_to_config(sources: dict[str, QtWidgets.QWidget], config: settngs.Config[settngs.Namespace]) -> None: # Source combo box value - config[0].talker_source = sources["talker_source"].currentData() + config[0].talker_source = sources["cbx_select_talker"].currentData() for tab in sources["tabs"].items(): for name, widget in tab[1]["widgets"].items(): @@ -171,33 +157,37 @@ def generate_source_option_tabs( comic_talker_tab_layout = comic_talker_tab.layout() talker_layout = QtWidgets.QGridLayout() - lbl_info = QtWidgets.QLabel("Information Source:") - cbx_info = QtWidgets.QComboBox() + lbl_select_talker = QtWidgets.QLabel("Metadata Sources:") + cbx_select_talker = QtWidgets.QComboBox() line = QtWidgets.QFrame() line.setFrameShape(QtWidgets.QFrame.HLine) line.setFrameShadow(QtWidgets.QFrame.Sunken) talker_tabs = QtWidgets.QTabWidget() - talker_layout.addWidget(lbl_info, 0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) - talker_layout.addWidget(cbx_info, 0, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) + talker_layout.addWidget(lbl_select_talker, 0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) + talker_layout.addWidget(cbx_select_talker, 0, 1, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Maximum) talker_layout.addWidget(line, 1, 0, 1, -1) talker_layout.addWidget(talker_tabs, 2, 0, 1, -1) comic_talker_tab_layout.addLayout(talker_layout) - # Add cbx_info combobox to sources for getting and setting talker - sources["talker_source"] = cbx_info + # Add combobox to sources for getting and setting talker + sources["cbx_select_talker"] = cbx_select_talker # Add source sub tabs to Comic Sources tab for talker_id, talker_obj in talkers.items(): # Add source to general tab dropdown list - cbx_info.addItem(talker_obj.name, talker_id) + cbx_select_talker.addItem(talker_obj.name, talker_id) tab_name = talker_id sources["tabs"][tab_name] = {"tab": QtWidgets.QWidget(), "widgets": {}} layout_grid = QtWidgets.QGridLayout() for option in config[1][f"talker_{talker_id}"][1].values(): + if not option.file: + continue + if option.dest in (f"{talker_id}_url", f"{talker_id}_key"): + continue current_widget = None if option.action is not None and ( option.action is argparse.BooleanOptionalAction @@ -215,18 +205,14 @@ def generate_source_option_tabs( sources["tabs"][tab_name]["widgets"][option.internal_name] = current_widget # option.type of None should be string elif (option.type is None and option.action is None) or option.type is str: - current_widget, btn = generate_textbox(option, layout_grid) + current_widget = generate_textbox(option, layout_grid) sources["tabs"][tab_name]["widgets"][option.internal_name] = current_widget - - if option.internal_name.endswith("key"): - # Attach test api function to button. A better way? - api_key_btn_connect(btn, talker_id, sources, talkers) - if option.internal_name.endswith("url"): - # Attach test api function to button. A better way? - api_url_btn_connect(btn, talker_id, sources, talkers) else: logger.debug(f"Unsupported talker option found. Name: {option.internal_name} Type: {option.type}") + # Add talker URL and API key fields + generate_api_widgets(talker_id, sources, config, layout_grid, talkers) + # Add vertical spacer vspacer = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) layout_grid.addItem(vspacer, layout_grid.rowCount() + 1, 0) diff --git a/comictalker/comictalker.py b/comictalker/comictalker.py index 26b08e8..5a5a916 100644 --- a/comictalker/comictalker.py +++ b/comictalker/comictalker.py @@ -141,9 +141,9 @@ class ComicTalker: self.api_url = self.default_api_url return settings - def check_api_key(self, key: str) -> bool: + def check_api_key(self, url: str, key: str) -> str: """ - This function should return true if the given api key is valid. + This function should return a string with the test outcome for display to user. """ raise NotImplementedError diff --git a/comictalker/talkers/comicvine.py b/comictalker/talkers/comicvine.py index 09fbc92..c14edb0 100644 --- a/comictalker/talkers/comicvine.py +++ b/comictalker/talkers/comicvine.py @@ -216,7 +216,11 @@ class ComicVineTalker(ComicTalker): self.remove_html_tables = settings["cv_remove_html_tables"] return settings - def check_api_key(self, key: str, url: str) -> bool: + def check_api_key( + self, + url: str, + key: str, + ) -> str: url = talker_utils.fix_url(url) if not url: url = self.default_api_url @@ -230,9 +234,12 @@ class ComicVineTalker(ComicTalker): ).json() # Bogus request, but if the key is wrong, you get error 100: "Invalid API Key" - return cv_response["status_code"] != 100 + if cv_response["status_code"] != 100: + return "API key is valid" + else: + return "API key is INVALID!" except Exception: - return False + return "Failed to connect to the URL!" def search_for_series( self, From b4a3e8c2ee88f10cd2a8c7fef658ea6a1db2d09d Mon Sep 17 00:00:00 2001 From: Mizaki Date: Fri, 24 Feb 2023 00:06:48 +0000 Subject: [PATCH 14/15] Add missing tool tips to labels Change metadata select label Use named tuple for talker tabs Retrun a string and bool for api check --- comictaggerlib/ui/talkeruigenerator.py | 41 +++++++++++++++++--------- comictalker/comictalker.py | 4 +-- comictalker/talkers/comicvine.py | 12 +++----- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/comictaggerlib/ui/talkeruigenerator.py b/comictaggerlib/ui/talkeruigenerator.py index cc785e8..d34c63e 100644 --- a/comictaggerlib/ui/talkeruigenerator.py +++ b/comictaggerlib/ui/talkeruigenerator.py @@ -3,6 +3,7 @@ from __future__ import annotations import argparse import logging from functools import partial +from typing import NamedTuple import settngs from PyQt5 import QtCore, QtWidgets @@ -12,6 +13,11 @@ from comictalker.comictalker import ComicTalker logger = logging.getLogger(__name__) +class TalkerTab(NamedTuple): + tab: QtWidgets.QWidget + widgets: dict[str, QtWidgets.QWidget] + + def generate_api_widgets( talker_id: str, sources: dict[str, QtWidgets.QWidget], @@ -27,7 +33,12 @@ def generate_api_widgets( key = le_key.text().strip() if le_url is not None: url = le_url.text().strip() - QtWidgets.QMessageBox.information(None, "API Test", talkers[talker_id].check_api_key(url, key)) + + check_text, check_bool = talkers[talker_id].check_api_key(url, key) + if check_bool: + QtWidgets.QMessageBox.information(None, "API Test Success", check_text) + else: + QtWidgets.QMessageBox.warning(None, "API Test Failed", check_text) # get the actual config objects in case they have overwritten the default talker_key = config[1][f"talker_{talker_id}"][1][f"{talker_id}_key"] @@ -42,7 +53,7 @@ def generate_api_widgets( btn_test_row = layout.rowCount() le_key = generate_textbox(talker_key, layout) # To enable setting and getting - sources["tabs"][talker_id]["widgets"][f"talker_{talker_id}_{talker_id}_key"] = le_key + sources["tabs"][talker_id].widgets[f"talker_{talker_id}_{talker_id}_key"] = le_key # only file settings are saved if talker_url.file: @@ -51,7 +62,7 @@ def generate_api_widgets( btn_test_row = layout.rowCount() le_url = generate_textbox(talker_url, layout) # To enable setting and getting - sources["tabs"][talker_id]["widgets"][f"talker_{talker_id}_{talker_id}_url"] = le_url + sources["tabs"][talker_id].widgets[f"talker_{talker_id}_{talker_id}_url"] = le_url # The button row was recorded so we add it if btn_test_row is not None: @@ -72,6 +83,7 @@ def generate_checkbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> def generate_spinbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> QtWidgets.QSpinBox: row = layout.rowCount() lbl = QtWidgets.QLabel(option.display_name) + lbl.setToolTip(option.help) layout.addWidget(lbl, row, 0) widget = QtWidgets.QSpinBox() widget.setRange(0, 9999) @@ -84,6 +96,7 @@ def generate_spinbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> def generate_doublespinbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> QtWidgets.QDoubleSpinBox: row = layout.rowCount() lbl = QtWidgets.QLabel(option.display_name) + lbl.setToolTip(option.help) layout.addWidget(lbl, row, 0) widget = QtWidgets.QDoubleSpinBox() widget.setRange(0, 9999.99) @@ -96,9 +109,9 @@ def generate_doublespinbox(option: settngs.Setting, layout: QtWidgets.QGridLayou def generate_textbox(option: settngs.Setting, layout: QtWidgets.QGridLayout) -> QtWidgets.QLineEdit: row = layout.rowCount() lbl = QtWidgets.QLabel(option.display_name) + lbl.setToolTip(option.help) layout.addWidget(lbl, row, 0) widget = QtWidgets.QLineEdit() - lbl.setToolTip(option.help) widget.setToolTip(option.help) layout.addWidget(widget, row, 1) @@ -110,7 +123,7 @@ def settings_to_talker_form(sources: dict[str, QtWidgets.QWidget], config: settn sources["cbx_select_talker"].setCurrentIndex(sources["cbx_select_talker"].findData(config[0].talker_source)) for talker in sources["tabs"].items(): - for name, widget in talker[1]["widgets"].items(): + for name, widget in talker[1].widgets.items(): value = getattr(config[0], name) value_type = type(value) try: @@ -129,7 +142,7 @@ def form_settings_to_config(sources: dict[str, QtWidgets.QWidget], config: settn config[0].talker_source = sources["cbx_select_talker"].currentData() for tab in sources["tabs"].items(): - for name, widget in tab[1]["widgets"].items(): + for name, widget in tab[1].widgets.items(): widget_value = None if isinstance(widget, (QtWidgets.QSpinBox, QtWidgets.QDoubleSpinBox)): widget_value = widget.value() @@ -157,7 +170,7 @@ def generate_source_option_tabs( comic_talker_tab_layout = comic_talker_tab.layout() talker_layout = QtWidgets.QGridLayout() - lbl_select_talker = QtWidgets.QLabel("Metadata Sources:") + lbl_select_talker = QtWidgets.QLabel("Metadata Source:") cbx_select_talker = QtWidgets.QComboBox() line = QtWidgets.QFrame() line.setFrameShape(QtWidgets.QFrame.HLine) @@ -180,7 +193,7 @@ def generate_source_option_tabs( cbx_select_talker.addItem(talker_obj.name, talker_id) tab_name = talker_id - sources["tabs"][tab_name] = {"tab": QtWidgets.QWidget(), "widgets": {}} + sources["tabs"][tab_name] = TalkerTab(tab=QtWidgets.QWidget(), widgets={}) layout_grid = QtWidgets.QGridLayout() for option in config[1][f"talker_{talker_id}"][1].values(): @@ -196,17 +209,17 @@ def generate_source_option_tabs( or option.action == "store_false" ): current_widget = generate_checkbox(option, layout_grid) - sources["tabs"][tab_name]["widgets"][option.internal_name] = current_widget + sources["tabs"][tab_name].widgets[option.internal_name] = current_widget elif option.type is int: current_widget = generate_spinbox(option, layout_grid) - sources["tabs"][tab_name]["widgets"][option.internal_name] = current_widget + sources["tabs"][tab_name].widgets[option.internal_name] = current_widget elif option.type is float: current_widget = generate_doublespinbox(option, layout_grid) - sources["tabs"][tab_name]["widgets"][option.internal_name] = current_widget + sources["tabs"][tab_name].widgets[option.internal_name] = current_widget # option.type of None should be string elif (option.type is None and option.action is None) or option.type is str: current_widget = generate_textbox(option, layout_grid) - sources["tabs"][tab_name]["widgets"][option.internal_name] = current_widget + sources["tabs"][tab_name]("widget")[option.internal_name] = current_widget else: logger.debug(f"Unsupported talker option found. Name: {option.internal_name} Type: {option.type}") @@ -217,9 +230,9 @@ def generate_source_option_tabs( vspacer = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) layout_grid.addItem(vspacer, layout_grid.rowCount() + 1, 0) # Display the new widgets - sources["tabs"][tab_name]["tab"].setLayout(layout_grid) + sources["tabs"][tab_name].tab.setLayout(layout_grid) # Add new sub tab to Comic Source tab - talker_tabs.addTab(sources["tabs"][tab_name]["tab"], talker_obj.name) + talker_tabs.addTab(sources["tabs"][tab_name].tab, talker_obj.name) return sources diff --git a/comictalker/comictalker.py b/comictalker/comictalker.py index 5a5a916..e5c5d04 100644 --- a/comictalker/comictalker.py +++ b/comictalker/comictalker.py @@ -141,9 +141,9 @@ class ComicTalker: self.api_url = self.default_api_url return settings - def check_api_key(self, url: str, key: str) -> str: + def check_api_key(self, url: str, key: str) -> tuple[str, bool]: """ - This function should return a string with the test outcome for display to user. + This function should return a string with the test outcome for display to user and a bool. """ raise NotImplementedError diff --git a/comictalker/talkers/comicvine.py b/comictalker/talkers/comicvine.py index c14edb0..3e43e05 100644 --- a/comictalker/talkers/comicvine.py +++ b/comictalker/talkers/comicvine.py @@ -216,11 +216,7 @@ class ComicVineTalker(ComicTalker): self.remove_html_tables = settings["cv_remove_html_tables"] return settings - def check_api_key( - self, - url: str, - key: str, - ) -> str: + def check_api_key(self, url: str, key: str) -> tuple[str, bool]: url = talker_utils.fix_url(url) if not url: url = self.default_api_url @@ -235,11 +231,11 @@ class ComicVineTalker(ComicTalker): # Bogus request, but if the key is wrong, you get error 100: "Invalid API Key" if cv_response["status_code"] != 100: - return "API key is valid" + return "The API key is valid", True else: - return "API key is INVALID!" + return "The API key is INVALID!", False except Exception: - return "Failed to connect to the URL!" + return "Failed to connect to the URL!", False def search_for_series( self, From 2611c284b87b82998dff6861b9322d89570ea316 Mon Sep 17 00:00:00 2001 From: Mizaki Date: Fri, 24 Feb 2023 13:23:29 +0000 Subject: [PATCH 15/15] Revert "docs(contributor): contrib-readme-action has updated readme" This reverts commit aba59bdbfe6aea0a2ef1f9757dd0406bffc84a6d. --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index f8378ca..c276be0 100644 --- a/README.md +++ b/README.md @@ -83,10 +83,10 @@ choco install comictagger - - mizaki + + fcanc
- mizaki + fcanc
@@ -96,19 +96,19 @@ choco install comictagger lordwelch + + + mizaki +
+ mizaki +
+ MichaelFitzurka
MichaelFitzurka
- - - - fcanc -
- fcanc -