Merge branch 'coretk-config' into coretk-nodedelete

This commit is contained in:
Huy Pham 2019-11-07 15:21:13 -08:00
commit 9898e50739
10 changed files with 377 additions and 534 deletions

View file

@ -50,10 +50,8 @@ class Application(tk.Frame):
self.master.config(menu=self.menubar) self.master.config(menu=self.menubar)
def draw_toolbar(self): def draw_toolbar(self):
edit_frame = tk.Frame(self) self.core_editbar = CoreToolbar(self, self)
edit_frame.pack(side=tk.LEFT, fill=tk.Y, ipadx=2, ipady=2) self.core_editbar.pack(side=tk.LEFT, fill=tk.Y, ipadx=2, ipady=2)
self.core_editbar = CoreToolbar(self, edit_frame, self.menubar)
self.core_editbar.create_toolbar()
def draw_canvas(self): def draw_canvas(self):
self.canvas = CanvasGraph( self.canvas = CanvasGraph(

View file

@ -42,7 +42,10 @@ def check_directory():
for background in LOCAL_BACKGROUND_PATH.glob("*"): for background in LOCAL_BACKGROUND_PATH.glob("*"):
new_background = BACKGROUNDS_PATH.joinpath(background.name) new_background = BACKGROUNDS_PATH.joinpath(background.name)
shutil.copy(background, new_background) shutil.copy(background, new_background)
config = {"servers": [{"name": "example", "address": "127.0.0.1", "port": 50051}]} config = {
"servers": [{"name": "example", "address": "127.0.0.1", "port": 50051}],
"nodes": [],
}
save_config(config) save_config(config)

View file

@ -8,12 +8,13 @@ from core.api.grpc import client, core_pb2
from coretk.coretocanvas import CoreToCanvasMapping from coretk.coretocanvas import CoreToCanvasMapping
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 Images
from coretk.interface import Interface, InterfaceManager from coretk.interface import Interface, InterfaceManager
from coretk.mobilitynodeconfig import MobilityNodeConfig from coretk.mobilitynodeconfig import MobilityNodeConfig
from coretk.wlannodeconfig import WlanNodeConfig from coretk.wlannodeconfig import WlanNodeConfig
link_layer_nodes = ["switch", "hub", "wlan", "rj45", "tunnel", "emane"] link_layer_nodes = ["switch", "hub", "wlan", "rj45", "tunnel", "emane"]
network_layer_nodes = ["router", "host", "PC", "mdr", "prouter", "OVS"] network_layer_nodes = ["router", "host", "PC", "mdr", "prouter"]
class Node: class Node:
@ -64,6 +65,14 @@ 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 CoreClient: class CoreClient:
def __init__(self, app): def __init__(self, app):
""" """
@ -77,13 +86,10 @@ class CoreClient:
self.interface_helper = None self.interface_helper = None
self.services = {} self.services = {}
# distributed server data # loaded configuration data
self.servers = {} self.servers = {}
for server_config in self.app.config["servers"]: self.custom_nodes = {}
server = CoreServer( self.read_config()
server_config["name"], server_config["address"], server_config["port"]
)
self.servers[server.name] = server
# data for managing the current session # data for managing the current session
self.nodes = {} self.nodes = {}
@ -99,6 +105,23 @@ class CoreClient:
self.emaneconfig_management = EmaneModelNodeConfig(app) self.emaneconfig_management = EmaneModelNodeConfig(app)
self.emane_config = None self.emane_config = None
def read_config(self):
# read distributed server
for server_config in self.app.config["servers"]:
server = CoreServer(
server_config["name"], server_config["address"], server_config["port"]
)
self.servers[server.name] = server
# read custom nodes
for node in self.app.config["nodes"]:
image_file = node["image"]
image = Images.get_custom(image_file)
custom_node = CustomNode(
node["name"], image, image_file, set(node["services"])
)
self.custom_nodes[custom_node.name] = custom_node
def handle_events(self, event): def handle_events(self, event):
logging.info("event: %s", event) logging.info("event: %s", event)
if event.link_event is not None: if event.link_event is not None:
@ -185,11 +208,9 @@ class CoreClient:
# draw tool bar appropritate with session state # draw tool bar appropritate with session state
if session_state == core_pb2.SessionState.RUNTIME: if session_state == core_pb2.SessionState.RUNTIME:
self.app.core_editbar.destroy_children_widgets() self.app.core_editbar.runtime_frame.tkraise()
self.app.core_editbar.create_runtime_toolbar()
else: else:
self.app.core_editbar.destroy_children_widgets() self.app.core_editbar.design_frame.tkraise()
self.app.core_editbar.create_toolbar()
def create_new_session(self): def create_new_session(self):
""" """
@ -217,7 +238,9 @@ class CoreClient:
s = self.client.get_session(sid).session s = self.client.get_session(sid).session
# delete links and nodes from running session # delete links and nodes from running session
if s.state == core_pb2.SessionState.RUNTIME: if s.state == core_pb2.SessionState.RUNTIME:
self.set_session_state("datacollect", sid) self.client.set_session_state(
self.session_id, core_pb2.SessionState.DATACOLLECT
)
self.delete_links(sid) self.delete_links(sid)
self.delete_nodes(sid) self.delete_nodes(sid)
self.delete_session(sid) self.delete_session(sid)
@ -251,48 +274,6 @@ class CoreClient:
# logging.info("get session: %s", response) # logging.info("get session: %s", response)
return response.session.state return response.session.state
def set_session_state(self, state, custom_session_id=None):
"""
Set session state
:param str state: session state to set
:return: nothing
"""
if custom_session_id is None:
sid = self.session_id
else:
sid = custom_session_id
response = None
if state == "configuration":
response = self.client.set_session_state(
sid, core_pb2.SessionState.CONFIGURATION
)
elif state == "instantiation":
response = self.client.set_session_state(
sid, core_pb2.SessionState.INSTANTIATION
)
elif state == "datacollect":
response = self.client.set_session_state(
sid, core_pb2.SessionState.DATACOLLECT
)
elif state == "shutdown":
response = self.client.set_session_state(
sid, core_pb2.SessionState.SHUTDOWN
)
elif state == "runtime":
response = self.client.set_session_state(sid, core_pb2.SessionState.RUNTIME)
elif state == "definition":
response = self.client.set_session_state(
sid, core_pb2.SessionState.DEFINITION
)
elif state == "none":
response = self.client.set_session_state(sid, core_pb2.SessionState.NONE)
else:
logging.error("coregrpc.py: set_session_state: INVALID STATE")
logging.info("set session state: %s", response)
def edit_node(self, node_id, x, y): def edit_node(self, node_id, x, y):
position = core_pb2.Position(x=x, y=y) position = core_pb2.Position(x=x, y=y)
response = self.client.edit_node(self.session_id, node_id, position) response = self.client.edit_node(self.session_id, node_id, position)
@ -451,6 +432,8 @@ class CoreClient:
node_type = core_pb2.NodeType.WIRELESS_LAN node_type = core_pb2.NodeType.WIRELESS_LAN
elif name == "rj45": elif name == "rj45":
node_type = core_pb2.NodeType.RJ45 node_type = core_pb2.NodeType.RJ45
elif name == "emane":
node_type = core_pb2.NodeType.EMANE
elif name == "tunnel": elif name == "tunnel":
node_type = core_pb2.NodeType.TUNNEL node_type = core_pb2.NodeType.TUNNEL
elif name == "emane": elif name == "emane":
@ -459,7 +442,7 @@ class CoreClient:
node_type = core_pb2.NodeType.DEFAULT node_type = core_pb2.NodeType.DEFAULT
node_model = name node_model = name
else: else:
logging.error("grpcmanagemeny.py INVALID node name") logging.error("invalid node name: %s", name)
nid = self.get_id() nid = self.get_id()
create_node = Node(session_id, nid, node_type, node_model, x, y, name) create_node = Node(session_id, nid, node_type, node_model, x, y, name)
@ -473,9 +456,8 @@ class CoreClient:
self.nodes[canvas_id] = create_node self.nodes[canvas_id] = create_node
self.core_mapping.map_core_id_to_canvas_id(nid, canvas_id) self.core_mapping.map_core_id_to_canvas_id(nid, canvas_id)
# self.core_id_to_canvas_id[nid] = canvas_id
logging.debug( logging.debug(
"Adding node to GrpcManager.. Session id: %s, Coords: (%s, %s), Name: %s", "Adding node to core.. session id: %s, coords: (%s, %s), name: %s",
session_id, session_id,
x, x,
y, y,

View file

@ -1,5 +1,6 @@
import logging import logging
import tkinter as tk import tkinter as tk
from functools import partial
from coretk.coretoolbarhelp import CoreToolbarHelp from coretk.coretoolbarhelp import CoreToolbarHelp
from coretk.dialogs.customnodes import CustomNodesDialog from coretk.dialogs.customnodes import CustomNodesDialog
@ -8,21 +9,20 @@ from coretk.images import ImageEnum, Images
from coretk.tooltip import CreateToolTip from coretk.tooltip import CreateToolTip
class CoreToolbar(object): class CoreToolbar(tk.Frame):
""" """
Core toolbar class Core toolbar class
""" """
def __init__(self, app, edit_frame, menubar): def __init__(self, master, app, cnf={}, **kwargs):
""" """
Create a CoreToolbar instance Create a CoreToolbar instance
:param tkinter.Frame edit_frame: edit frame :param tkinter.Frame edit_frame: edit frame
""" """
super().__init__(master, cnf, **kwargs)
self.app = app self.app = app
self.master = app.master self.master = app.master
self.edit_frame = edit_frame
self.menubar = menubar
self.radio_value = tk.IntVar() self.radio_value = tk.IntVar()
self.exec_radio_value = tk.IntVar() self.exec_radio_value = tk.IntVar()
@ -30,44 +30,144 @@ class CoreToolbar(object):
self.width = 32 self.width = 32
self.height = 32 self.height = 32
self.selection_tool_button = None
# Reference to the option menus # Reference to the option menus
self.selection_tool_button = None
self.link_layer_option_menu = None self.link_layer_option_menu = None
self.marker_option_menu = None self.marker_option_menu = None
self.network_layer_option_menu = None self.network_layer_option_menu = None
self.canvas = None self.canvas = None
self.node_button = None
self.network_button = None
self.annotation_button = None
def destroy_previous_frame(self): # frames
""" self.design_frame = None
Destroy any extra frame from previous before drawing a new one self.runtime_frame = None
self.node_picker = None
self.network_picker = None
self.annotation_picker = None
:return: nothing # draw components
""" self.draw()
if (
self.network_layer_option_menu
and self.network_layer_option_menu.winfo_exists()
):
self.network_layer_option_menu.destroy()
if self.link_layer_option_menu and self.link_layer_option_menu.winfo_exists():
self.link_layer_option_menu.destroy()
if self.marker_option_menu and self.marker_option_menu.winfo_exists():
self.marker_option_menu.destroy()
def destroy_children_widgets(self): def draw(self):
""" self.columnconfigure(0, weight=1)
Destroy all children of a parent widget self.rowconfigure(0, weight=1)
self.draw_design_frame()
self.draw_runtime_frame()
self.design_frame.tkraise()
:param tkinter.Frame parent: parent frame def draw_design_frame(self):
:return: nothing self.design_frame = tk.Frame(self)
""" self.design_frame.grid(row=0, column=0, sticky="nsew")
self.design_frame.columnconfigure(0, weight=1)
for i in self.edit_frame.winfo_children(): self.create_regular_button(
if i.winfo_name() != "!frame": self.design_frame,
i.destroy() Images.get(ImageEnum.START),
self.click_start_session_tool,
"start the session",
)
self.create_radio_button(
self.design_frame,
Images.get(ImageEnum.SELECT),
self.click_selection_tool,
self.radio_value,
1,
"selection tool",
)
self.create_radio_button(
self.design_frame,
Images.get(ImageEnum.LINK),
self.click_link_tool,
self.radio_value,
2,
"link tool",
)
self.create_node_button()
self.create_link_layer_button()
self.create_marker_button()
self.radio_value.set(1)
def create_button(self, img, func, frame, main_button, btt_message): def draw_runtime_frame(self):
self.runtime_frame = tk.Frame(self)
self.runtime_frame.grid(row=0, column=0, sticky="nsew")
self.runtime_frame.columnconfigure(0, weight=1)
self.create_regular_button(
self.runtime_frame,
Images.get(ImageEnum.STOP),
self.click_stop_button,
"stop the session",
)
self.create_radio_button(
self.runtime_frame,
Images.get(ImageEnum.SELECT),
self.click_selection_tool,
self.exec_radio_value,
1,
"selection tool",
)
self.create_observe_button()
self.create_radio_button(
self.runtime_frame,
Images.get(ImageEnum.PLOT),
self.click_plot_button,
self.exec_radio_value,
2,
"plot",
)
self.create_radio_button(
self.runtime_frame,
Images.get(ImageEnum.MARKER),
self.click_marker_button,
self.exec_radio_value,
3,
"marker",
)
self.create_radio_button(
self.runtime_frame,
Images.get(ImageEnum.TWONODE),
self.click_two_node_button,
self.exec_radio_value,
4,
"run command from one node to another",
)
self.create_regular_button(
self.runtime_frame, Images.get(ImageEnum.RUN), self.click_run_button, "run"
)
self.exec_radio_value.set(1)
def draw_node_picker(self):
self.hide_pickers()
self.node_picker = tk.Frame(self.master, padx=1, pady=1)
nodes = [
(ImageEnum.ROUTER, "router"),
(ImageEnum.HOST, "host"),
(ImageEnum.PC, "PC"),
(ImageEnum.MDR, "mdr"),
(ImageEnum.PROUTER, "prouter"),
(ImageEnum.EDITNODE, "custom node types"),
]
for image_enum, tooltip in nodes:
self.create_button(
Images.get(image_enum),
partial(self.update_button, self.node_button, image_enum, tooltip),
self.node_picker,
tooltip,
)
self.show_picker(self.node_button, self.node_picker)
def show_picker(self, button, picker):
first_button = self.winfo_children()[0]
x = button.winfo_rootx() - first_button.winfo_rootx() + 40
y = button.winfo_rooty() - first_button.winfo_rooty() - 1
picker.place(x=x, y=y)
self.app.bind_all("<Button-1>", lambda e: self.hide_pickers())
self.wait_window(picker)
self.app.unbind_all("<Button-1>")
def create_button(self, img, func, frame, tooltip):
""" """
Create button and put it on the frame Create button and put it on the frame
@ -78,9 +178,9 @@ class CoreToolbar(object):
:return: nothing :return: nothing
""" """
button = tk.Button(frame, width=self.width, height=self.height, image=img) button = tk.Button(frame, width=self.width, height=self.height, image=img)
button.bind("<Button-1>", lambda e: func())
button.pack(side=tk.LEFT, pady=1) button.pack(side=tk.LEFT, pady=1)
CreateToolTip(button, btt_message) CreateToolTip(button, tooltip)
button.bind("<Button-1>", lambda mb: func(main_button))
def create_radio_button(self, frame, image, func, variable, value, tooltip_msg): def create_radio_button(self, frame, image, func, variable, value, tooltip_msg):
button = tk.Radiobutton( button = tk.Radiobutton(
@ -93,326 +193,108 @@ class CoreToolbar(object):
variable=variable, variable=variable,
command=func, command=func,
) )
button.pack(side=tk.TOP, pady=1) button.grid()
CreateToolTip(button, tooltip_msg) CreateToolTip(button, tooltip_msg)
def create_regular_button(self, frame, image, func, btt_message): def create_regular_button(self, frame, image, func, tooltip):
button = tk.Button( button = tk.Button(
frame, width=self.width, height=self.height, image=image, command=func frame, width=self.width, height=self.height, image=image, command=func
) )
button.pack(side=tk.TOP, pady=1) button.grid()
CreateToolTip(button, btt_message) CreateToolTip(button, tooltip)
def draw_button_menu_frame(self, edit_frame, option_frame, main_button):
"""
Draw option menu frame right next to the main button
:param tkinter.Frame edit_frame: parent frame of the main button
:param tkinter.Frame option_frame: option frame to draw
:param tkinter.Radiobutton main_button: the main button
:return: nothing
"""
first_button = edit_frame.winfo_children()[0]
_x = main_button.winfo_rootx() - first_button.winfo_rootx() + 40
_y = main_button.winfo_rooty() - first_button.winfo_rooty() - 1
option_frame.place(x=_x, y=_y)
def bind_widgets_before_frame_hide(self, frame):
"""
Bind the widgets to a left click, when any of the widgets is clicked, the menu option frame is destroyed before
any further action is performed
:param tkinter.Frame frame: the frame to be destroyed
:return: nothing
"""
self.menubar.bind("<Button-1>", lambda e: frame.destroy())
self.master.bind("<Button-1>", lambda e: frame.destroy())
def unbind_widgets_after_frame_hide(self):
"""
Unbind the widgets to make sure everything works normally again after the menu option frame is destroyed
:return: nothing
"""
self.master.unbind("<Button-1>")
self.menubar.unbind("Button-1>")
def click_selection_tool(self): def click_selection_tool(self):
logging.debug("Click SELECTION TOOL") logging.debug("clicked selection tool")
self.canvas.mode = GraphMode.SELECT self.canvas.mode = GraphMode.SELECT
def click_start_session_tool(self): def click_start_session_tool(self):
""" """
Start session handler: redraw buttons, send node and link messages to grpc server Start session handler redraw buttons, send node and link messages to grpc
server.
:return: nothing :return: nothing
""" """
logging.debug("Click START STOP SESSION button") logging.debug("clicked start button")
helper = CoreToolbarHelp(self.app) helper = CoreToolbarHelp(self.app)
self.destroy_children_widgets()
self.canvas.mode = GraphMode.SELECT self.canvas.mode = GraphMode.SELECT
# set configuration state
# state = self.canvas.core_grpc.get_session_state()
# if state == core_pb2.SessionState.SHUTDOWN or self.application.is_open_xml:
# self.canvas.core_grpc.set_session_state(SessionStateEnum.DEFINITION.value)
# self.application.is_open_xml = False
#
# self.canvas.core_grpc.set_session_state(SessionStateEnum.CONFIGURATION.value)
# helper.add_nodes()
# helper.add_edges()
# self.canvas.core_grpc.set_session_state(SessionStateEnum.INSTANTIATION.value)
helper.gui_start_session() helper.gui_start_session()
self.create_runtime_toolbar() self.runtime_frame.tkraise()
# for node in self.canvas.grpc_manager.nodes.values():
# print(node.type, node.model, int(node.x), int(node.y), node.name, node.node_id)
# self.canvas.core_grpc.add_node(
# node.type, node.model, int(node.x), int(node.y), node.name, node.node_id
# )
# print(len(self.canvas.grpc_manager.edges))
# for edge in self.canvas.grpc_manager.edges.values():
# print(edge.id1, edge.id2, edge.type1, edge.type2)
# self.canvas.core_grpc.add_link(
# edge.id1, edge.id2, edge.type1, edge.type2, edge
# )
# self.canvas.core_grpc.get_session()
# self.application.is_open_xml = False
def click_link_tool(self): def click_link_tool(self):
logging.debug("Click LINK button") logging.debug("Click LINK button")
self.canvas.mode = GraphMode.EDGE self.canvas.mode = GraphMode.EDGE
def pick_router(self, main_button): def update_button(self, button, image_enum, name):
logging.debug("Pick router option") logging.info("update button(%s): %s, %s", button, image_enum, name)
self.network_layer_option_menu.destroy() self.hide_pickers()
main_button.configure(image=Images.get(ImageEnum.ROUTER)) if image_enum == ImageEnum.EDITNODE:
self.canvas.mode = GraphMode.PICKNODE dialog = CustomNodesDialog(self.app, self.app)
self.canvas.draw_node_image = Images.get(ImageEnum.ROUTER) dialog.show()
self.canvas.draw_node_name = "router" else:
image = Images.get(image_enum)
logging.info("updating button(%s): %s", button, name)
button.configure(image=image)
self.canvas.mode = GraphMode.NODE
self.canvas.draw_node_image = image
self.canvas.draw_node_name = name
def pick_host(self, main_button): def hide_pickers(self):
logging.debug("Pick host option") logging.info("hiding pickers")
self.network_layer_option_menu.destroy() if self.node_picker:
main_button.configure(image=Images.get(ImageEnum.HOST)) self.node_picker.destroy()
self.canvas.mode = GraphMode.PICKNODE self.node_picker = None
self.canvas.draw_node_image = Images.get(ImageEnum.HOST) if self.network_picker:
self.canvas.draw_node_name = "host" self.network_picker.destroy()
self.network_picker = None
if self.annotation_picker:
self.annotation_picker.destroy()
self.annotation_picker = None
def pick_pc(self, main_button): def create_node_button(self):
logging.debug("Pick PC option")
self.network_layer_option_menu.destroy()
main_button.configure(image=Images.get(ImageEnum.PC))
self.canvas.mode = GraphMode.PICKNODE
self.canvas.draw_node_image = Images.get(ImageEnum.PC)
self.canvas.draw_node_name = "PC"
def pick_mdr(self, main_button):
logging.debug("Pick MDR option")
self.network_layer_option_menu.destroy()
main_button.configure(image=Images.get(ImageEnum.MDR))
self.canvas.mode = GraphMode.PICKNODE
self.canvas.draw_node_image = Images.get(ImageEnum.MDR)
self.canvas.draw_node_name = "mdr"
def pick_prouter(self, main_button):
logging.debug("Pick prouter option")
self.network_layer_option_menu.destroy()
main_button.configure(image=Images.get(ImageEnum.PROUTER))
self.canvas.mode = GraphMode.PICKNODE
self.canvas.draw_node_image = Images.get(ImageEnum.PROUTER)
self.canvas.draw_node_name = "prouter"
def pick_ovs(self, main_button):
logging.debug("Pick OVS option")
self.network_layer_option_menu.destroy()
main_button.configure(image=Images.get(ImageEnum.OVS))
self.canvas.mode = GraphMode.PICKNODE
self.canvas.draw_node_image = Images.get(ImageEnum.OVS)
self.canvas.draw_node_name = "OVS"
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):
"""
Draw the options for network-layer button
:param tkinter.Radiobutton network_layer_button: network-layer button
:return: nothing
"""
# create a frame and add buttons to it
self.destroy_previous_frame()
option_frame = tk.Frame(self.master, padx=1, pady=1)
img_list = [
Images.get(ImageEnum.ROUTER),
Images.get(ImageEnum.HOST),
Images.get(ImageEnum.PC),
Images.get(ImageEnum.MDR),
Images.get(ImageEnum.PROUTER),
Images.get(ImageEnum.OVS),
Images.get(ImageEnum.EDITNODE),
]
func_list = [
self.pick_router,
self.pick_host,
self.pick_pc,
self.pick_mdr,
self.pick_prouter,
self.pick_ovs,
self.pick_editnode,
]
tooltip_list = [
"router",
"host",
"PC",
"mdr",
"prouter",
"OVS",
"edit node types",
]
for i in range(len(img_list)):
self.create_button(
img_list[i],
func_list[i],
option_frame,
network_layer_button,
tooltip_list[i],
)
# place frame at a calculated position as well as keep a reference of that frame
self.draw_button_menu_frame(self.edit_frame, option_frame, network_layer_button)
self.network_layer_option_menu = option_frame
# destroy the frame before any further actions on other widgets
self.bind_widgets_before_frame_hide(option_frame)
option_frame.wait_window(option_frame)
self.unbind_widgets_after_frame_hide()
def create_network_layer_button(self):
""" """
Create network layer button Create network layer button
:return: nothing :return: nothing
""" """
router_image = Images.get(ImageEnum.ROUTER) router_image = Images.get(ImageEnum.ROUTER)
network_layer_button = tk.Radiobutton( self.node_button = tk.Radiobutton(
self.edit_frame, self.design_frame,
indicatoron=False, indicatoron=False,
variable=self.radio_value, variable=self.radio_value,
value=3, value=3,
width=self.width, width=self.width,
height=self.height, height=self.height,
image=router_image, image=router_image,
command=lambda: self.draw_network_layer_options(network_layer_button), command=self.draw_node_picker,
) )
network_layer_button.pack(side=tk.TOP, pady=1) self.node_button.grid()
CreateToolTip(network_layer_button, "Network-layer virtual nodes") CreateToolTip(self.node_button, "Network-layer virtual nodes")
def pick_hub(self, main_button): def draw_network_picker(self):
logging.debug("Pick link-layer node HUB")
self.link_layer_option_menu.destroy()
main_button.configure(image=Images.get(ImageEnum.HUB))
self.canvas.mode = GraphMode.PICKNODE
self.canvas.draw_node_image = Images.get(ImageEnum.HUB)
self.canvas.draw_node_name = "hub"
def pick_switch(self, main_button):
logging.debug("Pick link-layer node SWITCH")
self.link_layer_option_menu.destroy()
main_button.configure(image=Images.get(ImageEnum.SWITCH))
self.canvas.mode = GraphMode.PICKNODE
self.canvas.draw_node_image = Images.get(ImageEnum.SWITCH)
self.canvas.draw_node_name = "switch"
def pick_wlan(self, main_button):
logging.debug("Pick link-layer node WLAN")
self.link_layer_option_menu.destroy()
main_button.configure(image=Images.get(ImageEnum.WLAN))
self.canvas.mode = GraphMode.PICKNODE
self.canvas.draw_node_image = Images.get(ImageEnum.WLAN)
self.canvas.draw_node_name = "wlan"
def pick_rj45(self, main_button):
logging.debug("Pick link-layer node RJ45")
self.link_layer_option_menu.destroy()
main_button.configure(image=Images.get(ImageEnum.RJ45))
self.canvas.mode = GraphMode.PICKNODE
self.canvas.draw_node_image = Images.get(ImageEnum.RJ45)
self.canvas.draw_node_name = "rj45"
def pick_tunnel(self, main_button):
logging.debug("Pick link-layer node TUNNEL")
self.link_layer_option_menu.destroy()
main_button.configure(image=Images.get(ImageEnum.TUNNEL))
self.canvas.mode = GraphMode.PICKNODE
self.canvas.draw_node_image = Images.get(ImageEnum.TUNNEL)
self.canvas.draw_node_name = "tunnel"
def pick_emane(self, main_button):
self.link_layer_option_menu.destroy()
main_button.configure(image=Images.get(ImageEnum.EMANE))
self.canvas.mode = GraphMode.PICKNODE
self.canvas.draw_node_image = Images.get(ImageEnum.EMANE)
self.canvas.draw_node_name = "emane"
def draw_link_layer_options(self, link_layer_button):
""" """
Draw the options for link-layer button Draw the options for link-layer button
:param tkinter.RadioButton link_layer_button: link-layer button :param tkinter.RadioButton link_layer_button: link-layer button
:return: nothing :return: nothing
""" """
# create a frame and add buttons to it self.hide_pickers()
self.destroy_previous_frame() self.network_picker = tk.Frame(self.master, padx=1, pady=1)
option_frame = tk.Frame(self.master, padx=1, pady=1) nodes = [
img_list = [ (ImageEnum.HUB, "hub", "ethernet hub"),
Images.get(ImageEnum.HUB), (ImageEnum.SWITCH, "switch", "ethernet switch"),
Images.get(ImageEnum.SWITCH), (ImageEnum.WLAN, "wlan", "wireless LAN"),
Images.get(ImageEnum.WLAN), (ImageEnum.EMANE, "emane", "EMANE"),
Images.get(ImageEnum.EMANE), (ImageEnum.RJ45, "rj45", "rj45 physical interface tool"),
Images.get(ImageEnum.RJ45), (ImageEnum.TUNNEL, "tunnel", "tunnel tool"),
Images.get(ImageEnum.TUNNEL),
] ]
func_list = [ for image_enum, name, tooltip in nodes:
self.pick_hub,
self.pick_switch,
self.pick_wlan,
self.pick_emane,
self.pick_rj45,
self.pick_tunnel,
]
tooltip_list = [
"ethernet hub",
"ethernet switch",
"wireless LAN",
"emane",
"rj45 physical interface tool",
"tunnel tool",
]
for i in range(len(img_list)):
self.create_button( self.create_button(
img_list[i], Images.get(image_enum),
func_list[i], partial(self.update_button, self.network_button, image_enum, name),
option_frame, self.network_picker,
link_layer_button, tooltip,
tooltip_list[i],
) )
self.show_picker(self.network_button, self.network_picker)
# place frame at a calculated position as well as keep a reference of the frame
self.draw_button_menu_frame(self.edit_frame, option_frame, link_layer_button)
self.link_layer_option_menu = option_frame
# destroy the frame before any further actions on other widgets
self.bind_widgets_before_frame_hide(option_frame)
option_frame.wait_window(option_frame)
self.unbind_widgets_after_frame_hide()
def create_link_layer_button(self): def create_link_layer_button(self):
""" """
@ -421,75 +303,42 @@ class CoreToolbar(object):
:return: nothing :return: nothing
""" """
hub_image = Images.get(ImageEnum.HUB) hub_image = Images.get(ImageEnum.HUB)
link_layer_button = tk.Radiobutton( self.network_button = tk.Radiobutton(
self.edit_frame, self.design_frame,
indicatoron=False, indicatoron=False,
variable=self.radio_value, variable=self.radio_value,
value=4, value=4,
width=self.width, width=self.width,
height=self.height, height=self.height,
image=hub_image, image=hub_image,
command=lambda: self.draw_link_layer_options(link_layer_button), command=self.draw_network_picker,
) )
link_layer_button.pack(side=tk.TOP, pady=1) self.network_button.grid()
CreateToolTip(link_layer_button, "link-layer nodes") CreateToolTip(self.network_button, "link-layer nodes")
def pick_marker(self, main_button): def draw_annotation_picker(self):
self.marker_option_menu.destroy()
main_button.configure(image=Images.get(ImageEnum.MARKER))
logging.debug("Pick MARKER")
def pick_oval(self, main_button):
self.marker_option_menu.destroy()
main_button.configure(image=Images.get(ImageEnum.OVAL))
logging.debug("Pick OVAL")
def pick_rectangle(self, main_button):
self.marker_option_menu.destroy()
main_button.configure(image=Images.get(ImageEnum.RECTANGLE))
logging.debug("Pick RECTANGLE")
def pick_text(self, main_button):
self.marker_option_menu.destroy()
main_button.configure(image=Images.get(ImageEnum.TEXT))
logging.debug("Pick TEXT")
def draw_marker_options(self, main_button):
""" """
Draw the options for marker button Draw the options for marker button
:param tkinter.Radiobutton main_button: the main button :param tkinter.Radiobutton main_button: the main button
:return: nothing :return: nothing
""" """
# create a frame and add buttons to it self.hide_pickers()
self.destroy_previous_frame() self.annotation_picker = tk.Frame(self.master, padx=1, pady=1)
option_frame = tk.Frame(self.master, padx=1, pady=1) nodes = [
img_list = [ (ImageEnum.MARKER, "marker"),
Images.get(ImageEnum.MARKER), (ImageEnum.OVAL, "oval"),
Images.get(ImageEnum.OVAL), (ImageEnum.RECTANGLE, "rectangle"),
Images.get(ImageEnum.RECTANGLE), (ImageEnum.TEXT, "text"),
Images.get(ImageEnum.TEXT),
] ]
func_list = [ for image_enum, tooltip in nodes:
self.pick_marker,
self.pick_oval,
self.pick_rectangle,
self.pick_text,
]
tooltip_list = ["marker", "oval", "rectangle", "text"]
for i in range(len(img_list)):
self.create_button( self.create_button(
img_list[i], func_list[i], option_frame, main_button, tooltip_list[i] Images.get(image_enum),
partial(self.update_annotation, image_enum),
self.annotation_picker,
tooltip,
) )
self.show_picker(self.annotation_button, self.annotation_picker)
# place the frame at a calculated position as well as keep a reference of that frame
self.draw_button_menu_frame(self.edit_frame, option_frame, main_button)
self.marker_option_menu = option_frame
# destroy the frame before any further actions on other widgets
self.bind_widgets_before_frame_hide(option_frame)
option_frame.wait_window(option_frame)
self.unbind_widgets_after_frame_hide()
def create_marker_button(self): def create_marker_button(self):
""" """
@ -498,55 +347,22 @@ class CoreToolbar(object):
:return: nothing :return: nothing
""" """
marker_image = Images.get(ImageEnum.MARKER) marker_image = Images.get(ImageEnum.MARKER)
marker_main_button = tk.Radiobutton( self.annotation_button = tk.Radiobutton(
self.edit_frame, self.design_frame,
indicatoron=False, indicatoron=False,
variable=self.radio_value, variable=self.radio_value,
value=5, value=5,
width=self.width, width=self.width,
height=self.height, height=self.height,
image=marker_image, image=marker_image,
command=lambda: self.draw_marker_options(marker_main_button), command=self.draw_annotation_picker,
) )
marker_main_button.pack(side=tk.TOP, pady=1) self.annotation_button.grid()
CreateToolTip(marker_main_button, "background annotation tools") CreateToolTip(self.annotation_button, "background annotation tools")
def create_toolbar(self):
"""
Create buttons for toolbar in edit mode
:return: nothing
"""
self.create_regular_button(
self.edit_frame,
Images.get(ImageEnum.START),
self.click_start_session_tool,
"start the session",
)
self.create_radio_button(
self.edit_frame,
Images.get(ImageEnum.SELECT),
self.click_selection_tool,
self.radio_value,
1,
"selection tool",
)
self.create_radio_button(
self.edit_frame,
Images.get(ImageEnum.LINK),
self.click_link_tool,
self.radio_value,
2,
"link tool",
)
self.create_network_layer_button()
self.create_link_layer_button()
self.create_marker_button()
self.radio_value.set(1)
def create_observe_button(self): def create_observe_button(self):
menu_button = tk.Menubutton( menu_button = tk.Menubutton(
self.edit_frame, self.runtime_frame,
image=Images.get(ImageEnum.OBSERVE), image=Images.get(ImageEnum.OBSERVE),
width=self.width, width=self.width,
height=self.height, height=self.height,
@ -555,7 +371,7 @@ class CoreToolbar(object):
) )
menu_button.menu = tk.Menu(menu_button, tearoff=0) menu_button.menu = tk.Menu(menu_button, tearoff=0)
menu_button["menu"] = menu_button.menu menu_button["menu"] = menu_button.menu
menu_button.pack(side=tk.TOP, pady=1) menu_button.grid()
menu_button.menu.add_command(label="None") menu_button.menu.add_command(label="None")
menu_button.menu.add_command(label="processes") menu_button.menu.add_command(label="processes")
@ -581,9 +397,13 @@ class CoreToolbar(object):
:return: nothing :return: nothing
""" """
logging.debug("Click on STOP button ") logging.debug("Click on STOP button ")
self.destroy_children_widgets()
self.app.core.stop_session() self.app.core.stop_session()
self.create_toolbar() self.design_frame.tkraise()
def update_annotation(self, image_enum):
logging.info("clicked annotation: ")
self.hide_pickers()
self.annotation_button.configure(image=Images.get(image_enum))
def click_run_button(self): def click_run_button(self):
logging.debug("Click on RUN button") logging.debug("Click on RUN button")
@ -596,48 +416,3 @@ class CoreToolbar(object):
def click_two_node_button(self): def click_two_node_button(self):
logging.debug("Click TWONODE button") logging.debug("Click TWONODE button")
def create_runtime_toolbar(self):
self.create_regular_button(
self.edit_frame,
Images.get(ImageEnum.STOP),
self.click_stop_button,
"stop the session",
)
self.create_radio_button(
self.edit_frame,
Images.get(ImageEnum.SELECT),
self.click_selection_tool,
self.exec_radio_value,
1,
"selection tool",
)
self.create_observe_button()
self.create_radio_button(
self.edit_frame,
Images.get(ImageEnum.PLOT),
self.click_plot_button,
self.exec_radio_value,
2,
"plot",
)
self.create_radio_button(
self.edit_frame,
Images.get(ImageEnum.MARKER),
self.click_marker_button,
self.exec_radio_value,
3,
"marker",
)
self.create_radio_button(
self.edit_frame,
Images.get(ImageEnum.TWONODE),
self.click_two_node_button,
self.exec_radio_value,
4,
"run command from one node to another",
)
self.create_regular_button(
self.edit_frame, Images.get(ImageEnum.RUN), self.click_run_button, "run"
)
self.exec_radio_value.set(1)

View file

@ -1,17 +1,21 @@
import logging
import tkinter as tk import tkinter as tk
from pathlib import Path
from coretk import appdirs
from coretk.coreclient import CustomNode
from coretk.dialogs.dialog import Dialog from coretk.dialogs.dialog import Dialog
from coretk.dialogs.nodeicon import IconDialog from coretk.dialogs.icondialog import IconDialog
from coretk.widgets import CheckboxList, ListboxScroll from coretk.widgets import CheckboxList, ListboxScroll
class ServicesSelectDialog(Dialog): class ServicesSelectDialog(Dialog):
def __init__(self, master, app): def __init__(self, master, app, current_services):
super().__init__(master, app, "Node Services", modal=True) super().__init__(master, app, "Node Services", modal=True)
self.groups = None self.groups = None
self.services = None self.services = None
self.current = None self.current = None
self.current_services = set() self.current_services = current_services
self.draw() self.draw()
def draw(self): def draw(self):
@ -37,14 +41,16 @@ class ServicesSelectDialog(Dialog):
self.current = ListboxScroll(frame, text="Selected") self.current = ListboxScroll(frame, text="Selected")
self.current.grid(row=0, column=2, sticky="nsew") self.current.grid(row=0, column=2, sticky="nsew")
for service in sorted(self.current_services):
self.current.listbox.insert(tk.END, service)
frame = tk.Frame(self) frame = tk.Frame(self)
frame.grid(stick="ew") frame.grid(stick="ew")
for i in range(2): for i in range(2):
frame.columnconfigure(i, weight=1) frame.columnconfigure(i, weight=1)
button = tk.Button(frame, text="Save") button = tk.Button(frame, text="Save", command=self.click_cancel)
button.grid(row=0, column=0, sticky="ew") button.grid(row=0, column=0, sticky="ew")
button = tk.Button(frame, text="Cancel", command=self.destroy) button = tk.Button(frame, text="Cancel", command=self.click_cancel)
button.grid(row=0, column=1, sticky="ew") button.grid(row=0, column=1, sticky="ew")
# trigger group change # trigger group change
@ -69,15 +75,24 @@ class ServicesSelectDialog(Dialog):
for name in sorted(self.current_services): for name in sorted(self.current_services):
self.current.listbox.insert(tk.END, name) self.current.listbox.insert(tk.END, name)
def click_cancel(self):
self.current_services = None
self.destroy()
class CustomNodesDialog(Dialog): class CustomNodesDialog(Dialog):
def __init__(self, master, app): def __init__(self, master, app):
super().__init__(master, app, "Custom Nodes", modal=True) super().__init__(master, app, "Custom Nodes", modal=True)
self.save_button = None self.edit_button = None
self.delete_button = None self.delete_button = None
self.nodes_list = None
self.name = tk.StringVar() self.name = tk.StringVar()
self.image_button = None self.image_button = None
self.image = None self.image = None
self.image_file = None
self.services = set()
self.selected = None
self.selected_index = None
self.draw() self.draw()
def draw(self): def draw(self):
@ -93,13 +108,11 @@ class CustomNodesDialog(Dialog):
frame.columnconfigure(0, weight=1) frame.columnconfigure(0, weight=1)
frame.rowconfigure(0, weight=1) frame.rowconfigure(0, weight=1)
scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL) self.nodes_list = ListboxScroll(frame)
scrollbar.grid(row=0, column=1, sticky="ns") self.nodes_list.grid(row=0, column=0, sticky="nsew")
self.nodes_list.listbox.bind("<<ListboxSelect>>", self.handle_node_select)
listbox = tk.Listbox(frame, selectmode=tk.SINGLE, yscrollcommand=scrollbar.set) for name in sorted(self.app.core.custom_nodes):
listbox.grid(row=0, column=0, sticky="nsew") self.nodes_list.listbox.insert(tk.END, name)
scrollbar.config(command=listbox.yview)
frame = tk.Frame(frame) frame = tk.Frame(frame)
frame.grid(row=0, column=2, sticky="nsew") frame.grid(row=0, column=2, sticky="nsew")
@ -120,10 +133,10 @@ class CustomNodesDialog(Dialog):
button = tk.Button(frame, text="Create", command=self.click_create) button = tk.Button(frame, text="Create", command=self.click_create)
button.grid(row=0, column=0, sticky="ew") button.grid(row=0, column=0, sticky="ew")
self.save_button = tk.Button( self.edit_button = tk.Button(
frame, text="Save", state=tk.DISABLED, command=self.click_save frame, text="Edit", state=tk.DISABLED, command=self.click_edit
) )
self.save_button.grid(row=0, column=1, sticky="ew") self.edit_button.grid(row=0, column=1, sticky="ew")
self.delete_button = tk.Button( self.delete_button = tk.Button(
frame, text="Delete", state=tk.DISABLED, command=self.click_delete frame, text="Delete", state=tk.DISABLED, command=self.click_delete
@ -142,22 +155,89 @@ class CustomNodesDialog(Dialog):
button = tk.Button(frame, text="Cancel", command=self.destroy) button = tk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="ew") button.grid(row=0, column=1, sticky="ew")
def reset_values(self):
self.name.set("")
self.image = None
self.image_file = None
self.services = set()
self.image_button.config(image="")
def click_icon(self): def click_icon(self):
dialog = IconDialog(self, self.app, self.name.get(), self.image) dialog = IconDialog(self, self.app, self.name.get(), self.image)
dialog.show() dialog.show()
if dialog.image: if dialog.image:
self.image = dialog.image self.image = dialog.image
self.image_file = dialog.file_path.get()
self.image_button.config(image=self.image) self.image_button.config(image=self.image)
def click_services(self): def click_services(self):
dialog = ServicesSelectDialog(self, self.app) dialog = ServicesSelectDialog(self, self.app, self.services)
dialog.show() dialog.show()
if dialog.current_services is not None:
def click_create(self): self.services = dialog.current_services
pass
def click_save(self): def click_save(self):
pass self.app.config["nodes"].clear()
for name in sorted(self.app.core.custom_nodes):
custom_node = 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),
}
)
logging.info("saving custom nodes: %s", self.app.config["nodes"])
appdirs.save_config(self.app.config)
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
self.nodes_list.listbox.insert(tk.END, name)
self.reset_values()
def click_edit(self):
name = self.name.get()
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).name
custom_node.services = self.services
self.app.core.custom_nodes[name] = custom_node
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)
def click_delete(self): def click_delete(self):
pass if self.selected and self.selected in self.app.core.custom_nodes:
self.nodes_list.listbox.delete(self.selected_index)
del self.app.core.custom_nodes[self.selected]
self.reset_values()
self.nodes_list.listbox.selection_clear(0, tk.END)
self.nodes_list.listbox.event_generate("<<ListboxSelect>>")
def handle_node_select(self, event):
selection = self.nodes_list.listbox.curselection()
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
self.image_button.config(image=self.image)
self.edit_button.config(state=tk.NORMAL)
self.delete_button.config(state=tk.NORMAL)
else:
self.selected = None
self.selected_index = None
self.edit_button.config(state=tk.DISABLED)
self.delete_button.config(state=tk.DISABLED)

View file

@ -2,7 +2,7 @@ import tkinter as tk
from tkinter import ttk from tkinter import ttk
from coretk.dialogs.dialog import Dialog from coretk.dialogs.dialog import Dialog
from coretk.dialogs.nodeicon import IconDialog from coretk.dialogs.icondialog import IconDialog
from coretk.dialogs.nodeservice import NodeServicesDialog from coretk.dialogs.nodeservice import NodeServicesDialog
NETWORKNODETYPES = ["switch", "hub", "wlan", "rj45", "tunnel"] NETWORKNODETYPES = ["switch", "hub", "wlan", "rj45", "tunnel"]

View file

@ -5,7 +5,7 @@ wlan configuration
import tkinter as tk import tkinter as tk
from coretk.dialogs.dialog import Dialog from coretk.dialogs.dialog import Dialog
from coretk.dialogs.nodeicon import IconDialog from coretk.dialogs.icondialog import IconDialog
class WlanConfigDialog(Dialog): class WlanConfigDialog(Dialog):

View file

@ -278,7 +278,7 @@ class CanvasGraph(tk.Canvas):
else: else:
self.focus_set() self.focus_set()
self.selected = self.get_selected(event) self.selected = self.get_selected(event)
logging.debug(f"click release selected: {self.selected}") logging.debug(f"click release selected({self.selected}) mode({self.mode})")
if self.mode == GraphMode.EDGE: if self.mode == GraphMode.EDGE:
self.handle_edge_release(event) self.handle_edge_release(event)
elif self.mode == GraphMode.NODE: elif self.mode == GraphMode.NODE:
@ -416,6 +416,7 @@ class CanvasGraph(tk.Canvas):
def add_node(self, x, y, image, node_name): def add_node(self, x, y, image, node_name):
plot_id = self.find_all()[0] plot_id = self.find_all()[0]
logging.info("add node event: %s - %s", plot_id, self.selected)
if self.selected == plot_id: if self.selected == plot_id:
node = CanvasNode( node = CanvasNode(
x=x, x=x,
@ -539,13 +540,13 @@ class CanvasNode:
self.x_coord, self.y_coord = self.canvas.coords(self.id) self.x_coord, self.y_coord = self.canvas.coords(self.id)
def click_press(self, event): def click_press(self, event):
logging.debug(f"click press {self.name}: {event}") logging.debug(f"node click press {self.name}: {event}")
self.moving = self.canvas.canvas_xy(event) self.moving = self.canvas.canvas_xy(event)
self.canvas.canvas_management.node_select(self) self.canvas.canvas_management.node_select(self)
def click_release(self, event): def click_release(self, event):
logging.debug(f"click release {self.name}: {event}") logging.debug(f"node click release {self.name}: {event}")
self.update_coords() self.update_coords()
self.canvas.core.update_node_location(self.id, self.x_coord, self.y_coord) self.canvas.core.update_node_location(self.id, self.x_coord, self.y_coord)
self.moving = None self.moving = None

View file

@ -29,6 +29,10 @@ class Images:
def get(cls, image): def get(cls, image):
return cls.images[image.value] return cls.images[image.value]
@classmethod
def get_custom(cls, name):
return cls.images[name]
@classmethod @classmethod
def convert_type_and_model_to_image(cls, node_type, node_model): def convert_type_and_model_to_image(cls, node_type, node_model):
""" """