From 0b3f3a5166aefc7da80bdc073b9f0f7a8fa27178 Mon Sep 17 00:00:00 2001 From: Huy Pham <42948410+hpham@users.noreply.github.com> Date: Mon, 9 Dec 2019 16:33:32 -0800 Subject: [PATCH] start working on input validation --- coretk/coretk/dialogs/canvassizeandscale.py | 34 ++++- coretk/coretk/dialogs/nodeconfig.py | 7 +- coretk/coretk/validation.py | 148 ++++++++++++++++++++ coretk/coretk/widgets.py | 18 ++- 4 files changed, 200 insertions(+), 7 deletions(-) diff --git a/coretk/coretk/dialogs/canvassizeandscale.py b/coretk/coretk/dialogs/canvassizeandscale.py index fe4c3b62..770d40df 100644 --- a/coretk/coretk/dialogs/canvassizeandscale.py +++ b/coretk/coretk/dialogs/canvassizeandscale.py @@ -4,6 +4,7 @@ size and scale import tkinter as tk from tkinter import font, ttk +from coretk import validation from coretk.dialogs.dialog import Dialog PAD = 5 @@ -37,6 +38,12 @@ 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): @@ -47,6 +54,11 @@ 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") @@ -59,11 +71,22 @@ 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.pixel_width) + entry = ttk.Entry( + frame, + textvariable=self.pixel_width, + validate="key", + validatecommand=(self.vcmd_canvas_int, "%P"), + ) 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) + entry = ttk.Entry( + frame, + textvariable=self.pixel_height, + validate="key", + validatecommand=(self.vcmd_canvas_int, "%P"), + ) entry.grid(row=0, column=3, sticky="ew", padx=PAD) label = ttk.Label(frame, text="Pixels") label.grid(row=0, column=4, sticky="w") @@ -94,7 +117,12 @@ class SizeAndScaleDialog(Dialog): frame.columnconfigure(1, weight=1) label = ttk.Label(frame, text=f"{PIXEL_SCALE} Pixels =") label.grid(row=0, column=0, sticky="w", padx=PAD) - entry = ttk.Entry(frame, textvariable=self.scale) + entry = ttk.Entry( + frame, + textvariable=self.scale, + validate="key", + validatecommand=(self.vcmd_canvas_float, "%P"), + ) entry.grid(row=0, column=1, sticky="ew", padx=PAD) label = ttk.Label(frame, text="Meters") label.grid(row=0, column=2, sticky="w") diff --git a/coretk/coretk/dialogs/nodeconfig.py b/coretk/coretk/dialogs/nodeconfig.py index f296e4cf..185a8f62 100644 --- a/coretk/coretk/dialogs/nodeconfig.py +++ b/coretk/coretk/dialogs/nodeconfig.py @@ -3,6 +3,7 @@ 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 @@ -69,7 +70,10 @@ class NodeConfigDialog(Dialog): # name field label = ttk.Label(frame, text="Name") label.grid(row=row, column=0, sticky="ew", padx=PAD, pady=PAD) - entry = ttk.Entry(frame, textvariable=self.name) + vcmd = self.app.master.register(validation.check_node_name) + entry = ttk.Entry( + frame, textvariable=self.name, validate="key", validatecommand=(vcmd, "%P") + ) entry.grid(row=row, column=1, sticky="ew") row += 1 @@ -206,5 +210,4 @@ class NodeConfigDialog(Dialog): # redraw self.canvas_node.redraw() - self.destroy() diff --git a/coretk/coretk/validation.py b/coretk/coretk/validation.py index 00563d68..400cac18 100644 --- a/coretk/coretk/validation.py +++ b/coretk/coretk/validation.py @@ -1,3 +1,151 @@ """ input validation """ +import logging +import tkinter as tk + + +def validate_command(master, func): + return master.register(func) + + +def check_positive_int(s): + logging.debug("int validation...") + try: + int_value = int(s) + if int_value >= 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 != "_": + 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: + 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: + 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 diff --git a/coretk/coretk/widgets.py b/coretk/coretk/widgets.py index 760846a3..2fddbf9f 100644 --- a/coretk/coretk/widgets.py +++ b/coretk/coretk/widgets.py @@ -5,6 +5,7 @@ from tkinter import font, ttk from tkinter.scrolledtext import ScrolledText from core.api.grpc import core_pb2 +from coretk import validation INT_TYPES = { core_pb2.ConfigOptionType.UINT8, @@ -60,6 +61,7 @@ class FrameScroll(ttk.LabelFrame): class ConfigFrame(FrameScroll): def __init__(self, master, app, config, **kw): super().__init__(master, app, ttk.Notebook, **kw) + self.app = app self.config = config self.values = {} @@ -67,6 +69,8 @@ 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, []) @@ -104,11 +108,21 @@ class ConfigFrame(FrameScroll): entry.grid(row=index, column=1, sticky="ew", pady=pady) elif option.type in INT_TYPES: value.set(option.value) - entry = ttk.Entry(frame, textvariable=value) + entry = ttk.Entry( + frame, + textvariable=value, + validate="key", + validatecommand=(vcmd_int, "%P"), + ) entry.grid(row=index, column=1, sticky="ew", pady=pady) elif option.type == core_pb2.ConfigOptionType.FLOAT: value.set(option.value) - entry = ttk.Entry(frame, textvariable=value) + entry = ttk.Entry( + frame, + textvariable=value, + validate="key", + validatecommand=(vcmd_float, "%P"), + ) entry.grid(row=index, column=1, sticky="ew", pady=pady) else: logging.error("unhandled config option type: %s", option.type)