Implemented GUI for syng #2
1 changed files with 66 additions and 41 deletions
107
syng/gui.py
107
syng/gui.py
|
@ -1,8 +1,10 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from collections.abc import Callable
|
||||||
from datetime import datetime, date, time
|
from datetime import datetime, date, time
|
||||||
import os
|
import os
|
||||||
import builtins
|
import builtins
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from typing import Any, Optional
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import PIL
|
import PIL
|
||||||
from yaml import load, Loader
|
from yaml import load, Loader
|
||||||
|
@ -11,7 +13,7 @@ import customtkinter
|
||||||
import qrcode
|
import qrcode
|
||||||
import secrets
|
import secrets
|
||||||
import string
|
import string
|
||||||
from tkinter import PhotoImage, filedialog
|
from tkinter import PhotoImage, Tk, filedialog
|
||||||
from tkcalendar import Calendar
|
from tkcalendar import Calendar
|
||||||
from tktimepicker import SpinTimePickerOld, AnalogPicker, AnalogThemes
|
from tktimepicker import SpinTimePickerOld, AnalogPicker, AnalogThemes
|
||||||
from tktimepicker import constants
|
from tktimepicker import constants
|
||||||
|
@ -23,7 +25,11 @@ from .server import main as server_main
|
||||||
|
|
||||||
|
|
||||||
class DateAndTimePickerWindow(customtkinter.CTkToplevel):
|
class DateAndTimePickerWindow(customtkinter.CTkToplevel):
|
||||||
def __init__(self, parent, input_field):
|
def __init__(
|
||||||
|
self,
|
||||||
|
parent: customtkinter.CTkFrame | customtkinter.CTkScrollableFrame,
|
||||||
|
input_field: customtkinter.CTkTextbox,
|
||||||
|
) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -56,7 +62,7 @@ class DateAndTimePickerWindow(customtkinter.CTkToplevel):
|
||||||
)
|
)
|
||||||
button.pack(expand=True, fill="x")
|
button.pack(expand=True, fill="x")
|
||||||
|
|
||||||
def insert(self, input_field: customtkinter.CTkTextbox):
|
def insert(self, input_field: customtkinter.CTkTextbox) -> None:
|
||||||
input_field.delete("0.0", "end")
|
input_field.delete("0.0", "end")
|
||||||
selected_date = self.calendar.selection_get()
|
selected_date = self.calendar.selection_get()
|
||||||
if not isinstance(selected_date, date):
|
if not isinstance(selected_date, date):
|
||||||
|
@ -73,12 +79,12 @@ class DateAndTimePickerWindow(customtkinter.CTkToplevel):
|
||||||
|
|
||||||
|
|
||||||
class OptionFrame(customtkinter.CTkScrollableFrame):
|
class OptionFrame(customtkinter.CTkScrollableFrame):
|
||||||
def add_option_label(self, text):
|
def add_option_label(self, text: str) -> None:
|
||||||
customtkinter.CTkLabel(self, text=text, justify="left").grid(
|
customtkinter.CTkLabel(self, text=text, justify="left").grid(
|
||||||
column=0, row=self.number_of_options, padx=5, pady=5, sticky="ne"
|
column=0, row=self.number_of_options, padx=5, pady=5, sticky="ne"
|
||||||
)
|
)
|
||||||
|
|
||||||
def add_bool_option(self, name, description, value=False):
|
def add_bool_option(self, name: str, description: str, value: bool = False) -> None:
|
||||||
self.add_option_label(description)
|
self.add_option_label(description)
|
||||||
self.bool_options[name] = customtkinter.CTkCheckBox(
|
self.bool_options[name] = customtkinter.CTkCheckBox(
|
||||||
self,
|
self,
|
||||||
|
@ -93,7 +99,13 @@ class OptionFrame(customtkinter.CTkScrollableFrame):
|
||||||
self.bool_options[name].grid(column=1, row=self.number_of_options, sticky="EW")
|
self.bool_options[name].grid(column=1, row=self.number_of_options, sticky="EW")
|
||||||
self.number_of_options += 1
|
self.number_of_options += 1
|
||||||
|
|
||||||
def add_string_option(self, name, description, value="", callback=None):
|
def add_string_option(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
description: str,
|
||||||
|
value: str = "",
|
||||||
|
callback: Optional[Callable[..., None]] = None,
|
||||||
|
):
|
||||||
self.add_option_label(description)
|
self.add_option_label(description)
|
||||||
if value is None:
|
if value is None:
|
||||||
value = ""
|
value = ""
|
||||||
|
@ -110,11 +122,22 @@ class OptionFrame(customtkinter.CTkScrollableFrame):
|
||||||
self.string_options[name].bind("<ButtonRelease>", callback)
|
self.string_options[name].bind("<ButtonRelease>", callback)
|
||||||
self.number_of_options += 1
|
self.number_of_options += 1
|
||||||
|
|
||||||
def del_list_element(self, name, element, frame):
|
def del_list_element(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
element: customtkinter.CTkTextbox,
|
||||||
|
frame: customtkinter.CTkFrame,
|
||||||
|
) -> None:
|
||||||
self.list_options[name].remove(element)
|
self.list_options[name].remove(element)
|
||||||
frame.destroy()
|
frame.destroy()
|
||||||
|
|
||||||
def add_list_element(self, name, frame, init, callback):
|
def add_list_element(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
frame: customtkinter.CTkFrame,
|
||||||
|
init: str,
|
||||||
|
callback: Optional[Callable[..., None]],
|
||||||
|
) -> None:
|
||||||
input_and_minus = customtkinter.CTkFrame(frame)
|
input_and_minus = customtkinter.CTkFrame(frame)
|
||||||
input_and_minus.pack(side="top", fill="x", expand=True)
|
input_and_minus.pack(side="top", fill="x", expand=True)
|
||||||
input_field = customtkinter.CTkTextbox(input_and_minus, wrap="none", height=1)
|
input_field = customtkinter.CTkTextbox(input_and_minus, wrap="none", height=1)
|
||||||
|
@ -133,7 +156,13 @@ class OptionFrame(customtkinter.CTkScrollableFrame):
|
||||||
minus_button.pack(side="right")
|
minus_button.pack(side="right")
|
||||||
self.list_options[name].append(input_field)
|
self.list_options[name].append(input_field)
|
||||||
|
|
||||||
def add_list_option(self, name, description, value=[], callback=None):
|
def add_list_option(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
description: str,
|
||||||
|
value: list[str] = [],
|
||||||
|
callback: Optional[Callable[..., None]] = None,
|
||||||
|
) -> None:
|
||||||
self.add_option_label(description)
|
self.add_option_label(description)
|
||||||
|
|
||||||
frame = customtkinter.CTkFrame(self)
|
frame = customtkinter.CTkFrame(self)
|
||||||
|
@ -151,7 +180,9 @@ class OptionFrame(customtkinter.CTkScrollableFrame):
|
||||||
|
|
||||||
self.number_of_options += 1
|
self.number_of_options += 1
|
||||||
|
|
||||||
def add_choose_option(self, name, description, values, value=""):
|
def add_choose_option(
|
||||||
|
self, name: str, description: str, values: list[str], value: str = ""
|
||||||
|
) -> None:
|
||||||
self.add_option_label(description)
|
self.add_option_label(description)
|
||||||
self.choose_options[name] = customtkinter.CTkOptionMenu(self, values=values)
|
self.choose_options[name] = customtkinter.CTkOptionMenu(self, values=values)
|
||||||
self.choose_options[name].grid(
|
self.choose_options[name].grid(
|
||||||
|
@ -160,7 +191,9 @@ class OptionFrame(customtkinter.CTkScrollableFrame):
|
||||||
self.choose_options[name].set(value)
|
self.choose_options[name].set(value)
|
||||||
self.number_of_options += 1
|
self.number_of_options += 1
|
||||||
|
|
||||||
def open_date_and_time_picker(self, name, input_field):
|
def open_date_and_time_picker(
|
||||||
|
self, name: str, input_field: customtkinter.CTkTextbox
|
||||||
|
) -> None:
|
||||||
if (
|
if (
|
||||||
name not in self.date_and_time_pickers
|
name not in self.date_and_time_pickers
|
||||||
or not self.date_and_time_pickers[name].winfo_exists()
|
or not self.date_and_time_pickers[name].winfo_exists()
|
||||||
|
@ -171,7 +204,7 @@ class OptionFrame(customtkinter.CTkScrollableFrame):
|
||||||
else:
|
else:
|
||||||
self.date_and_time_pickers[name].focus()
|
self.date_and_time_pickers[name].focus()
|
||||||
|
|
||||||
def add_date_time_option(self, name, description, value):
|
def add_date_time_option(self, name: str, description: str, value: str) -> None:
|
||||||
self.add_option_label(description)
|
self.add_option_label(description)
|
||||||
self.date_time_options[name] = None
|
self.date_time_options[name] = None
|
||||||
input_and_button = customtkinter.CTkFrame(self)
|
input_and_button = customtkinter.CTkFrame(self)
|
||||||
|
@ -193,7 +226,7 @@ class OptionFrame(customtkinter.CTkScrollableFrame):
|
||||||
button.pack(side="right")
|
button.pack(side="right")
|
||||||
self.number_of_options += 1
|
self.number_of_options += 1
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent: customtkinter.CTkFrame) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.columnconfigure((1,), weight=1)
|
self.columnconfigure((1,), weight=1)
|
||||||
self.number_of_options = 0
|
self.number_of_options = 0
|
||||||
|
@ -204,7 +237,7 @@ class OptionFrame(customtkinter.CTkScrollableFrame):
|
||||||
self.date_time_options = {}
|
self.date_time_options = {}
|
||||||
self.date_and_time_pickers = {}
|
self.date_and_time_pickers = {}
|
||||||
|
|
||||||
def get_config(self):
|
def get_config(self) -> dict[str, Any]:
|
||||||
config = {}
|
config = {}
|
||||||
for name, textbox in self.string_options.items():
|
for name, textbox in self.string_options.items():
|
||||||
config[name] = textbox.get("0.0", "end").strip()
|
config[name] = textbox.get("0.0", "end").strip()
|
||||||
|
@ -224,19 +257,9 @@ class OptionFrame(customtkinter.CTkScrollableFrame):
|
||||||
|
|
||||||
|
|
||||||
class SourceTab(OptionFrame):
|
class SourceTab(OptionFrame):
|
||||||
def updateStrVar(self, var: str, element: customtkinter.CTkTextbox, event):
|
def __init__(
|
||||||
value = element.get("0.0", "end").strip()
|
self, parent: customtkinter.CTkFrame, source_name: str, config: dict[str, Any]
|
||||||
self.vars[var] = value
|
) -> None:
|
||||||
|
|
||||||
def updateBoolVar(self, var: str, element: customtkinter.CTkCheckBox, event):
|
|
||||||
value = True if element.get() == 1 else False
|
|
||||||
self.vars[var] = value
|
|
||||||
|
|
||||||
def updateListVar(self, var: str, element: customtkinter.CTkTextbox, event):
|
|
||||||
value = [v.strip() for v in element.get("0.0", "end").strip().split(",")]
|
|
||||||
self.vars[var] = value
|
|
||||||
|
|
||||||
def __init__(self, parent, source_name, config):
|
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
source = available_sources[source_name]
|
source = available_sources[source_name]
|
||||||
self.vars: dict[str, str | bool | list[str]] = {}
|
self.vars: dict[str, str | bool | list[str]] = {}
|
||||||
|
@ -252,7 +275,12 @@ class SourceTab(OptionFrame):
|
||||||
|
|
||||||
|
|
||||||
class GeneralConfig(OptionFrame):
|
class GeneralConfig(OptionFrame):
|
||||||
def __init__(self, parent, config, callback):
|
def __init__(
|
||||||
|
self,
|
||||||
|
parent: customtkinter.CTkFrame,
|
||||||
|
config: dict[str, Any],
|
||||||
|
callback: Callable[..., None],
|
||||||
|
) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
self.add_string_option("server", "Server", config["server"], callback)
|
self.add_string_option("server", "Server", config["server"], callback)
|
||||||
|
@ -264,15 +292,12 @@ class GeneralConfig(OptionFrame):
|
||||||
["forced", "optional", "none"],
|
["forced", "optional", "none"],
|
||||||
str(config["waiting_room_policy"]).lower(),
|
str(config["waiting_room_policy"]).lower(),
|
||||||
)
|
)
|
||||||
# self.add_string_option(
|
|
||||||
# "last_song", "Time of last song\nin ISO-8601", config["last_song"]
|
|
||||||
# )
|
|
||||||
self.add_date_time_option("last_song", "Time of last song", config["last_song"])
|
self.add_date_time_option("last_song", "Time of last song", config["last_song"])
|
||||||
self.add_string_option(
|
self.add_string_option(
|
||||||
"preview_duration", "Preview Duration", config["preview_duration"]
|
"preview_duration", "Preview Duration", config["preview_duration"]
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_config(self):
|
def get_config(self) -> dict[str, Any]:
|
||||||
config = super().get_config()
|
config = super().get_config()
|
||||||
try:
|
try:
|
||||||
config["preview_duration"] = int(config["preview_duration"])
|
config["preview_duration"] = int(config["preview_duration"])
|
||||||
|
@ -283,10 +308,10 @@ class GeneralConfig(OptionFrame):
|
||||||
|
|
||||||
|
|
||||||
class SyngGui(customtkinter.CTk):
|
class SyngGui(customtkinter.CTk):
|
||||||
def loadConfig(self):
|
def loadConfig(self) -> None:
|
||||||
filedialog.askopenfilename()
|
filedialog.askopenfilename()
|
||||||
|
|
||||||
def on_close(self):
|
def on_close(self) -> None:
|
||||||
if self.server is not None:
|
if self.server is not None:
|
||||||
self.server.kill()
|
self.server.kill()
|
||||||
|
|
||||||
|
@ -296,7 +321,7 @@ class SyngGui(customtkinter.CTk):
|
||||||
self.withdraw()
|
self.withdraw()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__(className="Syng")
|
super().__init__(className="Syng")
|
||||||
self.protocol("WM_DELETE_WINDOW", self.on_close)
|
self.protocol("WM_DELETE_WINDOW", self.on_close)
|
||||||
|
|
||||||
|
@ -385,7 +410,7 @@ class SyngGui(customtkinter.CTk):
|
||||||
|
|
||||||
self.updateQr()
|
self.updateQr()
|
||||||
|
|
||||||
def start_client(self):
|
def start_client(self) -> None:
|
||||||
if self.client is None:
|
if self.client is None:
|
||||||
sources = {}
|
sources = {}
|
||||||
for source, tab in self.tabs.items():
|
for source, tab in self.tabs.items():
|
||||||
|
@ -404,18 +429,18 @@ class SyngGui(customtkinter.CTk):
|
||||||
self.client = None
|
self.client = None
|
||||||
self.startbutton.configure(text="Start")
|
self.startbutton.configure(text="Start")
|
||||||
|
|
||||||
def start_server(self):
|
def start_server(self) -> None:
|
||||||
self.server = multiprocessing.Process(target=server_main)
|
self.server = multiprocessing.Process(target=server_main)
|
||||||
self.server.start()
|
self.server.start()
|
||||||
|
|
||||||
def open_web(self):
|
def open_web(self) -> None:
|
||||||
config = self.general_config.get_config()
|
config = self.general_config.get_config()
|
||||||
server = config["server"]
|
server = config["server"]
|
||||||
server += "" if server.endswith("/") else "/"
|
server += "" if server.endswith("/") else "/"
|
||||||
room = config["room"]
|
room = config["room"]
|
||||||
webbrowser.open(server + room)
|
webbrowser.open(server + room)
|
||||||
|
|
||||||
def changeQr(self, data: str):
|
def changeQr(self, data: str) -> None:
|
||||||
qr = qrcode.QRCode(box_size=20, border=2)
|
qr = qrcode.QRCode(box_size=20, border=2)
|
||||||
qr.add_data(data)
|
qr.add_data(data)
|
||||||
qr.make()
|
qr.make()
|
||||||
|
@ -424,7 +449,7 @@ class SyngGui(customtkinter.CTk):
|
||||||
tkQrcode = customtkinter.CTkImage(light_image=image, size=(280, 280))
|
tkQrcode = customtkinter.CTkImage(light_image=image, size=(280, 280))
|
||||||
self.qrlabel.configure(image=tkQrcode)
|
self.qrlabel.configure(image=tkQrcode)
|
||||||
|
|
||||||
def updateQr(self, _evt=None):
|
def updateQr(self, _evt: None = None) -> None:
|
||||||
config = self.general_config.get_config()
|
config = self.general_config.get_config()
|
||||||
server = config["server"]
|
server = config["server"]
|
||||||
server += "" if server.endswith("/") else "/"
|
server += "" if server.endswith("/") else "/"
|
||||||
|
@ -433,7 +458,7 @@ class SyngGui(customtkinter.CTk):
|
||||||
self.changeQr(server + room)
|
self.changeQr(server + room)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main() -> None:
|
||||||
SyngGui().mainloop()
|
SyngGui().mainloop()
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue