Added Import/Export/Clear-Config buttons, hide advanced options by default
This commit is contained in:
parent
eb725c7c33
commit
c78a48bd10
2 changed files with 214 additions and 43 deletions
|
@ -67,6 +67,7 @@ def default_config() -> dict[str, Optional[int | str]]:
|
||||||
"waiting_room_policy": None,
|
"waiting_room_policy": None,
|
||||||
"key": None,
|
"key": None,
|
||||||
"mpv_options": "",
|
"mpv_options": "",
|
||||||
|
"show_advanced": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
256
syng/gui.py
256
syng/gui.py
|
@ -15,6 +15,7 @@ import secrets
|
||||||
import string
|
import string
|
||||||
import signal
|
import signal
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not TYPE_CHECKING:
|
if not TYPE_CHECKING:
|
||||||
from ctypes import windll
|
from ctypes import windll
|
||||||
|
@ -25,7 +26,7 @@ except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
from PyQt6.QtCore import QTimer
|
from PyQt6.QtCore import QTimer, Qt
|
||||||
from PyQt6.QtGui import QCloseEvent, QIcon, QPixmap
|
from PyQt6.QtGui import QCloseEvent, QIcon, QPixmap
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QApplication,
|
QApplication,
|
||||||
|
@ -36,8 +37,10 @@ from PyQt6.QtWidgets import (
|
||||||
QFormLayout,
|
QFormLayout,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QLabel,
|
QLabel,
|
||||||
|
QLayout,
|
||||||
QLineEdit,
|
QLineEdit,
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
|
QMessageBox,
|
||||||
QPushButton,
|
QPushButton,
|
||||||
QSizePolicy,
|
QSizePolicy,
|
||||||
QSpacerItem,
|
QSpacerItem,
|
||||||
|
@ -335,6 +338,7 @@ class OptionFrame(QWidget):
|
||||||
|
|
||||||
for name, textbox in self.int_options.items():
|
for name, textbox in self.int_options.items():
|
||||||
config[name] = textbox.value()
|
config[name] = textbox.value()
|
||||||
|
|
||||||
for name, optionmenu in self.choose_options.items():
|
for name, optionmenu in self.choose_options.items():
|
||||||
config[name] = optionmenu.currentText().strip()
|
config[name] = optionmenu.currentText().strip()
|
||||||
|
|
||||||
|
@ -357,6 +361,35 @@ class OptionFrame(QWidget):
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
def load_config(self, config: dict[str, Any]) -> None:
|
||||||
|
for name, textbox in self.string_options.items():
|
||||||
|
textbox.setText(config[name])
|
||||||
|
|
||||||
|
for name, textbox in self.int_options.items():
|
||||||
|
try:
|
||||||
|
textbox.setValue(config[name])
|
||||||
|
except ValueError:
|
||||||
|
textbox.setValue(0)
|
||||||
|
|
||||||
|
for name, optionmenu in self.choose_options.items():
|
||||||
|
optionmenu.setCurrentText(str(config[name]))
|
||||||
|
|
||||||
|
for name, checkbox in self.bool_options.items():
|
||||||
|
checkbox.setChecked(config[name])
|
||||||
|
|
||||||
|
for name, textboxes in self.list_options.items():
|
||||||
|
for i, textbox in enumerate(textboxes):
|
||||||
|
textbox.setText(config[name][i])
|
||||||
|
|
||||||
|
for name, (picker, checkbox) in self.date_time_options.items():
|
||||||
|
if config[name] is not None:
|
||||||
|
picker.setDateTime(datetime.fromisoformat(config[name]))
|
||||||
|
checkbox.setChecked(True)
|
||||||
|
else:
|
||||||
|
picker.setDateTime(datetime.now())
|
||||||
|
picker.setEnabled(False)
|
||||||
|
checkbox.setChecked(False)
|
||||||
|
|
||||||
|
|
||||||
class SourceTab(OptionFrame):
|
class SourceTab(OptionFrame):
|
||||||
def __init__(self, parent: QWidget, source_name: str, config: dict[str, Any]) -> None:
|
def __init__(self, parent: QWidget, source_name: str, config: dict[str, Any]) -> None:
|
||||||
|
@ -411,6 +444,25 @@ class GeneralConfig(OptionFrame):
|
||||||
)
|
)
|
||||||
self.add_string_option("mpv_options", "Additional MPV Arguments", config["mpv_options"])
|
self.add_string_option("mpv_options", "Additional MPV Arguments", config["mpv_options"])
|
||||||
|
|
||||||
|
if not config["show_advanced"]:
|
||||||
|
for option in [
|
||||||
|
"waiting_room_policy",
|
||||||
|
"last_song",
|
||||||
|
"preview_duration",
|
||||||
|
"key",
|
||||||
|
"mpv_options",
|
||||||
|
]:
|
||||||
|
self.rows[option][0].setVisible(False)
|
||||||
|
widget_or_layout = self.rows[option][1]
|
||||||
|
if isinstance(widget_or_layout, QWidget):
|
||||||
|
widget_or_layout.setVisible(False)
|
||||||
|
else:
|
||||||
|
for i in range(widget_or_layout.count()):
|
||||||
|
item = widget_or_layout.itemAt(i)
|
||||||
|
widget = item.widget() if item else None
|
||||||
|
if widget:
|
||||||
|
widget.setVisible(False)
|
||||||
|
|
||||||
def get_config(self) -> dict[str, Any]:
|
def get_config(self) -> dict[str, Any]:
|
||||||
config = super().get_config()
|
config = super().get_config()
|
||||||
return config
|
return config
|
||||||
|
@ -429,7 +481,7 @@ class SyngGui(QMainWindow):
|
||||||
|
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def add_buttons(self) -> None:
|
def add_buttons(self, show_advanced: bool) -> None:
|
||||||
self.buttons_layout = QHBoxLayout()
|
self.buttons_layout = QHBoxLayout()
|
||||||
self.central_layout.addLayout(self.buttons_layout)
|
self.central_layout.addLayout(self.buttons_layout)
|
||||||
|
|
||||||
|
@ -437,27 +489,78 @@ class SyngGui(QMainWindow):
|
||||||
# self.startsyng_serverbutton.clicked.connect(self.start_syng_server)
|
# self.startsyng_serverbutton.clicked.connect(self.start_syng_server)
|
||||||
# self.buttons_layout.addWidget(self.startsyng_serverbutton)
|
# self.buttons_layout.addWidget(self.startsyng_serverbutton)
|
||||||
|
|
||||||
spacer_item = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
self.resetbutton = QPushButton("Set Config to Default")
|
||||||
self.notification_label = QLabel("", self)
|
self.exportbutton = QPushButton("Export Config")
|
||||||
spacer_item2 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
self.importbutton = QPushButton("Import Config")
|
||||||
self.buttons_layout.addItem(spacer_item)
|
self.buttons_layout.addWidget(self.resetbutton)
|
||||||
self.buttons_layout.addWidget(self.notification_label)
|
self.buttons_layout.addWidget(self.exportbutton)
|
||||||
self.buttons_layout.addItem(spacer_item2)
|
self.buttons_layout.addWidget(self.importbutton)
|
||||||
|
self.resetbutton.clicked.connect(self.clear_config)
|
||||||
|
self.exportbutton.clicked.connect(self.export_config)
|
||||||
|
self.importbutton.clicked.connect(self.import_config)
|
||||||
|
if not show_advanced:
|
||||||
|
self.resetbutton.hide()
|
||||||
|
self.exportbutton.hide()
|
||||||
|
self.importbutton.hide()
|
||||||
|
|
||||||
self.savebutton = QPushButton("Apply")
|
self.show_advanced_toggle = QCheckBox("Show Advanced Options")
|
||||||
|
self.show_advanced_toggle.setChecked(show_advanced)
|
||||||
|
self.show_advanced_toggle.stateChanged.connect(self.toggle_advanced)
|
||||||
|
self.buttons_layout.addWidget(self.show_advanced_toggle)
|
||||||
|
|
||||||
|
spacer_item = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||||
|
# self.notification_label = QLabel("", self)
|
||||||
|
# spacer_item2 = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||||
|
self.buttons_layout.addItem(spacer_item)
|
||||||
|
# self.buttons_layout.addWidget(self.notification_label)
|
||||||
|
# self.buttons_layout.addItem(spacer_item2)
|
||||||
|
|
||||||
|
self.savebutton = QPushButton("Save")
|
||||||
self.savebutton.clicked.connect(self.save_config)
|
self.savebutton.clicked.connect(self.save_config)
|
||||||
self.buttons_layout.addWidget(self.savebutton)
|
self.buttons_layout.addWidget(self.savebutton)
|
||||||
|
|
||||||
self.startbutton = QPushButton("Apply and Start")
|
self.startbutton = QPushButton("Save and Start")
|
||||||
|
|
||||||
self.startbutton.clicked.connect(self.start_syng_client)
|
self.startbutton.clicked.connect(self.start_syng_client)
|
||||||
self.buttons_layout.addWidget(self.startbutton)
|
self.buttons_layout.addWidget(self.startbutton)
|
||||||
|
|
||||||
|
def toggle_advanced(self, state: bool) -> None:
|
||||||
|
self.resetbutton.setVisible(state)
|
||||||
|
self.exportbutton.setVisible(state)
|
||||||
|
self.importbutton.setVisible(state)
|
||||||
|
|
||||||
|
for option in [
|
||||||
|
"waiting_room_policy",
|
||||||
|
"last_song",
|
||||||
|
"preview_duration",
|
||||||
|
"key",
|
||||||
|
"mpv_options",
|
||||||
|
]:
|
||||||
|
self.general_config.rows[option][0].setVisible(state)
|
||||||
|
widget_or_layout = self.general_config.rows[option][1]
|
||||||
|
if isinstance(widget_or_layout, QWidget):
|
||||||
|
widget_or_layout.setVisible(state)
|
||||||
|
else:
|
||||||
|
for i in range(widget_or_layout.count()):
|
||||||
|
item = widget_or_layout.itemAt(i)
|
||||||
|
widget = item.widget() if item else None
|
||||||
|
if widget:
|
||||||
|
widget.setVisible(state)
|
||||||
|
|
||||||
|
tabbar = self.tabview.tabBar()
|
||||||
|
if not state:
|
||||||
|
if tabbar is not None:
|
||||||
|
tabbar.hide()
|
||||||
|
self.tabview.setCurrentIndex(0)
|
||||||
|
else:
|
||||||
|
if tabbar is not None:
|
||||||
|
tabbar.show()
|
||||||
|
|
||||||
def init_frame(self) -> None:
|
def init_frame(self) -> None:
|
||||||
self.frm = QHBoxLayout()
|
self.frm = QHBoxLayout()
|
||||||
self.central_layout.addLayout(self.frm)
|
self.central_layout.addLayout(self.frm)
|
||||||
|
|
||||||
def init_tabs(self) -> None:
|
def init_tabs(self, show_advanced: bool) -> None:
|
||||||
self.tabview = QTabWidget(parent=self.central_widget)
|
self.tabview = QTabWidget(parent=self.central_widget)
|
||||||
self.tabview.setAcceptDrops(False)
|
self.tabview.setAcceptDrops(False)
|
||||||
self.tabview.setTabPosition(QTabWidget.TabPosition.West)
|
self.tabview.setTabPosition(QTabWidget.TabPosition.West)
|
||||||
|
@ -470,6 +573,11 @@ class SyngGui(QMainWindow):
|
||||||
for i, source in enumerate(available_sources):
|
for i, source in enumerate(available_sources):
|
||||||
self.tabview.setTabText(i + 1, source)
|
self.tabview.setTabText(i + 1, source)
|
||||||
|
|
||||||
|
if not show_advanced:
|
||||||
|
tabbar = self.tabview.tabBar()
|
||||||
|
if tabbar is not None:
|
||||||
|
tabbar.hide()
|
||||||
|
|
||||||
self.frm.addWidget(self.tabview)
|
self.frm.addWidget(self.tabview)
|
||||||
|
|
||||||
def add_qr(self) -> None:
|
def add_qr(self) -> None:
|
||||||
|
@ -479,9 +587,14 @@ class SyngGui(QMainWindow):
|
||||||
|
|
||||||
self.qr_label = QLabel(self.qr_widget)
|
self.qr_label = QLabel(self.qr_widget)
|
||||||
self.linklabel = QLabel(self.qr_widget)
|
self.linklabel = QLabel(self.qr_widget)
|
||||||
|
self.notification_label = QLabel("", self.qr_widget)
|
||||||
|
|
||||||
self.qr_layout.addWidget(self.qr_label)
|
self.qr_layout.addWidget(self.qr_label)
|
||||||
self.qr_layout.addWidget(self.linklabel)
|
self.qr_layout.addWidget(self.linklabel)
|
||||||
|
self.qr_layout.addWidget(self.notification_label)
|
||||||
|
self.qr_layout.setAlignment(self.linklabel, Qt.AlignmentFlag.AlignCenter)
|
||||||
|
self.qr_layout.setAlignment(self.notification_label, Qt.AlignmentFlag.AlignCenter)
|
||||||
|
self.qr_layout.setAlignment(self.qr_label, Qt.AlignmentFlag.AlignCenter)
|
||||||
|
|
||||||
self.linklabel.setOpenExternalLinks(True)
|
self.linklabel.setOpenExternalLinks(True)
|
||||||
|
|
||||||
|
@ -508,46 +621,20 @@ class SyngGui(QMainWindow):
|
||||||
|
|
||||||
self.configfile = os.path.join(platformdirs.user_config_dir("syng"), "config.yaml")
|
self.configfile = os.path.join(platformdirs.user_config_dir("syng"), "config.yaml")
|
||||||
|
|
||||||
try:
|
|
||||||
with open(self.configfile, encoding="utf8") as cfile:
|
|
||||||
loaded_config = load(cfile, Loader=Loader)
|
|
||||||
except FileNotFoundError:
|
|
||||||
print("No config found, using default values")
|
|
||||||
loaded_config = {}
|
|
||||||
config: dict[str, dict[str, Any]] = {"sources": {}, "config": default_config()}
|
|
||||||
|
|
||||||
try:
|
|
||||||
config["config"] |= loaded_config["config"]
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
print("Could not load config")
|
|
||||||
|
|
||||||
if not config["config"]["secret"]:
|
|
||||||
config["config"]["secret"] = "".join(
|
|
||||||
secrets.choice(string.ascii_letters + string.digits) for _ in range(8)
|
|
||||||
)
|
|
||||||
|
|
||||||
if config["config"]["room"] == "":
|
|
||||||
config["config"]["room"] = "".join(
|
|
||||||
[random.choice(string.ascii_letters) for _ in range(6)]
|
|
||||||
).upper()
|
|
||||||
|
|
||||||
self.central_widget = QWidget(parent=self)
|
self.central_widget = QWidget(parent=self)
|
||||||
self.central_layout = QVBoxLayout(self.central_widget)
|
self.central_layout = QVBoxLayout(self.central_widget)
|
||||||
|
|
||||||
|
config = self.load_config(self.configfile)
|
||||||
|
|
||||||
self.init_frame()
|
self.init_frame()
|
||||||
self.init_tabs()
|
self.init_tabs(config["config"]["show_advanced"])
|
||||||
self.add_buttons()
|
self.add_buttons(config["config"]["show_advanced"])
|
||||||
self.add_qr()
|
self.add_qr()
|
||||||
self.add_general_config(config["config"])
|
self.add_general_config(config["config"])
|
||||||
self.tabs: dict[str, SourceTab] = {}
|
self.tabs: dict[str, SourceTab] = {}
|
||||||
|
|
||||||
for source_name in available_sources:
|
for source_name in available_sources:
|
||||||
try:
|
self.add_source_config(source_name, config["sources"][source_name])
|
||||||
source_config = loaded_config["sources"][source_name]
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
source_config = {}
|
|
||||||
|
|
||||||
self.add_source_config(source_name, source_config)
|
|
||||||
|
|
||||||
self.update_qr()
|
self.update_qr()
|
||||||
|
|
||||||
|
@ -557,6 +644,65 @@ class SyngGui(QMainWindow):
|
||||||
self.timer = QTimer()
|
self.timer = QTimer()
|
||||||
self.timer.timeout.connect(self.check_if_client_is_running)
|
self.timer.timeout.connect(self.check_if_client_is_running)
|
||||||
|
|
||||||
|
def complete_config(self, config: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
output: dict[str, dict[str, Any]] = {"sources": {}, "config": default_config()}
|
||||||
|
|
||||||
|
try:
|
||||||
|
output["config"] |= config["config"]
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
print("Could not load config")
|
||||||
|
|
||||||
|
if not output["config"]["secret"]:
|
||||||
|
output["config"]["secret"] = "".join(
|
||||||
|
secrets.choice(string.ascii_letters + string.digits) for _ in range(8)
|
||||||
|
)
|
||||||
|
|
||||||
|
if output["config"]["room"] == "":
|
||||||
|
output["config"]["room"] = "".join(
|
||||||
|
[random.choice(string.ascii_letters) for _ in range(6)]
|
||||||
|
).upper()
|
||||||
|
|
||||||
|
for source_name, source in available_sources.items():
|
||||||
|
source_config = {}
|
||||||
|
for name, option in source.config_schema.items():
|
||||||
|
source_config[name] = option.default
|
||||||
|
|
||||||
|
output["sources"][source_name] = source_config
|
||||||
|
|
||||||
|
try:
|
||||||
|
output["sources"][source_name] |= config["sources"][source_name]
|
||||||
|
except (KeyError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def clear_config(self) -> None:
|
||||||
|
answer = QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Set to Config to Default",
|
||||||
|
"Are you sure you want to clear the config?",
|
||||||
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||||
|
)
|
||||||
|
if answer == QMessageBox.StandardButton.Yes:
|
||||||
|
self.update_config(self.complete_config({"config": {}, "sources": {}}))
|
||||||
|
|
||||||
|
def load_config(self, filename: str) -> dict[str, Any]:
|
||||||
|
try:
|
||||||
|
with open(filename, encoding="utf8") as cfile:
|
||||||
|
loaded_config = load(cfile, Loader=Loader)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print("No config found, using default values")
|
||||||
|
loaded_config = {}
|
||||||
|
|
||||||
|
return self.complete_config(loaded_config)
|
||||||
|
|
||||||
|
def update_config(self, config: dict[str, Any]) -> None:
|
||||||
|
self.general_config.load_config(config["config"])
|
||||||
|
for source_name, source_config in config["sources"].items():
|
||||||
|
self.tabs[source_name].load_config(source_config)
|
||||||
|
|
||||||
|
self.update_qr()
|
||||||
|
|
||||||
def save_config(self) -> None:
|
def save_config(self) -> None:
|
||||||
os.makedirs(os.path.dirname(self.configfile), exist_ok=True)
|
os.makedirs(os.path.dirname(self.configfile), exist_ok=True)
|
||||||
|
|
||||||
|
@ -568,10 +714,27 @@ class SyngGui(QMainWindow):
|
||||||
for source, tab in self.tabs.items():
|
for source, tab in self.tabs.items():
|
||||||
sources[source] = tab.get_config()
|
sources[source] = tab.get_config()
|
||||||
|
|
||||||
general_config = self.general_config.get_config()
|
general_config = self.general_config.get_config() | {
|
||||||
|
"show_advanced": self.show_advanced_toggle.isChecked()
|
||||||
|
}
|
||||||
|
|
||||||
return {"sources": sources, "config": general_config}
|
return {"sources": sources, "config": general_config}
|
||||||
|
|
||||||
|
def import_config(self) -> None:
|
||||||
|
filename = QFileDialog.getOpenFileName(self, "Open File", "", "YAML Files (*.yaml)")[0]
|
||||||
|
|
||||||
|
if filename:
|
||||||
|
config = self.load_config(filename)
|
||||||
|
self.update_config(config)
|
||||||
|
|
||||||
|
def export_config(self) -> None:
|
||||||
|
filename = QFileDialog.getSaveFileName(self, "Save File", "", "YAML Files (*.yaml)")[0]
|
||||||
|
if filename:
|
||||||
|
config = self.gather_config()
|
||||||
|
|
||||||
|
with open(filename, "w", encoding="utf-8") as f:
|
||||||
|
dump(config, f, Dumper=Dumper)
|
||||||
|
|
||||||
def check_if_client_is_running(self) -> None:
|
def check_if_client_is_running(self) -> None:
|
||||||
if self.syng_client is None:
|
if self.syng_client is None:
|
||||||
self.timer.stop()
|
self.timer.stop()
|
||||||
|
@ -674,6 +837,9 @@ def run_gui() -> None:
|
||||||
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
|
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
|
||||||
base_dir = sys._MEIPASS
|
base_dir = sys._MEIPASS
|
||||||
|
|
||||||
|
# initialize cache dir
|
||||||
|
os.makedirs(platformdirs.user_cache_dir("syng"), exist_ok=True)
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||||
|
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
|
@ -683,3 +849,7 @@ def run_gui() -> None:
|
||||||
window = SyngGui()
|
window = SyngGui()
|
||||||
window.show()
|
window.show()
|
||||||
app.exec()
|
app.exec()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run_gui()
|
||||||
|
|
Loading…
Add table
Reference in a new issue