pygui: initial changes to allow multiple wired links between nodes, updated to commonize logic for creating edges and adjustments in the UI for several edge related cases

This commit is contained in:
Blake Harnden 2020-10-12 20:28:27 -07:00
parent 96dddb687d
commit 29bd6ef7f8
5 changed files with 119 additions and 114 deletions

View file

@ -87,14 +87,14 @@ class CoreClient:
self.read_config()
# helpers
self.iface_to_edge: Dict[Tuple[int, ...], Tuple[int, ...]] = {}
self.iface_to_edge: Dict[Tuple[int, ...], CanvasEdge] = {}
self.ifaces_manager: InterfaceManager = InterfaceManager(self.app)
self.observer: Optional[str] = None
# session data
self.mobility_players: Dict[int, MobilityPlayer] = {}
self.canvas_nodes: Dict[int, CanvasNode] = {}
self.links: Dict[Tuple[int, int], CanvasEdge] = {}
self.links: Dict[str, CanvasEdge] = {}
self.handling_throughputs: Optional[grpc.Future] = None
self.handling_cpu_usage: Optional[grpc.Future] = None
self.handling_events: Optional[grpc.Future] = None
@ -225,7 +225,9 @@ class CoreClient:
self.app.canvas.add_wired_edge(canvas_node1, canvas_node2, event.link)
self.app.canvas.organize()
elif event.message_type == MessageType.DELETE:
self.app.canvas.delete_wired_edge(canvas_node1, canvas_node2)
self.app.canvas.delete_wired_edge(
canvas_node1, canvas_node2, event.link
)
elif event.message_type == MessageType.NONE:
self.app.canvas.update_wired_edge(
canvas_node1, canvas_node2, event.link
@ -877,7 +879,7 @@ class CoreClient:
def create_link(
self, edge: CanvasEdge, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode
) -> None:
) -> Link:
"""
Create core link for a pair of canvas nodes, with token referencing
the canvas edge.
@ -888,15 +890,9 @@ class CoreClient:
src_iface = None
if NodeUtils.is_container_node(src_node.type):
src_iface = self.create_iface(canvas_src_node)
self.iface_to_edge[(src_node.id, src_iface.id)] = edge.token
edge.src_iface = src_iface
canvas_src_node.ifaces[src_iface.id] = src_iface
dst_iface = None
if NodeUtils.is_container_node(dst_node.type):
dst_iface = self.create_iface(canvas_dst_node)
self.iface_to_edge[(dst_node.id, dst_iface.id)] = edge.token
edge.dst_iface = dst_iface
canvas_dst_node.ifaces[dst_iface.id] = dst_iface
link = Link(
type=LinkType.WIRED,
node1_id=src_node.id,
@ -904,9 +900,21 @@ class CoreClient:
iface1=src_iface,
iface2=dst_iface,
)
edge.set_link(link)
self.links[edge.token] = edge
logging.info("added link between %s and %s", src_node.name, dst_node.name)
return link
def save_edge(
self, edge: CanvasEdge, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode
) -> None:
self.links[edge.token] = edge
src_node = canvas_src_node.core_node
dst_node = canvas_dst_node.core_node
if NodeUtils.is_container_node(src_node.type):
src_iface_id = edge.src_iface.id
self.iface_to_edge[(src_node.id, src_iface_id)] = edge
if NodeUtils.is_container_node(dst_node.type):
dst_iface_id = edge.dst_iface.id
self.iface_to_edge[(dst_node.id, dst_iface_id)] = edge
def get_wlan_configs_proto(self) -> List[wlan_pb2.WlanConfig]:
configs = []

View file

@ -68,10 +68,14 @@ class LinkConfigurationDialog(Dialog):
def draw(self) -> None:
self.top.columnconfigure(0, weight=1)
source_name = self.app.canvas.nodes[self.edge.src].core_node.name
dest_name = self.app.canvas.nodes[self.edge.dst].core_node.name
src_label = self.app.canvas.nodes[self.edge.src].core_node.name
if self.edge.link.iface1:
src_label += f":{self.edge.link.iface1.name}"
dst_label = self.app.canvas.nodes[self.edge.dst].core_node.name
if self.edge.link.iface2:
dst_label += f":{self.edge.link.iface2.name}"
label = ttk.Label(
self.top, text=f"Link from {source_name} to {dest_name}", anchor=tk.CENTER
self.top, text=f"{src_label} to {dst_label}", anchor=tk.CENTER
)
label.grid(row=0, column=0, sticky=tk.EW, pady=PADY)

View file

@ -22,11 +22,14 @@ WIRELESS_COLOR: str = "#009933"
ARC_DISTANCE: int = 50
def create_edge_token(src: int, dst: int, network: int = None) -> Tuple[int, ...]:
values = [src, dst]
if network is not None:
values.append(network)
return tuple(sorted(values))
def create_wireless_token(src: int, dst: int, network: int) -> str:
return f"{src}-{dst}-{network}"
def create_edge_token(src: int, dst: int, link: Link) -> str:
iface1_id = link.iface1.id if link.iface1 else None
iface2_id = link.iface2.id if link.iface2 else None
return f"{src}-{iface1_id}-{dst}-{iface2_id}"
def arc_edges(edges) -> None:
@ -67,17 +70,13 @@ class Edge:
self.src: int = src
self.dst: int = dst
self.arc: int = 0
self.token: Optional[Tuple[int, ...]] = None
self.token: Optional[str] = None
self.src_label: Optional[int] = None
self.middle_label: Optional[int] = None
self.dst_label: Optional[int] = None
self.color: str = EDGE_COLOR
self.width: int = EDGE_WIDTH
@classmethod
def create_token(cls, src: int, dst: int) -> Tuple[int, ...]:
return tuple(sorted([src, dst]))
def scaled_width(self) -> float:
return self.width * self.canvas.app.app_scale
@ -242,15 +241,17 @@ class CanvasWirelessEdge(Edge):
canvas: "CanvasGraph",
src: int,
dst: int,
network_id: int,
token: str,
src_pos: Tuple[float, float],
dst_pos: Tuple[float, float],
token: Tuple[int, ...],
link: Link,
) -> None:
logging.debug("drawing wireless link from node %s to node %s", src, dst)
super().__init__(canvas, src, dst)
self.network_id: int = network_id
self.link: Link = link
self.token: Tuple[int, ...] = token
self.token: str = token
self.width: float = WIRELESS_WIDTH
color = link.color if link.color else WIRELESS_COLOR
self.color: str = color
@ -362,7 +363,6 @@ class CanvasEdge(Edge):
def complete(self, dst: int, linked_wireless: bool) -> None:
self.dst = dst
self.linked_wireless = linked_wireless
self.token = create_edge_token(self.src, self.dst)
dst_pos = self.canvas.coords(self.dst)
self.move_dst(dst_pos)
self.check_wireless()

View file

@ -21,8 +21,10 @@ from core.gui.graph.edges import (
EDGE_WIDTH,
CanvasEdge,
CanvasWirelessEdge,
Edge,
arc_edges,
create_edge_token,
create_wireless_token,
)
from core.gui.graph.enums import GraphMode, ScaleOption
from core.gui.graph.node import CanvasNode
@ -69,9 +71,9 @@ class CanvasGraph(tk.Canvas):
self.selected: Optional[int] = None
self.node_draw: Optional[NodeDraw] = None
self.nodes: Dict[int, CanvasNode] = {}
self.edges: Dict[int, CanvasEdge] = {}
self.edges: Dict[str, CanvasEdge] = {}
self.shapes: Dict[int, Shape] = {}
self.wireless_edges: Dict[Tuple[int, ...], CanvasWirelessEdge] = {}
self.wireless_edges: Dict[str, CanvasWirelessEdge] = {}
# map wireless/EMANE node to the set of MDRs connected to that node
self.wireless_network: Dict[int, Set[int]] = {}
@ -206,14 +208,9 @@ class CanvasGraph(tk.Canvas):
iface_id = iface_throughput.iface_id
throughput = iface_throughput.throughput
iface_to_edge_id = (node_id, iface_id)
token = self.core.iface_to_edge.get(iface_to_edge_id)
if not token:
continue
edge = self.edges.get(token)
edge = self.core.iface_to_edge.get(iface_to_edge_id)
if edge:
edge.set_throughput(throughput)
else:
del self.core.iface_to_edge[iface_to_edge_id]
def draw_grid(self) -> None:
"""
@ -230,7 +227,7 @@ class CanvasGraph(tk.Canvas):
self.tag_lower(self.rect)
def add_wired_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None:
token = create_edge_token(src.id, dst.id)
token = create_edge_token(src.id, dst.id, link)
if token in self.edges and link.options.unidirectional:
edge = self.edges[token]
edge.asymmetric_link = link
@ -240,72 +237,50 @@ class CanvasGraph(tk.Canvas):
src_pos = (node1.position.x, node1.position.y)
dst_pos = (node2.position.x, node2.position.y)
edge = CanvasEdge(self, src.id, src_pos, dst_pos)
edge.linked_wireless = self.is_linked_wireless(src.id, dst.id)
edge.token = token
edge.dst = dst.id
edge.set_link(link)
edge.check_wireless()
src.edges.add(edge)
dst.edges.add(edge)
self.edges[edge.token] = edge
self.core.links[edge.token] = edge
if link.iface1:
iface1 = link.iface1
self.core.iface_to_edge[(node1.id, iface1.id)] = token
src.ifaces[iface1.id] = iface1
edge.src_iface = iface1
if link.iface2:
iface2 = link.iface2
self.core.iface_to_edge[(node2.id, iface2.id)] = edge.token
dst.ifaces[iface2.id] = iface2
edge.dst_iface = iface2
self.complete_edge(src, dst, edge)
def delete_wired_edge(self, src: CanvasNode, dst: CanvasNode) -> None:
token = create_edge_token(src.id, dst.id)
def delete_wired_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None:
token = create_edge_token(src.id, dst.id, link)
edge = self.edges.get(token)
if not edge:
return
self.delete_edge(edge)
if edge:
self.delete_edge(edge)
def update_wired_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None:
token = create_edge_token(src.id, dst.id)
token = create_edge_token(src.id, dst.id, link)
edge = self.edges.get(token)
if not edge:
return
edge.link.options = deepcopy(link.options)
if edge:
edge.link.options = deepcopy(link.options)
def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None:
network_id = link.network_id if link.network_id else None
token = create_edge_token(src.id, dst.id, network_id)
token = create_wireless_token(src.id, dst.id, network_id)
if token in self.wireless_edges:
logging.warning("ignoring link that already exists: %s", link)
return
src_pos = self.coords(src.id)
dst_pos = self.coords(dst.id)
edge = CanvasWirelessEdge(self, src.id, dst.id, src_pos, dst_pos, token, link)
edge = CanvasWirelessEdge(
self, src.id, dst.id, network_id, token, src_pos, dst_pos, link
)
self.wireless_edges[token] = edge
src.wireless_edges.add(edge)
dst.wireless_edges.add(edge)
self.tag_raise(src.id)
self.tag_raise(dst.id)
# update arcs when there are multiple links
common_edges = list(src.wireless_edges & dst.wireless_edges)
arc_edges(common_edges)
self.arc_common_edges(edge)
def delete_wireless_edge(
self, src: CanvasNode, dst: CanvasNode, link: Link
) -> None:
network_id = link.network_id if link.network_id else None
token = create_edge_token(src.id, dst.id, network_id)
token = create_wireless_token(src.id, dst.id, network_id)
if token not in self.wireless_edges:
return
edge = self.wireless_edges.pop(token)
edge.delete()
src.wireless_edges.remove(edge)
dst.wireless_edges.remove(edge)
# update arcs when there are multiple links
common_edges = list(src.wireless_edges & dst.wireless_edges)
arc_edges(common_edges)
self.arc_common_edges(edge)
def update_wireless_edge(
self, src: CanvasNode, dst: CanvasNode, link: Link
@ -313,7 +288,7 @@ class CanvasGraph(tk.Canvas):
if not link.label:
return
network_id = link.network_id if link.network_id else None
token = create_edge_token(src.id, dst.id, network_id)
token = create_wireless_token(src.id, dst.id, network_id)
if token not in self.wireless_edges:
self.add_wireless_edge(src, dst, link)
else:
@ -454,12 +429,6 @@ class CanvasGraph(tk.Canvas):
edge.delete()
return
# ignore repeated edges
token = create_edge_token(edge.src, self.selected)
if token in self.edges:
edge.delete()
return
# rj45 nodes can only support one link
if NodeUtils.is_rj45_node(src_node.core_node.type) and src_node.edges:
edge.delete()
@ -468,14 +437,15 @@ class CanvasGraph(tk.Canvas):
edge.delete()
return
# set dst node and snap edge to center
linked_wireless = self.is_linked_wireless(src_node.id, self.selected)
edge.complete(self.selected, linked_wireless)
# finalize edge creation
self.complete_edge(src_node, dst_node, edge)
self.edges[edge.token] = edge
src_node.edges.add(edge)
dst_node.edges.add(edge)
self.core.create_link(edge, src_node, dst_node)
def arc_common_edges(self, edge: Edge) -> None:
src_node = self.nodes[edge.src]
dst_node = self.nodes[edge.dst]
common_edges = list(src_node.edges & dst_node.edges)
common_edges += list(src_node.wireless_edges & dst_node.wireless_edges)
arc_edges(common_edges)
def select_object(self, object_id: int, choose_multiple: bool = False) -> None:
"""
@ -572,6 +542,7 @@ class CanvasGraph(tk.Canvas):
if dst_wireless:
src_node.delete_antenna()
self.core.deleted_canvas_edges([edge])
self.arc_common_edges(edge)
def zoom(self, event: tk.Event, factor: float = None) -> None:
if not factor:
@ -903,20 +874,41 @@ class CanvasGraph(tk.Canvas):
def is_selection_mode(self) -> bool:
return self.mode == GraphMode.SELECT
def create_edge(self, source: CanvasNode, dest: CanvasNode) -> None:
def create_edge(self, src: CanvasNode, dst: CanvasNode) -> CanvasEdge:
"""
create an edge between source node and destination node
"""
token = create_edge_token(source.id, dest.id)
if token not in self.edges:
pos = (source.core_node.position.x, source.core_node.position.y)
edge = CanvasEdge(self, source.id, pos, pos)
linked_wireless = self.is_linked_wireless(source.id, dest.id)
edge.complete(dest.id, linked_wireless)
self.edges[edge.token] = edge
self.nodes[source.id].edges.add(edge)
self.nodes[dest.id].edges.add(edge)
self.core.create_link(edge, source, dest)
pos = (src.core_node.position.x, src.core_node.position.y)
edge = CanvasEdge(self, src.id, pos, pos)
self.complete_edge(src, dst, edge)
return edge
def complete_edge(
self,
src: CanvasNode,
dst: CanvasNode,
edge: CanvasEdge,
link: Optional[Link] = None,
) -> None:
linked_wireless = self.is_linked_wireless(src.id, dst.id)
edge.complete(dst.id, linked_wireless)
src.edges.add(edge)
dst.edges.add(edge)
if link is None:
link = self.core.create_link(edge, src, dst)
if link.iface1:
iface1 = link.iface1
src.ifaces[iface1.id] = iface1
edge.src_iface = iface1
if link.iface2:
iface2 = link.iface2
dst.ifaces[iface2.id] = iface2
edge.dst_iface = iface2
edge.set_link(link)
edge.token = create_edge_token(src.id, dst.id, edge.link)
self.arc_common_edges(edge)
self.edges[edge.token] = edge
self.core.save_edge(edge, src, dst)
def copy(self) -> None:
if self.core.is_runtime():
@ -970,13 +962,12 @@ class CanvasGraph(tk.Canvas):
if edge.src not in to_copy_ids or edge.dst not in to_copy_ids:
if canvas_node.id == edge.src:
dst_node = self.nodes[edge.dst]
self.create_edge(node, dst_node)
token = create_edge_token(node.id, dst_node.id)
copy_edge = self.create_edge(node, dst_node)
elif canvas_node.id == edge.dst:
src_node = self.nodes[edge.src]
self.create_edge(src_node, node)
token = create_edge_token(src_node.id, node.id)
copy_edge = self.edges[token]
copy_edge = self.create_edge(src_node, node)
else:
continue
copy_link = copy_edge.link
iface1_id = copy_link.iface1.id if copy_link.iface1 else None
iface2_id = copy_link.iface2.id if copy_link.iface2 else None
@ -1003,13 +994,11 @@ class CanvasGraph(tk.Canvas):
# copy link and link config
for edge in to_copy_edges:
src_node_id = copy_map[edge.token[0]]
dst_node_id = copy_map[edge.token[1]]
src_node_id = copy_map[edge.src]
dst_node_id = copy_map[edge.dst]
src_node_copy = self.nodes[src_node_id]
dst_node_copy = self.nodes[dst_node_id]
self.create_edge(src_node_copy, dst_node_copy)
token = create_edge_token(src_node_copy.id, dst_node_copy.id)
copy_edge = self.edges[token]
copy_edge = self.create_edge(src_node_copy, dst_node_copy)
copy_link = copy_edge.link
iface1_id = copy_link.iface1.id if copy_link.iface1 else None
iface2_id = copy_link.iface2.id if copy_link.iface2 else None

View file

@ -247,14 +247,18 @@ class CanvasNode:
)
unlink_menu = tk.Menu(self.context)
for edge in self.edges:
other_id = edge.src
if self.id == other_id:
link = edge.link
if self.id == edge.src:
other_id = edge.dst
other_iface = link.iface2.name if link.iface2 else None
else:
other_id = edge.src
other_iface = link.iface1.name if link.iface1 else None
other_node = self.canvas.nodes[other_id]
other_name = other_node.core_node.name
label = f"{other_name}:{other_iface}" if other_iface else other_name
func_unlink = functools.partial(self.click_unlink, edge)
unlink_menu.add_command(
label=other_node.core_node.name, command=func_unlink
)
unlink_menu.add_command(label=label, command=func_unlink)
themes.style_menu(unlink_menu)
self.context.add_cascade(label="Unlink", menu=unlink_menu)
edit_menu = tk.Menu(self.context)