removed tk and added qt gui
This commit is contained in:
parent
7689172494
commit
50585463fc
3 changed files with 291 additions and 288 deletions
136
poetry.lock
generated
136
poetry.lock
generated
|
@ -252,20 +252,6 @@ docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphi
|
|||
tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
||||
tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
|
||||
|
||||
[[package]]
|
||||
name = "babel"
|
||||
version = "2.16.0"
|
||||
description = "Internationalization utilities"
|
||||
optional = true
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "babel-2.16.0-py3-none-any.whl", hash = "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b"},
|
||||
{file = "babel-2.16.0.tar.gz", hash = "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "bidict"
|
||||
version = "0.23.1"
|
||||
|
@ -608,35 +594,6 @@ files = [
|
|||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "customtkinter"
|
||||
version = "5.2.2"
|
||||
description = "Create modern looking GUIs with Python"
|
||||
optional = true
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "customtkinter-5.2.2-py3-none-any.whl", hash = "sha256:14ad3e7cd3cb3b9eb642b9d4e8711ae80d3f79fb82545ad11258eeffb2e6b37c"},
|
||||
{file = "customtkinter-5.2.2.tar.gz", hash = "sha256:fd8db3bafa961c982ee6030dba80b4c2e25858630756b513986db19113d8d207"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
darkdetect = "*"
|
||||
packaging = "*"
|
||||
|
||||
[[package]]
|
||||
name = "darkdetect"
|
||||
version = "0.8.0"
|
||||
description = "Detect OS Dark Mode from Python"
|
||||
optional = true
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "darkdetect-0.8.0-py3-none-any.whl", hash = "sha256:a7509ccf517eaad92b31c214f593dbcf138ea8a43b2935406bbd565e15527a85"},
|
||||
{file = "darkdetect-0.8.0.tar.gz", hash = "sha256:b5428e1170263eb5dea44c25dc3895edd75e6f52300986353cd63533fe7df8b1"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
macos-listener = ["pyobjc-framework-Cocoa"]
|
||||
|
||||
[[package]]
|
||||
name = "dill"
|
||||
version = "0.3.8"
|
||||
|
@ -1315,6 +1272,70 @@ files = [
|
|||
{file = "pypng-0.20220715.0.tar.gz", hash = "sha256:739c433ba96f078315de54c0db975aee537cbc3e1d0ae4ed9aab0ca1e427e2c1"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyqt6"
|
||||
version = "6.7.1"
|
||||
description = "Python bindings for the Qt cross platform application toolkit"
|
||||
optional = true
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "PyQt6-6.7.1-1-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7f397f4b38b23b5588eb2c0933510deb953d96b1f0323a916c4839c2a66ccccc"},
|
||||
{file = "PyQt6-6.7.1-1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2f202b7941aa74e5c7e1463a6f27d9131dbc1e6cabe85571d7364f5b3de7397"},
|
||||
{file = "PyQt6-6.7.1-cp38-abi3-macosx_11_0_universal2.whl", hash = "sha256:f053378e3aef6248fa612c8afddda17f942fb63f9fe8a9aeb2a6b6b4cbb0eba9"},
|
||||
{file = "PyQt6-6.7.1-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0adb7914c732ad1dee46d9cec838a98cb2b11bc38cc3b7b36fbd8701ae64bf47"},
|
||||
{file = "PyQt6-6.7.1-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2d771fa0981514cb1ee937633dfa64f14caa902707d9afffab66677f3a73e3da"},
|
||||
{file = "PyQt6-6.7.1-cp38-abi3-win_amd64.whl", hash = "sha256:fa3954698233fe286a8afc477b84d8517f0788eb46b74da69d3ccc0170d3714c"},
|
||||
{file = "PyQt6-6.7.1.tar.gz", hash = "sha256:3672a82ccd3a62e99ab200a13903421e2928e399fda25ced98d140313ad59cb9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
PyQt6-Qt6 = ">=6.7.0,<6.8.0"
|
||||
PyQt6-sip = ">=13.8,<14"
|
||||
|
||||
[[package]]
|
||||
name = "pyqt6-qt6"
|
||||
version = "6.7.2"
|
||||
description = "The subset of a Qt installation needed by PyQt6."
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "PyQt6_Qt6-6.7.2-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:065415589219a2f364aba29d6a98920bb32810286301acbfa157e522d30369e3"},
|
||||
{file = "PyQt6_Qt6-6.7.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7f817efa86a0e8eda9152c85b73405463fbf3266299090f32bbb2266da540ead"},
|
||||
{file = "PyQt6_Qt6-6.7.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:05f2c7d195d316d9e678a92ecac0252a24ed175bd2444cc6077441807d756580"},
|
||||
{file = "PyQt6_Qt6-6.7.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:fc93945eaef4536d68bd53566535efcbe78a7c05c2a533790a8fd022bac8bfaa"},
|
||||
{file = "PyQt6_Qt6-6.7.2-py3-none-win_amd64.whl", hash = "sha256:b2d7e5ddb1b9764cd60f1d730fa7bf7a1f0f61b2630967c81761d3d0a5a8a2e0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyqt6-sip"
|
||||
version = "13.8.0"
|
||||
description = "The sip module support for PyQt6"
|
||||
optional = true
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "PyQt6_sip-13.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cedd554c643e54c4c2e12b5874781a87441a1b405acf3650a4a2e1df42aae231"},
|
||||
{file = "PyQt6_sip-13.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f57275b5af774529f9838adcfb58869ba3ebdaf805daea113bb0697a96a3f3cb"},
|
||||
{file = "PyQt6_sip-13.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:835ed22eab977f75fd77e60d4ff308a1fa794b1d0c04849311f36d2a080cdf3b"},
|
||||
{file = "PyQt6_sip-13.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d8b22a6850917c68ce83fc152a8b606ecb2efaaeed35be53110468885d6cdd9d"},
|
||||
{file = "PyQt6_sip-13.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b203b6fbae4a8f2d27f35b7df46200057033d9ecd9134bcf30e3eab66d43572c"},
|
||||
{file = "PyQt6_sip-13.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:beaddc1ec96b342f4e239702f91802706a80cb403166c2da318cec4ad8b790cb"},
|
||||
{file = "PyQt6_sip-13.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5c086b7c9c7996ea9b7522646cc24eebbf3591ec9dd38f65c0a3fdb0dbeaac7"},
|
||||
{file = "PyQt6_sip-13.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:dd168667addf01f8a4b0fa7755323e43e4cd12ca4bade558c61f713a5d48ba1a"},
|
||||
{file = "PyQt6_sip-13.8.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:33d9b399fc9c9dc99496266842b0fb2735d924604774e97cf9b555667cc0fc59"},
|
||||
{file = "PyQt6_sip-13.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:056af69d1d8d28d5968066ec5da908afd82fc0be07b67cf2b84b9f02228416ce"},
|
||||
{file = "PyQt6_sip-13.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:08dd81037a2864982ece2bf9891f3bf4558e247034e112993ea1a3fe239458cb"},
|
||||
{file = "PyQt6_sip-13.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:fbb249b82c53180f1420571ece5dc24fea1188ba435923edd055599dffe7abfb"},
|
||||
{file = "PyQt6_sip-13.8.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:6bce6bc5870d9e87efe5338b1ee4a7b9d7d26cdd16a79a5757d80b6f25e71edc"},
|
||||
{file = "PyQt6_sip-13.8.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd81144b0770084e8005d3a121c9382e6f9bc8d0bb320dd618718ffe5090e0e6"},
|
||||
{file = "PyQt6_sip-13.8.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:755beb5d271d081e56618fb30342cdd901464f721450495cb7cb0212764da89e"},
|
||||
{file = "PyQt6_sip-13.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:7a0bbc0918eab5b6351735d40cf22cbfa5aa2476b55e0d5fe881aeed7d871c29"},
|
||||
{file = "PyQt6_sip-13.8.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7f84c472afdc7d316ff683f63129350d645ef82d9b3fd75a609b08472d1f7291"},
|
||||
{file = "PyQt6_sip-13.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1bf29e95f10a8a00819dac804ca7e5eba5fc1769adcd74c837c11477bf81954"},
|
||||
{file = "PyQt6_sip-13.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9ea9223c94906efd68148f12ae45b51a21d67e86704225ddc92bce9c54e4d93c"},
|
||||
{file = "PyQt6_sip-13.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:2559afa68825d08de09d71c42f3b6ad839dcc30f91e7c6d0785e07830d5541a5"},
|
||||
{file = "PyQt6_sip-13.8.0.tar.gz", hash = "sha256:2f74cf3d6d9cab5152bd9f49d570b2dfb87553ebb5c4919abfde27f5b9fd69d4"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-engineio"
|
||||
version = "4.9.1"
|
||||
|
@ -1575,31 +1596,6 @@ files = [
|
|||
{file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tkcalendar"
|
||||
version = "1.6.1"
|
||||
description = "Calendar and DateEntry widgets for Tkinter"
|
||||
optional = true
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "tkcalendar-1.6.1-py3-none-any.whl", hash = "sha256:9d3a80816a7b32d64fab696fa3d2a007fb23c87953267d5e343a38ff4cd7c15c"},
|
||||
{file = "tkcalendar-1.6.1.tar.gz", hash = "sha256:5edf958c0a59429e90309e9b805b2e229192bbcab952460247204d7030eea5cf"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
babel = "*"
|
||||
|
||||
[[package]]
|
||||
name = "tktimepicker"
|
||||
version = "2.0.2"
|
||||
description = "This package provides you with easy to customize timepickers"
|
||||
optional = true
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "tkTimePicker-2.0.2-py3-none-any.whl", hash = "sha256:1c8232dcf1314815414a6c9cb69b1e277cc289d5989952ff98e61ba18a9c3150"},
|
||||
{file = "tkTimePicker-2.0.2.tar.gz", hash = "sha256:b8a0d7137f6c660f9886d2cd0141c13bc334ba92578e09c3b75f3c46728066ef"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.0.1"
|
||||
|
@ -1918,10 +1914,10 @@ static-analysis = ["autopep8 (>=2.0,<3.0)", "ruff (>=0.5.0,<0.6.0)"]
|
|||
test = ["pytest (>=8.1,<9.0)"]
|
||||
|
||||
[extras]
|
||||
client = ["customtkinter", "minio", "mutagen", "packaging", "pillow", "platformdirs", "pymediainfo", "pyyaml", "qrcode", "tkcalendar", "tktimepicker"]
|
||||
client = ["minio", "mutagen", "packaging", "pillow", "platformdirs", "pymediainfo", "pyqt6", "pyyaml", "qrcode"]
|
||||
server = ["alt-profanity-check"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "3310779a7a61511f14b32d0008db391c5eb0200c67187e61fd4d46cd1ba17677"
|
||||
content-hash = "2c3d2e35ed8bf2563a078971cede2cc5ba60f9802631ee6f1f5761777dc7e3a5"
|
||||
|
|
|
@ -18,15 +18,13 @@ yt-dlp = { version = "*"}
|
|||
minio = { version = "^7.2.0", optional = true }
|
||||
mutagen = { version = "^1.47.0", optional = true }
|
||||
pillow = { version = "^10.1.0", optional = true}
|
||||
customtkinter = { version = "^5.2.1", optional = true}
|
||||
qrcode = { version = "^7.4.2", optional = true }
|
||||
pymediainfo = { version = "^6.1.0", optional = true }
|
||||
pyyaml = { version = "^6.0.1", optional = true }
|
||||
tkcalendar = { version = "^1.6.1", optional = true }
|
||||
tktimepicker = { version = "^2.0.2", optional = true }
|
||||
platformdirs = { version = "^4.0.0", optional = true }
|
||||
packaging = {version = "^23.2", optional = true}
|
||||
alt-profanity-check = {version = "^1.4.1", optional = true}
|
||||
pyqt6 = {version = "^6.7.1", optional = true}
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
types-pyyaml = "^6.0.12.12"
|
||||
|
@ -36,9 +34,9 @@ pylint = "^3.2.7"
|
|||
|
||||
|
||||
[tool.poetry.extras]
|
||||
client = ["minio", "mutagen", "pillow", "customtkinter", "qrcode",
|
||||
"pymediainfo", "pyyaml", "tkcalendar", "tktimepicker", "platformdirs",
|
||||
"packaging"]
|
||||
client = ["minio", "mutagen", "pillow", "qrcode",
|
||||
"pymediainfo", "pyyaml", "platformdirs",
|
||||
"packaging", "pyqt6"]
|
||||
server = ["alt-profanity-check"]
|
||||
|
||||
[build-system]
|
||||
|
|
433
syng/gui.py
433
syng/gui.py
|
@ -1,7 +1,8 @@
|
|||
from argparse import Namespace
|
||||
from io import BytesIO
|
||||
from multiprocessing import Process
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime, date, time
|
||||
from datetime import datetime
|
||||
import os
|
||||
import builtins
|
||||
from functools import partial
|
||||
|
@ -11,12 +12,26 @@ import multiprocessing
|
|||
import secrets
|
||||
import string
|
||||
|
||||
from PIL import ImageTk
|
||||
from PyQt6.QtGui import QCloseEvent, QIcon, QPixmap
|
||||
from PyQt6.QtWidgets import (
|
||||
QApplication,
|
||||
QCheckBox,
|
||||
QComboBox,
|
||||
QDateTimeEdit,
|
||||
QFormLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QMainWindow,
|
||||
QPushButton,
|
||||
QSizePolicy,
|
||||
QSpacerItem,
|
||||
QTabWidget,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from yaml import dump, load, Loader, Dumper
|
||||
import customtkinter
|
||||
from qrcode.main import QRCode
|
||||
from tkcalendar import Calendar
|
||||
from tktimepicker import AnalogPicker, AnalogThemes, constants
|
||||
import platformdirs
|
||||
|
||||
from .client import create_async_and_start_client, default_config
|
||||
|
@ -34,132 +49,102 @@ except ImportError:
|
|||
SERVER_AVAILABLE = False
|
||||
|
||||
|
||||
class DateAndTimePickerWindow(customtkinter.CTkToplevel): # type: ignore
|
||||
def __init__(
|
||||
self,
|
||||
parent: customtkinter.CTkFrame | customtkinter.CTkScrollableFrame,
|
||||
input_field: customtkinter.CTkTextbox,
|
||||
) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
try:
|
||||
iso_string = input_field.get("0.0", "end").strip()
|
||||
selected = datetime.fromisoformat(iso_string)
|
||||
except ValueError:
|
||||
selected = datetime.now()
|
||||
|
||||
self.calendar = Calendar(self)
|
||||
self.calendar.pack(
|
||||
expand=True,
|
||||
fill="both",
|
||||
)
|
||||
self.timepicker = AnalogPicker(
|
||||
self,
|
||||
type=constants.HOURS12,
|
||||
period=constants.AM if selected.hour < 12 else constants.PM,
|
||||
)
|
||||
theme = AnalogThemes(self.timepicker)
|
||||
theme.setDracula()
|
||||
|
||||
self.calendar.selection_set(selected)
|
||||
self.timepicker.setHours(selected.hour % 12)
|
||||
self.timepicker.setMinutes(selected.minute)
|
||||
|
||||
self.timepicker.pack(expand=True, fill="both")
|
||||
|
||||
button = customtkinter.CTkButton(self, text="Ok", command=partial(self.insert, input_field))
|
||||
button.pack(expand=True, fill="x")
|
||||
|
||||
def insert(self, input_field: customtkinter.CTkTextbox) -> None:
|
||||
input_field.delete("0.0", "end")
|
||||
selected_date = self.calendar.selection_get()
|
||||
if not isinstance(selected_date, date):
|
||||
return
|
||||
hours, minutes, ampm = self.timepicker.time()
|
||||
hours = hours % 12
|
||||
if ampm == "PM":
|
||||
hours = hours + 12
|
||||
|
||||
selected_datetime = datetime.combine(selected_date, time(hours, minutes))
|
||||
input_field.insert("0.0", selected_datetime.isoformat())
|
||||
self.withdraw()
|
||||
self.destroy()
|
||||
|
||||
|
||||
class OptionFrame(customtkinter.CTkScrollableFrame): # type:ignore
|
||||
def add_option_label(self, text: str) -> None:
|
||||
customtkinter.CTkLabel(self, text=text, justify="left").grid(
|
||||
column=0, row=self.number_of_options, padx=5, pady=5, sticky="ne"
|
||||
)
|
||||
|
||||
# TODO: ScrollableFrame
|
||||
class OptionFrame(QWidget):
|
||||
def add_bool_option(self, name: str, description: str, value: bool = False) -> None:
|
||||
self.add_option_label(description)
|
||||
self.bool_options[name] = customtkinter.CTkCheckBox(
|
||||
self,
|
||||
text="",
|
||||
onvalue=True,
|
||||
offvalue=False,
|
||||
)
|
||||
if value:
|
||||
self.bool_options[name].select()
|
||||
else:
|
||||
self.bool_options[name].deselect()
|
||||
self.bool_options[name].grid(column=1, row=self.number_of_options, sticky="EW")
|
||||
label = QLabel(description, self)
|
||||
|
||||
self.bool_options[name] = QCheckBox(self)
|
||||
self.bool_options[name].setChecked(value)
|
||||
self.form_layout.addRow(label, self.bool_options[name])
|
||||
self.number_of_options += 1
|
||||
|
||||
def add_string_option(
|
||||
self,
|
||||
name: str,
|
||||
description: str,
|
||||
value: str = "",
|
||||
value: Optional[str] = "",
|
||||
callback: Optional[Callable[..., None]] = None,
|
||||
) -> None:
|
||||
self.add_option_label(description)
|
||||
if value is None:
|
||||
value = ""
|
||||
|
||||
self.string_options[name] = customtkinter.CTkTextbox(self, wrap="none", height=1)
|
||||
self.string_options[name].grid(column=1, row=self.number_of_options, sticky="EW")
|
||||
self.string_options[name].insert("0.0", value)
|
||||
label = QLabel(description, self)
|
||||
|
||||
self.string_options[name] = QLineEdit(self)
|
||||
self.string_options[name].insert(value)
|
||||
self.form_layout.addRow(label, self.string_options[name])
|
||||
if callback is not None:
|
||||
self.string_options[name].bind("<KeyRelease>", callback)
|
||||
self.string_options[name].bind("<ButtonRelease>", callback)
|
||||
self.string_options[name].textChanged.connect(callback)
|
||||
self.number_of_options += 1
|
||||
|
||||
def del_list_element(
|
||||
self,
|
||||
name: str,
|
||||
element: customtkinter.CTkTextbox,
|
||||
frame: customtkinter.CTkFrame,
|
||||
element: QLineEdit,
|
||||
line: QWidget,
|
||||
frame: QWidget,
|
||||
) -> None:
|
||||
self.list_options[name].remove(element)
|
||||
frame.destroy()
|
||||
layout = frame.layout()
|
||||
if layout is None:
|
||||
raise ValueError("No layout found")
|
||||
layout.removeWidget(line)
|
||||
line.deleteLater()
|
||||
|
||||
def add_list_element(
|
||||
self,
|
||||
name: str,
|
||||
frame: customtkinter.CTkFrame,
|
||||
frame: QWidget,
|
||||
init: str,
|
||||
callback: Optional[Callable[..., None]],
|
||||
) -> None:
|
||||
input_and_minus = customtkinter.CTkFrame(frame)
|
||||
input_and_minus.pack(side="top", fill="x", expand=True)
|
||||
input_field = customtkinter.CTkTextbox(input_and_minus, wrap="none", height=1)
|
||||
input_field.pack(side="left", fill="x", expand=True)
|
||||
input_field.insert("0.0", init)
|
||||
if callback is not None:
|
||||
input_field.bind("<KeyRelease>", callback)
|
||||
input_field.bind("<ButtonRelease>", callback)
|
||||
input_and_minus = QWidget(frame)
|
||||
input_and_minus_layout = QHBoxLayout(input_and_minus)
|
||||
input_and_minus.setLayout(input_and_minus_layout)
|
||||
|
||||
minus_button = customtkinter.CTkButton(
|
||||
input_and_minus,
|
||||
text="-",
|
||||
width=40,
|
||||
command=partial(self.del_list_element, name, input_field, input_and_minus),
|
||||
input_field = QLineEdit(frame)
|
||||
input_field.setText(init)
|
||||
input_field.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
input_and_minus_layout.addWidget(input_field)
|
||||
if callback is not None:
|
||||
input_field.textChanged.connect(callback)
|
||||
|
||||
minus_button = QPushButton("-", frame)
|
||||
minus_button.clicked.connect(
|
||||
partial(self.del_list_element, name, input_field, input_and_minus, frame)
|
||||
)
|
||||
minus_button.pack(side="right")
|
||||
minus_button.setFixedWidth(40)
|
||||
minus_button.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed)
|
||||
input_and_minus_layout.addWidget(minus_button)
|
||||
|
||||
layout = frame.layout()
|
||||
|
||||
if not isinstance(layout, QVBoxLayout):
|
||||
raise ValueError("No layout found")
|
||||
# insert second from last
|
||||
layout.insertWidget(layout.count() - 1, input_and_minus)
|
||||
|
||||
self.list_options[name].append(input_field)
|
||||
|
||||
# input_and_minus = customtkinter.CTkFrame(frame)
|
||||
# input_and_minus.pack(side="top", fill="x", expand=True)
|
||||
# input_field = customtkinter.CTkTextbox(input_and_minus, wrap="none", height=1)
|
||||
# input_field.pack(side="left", fill="x", expand=True)
|
||||
# input_field.insert("0.0", init)
|
||||
# if callback is not None:
|
||||
# input_field.bind("<KeyRelease>", callback)
|
||||
# input_field.bind("<ButtonRelease>", callback)
|
||||
#
|
||||
# minus_button = customtkinter.CTkButton(
|
||||
# input_and_minus,
|
||||
# text="-",
|
||||
# width=40,
|
||||
# command=partial(self.del_list_element, name, input_field, input_and_minus),
|
||||
# )
|
||||
# minus_button.pack(side="right")
|
||||
# self.list_options[name].append(input_field)
|
||||
|
||||
def add_list_option(
|
||||
self,
|
||||
name: str,
|
||||
|
@ -167,100 +152,95 @@ class OptionFrame(customtkinter.CTkScrollableFrame): # type:ignore
|
|||
value: list[str],
|
||||
callback: Optional[Callable[..., None]] = None,
|
||||
) -> None:
|
||||
self.add_option_label(description)
|
||||
label = QLabel(description, self)
|
||||
|
||||
frame = customtkinter.CTkFrame(self)
|
||||
frame.grid(column=1, row=self.number_of_options, sticky="EW")
|
||||
container = QWidget(self)
|
||||
container_layout = QVBoxLayout(container)
|
||||
container.setLayout(container_layout)
|
||||
|
||||
self.form_layout.addRow(label, container)
|
||||
|
||||
# frame = customtkinter.CTkFrame(self)
|
||||
# frame.grid(column=1, row=self.number_of_options, sticky="EW")
|
||||
|
||||
self.list_options[name] = []
|
||||
for v in value:
|
||||
self.add_list_element(name, frame, v, callback)
|
||||
plus_button = customtkinter.CTkButton(
|
||||
frame,
|
||||
text="+",
|
||||
command=partial(self.add_list_element, name, frame, "", callback),
|
||||
)
|
||||
plus_button.pack(side="bottom", fill="x", expand=True)
|
||||
self.add_list_element(name, container, v, callback)
|
||||
plus_button = QPushButton("+", self)
|
||||
plus_button.clicked.connect(partial(self.add_list_element, name, container, "", callback))
|
||||
plus_button.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
|
||||
# customtkinter.CTkButton(
|
||||
# frame,
|
||||
# text="+",
|
||||
# command=partial(self.add_list_element, name, frame, "", callback),
|
||||
# )
|
||||
container_layout.addWidget(plus_button)
|
||||
# plus_button.pack(side="bottom", fill="x", expand=True)
|
||||
|
||||
self.number_of_options += 1
|
||||
|
||||
def add_choose_option(
|
||||
self, name: str, description: str, values: list[str], value: str = ""
|
||||
) -> None:
|
||||
self.add_option_label(description)
|
||||
self.choose_options[name] = customtkinter.CTkOptionMenu(self, values=values)
|
||||
self.choose_options[name].grid(column=1, row=self.number_of_options, sticky="EW")
|
||||
self.choose_options[name].set(value)
|
||||
self.number_of_options += 1
|
||||
label = QLabel(description, self)
|
||||
|
||||
def open_date_and_time_picker(self, name: str, input_field: customtkinter.CTkTextbox) -> None:
|
||||
if (
|
||||
name not in self.date_and_time_pickers
|
||||
or not self.date_and_time_pickers[name].winfo_exists()
|
||||
):
|
||||
self.date_and_time_pickers[name] = DateAndTimePickerWindow(self, input_field)
|
||||
else:
|
||||
self.date_and_time_pickers[name].focus()
|
||||
self.choose_options[name] = QComboBox(self)
|
||||
self.choose_options[name].addItems(values)
|
||||
self.choose_options[name].setCurrentText(value)
|
||||
self.form_layout.addRow(label, self.choose_options[name])
|
||||
self.number_of_options += 1
|
||||
|
||||
def add_date_time_option(self, name: str, description: str, value: str) -> None:
|
||||
self.add_option_label(description)
|
||||
input_and_button = customtkinter.CTkFrame(self)
|
||||
input_and_button.grid(column=1, row=self.number_of_options, sticky="EW")
|
||||
input_field = customtkinter.CTkTextbox(input_and_button, wrap="none", height=1)
|
||||
input_field.pack(side="left", fill="x", expand=True)
|
||||
self.date_time_options[name] = input_field
|
||||
label = QLabel(description, self)
|
||||
self.date_time_options[name] = QDateTimeEdit(self)
|
||||
try:
|
||||
datetime.fromisoformat(value)
|
||||
self.date_time_options[name].setDateTime(datetime.fromisoformat(value))
|
||||
except (TypeError, ValueError):
|
||||
value = ""
|
||||
input_field.insert("0.0", value)
|
||||
self.date_time_options[name].setDateTime(datetime.now()) # TODO
|
||||
|
||||
self.form_layout.addRow(label, self.date_time_options[name])
|
||||
|
||||
button = customtkinter.CTkButton(
|
||||
input_and_button,
|
||||
text="...",
|
||||
width=40,
|
||||
command=partial(self.open_date_and_time_picker, name, input_field),
|
||||
)
|
||||
button.pack(side="right")
|
||||
self.number_of_options += 1
|
||||
|
||||
def __init__(self, parent: customtkinter.CTkFrame) -> None:
|
||||
def __init__(self, parent: Optional[QWidget] = None) -> None:
|
||||
super().__init__(parent)
|
||||
self.columnconfigure((1,), weight=1)
|
||||
self.form_layout = QFormLayout(self)
|
||||
self.setLayout(self.form_layout)
|
||||
self.number_of_options: int = 0
|
||||
self.string_options: dict[str, customtkinter.CTkTextbox] = {}
|
||||
self.choose_options: dict[str, customtkinter.CTkOptionMenu] = {}
|
||||
self.bool_options: dict[str, customtkinter.CTkCheckBox] = {}
|
||||
self.list_options: dict[str, list[customtkinter.CTkTextbox]] = {}
|
||||
self.date_time_options: dict[str, customtkinter.CTkTextbox] = {}
|
||||
self.date_and_time_pickers: dict[str, DateAndTimePickerWindow] = {}
|
||||
self.string_options: dict[str, QLineEdit] = {}
|
||||
self.choose_options: dict[str, QComboBox] = {}
|
||||
self.bool_options: dict[str, QCheckBox] = {}
|
||||
self.list_options: dict[str, list[QLineEdit]] = {}
|
||||
self.date_time_options: dict[str, QDateTimeEdit] = {}
|
||||
|
||||
def get_config(self) -> dict[str, Any]:
|
||||
config: dict[str, Any] = {}
|
||||
for name, textbox in self.string_options.items():
|
||||
config[name] = textbox.get("0.0", "end").strip()
|
||||
config[name] = textbox.text().strip()
|
||||
|
||||
for name, optionmenu in self.choose_options.items():
|
||||
config[name] = optionmenu.get().strip()
|
||||
config[name] = optionmenu.currentText().strip()
|
||||
|
||||
for name, checkbox in self.bool_options.items():
|
||||
config[name] = checkbox.get() == 1
|
||||
config[name] = checkbox.isChecked()
|
||||
|
||||
for name, textboxes in self.list_options.items():
|
||||
config[name] = []
|
||||
for textbox in textboxes:
|
||||
config[name].append(textbox.get("0.0", "end").strip())
|
||||
config[name].append(textbox.text().strip())
|
||||
|
||||
for name, picker in self.date_time_options.items():
|
||||
config[name] = picker.get("0.0", "end").strip()
|
||||
try:
|
||||
config[name] = picker.dateTime().toPyDateTime().isoformat()
|
||||
except ValueError:
|
||||
config[name] = None
|
||||
|
||||
return config
|
||||
|
||||
|
||||
class SourceTab(OptionFrame):
|
||||
def __init__(
|
||||
self, parent: customtkinter.CTkFrame, source_name: str, config: dict[str, Any]
|
||||
) -> None:
|
||||
def __init__(self, parent: QWidget, source_name: str, config: dict[str, Any]) -> None:
|
||||
super().__init__(parent)
|
||||
source = available_sources[source_name]
|
||||
self.vars: dict[str, str | bool | list[str]] = {}
|
||||
|
@ -278,7 +258,7 @@ class SourceTab(OptionFrame):
|
|||
class GeneralConfig(OptionFrame):
|
||||
def __init__(
|
||||
self,
|
||||
parent: customtkinter.CTkFrame,
|
||||
parent: QWidget,
|
||||
config: dict[str, Any],
|
||||
callback: Callable[..., None],
|
||||
) -> None:
|
||||
|
@ -294,7 +274,9 @@ class GeneralConfig(OptionFrame):
|
|||
str(config["waiting_room_policy"]).lower(),
|
||||
)
|
||||
self.add_date_time_option("last_song", "Time of last song", config["last_song"])
|
||||
self.add_string_option("preview_duration", "Preview Duration", config["preview_duration"])
|
||||
self.add_string_option(
|
||||
"preview_duration", "Preview duration in seconds", str(config["preview_duration"])
|
||||
)
|
||||
self.add_string_option("key", "Key for server", config["key"])
|
||||
|
||||
def get_config(self) -> dict[str, Any]:
|
||||
|
@ -307,8 +289,8 @@ class GeneralConfig(OptionFrame):
|
|||
return config
|
||||
|
||||
|
||||
class SyngGui(customtkinter.CTk): # type:ignore
|
||||
def on_close(self) -> None:
|
||||
class SyngGui(QMainWindow):
|
||||
def closeEvent(self, a0: Optional[QCloseEvent]) -> None:
|
||||
if self.syng_server is not None:
|
||||
self.syng_server.kill()
|
||||
self.syng_server.join()
|
||||
|
@ -317,64 +299,78 @@ class SyngGui(customtkinter.CTk): # type:ignore
|
|||
self.syng_client.terminate()
|
||||
self.syng_client.join()
|
||||
|
||||
self.withdraw()
|
||||
# self.withdraw()
|
||||
self.destroy()
|
||||
|
||||
def add_buttons(self) -> None:
|
||||
button_line = customtkinter.CTkFrame(self)
|
||||
button_line.pack(side="bottom", fill="x")
|
||||
self.buttons_layout = QHBoxLayout()
|
||||
self.central_layout.addLayout(self.buttons_layout)
|
||||
|
||||
self.startsyng_serverbutton = customtkinter.CTkButton(
|
||||
button_line, text="Start Local Server", command=self.start_syng_server
|
||||
)
|
||||
self.startsyng_serverbutton.pack(side="left", expand=True, anchor="w", padx=10, pady=5)
|
||||
self.startsyng_serverbutton = QPushButton("Start Local Server")
|
||||
self.startsyng_serverbutton.clicked.connect(self.start_syng_server)
|
||||
self.buttons_layout.addWidget(self.startsyng_serverbutton)
|
||||
|
||||
savebutton = customtkinter.CTkButton(button_line, text="Save", command=self.save_config)
|
||||
savebutton.pack(side="left", padx=10, pady=5)
|
||||
spacer_item = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||
self.buttons_layout.addItem(spacer_item)
|
||||
|
||||
self.startbutton = customtkinter.CTkButton(
|
||||
button_line, text="Save and Start", command=self.start_syng_client
|
||||
)
|
||||
self.startbutton.pack(side="left", padx=10, pady=10)
|
||||
self.savebutton = QPushButton("Save")
|
||||
self.savebutton.clicked.connect(self.save_config)
|
||||
self.buttons_layout.addWidget(self.savebutton)
|
||||
|
||||
def init_frame(self):
|
||||
self.frm = customtkinter.CTkFrame(self)
|
||||
self.frm.pack(ipadx=10, padx=10, fill="both", expand=True)
|
||||
self.startbutton = QPushButton("Save and Start")
|
||||
self.startbutton.clicked.connect(self.start_syng_client)
|
||||
self.buttons_layout.addWidget(self.startbutton)
|
||||
|
||||
def init_tabs(self):
|
||||
self.tabview = customtkinter.CTkTabview(self.frm, width=600, height=500)
|
||||
self.tabview.pack(side="right", padx=10, pady=10, fill="both", expand=True)
|
||||
def init_frame(self) -> None:
|
||||
self.frm = QHBoxLayout()
|
||||
self.central_layout.addLayout(self.frm)
|
||||
|
||||
self.tabview.add("General")
|
||||
for source in available_sources:
|
||||
self.tabview.add(source)
|
||||
self.tabview.set("General")
|
||||
def init_tabs(self) -> None:
|
||||
self.tabview = QTabWidget(parent=self.central_widget)
|
||||
self.tabview.setAcceptDrops(False)
|
||||
self.tabview.setTabPosition(QTabWidget.TabPosition.West)
|
||||
self.tabview.setTabShape(QTabWidget.TabShape.Rounded)
|
||||
self.tabview.setDocumentMode(False)
|
||||
self.tabview.setTabsClosable(False)
|
||||
self.tabview.setObjectName("tabWidget")
|
||||
|
||||
self.tabview.setTabText(0, "General")
|
||||
for i, source in enumerate(available_sources):
|
||||
self.tabview.setTabText(i + 1, source)
|
||||
|
||||
self.frm.addWidget(self.tabview)
|
||||
|
||||
def add_qr(self) -> None:
|
||||
self.qrlabel = customtkinter.CTkLabel(self.frm, text="")
|
||||
self.qrlabel.pack(side="top", anchor="n", padx=10, pady=10)
|
||||
self.linklabel = customtkinter.CTkLabel(self.frm, text="")
|
||||
self.linklabel.bind("<Button-1>", lambda _: self.open_web())
|
||||
self.linklabel.pack()
|
||||
self.qr_widget = QWidget(parent=self.central_widget)
|
||||
self.qr_layout = QVBoxLayout(self.qr_widget)
|
||||
self.qr_widget.setLayout(self.qr_layout)
|
||||
|
||||
self.qr_label = QLabel(self.qr_widget)
|
||||
self.linklabel = QLabel(self.qr_widget)
|
||||
|
||||
self.qr_layout.addWidget(self.qr_label)
|
||||
self.qr_layout.addWidget(self.linklabel)
|
||||
|
||||
self.linklabel.setOpenExternalLinks(True)
|
||||
|
||||
self.frm.addWidget(self.qr_widget)
|
||||
|
||||
def add_general_config(self, config: dict[str, Any]) -> None:
|
||||
self.general_config = GeneralConfig(self.tabview.tab("General"), config, self.update_qr)
|
||||
self.general_config.pack(ipadx=10, fill="both", expand=True)
|
||||
self.general_config = GeneralConfig(self, config, self.update_qr)
|
||||
self.tabview.addTab(self.general_config, "General")
|
||||
|
||||
def add_source_config(self, source_name: str, source_config: dict[str, Any]) -> None:
|
||||
self.tabs[source_name] = SourceTab(
|
||||
self.tabview.tab(source_name), source_name, source_config
|
||||
)
|
||||
self.tabs[source_name].pack(ipadx=10, expand=True, fill="both")
|
||||
self.tabs[source_name] = SourceTab(self, source_name, source_config)
|
||||
self.tabview.addTab(self.tabs[source_name], source_name)
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(className="Syng")
|
||||
self.protocol("WM_DELETE_WINDOW", self.on_close)
|
||||
super().__init__()
|
||||
self.setWindowTitle("Syng")
|
||||
|
||||
rel_path = os.path.dirname(__file__)
|
||||
img = ImageTk.PhotoImage(file=os.path.join(rel_path, "static/syng.png"))
|
||||
self.wm_iconbitmap()
|
||||
self.iconphoto(False, img)
|
||||
qt_img = QPixmap(os.path.join(rel_path, "static/syng.png"))
|
||||
qt_icon = QIcon(qt_img)
|
||||
self.setWindowIcon(qt_icon)
|
||||
|
||||
self.syng_server: Optional[Process] = None
|
||||
self.syng_client: Optional[Process] = None
|
||||
|
@ -398,15 +394,15 @@ class SyngGui(customtkinter.CTk): # type:ignore
|
|||
config["config"]["secret"] = "".join(
|
||||
secrets.choice(string.ascii_letters + string.digits) for _ in range(8)
|
||||
)
|
||||
self.central_widget = QWidget(parent=self)
|
||||
self.central_layout = QVBoxLayout(self.central_widget)
|
||||
|
||||
self.wm_title("Syng")
|
||||
|
||||
self.add_buttons()
|
||||
self.init_frame()
|
||||
self.init_tabs()
|
||||
self.add_buttons()
|
||||
self.add_qr()
|
||||
self.add_general_config(config["config"])
|
||||
self.tabs = {}
|
||||
self.tabs: dict[str, SourceTab] = {}
|
||||
|
||||
for source_name in available_sources:
|
||||
try:
|
||||
|
@ -418,6 +414,8 @@ class SyngGui(customtkinter.CTk): # type:ignore
|
|||
|
||||
self.update_qr()
|
||||
|
||||
self.setCentralWidget(self.central_widget)
|
||||
|
||||
def save_config(self) -> None:
|
||||
os.makedirs(os.path.dirname(self.configfile), exist_ok=True)
|
||||
|
||||
|
@ -441,12 +439,13 @@ class SyngGui(customtkinter.CTk): # type:ignore
|
|||
target=create_async_and_start_client, args=(config,)
|
||||
)
|
||||
self.syng_client.start()
|
||||
self.startbutton.configure(text="Stop")
|
||||
self.startbutton.setText("Stop")
|
||||
else:
|
||||
self.syng_client.terminate()
|
||||
self.syng_client.join()
|
||||
self.syng_client = None
|
||||
self.startbutton.configure(text="Save and Start")
|
||||
# self.startbutton.configure(text="Save and Start")
|
||||
self.startbutton.setText("Save and Start")
|
||||
|
||||
def start_syng_server(self) -> None:
|
||||
if self.syng_server is None:
|
||||
|
@ -459,16 +458,18 @@ class SyngGui(customtkinter.CTk): # type:ignore
|
|||
port=8080,
|
||||
registration_keyfile=None,
|
||||
root_folder=root_path,
|
||||
private=False,
|
||||
restricted=False,
|
||||
)
|
||||
],
|
||||
)
|
||||
self.syng_server.start()
|
||||
self.startsyng_serverbutton.configure(text="Stop Local Server")
|
||||
self.startsyng_serverbutton.setText("Stop Local Server")
|
||||
else:
|
||||
self.syng_server.terminate()
|
||||
self.syng_server.join()
|
||||
self.syng_server = None
|
||||
self.startsyng_serverbutton.configure(text="Start Local Server")
|
||||
self.startsyng_serverbutton.setText("Start Local Server")
|
||||
|
||||
def open_web(self) -> None:
|
||||
config = self.general_config.get_config()
|
||||
|
@ -478,21 +479,29 @@ class SyngGui(customtkinter.CTk): # type:ignore
|
|||
webbrowser.open(syng_server + room)
|
||||
|
||||
def change_qr(self, data: str) -> None:
|
||||
qr = QRCode(box_size=20, border=2)
|
||||
qr = QRCode(box_size=10, border=2)
|
||||
qr.add_data(data)
|
||||
qr.make()
|
||||
image = qr.make_image().convert("RGB")
|
||||
tk_qrcode = customtkinter.CTkImage(light_image=image, size=(280, 280))
|
||||
self.qrlabel.configure(image=tk_qrcode)
|
||||
buf = BytesIO()
|
||||
image.save(buf, "PNG")
|
||||
qr_pixmap = QPixmap()
|
||||
qr_pixmap.loadFromData(buf.getvalue(), "PNG")
|
||||
self.qr_label.setPixmap(qr_pixmap)
|
||||
|
||||
def update_qr(self, _evt: None = None) -> None:
|
||||
def update_qr(self) -> None:
|
||||
config = self.general_config.get_config()
|
||||
syng_server = config["server"]
|
||||
syng_server += "" if syng_server.endswith("/") else "/"
|
||||
room = config["room"]
|
||||
self.linklabel.configure(text=syng_server + room)
|
||||
self.linklabel.setText(f'<a href="{syng_server + room}">{syng_server + room}</a>')
|
||||
self.change_qr(syng_server + room)
|
||||
|
||||
|
||||
def run_gui() -> None:
|
||||
SyngGui().mainloop()
|
||||
app = QApplication([])
|
||||
app.setApplicationName("Syng")
|
||||
app.setDesktopFileName("rocks.syng.Syng")
|
||||
window = SyngGui()
|
||||
window.show()
|
||||
app.exec()
|
||||
|
|
Loading…
Add table
Reference in a new issue