123 lines
2.9 KiB
Python
123 lines
2.9 KiB
Python
"""
|
|
input validation
|
|
"""
|
|
import re
|
|
import tkinter as tk
|
|
from tkinter import ttk
|
|
from typing import Any, Optional, Pattern
|
|
|
|
SMALLEST_SCALE: float = 0.5
|
|
LARGEST_SCALE: float = 5.0
|
|
HEX_REGEX: Pattern = re.compile("^([#]([0-9]|[a-f])+)$|^[#]$")
|
|
|
|
|
|
class ValidationEntry(ttk.Entry):
|
|
empty: Optional[str] = None
|
|
|
|
def __init__(
|
|
self,
|
|
master: tk.BaseWidget = None,
|
|
widget: tk.BaseWidget = None,
|
|
empty_enabled: bool = True,
|
|
**kwargs: Any
|
|
) -> None:
|
|
super().__init__(master, widget, **kwargs)
|
|
cmd = self.register(self.is_valid)
|
|
self.configure(validate="key", validatecommand=(cmd, "%P"))
|
|
if self.empty is not None and empty_enabled:
|
|
self.bind("<FocusOut>", self.focus_out)
|
|
|
|
def is_valid(self, s: str) -> bool:
|
|
raise NotImplementedError
|
|
|
|
def focus_out(self, _event: tk.Event) -> None:
|
|
value = self.get()
|
|
if not value:
|
|
self.insert(tk.END, self.empty)
|
|
|
|
|
|
class PositiveIntEntry(ValidationEntry):
|
|
empty: str = "0"
|
|
|
|
def is_valid(self, s: str) -> bool:
|
|
if not s:
|
|
return True
|
|
try:
|
|
value = int(s)
|
|
return value >= 0
|
|
except ValueError:
|
|
return False
|
|
|
|
|
|
class PositiveFloatEntry(ValidationEntry):
|
|
empty = "0.0"
|
|
|
|
def is_valid(self, s: str) -> bool:
|
|
if not s:
|
|
return True
|
|
try:
|
|
value = float(s)
|
|
return value >= 0.0
|
|
except ValueError:
|
|
return False
|
|
|
|
|
|
class FloatEntry(ValidationEntry):
|
|
empty = "0.0"
|
|
|
|
def is_valid(self, s: str) -> bool:
|
|
if not s:
|
|
return True
|
|
try:
|
|
float(s)
|
|
return True
|
|
except ValueError:
|
|
return False
|
|
|
|
|
|
class RgbEntry(ValidationEntry):
|
|
def is_valid(self, s: str) -> bool:
|
|
if not s:
|
|
return True
|
|
if s.startswith("0") and len(s) >= 2:
|
|
return False
|
|
try:
|
|
value = int(s)
|
|
return 0 <= value <= 255
|
|
except ValueError:
|
|
return False
|
|
|
|
|
|
class HexEntry(ValidationEntry):
|
|
def is_valid(self, s: str) -> bool:
|
|
if not s:
|
|
return True
|
|
if HEX_REGEX.match(s):
|
|
return 0 <= len(s) <= 7
|
|
else:
|
|
return False
|
|
|
|
|
|
class NodeNameEntry(ValidationEntry):
|
|
empty: str = "noname"
|
|
|
|
def is_valid(self, s: str) -> bool:
|
|
if len(s) < 0:
|
|
return False
|
|
if len(s) == 0:
|
|
return True
|
|
for x in s:
|
|
if not x.isalnum() and x != "-":
|
|
return False
|
|
return True
|
|
|
|
|
|
class AppScaleEntry(ValidationEntry):
|
|
def is_valid(self, s: str) -> bool:
|
|
if not s:
|
|
return True
|
|
try:
|
|
float_value = float(s)
|
|
return SMALLEST_SCALE <= float_value <= LARGEST_SCALE or float_value == 0
|
|
except ValueError:
|
|
return False
|