removed CustomNode class, added nodeutils and NodeDraw to support defining the current node type to draw and reuse for custom nodes as well

This commit is contained in:
bharnden 2019-11-15 23:31:41 -08:00
parent 7981340b13
commit 8ad9b7d728
9 changed files with 146 additions and 139 deletions

View file

@ -9,12 +9,16 @@ from coretk.graph import CanvasGraph
from coretk.images import ImageEnum, Images
from coretk.menuaction import MenuAction
from coretk.menubar import Menubar
from coretk.nodeutils import NodeUtils
from coretk.toolbar import Toolbar
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
# load node icons
NodeUtils.setup()
# widgets
self.menubar = None
self.toolbar = None

View file

@ -7,13 +7,12 @@ import os
from core.api.grpc import client, core_pb2
from coretk.dialogs.sessions import SessionsDialog
from coretk.emaneodelnodeconfig import EmaneModelNodeConfig
from coretk.images import NODE_WIDTH, Images
from coretk.interface import InterfaceManager
from coretk.mobilitynodeconfig import MobilityNodeConfig
from coretk.nodeutils import NodeDraw
from coretk.servicenodeconfig import ServiceNodeConfig
from coretk.wlannodeconfig import WlanNodeConfig
NETWORK_NODES = {"switch", "hub", "wlan", "rj45", "tunnel", "emane"}
DEFAULT_NODES = {"router", "host", "PC", "mdr", "prouter"}
OBSERVERS = {
"processes": "ps",
@ -35,14 +34,6 @@ class CoreServer:
self.port = port
class CustomNode:
def __init__(self, name, image, image_file, services):
self.name = name
self.image = image
self.image_file = image_file
self.services = services
class Observer:
def __init__(self, name, cmd):
self.name = name
@ -96,12 +87,11 @@ class CoreClient:
# read custom nodes
for config in self.app.config.get("nodes", []):
name = config["name"]
image_file = config["image"]
image = Images.get_custom(image_file, NODE_WIDTH)
custom_node = CustomNode(
config["name"], image, image_file, set(config["services"])
)
self.custom_nodes[custom_node.name] = custom_node
services = set(config["services"])
node_draw = NodeDraw.from_custom(name, image_file, services)
self.custom_nodes[name] = node_draw
# read observers
for config in self.app.config.get("observers", []):
@ -448,6 +438,7 @@ class CoreClient:
self.emaneconfig_management.set_default_config(node_id)
# set default service configurations
# TODO: need to deal with this and custom node cases
if node_type == core_pb2.NodeType.DEFAULT:
self.serviceconfig_manager.node_default_services_configuration(
node_id=node_id, node_model=model

View file

@ -3,9 +3,9 @@ import tkinter as tk
from pathlib import Path
from tkinter import ttk
from coretk.coreclient import CustomNode
from coretk.dialogs.dialog import Dialog
from coretk.dialogs.icondialog import IconDialog
from coretk.nodeutils import NodeDraw
from coretk.widgets import CheckboxList, ListboxScroll
@ -119,7 +119,9 @@ class CustomNodesDialog(Dialog):
frame.columnconfigure(0, weight=1)
entry = ttk.Entry(frame, textvariable=self.name)
entry.grid(sticky="ew")
self.image_button = ttk.Button(frame, text="Icon", command=self.click_icon)
self.image_button = ttk.Button(
frame, text="Icon", compound=tk.LEFT, command=self.click_icon
)
self.image_button.grid(sticky="ew")
button = ttk.Button(frame, text="Services", command=self.click_services)
button.grid(sticky="ew")
@ -180,12 +182,12 @@ class CustomNodesDialog(Dialog):
def click_save(self):
self.app.config["nodes"].clear()
for name in sorted(self.app.core.custom_nodes):
custom_node = self.app.core.custom_nodes[name]
node_draw = self.app.core.custom_nodes[name]
self.app.config["nodes"].append(
{
"name": custom_node.name,
"image": custom_node.image_file,
"services": list(custom_node.services),
"name": name,
"image": node_draw.image_file,
"services": list(node_draw.services),
}
)
logging.info("saving custom nodes: %s", self.app.config["nodes"])
@ -195,10 +197,9 @@ class CustomNodesDialog(Dialog):
def click_create(self):
name = self.name.get()
if name not in self.app.core.custom_nodes:
custom_node = CustomNode(
name, self.image, Path(self.image_file).name, set(self.services)
)
self.app.core.custom_nodes[name] = custom_node
image_file = Path(self.image_file).stem
node_draw = NodeDraw.from_custom(name, image_file, set(self.services))
self.app.core.custom_nodes[name] = node_draw
self.nodes_list.listbox.insert(tk.END, name)
self.reset_values()
@ -207,12 +208,12 @@ class CustomNodesDialog(Dialog):
if self.selected:
previous_name = self.selected
self.selected = name
custom_node = self.app.core.custom_nodes.pop(previous_name)
custom_node.name = name
custom_node.image = self.image
custom_node.image_file = Path(self.image_file).stem
custom_node.services = self.services
self.app.core.custom_nodes[name] = custom_node
node_draw = self.app.core.custom_nodes.pop(previous_name)
node_draw.model = name
node_draw.image_file = Path(self.image_file).stem
node_draw.image = self.image
node_draw.services = self.services
self.app.core.custom_nodes[name] = node_draw
self.nodes_list.listbox.delete(self.selected_index)
self.nodes_list.listbox.insert(self.selected_index, name)
self.nodes_list.listbox.selection_set(self.selected_index)
@ -230,11 +231,11 @@ class CustomNodesDialog(Dialog):
if selection:
self.selected_index = selection[0]
self.selected = self.nodes_list.listbox.get(self.selected_index)
custom_node = self.app.core.custom_nodes[self.selected]
self.name.set(custom_node.name)
self.services = custom_node.services
self.image = custom_node.image
self.image_file = custom_node.image_file
node_draw = self.app.core.custom_nodes[self.selected]
self.name.set(node_draw.model)
self.services = node_draw.services
self.image = node_draw.image
self.image_file = node_draw.image_file
self.image_button.config(image=self.image)
self.edit_button.config(state=tk.NORMAL)
self.delete_button.config(state=tk.NORMAL)

View file

@ -1,6 +1,7 @@
import tkinter as tk
from tkinter import filedialog, ttk
from coretk import nodeutils
from coretk.appconfig import ICONS_PATH
from coretk.dialogs.dialog import Dialog
from coretk.images import Images
@ -54,7 +55,7 @@ class IconDialog(Dialog):
),
)
if file_path:
self.image = Images.create(file_path, 32, 32)
self.image = Images.create(file_path, nodeutils.ICON_SIZE)
self.image_label.config(image=self.image)
self.file_path.set(file_path)

View file

@ -11,6 +11,7 @@ from coretk.graph_helper import GraphHelper, WlanAntennaManager
from coretk.images import Images
from coretk.linkinfo import LinkInfo, Throughput
from coretk.nodedelete import CanvasComponentManagement
from coretk.nodeutils import NodeUtils
from coretk.wirelessconnection import WirelessConnection
@ -37,9 +38,7 @@ class CanvasGraph(tk.Canvas):
kwargs["highlightthickness"] = 0
super().__init__(master, cnf, **kwargs)
self.mode = GraphMode.SELECT
self.draw_node_image = None
self.draw_node_type = None
self.draw_node_model = None
self.node_draw = None
self.selected = None
self.node_context = None
self.nodes = {}
@ -96,9 +95,7 @@ class CanvasGraph(tk.Canvas):
# set the private variables to default value
self.mode = GraphMode.SELECT
self.draw_node_image = None
self.draw_node_type = None
self.draw_node_model = None
self.node_draw = None
self.selected = None
self.node_context = None
self.nodes.clear()
@ -157,7 +154,7 @@ class CanvasGraph(tk.Canvas):
continue
# draw nodes on the canvas
image = Images.node_icon(core_node.type, core_node.model)
image = NodeUtils.node_icon(core_node.type, core_node.model)
position = core_node.position
node = CanvasNode(position.x, position.y, image, self.master, core_node)
self.nodes[node.id] = node
@ -267,13 +264,7 @@ class CanvasGraph(tk.Canvas):
self.handle_edge_release(event)
elif self.mode == GraphMode.NODE:
x, y = self.canvas_xy(event)
self.add_node(
x,
y,
self.draw_node_image,
self.draw_node_type,
self.draw_node_model,
)
self.add_node(x, y)
elif self.mode == GraphMode.PICKNODE:
self.mode = GraphMode.NODE
@ -404,12 +395,14 @@ class CanvasGraph(tk.Canvas):
# delete the related data from core
self.core.delete_wanted_graph_nodes(node_ids, to_delete_edge_tokens)
def add_node(self, x, y, image, node_type, model):
def add_node(self, x, y):
plot_id = self.find_all()[0]
logging.info("add node event: %s - %s", plot_id, self.selected)
if self.selected == plot_id:
core_node = self.core.create_node(int(x), int(y), node_type, model)
node = CanvasNode(x, y, image, self.master, core_node)
core_node = self.core.create_node(
int(x), int(y), self.node_draw.node_type, self.node_draw.model
)
node = CanvasNode(x, y, self.node_draw.image, self.master, core_node)
self.core.canvas_nodes[core_node.id] = node
self.nodes[node.id] = node
return node

View file

@ -1,9 +1,7 @@
import logging
from enum import Enum
from PIL import Image, ImageTk
from core.api.grpc import core_pb2
from coretk.appconfig import LOCAL_ICONS_PATH
NODE_WIDTH = 32
@ -35,45 +33,6 @@ class Images:
file_path = cls.images[name]
return cls.create(file_path, width, height)
@classmethod
def node_icon(cls, node_type, node_model):
"""
Retrieve image based on type and model
:param core_pb2.NodeType node_type: core node type
:param string node_model: the node model
:return: core node icon
:rtype: PhotoImage
"""
image_enum = ImageEnum.ROUTER
if node_type == core_pb2.NodeType.SWITCH:
image_enum = ImageEnum.SWITCH
elif node_type == core_pb2.NodeType.HUB:
image_enum = ImageEnum.HUB
elif node_type == core_pb2.NodeType.WIRELESS_LAN:
image_enum = ImageEnum.WLAN
elif node_type == core_pb2.NodeType.EMANE:
image_enum = ImageEnum.EMANE
elif node_type == core_pb2.NodeType.RJ45:
image_enum = ImageEnum.RJ45
elif node_type == core_pb2.NodeType.TUNNEL:
image_enum = ImageEnum.TUNNEL
elif node_type == core_pb2.NodeType.DEFAULT:
if node_model == "router":
image_enum = ImageEnum.ROUTER
elif node_model == "host":
image_enum = ImageEnum.HOST
elif node_model == "PC":
image_enum = ImageEnum.PC
elif node_model == "mdr":
image_enum = ImageEnum.MDR
elif node_model == "prouter":
image_enum = ImageEnum.PROUTER
else:
logging.error("invalid node model: %s", node_model)
else:
logging.error("invalid node type: %s", node_type)
return Images.get(image_enum, NODE_WIDTH)
class ImageEnum(Enum):
SWITCH = "lanswitch"

View file

@ -0,0 +1,79 @@
from core.api.grpc.core_pb2 import NodeType
from coretk.images import ImageEnum, Images
ICON_SIZE = 32
class NodeDraw:
def __init__(self):
self.custom = False
self.image = None
self.image_enum = None
self.image_file = None
self.node_type = None
self.model = None
self.tooltip = None
self.services = set()
@classmethod
def from_setup(cls, image_enum, node_type, model=None, tooltip=None):
node_draw = NodeDraw()
node_draw.image_enum = image_enum
node_draw.image = Images.get(image_enum, ICON_SIZE)
node_draw.node_type = node_type
node_draw.model = model
if tooltip is None:
tooltip = model
node_draw.tooltip = tooltip
return node_draw
@classmethod
def from_custom(cls, name, image_file, services):
node_draw = NodeDraw()
node_draw.custom = True
node_draw.image_file = image_file
node_draw.image = Images.get_custom(image_file, ICON_SIZE)
node_draw.node_type = NodeType.DEFAULT
node_draw.services = services
node_draw.model = name
node_draw.tooltip = name
return node_draw
class NodeUtils:
NODES = []
NETWORK_NODES = []
NODE_ICONS = {}
@classmethod
def node_icon(cls, node_type, model):
return cls.NODE_ICONS[(node_type, model)]
@classmethod
def setup(cls):
nodes = [
(ImageEnum.ROUTER, NodeType.DEFAULT, "router"),
(ImageEnum.HOST, NodeType.DEFAULT, "host"),
(ImageEnum.PC, NodeType.DEFAULT, "PC"),
(ImageEnum.MDR, NodeType.DEFAULT, "mdr"),
(ImageEnum.PROUTER, NodeType.DEFAULT, "prouter"),
(ImageEnum.DOCKER, NodeType.DOCKER, "Docker"),
(ImageEnum.LXC, NodeType.LXC, "LXC"),
]
for image_enum, node_type, model in nodes:
node_draw = NodeDraw.from_setup(image_enum, node_type, model)
cls.NODES.append(node_draw)
cls.NODE_ICONS[(node_type, model)] = node_draw.image
network_nodes = [
(ImageEnum.HUB, NodeType.HUB, "ethernet hub"),
(ImageEnum.SWITCH, NodeType.SWITCH, "ethernet switch"),
(ImageEnum.WLAN, NodeType.WIRELESS_LAN, "wireless LAN"),
(ImageEnum.EMANE, NodeType.EMANE, "EMANE"),
(ImageEnum.RJ45, NodeType.RJ45, "rj45 physical interface tool"),
(ImageEnum.TUNNEL, NodeType.TUNNEL, "tunnel tool"),
]
for image_enum, node_type, tooltip in network_nodes:
node_draw = NodeDraw.from_setup(image_enum, node_type, tooltip=tooltip)
cls.NETWORK_NODES.append(node_draw)
cls.NODE_ICONS[(node_type, None)] = node_draw.image

View file

@ -54,7 +54,12 @@ def load(style):
},
},
"TButton": {
"configure": {"width": 8, "padding": (5, 1), "relief": tk.RAISED},
"configure": {
"width": 8,
"padding": (5, 1),
"relief": tk.RAISED,
"anchor": tk.CENTER,
},
"map": {
"relief": [("pressed", tk.SUNKEN)],
"shiftrelief": [("pressed", 1)],

View file

@ -3,10 +3,10 @@ import tkinter as tk
from functools import partial
from tkinter import ttk
from core.api.grpc import core_pb2
from coretk.dialogs.customnodes import CustomNodesDialog
from coretk.graph import GraphMode
from coretk.images import ImageEnum, Images
from coretk.nodeutils import NodeUtils
from coretk.tooltip import Tooltip
WIDTH = 32
@ -129,33 +129,16 @@ class Toolbar(ttk.Frame):
def draw_node_picker(self):
self.hide_pickers()
self.node_picker = ttk.Frame(self.master)
nodes = [
(ImageEnum.ROUTER, core_pb2.NodeType.DEFAULT, "router"),
(ImageEnum.HOST, core_pb2.NodeType.DEFAULT, "host"),
(ImageEnum.PC, core_pb2.NodeType.DEFAULT, "PC"),
(ImageEnum.MDR, core_pb2.NodeType.DEFAULT, "mdr"),
(ImageEnum.PROUTER, core_pb2.NodeType.DEFAULT, "prouter"),
(ImageEnum.DOCKER, core_pb2.NodeType.DOCKER, "Docker"),
(ImageEnum.LXC, core_pb2.NodeType.LXC, "LXC"),
]
# draw default nodes
for image_enum, node_type, model in nodes:
image = icon(image_enum)
func = partial(
self.update_button, self.node_button, image, node_type, model
)
self.create_picker_button(image, func, self.node_picker, model)
for node_draw in NodeUtils.NODES:
image = icon(node_draw.image_enum)
func = partial(self.update_button, self.node_button, image, node_draw)
self.create_picker_button(image, func, self.node_picker, node_draw.tooltip)
# draw custom nodes
for name in sorted(self.app.core.custom_nodes):
custom_node = self.app.core.custom_nodes[name]
image = custom_node.image
func = partial(
self.update_button,
self.node_button,
image,
core_pb2.NodeType.DEFAULT,
name,
)
node_draw = self.app.core.custom_nodes[name]
image = Images.get_custom(node_draw.image_file, WIDTH)
func = partial(self.update_button, self.node_button, image, node_draw)
self.create_picker_button(image, func, self.node_picker, name)
# draw edit node
image = icon(ImageEnum.EDITNODE)
@ -227,15 +210,13 @@ class Toolbar(ttk.Frame):
dialog = CustomNodesDialog(self.app, self.app)
dialog.show()
def update_button(self, button, image, node_type, model=None):
logging.info("update button(%s): %s", button, node_type)
def update_button(self, button, image, node_draw):
logging.info("update button(%s): %s", button, node_draw)
self.hide_pickers()
button.configure(image=image)
button.image = image
self.app.canvas.mode = GraphMode.NODE
self.app.canvas.draw_node_image = image
self.app.canvas.draw_node_type = node_type
self.app.canvas.draw_node_model = model
self.app.canvas.node_draw = node_draw
def hide_pickers(self):
logging.info("hiding pickers")
@ -271,21 +252,13 @@ class Toolbar(ttk.Frame):
"""
self.hide_pickers()
self.network_picker = ttk.Frame(self.master)
nodes = [
(ImageEnum.HUB, core_pb2.NodeType.HUB, "ethernet hub"),
(ImageEnum.SWITCH, core_pb2.NodeType.SWITCH, "ethernet switch"),
(ImageEnum.WLAN, core_pb2.NodeType.WIRELESS_LAN, "wireless LAN"),
(ImageEnum.EMANE, core_pb2.NodeType.EMANE, "EMANE"),
(ImageEnum.RJ45, core_pb2.NodeType.RJ45, "rj45 physical interface tool"),
(ImageEnum.TUNNEL, core_pb2.NodeType.TUNNEL, "tunnel tool"),
]
for image_enum, node_type, tooltip in nodes:
image = icon(image_enum)
for node_draw in NodeUtils.NETWORK_NODES:
image = icon(node_draw.image_enum)
self.create_picker_button(
image,
partial(self.update_button, self.network_button, image, node_type),
partial(self.update_button, self.network_button, image, node_draw),
self.network_picker,
tooltip,
node_draw.tooltip,
)
self.design_select(self.network_button)
self.network_button.after(
@ -294,7 +267,8 @@ class Toolbar(ttk.Frame):
def create_network_button(self):
"""
Create link-layer node button and the options that represent different link-layer node types
Create link-layer node button and the options that represent different
link-layer node types.
:return: nothing
"""