Move talker settings menu generator to a separate file
This commit is contained in:
parent
83a8d5d5e1
commit
6a6a3320cb
@ -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_<name>_<nm>
|
||||
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()
|
||||
|
213
comictaggerlib/ui/talkeruigenerator.py
Normal file
213
comictaggerlib/ui/talkeruigenerator.py
Normal file
@ -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_<name>_<nm>: 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
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user