Merge pull request #429 from coreemu/enhancement/pygui-multi-links
Enhancement/pygui multi links
This commit is contained in:
commit
18a6442c8e
10 changed files with 243 additions and 125 deletions
|
@ -5,7 +5,7 @@ verify_ssl = true
|
||||||
|
|
||||||
[scripts]
|
[scripts]
|
||||||
core = "python scripts/core-daemon -f data/core.conf -l data/logging.conf"
|
core = "python scripts/core-daemon -f data/core.conf -l data/logging.conf"
|
||||||
coretk = "python scripts/coretk-gui"
|
core-pygui = "python scripts/core-pygui"
|
||||||
test = "pytest -v tests"
|
test = "pytest -v tests"
|
||||||
test-mock = "pytest -v --mock tests"
|
test-mock = "pytest -v --mock tests"
|
||||||
test-emane = "pytest -v tests/emane"
|
test-emane = "pytest -v tests/emane"
|
||||||
|
|
|
@ -84,6 +84,7 @@ def handle_link_event(event: LinkData) -> core_pb2.LinkEvent:
|
||||||
interface_one=interface_one,
|
interface_one=interface_one,
|
||||||
interface_two=interface_two,
|
interface_two=interface_two,
|
||||||
options=options,
|
options=options,
|
||||||
|
network_id=event.network_id,
|
||||||
)
|
)
|
||||||
return core_pb2.LinkEvent(message_type=event.message_type.value, link=link)
|
return core_pb2.LinkEvent(message_type=event.message_type.value, link=link)
|
||||||
|
|
||||||
|
|
|
@ -370,6 +370,7 @@ def convert_link(session: Session, link_data: LinkData) -> core_pb2.Link:
|
||||||
interface_one=interface_one,
|
interface_one=interface_one,
|
||||||
interface_two=interface_two,
|
interface_two=interface_two,
|
||||||
options=options,
|
options=options,
|
||||||
|
network_id=link_data.network_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -207,13 +207,20 @@ class CoreClient:
|
||||||
logging.debug("Link event: %s", event)
|
logging.debug("Link event: %s", event)
|
||||||
node_one_id = event.link.node_one_id
|
node_one_id = event.link.node_one_id
|
||||||
node_two_id = event.link.node_two_id
|
node_two_id = event.link.node_two_id
|
||||||
|
network_id = event.link.network_id
|
||||||
|
if node_one_id == node_two_id:
|
||||||
|
logging.warning("ignoring invalid link: %s", event)
|
||||||
|
return
|
||||||
canvas_node_one = self.canvas_nodes[node_one_id]
|
canvas_node_one = self.canvas_nodes[node_one_id]
|
||||||
canvas_node_two = self.canvas_nodes[node_two_id]
|
canvas_node_two = self.canvas_nodes[node_two_id]
|
||||||
|
|
||||||
if event.message_type == core_pb2.MessageType.ADD:
|
if event.message_type == core_pb2.MessageType.ADD:
|
||||||
self.app.canvas.add_wireless_edge(canvas_node_one, canvas_node_two)
|
self.app.canvas.add_wireless_edge(
|
||||||
|
canvas_node_one, canvas_node_two, network_id
|
||||||
|
)
|
||||||
elif event.message_type == core_pb2.MessageType.DELETE:
|
elif event.message_type == core_pb2.MessageType.DELETE:
|
||||||
self.app.canvas.delete_wireless_edge(canvas_node_one, canvas_node_two)
|
self.app.canvas.delete_wireless_edge(
|
||||||
|
canvas_node_one, canvas_node_two, network_id
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logging.warning("unknown link event: %s", event.message_type)
|
logging.warning("unknown link event: %s", event.message_type)
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from typing import TYPE_CHECKING, Any, Tuple
|
from typing import TYPE_CHECKING, Any, Tuple
|
||||||
|
|
||||||
|
from core.api.grpc import core_pb2
|
||||||
from core.gui import themes
|
from core.gui import themes
|
||||||
from core.gui.dialogs.linkconfig import LinkConfigurationDialog
|
from core.gui.dialogs.linkconfig import LinkConfigurationDialog
|
||||||
from core.gui.graph import tags
|
from core.gui.graph import tags
|
||||||
from core.gui.nodeutils import EdgeUtils, NodeUtils
|
from core.gui.nodeutils import NodeUtils
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.gui.graph.graph import CanvasGraph
|
from core.gui.graph.graph import CanvasGraph
|
||||||
|
@ -15,82 +17,202 @@ EDGE_WIDTH = 3
|
||||||
EDGE_COLOR = "#ff0000"
|
EDGE_COLOR = "#ff0000"
|
||||||
WIRELESS_WIDTH = 1.5
|
WIRELESS_WIDTH = 1.5
|
||||||
WIRELESS_COLOR = "#009933"
|
WIRELESS_COLOR = "#009933"
|
||||||
|
ARC_DISTANCE = 50
|
||||||
|
|
||||||
|
|
||||||
class CanvasWirelessEdge:
|
def interface_label(interface: core_pb2.Interface) -> str:
|
||||||
def __init__(
|
label = ""
|
||||||
self,
|
if interface.ip4:
|
||||||
token: Tuple[Any, ...],
|
label = f"{interface.ip4}/{interface.ip4mask}"
|
||||||
position: Tuple[float, float, float, float],
|
if interface.ip6:
|
||||||
src: int,
|
label = f"{label}\n{interface.ip6}/{interface.ip6mask}"
|
||||||
dst: int,
|
return label
|
||||||
canvas: "CanvasGraph",
|
|
||||||
):
|
|
||||||
logging.debug("Draw wireless link from node %s to node %s", src, dst)
|
def create_edge_token(src: int, dst: int, network: int = None) -> Tuple[int, ...]:
|
||||||
self.token = token
|
values = [src, dst]
|
||||||
|
if network is not None:
|
||||||
|
values.append(network)
|
||||||
|
return tuple(sorted(values))
|
||||||
|
|
||||||
|
|
||||||
|
def arc_edges(edges) -> None:
|
||||||
|
if not edges:
|
||||||
|
return
|
||||||
|
mid_index = len(edges) // 2
|
||||||
|
if mid_index == 0:
|
||||||
|
arc_step = ARC_DISTANCE
|
||||||
|
else:
|
||||||
|
arc_step = ARC_DISTANCE / mid_index
|
||||||
|
# below edges
|
||||||
|
arc = 0
|
||||||
|
for edge in edges[:mid_index]:
|
||||||
|
arc -= arc_step
|
||||||
|
edge.arc = arc
|
||||||
|
edge.redraw()
|
||||||
|
# mid edge
|
||||||
|
if len(edges) % 2 != 0:
|
||||||
|
arc = 0
|
||||||
|
edge = edges[mid_index]
|
||||||
|
edge.arc = arc
|
||||||
|
edge.redraw()
|
||||||
|
mid_index += 1
|
||||||
|
# above edges
|
||||||
|
arc = 0
|
||||||
|
for edge in edges[mid_index:]:
|
||||||
|
arc += arc_step
|
||||||
|
edge.arc = arc
|
||||||
|
edge.redraw()
|
||||||
|
|
||||||
|
|
||||||
|
class Edge:
|
||||||
|
tag = tags.EDGE
|
||||||
|
|
||||||
|
def __init__(self, canvas: "CanvasGraph", src: int, dst: int = None) -> None:
|
||||||
|
self.canvas = canvas
|
||||||
|
self.id = None
|
||||||
self.src = src
|
self.src = src
|
||||||
self.dst = dst
|
self.dst = dst
|
||||||
self.canvas = canvas
|
self.arc = 0
|
||||||
|
self.token = None
|
||||||
|
self.color = EDGE_COLOR
|
||||||
|
self.width = EDGE_WIDTH
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_token(cls, src: int, dst: int) -> Tuple[int, ...]:
|
||||||
|
return tuple(sorted([src, dst]))
|
||||||
|
|
||||||
|
def _get_arcpoint(
|
||||||
|
self, src_pos: Tuple[float, float], dst_pos: Tuple[float, float]
|
||||||
|
) -> Tuple[float, float]:
|
||||||
|
src_x, src_y = src_pos
|
||||||
|
dst_x, dst_y = dst_pos
|
||||||
|
mp_x = (src_x + dst_x) / 2
|
||||||
|
mp_y = (src_y + dst_y) / 2
|
||||||
|
slope_denominator = src_x - dst_x
|
||||||
|
slope_numerator = src_y - dst_y
|
||||||
|
# vertical line
|
||||||
|
if slope_denominator == 0:
|
||||||
|
return mp_x + self.arc, mp_y
|
||||||
|
# horizontal line
|
||||||
|
if slope_numerator == 0:
|
||||||
|
return mp_x, mp_y + self.arc
|
||||||
|
# everything else
|
||||||
|
m = slope_numerator / slope_denominator
|
||||||
|
perp_m = -1 / m
|
||||||
|
b = mp_y - (perp_m * mp_x)
|
||||||
|
# get arc x and y
|
||||||
|
offset = math.sqrt(self.arc ** 2 / (1 + (1 / m ** 2)))
|
||||||
|
arc_x = mp_x
|
||||||
|
if self.arc >= 0:
|
||||||
|
arc_x += offset
|
||||||
|
else:
|
||||||
|
arc_x -= offset
|
||||||
|
arc_y = (perp_m * arc_x) + b
|
||||||
|
return arc_x, arc_y
|
||||||
|
|
||||||
|
def draw(self, src_pos: Tuple[float, float], dst_pos: Tuple[float, float]) -> None:
|
||||||
|
arc_pos = self._get_arcpoint(src_pos, dst_pos)
|
||||||
self.id = self.canvas.create_line(
|
self.id = self.canvas.create_line(
|
||||||
*position,
|
*src_pos,
|
||||||
tags=tags.WIRELESS_EDGE,
|
*arc_pos,
|
||||||
width=WIRELESS_WIDTH * self.canvas.app.app_scale,
|
*dst_pos,
|
||||||
fill=WIRELESS_COLOR,
|
smooth=True,
|
||||||
|
tags=self.tag,
|
||||||
|
width=self.width * self.canvas.app.app_scale,
|
||||||
|
fill=self.color,
|
||||||
)
|
)
|
||||||
|
|
||||||
def delete(self):
|
def redraw(self):
|
||||||
|
width = self.width * self.canvas.app.app_scale
|
||||||
|
self.canvas.itemconfig(self.id, width=width, fill=self.color)
|
||||||
|
src_x, src_y, _, _, _, _ = self.canvas.coords(self.id)
|
||||||
|
self.move_src(src_x, src_y)
|
||||||
|
|
||||||
|
def move_node(self, node_id: int, x: float, y: float) -> None:
|
||||||
|
if self.src == node_id:
|
||||||
|
self.move_src(x, y)
|
||||||
|
else:
|
||||||
|
self.move_dst(x, y)
|
||||||
|
|
||||||
|
def move_dst(self, x: float, y: float) -> None:
|
||||||
|
dst_pos = (x, y)
|
||||||
|
src_x, src_y, _, _, _, _ = self.canvas.coords(self.id)
|
||||||
|
src_pos = (src_x, src_y)
|
||||||
|
arc_pos = self._get_arcpoint(src_pos, dst_pos)
|
||||||
|
self.canvas.coords(self.id, *src_pos, *arc_pos, *dst_pos)
|
||||||
|
|
||||||
|
def move_src(self, x: float, y: float) -> None:
|
||||||
|
src_pos = (x, y)
|
||||||
|
_, _, _, _, dst_x, dst_y = self.canvas.coords(self.id)
|
||||||
|
dst_pos = (dst_x, dst_y)
|
||||||
|
arc_pos = self._get_arcpoint(src_pos, dst_pos)
|
||||||
|
self.canvas.coords(self.id, *src_pos, *arc_pos, *dst_pos)
|
||||||
|
|
||||||
|
def delete(self) -> None:
|
||||||
self.canvas.delete(self.id)
|
self.canvas.delete(self.id)
|
||||||
|
|
||||||
|
|
||||||
class CanvasEdge:
|
class CanvasWirelessEdge(Edge):
|
||||||
|
tag = tags.WIRELESS_EDGE
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
canvas: "CanvasGraph",
|
||||||
|
src: int,
|
||||||
|
dst: int,
|
||||||
|
src_pos: Tuple[float, float],
|
||||||
|
dst_pos: Tuple[float, float],
|
||||||
|
token: Tuple[Any, ...],
|
||||||
|
) -> None:
|
||||||
|
logging.debug("drawing wireless link from node %s to node %s", src, dst)
|
||||||
|
super().__init__(canvas, src, dst)
|
||||||
|
self.token = token
|
||||||
|
self.width = WIRELESS_WIDTH
|
||||||
|
self.color = WIRELESS_COLOR
|
||||||
|
self.draw(src_pos, dst_pos)
|
||||||
|
|
||||||
|
|
||||||
|
class CanvasEdge(Edge):
|
||||||
"""
|
"""
|
||||||
Canvas edge class
|
Canvas edge class
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
x1: float,
|
|
||||||
y1: float,
|
|
||||||
x2: float,
|
|
||||||
y2: float,
|
|
||||||
src: int,
|
|
||||||
canvas: "CanvasGraph",
|
canvas: "CanvasGraph",
|
||||||
):
|
src: int,
|
||||||
|
src_pos: Tuple[float, float],
|
||||||
|
dst_pos: Tuple[float, float],
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Create an instance of canvas edge object
|
Create an instance of canvas edge object
|
||||||
"""
|
"""
|
||||||
self.src = src
|
super().__init__(canvas, src)
|
||||||
self.dst = None
|
|
||||||
self.src_interface = None
|
self.src_interface = None
|
||||||
self.dst_interface = None
|
self.dst_interface = None
|
||||||
self.canvas = canvas
|
|
||||||
self.id = self.canvas.create_line(
|
|
||||||
x1,
|
|
||||||
y1,
|
|
||||||
x2,
|
|
||||||
y2,
|
|
||||||
tags=tags.EDGE,
|
|
||||||
width=EDGE_WIDTH * self.canvas.app.app_scale,
|
|
||||||
fill=EDGE_COLOR,
|
|
||||||
)
|
|
||||||
self.text_src = None
|
self.text_src = None
|
||||||
self.text_dst = None
|
self.text_dst = None
|
||||||
self.text_middle = None
|
self.text_middle = None
|
||||||
self.token = None
|
|
||||||
self.link = None
|
self.link = None
|
||||||
self.asymmetric_link = None
|
self.asymmetric_link = None
|
||||||
self.throughput = None
|
self.throughput = None
|
||||||
|
self.draw(src_pos, dst_pos)
|
||||||
self.set_binding()
|
self.set_binding()
|
||||||
|
|
||||||
def set_binding(self):
|
def move_node(self, node_id: int, x: float, y: float) -> None:
|
||||||
|
super().move_node(node_id, x, y)
|
||||||
|
self.update_labels()
|
||||||
|
|
||||||
|
def set_binding(self) -> None:
|
||||||
self.canvas.tag_bind(self.id, "<ButtonRelease-3>", self.create_context)
|
self.canvas.tag_bind(self.id, "<ButtonRelease-3>", self.create_context)
|
||||||
|
|
||||||
def set_link(self, link):
|
def set_link(self, link) -> None:
|
||||||
self.link = link
|
self.link = link
|
||||||
self.draw_labels()
|
self.draw_labels()
|
||||||
|
|
||||||
def get_coordinates(self) -> [float, float, float, float]:
|
def get_coordinates(self) -> [float, float, float, float]:
|
||||||
x1, y1, x2, y2 = self.canvas.coords(self.id)
|
x1, y1, _, _, x2, y2 = self.canvas.coords(self.id)
|
||||||
v1 = x2 - x1
|
v1 = x2 - x1
|
||||||
v2 = y2 - y1
|
v2 = y2 - y1
|
||||||
ux = TEXT_DISTANCE * v1
|
ux = TEXT_DISTANCE * v1
|
||||||
|
@ -107,24 +229,16 @@ class CanvasEdge:
|
||||||
y = (y1 + y2) / 2
|
y = (y1 + y2) / 2
|
||||||
return x, y
|
return x, y
|
||||||
|
|
||||||
def create_labels(self):
|
def create_labels(self) -> Tuple[str, str]:
|
||||||
label_one = None
|
label_one = None
|
||||||
if self.link.HasField("interface_one"):
|
if self.link.HasField("interface_one"):
|
||||||
label_one = self.create_label(self.link.interface_one)
|
label_one = interface_label(self.link.interface_one)
|
||||||
label_two = None
|
label_two = None
|
||||||
if self.link.HasField("interface_two"):
|
if self.link.HasField("interface_two"):
|
||||||
label_two = self.create_label(self.link.interface_two)
|
label_two = interface_label(self.link.interface_two)
|
||||||
return label_one, label_two
|
return label_one, label_two
|
||||||
|
|
||||||
def create_label(self, interface):
|
def draw_labels(self) -> None:
|
||||||
label = ""
|
|
||||||
if interface.ip4:
|
|
||||||
label = f"{interface.ip4}/{interface.ip4mask}"
|
|
||||||
if interface.ip6:
|
|
||||||
label = f"{label}\n{interface.ip6}/{interface.ip6mask}"
|
|
||||||
return label
|
|
||||||
|
|
||||||
def draw_labels(self):
|
|
||||||
x1, y1, x2, y2 = self.get_coordinates()
|
x1, y1, x2, y2 = self.get_coordinates()
|
||||||
label_one, label_two = self.create_labels()
|
label_one, label_two = self.create_labels()
|
||||||
self.text_src = self.canvas.create_text(
|
self.text_src = self.canvas.create_text(
|
||||||
|
@ -144,12 +258,13 @@ class CanvasEdge:
|
||||||
tags=tags.LINK_INFO,
|
tags=tags.LINK_INFO,
|
||||||
)
|
)
|
||||||
|
|
||||||
def redraw(self):
|
def redraw(self) -> None:
|
||||||
|
super().redraw()
|
||||||
label_one, label_two = self.create_labels()
|
label_one, label_two = self.create_labels()
|
||||||
self.canvas.itemconfig(self.text_src, text=label_one)
|
self.canvas.itemconfig(self.text_src, text=label_one)
|
||||||
self.canvas.itemconfig(self.text_dst, text=label_two)
|
self.canvas.itemconfig(self.text_dst, text=label_two)
|
||||||
|
|
||||||
def update_labels(self):
|
def update_labels(self) -> None:
|
||||||
"""
|
"""
|
||||||
Move edge labels based on current position.
|
Move edge labels based on current position.
|
||||||
"""
|
"""
|
||||||
|
@ -160,7 +275,7 @@ class CanvasEdge:
|
||||||
x, y = self.get_midpoint()
|
x, y = self.get_midpoint()
|
||||||
self.canvas.coords(self.text_middle, x, y)
|
self.canvas.coords(self.text_middle, x, y)
|
||||||
|
|
||||||
def set_throughput(self, throughput: float):
|
def set_throughput(self, throughput: float) -> None:
|
||||||
throughput = 0.001 * throughput
|
throughput = 0.001 * throughput
|
||||||
value = f"{throughput:.3f} kbps"
|
value = f"{throughput:.3f} kbps"
|
||||||
if self.text_middle is None:
|
if self.text_middle is None:
|
||||||
|
@ -179,18 +294,17 @@ class CanvasEdge:
|
||||||
width = EDGE_WIDTH
|
width = EDGE_WIDTH
|
||||||
self.canvas.itemconfig(self.id, fill=color, width=width)
|
self.canvas.itemconfig(self.id, fill=color, width=width)
|
||||||
|
|
||||||
def complete(self, dst: int):
|
def complete(self, dst: int) -> None:
|
||||||
self.dst = dst
|
self.dst = dst
|
||||||
self.token = EdgeUtils.get_token(self.src, self.dst)
|
self.token = create_edge_token(self.src, self.dst)
|
||||||
x, y = self.canvas.coords(self.dst)
|
x, y = self.canvas.coords(self.dst)
|
||||||
x1, y1, _, _ = self.canvas.coords(self.id)
|
self.move_dst(x, y)
|
||||||
self.canvas.coords(self.id, x1, y1, x, y)
|
|
||||||
self.check_wireless()
|
self.check_wireless()
|
||||||
self.canvas.tag_raise(self.src)
|
self.canvas.tag_raise(self.src)
|
||||||
self.canvas.tag_raise(self.dst)
|
self.canvas.tag_raise(self.dst)
|
||||||
logging.debug("Draw wired link from node %s to node %s", self.src, dst)
|
logging.debug("Draw wired link from node %s to node %s", self.src, dst)
|
||||||
|
|
||||||
def is_wireless(self) -> [bool, bool]:
|
def is_wireless(self) -> bool:
|
||||||
src_node = self.canvas.nodes[self.src]
|
src_node = self.canvas.nodes[self.src]
|
||||||
dst_node = self.canvas.nodes[self.dst]
|
dst_node = self.canvas.nodes[self.dst]
|
||||||
src_node_type = src_node.core_node.type
|
src_node_type = src_node.core_node.type
|
||||||
|
@ -210,12 +324,12 @@ class CanvasEdge:
|
||||||
wlan_network[self.dst].add(self.src)
|
wlan_network[self.dst].add(self.src)
|
||||||
return is_src_wireless or is_dst_wireless
|
return is_src_wireless or is_dst_wireless
|
||||||
|
|
||||||
def check_wireless(self):
|
def check_wireless(self) -> None:
|
||||||
if self.is_wireless():
|
if self.is_wireless():
|
||||||
self.canvas.itemconfig(self.id, state=tk.HIDDEN)
|
self.canvas.itemconfig(self.id, state=tk.HIDDEN)
|
||||||
self._check_antenna()
|
self._check_antenna()
|
||||||
|
|
||||||
def _check_antenna(self):
|
def _check_antenna(self) -> None:
|
||||||
src_node = self.canvas.nodes[self.src]
|
src_node = self.canvas.nodes[self.src]
|
||||||
dst_node = self.canvas.nodes[self.dst]
|
dst_node = self.canvas.nodes[self.dst]
|
||||||
src_node_type = src_node.core_node.type
|
src_node_type = src_node.core_node.type
|
||||||
|
@ -230,20 +344,19 @@ class CanvasEdge:
|
||||||
else:
|
else:
|
||||||
src_node.add_antenna()
|
src_node.add_antenna()
|
||||||
|
|
||||||
def delete(self):
|
def delete(self) -> None:
|
||||||
logging.debug("Delete canvas edge, id: %s", self.id)
|
logging.debug("Delete canvas edge, id: %s", self.id)
|
||||||
self.canvas.delete(self.id)
|
super().delete()
|
||||||
if self.link:
|
self.canvas.delete(self.text_src)
|
||||||
self.canvas.delete(self.text_src)
|
self.canvas.delete(self.text_dst)
|
||||||
self.canvas.delete(self.text_dst)
|
|
||||||
self.canvas.delete(self.text_middle)
|
self.canvas.delete(self.text_middle)
|
||||||
|
|
||||||
def reset(self):
|
def reset(self) -> None:
|
||||||
self.canvas.delete(self.text_middle)
|
self.canvas.delete(self.text_middle)
|
||||||
self.text_middle = None
|
self.text_middle = None
|
||||||
self.canvas.itemconfig(self.id, fill=EDGE_COLOR, width=EDGE_WIDTH)
|
self.canvas.itemconfig(self.id, fill=EDGE_COLOR, width=EDGE_WIDTH)
|
||||||
|
|
||||||
def create_context(self, event: tk.Event):
|
def create_context(self, event: tk.Event) -> None:
|
||||||
context = tk.Menu(self.canvas)
|
context = tk.Menu(self.canvas)
|
||||||
themes.style_menu(context)
|
themes.style_menu(context)
|
||||||
context.add_command(label="Configure", command=self.configure)
|
context.add_command(label="Configure", command=self.configure)
|
||||||
|
@ -256,6 +369,6 @@ class CanvasEdge:
|
||||||
context.entryconfigure(3, state="disabled")
|
context.entryconfigure(3, state="disabled")
|
||||||
context.post(event.x_root, event.y_root)
|
context.post(event.x_root, event.y_root)
|
||||||
|
|
||||||
def configure(self):
|
def configure(self) -> None:
|
||||||
dialog = LinkConfigurationDialog(self.canvas, self.canvas.app, self)
|
dialog = LinkConfigurationDialog(self.canvas, self.canvas.app, self)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
|
@ -7,13 +7,19 @@ from PIL import Image, ImageTk
|
||||||
from core.api.grpc import core_pb2
|
from core.api.grpc import core_pb2
|
||||||
from core.gui.dialogs.shapemod import ShapeDialog
|
from core.gui.dialogs.shapemod import ShapeDialog
|
||||||
from core.gui.graph import tags
|
from core.gui.graph import tags
|
||||||
from core.gui.graph.edges import EDGE_WIDTH, CanvasEdge, CanvasWirelessEdge
|
from core.gui.graph.edges import (
|
||||||
|
EDGE_WIDTH,
|
||||||
|
CanvasEdge,
|
||||||
|
CanvasWirelessEdge,
|
||||||
|
arc_edges,
|
||||||
|
create_edge_token,
|
||||||
|
)
|
||||||
from core.gui.graph.enums import GraphMode, ScaleOption
|
from core.gui.graph.enums import GraphMode, ScaleOption
|
||||||
from core.gui.graph.node import CanvasNode
|
from core.gui.graph.node import CanvasNode
|
||||||
from core.gui.graph.shape import Shape
|
from core.gui.graph.shape import Shape
|
||||||
from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker
|
from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker
|
||||||
from core.gui.images import ImageEnum, Images, TypeToImage
|
from core.gui.images import ImageEnum, Images, TypeToImage
|
||||||
from core.gui.nodeutils import EdgeUtils, NodeUtils
|
from core.gui.nodeutils import NodeUtils
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from core.gui.app import Application
|
from core.gui.app import Application
|
||||||
|
@ -193,27 +199,40 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.tag_lower(tags.GRIDLINE)
|
self.tag_lower(tags.GRIDLINE)
|
||||||
self.tag_lower(self.grid)
|
self.tag_lower(self.grid)
|
||||||
|
|
||||||
def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode):
|
def add_wireless_edge(
|
||||||
|
self, src: CanvasNode, dst: CanvasNode, network_id: int = None
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
add a wireless edge between 2 canvas nodes
|
add a wireless edge between 2 canvas nodes
|
||||||
"""
|
"""
|
||||||
token = EdgeUtils.get_token(src.id, dst.id)
|
token = create_edge_token(src.id, dst.id, network_id)
|
||||||
x1, y1 = self.coords(src.id)
|
if token in self.wireless_edges:
|
||||||
x2, y2 = self.coords(dst.id)
|
return
|
||||||
position = (x1, y1, x2, y2)
|
src_pos = self.coords(src.id)
|
||||||
edge = CanvasWirelessEdge(token, position, src.id, dst.id, self)
|
dst_pos = self.coords(dst.id)
|
||||||
|
edge = CanvasWirelessEdge(self, src.id, dst.id, src_pos, dst_pos, token)
|
||||||
self.wireless_edges[token] = edge
|
self.wireless_edges[token] = edge
|
||||||
src.wireless_edges.add(edge)
|
src.wireless_edges.add(edge)
|
||||||
dst.wireless_edges.add(edge)
|
dst.wireless_edges.add(edge)
|
||||||
self.tag_raise(src.id)
|
self.tag_raise(src.id)
|
||||||
self.tag_raise(dst.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)
|
||||||
|
|
||||||
def delete_wireless_edge(self, src: CanvasNode, dst: CanvasNode):
|
def delete_wireless_edge(
|
||||||
token = EdgeUtils.get_token(src.id, dst.id)
|
self, src: CanvasNode, dst: CanvasNode, network_id: int = None
|
||||||
|
):
|
||||||
|
token = create_edge_token(src.id, dst.id, network_id)
|
||||||
|
if token not in self.wireless_edges:
|
||||||
|
return
|
||||||
edge = self.wireless_edges.pop(token)
|
edge = self.wireless_edges.pop(token)
|
||||||
edge.delete()
|
edge.delete()
|
||||||
src.wireless_edges.remove(edge)
|
src.wireless_edges.remove(edge)
|
||||||
dst.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)
|
||||||
|
|
||||||
def draw_session(self, session: core_pb2.Session):
|
def draw_session(self, session: core_pb2.Session):
|
||||||
"""
|
"""
|
||||||
|
@ -246,20 +265,15 @@ class CanvasGraph(tk.Canvas):
|
||||||
node_one = canvas_node_one.core_node
|
node_one = canvas_node_one.core_node
|
||||||
canvas_node_two = self.core.canvas_nodes[link.node_two_id]
|
canvas_node_two = self.core.canvas_nodes[link.node_two_id]
|
||||||
node_two = canvas_node_two.core_node
|
node_two = canvas_node_two.core_node
|
||||||
token = EdgeUtils.get_token(canvas_node_one.id, canvas_node_two.id)
|
token = create_edge_token(canvas_node_one.id, canvas_node_two.id)
|
||||||
|
|
||||||
if link.type == core_pb2.LinkType.WIRELESS:
|
if link.type == core_pb2.LinkType.WIRELESS:
|
||||||
self.add_wireless_edge(canvas_node_one, canvas_node_two)
|
self.add_wireless_edge(canvas_node_one, canvas_node_two)
|
||||||
else:
|
else:
|
||||||
if token not in self.edges:
|
if token not in self.edges:
|
||||||
edge = CanvasEdge(
|
src_pos = (node_one.position.x, node_one.position.y)
|
||||||
node_one.position.x,
|
dst_pos = (node_two.position.x, node_two.position.y)
|
||||||
node_one.position.y,
|
edge = CanvasEdge(self, canvas_node_one.id, src_pos, dst_pos)
|
||||||
node_two.position.x,
|
|
||||||
node_two.position.y,
|
|
||||||
canvas_node_one.id,
|
|
||||||
self,
|
|
||||||
)
|
|
||||||
edge.token = token
|
edge.token = token
|
||||||
edge.dst = canvas_node_two.id
|
edge.dst = canvas_node_two.id
|
||||||
edge.set_link(link)
|
edge.set_link(link)
|
||||||
|
@ -391,7 +405,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
return
|
return
|
||||||
|
|
||||||
# ignore repeated edges
|
# ignore repeated edges
|
||||||
token = EdgeUtils.get_token(edge.src, self.selected)
|
token = create_edge_token(edge.src, self.selected)
|
||||||
if token in self.edges:
|
if token in self.edges:
|
||||||
edge.delete()
|
edge.delete()
|
||||||
return
|
return
|
||||||
|
@ -520,11 +534,10 @@ class CanvasGraph(tk.Canvas):
|
||||||
logging.debug("click press offset(%s, %s)", x_check, y_check)
|
logging.debug("click press offset(%s, %s)", x_check, y_check)
|
||||||
is_node = selected in self.nodes
|
is_node = selected in self.nodes
|
||||||
if self.mode == GraphMode.EDGE and is_node:
|
if self.mode == GraphMode.EDGE and is_node:
|
||||||
x, y = self.coords(selected)
|
pos = self.coords(selected)
|
||||||
self.drawing_edge = CanvasEdge(x, y, x, y, selected, self)
|
self.drawing_edge = CanvasEdge(self, selected, pos, pos)
|
||||||
|
|
||||||
if self.mode == GraphMode.ANNOTATION:
|
if self.mode == GraphMode.ANNOTATION:
|
||||||
|
|
||||||
if is_marker(self.annotation_type):
|
if is_marker(self.annotation_type):
|
||||||
r = self.app.toolbar.marker_tool.radius
|
r = self.app.toolbar.marker_tool.radius
|
||||||
self.create_oval(
|
self.create_oval(
|
||||||
|
@ -603,8 +616,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.cursor = x, y
|
self.cursor = x, y
|
||||||
|
|
||||||
if self.mode == GraphMode.EDGE and self.drawing_edge is not None:
|
if self.mode == GraphMode.EDGE and self.drawing_edge is not None:
|
||||||
x1, y1, _, _ = self.coords(self.drawing_edge.id)
|
self.drawing_edge.move_dst(x, y)
|
||||||
self.coords(self.drawing_edge.id, x1, y1, x, y)
|
|
||||||
if self.mode == GraphMode.ANNOTATION:
|
if self.mode == GraphMode.ANNOTATION:
|
||||||
if is_draw_shape(self.annotation_type) and self.shape_drawing:
|
if is_draw_shape(self.annotation_type) and self.shape_drawing:
|
||||||
shape = self.shapes[self.selected]
|
shape = self.shapes[self.selected]
|
||||||
|
@ -841,11 +853,10 @@ class CanvasGraph(tk.Canvas):
|
||||||
"""
|
"""
|
||||||
create an edge between source node and destination node
|
create an edge between source node and destination node
|
||||||
"""
|
"""
|
||||||
if (source.id, dest.id) not in self.edges:
|
token = create_edge_token(source.id, dest.id)
|
||||||
pos0 = source.core_node.position
|
if token not in self.edges:
|
||||||
x0 = pos0.x
|
pos = (source.core_node.position.x, source.core_node.position.y)
|
||||||
y0 = pos0.y
|
edge = CanvasEdge(self, source.id, pos, pos)
|
||||||
edge = CanvasEdge(x0, y0, x0, y0, source.id, self)
|
|
||||||
edge.complete(dest.id)
|
edge.complete(dest.id)
|
||||||
self.edges[edge.token] = edge
|
self.edges[edge.token] = edge
|
||||||
self.nodes[source.id].edges.add(edge)
|
self.nodes[source.id].edges.add(edge)
|
||||||
|
@ -905,7 +916,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
dest_node_copy = self.nodes[copy_map[edge.token[1]]]
|
dest_node_copy = self.nodes[copy_map[edge.token[1]]]
|
||||||
self.create_edge(source_node_copy, dest_node_copy)
|
self.create_edge(source_node_copy, dest_node_copy)
|
||||||
copy_edge = self.edges[
|
copy_edge = self.edges[
|
||||||
EdgeUtils.get_token(source_node_copy.id, dest_node_copy.id)
|
create_edge_token(source_node_copy.id, dest_node_copy.id)
|
||||||
]
|
]
|
||||||
copy_link = copy_edge.link
|
copy_link = copy_edge.link
|
||||||
options = edge.link.options
|
options = edge.link.options
|
||||||
|
|
|
@ -148,19 +148,9 @@ class CanvasNode:
|
||||||
|
|
||||||
# move edges
|
# move edges
|
||||||
for edge in self.edges:
|
for edge in self.edges:
|
||||||
x1, y1, x2, y2 = self.canvas.coords(edge.id)
|
edge.move_node(self.id, x, y)
|
||||||
if edge.src == self.id:
|
|
||||||
self.canvas.coords(edge.id, x, y, x2, y2)
|
|
||||||
else:
|
|
||||||
self.canvas.coords(edge.id, x1, y1, x, y)
|
|
||||||
edge.update_labels()
|
|
||||||
|
|
||||||
for edge in self.wireless_edges:
|
for edge in self.wireless_edges:
|
||||||
x1, y1, x2, y2 = self.canvas.coords(edge.id)
|
edge.move_node(self.id, x, y)
|
||||||
if edge.src == self.id:
|
|
||||||
self.canvas.coords(edge.id, x, y, x2, y2)
|
|
||||||
else:
|
|
||||||
self.canvas.coords(edge.id, x1, y1, x, y)
|
|
||||||
|
|
||||||
# set actual coords for node and update core is running
|
# set actual coords for node and update core is running
|
||||||
real_x, real_y = self.canvas.get_actual_coords(x, y)
|
real_x, real_y = self.canvas.get_actual_coords(x, y)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Union
|
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Union
|
||||||
|
|
||||||
from core.api.grpc.core_pb2 import NodeType
|
from core.api.grpc.core_pb2 import NodeType
|
||||||
from core.gui.images import ImageEnum, Images, TypeToImage
|
from core.gui.images import ImageEnum, Images, TypeToImage
|
||||||
|
@ -172,9 +172,3 @@ class NodeUtils:
|
||||||
cls.NETWORK_NODES.append(node_draw)
|
cls.NETWORK_NODES.append(node_draw)
|
||||||
cls.NODE_ICONS[(node_type, None)] = node_draw.image
|
cls.NODE_ICONS[(node_type, None)] = node_draw.image
|
||||||
cls.ANTENNA_ICON = Images.get(ImageEnum.ANTENNA, ANTENNA_SIZE)
|
cls.ANTENNA_ICON = Images.get(ImageEnum.ANTENNA, ANTENNA_SIZE)
|
||||||
|
|
||||||
|
|
||||||
class EdgeUtils:
|
|
||||||
@classmethod
|
|
||||||
def get_token(cls, src: int, dst: int) -> Tuple[int, ...]:
|
|
||||||
return tuple(sorted([src, dst]))
|
|
||||||
|
|
|
@ -686,6 +686,7 @@ message Link {
|
||||||
Interface interface_one = 4;
|
Interface interface_one = 4;
|
||||||
Interface interface_two = 5;
|
Interface interface_two = 5;
|
||||||
LinkOptions options = 6;
|
LinkOptions options = 6;
|
||||||
|
int32 network_id = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LinkOptions {
|
message LinkOptions {
|
||||||
|
|
Loading…
Add table
Reference in a new issue