From 1c36c5e2915542821227fb6f0f53b315bc125f91 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 5 Nov 2019 16:16:46 -0800 Subject: [PATCH 1/6] startred custom node dialog, update node services dialog to use service data retrieved from grpc --- coretk/coretk/appdirs.py | 14 ++--- coretk/coretk/coreclient.py | 10 +++- coretk/coretk/coretoolbar.py | 17 +----- coretk/coretk/dialogs/customnodes.py | 82 ++++++++++++++++++++++++++++ coretk/coretk/dialogs/nodeservice.py | 71 +++--------------------- 5 files changed, 110 insertions(+), 84 deletions(-) create mode 100644 coretk/coretk/dialogs/customnodes.py diff --git a/coretk/coretk/appdirs.py b/coretk/coretk/appdirs.py index 80710920..51a78f76 100644 --- a/coretk/coretk/appdirs.py +++ b/coretk/coretk/appdirs.py @@ -42,15 +42,15 @@ def check_directory(): for background in LOCAL_BACKGROUND_PATH.glob("*"): new_background = BACKGROUNDS_PATH.joinpath(background.name) shutil.copy(background, new_background) - with CONFIG_PATH.open("w") as f: - yaml.dump( - {"servers": [{"name": "example", "address": "127.0.0.1", "port": 50051}]}, - f, - Dumper=IndentDumper, - default_flow_style=False, - ) + config = {"servers": [{"name": "example", "address": "127.0.0.1", "port": 50051}]} + save_config(config) def read_config(): with CONFIG_PATH.open("r") as f: return yaml.load(f, Loader=yaml.SafeLoader) + + +def save_config(config): + with CONFIG_PATH.open("w") as f: + yaml.dump(config, f, Dumper=IndentDumper, default_flow_style=False) diff --git a/coretk/coretk/coreclient.py b/coretk/coretk/coreclient.py index ccb4c7ee..a5ccc14a 100644 --- a/coretk/coretk/coreclient.py +++ b/coretk/coretk/coreclient.py @@ -74,6 +74,7 @@ class CoreClient: self.app = app self.master = app.master self.interface_helper = None + self.services = {} # distributed server data self.servers = {} @@ -188,9 +189,16 @@ class CoreClient: :return: existing sessions """ self.client.connect() - response = self.client.get_sessions() + + # get service information + response = self.client.get_services() + for service in response.services: + group_services = self.services.setdefault(service.group, []) + group_services.append(service) # if there are no sessions, create a new session, else join a session + response = self.client.get_sessions() + logging.info("current sessions: %s", response) sessions = response.sessions if len(sessions) == 0: self.create_new_session() diff --git a/coretk/coretk/coretoolbar.py b/coretk/coretk/coretoolbar.py index 5d7c05ae..f73cc384 100644 --- a/coretk/coretk/coretoolbar.py +++ b/coretk/coretk/coretoolbar.py @@ -1,24 +1,12 @@ import logging import tkinter as tk -# from core.api.grpc import core_pb2 from coretk.coretoolbarhelp import CoreToolbarHelp +from coretk.dialogs.customnodes import CustomNodesDialog from coretk.graph import GraphMode from coretk.images import ImageEnum, Images from coretk.tooltip import CreateToolTip -# from enum import Enum - - -# class SessionStateEnum(Enum): -# NONE = "none" -# DEFINITION = "definition" -# CONFIGURATION = "configuration" -# RUNTIME = "runtime" -# DATACOLLECT = "datacollect" -# SHUTDOWN = "shutdown" -# INSTANTIATION = "instantiation" - class CoreToolbar(object): """ @@ -245,11 +233,12 @@ class CoreToolbar(object): self.canvas.draw_node_image = Images.get(ImageEnum.OVS) self.canvas.draw_node_name = "OVS" - # TODO what graph node is this def pick_editnode(self, main_button): self.network_layer_option_menu.destroy() main_button.configure(image=Images.get(ImageEnum.EDITNODE)) logging.debug("Pick editnode option") + dialog = CustomNodesDialog(self.app, self.app) + dialog.show() def draw_network_layer_options(self, network_layer_button): """ diff --git a/coretk/coretk/dialogs/customnodes.py b/coretk/coretk/dialogs/customnodes.py new file mode 100644 index 00000000..b38196fe --- /dev/null +++ b/coretk/coretk/dialogs/customnodes.py @@ -0,0 +1,82 @@ +import tkinter as tk + +from coretk.dialogs.dialog import Dialog + + +class CustomNodesDialog(Dialog): + def __init__(self, master, app): + super().__init__(master, app, "Custom Nodes", modal=True) + self.save_button = None + self.delete_button = None + self.draw() + + def draw(self): + self.columnconfigure(0, weight=1) + self.rowconfigure(0, weight=1) + self.draw_node_config() + self.draw_node_buttons() + self.draw_buttons() + + def draw_node_config(self): + frame = tk.Frame(self) + frame.grid(sticky="nsew") + frame.columnconfigure(0, weight=1) + frame.rowconfigure(0, weight=1) + + scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL) + scrollbar.grid(row=0, column=1, sticky="ns") + + listbox = tk.Listbox(frame) + listbox.grid( + row=0, + column=0, + selectmode=tk.SINGLE, + yscrollcommand=scrollbar.set, + sticky="nsew", + ) + + scrollbar.config(command=listbox.yview) + + frame = tk.Frame(frame) + frame.grid(row=0, column=2, sticky="nsew") + frame.columnconfigure(0, weight=1) + + def draw_node_buttons(self): + frame = tk.Frame(self) + frame.grid(pady=2, sticky="ew") + for i in range(3): + frame.columnconfigure(i, weight=1) + + button = tk.Button(frame, text="Create", command=self.click_create) + button.grid(row=0, column=0, sticky="ew") + + self.save_button = tk.Button( + frame, text="Save", state=tk.DISABLED, command=self.click_save + ) + self.save_button.grid(row=0, column=1, sticky="ew") + + self.delete_button = tk.Button( + frame, text="Delete", state=tk.DISABLED, command=self.click_delete + ) + self.delete_button.grid(row=0, column=2, sticky="ew") + + def draw_buttons(self): + frame = tk.Frame(self) + frame.grid(sticky="ew") + for i in range(2): + frame.columnconfigure(i, weight=1) + + button = tk.Button(frame, text="Save Configuration") + button.grid(row=0, column=0, sticky="ew") + + button = tk.Button(frame, text="Cancel", command=self.destroy) + button.grid(row=0, column=1, sticky="ew") + + def click_create(self): + pass + + def click_save(self): + pass + + def click_delete(self): + pass diff --git a/coretk/coretk/dialogs/nodeservice.py b/coretk/coretk/dialogs/nodeservice.py index 82d36cca..6d92b98e 100644 --- a/coretk/coretk/dialogs/nodeservice.py +++ b/coretk/coretk/dialogs/nodeservice.py @@ -6,58 +6,6 @@ from tkinter import messagebox from coretk.dialogs.dialog import Dialog -CORE_DEFAULT_GROUPS = ["EMANE", "FRR", "ProtoSvc", "Quagga", "Security", "Utility"] -DEFAULT_GROUP_RADIO_VALUE = { - "EMANE": 1, - "FRR": 2, - "ProtoSvc": 3, - "Quagga": 4, - "Security": 5, - "Utility": 6, -} -DEFAULT_GROUP_SERVICES = { - "EMANE": ["transportd"], - "FRR": [ - "FRRBable", - "FRRBGP", - "FRROSPFv2", - "FRROSPFv3", - "FRRpimd", - "FRRRIP", - "FRRRIPNG", - "FRRzebra", - ], - "ProtoSvc": ["MGEN_Sink", "MgenActor", "SMF"], - "Quagga": [ - "Babel", - "BGP", - "OSPFv2", - "OSPFv3", - "OSPFv3MDR", - "RIP", - "RIPNG", - "Xpimd", - "zebra", - ], - "Security": ["Firewall", "IPsec", "NAT", "VPNClient", "VPNServer"], - "Utility": [ - "atd", - "DefaultMulticastRoute", - "DefaultRoute", - "DHCP", - "DHCPClient", - "FTP", - "HTTP", - "IPForward ", - "pcap", - "radvd", - "SSH", - "StaticRoute", - "ucarp", - "UserDefined", - ], -} - class NodeServicesDialog(Dialog): def __init__(self, master, app, canvas_node): @@ -66,6 +14,7 @@ class NodeServicesDialog(Dialog): self.core_groups = [] self.service_to_config = None self.config_frame = None + self.services_list = None self.draw() def draw(self): @@ -110,7 +59,7 @@ class NodeServicesDialog(Dialog): listbox.grid(row=1, column=0, sticky="nsew") listbox.bind("<>", self.handle_group_change) - for group in CORE_DEFAULT_GROUPS: + for group in sorted(self.app.core.services): listbox.insert(tk.END, group) scrollbar.config(command=listbox.yview) @@ -127,7 +76,7 @@ class NodeServicesDialog(Dialog): scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL) scrollbar.grid(row=1, column=1, sticky="ns") - listbox = tk.Listbox( + self.services_list = tk.Listbox( frame, selectmode=tk.SINGLE, yscrollcommand=scrollbar.set, @@ -135,10 +84,10 @@ class NodeServicesDialog(Dialog): highlightthickness=0.5, bd=0, ) - listbox.grid(row=1, column=0, sticky="nsew") - listbox.bind("<>", self.handle_service_change) + self.services_list.grid(row=1, column=0, sticky="nsew") + self.services_list.bind("<>", self.handle_service_change) - scrollbar.config(command=listbox.yview) + scrollbar.config(command=self.services_list.yview) def draw_current_services(self): frame = tk.Frame(self.config_frame) @@ -188,11 +137,9 @@ class NodeServicesDialog(Dialog): self.display_group_services(s) def display_group_services(self, group_name): - group_services_frame = self.config_frame.grid_slaves(row=0, column=1)[0] - listbox = group_services_frame.grid_slaves(row=1, column=0)[0] - listbox.delete(0, tk.END) - for s in DEFAULT_GROUP_SERVICES[group_name]: - listbox.insert(tk.END, s) + self.services_list.delete(0, tk.END) + for service in sorted(self.app.core.services[group_name], key=lambda x: x.name): + self.services_list.insert(tk.END, service.name) def handle_service_change(self, event): print("select group service") From cb72a70d850b9bb8aa06ee070ee56867b406fc21 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 5 Nov 2019 16:23:47 -0800 Subject: [PATCH 2/6] change to fix coretk action --- .github/workflows/coretk-checks.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/coretk-checks.yml b/.github/workflows/coretk-checks.yml index 324babfd..e99ed214 100644 --- a/.github/workflows/coretk-checks.yml +++ b/.github/workflows/coretk-checks.yml @@ -15,7 +15,11 @@ jobs: run: | python -m pip install --upgrade pip pip install pipenv - cd coretk + cd daemon + cp setup.py.in setup.py + cp core/constants.py.in core/constants.py + sed -i 's/True/False/g' core/constants.py + cd ../coretk pipenv install --dev - name: isort run: | From 73b147b1520a2eed0f73d3411575932af1129299 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 5 Nov 2019 16:56:47 -0800 Subject: [PATCH 3/6] updated tk servers dialog to save config --- coretk/coretk/dialogs/servers.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/coretk/coretk/dialogs/servers.py b/coretk/coretk/dialogs/servers.py index dbc31267..9ab6a694 100644 --- a/coretk/coretk/dialogs/servers.py +++ b/coretk/coretk/dialogs/servers.py @@ -1,5 +1,6 @@ import tkinter as tk +from coretk import appdirs from coretk.coreclient import CoreServer from coretk.dialogs.dialog import Dialog @@ -113,7 +114,14 @@ class ServersDialog(Dialog): button.grid(row=0, column=1, sticky="ew") def click_save_configuration(self): - pass + servers = [] + for name in sorted(self.app.core.servers): + server = self.app.core.servers[name] + servers.append( + {"name": server.name, "address": server.address, "port": server.port} + ) + self.app.config["servers"] = servers + appdirs.save_config(self.app.config) def click_create(self): name = self.name.get() From 275a03b9e72791473d97a44a75ea45456d39a44a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 5 Nov 2019 17:32:48 -0800 Subject: [PATCH 4/6] added observer widget dialog, still needs to be hooked to where widgets will be kept --- coretk/coretk/coremenubar.py | 4 +- coretk/coretk/dialogs/observerwidgets.py | 148 +++++++++++++++++++++++ coretk/coretk/dialogs/servers.py | 9 +- coretk/coretk/menuaction.py | 5 + 4 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 coretk/coretk/dialogs/observerwidgets.py diff --git a/coretk/coretk/coremenubar.py b/coretk/coretk/coremenubar.py index 5add9790..f720b093 100644 --- a/coretk/coretk/coremenubar.py +++ b/coretk/coretk/coremenubar.py @@ -543,7 +543,9 @@ class CoreMenubar(object): observer_widget_menu.add_command( label="PIM neighbors", command=action.sub_menu_items ) - observer_widget_menu.add_command(label="Edit...", command=action.sub_menu_items) + observer_widget_menu.add_command( + label="Edit...", command=self.menu_action.edit_observer_widgets + ) widget_menu.add_cascade(label="Observer Widgets", menu=observer_widget_menu) diff --git a/coretk/coretk/dialogs/observerwidgets.py b/coretk/coretk/dialogs/observerwidgets.py new file mode 100644 index 00000000..c1c4d170 --- /dev/null +++ b/coretk/coretk/dialogs/observerwidgets.py @@ -0,0 +1,148 @@ +import tkinter as tk + +from coretk.dialogs.dialog import Dialog + + +class Widget: + def __init__(self, name, command): + self.name = name + self.command = command + + +class ObserverWidgetsDialog(Dialog): + def __init__(self, master, app): + super().__init__(master, app, "Observer Widgets", modal=True) + self.config_widgets = {} + self.widgets = None + self.save_button = None + self.delete_button = None + self.selected = None + self.selected_index = None + self.name = tk.StringVar() + self.command = tk.StringVar() + self.draw() + + def draw(self): + self.columnconfigure(0, weight=1) + self.rowconfigure(0, weight=1) + self.draw_widgets() + self.draw_widget_fields() + self.draw_widget_buttons() + self.draw_apply_buttons() + + def draw_widgets(self): + frame = tk.Frame(self) + frame.grid(sticky="nsew") + frame.columnconfigure(0, weight=1) + frame.rowconfigure(0, weight=1) + + scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL) + scrollbar.grid(row=0, column=1, sticky="ns") + + self.widgets = tk.Listbox( + frame, selectmode=tk.SINGLE, yscrollcommand=scrollbar.set + ) + self.widgets.grid(row=0, column=0, sticky="nsew") + self.widgets.bind("<>", self.handle_widget_change) + + scrollbar.config(command=self.widgets.yview) + + def draw_widget_fields(self): + frame = tk.Frame(self) + frame.grid(sticky="ew") + frame.columnconfigure(1, weight=1) + + label = tk.Label(frame, text="Name") + label.grid(row=0, column=0, sticky="w") + entry = tk.Entry(frame, textvariable=self.name) + entry.grid(row=0, column=1, sticky="ew") + + label = tk.Label(frame, text="Command") + label.grid(row=1, column=0, sticky="w") + entry = tk.Entry(frame, textvariable=self.command) + entry.grid(row=1, column=1, sticky="ew") + + def draw_widget_buttons(self): + frame = tk.Frame(self) + frame.grid(pady=2, sticky="ew") + for i in range(3): + frame.columnconfigure(i, weight=1) + + button = tk.Button(frame, text="Create", command=self.click_create) + button.grid(row=0, column=0, sticky="ew") + + self.save_button = tk.Button( + frame, text="Save", state=tk.DISABLED, command=self.click_save + ) + self.save_button.grid(row=0, column=1, sticky="ew") + + self.delete_button = tk.Button( + frame, text="Delete", state=tk.DISABLED, command=self.click_delete + ) + self.delete_button.grid(row=0, column=2, sticky="ew") + + def draw_apply_buttons(self): + frame = tk.Frame(self) + frame.grid(sticky="ew") + for i in range(2): + frame.columnconfigure(i, weight=1) + + button = tk.Button( + frame, text="Save Configuration", command=self.click_save_configuration + ) + button.grid(row=0, column=0, sticky="ew") + + button = tk.Button(frame, text="Cancel", command=self.destroy) + button.grid(row=0, column=1, sticky="ew") + + def click_save_configuration(self): + pass + + def click_create(self): + name = self.name.get() + if name not in self.config_widgets: + command = self.command.get() + widget = Widget(name, command) + self.config_widgets[name] = widget + self.widgets.insert(tk.END, name) + + def click_save(self): + name = self.name.get() + if self.selected: + previous_name = self.selected + self.selected = name + widget = self.config_widgets.pop(previous_name) + widget.name = name + widget.command = self.command.get() + self.config_widgets[name] = widget + self.widgets.delete(self.selected_index) + self.widgets.insert(self.selected_index, name) + self.widgets.selection_set(self.selected_index) + + def click_delete(self): + if self.selected: + self.widgets.delete(self.selected_index) + del self.config_widgets[self.selected] + self.selected = None + self.selected_index = None + self.name.set("") + self.command.set("") + self.widgets.selection_clear(0, tk.END) + self.save_button.config(state=tk.DISABLED) + self.delete_button.config(state=tk.DISABLED) + + def handle_widget_change(self, event): + selection = self.widgets.curselection() + if selection: + self.selected_index = selection[0] + self.selected = self.widgets.get(self.selected_index) + widget = self.config_widgets[self.selected] + self.name.set(widget.name) + self.command.set(widget.command) + self.save_button.config(state=tk.NORMAL) + self.delete_button.config(state=tk.NORMAL) + else: + self.selected_index = None + self.selected = None + self.save_button.config(state=tk.DISABLED) + self.delete_button.config(state=tk.DISABLED) diff --git a/coretk/coretk/dialogs/servers.py b/coretk/coretk/dialogs/servers.py index 9ab6a694..3ff95cde 100644 --- a/coretk/coretk/dialogs/servers.py +++ b/coretk/coretk/dialogs/servers.py @@ -40,12 +40,7 @@ class ServersDialog(Dialog): scrollbar.grid(row=0, column=1, sticky="ns") self.servers = tk.Listbox( - frame, - selectmode=tk.SINGLE, - yscrollcommand=scrollbar.set, - relief=tk.FLAT, - highlightthickness=0.5, - bd=0, + frame, selectmode=tk.SINGLE, yscrollcommand=scrollbar.set ) self.servers.grid(row=0, column=0, sticky="nsew") self.servers.bind("<>", self.handle_server_change) @@ -134,7 +129,7 @@ class ServersDialog(Dialog): def click_save(self): name = self.name.get() - if self.selected and name not in self.app.core.servers: + if self.selected: previous_name = self.selected self.selected = name server = self.app.core.servers.pop(previous_name) diff --git a/coretk/coretk/menuaction.py b/coretk/coretk/menuaction.py index a60f7458..391cf765 100644 --- a/coretk/coretk/menuaction.py +++ b/coretk/coretk/menuaction.py @@ -11,6 +11,7 @@ from coretk.appdirs import XML_PATH from coretk.dialogs.canvasbackground import CanvasBackgroundDialog from coretk.dialogs.canvassizeandscale import SizeAndScaleDialog from coretk.dialogs.hooks import HooksDialog +from coretk.dialogs.observerwidgets import ObserverWidgetsDialog from coretk.dialogs.servers import ServersDialog from coretk.dialogs.sessionoptions import SessionOptionsDialog from coretk.dialogs.sessions import SessionsDialog @@ -403,3 +404,7 @@ class MenuAction: logging.debug("Click session emulation servers") dialog = ServersDialog(self.app, self.app) dialog.show() + + def edit_observer_widgets(self): + dialog = ObserverWidgetsDialog(self.app, self.app) + dialog.show() From 2b3e071045f4aa3c4ad1199210c87ec27c7bd915 Mon Sep 17 00:00:00 2001 From: bharnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 5 Nov 2019 22:44:50 -0800 Subject: [PATCH 5/6] updated nodeicondialog to just icondialog, added custom widgets for convenience, listboxscroll and checkboxlist --- coretk/coretk/dialogs/customnodes.py | 94 +++++++++++++++++++++++++--- coretk/coretk/dialogs/nodeconfig.py | 6 +- coretk/coretk/dialogs/nodeicon.py | 14 ++--- coretk/coretk/dialogs/wlanconfig.py | 6 +- coretk/coretk/widgets.py | 58 +++++++++++++++++ 5 files changed, 155 insertions(+), 23 deletions(-) create mode 100644 coretk/coretk/widgets.py diff --git a/coretk/coretk/dialogs/customnodes.py b/coretk/coretk/dialogs/customnodes.py index b38196fe..841b2dcf 100644 --- a/coretk/coretk/dialogs/customnodes.py +++ b/coretk/coretk/dialogs/customnodes.py @@ -1,6 +1,68 @@ import tkinter as tk from coretk.dialogs.dialog import Dialog +from coretk.dialogs.nodeicon import IconDialog +from coretk.widgets import CheckboxList, ListboxScroll + + +class ServicesSelectDialog(Dialog): + def __init__(self, master, app): + super().__init__(master, app, "Node Services", modal=True) + self.groups = None + self.services = None + self.current = None + self.current_services = set() + self.draw() + + def draw(self): + self.columnconfigure(0, weight=1) + self.rowconfigure(0, weight=1) + + frame = tk.Frame(self) + frame.grid(stick="nsew") + frame.rowconfigure(0, weight=1) + for i in range(3): + frame.columnconfigure(i, weight=1) + self.groups = ListboxScroll(frame, text="Groups") + self.groups.grid(row=0, column=0, sticky="nsew") + for group in sorted(self.app.core.services): + self.groups.listbox.insert(tk.END, group) + self.groups.listbox.bind("<>", self.handle_group_change) + + self.services = CheckboxList( + frame, text="Services", clicked=self.service_clicked + ) + self.services.grid(row=0, column=1, sticky="nsew") + + self.current = ListboxScroll(frame, text="Selected") + self.current.grid(row=0, column=2, sticky="nsew") + + frame = tk.Frame(self) + frame.grid(stick="ew") + for i in range(2): + frame.columnconfigure(i, weight=1) + button = tk.Button(frame, text="Save") + button.grid(row=0, column=0, sticky="ew") + button = tk.Button(frame, text="Cancel", command=self.destroy) + button.grid(row=0, column=1, sticky="ew") + + def handle_group_change(self, event): + selection = self.groups.listbox.curselection() + if selection: + index = selection[0] + group = self.groups.listbox.get(index) + self.services.clear() + for service in sorted(self.app.core.services[group], key=lambda x: x.name): + self.services.add(service.name) + + def service_clicked(self, name, var): + if var.get() and name not in self.current_services: + self.current_services.add(name) + elif not var.get() and name in self.current_services: + self.current_services.remove(name) + self.current.listbox.delete(0, tk.END) + for name in sorted(self.current_services): + self.current.listbox.insert(tk.END, name) class CustomNodesDialog(Dialog): @@ -8,6 +70,9 @@ class CustomNodesDialog(Dialog): super().__init__(master, app, "Custom Nodes", modal=True) self.save_button = None self.delete_button = None + self.name = tk.StringVar() + self.image_button = None + self.image = None self.draw() def draw(self): @@ -26,20 +91,20 @@ class CustomNodesDialog(Dialog): scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL) scrollbar.grid(row=0, column=1, sticky="ns") - listbox = tk.Listbox(frame) - listbox.grid( - row=0, - column=0, - selectmode=tk.SINGLE, - yscrollcommand=scrollbar.set, - sticky="nsew", - ) + listbox = tk.Listbox(frame, selectmode=tk.SINGLE, yscrollcommand=scrollbar.set) + listbox.grid(row=0, column=0, sticky="nsew") scrollbar.config(command=listbox.yview) frame = tk.Frame(frame) frame.grid(row=0, column=2, sticky="nsew") frame.columnconfigure(0, weight=1) + entry = tk.Entry(frame, textvariable=self.name) + entry.grid(sticky="ew") + self.image_button = tk.Button(frame, text="Icon", command=self.click_icon) + self.image_button.grid(sticky="ew") + button = tk.Button(frame, text="Services", command=self.click_services) + button.grid(sticky="ew") def draw_node_buttons(self): frame = tk.Frame(self) @@ -66,12 +131,23 @@ class CustomNodesDialog(Dialog): for i in range(2): frame.columnconfigure(i, weight=1) - button = tk.Button(frame, text="Save Configuration") + button = tk.Button(frame, text="Save", command=self.click_save) button.grid(row=0, column=0, sticky="ew") button = tk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") + def click_icon(self): + dialog = IconDialog(self, self.app, self.name.get(), self.image) + dialog.show() + if dialog.image: + self.image = dialog.image + self.image_button.config(image=self.image) + + def click_services(self): + dialog = ServicesSelectDialog(self, self.app) + dialog.show() + def click_create(self): pass diff --git a/coretk/coretk/dialogs/nodeconfig.py b/coretk/coretk/dialogs/nodeconfig.py index b8548c51..bc03dc51 100644 --- a/coretk/coretk/dialogs/nodeconfig.py +++ b/coretk/coretk/dialogs/nodeconfig.py @@ -2,7 +2,7 @@ import tkinter as tk from tkinter import ttk from coretk.dialogs.dialog import Dialog -from coretk.dialogs.nodeicon import NodeIconDialog +from coretk.dialogs.nodeicon import IconDialog from coretk.dialogs.nodeservice import NodeServicesDialog NETWORKNODETYPES = ["switch", "hub", "wlan", "rj45", "tunnel"] @@ -91,7 +91,9 @@ class NodeConfigDialog(Dialog): dialog.show() def click_icon(self): - dialog = NodeIconDialog(self, self.app, self.canvas_node) + dialog = IconDialog( + self, self.app, self.canvas_node.name, self.canvas_node.image + ) dialog.show() if dialog.image: self.image = dialog.image diff --git a/coretk/coretk/dialogs/nodeicon.py b/coretk/coretk/dialogs/nodeicon.py index d82c0756..4d26e29f 100644 --- a/coretk/coretk/dialogs/nodeicon.py +++ b/coretk/coretk/dialogs/nodeicon.py @@ -6,18 +6,12 @@ from coretk.dialogs.dialog import Dialog from coretk.images import Images -class NodeIconDialog(Dialog): - def __init__(self, master, app, canvas_node): - """ - create an instance of ImageModification - :param master: dialog master - :param coretk.app.Application: main app - :param coretk.graph.CanvasNode canvas_node: node object - """ - super().__init__(master, app, f"{canvas_node.name} Icon", modal=True) +class IconDialog(Dialog): + def __init__(self, master, app, name, image): + super().__init__(master, app, f"{name} Icon", modal=True) self.file_path = tk.StringVar() self.image_label = None - self.image = canvas_node.image + self.image = image self.draw() def draw(self): diff --git a/coretk/coretk/dialogs/wlanconfig.py b/coretk/coretk/dialogs/wlanconfig.py index ad10bf40..d57c8935 100644 --- a/coretk/coretk/dialogs/wlanconfig.py +++ b/coretk/coretk/dialogs/wlanconfig.py @@ -5,7 +5,7 @@ wlan configuration import tkinter as tk from coretk.dialogs.dialog import Dialog -from coretk.dialogs.nodeicon import NodeIconDialog +from coretk.dialogs.nodeicon import IconDialog class WlanConfigDialog(Dialog): @@ -167,7 +167,9 @@ class WlanConfigDialog(Dialog): button.grid(row=0, column=1, padx=2, sticky="ew") def click_icon(self): - dialog = NodeIconDialog(self, self.app, self.canvas_node) + dialog = IconDialog( + self, self.app, self.canvas_node.name, self.canvas_node.image + ) dialog.show() if dialog.image: self.image = dialog.image diff --git a/coretk/coretk/widgets.py b/coretk/coretk/widgets.py new file mode 100644 index 00000000..80347e73 --- /dev/null +++ b/coretk/coretk/widgets.py @@ -0,0 +1,58 @@ +import tkinter as tk +from functools import partial + + +class ListboxScroll(tk.LabelFrame): + def __init__(self, master=None, cnf={}, **kw): + super().__init__(master, cnf, **kw) + self.columnconfigure(0, weight=1) + self.rowconfigure(0, weight=1) + self.scrollbar = tk.Scrollbar(self, orient=tk.VERTICAL) + self.scrollbar.grid(row=0, column=1, sticky="ns") + self.listbox = tk.Listbox( + self, selectmode=tk.SINGLE, yscrollcommand=self.scrollbar.set + ) + self.listbox.grid(row=0, column=0, sticky="nsew") + self.scrollbar.config(command=self.listbox.yview) + + +class CheckboxList(tk.LabelFrame): + def __init__(self, master=None, cnf={}, clicked=None, **kw): + super().__init__(master, cnf, **kw) + self.clicked = clicked + self.rowconfigure(0, weight=1) + self.columnconfigure(0, weight=1) + self.columnconfigure(1, weight=1) + self.canvas = tk.Canvas(self, highlightthickness=0) + self.canvas.grid(row=0, columnspan=2, sticky="nsew", padx=2, pady=2) + self.canvas.columnconfigure(0, weight=1) + self.canvas.rowconfigure(0, weight=1) + self.scrollbar = tk.Scrollbar( + self, orient="vertical", command=self.canvas.yview + ) + self.scrollbar.grid(row=0, column=2, sticky="ns") + self.frame = tk.Frame(self.canvas, padx=2, pady=2) + self.frame.columnconfigure(0, weight=1) + self.frame_id = self.canvas.create_window(0, 0, anchor="nw", window=self.frame) + self.canvas.update_idletasks() + self.canvas.configure( + scrollregion=self.canvas.bbox("all"), yscrollcommand=self.scrollbar.set + ) + self.frame.bind( + "", + lambda event: self.canvas.configure(scrollregion=self.canvas.bbox("all")), + ) + self.canvas.bind( + "", + lambda event: self.canvas.itemconfig(self.frame_id, width=event.width), + ) + + def clear(self): + for widget in self.frame.winfo_children(): + widget.destroy() + + def add(self, name): + var = tk.BooleanVar() + func = partial(self.clicked, name, var) + checkbox = tk.Checkbutton(self.frame, text=name, variable=var, command=func) + checkbox.grid(sticky="w") From b71f93e60669cbd90b7c47c500401287982a7bd6 Mon Sep 17 00:00:00 2001 From: bharnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 6 Nov 2019 07:22:40 -0800 Subject: [PATCH 6/6] added scrollable frame widget which can re-use code to be the basis for other scrollable frame widgets --- coretk/coretk/widgets.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/coretk/coretk/widgets.py b/coretk/coretk/widgets.py index 80347e73..606f7662 100644 --- a/coretk/coretk/widgets.py +++ b/coretk/coretk/widgets.py @@ -2,6 +2,41 @@ import tkinter as tk from functools import partial +class FrameScroll(tk.LabelFrame): + def __init__(self, master=None, cnf={}, **kw): + super().__init__(master, cnf, **kw) + self.rowconfigure(0, weight=1) + self.columnconfigure(0, weight=1) + self.columnconfigure(1, weight=1) + self.canvas = tk.Canvas(self, highlightthickness=0) + self.canvas.grid(row=0, columnspan=2, sticky="nsew", padx=2, pady=2) + self.canvas.columnconfigure(0, weight=1) + self.canvas.rowconfigure(0, weight=1) + self.scrollbar = tk.Scrollbar( + self, orient="vertical", command=self.canvas.yview + ) + self.scrollbar.grid(row=0, column=2, sticky="ns") + self.frame = tk.Frame(self.canvas, padx=2, pady=2) + self.frame.columnconfigure(0, weight=1) + self.frame_id = self.canvas.create_window(0, 0, anchor="nw", window=self.frame) + self.canvas.update_idletasks() + self.canvas.configure( + scrollregion=self.canvas.bbox("all"), yscrollcommand=self.scrollbar.set + ) + self.frame.bind( + "", + lambda event: self.canvas.configure(scrollregion=self.canvas.bbox("all")), + ) + self.canvas.bind( + "", + lambda event: self.canvas.itemconfig(self.frame_id, width=event.width), + ) + + def clear(self): + for widget in self.frame.winfo_children(): + widget.destroy() + + class ListboxScroll(tk.LabelFrame): def __init__(self, master=None, cnf={}, **kw): super().__init__(master, cnf, **kw)