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.images import ImageEnum, Images
from coretk.menuaction import MenuAction from coretk.menuaction import MenuAction
from coretk.menubar import Menubar from coretk.menubar import Menubar
from coretk.nodeutils import NodeUtils
from coretk.toolbar import Toolbar from coretk.toolbar import Toolbar
class Application(tk.Frame): class Application(tk.Frame):
def __init__(self, master=None): def __init__(self, master=None):
super().__init__(master) super().__init__(master)
# load node icons
NodeUtils.setup()
# widgets # widgets
self.menubar = None self.menubar = None
self.toolbar = None self.toolbar = None

View file

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

View file

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

View file

@ -1,6 +1,7 @@
import tkinter as tk import tkinter as tk
from tkinter import filedialog, ttk from tkinter import filedialog, ttk
from coretk import nodeutils
from coretk.appconfig import ICONS_PATH from coretk.appconfig import ICONS_PATH
from coretk.dialogs.dialog import Dialog from coretk.dialogs.dialog import Dialog
from coretk.images import Images from coretk.images import Images
@ -54,7 +55,7 @@ class IconDialog(Dialog):
), ),
) )
if file_path: 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.image_label.config(image=self.image)
self.file_path.set(file_path) 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.images import Images
from coretk.linkinfo import LinkInfo, Throughput from coretk.linkinfo import LinkInfo, Throughput
from coretk.nodedelete import CanvasComponentManagement from coretk.nodedelete import CanvasComponentManagement
from coretk.nodeutils import NodeUtils
from coretk.wirelessconnection import WirelessConnection from coretk.wirelessconnection import WirelessConnection
@ -37,9 +38,7 @@ class CanvasGraph(tk.Canvas):
kwargs["highlightthickness"] = 0 kwargs["highlightthickness"] = 0
super().__init__(master, cnf, **kwargs) super().__init__(master, cnf, **kwargs)
self.mode = GraphMode.SELECT self.mode = GraphMode.SELECT
self.draw_node_image = None self.node_draw = None
self.draw_node_type = None
self.draw_node_model = None
self.selected = None self.selected = None
self.node_context = None self.node_context = None
self.nodes = {} self.nodes = {}
@ -96,9 +95,7 @@ class CanvasGraph(tk.Canvas):
# set the private variables to default value # set the private variables to default value
self.mode = GraphMode.SELECT self.mode = GraphMode.SELECT
self.draw_node_image = None self.node_draw = None
self.draw_node_type = None
self.draw_node_model = None
self.selected = None self.selected = None
self.node_context = None self.node_context = None
self.nodes.clear() self.nodes.clear()
@ -157,7 +154,7 @@ class CanvasGraph(tk.Canvas):
continue continue
# draw nodes on the canvas # 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 position = core_node.position
node = CanvasNode(position.x, position.y, image, self.master, core_node) node = CanvasNode(position.x, position.y, image, self.master, core_node)
self.nodes[node.id] = node self.nodes[node.id] = node
@ -267,13 +264,7 @@ class CanvasGraph(tk.Canvas):
self.handle_edge_release(event) self.handle_edge_release(event)
elif self.mode == GraphMode.NODE: elif self.mode == GraphMode.NODE:
x, y = self.canvas_xy(event) x, y = self.canvas_xy(event)
self.add_node( self.add_node(x, y)
x,
y,
self.draw_node_image,
self.draw_node_type,
self.draw_node_model,
)
elif self.mode == GraphMode.PICKNODE: elif self.mode == GraphMode.PICKNODE:
self.mode = GraphMode.NODE self.mode = GraphMode.NODE
@ -404,12 +395,14 @@ class CanvasGraph(tk.Canvas):
# delete the related data from core # delete the related data from core
self.core.delete_wanted_graph_nodes(node_ids, to_delete_edge_tokens) 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] plot_id = self.find_all()[0]
logging.info("add node event: %s - %s", plot_id, self.selected) logging.info("add node event: %s - %s", plot_id, self.selected)
if self.selected == plot_id: if self.selected == plot_id:
core_node = self.core.create_node(int(x), int(y), node_type, model) core_node = self.core.create_node(
node = CanvasNode(x, y, image, self.master, core_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.core.canvas_nodes[core_node.id] = node
self.nodes[node.id] = node self.nodes[node.id] = node
return node return node

View file

@ -1,9 +1,7 @@
import logging
from enum import Enum from enum import Enum
from PIL import Image, ImageTk from PIL import Image, ImageTk
from core.api.grpc import core_pb2
from coretk.appconfig import LOCAL_ICONS_PATH from coretk.appconfig import LOCAL_ICONS_PATH
NODE_WIDTH = 32 NODE_WIDTH = 32
@ -35,45 +33,6 @@ class Images:
file_path = cls.images[name] file_path = cls.images[name]
return cls.create(file_path, width, height) 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): class ImageEnum(Enum):
SWITCH = "lanswitch" 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": { "TButton": {
"configure": {"width": 8, "padding": (5, 1), "relief": tk.RAISED}, "configure": {
"width": 8,
"padding": (5, 1),
"relief": tk.RAISED,
"anchor": tk.CENTER,
},
"map": { "map": {
"relief": [("pressed", tk.SUNKEN)], "relief": [("pressed", tk.SUNKEN)],
"shiftrelief": [("pressed", 1)], "shiftrelief": [("pressed", 1)],

View file

@ -3,10 +3,10 @@ import tkinter as tk
from functools import partial from functools import partial
from tkinter import ttk from tkinter import ttk
from core.api.grpc import core_pb2
from coretk.dialogs.customnodes import CustomNodesDialog from coretk.dialogs.customnodes import CustomNodesDialog
from coretk.graph import GraphMode from coretk.graph import GraphMode
from coretk.images import ImageEnum, Images from coretk.images import ImageEnum, Images
from coretk.nodeutils import NodeUtils
from coretk.tooltip import Tooltip from coretk.tooltip import Tooltip
WIDTH = 32 WIDTH = 32
@ -129,33 +129,16 @@ class Toolbar(ttk.Frame):
def draw_node_picker(self): def draw_node_picker(self):
self.hide_pickers() self.hide_pickers()
self.node_picker = ttk.Frame(self.master) 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 # draw default nodes
for image_enum, node_type, model in nodes: for node_draw in NodeUtils.NODES:
image = icon(image_enum) image = icon(node_draw.image_enum)
func = partial( func = partial(self.update_button, self.node_button, image, node_draw)
self.update_button, self.node_button, image, node_type, model self.create_picker_button(image, func, self.node_picker, node_draw.tooltip)
)
self.create_picker_button(image, func, self.node_picker, model)
# draw custom nodes # draw custom nodes
for name in sorted(self.app.core.custom_nodes): 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]
image = custom_node.image image = Images.get_custom(node_draw.image_file, WIDTH)
func = partial( func = partial(self.update_button, self.node_button, image, node_draw)
self.update_button,
self.node_button,
image,
core_pb2.NodeType.DEFAULT,
name,
)
self.create_picker_button(image, func, self.node_picker, name) self.create_picker_button(image, func, self.node_picker, name)
# draw edit node # draw edit node
image = icon(ImageEnum.EDITNODE) image = icon(ImageEnum.EDITNODE)
@ -227,15 +210,13 @@ class Toolbar(ttk.Frame):
dialog = CustomNodesDialog(self.app, self.app) dialog = CustomNodesDialog(self.app, self.app)
dialog.show() dialog.show()
def update_button(self, button, image, node_type, model=None): def update_button(self, button, image, node_draw):
logging.info("update button(%s): %s", button, node_type) logging.info("update button(%s): %s", button, node_draw)
self.hide_pickers() self.hide_pickers()
button.configure(image=image) button.configure(image=image)
button.image = image button.image = image
self.app.canvas.mode = GraphMode.NODE self.app.canvas.mode = GraphMode.NODE
self.app.canvas.draw_node_image = image self.app.canvas.node_draw = node_draw
self.app.canvas.draw_node_type = node_type
self.app.canvas.draw_node_model = model
def hide_pickers(self): def hide_pickers(self):
logging.info("hiding pickers") logging.info("hiding pickers")
@ -271,21 +252,13 @@ class Toolbar(ttk.Frame):
""" """
self.hide_pickers() self.hide_pickers()
self.network_picker = ttk.Frame(self.master) self.network_picker = ttk.Frame(self.master)
nodes = [ for node_draw in NodeUtils.NETWORK_NODES:
(ImageEnum.HUB, core_pb2.NodeType.HUB, "ethernet hub"), image = icon(node_draw.image_enum)
(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)
self.create_picker_button( self.create_picker_button(
image, image,
partial(self.update_button, self.network_button, image, node_type), partial(self.update_button, self.network_button, image, node_draw),
self.network_picker, self.network_picker,
tooltip, node_draw.tooltip,
) )
self.design_select(self.network_button) self.design_select(self.network_button)
self.network_button.after( self.network_button.after(
@ -294,7 +267,8 @@ class Toolbar(ttk.Frame):
def create_network_button(self): 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 :return: nothing
""" """