diff --git a/coretk/coretk/app.py b/coretk/coretk/app.py index 4d51cd1e..47626d0d 100644 --- a/coretk/coretk/app.py +++ b/coretk/coretk/app.py @@ -12,6 +12,7 @@ from coretk.menubar import Menubar from coretk.nodeutils import NodeUtils from coretk.statusbar import StatusBar from coretk.toolbar import Toolbar +from coretk.validation import InputValidation class Application(tk.Frame): @@ -25,6 +26,7 @@ class Application(tk.Frame): self.toolbar = None self.canvas = None self.statusbar = None + self.validation = None # setup self.guiconfig = appconfig.read() @@ -48,6 +50,7 @@ class Application(tk.Frame): image = Images.get(ImageEnum.CORE, 16) self.master.tk.call("wm", "iconphoto", self.master._w, image) self.pack(fill=tk.BOTH, expand=True) + self.validation = InputValidation(self) def center(self): width = 1000 diff --git a/coretk/coretk/dialogs/canvassizeandscale.py b/coretk/coretk/dialogs/canvassizeandscale.py index 669c0732..624e4246 100644 --- a/coretk/coretk/dialogs/canvassizeandscale.py +++ b/coretk/coretk/dialogs/canvassizeandscale.py @@ -4,7 +4,6 @@ size and scale import tkinter as tk from tkinter import font, ttk -from coretk import validation from coretk.dialogs.dialog import Dialog PAD = 5 @@ -20,6 +19,7 @@ class SizeAndScaleDialog(Dialog): """ super().__init__(master, app, "Canvas Size and Scale", modal=True) self.canvas = self.app.canvas + self.validation = app.validation self.section_font = font.Font(weight="bold") # get current canvas dimensions plot = self.canvas.find_withtag("rectangle") @@ -38,12 +38,6 @@ class SizeAndScaleDialog(Dialog): self.meters_width = tk.IntVar(value=width / PIXEL_SCALE * location.scale) self.meters_height = tk.IntVar(value=height / PIXEL_SCALE * location.scale) self.save_default = tk.BooleanVar(value=False) - self.vcmd_canvas_int = validation.validate_command( - app.master, validation.check_canvas_int - ) - self.vcmd_canvas_float = validation.validate_command( - app.master, validation.check_canvas_float - ) self.draw() def draw(self): @@ -54,11 +48,6 @@ class SizeAndScaleDialog(Dialog): self.draw_save_as_default() self.draw_buttons() - def focus_out(self, event): - value = event.widget.get() - if value == "": - event.widget.insert(tk.END, 0) - def draw_size(self): label_frame = ttk.Labelframe(self.top, text="Size", padding=PAD) label_frame.grid(sticky="ew") @@ -75,18 +64,19 @@ class SizeAndScaleDialog(Dialog): frame, textvariable=self.pixel_width, validate="key", - validatecommand=(self.vcmd_canvas_int, "%P"), + validatecommand=(self.validation.positive_int, "%P"), ) + entry.bind("", self.validation.focus_out) entry.grid(row=0, column=1, sticky="ew", padx=PAD) - entry.bind("", self.focus_out) label = ttk.Label(frame, text="x Height") label.grid(row=0, column=2, sticky="w", padx=PAD) entry = ttk.Entry( frame, textvariable=self.pixel_height, validate="key", - validatecommand=(self.vcmd_canvas_int, "%P"), + validatecommand=(self.validation.positive_int, "%P"), ) + entry.bind("", self.validation.focus_out) entry.grid(row=0, column=3, sticky="ew", padx=PAD) label = ttk.Label(frame, text="Pixels") label.grid(row=0, column=4, sticky="w") @@ -98,11 +88,23 @@ class SizeAndScaleDialog(Dialog): frame.columnconfigure(3, weight=1) label = ttk.Label(frame, text="Width") label.grid(row=0, column=0, sticky="w", padx=PAD) - entry = ttk.Entry(frame, textvariable=self.meters_width) + entry = ttk.Entry( + frame, + textvariable=self.meters_width, + validate="key", + validatecommand=(self.validation.positive_float, "%P"), + ) + entry.bind("", self.validation.focus_out) entry.grid(row=0, column=1, sticky="ew", padx=PAD) label = ttk.Label(frame, text="x Height") label.grid(row=0, column=2, sticky="w", padx=PAD) - entry = ttk.Entry(frame, textvariable=self.meters_height) + entry = ttk.Entry( + frame, + textvariable=self.meters_height, + validate="key", + validatecommand=(self.validation.positive_float, "%P"), + ) + entry.bind("", self.validation.focus_out) entry.grid(row=0, column=3, sticky="ew", padx=PAD) label = ttk.Label(frame, text="Meters") label.grid(row=0, column=4, sticky="w") @@ -121,8 +123,9 @@ class SizeAndScaleDialog(Dialog): frame, textvariable=self.scale, validate="key", - validatecommand=(self.vcmd_canvas_float, "%P"), + validatecommand=(self.validation.positive_float, "%P"), ) + entry.bind("", self.validation.focus_out) entry.grid(row=0, column=1, sticky="ew", padx=PAD) label = ttk.Label(frame, text="Meters") label.grid(row=0, column=2, sticky="w") @@ -144,12 +147,24 @@ class SizeAndScaleDialog(Dialog): label = ttk.Label(frame, text="X") label.grid(row=0, column=0, sticky="w", padx=PAD) - entry = ttk.Entry(frame, textvariable=self.x) + entry = ttk.Entry( + frame, + textvariable=self.x, + validate="key", + validatecommand=(self.validation.positive_float, "%P"), + ) + entry.bind("", self.validation.focus_out) entry.grid(row=0, column=1, sticky="ew", padx=PAD) label = ttk.Label(frame, text="Y") label.grid(row=0, column=2, sticky="w", padx=PAD) - entry = ttk.Entry(frame, textvariable=self.y) + entry = ttk.Entry( + frame, + textvariable=self.y, + validate="key", + validatecommand=(self.validation.positive_float, "%P"), + ) + entry.bind("", self.validation.focus_out) entry.grid(row=0, column=3, sticky="ew", padx=PAD) label = ttk.Label(label_frame, text="Translates To") @@ -163,17 +178,35 @@ class SizeAndScaleDialog(Dialog): label = ttk.Label(frame, text="Lat") label.grid(row=0, column=0, sticky="w", padx=PAD) - entry = ttk.Entry(frame, textvariable=self.lat) + entry = ttk.Entry( + frame, + textvariable=self.lat, + validate="key", + validatecommand=(self.validation.positive_float, "%P"), + ) + entry.bind("", self.validation.focus_out) entry.grid(row=0, column=1, sticky="ew", padx=PAD) label = ttk.Label(frame, text="Lon") label.grid(row=0, column=2, sticky="w", padx=PAD) - entry = ttk.Entry(frame, textvariable=self.lon) + entry = ttk.Entry( + frame, + textvariable=self.lon, + validate="key", + validatecommand=(self.validation.positive_float, "%P"), + ) + entry.bind("", self.validation.focus_out) entry.grid(row=0, column=3, sticky="ew", padx=PAD) label = ttk.Label(frame, text="Alt") label.grid(row=0, column=4, sticky="w", padx=PAD) - entry = ttk.Entry(frame, textvariable=self.alt) + entry = ttk.Entry( + frame, + textvariable=self.alt, + validate="key", + validatecommand=(self.validation.positive_float, "%P"), + ) + entry.bind("", self.validation.focus_out) entry.grid(row=0, column=5, sticky="ew") def draw_save_as_default(self): diff --git a/coretk/coretk/dialogs/nodeconfig.py b/coretk/coretk/dialogs/nodeconfig.py index 185a8f62..9f7c4d6c 100644 --- a/coretk/coretk/dialogs/nodeconfig.py +++ b/coretk/coretk/dialogs/nodeconfig.py @@ -3,7 +3,6 @@ import tkinter as tk from functools import partial from tkinter import ttk -import coretk.validation as validation from coretk.dialogs.dialog import Dialog from coretk.dialogs.icondialog import IconDialog from coretk.dialogs.nodeservice import NodeService @@ -70,10 +69,13 @@ class NodeConfigDialog(Dialog): # name field label = ttk.Label(frame, text="Name") label.grid(row=row, column=0, sticky="ew", padx=PAD, pady=PAD) - vcmd = self.app.master.register(validation.check_node_name) entry = ttk.Entry( - frame, textvariable=self.name, validate="key", validatecommand=(vcmd, "%P") + frame, + textvariable=self.name, + validate="key", + validatecommand=(self.app.validation.name, "%P"), ) + entry.bind("", self.app.validation.name_focus_out) entry.grid(row=row, column=1, sticky="ew") row += 1 diff --git a/coretk/coretk/validation.py b/coretk/coretk/validation.py index 400cac18..a580c712 100644 --- a/coretk/coretk/validation.py +++ b/coretk/coretk/validation.py @@ -5,147 +5,85 @@ import logging import tkinter as tk -def validate_command(master, func): - return master.register(func) +class InputValidation: + def __init__(self, app): + self.master = app.master + self.positive_int = None + self.positive_float = None + self.name = None + self.register() + def register(self): + self.positive_int = self.master.register(self.check_positive_int) + self.positive_float = self.master.register(self.check_positive_float) + self.name = self.master.register(self.check_node_name) -def check_positive_int(s): - logging.debug("int validation...") - try: - int_value = int(s) - if int_value >= 0: + def focus_out(self, event): + value = event.widget.get() + if value == "": + event.widget.insert(tk.END, 0) + + def name_focus_out(self, event): + logging.debug("name focus out") + value = event.widget.get() + if value == "": + event.widget.insert(tk.END, "empty") + + def check_positive_int(self, s): + logging.debug("int validation...") + if len(s) == 0: return True - return False - except ValueError: - return False - - -def check_positive_float(s): - logging.debug("float validation...") - try: - float_value = float(s) - if float_value >= 0.0: - return True - return False - except ValueError: - return False - - -def check_node_name(name): - logging.debug("node name validation...") - if len(name) <= 0: - return False - for char in name: - if not char.isalnum() and char != "_": + try: + int_value = int(s) + if int_value >= 0: + return True + return False + except ValueError: return False - return True - -def check_canvas_int(s): - logging.debug("int validation...") - if len(s) == 0: - return True - try: - int_value = int(s) - if int_value >= 0: + def check_positive_float(self, s): + logging.debug("float validation...") + if len(s) == 0: return True - return False - except ValueError: - return False + try: + float_value = float(s) + if float_value >= 0.0: + return True + return False + except ValueError: + return False - -def check_canvas_float(s): - logging.debug("canvas float validation") - if not s: - return True - try: - float_value = float(s) - if float_value >= 0.0: + def check_node_name(self, s): + logging.debug("node name validation...") + if len(s) < 0: + return False + if len(s) == 0: return True - return False - except ValueError: - return False + for char in s: + if not char.isalnum() and char != "_": + return False + return True + def check_canvas_int(sefl, s): + logging.debug("int validation...") + if len(s) == 0: + return True + try: + int_value = int(s) + if int_value >= 0: + return True + return False + except ValueError: + return False -def check_interface(name): - logging.debug("interface name validation...") - if len(name) <= 0: - return False, "Interface name cannot be an empty string" - for char in name: - if not char.isalnum() and char != "_": - return ( - False, - "Interface name can only contain alphanumeric letter (a-z) and (0-9) or underscores (_)", - ) - return True, "" - - -def combine_message(key, current_validation, current_message, res, msg): - if not res: - current_validation = res - current_message = current_message + key + ": " + msg + "\n\n" - return current_validation, current_message - - -def check_wlan_config(config): - result = True - message = "" - checks = ["bandwidth", "delay", "error", "jitter", "range"] - for check in checks: - if check in ["bandwidth", "delay", "jitter"]: - res, msg = check_positive_int(config[check].value) - result, message = combine_message(check, result, message, res, msg) - elif check in ["range", "error"]: - res, msg = check_positive_float(config[check].value) - result, message = combine_message(check, result, message, res, msg) - return result, message - - -def check_size_and_scale(dialog): - result = True - message = "" - try: - pixel_width = dialog.pixel_width.get() - if pixel_width < 0: - result, message = combine_message( - "pixel width", result, message, False, "cannot be negative" - ) - except tk.TclError: - result, message = combine_message( - "pixel width", - result, - message, - False, - "invalid value, input non-negative float", - ) - try: - pixel_height = dialog.pixel_height.get() - if pixel_height < 0: - result, message = combine_message( - "pixel height", result, message, False, "cannot be negative" - ) - except tk.TclError: - result, message = combine_message( - "pixel height", - result, - message, - False, - "invalid value, input non-negative float", - ) - try: - scale = dialog.scale.get() - if scale <= 0: - result, message = combine_message( - "scale", result, message, False, "cannot be negative" - ) - except tk.TclError: - result, message = combine_message( - "scale", result, message, False, "invalid value, input non-negative float" - ) - # pixel_height = dialog.pixel_height.get() - # print(pixel_width, pixel_height) - # res, msg = check_positive_int(pixel_width) - # result, message = combine_message("pixel width", result, message, res, msg) - # res, msg = check_positive_int(pixel_height) - # result, message = combine_message("pixel height", result, message, res, msg) - return result, message + def check_canvas_float(self, s): + logging.debug("canvas float validation") + if not s: + return True + try: + float_value = float(s) + if float_value >= 0.0: + return True + return False + except ValueError: + return False diff --git a/coretk/coretk/widgets.py b/coretk/coretk/widgets.py index 521edc98..7d908476 100644 --- a/coretk/coretk/widgets.py +++ b/coretk/coretk/widgets.py @@ -5,7 +5,6 @@ from tkinter import filedialog, font, ttk from tkinter.scrolledtext import ScrolledText from core.api.grpc import core_pb2 -from coretk import validation INT_TYPES = { core_pb2.ConfigOptionType.UINT8, @@ -75,8 +74,6 @@ class ConfigFrame(FrameScroll): padx = 2 pady = 2 group_mapping = {} - vcmd_int = self.app.master.register(validation.check_positive_int) - vcmd_float = self.app.master.register(validation.check_positive_float) for key in self.config: option = self.config[key] group = group_mapping.setdefault(option.group, []) @@ -128,8 +125,9 @@ class ConfigFrame(FrameScroll): frame, textvariable=value, validate="key", - validatecommand=(vcmd_int, "%P"), + validatecommand=(self.app.validation.positive_int, "%P"), ) + entry.bind("", self.app.validation.focus_out) entry.grid(row=index, column=1, sticky="ew", pady=pady) elif option.type == core_pb2.ConfigOptionType.FLOAT: value.set(option.value) @@ -137,8 +135,9 @@ class ConfigFrame(FrameScroll): frame, textvariable=value, validate="key", - validatecommand=(vcmd_float, "%P"), + validatecommand=(self.app.validation.positive_float, "%P"), ) + entry.bind("", self.app.validation.focus_out) entry.grid(row=index, column=1, sticky="ew", pady=pady) else: logging.error("unhandled config option type: %s", option.type)