commit
7e50dbdc65
49 changed files with 1204 additions and 530 deletions
|
@ -17,7 +17,7 @@ HEIGHT = 800
|
||||||
|
|
||||||
|
|
||||||
class Application(tk.Frame):
|
class Application(tk.Frame):
|
||||||
def __init__(self, proxy):
|
def __init__(self, proxy: bool):
|
||||||
super().__init__(master=None)
|
super().__init__(master=None)
|
||||||
# load node icons
|
# load node icons
|
||||||
NodeUtils.setup()
|
NodeUtils.setup()
|
||||||
|
|
|
@ -5,6 +5,7 @@ import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING, Dict, List
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -14,11 +15,16 @@ from core.gui.dialogs.mobilityplayer import MobilityPlayer
|
||||||
from core.gui.dialogs.sessions import SessionsDialog
|
from core.gui.dialogs.sessions import SessionsDialog
|
||||||
from core.gui.errors import show_grpc_error
|
from core.gui.errors import show_grpc_error
|
||||||
from core.gui.graph import tags
|
from core.gui.graph import tags
|
||||||
|
from core.gui.graph.edges import CanvasEdge
|
||||||
|
from core.gui.graph.node import CanvasNode
|
||||||
from core.gui.graph.shape import AnnotationData, Shape
|
from core.gui.graph.shape import AnnotationData, Shape
|
||||||
from core.gui.graph.shapeutils import ShapeType
|
from core.gui.graph.shapeutils import ShapeType
|
||||||
from core.gui.interface import InterfaceManager
|
from core.gui.interface import InterfaceManager
|
||||||
from core.gui.nodeutils import NodeDraw, NodeUtils
|
from core.gui.nodeutils import NodeDraw, NodeUtils
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
GUI_SOURCE = "gui"
|
GUI_SOURCE = "gui"
|
||||||
OBSERVERS = {
|
OBSERVERS = {
|
||||||
"processes": "ps",
|
"processes": "ps",
|
||||||
|
@ -34,20 +40,20 @@ OBSERVERS = {
|
||||||
|
|
||||||
|
|
||||||
class CoreServer:
|
class CoreServer:
|
||||||
def __init__(self, name, address, port):
|
def __init__(self, name: str, address: str, port: int):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.address = address
|
self.address = address
|
||||||
self.port = port
|
self.port = port
|
||||||
|
|
||||||
|
|
||||||
class Observer:
|
class Observer:
|
||||||
def __init__(self, name, cmd):
|
def __init__(self, name: str, cmd: str):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.cmd = cmd
|
self.cmd = cmd
|
||||||
|
|
||||||
|
|
||||||
class CoreClient:
|
class CoreClient:
|
||||||
def __init__(self, app, proxy):
|
def __init__(self, app: "Application", proxy: bool):
|
||||||
"""
|
"""
|
||||||
Create a CoreGrpc instance
|
Create a CoreGrpc instance
|
||||||
"""
|
"""
|
||||||
|
@ -110,7 +116,7 @@ class CoreClient:
|
||||||
self.handling_events.cancel()
|
self.handling_events.cancel()
|
||||||
self.handling_events = None
|
self.handling_events = None
|
||||||
|
|
||||||
def set_observer(self, value):
|
def set_observer(self, value: str):
|
||||||
self.observer = value
|
self.observer = value
|
||||||
|
|
||||||
def read_config(self):
|
def read_config(self):
|
||||||
|
@ -132,7 +138,7 @@ class CoreClient:
|
||||||
observer = Observer(config["name"], config["cmd"])
|
observer = Observer(config["name"], config["cmd"])
|
||||||
self.custom_observers[observer.name] = observer
|
self.custom_observers[observer.name] = observer
|
||||||
|
|
||||||
def handle_events(self, event):
|
def handle_events(self, event: core_pb2.Event):
|
||||||
if event.session_id != self.session_id:
|
if event.session_id != self.session_id:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
"ignoring event session(%s) current(%s)",
|
"ignoring event session(%s) current(%s)",
|
||||||
|
@ -170,7 +176,7 @@ class CoreClient:
|
||||||
else:
|
else:
|
||||||
logging.info("unhandled event: %s", event)
|
logging.info("unhandled event: %s", event)
|
||||||
|
|
||||||
def handle_link_event(self, event):
|
def handle_link_event(self, event: core_pb2.LinkEvent):
|
||||||
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
|
||||||
canvas_node_one = self.canvas_nodes[node_one_id]
|
canvas_node_one = self.canvas_nodes[node_one_id]
|
||||||
|
@ -183,7 +189,7 @@ class CoreClient:
|
||||||
else:
|
else:
|
||||||
logging.warning("unknown link event: %s", event.message_type)
|
logging.warning("unknown link event: %s", event.message_type)
|
||||||
|
|
||||||
def handle_node_event(self, event):
|
def handle_node_event(self, event: core_pb2.NodeEvent):
|
||||||
if event.source == GUI_SOURCE:
|
if event.source == GUI_SOURCE:
|
||||||
return
|
return
|
||||||
node_id = event.node.id
|
node_id = event.node.id
|
||||||
|
@ -201,7 +207,7 @@ class CoreClient:
|
||||||
self.handling_throughputs.cancel()
|
self.handling_throughputs.cancel()
|
||||||
self.handling_throughputs = None
|
self.handling_throughputs = None
|
||||||
|
|
||||||
def handle_throughputs(self, event):
|
def handle_throughputs(self, event: core_pb2.ThroughputsEvent):
|
||||||
if event.session_id != self.session_id:
|
if event.session_id != self.session_id:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
"ignoring throughput event session(%s) current(%s)",
|
"ignoring throughput event session(%s) current(%s)",
|
||||||
|
@ -212,11 +218,11 @@ class CoreClient:
|
||||||
logging.info("handling throughputs event: %s", event)
|
logging.info("handling throughputs event: %s", event)
|
||||||
self.app.canvas.set_throughputs(event)
|
self.app.canvas.set_throughputs(event)
|
||||||
|
|
||||||
def handle_exception_event(self, event):
|
def handle_exception_event(self, event: core_pb2.ExceptionEvent):
|
||||||
logging.info("exception event: %s", event)
|
logging.info("exception event: %s", event)
|
||||||
self.app.statusbar.core_alarms.append(event)
|
self.app.statusbar.core_alarms.append(event)
|
||||||
|
|
||||||
def join_session(self, session_id, query_location=True):
|
def join_session(self, session_id: int, query_location: bool = True):
|
||||||
# update session and title
|
# update session and title
|
||||||
self.session_id = session_id
|
self.session_id = session_id
|
||||||
self.master.title(f"CORE Session({self.session_id})")
|
self.master.title(f"CORE Session({self.session_id})")
|
||||||
|
@ -297,10 +303,10 @@ class CoreClient:
|
||||||
# update ui to represent current state
|
# update ui to represent current state
|
||||||
self.app.after(0, self.app.joined_session_update)
|
self.app.after(0, self.app.joined_session_update)
|
||||||
|
|
||||||
def is_runtime(self):
|
def is_runtime(self) -> bool:
|
||||||
return self.state == core_pb2.SessionState.RUNTIME
|
return self.state == core_pb2.SessionState.RUNTIME
|
||||||
|
|
||||||
def parse_metadata(self, config):
|
def parse_metadata(self, config: Dict[str, str]):
|
||||||
# canvas setting
|
# canvas setting
|
||||||
canvas_config = config.get("canvas")
|
canvas_config = config.get("canvas")
|
||||||
logging.info("canvas metadata: %s", canvas_config)
|
logging.info("canvas metadata: %s", canvas_config)
|
||||||
|
@ -364,8 +370,6 @@ class CoreClient:
|
||||||
def create_new_session(self):
|
def create_new_session(self):
|
||||||
"""
|
"""
|
||||||
Create a new session
|
Create a new session
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
response = self.client.create_session()
|
response = self.client.create_session()
|
||||||
|
@ -384,7 +388,7 @@ class CoreClient:
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
self.app.after(0, show_grpc_error, e)
|
self.app.after(0, show_grpc_error, e)
|
||||||
|
|
||||||
def delete_session(self, session_id=None):
|
def delete_session(self, session_id: int = None):
|
||||||
if session_id is None:
|
if session_id is None:
|
||||||
session_id = self.session_id
|
session_id = self.session_id
|
||||||
try:
|
try:
|
||||||
|
@ -396,8 +400,6 @@ class CoreClient:
|
||||||
def set_up(self):
|
def set_up(self):
|
||||||
"""
|
"""
|
||||||
Query sessions, if there exist any, prompt whether to join one
|
Query sessions, if there exist any, prompt whether to join one
|
||||||
|
|
||||||
:return: existing sessions
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.client.connect()
|
self.client.connect()
|
||||||
|
@ -426,7 +428,7 @@ class CoreClient:
|
||||||
self.app.after(0, show_grpc_error, e)
|
self.app.after(0, show_grpc_error, e)
|
||||||
self.app.close()
|
self.app.close()
|
||||||
|
|
||||||
def edit_node(self, core_node):
|
def edit_node(self, core_node: core_pb2.Node):
|
||||||
try:
|
try:
|
||||||
self.client.edit_node(
|
self.client.edit_node(
|
||||||
self.session_id, core_node.id, core_node.position, source=GUI_SOURCE
|
self.session_id, core_node.id, core_node.position, source=GUI_SOURCE
|
||||||
|
@ -434,7 +436,7 @@ class CoreClient:
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
self.app.after(0, show_grpc_error, e)
|
self.app.after(0, show_grpc_error, e)
|
||||||
|
|
||||||
def start_session(self):
|
def start_session(self) -> core_pb2.StartSessionResponse:
|
||||||
nodes = [x.core_node for x in self.canvas_nodes.values()]
|
nodes = [x.core_node for x in self.canvas_nodes.values()]
|
||||||
links = [x.link for x in self.links.values()]
|
links = [x.link for x in self.links.values()]
|
||||||
wlan_configs = self.get_wlan_configs_proto()
|
wlan_configs = self.get_wlan_configs_proto()
|
||||||
|
@ -477,7 +479,7 @@ class CoreClient:
|
||||||
self.app.after(0, show_grpc_error, e)
|
self.app.after(0, show_grpc_error, e)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def stop_session(self, session_id=None):
|
def stop_session(self, session_id: int = None) -> core_pb2.StartSessionResponse:
|
||||||
if not session_id:
|
if not session_id:
|
||||||
session_id = self.session_id
|
session_id = self.session_id
|
||||||
response = core_pb2.StopSessionResponse(result=False)
|
response = core_pb2.StopSessionResponse(result=False)
|
||||||
|
@ -519,7 +521,7 @@ class CoreClient:
|
||||||
response = self.client.set_session_metadata(self.session_id, metadata)
|
response = self.client.set_session_metadata(self.session_id, metadata)
|
||||||
logging.info("set session metadata: %s", response)
|
logging.info("set session metadata: %s", response)
|
||||||
|
|
||||||
def launch_terminal(self, node_id):
|
def launch_terminal(self, node_id: int):
|
||||||
try:
|
try:
|
||||||
terminal = self.app.guiconfig["preferences"]["terminal"]
|
terminal = self.app.guiconfig["preferences"]["terminal"]
|
||||||
response = self.client.get_node_terminal(self.session_id, node_id)
|
response = self.client.get_node_terminal(self.session_id, node_id)
|
||||||
|
@ -528,12 +530,9 @@ class CoreClient:
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
self.app.after(0, show_grpc_error, e)
|
self.app.after(0, show_grpc_error, e)
|
||||||
|
|
||||||
def save_xml(self, file_path):
|
def save_xml(self, file_path: str):
|
||||||
"""
|
"""
|
||||||
Save core session as to an xml file
|
Save core session as to an xml file
|
||||||
|
|
||||||
:param str file_path: file path that user pick
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if self.state != core_pb2.SessionState.RUNTIME:
|
if self.state != core_pb2.SessionState.RUNTIME:
|
||||||
|
@ -546,12 +545,9 @@ class CoreClient:
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
self.app.after(0, show_grpc_error, e)
|
self.app.after(0, show_grpc_error, e)
|
||||||
|
|
||||||
def open_xml(self, file_path):
|
def open_xml(self, file_path: str):
|
||||||
"""
|
"""
|
||||||
Open core xml
|
Open core xml
|
||||||
|
|
||||||
:param str file_path: file to open
|
|
||||||
:return: session id
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
response = self.client.open_xml(file_path)
|
response = self.client.open_xml(file_path)
|
||||||
|
@ -560,12 +556,21 @@ class CoreClient:
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
self.app.after(0, show_grpc_error, e)
|
self.app.after(0, show_grpc_error, e)
|
||||||
|
|
||||||
def get_node_service(self, node_id, service_name):
|
def get_node_service(
|
||||||
|
self, node_id: int, service_name: str
|
||||||
|
) -> core_pb2.NodeServiceData:
|
||||||
response = self.client.get_node_service(self.session_id, node_id, service_name)
|
response = self.client.get_node_service(self.session_id, node_id, service_name)
|
||||||
logging.debug("get node service %s", response)
|
logging.debug("get node service %s", response)
|
||||||
return response.service
|
return response.service
|
||||||
|
|
||||||
def set_node_service(self, node_id, service_name, startups, validations, shutdowns):
|
def set_node_service(
|
||||||
|
self,
|
||||||
|
node_id: int,
|
||||||
|
service_name: str,
|
||||||
|
startups: List[str],
|
||||||
|
validations: List[str],
|
||||||
|
shutdowns: List[str],
|
||||||
|
) -> core_pb2.NodeServiceData:
|
||||||
response = self.client.set_node_service(
|
response = self.client.set_node_service(
|
||||||
self.session_id, node_id, service_name, startups, validations, shutdowns
|
self.session_id, node_id, service_name, startups, validations, shutdowns
|
||||||
)
|
)
|
||||||
|
@ -574,14 +579,18 @@ class CoreClient:
|
||||||
logging.debug("get node service : %s", response)
|
logging.debug("get node service : %s", response)
|
||||||
return response.service
|
return response.service
|
||||||
|
|
||||||
def get_node_service_file(self, node_id, service_name, file_name):
|
def get_node_service_file(
|
||||||
|
self, node_id: int, service_name: str, file_name: str
|
||||||
|
) -> str:
|
||||||
response = self.client.get_node_service_file(
|
response = self.client.get_node_service_file(
|
||||||
self.session_id, node_id, service_name, file_name
|
self.session_id, node_id, service_name, file_name
|
||||||
)
|
)
|
||||||
logging.debug("get service file %s", response)
|
logging.debug("get service file %s", response)
|
||||||
return response.data
|
return response.data
|
||||||
|
|
||||||
def set_node_service_file(self, node_id, service_name, file_name, data):
|
def set_node_service_file(
|
||||||
|
self, node_id: int, service_name: str, file_name: str, data: bytes
|
||||||
|
):
|
||||||
response = self.client.set_node_service_file(
|
response = self.client.set_node_service_file(
|
||||||
self.session_id, node_id, service_name, file_name, data
|
self.session_id, node_id, service_name, file_name, data
|
||||||
)
|
)
|
||||||
|
@ -590,8 +599,6 @@ class CoreClient:
|
||||||
def create_nodes_and_links(self):
|
def create_nodes_and_links(self):
|
||||||
"""
|
"""
|
||||||
create nodes and links that have not been created yet
|
create nodes and links that have not been created yet
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
node_protos = [x.core_node for x in self.canvas_nodes.values()]
|
node_protos = [x.core_node for x in self.canvas_nodes.values()]
|
||||||
link_protos = [x.link for x in self.links.values()]
|
link_protos = [x.link for x in self.links.values()]
|
||||||
|
@ -618,8 +625,6 @@ class CoreClient:
|
||||||
def send_data(self):
|
def send_data(self):
|
||||||
"""
|
"""
|
||||||
send to daemon all session info, but don't start the session
|
send to daemon all session info, but don't start the session
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.create_nodes_and_links()
|
self.create_nodes_and_links()
|
||||||
for config_proto in self.get_wlan_configs_proto():
|
for config_proto in self.get_wlan_configs_proto():
|
||||||
|
@ -664,18 +669,13 @@ class CoreClient:
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
Clean ups when done using grpc
|
Clean ups when done using grpc
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
logging.debug("close grpc")
|
logging.debug("close grpc")
|
||||||
self.client.close()
|
self.client.close()
|
||||||
|
|
||||||
def next_node_id(self):
|
def next_node_id(self) -> int:
|
||||||
"""
|
"""
|
||||||
Get the next usable node id.
|
Get the next usable node id.
|
||||||
|
|
||||||
:return: the next id to be used
|
|
||||||
:rtype: int
|
|
||||||
"""
|
"""
|
||||||
i = 1
|
i = 1
|
||||||
while True:
|
while True:
|
||||||
|
@ -684,15 +684,11 @@ class CoreClient:
|
||||||
i += 1
|
i += 1
|
||||||
return i
|
return i
|
||||||
|
|
||||||
def create_node(self, x, y, node_type, model):
|
def create_node(
|
||||||
|
self, x: int, y: int, node_type: core_pb2.NodeType, model: str
|
||||||
|
) -> core_pb2.Node:
|
||||||
"""
|
"""
|
||||||
Add node, with information filled in, to grpc manager
|
Add node, with information filled in, to grpc manager
|
||||||
|
|
||||||
:param int x: x coord
|
|
||||||
:param int y: y coord
|
|
||||||
:param core_pb2.NodeType node_type: node type
|
|
||||||
:param str model: node model
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
node_id = self.next_node_id()
|
node_id = self.next_node_id()
|
||||||
position = core_pb2.Position(x=x, y=y)
|
position = core_pb2.Position(x=x, y=y)
|
||||||
|
@ -727,13 +723,10 @@ class CoreClient:
|
||||||
)
|
)
|
||||||
return node
|
return node
|
||||||
|
|
||||||
def delete_graph_nodes(self, canvas_nodes):
|
def delete_graph_nodes(self, canvas_nodes: List[core_pb2.Node]):
|
||||||
"""
|
"""
|
||||||
remove the nodes selected by the user and anything related to that node
|
remove the nodes selected by the user and anything related to that node
|
||||||
such as link, configurations, interfaces
|
such as link, configurations, interfaces
|
||||||
|
|
||||||
:param list canvas_nodes: list of nodes to delete
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
edges = set()
|
edges = set()
|
||||||
for canvas_node in canvas_nodes:
|
for canvas_node in canvas_nodes:
|
||||||
|
@ -755,12 +748,9 @@ class CoreClient:
|
||||||
if edge in edges:
|
if edge in edges:
|
||||||
continue
|
continue
|
||||||
edges.add(edge)
|
edges.add(edge)
|
||||||
#
|
|
||||||
# if edge.token not in self.links:
|
|
||||||
# logging.error("unknown edge: %s", edge.token)
|
|
||||||
self.links.pop(edge.token, None)
|
self.links.pop(edge.token, None)
|
||||||
|
|
||||||
def create_interface(self, canvas_node):
|
def create_interface(self, canvas_node: CanvasNode) -> core_pb2.Interface:
|
||||||
node = canvas_node.core_node
|
node = canvas_node.core_node
|
||||||
ip4, ip6, prefix = self.interfaces_manager.get_ips(node.id)
|
ip4, ip6, prefix = self.interfaces_manager.get_ips(node.id)
|
||||||
interface_id = len(canvas_node.interfaces)
|
interface_id = len(canvas_node.interfaces)
|
||||||
|
@ -777,16 +767,12 @@ class CoreClient:
|
||||||
)
|
)
|
||||||
return interface
|
return interface
|
||||||
|
|
||||||
def create_link(self, edge, canvas_src_node, canvas_dst_node):
|
def create_link(
|
||||||
|
self, edge: CanvasEdge, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Create core link for a pair of canvas nodes, with token referencing
|
Create core link for a pair of canvas nodes, with token referencing
|
||||||
the canvas edge.
|
the canvas edge.
|
||||||
|
|
||||||
:param edge: edge for link
|
|
||||||
:param canvas_src_node: canvas node one
|
|
||||||
:param canvas_dst_node: canvas node two
|
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
src_node = canvas_src_node.core_node
|
src_node = canvas_src_node.core_node
|
||||||
dst_node = canvas_dst_node.core_node
|
dst_node = canvas_dst_node.core_node
|
||||||
|
@ -816,7 +802,7 @@ class CoreClient:
|
||||||
edge.set_link(link)
|
edge.set_link(link)
|
||||||
self.links[edge.token] = edge
|
self.links[edge.token] = edge
|
||||||
|
|
||||||
def get_wlan_configs_proto(self):
|
def get_wlan_configs_proto(self) -> List[core_pb2.WlanConfig]:
|
||||||
configs = []
|
configs = []
|
||||||
for node_id, config in self.wlan_configs.items():
|
for node_id, config in self.wlan_configs.items():
|
||||||
config = {x: config[x].value for x in config}
|
config = {x: config[x].value for x in config}
|
||||||
|
@ -824,7 +810,7 @@ class CoreClient:
|
||||||
configs.append(wlan_config)
|
configs.append(wlan_config)
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
def get_mobility_configs_proto(self):
|
def get_mobility_configs_proto(self) -> List[core_pb2.MobilityConfig]:
|
||||||
configs = []
|
configs = []
|
||||||
for node_id, config in self.mobility_configs.items():
|
for node_id, config in self.mobility_configs.items():
|
||||||
config = {x: config[x].value for x in config}
|
config = {x: config[x].value for x in config}
|
||||||
|
@ -832,7 +818,7 @@ class CoreClient:
|
||||||
configs.append(mobility_config)
|
configs.append(mobility_config)
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
def get_emane_model_configs_proto(self):
|
def get_emane_model_configs_proto(self) -> List[core_pb2.EmaneModelConfig]:
|
||||||
configs = []
|
configs = []
|
||||||
for key, config in self.emane_model_configs.items():
|
for key, config in self.emane_model_configs.items():
|
||||||
node_id, model, interface = key
|
node_id, model, interface = key
|
||||||
|
@ -845,7 +831,7 @@ class CoreClient:
|
||||||
configs.append(config_proto)
|
configs.append(config_proto)
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
def get_service_configs_proto(self):
|
def get_service_configs_proto(self) -> List[core_pb2.ServiceConfig]:
|
||||||
configs = []
|
configs = []
|
||||||
for node_id, services in self.service_configs.items():
|
for node_id, services in self.service_configs.items():
|
||||||
for name, config in services.items():
|
for name, config in services.items():
|
||||||
|
@ -859,7 +845,7 @@ class CoreClient:
|
||||||
configs.append(config_proto)
|
configs.append(config_proto)
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
def get_service_file_configs_proto(self):
|
def get_service_file_configs_proto(self) -> List[core_pb2.ServiceFileConfig]:
|
||||||
configs = []
|
configs = []
|
||||||
for (node_id, file_configs) in self.file_configs.items():
|
for (node_id, file_configs) in self.file_configs.items():
|
||||||
for service, file_config in file_configs.items():
|
for service, file_config in file_configs.items():
|
||||||
|
@ -870,25 +856,27 @@ class CoreClient:
|
||||||
configs.append(config_proto)
|
configs.append(config_proto)
|
||||||
return configs
|
return configs
|
||||||
|
|
||||||
def run(self, node_id):
|
def run(self, node_id: int) -> str:
|
||||||
logging.info("running node(%s) cmd: %s", node_id, self.observer)
|
logging.info("running node(%s) cmd: %s", node_id, self.observer)
|
||||||
return self.client.node_command(self.session_id, node_id, self.observer).output
|
return self.client.node_command(self.session_id, node_id, self.observer).output
|
||||||
|
|
||||||
def get_wlan_config(self, node_id):
|
def get_wlan_config(self, node_id: int) -> Dict[str, core_pb2.ConfigOption]:
|
||||||
config = self.wlan_configs.get(node_id)
|
config = self.wlan_configs.get(node_id)
|
||||||
if not config:
|
if not config:
|
||||||
response = self.client.get_wlan_config(self.session_id, node_id)
|
response = self.client.get_wlan_config(self.session_id, node_id)
|
||||||
config = response.config
|
config = response.config
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def get_mobility_config(self, node_id):
|
def get_mobility_config(self, node_id: int) -> Dict[str, core_pb2.ConfigOption]:
|
||||||
config = self.mobility_configs.get(node_id)
|
config = self.mobility_configs.get(node_id)
|
||||||
if not config:
|
if not config:
|
||||||
response = self.client.get_mobility_config(self.session_id, node_id)
|
response = self.client.get_mobility_config(self.session_id, node_id)
|
||||||
config = response.config
|
config = response.config
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def get_emane_model_config(self, node_id, model, interface=None):
|
def get_emane_model_config(
|
||||||
|
self, node_id: int, model: str, interface: int = None
|
||||||
|
) -> Dict[str, core_pb2.ConfigOption]:
|
||||||
logging.info("getting emane model config: %s %s %s", node_id, model, interface)
|
logging.info("getting emane model config: %s %s %s", node_id, model, interface)
|
||||||
config = self.emane_model_configs.get((node_id, model, interface))
|
config = self.emane_model_configs.get((node_id, model, interface))
|
||||||
if not config:
|
if not config:
|
||||||
|
@ -900,15 +888,21 @@ class CoreClient:
|
||||||
config = response.config
|
config = response.config
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def set_emane_model_config(self, node_id, model, config, interface=None):
|
def set_emane_model_config(
|
||||||
|
self,
|
||||||
|
node_id: int,
|
||||||
|
model: str,
|
||||||
|
config: Dict[str, core_pb2.ConfigOption],
|
||||||
|
interface: int = None,
|
||||||
|
):
|
||||||
logging.info("setting emane model config: %s %s %s", node_id, model, interface)
|
logging.info("setting emane model config: %s %s %s", node_id, model, interface)
|
||||||
self.emane_model_configs[(node_id, model, interface)] = config
|
self.emane_model_configs[(node_id, model, interface)] = config
|
||||||
|
|
||||||
def copy_node_service(self, _from, _to):
|
def copy_node_service(self, _from: int, _to: int):
|
||||||
services = self.canvas_nodes[_from].core_node.services
|
services = self.canvas_nodes[_from].core_node.services
|
||||||
self.canvas_nodes[_to].core_node.services[:] = services
|
self.canvas_nodes[_to].core_node.services[:] = services
|
||||||
|
|
||||||
def copy_node_config(self, _from, _to):
|
def copy_node_config(self, _from: int, _to: int):
|
||||||
node_type = self.canvas_nodes[_from].core_node.type
|
node_type = self.canvas_nodes[_from].core_node.type
|
||||||
if node_type == core_pb2.NodeType.DEFAULT:
|
if node_type == core_pb2.NodeType.DEFAULT:
|
||||||
services = self.canvas_nodes[_from].core_node.services
|
services = self.canvas_nodes[_from].core_node.services
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.widgets import CodeText
|
from core.gui.widgets import CodeText
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
LICENSE = """\
|
LICENSE = """\
|
||||||
Copyright (c) 2005-2020, the Boeing Company.
|
Copyright (c) 2005-2020, the Boeing Company.
|
||||||
|
|
||||||
|
@ -31,7 +35,7 @@ THE POSSIBILITY OF SUCH DAMAGE.\
|
||||||
|
|
||||||
|
|
||||||
class AboutDialog(Dialog):
|
class AboutDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "About CORE", modal=True)
|
super().__init__(master, app, "About CORE", modal=True)
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
|
|
|
@ -3,15 +3,19 @@ check engine light
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.api.grpc.core_pb2 import ExceptionLevel
|
from core.api.grpc.core_pb2 import ExceptionLevel
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
from core.gui.widgets import CodeText
|
from core.gui.widgets import CodeText
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class AlertsDialog(Dialog):
|
class AlertsDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "Alerts", modal=True)
|
super().__init__(master, app, "Alerts", modal=True)
|
||||||
self.app = app
|
self.app = app
|
||||||
self.tree = None
|
self.tree = None
|
||||||
|
@ -110,7 +114,7 @@ class AlertsDialog(Dialog):
|
||||||
dialog = DaemonLog(self, self.app)
|
dialog = DaemonLog(self, self.app)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def click_select(self, event):
|
def click_select(self, event: tk.Event):
|
||||||
current = self.tree.selection()[0]
|
current = self.tree.selection()[0]
|
||||||
alarm = self.alarm_map[current]
|
alarm = self.alarm_map[current]
|
||||||
self.codetext.text.config(state=tk.NORMAL)
|
self.codetext.text.config(state=tk.NORMAL)
|
||||||
|
@ -120,7 +124,7 @@ class AlertsDialog(Dialog):
|
||||||
|
|
||||||
|
|
||||||
class DaemonLog(Dialog):
|
class DaemonLog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: tk.Widget, app: "Application"):
|
||||||
super().__init__(master, app, "core-daemon log", modal=True)
|
super().__init__(master, app, "core-daemon log", modal=True)
|
||||||
self.columnconfigure(0, weight=1)
|
self.columnconfigure(0, weight=1)
|
||||||
self.path = tk.StringVar(value="/var/log/core-daemon.log")
|
self.path = tk.StringVar(value="/var/log/core-daemon.log")
|
||||||
|
|
|
@ -3,19 +3,21 @@ size and scale
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import font, ttk
|
from tkinter import font, ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
PIXEL_SCALE = 100
|
PIXEL_SCALE = 100
|
||||||
|
|
||||||
|
|
||||||
class SizeAndScaleDialog(Dialog):
|
class SizeAndScaleDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
"""
|
"""
|
||||||
create an instance for size and scale object
|
create an instance for size and scale object
|
||||||
|
|
||||||
:param app: main application
|
|
||||||
"""
|
"""
|
||||||
super().__init__(master, app, "Canvas Size and Scale", modal=True)
|
super().__init__(master, app, "Canvas Size and Scale", modal=True)
|
||||||
self.canvas = self.app.canvas
|
self.canvas = self.app.canvas
|
||||||
|
|
|
@ -4,6 +4,7 @@ set wallpaper
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.appconfig import BACKGROUNDS_PATH
|
from core.gui.appconfig import BACKGROUNDS_PATH
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
|
@ -11,13 +12,14 @@ from core.gui.images import Images
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
from core.gui.widgets import image_chooser
|
from core.gui.widgets import image_chooser
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class CanvasWallpaperDialog(Dialog):
|
class CanvasWallpaperDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
"""
|
"""
|
||||||
create an instance of CanvasWallpaper object
|
create an instance of CanvasWallpaper object
|
||||||
|
|
||||||
:param coretk.app.Application app: root application
|
|
||||||
"""
|
"""
|
||||||
super().__init__(master, app, "Canvas Background", modal=True)
|
super().__init__(master, app, "Canvas Background", modal=True)
|
||||||
self.canvas = self.app.canvas
|
self.canvas = self.app.canvas
|
||||||
|
@ -140,8 +142,6 @@ class CanvasWallpaperDialog(Dialog):
|
||||||
def click_clear(self):
|
def click_clear(self):
|
||||||
"""
|
"""
|
||||||
delete like shown in image link entry if there is any
|
delete like shown in image link entry if there is any
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
# delete entry
|
# delete entry
|
||||||
self.filename.set("")
|
self.filename.set("")
|
||||||
|
|
|
@ -4,12 +4,16 @@ custom color picker
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class ColorPickerDialog(Dialog):
|
class ColorPickerDialog(Dialog):
|
||||||
def __init__(self, master, app, initcolor="#000000"):
|
def __init__(self, master: Any, app: "Application", initcolor: str = "#000000"):
|
||||||
super().__init__(master, app, "color picker", modal=True)
|
super().__init__(master, app, "color picker", modal=True)
|
||||||
self.red_entry = None
|
self.red_entry = None
|
||||||
self.blue_entry = None
|
self.blue_entry = None
|
||||||
|
@ -31,7 +35,7 @@ class ColorPickerDialog(Dialog):
|
||||||
self.draw()
|
self.draw()
|
||||||
self.set_bindings()
|
self.set_bindings()
|
||||||
|
|
||||||
def askcolor(self):
|
def askcolor(self) -> str:
|
||||||
self.show()
|
self.show()
|
||||||
return self.color
|
return self.color
|
||||||
|
|
||||||
|
@ -175,19 +179,16 @@ class ColorPickerDialog(Dialog):
|
||||||
self.color = self.hex.get()
|
self.color = self.hex.get()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def get_hex(self):
|
def get_hex(self) -> str:
|
||||||
"""
|
"""
|
||||||
convert current RGB values into hex color
|
convert current RGB values into hex color
|
||||||
|
|
||||||
:rtype: str
|
|
||||||
:return: hex color
|
|
||||||
"""
|
"""
|
||||||
red = self.red_entry.get()
|
red = self.red_entry.get()
|
||||||
blue = self.blue_entry.get()
|
blue = self.blue_entry.get()
|
||||||
green = self.green_entry.get()
|
green = self.green_entry.get()
|
||||||
return "#%02x%02x%02x" % (int(red), int(green), int(blue))
|
return "#%02x%02x%02x" % (int(red), int(green), int(blue))
|
||||||
|
|
||||||
def current_focus(self, focus):
|
def current_focus(self, focus: str):
|
||||||
self.focus = focus
|
self.focus = focus
|
||||||
|
|
||||||
def update_color(self, arg1=None, arg2=None, arg3=None):
|
def update_color(self, arg1=None, arg2=None, arg3=None):
|
||||||
|
@ -210,35 +211,31 @@ class ColorPickerDialog(Dialog):
|
||||||
self.set_entry(red, green, blue)
|
self.set_entry(red, green, blue)
|
||||||
self.set_scale(red, green, blue)
|
self.set_scale(red, green, blue)
|
||||||
self.display.config(background=hex_code)
|
self.display.config(background=hex_code)
|
||||||
self.set_label(red, green, blue)
|
self.set_label(str(red), str(green), str(blue))
|
||||||
|
|
||||||
def scale_callback(self, var, color_var):
|
def scale_callback(self, var: tk.IntVar, color_var: tk.IntVar):
|
||||||
color_var.set(var.get())
|
color_var.set(var.get())
|
||||||
self.focus = "rgb"
|
self.focus = "rgb"
|
||||||
self.update_color()
|
self.update_color()
|
||||||
|
|
||||||
def set_scale(self, red, green, blue):
|
def set_scale(self, red: int, green: int, blue: int):
|
||||||
self.red_scale.set(red)
|
self.red_scale.set(red)
|
||||||
self.green_scale.set(green)
|
self.green_scale.set(green)
|
||||||
self.blue_scale.set(blue)
|
self.blue_scale.set(blue)
|
||||||
|
|
||||||
def set_entry(self, red, green, blue):
|
def set_entry(self, red: int, green: int, blue: int):
|
||||||
self.red.set(red)
|
self.red.set(red)
|
||||||
self.green.set(green)
|
self.green.set(green)
|
||||||
self.blue.set(blue)
|
self.blue.set(blue)
|
||||||
|
|
||||||
def set_label(self, red, green, blue):
|
def set_label(self, red: str, green: str, blue: str):
|
||||||
self.red_label.configure(background="#%02x%02x%02x" % (int(red), 0, 0))
|
self.red_label.configure(background="#%02x%02x%02x" % (int(red), 0, 0))
|
||||||
self.green_label.configure(background="#%02x%02x%02x" % (0, int(green), 0))
|
self.green_label.configure(background="#%02x%02x%02x" % (0, int(green), 0))
|
||||||
self.blue_label.configure(background="#%02x%02x%02x" % (0, 0, int(blue)))
|
self.blue_label.configure(background="#%02x%02x%02x" % (0, 0, int(blue)))
|
||||||
|
|
||||||
def get_rgb(self, hex_code):
|
def get_rgb(self, hex_code: str) -> [int, int, int]:
|
||||||
"""
|
"""
|
||||||
convert a valid hex code to RGB values
|
convert a valid hex code to RGB values
|
||||||
|
|
||||||
:param string hex_code: color in hex
|
|
||||||
:rtype: tuple(int, int, int)
|
|
||||||
:return: the RGB values
|
|
||||||
"""
|
"""
|
||||||
if len(hex_code) == 4:
|
if len(hex_code) == 4:
|
||||||
red = hex_code[1]
|
red = hex_code[1]
|
||||||
|
|
|
@ -5,14 +5,18 @@ copy service config dialog
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Any, Tuple
|
||||||
|
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import FRAME_PAD, PADX
|
from core.gui.themes import FRAME_PAD, PADX
|
||||||
from core.gui.widgets import CodeText
|
from core.gui.widgets import CodeText
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class CopyServiceConfigDialog(Dialog):
|
class CopyServiceConfigDialog(Dialog):
|
||||||
def __init__(self, master, app, node_id):
|
def __init__(self, master: Any, app: "Application", node_id: int):
|
||||||
super().__init__(master, app, f"Copy services to node {node_id}", modal=True)
|
super().__init__(master, app, f"Copy services to node {node_id}", modal=True)
|
||||||
self.parent = master
|
self.parent = master
|
||||||
self.app = app
|
self.app = app
|
||||||
|
@ -128,6 +132,7 @@ class CopyServiceConfigDialog(Dialog):
|
||||||
|
|
||||||
def click_view(self):
|
def click_view(self):
|
||||||
selected = self.tree.selection()
|
selected = self.tree.selection()
|
||||||
|
data = ""
|
||||||
if selected:
|
if selected:
|
||||||
item = self.tree.item(selected[0])
|
item = self.tree.item(selected[0])
|
||||||
if "file" in item["tags"]:
|
if "file" in item["tags"]:
|
||||||
|
@ -157,7 +162,7 @@ class CopyServiceConfigDialog(Dialog):
|
||||||
)
|
)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def get_node_service(self, selected):
|
def get_node_service(self, selected: Tuple[str]) -> [int, str]:
|
||||||
service_tree_id = self.tree.parent(selected[0])
|
service_tree_id = self.tree.parent(selected[0])
|
||||||
service_name = self.tree.item(service_tree_id)["text"]
|
service_name = self.tree.item(service_tree_id)["text"]
|
||||||
node_tree_id = self.tree.parent(service_tree_id)
|
node_tree_id = self.tree.parent(service_tree_id)
|
||||||
|
@ -166,7 +171,14 @@ class CopyServiceConfigDialog(Dialog):
|
||||||
|
|
||||||
|
|
||||||
class ViewConfigDialog(Dialog):
|
class ViewConfigDialog(Dialog):
|
||||||
def __init__(self, master, app, node_id, data, filename=None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
master: Any,
|
||||||
|
app: "Application",
|
||||||
|
node_id: int,
|
||||||
|
data: str,
|
||||||
|
filename: str = None,
|
||||||
|
):
|
||||||
super().__init__(master, app, f"n{node_id} config data", modal=True)
|
super().__init__(master, app, f"n{node_id} config data", modal=True)
|
||||||
self.data = data
|
self.data = data
|
||||||
self.service_data = None
|
self.service_data = None
|
||||||
|
|
|
@ -2,6 +2,7 @@ import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Any, Set
|
||||||
|
|
||||||
from core.gui import nodeutils
|
from core.gui import nodeutils
|
||||||
from core.gui.appconfig import ICONS_PATH
|
from core.gui.appconfig import ICONS_PATH
|
||||||
|
@ -11,9 +12,12 @@ from core.gui.nodeutils import NodeDraw
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
from core.gui.widgets import CheckboxList, ListboxScroll, image_chooser
|
from core.gui.widgets import CheckboxList, ListboxScroll, image_chooser
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class ServicesSelectDialog(Dialog):
|
class ServicesSelectDialog(Dialog):
|
||||||
def __init__(self, master, app, current_services):
|
def __init__(self, master: Any, app: "Application", current_services: Set[str]):
|
||||||
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
|
||||||
|
@ -71,7 +75,7 @@ class ServicesSelectDialog(Dialog):
|
||||||
# trigger group change
|
# trigger group change
|
||||||
self.groups.listbox.event_generate("<<ListboxSelect>>")
|
self.groups.listbox.event_generate("<<ListboxSelect>>")
|
||||||
|
|
||||||
def handle_group_change(self, event):
|
def handle_group_change(self, event: tk.Event):
|
||||||
selection = self.groups.listbox.curselection()
|
selection = self.groups.listbox.curselection()
|
||||||
if selection:
|
if selection:
|
||||||
index = selection[0]
|
index = selection[0]
|
||||||
|
@ -81,7 +85,7 @@ class ServicesSelectDialog(Dialog):
|
||||||
checked = name in self.current_services
|
checked = name in self.current_services
|
||||||
self.services.add(name, checked)
|
self.services.add(name, checked)
|
||||||
|
|
||||||
def service_clicked(self, name, var):
|
def service_clicked(self, name: str, var: tk.BooleanVar):
|
||||||
if var.get() and name not in self.current_services:
|
if var.get() and name not in self.current_services:
|
||||||
self.current_services.add(name)
|
self.current_services.add(name)
|
||||||
elif not var.get() and name in self.current_services:
|
elif not var.get() and name in self.current_services:
|
||||||
|
@ -96,7 +100,7 @@ class ServicesSelectDialog(Dialog):
|
||||||
|
|
||||||
|
|
||||||
class CustomNodesDialog(Dialog):
|
class CustomNodesDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "Custom Nodes", modal=True)
|
super().__init__(master, app, "Custom Nodes", modal=True)
|
||||||
self.edit_button = None
|
self.edit_button = None
|
||||||
self.delete_button = None
|
self.delete_button = None
|
||||||
|
@ -241,7 +245,7 @@ class CustomNodesDialog(Dialog):
|
||||||
self.nodes_list.listbox.selection_clear(0, tk.END)
|
self.nodes_list.listbox.selection_clear(0, tk.END)
|
||||||
self.nodes_list.listbox.event_generate("<<ListboxSelect>>")
|
self.nodes_list.listbox.event_generate("<<ListboxSelect>>")
|
||||||
|
|
||||||
def handle_node_select(self, event):
|
def handle_node_select(self, event: tk.Event):
|
||||||
selection = self.nodes_list.listbox.curselection()
|
selection = self.nodes_list.listbox.curselection()
|
||||||
if selection:
|
if selection:
|
||||||
self.selected_index = selection[0]
|
self.selected_index = selection[0]
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.images import ImageEnum, Images
|
from core.gui.images import ImageEnum, Images
|
||||||
from core.gui.themes import DIALOG_PAD
|
from core.gui.themes import DIALOG_PAD
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class Dialog(tk.Toplevel):
|
class Dialog(tk.Toplevel):
|
||||||
def __init__(self, master, app, title, modal=False):
|
def __init__(
|
||||||
|
self, master: tk.Widget, app: "Application", title: str, modal: bool = False
|
||||||
|
):
|
||||||
super().__init__(master)
|
super().__init__(master)
|
||||||
self.withdraw()
|
self.withdraw()
|
||||||
self.app = app
|
self.app = app
|
||||||
|
@ -30,7 +36,7 @@ class Dialog(tk.Toplevel):
|
||||||
self.grab_set()
|
self.grab_set()
|
||||||
self.wait_window()
|
self.wait_window()
|
||||||
|
|
||||||
def draw_spacer(self, row=None):
|
def draw_spacer(self, row: int = None):
|
||||||
frame = ttk.Frame(self.top)
|
frame = ttk.Frame(self.top)
|
||||||
frame.grid(row=row, sticky="nsew")
|
frame.grid(row=row, sticky="nsew")
|
||||||
frame.rowconfigure(0, weight=1)
|
frame.rowconfigure(0, weight=1)
|
||||||
|
|
|
@ -5,18 +5,24 @@ import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
from core.api.grpc import core_pb2
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.errors import show_grpc_error
|
from core.gui.errors import show_grpc_error
|
||||||
from core.gui.images import ImageEnum, Images
|
from core.gui.images import ImageEnum, Images
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
from core.gui.widgets import ConfigFrame
|
from core.gui.widgets import ConfigFrame
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.node import CanvasNode
|
||||||
|
|
||||||
|
|
||||||
class GlobalEmaneDialog(Dialog):
|
class GlobalEmaneDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: Any, app: "Application"):
|
||||||
super().__init__(master, app, "EMANE Configuration", modal=True)
|
super().__init__(master, app, "EMANE Configuration", modal=True)
|
||||||
self.config_frame = None
|
self.config_frame = None
|
||||||
self.draw()
|
self.draw()
|
||||||
|
@ -47,7 +53,14 @@ class GlobalEmaneDialog(Dialog):
|
||||||
|
|
||||||
|
|
||||||
class EmaneModelDialog(Dialog):
|
class EmaneModelDialog(Dialog):
|
||||||
def __init__(self, master, app, node, model, interface=None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
master: Any,
|
||||||
|
app: "Application",
|
||||||
|
node: core_pb2.Node,
|
||||||
|
model: str,
|
||||||
|
interface: int = None,
|
||||||
|
):
|
||||||
super().__init__(master, app, f"{node.name} {model} Configuration", modal=True)
|
super().__init__(master, app, f"{node.name} {model} Configuration", modal=True)
|
||||||
self.node = node
|
self.node = node
|
||||||
self.model = f"emane_{model}"
|
self.model = f"emane_{model}"
|
||||||
|
@ -91,7 +104,9 @@ class EmaneModelDialog(Dialog):
|
||||||
|
|
||||||
|
|
||||||
class EmaneConfigDialog(Dialog):
|
class EmaneConfigDialog(Dialog):
|
||||||
def __init__(self, master, app, canvas_node):
|
def __init__(
|
||||||
|
self, master: "Application", app: "Application", canvas_node: "CanvasNode"
|
||||||
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
master, app, f"{canvas_node.core_node.name} EMANE Configuration", modal=True
|
master, app, f"{canvas_node.core_node.name} EMANE Configuration", modal=True
|
||||||
)
|
)
|
||||||
|
@ -116,8 +131,6 @@ class EmaneConfigDialog(Dialog):
|
||||||
def draw_emane_configuration(self):
|
def draw_emane_configuration(self):
|
||||||
"""
|
"""
|
||||||
draw the main frame for emane configuration
|
draw the main frame for emane configuration
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
label = ttk.Label(
|
label = ttk.Label(
|
||||||
self.top,
|
self.top,
|
||||||
|
@ -143,8 +156,6 @@ class EmaneConfigDialog(Dialog):
|
||||||
def draw_emane_models(self):
|
def draw_emane_models(self):
|
||||||
"""
|
"""
|
||||||
create a combobox that has all the known emane models
|
create a combobox that has all the known emane models
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
frame = ttk.Frame(self.top)
|
frame = ttk.Frame(self.top)
|
||||||
frame.grid(sticky="ew", pady=PADY)
|
frame.grid(sticky="ew", pady=PADY)
|
||||||
|
@ -210,8 +221,6 @@ class EmaneConfigDialog(Dialog):
|
||||||
def click_model_config(self):
|
def click_model_config(self):
|
||||||
"""
|
"""
|
||||||
draw emane model configuration
|
draw emane model configuration
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
model_name = self.emane_model.get()
|
model_name = self.emane_model.get()
|
||||||
logging.info("configuring emane model: %s", model_name)
|
logging.info("configuring emane model: %s", model_name)
|
||||||
|
@ -220,12 +229,9 @@ class EmaneConfigDialog(Dialog):
|
||||||
)
|
)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def emane_model_change(self, event):
|
def emane_model_change(self, event: tk.Event):
|
||||||
"""
|
"""
|
||||||
update emane model options button
|
update emane model options button
|
||||||
|
|
||||||
:param event:
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
model_name = self.emane_model.get()
|
model_name = self.emane_model.get()
|
||||||
self.emane_model_button.config(text=f"{model_name} options")
|
self.emane_model_button.config(text=f"{model_name} options")
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from core.api.grpc import core_pb2
|
from core.api.grpc import core_pb2
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
from core.gui.widgets import CodeText, ListboxScroll
|
from core.gui.widgets import CodeText, ListboxScroll
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class HookDialog(Dialog):
|
class HookDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: Any, app: "Application"):
|
||||||
super().__init__(master, app, "Hook", modal=True)
|
super().__init__(master, app, "Hook", modal=True)
|
||||||
self.name = tk.StringVar()
|
self.name = tk.StringVar()
|
||||||
self.codetext = None
|
self.codetext = None
|
||||||
|
@ -62,11 +66,11 @@ class HookDialog(Dialog):
|
||||||
button = ttk.Button(frame, text="Cancel", command=lambda: self.destroy())
|
button = ttk.Button(frame, text="Cancel", command=lambda: self.destroy())
|
||||||
button.grid(row=0, column=1, sticky="ew")
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
def state_change(self, event):
|
def state_change(self, event: tk.Event):
|
||||||
state_name = self.state.get()
|
state_name = self.state.get()
|
||||||
self.name.set(f"{state_name.lower()}_hook.sh")
|
self.name.set(f"{state_name.lower()}_hook.sh")
|
||||||
|
|
||||||
def set(self, hook):
|
def set(self, hook: core_pb2.Hook):
|
||||||
self.hook = hook
|
self.hook = hook
|
||||||
self.name.set(hook.file)
|
self.name.set(hook.file)
|
||||||
self.codetext.text.delete(1.0, tk.END)
|
self.codetext.text.delete(1.0, tk.END)
|
||||||
|
@ -84,7 +88,7 @@ class HookDialog(Dialog):
|
||||||
|
|
||||||
|
|
||||||
class HooksDialog(Dialog):
|
class HooksDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "Hooks", modal=True)
|
super().__init__(master, app, "Hooks", modal=True)
|
||||||
self.listbox = None
|
self.listbox = None
|
||||||
self.edit_button = None
|
self.edit_button = None
|
||||||
|
@ -140,7 +144,7 @@ class HooksDialog(Dialog):
|
||||||
self.edit_button.config(state=tk.DISABLED)
|
self.edit_button.config(state=tk.DISABLED)
|
||||||
self.delete_button.config(state=tk.DISABLED)
|
self.delete_button.config(state=tk.DISABLED)
|
||||||
|
|
||||||
def select(self, event):
|
def select(self, event: tk.Event):
|
||||||
if self.listbox.curselection():
|
if self.listbox.curselection():
|
||||||
index = self.listbox.curselection()[0]
|
index = self.listbox.curselection()[0]
|
||||||
self.selected = self.listbox.get(index)
|
self.selected = self.listbox.get(index)
|
||||||
|
|
|
@ -4,14 +4,19 @@ link configuration
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Union
|
||||||
|
|
||||||
from core.api.grpc import core_pb2
|
from core.api.grpc import core_pb2
|
||||||
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.graph import CanvasGraph, CanvasEdge
|
||||||
|
|
||||||
def get_int(var):
|
|
||||||
|
def get_int(var: tk.StringVar) -> Union[int, None]:
|
||||||
value = var.get()
|
value = var.get()
|
||||||
if value != "":
|
if value != "":
|
||||||
return int(value)
|
return int(value)
|
||||||
|
@ -19,7 +24,7 @@ def get_int(var):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_float(var):
|
def get_float(var: tk.StringVar) -> Union[float, None]:
|
||||||
value = var.get()
|
value = var.get()
|
||||||
if value != "":
|
if value != "":
|
||||||
return float(value)
|
return float(value)
|
||||||
|
@ -28,7 +33,7 @@ def get_float(var):
|
||||||
|
|
||||||
|
|
||||||
class LinkConfigurationDialog(Dialog):
|
class LinkConfigurationDialog(Dialog):
|
||||||
def __init__(self, master, app, edge):
|
def __init__(self, master: "CanvasGraph", app: "Application", edge: "CanvasEdge"):
|
||||||
super().__init__(master, app, "Link Configuration", modal=True)
|
super().__init__(master, app, "Link Configuration", modal=True)
|
||||||
self.app = app
|
self.app = app
|
||||||
self.edge = edge
|
self.edge = edge
|
||||||
|
@ -103,7 +108,7 @@ class LinkConfigurationDialog(Dialog):
|
||||||
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
button.grid(row=0, column=1, sticky="ew")
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
def get_frame(self):
|
def get_frame(self) -> ttk.Frame:
|
||||||
frame = ttk.Frame(self.top)
|
frame = ttk.Frame(self.top)
|
||||||
frame.columnconfigure(1, weight=1)
|
frame.columnconfigure(1, weight=1)
|
||||||
if self.is_symmetric:
|
if self.is_symmetric:
|
||||||
|
@ -339,8 +344,6 @@ class LinkConfigurationDialog(Dialog):
|
||||||
def load_link_config(self):
|
def load_link_config(self):
|
||||||
"""
|
"""
|
||||||
populate link config to the table
|
populate link config to the table
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
width = self.app.canvas.itemcget(self.edge.id, "width")
|
width = self.app.canvas.itemcget(self.edge.id, "width")
|
||||||
self.width.set(width)
|
self.width.set(width)
|
||||||
|
|
|
@ -4,15 +4,21 @@ marker dialog
|
||||||
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
MARKER_THICKNESS = [3, 5, 8, 10]
|
MARKER_THICKNESS = [3, 5, 8, 10]
|
||||||
|
|
||||||
|
|
||||||
class MarkerDialog(Dialog):
|
class MarkerDialog(Dialog):
|
||||||
def __init__(self, master, app, initcolor="#000000"):
|
def __init__(
|
||||||
|
self, master: "Application", app: "Application", initcolor: str = "#000000"
|
||||||
|
):
|
||||||
super().__init__(master, app, "marker tool", modal=False)
|
super().__init__(master, app, "marker tool", modal=False)
|
||||||
self.app = app
|
self.app = app
|
||||||
self.color = initcolor
|
self.color = initcolor
|
||||||
|
@ -53,13 +59,13 @@ class MarkerDialog(Dialog):
|
||||||
for i in canvas.find_withtag("marker"):
|
for i in canvas.find_withtag("marker"):
|
||||||
canvas.delete(i)
|
canvas.delete(i)
|
||||||
|
|
||||||
def change_color(self, event):
|
def change_color(self, event: tk.Event):
|
||||||
color_picker = ColorPickerDialog(self, self.app, self.color)
|
color_picker = ColorPickerDialog(self, self.app, self.color)
|
||||||
color = color_picker.askcolor()
|
color = color_picker.askcolor()
|
||||||
event.widget.configure(background=color)
|
event.widget.configure(background=color)
|
||||||
self.color = color
|
self.color = color
|
||||||
|
|
||||||
def change_thickness(self, event):
|
def change_thickness(self, event: tk.Event):
|
||||||
self.radius = self.marker_thickness.get()
|
self.radius = self.marker_thickness.get()
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
mobility configuration
|
mobility configuration
|
||||||
"""
|
"""
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -10,9 +11,15 @@ from core.gui.errors import show_grpc_error
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
from core.gui.widgets import ConfigFrame
|
from core.gui.widgets import ConfigFrame
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.node import CanvasNode
|
||||||
|
|
||||||
|
|
||||||
class MobilityConfigDialog(Dialog):
|
class MobilityConfigDialog(Dialog):
|
||||||
def __init__(self, master, app, canvas_node):
|
def __init__(
|
||||||
|
self, master: "Application", app: "Application", canvas_node: "CanvasNode"
|
||||||
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
master,
|
master,
|
||||||
app,
|
app,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -9,11 +10,21 @@ from core.gui.errors import show_grpc_error
|
||||||
from core.gui.images import ImageEnum, Images
|
from core.gui.images import ImageEnum, Images
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.node import CanvasNode
|
||||||
|
|
||||||
ICON_SIZE = 16
|
ICON_SIZE = 16
|
||||||
|
|
||||||
|
|
||||||
class MobilityPlayer:
|
class MobilityPlayer:
|
||||||
def __init__(self, master, app, canvas_node, config):
|
def __init__(
|
||||||
|
self,
|
||||||
|
master: "Application",
|
||||||
|
app: "Application",
|
||||||
|
canvas_node: "CanvasNode",
|
||||||
|
config,
|
||||||
|
):
|
||||||
self.master = master
|
self.master = master
|
||||||
self.app = app
|
self.app = app
|
||||||
self.canvas_node = canvas_node
|
self.canvas_node = canvas_node
|
||||||
|
@ -57,7 +68,9 @@ class MobilityPlayer:
|
||||||
|
|
||||||
|
|
||||||
class MobilityPlayerDialog(Dialog):
|
class MobilityPlayerDialog(Dialog):
|
||||||
def __init__(self, master, app, canvas_node, config):
|
def __init__(
|
||||||
|
self, master: Any, app: "Application", canvas_node: "CanvasNode", config
|
||||||
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
master, app, f"{canvas_node.core_node.name} Mobility Player", modal=False
|
master, app, f"{canvas_node.core_node.name} Mobility Player", modal=False
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,6 +2,7 @@ import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui import nodeutils
|
from core.gui import nodeutils
|
||||||
from core.gui.appconfig import ICONS_PATH
|
from core.gui.appconfig import ICONS_PATH
|
||||||
|
@ -12,20 +13,32 @@ from core.gui.nodeutils import NodeUtils
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
from core.gui.widgets import ListboxScroll, image_chooser
|
from core.gui.widgets import ListboxScroll, image_chooser
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.node import CanvasNode
|
||||||
|
|
||||||
def mac_auto(is_auto, entry):
|
|
||||||
|
def mac_auto(is_auto: tk.BooleanVar, entry: ttk.Entry):
|
||||||
logging.info("mac auto clicked")
|
logging.info("mac auto clicked")
|
||||||
if is_auto.get():
|
if is_auto.get():
|
||||||
logging.info("disabling mac")
|
logging.info("disabling mac")
|
||||||
entry.var.set("")
|
entry.delete(0, tk.END)
|
||||||
|
entry.insert(tk.END, "")
|
||||||
entry.config(state=tk.DISABLED)
|
entry.config(state=tk.DISABLED)
|
||||||
else:
|
else:
|
||||||
entry.var.set("00:00:00:00:00:00")
|
entry.delete(0, tk.END)
|
||||||
|
entry.insert(tk.END, "00:00:00:00:00:00")
|
||||||
entry.config(state=tk.NORMAL)
|
entry.config(state=tk.NORMAL)
|
||||||
|
|
||||||
|
|
||||||
class InterfaceData:
|
class InterfaceData:
|
||||||
def __init__(self, is_auto, mac, ip4, ip6):
|
def __init__(
|
||||||
|
self,
|
||||||
|
is_auto: tk.BooleanVar,
|
||||||
|
mac: tk.StringVar,
|
||||||
|
ip4: tk.StringVar,
|
||||||
|
ip6: tk.StringVar,
|
||||||
|
):
|
||||||
self.is_auto = is_auto
|
self.is_auto = is_auto
|
||||||
self.mac = mac
|
self.mac = mac
|
||||||
self.ip4 = ip4
|
self.ip4 = ip4
|
||||||
|
@ -33,13 +46,11 @@ class InterfaceData:
|
||||||
|
|
||||||
|
|
||||||
class NodeConfigDialog(Dialog):
|
class NodeConfigDialog(Dialog):
|
||||||
def __init__(self, master, app, canvas_node):
|
def __init__(
|
||||||
|
self, master: "Application", app: "Application", canvas_node: "CanvasNode"
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
create an instance of node configuration
|
create an instance of node configuration
|
||||||
|
|
||||||
:param master: dialog master
|
|
||||||
:param coretk.app.Application: main app
|
|
||||||
:param coretk.graph.CanvasNode canvas_node: canvas node object
|
|
||||||
"""
|
"""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
master, app, f"{canvas_node.core_node.name} Configuration", modal=True
|
master, app, f"{canvas_node.core_node.name} Configuration", modal=True
|
||||||
|
@ -217,7 +228,7 @@ class NodeConfigDialog(Dialog):
|
||||||
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
button.grid(row=0, column=1, sticky="ew")
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
def click_emane_config(self, emane_model, interface_id):
|
def click_emane_config(self, emane_model: str, interface_id: int):
|
||||||
dialog = EmaneModelDialog(self, self.app, self.node, emane_model, interface_id)
|
dialog = EmaneModelDialog(self, self.app, self.node, emane_model, interface_id)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
|
@ -248,7 +259,7 @@ class NodeConfigDialog(Dialog):
|
||||||
self.canvas_node.redraw()
|
self.canvas_node.redraw()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def interface_select(self, event):
|
def interface_select(self, event: tk.Event):
|
||||||
listbox = event.widget
|
listbox = event.widget
|
||||||
cur = listbox.curselection()
|
cur = listbox.curselection()
|
||||||
if cur:
|
if cur:
|
||||||
|
|
|
@ -3,15 +3,26 @@ core node services
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import messagebox, ttk
|
from tkinter import messagebox, ttk
|
||||||
|
from typing import TYPE_CHECKING, Any, Set
|
||||||
|
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.dialogs.serviceconfig import ServiceConfigDialog
|
from core.gui.dialogs.serviceconfig import ServiceConfigDialog
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
from core.gui.widgets import CheckboxList, ListboxScroll
|
from core.gui.widgets import CheckboxList, ListboxScroll
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.node import CanvasNode
|
||||||
|
|
||||||
|
|
||||||
class NodeServiceDialog(Dialog):
|
class NodeServiceDialog(Dialog):
|
||||||
def __init__(self, master, app, canvas_node, services=None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
master: Any,
|
||||||
|
app: "Application",
|
||||||
|
canvas_node: "CanvasNode",
|
||||||
|
services: Set[str] = None,
|
||||||
|
):
|
||||||
title = f"{canvas_node.core_node.name} Services"
|
title = f"{canvas_node.core_node.name} Services"
|
||||||
super().__init__(master, app, title, modal=True)
|
super().__init__(master, app, title, modal=True)
|
||||||
self.app = app
|
self.app = app
|
||||||
|
@ -87,7 +98,7 @@ class NodeServiceDialog(Dialog):
|
||||||
# trigger group change
|
# trigger group change
|
||||||
self.groups.listbox.event_generate("<<ListboxSelect>>")
|
self.groups.listbox.event_generate("<<ListboxSelect>>")
|
||||||
|
|
||||||
def handle_group_change(self, event=None):
|
def handle_group_change(self, event: tk.Event = None):
|
||||||
selection = self.groups.listbox.curselection()
|
selection = self.groups.listbox.curselection()
|
||||||
if selection:
|
if selection:
|
||||||
index = selection[0]
|
index = selection[0]
|
||||||
|
@ -97,7 +108,7 @@ class NodeServiceDialog(Dialog):
|
||||||
checked = name in self.current_services
|
checked = name in self.current_services
|
||||||
self.services.add(name, checked)
|
self.services.add(name, checked)
|
||||||
|
|
||||||
def service_clicked(self, name, var):
|
def service_clicked(self, name: str, var: tk.IntVar):
|
||||||
if var.get() and name not in self.current_services:
|
if var.get() and name not in self.current_services:
|
||||||
self.current_services.add(name)
|
self.current_services.add(name)
|
||||||
elif not var.get() and name in self.current_services:
|
elif not var.get() and name in self.current_services:
|
||||||
|
@ -150,7 +161,7 @@ class NodeServiceDialog(Dialog):
|
||||||
checkbutton.invoke()
|
checkbutton.invoke()
|
||||||
return
|
return
|
||||||
|
|
||||||
def is_custom_service(self, service):
|
def is_custom_service(self, service: str) -> bool:
|
||||||
service_configs = self.app.core.service_configs
|
service_configs = self.app.core.service_configs
|
||||||
file_configs = self.app.core.file_configs
|
file_configs = self.app.core.file_configs
|
||||||
if self.node_id in service_configs and service in service_configs[self.node_id]:
|
if self.node_id in service_configs and service in service_configs[self.node_id]:
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.coreclient import Observer
|
from core.gui.coreclient import Observer
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
from core.gui.widgets import ListboxScroll
|
from core.gui.widgets import ListboxScroll
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class ObserverDialog(Dialog):
|
class ObserverDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "Observer Widgets", modal=True)
|
super().__init__(master, app, "Observer Widgets", modal=True)
|
||||||
self.observers = None
|
self.observers = None
|
||||||
self.save_button = None
|
self.save_button = None
|
||||||
|
@ -126,7 +130,7 @@ class ObserverDialog(Dialog):
|
||||||
self.save_button.config(state=tk.DISABLED)
|
self.save_button.config(state=tk.DISABLED)
|
||||||
self.delete_button.config(state=tk.DISABLED)
|
self.delete_button.config(state=tk.DISABLED)
|
||||||
|
|
||||||
def handle_observer_change(self, event):
|
def handle_observer_change(self, event: tk.Event):
|
||||||
selection = self.observers.curselection()
|
selection = self.observers.curselection()
|
||||||
if selection:
|
if selection:
|
||||||
self.selected_index = selection[0]
|
self.selected_index = selection[0]
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui import appconfig
|
from core.gui import appconfig
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class PreferencesDialog(Dialog):
|
class PreferencesDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "Preferences", modal=True)
|
super().__init__(master, app, "Preferences", modal=True)
|
||||||
preferences = self.app.guiconfig["preferences"]
|
preferences = self.app.guiconfig["preferences"]
|
||||||
self.editor = tk.StringVar(value=preferences["editor"])
|
self.editor = tk.StringVar(value=preferences["editor"])
|
||||||
|
@ -72,7 +76,7 @@ class PreferencesDialog(Dialog):
|
||||||
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
button.grid(row=0, column=1, sticky="ew")
|
button.grid(row=0, column=1, sticky="ew")
|
||||||
|
|
||||||
def theme_change(self, event):
|
def theme_change(self, event: tk.Event):
|
||||||
theme = self.theme.get()
|
theme = self.theme.get()
|
||||||
logging.info("changing theme: %s", theme)
|
logging.info("changing theme: %s", theme)
|
||||||
self.app.style.theme_use(theme)
|
self.app.style.theme_use(theme)
|
||||||
|
|
|
@ -1,18 +1,22 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.coreclient import CoreServer
|
from core.gui.coreclient import CoreServer
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
from core.gui.widgets import ListboxScroll
|
from core.gui.widgets import ListboxScroll
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
DEFAULT_NAME = "example"
|
DEFAULT_NAME = "example"
|
||||||
DEFAULT_ADDRESS = "127.0.0.1"
|
DEFAULT_ADDRESS = "127.0.0.1"
|
||||||
DEFAULT_PORT = 50051
|
DEFAULT_PORT = 50051
|
||||||
|
|
||||||
|
|
||||||
class ServersDialog(Dialog):
|
class ServersDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "CORE Servers", modal=True)
|
super().__init__(master, app, "CORE Servers", modal=True)
|
||||||
self.name = tk.StringVar(value=DEFAULT_NAME)
|
self.name = tk.StringVar(value=DEFAULT_NAME)
|
||||||
self.address = tk.StringVar(value=DEFAULT_ADDRESS)
|
self.address = tk.StringVar(value=DEFAULT_ADDRESS)
|
||||||
|
@ -155,7 +159,7 @@ class ServersDialog(Dialog):
|
||||||
self.save_button.config(state=tk.DISABLED)
|
self.save_button.config(state=tk.DISABLED)
|
||||||
self.delete_button.config(state=tk.DISABLED)
|
self.delete_button.config(state=tk.DISABLED)
|
||||||
|
|
||||||
def handle_server_change(self, event):
|
def handle_server_change(self, event: tk.Event):
|
||||||
selection = self.servers.curselection()
|
selection = self.servers.curselection()
|
||||||
if selection:
|
if selection:
|
||||||
self.selected_index = selection[0]
|
self.selected_index = selection[0]
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
"Service configuration dialog"
|
"""
|
||||||
|
Service configuration dialog
|
||||||
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Any, List
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -12,9 +15,14 @@ from core.gui.images import ImageEnum, Images
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
from core.gui.widgets import CodeText, ListboxScroll
|
from core.gui.widgets import CodeText, ListboxScroll
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class ServiceConfigDialog(Dialog):
|
class ServiceConfigDialog(Dialog):
|
||||||
def __init__(self, master, app, service_name, node_id):
|
def __init__(
|
||||||
|
self, master: Any, app: "Application", service_name: str, node_id: int
|
||||||
|
):
|
||||||
title = f"{service_name} Service"
|
title = f"{service_name} Service"
|
||||||
super().__init__(master, app, title, modal=True)
|
super().__init__(master, app, title, modal=True)
|
||||||
self.master = master
|
self.master = master
|
||||||
|
@ -225,7 +233,7 @@ class ServiceConfigDialog(Dialog):
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
tab.rowconfigure(i, weight=1)
|
tab.rowconfigure(i, weight=1)
|
||||||
self.notebook.add(tab, text="Startup/Shutdown")
|
self.notebook.add(tab, text="Startup/Shutdown")
|
||||||
|
commands = []
|
||||||
# tab 3
|
# tab 3
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
label_frame = None
|
label_frame = None
|
||||||
|
@ -345,7 +353,7 @@ class ServiceConfigDialog(Dialog):
|
||||||
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
button = ttk.Button(frame, text="Cancel", command=self.destroy)
|
||||||
button.grid(row=0, column=3, sticky="ew")
|
button.grid(row=0, column=3, sticky="ew")
|
||||||
|
|
||||||
def add_filename(self, event):
|
def add_filename(self, event: tk.Event):
|
||||||
# not worry about it for now
|
# not worry about it for now
|
||||||
return
|
return
|
||||||
frame_contains_button = event.widget.master
|
frame_contains_button = event.widget.master
|
||||||
|
@ -354,7 +362,7 @@ class ServiceConfigDialog(Dialog):
|
||||||
if filename not in combobox["values"]:
|
if filename not in combobox["values"]:
|
||||||
combobox["values"] += (filename,)
|
combobox["values"] += (filename,)
|
||||||
|
|
||||||
def delete_filename(self, event):
|
def delete_filename(self, event: tk.Event):
|
||||||
# not worry about it for now
|
# not worry about it for now
|
||||||
return
|
return
|
||||||
frame_comntains_button = event.widget.master
|
frame_comntains_button = event.widget.master
|
||||||
|
@ -364,7 +372,7 @@ class ServiceConfigDialog(Dialog):
|
||||||
combobox["values"] = tuple([x for x in combobox["values"] if x != filename])
|
combobox["values"] = tuple([x for x in combobox["values"] if x != filename])
|
||||||
combobox.set("")
|
combobox.set("")
|
||||||
|
|
||||||
def add_command(self, event):
|
def add_command(self, event: tk.Event):
|
||||||
frame_contains_button = event.widget.master
|
frame_contains_button = event.widget.master
|
||||||
listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox
|
listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox
|
||||||
command_to_add = frame_contains_button.grid_slaves(row=0, column=0)[0].get()
|
command_to_add = frame_contains_button.grid_slaves(row=0, column=0)[0].get()
|
||||||
|
@ -375,7 +383,7 @@ class ServiceConfigDialog(Dialog):
|
||||||
return
|
return
|
||||||
listbox.insert(tk.END, command_to_add)
|
listbox.insert(tk.END, command_to_add)
|
||||||
|
|
||||||
def update_entry(self, event):
|
def update_entry(self, event: tk.Event):
|
||||||
listbox = event.widget
|
listbox = event.widget
|
||||||
current_selection = listbox.curselection()
|
current_selection = listbox.curselection()
|
||||||
if len(current_selection) > 0:
|
if len(current_selection) > 0:
|
||||||
|
@ -386,7 +394,7 @@ class ServiceConfigDialog(Dialog):
|
||||||
entry.delete(0, "end")
|
entry.delete(0, "end")
|
||||||
entry.insert(0, cmd)
|
entry.insert(0, cmd)
|
||||||
|
|
||||||
def delete_command(self, event):
|
def delete_command(self, event: tk.Event):
|
||||||
button = event.widget
|
button = event.widget
|
||||||
frame_contains_button = button.master
|
frame_contains_button = button.master
|
||||||
listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox
|
listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox
|
||||||
|
@ -439,13 +447,13 @@ class ServiceConfigDialog(Dialog):
|
||||||
show_grpc_error(e)
|
show_grpc_error(e)
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def display_service_file_data(self, event):
|
def display_service_file_data(self, event: tk.Event):
|
||||||
combobox = event.widget
|
combobox = event.widget
|
||||||
filename = combobox.get()
|
filename = combobox.get()
|
||||||
self.service_file_data.text.delete(1.0, "end")
|
self.service_file_data.text.delete(1.0, "end")
|
||||||
self.service_file_data.text.insert("end", self.temp_service_files[filename])
|
self.service_file_data.text.insert("end", self.temp_service_files[filename])
|
||||||
|
|
||||||
def update_temp_service_file_data(self, event):
|
def update_temp_service_file_data(self, event: tk.Event):
|
||||||
scrolledtext = event.widget
|
scrolledtext = event.widget
|
||||||
filename = self.filename_combobox.get()
|
filename = self.filename_combobox.get()
|
||||||
self.temp_service_files[filename] = scrolledtext.get(1.0, "end")
|
self.temp_service_files[filename] = scrolledtext.get(1.0, "end")
|
||||||
|
@ -490,7 +498,9 @@ class ServiceConfigDialog(Dialog):
|
||||||
dialog = CopyServiceConfigDialog(self, self.app, self.node_id)
|
dialog = CopyServiceConfigDialog(self, self.app, self.node_id)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def append_commands(self, commands, listbox, to_add):
|
def append_commands(
|
||||||
|
self, commands: List[str], listbox: tk.Listbox, to_add: List[str]
|
||||||
|
):
|
||||||
for cmd in to_add:
|
for cmd in to_add:
|
||||||
commands.append(cmd)
|
commands.append(cmd)
|
||||||
listbox.insert(tk.END, cmd)
|
listbox.insert(tk.END, cmd)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -8,9 +9,12 @@ from core.gui.errors import show_grpc_error
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
from core.gui.widgets import ConfigFrame
|
from core.gui.widgets import ConfigFrame
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class SessionOptionsDialog(Dialog):
|
class SessionOptionsDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "Session Options", modal=True)
|
super().__init__(master, app, "Session Options", modal=True)
|
||||||
self.config_frame = None
|
self.config_frame = None
|
||||||
self.config = self.get_config()
|
self.config = self.get_config()
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING, Iterable
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -11,9 +12,12 @@ from core.gui.images import ImageEnum, Images
|
||||||
from core.gui.task import BackgroundTask
|
from core.gui.task import BackgroundTask
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class SessionsDialog(Dialog):
|
class SessionsDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "Sessions", modal=True)
|
super().__init__(master, app, "Sessions", modal=True)
|
||||||
self.selected = False
|
self.selected = False
|
||||||
self.selected_id = None
|
self.selected_id = None
|
||||||
|
@ -21,7 +25,7 @@ class SessionsDialog(Dialog):
|
||||||
self.sessions = self.get_sessions()
|
self.sessions = self.get_sessions()
|
||||||
self.draw()
|
self.draw()
|
||||||
|
|
||||||
def get_sessions(self):
|
def get_sessions(self) -> Iterable[core_pb2.SessionSummary]:
|
||||||
try:
|
try:
|
||||||
response = self.app.core.client.get_sessions()
|
response = self.app.core.client.get_sessions()
|
||||||
logging.info("sessions: %s", response)
|
logging.info("sessions: %s", response)
|
||||||
|
@ -40,7 +44,6 @@ class SessionsDialog(Dialog):
|
||||||
def draw_description(self):
|
def draw_description(self):
|
||||||
"""
|
"""
|
||||||
write a short description
|
write a short description
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
label = ttk.Label(
|
label = ttk.Label(
|
||||||
self.top,
|
self.top,
|
||||||
|
@ -129,7 +132,7 @@ class SessionsDialog(Dialog):
|
||||||
self.app.core.create_new_session()
|
self.app.core.create_new_session()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def click_select(self, event):
|
def click_select(self, event: tk.Event):
|
||||||
item = self.tree.selection()
|
item = self.tree.selection()
|
||||||
session_id = int(self.tree.item(item, "text"))
|
session_id = int(self.tree.item(item, "text"))
|
||||||
self.selected = True
|
self.selected = True
|
||||||
|
@ -138,8 +141,6 @@ class SessionsDialog(Dialog):
|
||||||
def click_connect(self):
|
def click_connect(self):
|
||||||
"""
|
"""
|
||||||
if no session is selected yet, create a new one else join that session
|
if no session is selected yet, create a new one else join that session
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
if self.selected and self.selected_id is not None:
|
if self.selected and self.selected_id is not None:
|
||||||
self.join_session(self.selected_id)
|
self.join_session(self.selected_id)
|
||||||
|
@ -152,8 +153,6 @@ class SessionsDialog(Dialog):
|
||||||
"""
|
"""
|
||||||
if no session is currently selected create a new session else shut the selected
|
if no session is currently selected create a new session else shut the selected
|
||||||
session down.
|
session down.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
if self.selected and self.selected_id is not None:
|
if self.selected and self.selected_id is not None:
|
||||||
self.shutdown_session(self.selected_id)
|
self.shutdown_session(self.selected_id)
|
||||||
|
@ -162,18 +161,18 @@ class SessionsDialog(Dialog):
|
||||||
else:
|
else:
|
||||||
logging.error("querysessiondrawing.py invalid state")
|
logging.error("querysessiondrawing.py invalid state")
|
||||||
|
|
||||||
def join_session(self, session_id):
|
def join_session(self, session_id: int):
|
||||||
self.app.statusbar.progress_bar.start(5)
|
self.app.statusbar.progress_bar.start(5)
|
||||||
task = BackgroundTask(self.app, self.app.core.join_session, args=(session_id,))
|
task = BackgroundTask(self.app, self.app.core.join_session, args=(session_id,))
|
||||||
task.start()
|
task.start()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def on_selected(self, event):
|
def on_selected(self, event: tk.Event):
|
||||||
item = self.tree.selection()
|
item = self.tree.selection()
|
||||||
sid = int(self.tree.item(item, "text"))
|
sid = int(self.tree.item(item, "text"))
|
||||||
self.join_session(sid)
|
self.join_session(sid)
|
||||||
|
|
||||||
def shutdown_session(self, sid):
|
def shutdown_session(self, sid: int):
|
||||||
self.app.core.stop_session(sid)
|
self.app.core.stop_session(sid)
|
||||||
self.click_new()
|
self.click_new()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
|
@ -3,6 +3,7 @@ shape input dialog
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import font, ttk
|
from tkinter import font, ttk
|
||||||
|
from typing import TYPE_CHECKING, List, Union
|
||||||
|
|
||||||
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
|
@ -10,12 +11,16 @@ from core.gui.graph import tags
|
||||||
from core.gui.graph.shapeutils import is_draw_shape, is_shape_text
|
from core.gui.graph.shapeutils import is_draw_shape, is_shape_text
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.shape import Shape
|
||||||
|
|
||||||
FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72]
|
FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72]
|
||||||
BORDER_WIDTH = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
BORDER_WIDTH = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||||
|
|
||||||
|
|
||||||
class ShapeDialog(Dialog):
|
class ShapeDialog(Dialog):
|
||||||
def __init__(self, master, app, shape):
|
def __init__(self, master: "Application", app: "Application", shape: "Shape"):
|
||||||
if is_draw_shape(shape.shape_type):
|
if is_draw_shape(shape.shape_type):
|
||||||
title = "Add Shape"
|
title = "Add Shape"
|
||||||
else:
|
else:
|
||||||
|
@ -162,10 +167,9 @@ class ShapeDialog(Dialog):
|
||||||
self.add_text()
|
self.add_text()
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def make_font(self):
|
def make_font(self) -> List[Union[int, str]]:
|
||||||
"""
|
"""
|
||||||
create font for text or shape label
|
create font for text or shape label
|
||||||
:return: list(font specifications)
|
|
||||||
"""
|
"""
|
||||||
size = int(self.font_size.get())
|
size = int(self.font_size.get())
|
||||||
text_font = [self.font.get(), size]
|
text_font = [self.font.get(), size]
|
||||||
|
@ -180,8 +184,6 @@ class ShapeDialog(Dialog):
|
||||||
def save_text(self):
|
def save_text(self):
|
||||||
"""
|
"""
|
||||||
save info related to text or shape label
|
save info related to text or shape label
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
data = self.shape.shape_data
|
data = self.shape.shape_data
|
||||||
data.text = self.shape_text.get()
|
data.text = self.shape_text.get()
|
||||||
|
@ -195,8 +197,6 @@ class ShapeDialog(Dialog):
|
||||||
def save_shape(self):
|
def save_shape(self):
|
||||||
"""
|
"""
|
||||||
save info related to shape
|
save info related to shape
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
data = self.shape.shape_data
|
data = self.shape.shape_data
|
||||||
data.fill_color = self.fill_color
|
data.fill_color = self.fill_color
|
||||||
|
@ -206,8 +206,6 @@ class ShapeDialog(Dialog):
|
||||||
def add_text(self):
|
def add_text(self):
|
||||||
"""
|
"""
|
||||||
add text to canvas
|
add text to canvas
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
text = self.shape_text.get()
|
text = self.shape_text.get()
|
||||||
text_font = self.make_font()
|
text_font = self.make_font()
|
||||||
|
|
|
@ -3,14 +3,18 @@ throughput dialog
|
||||||
"""
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
from core.gui.dialogs.colorpicker import ColorPickerDialog
|
||||||
from core.gui.dialogs.dialog import Dialog
|
from core.gui.dialogs.dialog import Dialog
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class ThroughputDialog(Dialog):
|
class ThroughputDialog(Dialog):
|
||||||
def __init__(self, master, app):
|
def __init__(self, master: "Application", app: "Application"):
|
||||||
super().__init__(master, app, "Throughput Config", modal=False)
|
super().__init__(master, app, "Throughput Config", modal=False)
|
||||||
self.app = app
|
self.app = app
|
||||||
self.canvas = app.canvas
|
self.canvas = app.canvas
|
||||||
|
|
|
@ -3,6 +3,7 @@ wlan configuration
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -11,9 +12,15 @@ from core.gui.errors import show_grpc_error
|
||||||
from core.gui.themes import PADX, PADY
|
from core.gui.themes import PADX, PADY
|
||||||
from core.gui.widgets import ConfigFrame
|
from core.gui.widgets import ConfigFrame
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.node import CanvasNode
|
||||||
|
|
||||||
|
|
||||||
class WlanConfigDialog(Dialog):
|
class WlanConfigDialog(Dialog):
|
||||||
def __init__(self, master, app, canvas_node):
|
def __init__(
|
||||||
|
self, master: "Application", app: "Application", canvas_node: "CanvasNode"
|
||||||
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
master, app, f"{canvas_node.core_node.name} Wlan Configuration", modal=True
|
master, app, f"{canvas_node.core_node.name} Wlan Configuration", modal=True
|
||||||
)
|
)
|
||||||
|
@ -38,8 +45,6 @@ class WlanConfigDialog(Dialog):
|
||||||
def draw_apply_buttons(self):
|
def draw_apply_buttons(self):
|
||||||
"""
|
"""
|
||||||
create node configuration options
|
create node configuration options
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
frame = ttk.Frame(self.top)
|
frame = ttk.Frame(self.top)
|
||||||
frame.grid(sticky="ew")
|
frame.grid(sticky="ew")
|
||||||
|
@ -55,8 +60,6 @@ class WlanConfigDialog(Dialog):
|
||||||
def click_apply(self):
|
def click_apply(self):
|
||||||
"""
|
"""
|
||||||
retrieve user's wlan configuration and store the new configuration values
|
retrieve user's wlan configuration and store the new configuration values
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
config = self.config_frame.parse_config()
|
config = self.config_frame.parse_config()
|
||||||
self.app.core.wlan_configs[self.node.id] = self.config
|
self.app.core.wlan_configs[self.node.id] = self.config
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
from tkinter import messagebox
|
from tkinter import messagebox
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
import grpc
|
||||||
|
|
||||||
|
|
||||||
def show_grpc_error(e):
|
def show_grpc_error(e: "grpc.RpcError"):
|
||||||
title = [x.capitalize() for x in e.code().name.lower().split("_")]
|
title = [x.capitalize() for x in e.code().name.lower().split("_")]
|
||||||
title = " ".join(title)
|
title = " ".join(title)
|
||||||
title = f"GRPC {title}"
|
title = f"GRPC {title}"
|
||||||
|
|
|
@ -1,19 +1,30 @@
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter.font import Font
|
from tkinter.font import Font
|
||||||
|
from typing import TYPE_CHECKING, Any, Tuple
|
||||||
|
|
||||||
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 NodeUtils
|
from core.gui.nodeutils import NodeUtils
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.graph.graph import CanvasGraph
|
||||||
|
|
||||||
TEXT_DISTANCE = 0.30
|
TEXT_DISTANCE = 0.30
|
||||||
EDGE_WIDTH = 3
|
EDGE_WIDTH = 3
|
||||||
EDGE_COLOR = "#ff0000"
|
EDGE_COLOR = "#ff0000"
|
||||||
|
|
||||||
|
|
||||||
class CanvasWirelessEdge:
|
class CanvasWirelessEdge:
|
||||||
def __init__(self, token, position, src, dst, canvas):
|
def __init__(
|
||||||
|
self,
|
||||||
|
token: Tuple[Any, ...],
|
||||||
|
position: Tuple[float, float, float, float],
|
||||||
|
src: int,
|
||||||
|
dst: int,
|
||||||
|
canvas: "CanvasGraph",
|
||||||
|
):
|
||||||
self.token = token
|
self.token = token
|
||||||
self.src = src
|
self.src = src
|
||||||
self.dst = dst
|
self.dst = dst
|
||||||
|
@ -31,15 +42,17 @@ class CanvasEdge:
|
||||||
Canvas edge class
|
Canvas edge class
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, x1, y1, x2, y2, src, canvas):
|
def __init__(
|
||||||
|
self,
|
||||||
|
x1: float,
|
||||||
|
y1: float,
|
||||||
|
x2: float,
|
||||||
|
y2: float,
|
||||||
|
src: int,
|
||||||
|
canvas: "CanvasGraph",
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Create an instance of canvas edge object
|
Create an instance of canvas edge object
|
||||||
:param int x1: source x-coord
|
|
||||||
:param int y1: source y-coord
|
|
||||||
:param int x2: destination x-coord
|
|
||||||
:param int y2: destination y-coord
|
|
||||||
:param int src: source id
|
|
||||||
:param coretk.graph.graph.GraphCanvas canvas: canvas object
|
|
||||||
"""
|
"""
|
||||||
self.src = src
|
self.src = src
|
||||||
self.dst = None
|
self.dst = None
|
||||||
|
@ -66,7 +79,7 @@ class CanvasEdge:
|
||||||
self.link = link
|
self.link = link
|
||||||
self.draw_labels()
|
self.draw_labels()
|
||||||
|
|
||||||
def get_coordinates(self):
|
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
|
||||||
|
@ -78,7 +91,7 @@ class CanvasEdge:
|
||||||
y2 = y2 - uy
|
y2 = y2 - uy
|
||||||
return x1, y1, x2, y2
|
return x1, y1, x2, y2
|
||||||
|
|
||||||
def get_midpoint(self):
|
def get_midpoint(self) -> [float, float]:
|
||||||
x1, y1, x2, y2 = self.canvas.coords(self.id)
|
x1, y1, x2, y2 = self.canvas.coords(self.id)
|
||||||
x = (x1 + x2) / 2
|
x = (x1 + x2) / 2
|
||||||
y = (y1 + y2) / 2
|
y = (y1 + y2) / 2
|
||||||
|
@ -118,8 +131,6 @@ class CanvasEdge:
|
||||||
def update_labels(self):
|
def update_labels(self):
|
||||||
"""
|
"""
|
||||||
Move edge labels based on current position.
|
Move edge labels based on current position.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
x1, y1, x2, y2 = self.get_coordinates()
|
x1, y1, x2, y2 = self.get_coordinates()
|
||||||
self.canvas.coords(self.text_src, x1, y1)
|
self.canvas.coords(self.text_src, x1, y1)
|
||||||
|
@ -128,7 +139,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):
|
def set_throughput(self, throughput: float):
|
||||||
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:
|
||||||
|
@ -147,7 +158,7 @@ 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):
|
def complete(self, dst: int):
|
||||||
self.dst = dst
|
self.dst = dst
|
||||||
self.token = tuple(sorted((self.src, self.dst)))
|
self.token = tuple(sorted((self.src, self.dst)))
|
||||||
x, y = self.canvas.coords(self.dst)
|
x, y = self.canvas.coords(self.dst)
|
||||||
|
@ -157,7 +168,7 @@ class CanvasEdge:
|
||||||
self.canvas.tag_raise(self.src)
|
self.canvas.tag_raise(self.src)
|
||||||
self.canvas.tag_raise(self.dst)
|
self.canvas.tag_raise(self.dst)
|
||||||
|
|
||||||
def is_wireless(self):
|
def is_wireless(self) -> [bool, 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
|
||||||
|
@ -183,7 +194,6 @@ class CanvasEdge:
|
||||||
dst_node.add_antenna()
|
dst_node.add_antenna()
|
||||||
elif not is_src_wireless and is_dst_wireless:
|
elif not is_src_wireless and is_dst_wireless:
|
||||||
src_node.add_antenna()
|
src_node.add_antenna()
|
||||||
# TODO: remove this? dont allow linking wireless nodes?
|
|
||||||
else:
|
else:
|
||||||
src_node.add_antenna()
|
src_node.add_antenna()
|
||||||
|
|
||||||
|
@ -199,7 +209,7 @@ class CanvasEdge:
|
||||||
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):
|
def create_context(self, event: tk.Event):
|
||||||
logging.debug("create link context")
|
logging.debug("create link context")
|
||||||
context = tk.Menu(self.canvas)
|
context = tk.Menu(self.canvas)
|
||||||
themes.style_menu(context)
|
themes.style_menu(context)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
from typing import TYPE_CHECKING, List, Tuple
|
||||||
|
|
||||||
from PIL import Image, ImageTk
|
from PIL import Image, ImageTk
|
||||||
|
|
||||||
|
@ -15,12 +16,18 @@ from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker
|
||||||
from core.gui.images import Images
|
from core.gui.images import Images
|
||||||
from core.gui.nodeutils import NodeUtils
|
from core.gui.nodeutils import NodeUtils
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.coreclient import CoreClient
|
||||||
|
|
||||||
ZOOM_IN = 1.1
|
ZOOM_IN = 1.1
|
||||||
ZOOM_OUT = 0.9
|
ZOOM_OUT = 0.9
|
||||||
|
|
||||||
|
|
||||||
class CanvasGraph(tk.Canvas):
|
class CanvasGraph(tk.Canvas):
|
||||||
def __init__(self, master, core, width, height):
|
def __init__(
|
||||||
|
self, master: "Application", core: "CoreClient", width: int, height: int
|
||||||
|
):
|
||||||
super().__init__(master, highlightthickness=0, background="#cccccc")
|
super().__init__(master, highlightthickness=0, background="#cccccc")
|
||||||
self.app = master
|
self.app = master
|
||||||
self.core = core
|
self.core = core
|
||||||
|
@ -67,7 +74,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.draw_canvas()
|
self.draw_canvas()
|
||||||
self.draw_grid()
|
self.draw_grid()
|
||||||
|
|
||||||
def draw_canvas(self, dimensions=None):
|
def draw_canvas(self, dimensions: Tuple[int, int] = None):
|
||||||
if self.grid is not None:
|
if self.grid is not None:
|
||||||
self.delete(self.grid)
|
self.delete(self.grid)
|
||||||
if not dimensions:
|
if not dimensions:
|
||||||
|
@ -84,13 +91,11 @@ class CanvasGraph(tk.Canvas):
|
||||||
)
|
)
|
||||||
self.configure(scrollregion=self.bbox(tk.ALL))
|
self.configure(scrollregion=self.bbox(tk.ALL))
|
||||||
|
|
||||||
def reset_and_redraw(self, session):
|
def reset_and_redraw(self, session: core_pb2.Session):
|
||||||
"""
|
"""
|
||||||
Reset the private variables CanvasGraph object, redraw nodes given the new grpc
|
Reset the private variables CanvasGraph object, redraw nodes given the new grpc
|
||||||
client.
|
client.
|
||||||
|
|
||||||
:param core.api.grpc.core_pb2.Session session: session to draw
|
:param core.api.grpc.core_pb2.Session session: session to draw
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
# hide context
|
# hide context
|
||||||
self.hide_context()
|
self.hide_context()
|
||||||
|
@ -114,8 +119,6 @@ class CanvasGraph(tk.Canvas):
|
||||||
def setup_bindings(self):
|
def setup_bindings(self):
|
||||||
"""
|
"""
|
||||||
Bind any mouse events or hot keys to the matching action
|
Bind any mouse events or hot keys to the matching action
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.bind("<ButtonPress-1>", self.click_press)
|
self.bind("<ButtonPress-1>", self.click_press)
|
||||||
self.bind("<ButtonRelease-1>", self.click_release)
|
self.bind("<ButtonRelease-1>", self.click_release)
|
||||||
|
@ -135,28 +138,28 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.context.unpost()
|
self.context.unpost()
|
||||||
self.context = None
|
self.context = None
|
||||||
|
|
||||||
def get_actual_coords(self, x, y):
|
def get_actual_coords(self, x: float, y: float) -> [float, float]:
|
||||||
actual_x = (x - self.offset[0]) / self.ratio
|
actual_x = (x - self.offset[0]) / self.ratio
|
||||||
actual_y = (y - self.offset[1]) / self.ratio
|
actual_y = (y - self.offset[1]) / self.ratio
|
||||||
return actual_x, actual_y
|
return actual_x, actual_y
|
||||||
|
|
||||||
def get_scaled_coords(self, x, y):
|
def get_scaled_coords(self, x: float, y: float) -> [float, float]:
|
||||||
scaled_x = (x * self.ratio) + self.offset[0]
|
scaled_x = (x * self.ratio) + self.offset[0]
|
||||||
scaled_y = (y * self.ratio) + self.offset[1]
|
scaled_y = (y * self.ratio) + self.offset[1]
|
||||||
return scaled_x, scaled_y
|
return scaled_x, scaled_y
|
||||||
|
|
||||||
def inside_canvas(self, x, y):
|
def inside_canvas(self, x: float, y: float) -> [bool, bool]:
|
||||||
x1, y1, x2, y2 = self.bbox(self.grid)
|
x1, y1, x2, y2 = self.bbox(self.grid)
|
||||||
valid_x = x1 <= x <= x2
|
valid_x = x1 <= x <= x2
|
||||||
valid_y = y1 <= y <= y2
|
valid_y = y1 <= y <= y2
|
||||||
return valid_x and valid_y
|
return valid_x and valid_y
|
||||||
|
|
||||||
def valid_position(self, x1, y1, x2, y2):
|
def valid_position(self, x1: int, y1: int, x2: int, y2: int) -> [bool, bool]:
|
||||||
valid_topleft = self.inside_canvas(x1, y1)
|
valid_topleft = self.inside_canvas(x1, y1)
|
||||||
valid_bottomright = self.inside_canvas(x2, y2)
|
valid_bottomright = self.inside_canvas(x2, y2)
|
||||||
return valid_topleft and valid_bottomright
|
return valid_topleft and valid_bottomright
|
||||||
|
|
||||||
def set_throughputs(self, throughputs_event):
|
def set_throughputs(self, throughputs_event: core_pb2.ThroughputsEvent):
|
||||||
for interface_throughput in throughputs_event.interface_throughputs:
|
for interface_throughput in throughputs_event.interface_throughputs:
|
||||||
node_id = interface_throughput.node_id
|
node_id = interface_throughput.node_id
|
||||||
interface_id = interface_throughput.interface_id
|
interface_id = interface_throughput.interface_id
|
||||||
|
@ -174,8 +177,6 @@ class CanvasGraph(tk.Canvas):
|
||||||
def draw_grid(self):
|
def draw_grid(self):
|
||||||
"""
|
"""
|
||||||
Create grid.
|
Create grid.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
width, height = self.width_and_height()
|
width, height = self.width_and_height()
|
||||||
width = int(width)
|
width = int(width)
|
||||||
|
@ -187,13 +188,9 @@ 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, dst):
|
def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode):
|
||||||
"""
|
"""
|
||||||
add a wireless edge between 2 canvas nodes
|
add a wireless edge between 2 canvas nodes
|
||||||
|
|
||||||
:param CanvasNode src: source node
|
|
||||||
:param CanvasNode dst: destination node
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
token = tuple(sorted((src.id, dst.id)))
|
token = tuple(sorted((src.id, dst.id)))
|
||||||
x1, y1 = self.coords(src.id)
|
x1, y1 = self.coords(src.id)
|
||||||
|
@ -206,18 +203,16 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.tag_raise(src.id)
|
self.tag_raise(src.id)
|
||||||
self.tag_raise(dst.id)
|
self.tag_raise(dst.id)
|
||||||
|
|
||||||
def delete_wireless_edge(self, src, dst):
|
def delete_wireless_edge(self, src: CanvasNode, dst: CanvasNode):
|
||||||
token = tuple(sorted((src.id, dst.id)))
|
token = tuple(sorted((src.id, dst.id)))
|
||||||
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)
|
||||||
|
|
||||||
def draw_session(self, session):
|
def draw_session(self, session: core_pb2.Session):
|
||||||
"""
|
"""
|
||||||
Draw existing session.
|
Draw existing session.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
# draw existing nodes
|
# draw existing nodes
|
||||||
for core_node in session.nodes:
|
for core_node in session.nodes:
|
||||||
|
@ -296,25 +291,17 @@ class CanvasGraph(tk.Canvas):
|
||||||
for edge in self.edges.values():
|
for edge in self.edges.values():
|
||||||
edge.reset()
|
edge.reset()
|
||||||
|
|
||||||
def canvas_xy(self, event):
|
def canvas_xy(self, event: tk.Event) -> [float, float]:
|
||||||
"""
|
"""
|
||||||
Convert window coordinate to canvas coordinate
|
Convert window coordinate to canvas coordinate
|
||||||
|
|
||||||
:param event:
|
|
||||||
:rtype: (int, int)
|
|
||||||
:return: x, y canvas coordinate
|
|
||||||
"""
|
"""
|
||||||
x = self.canvasx(event.x)
|
x = self.canvasx(event.x)
|
||||||
y = self.canvasy(event.y)
|
y = self.canvasy(event.y)
|
||||||
return x, y
|
return x, y
|
||||||
|
|
||||||
def get_selected(self, event):
|
def get_selected(self, event: tk.Event) -> int:
|
||||||
"""
|
"""
|
||||||
Retrieve the item id that is on the mouse position
|
Retrieve the item id that is on the mouse position
|
||||||
|
|
||||||
:param event: mouse event
|
|
||||||
:rtype: int
|
|
||||||
:return: the item that the mouse point to
|
|
||||||
"""
|
"""
|
||||||
x, y = self.canvas_xy(event)
|
x, y = self.canvas_xy(event)
|
||||||
overlapping = self.find_overlapping(x, y, x, y)
|
overlapping = self.find_overlapping(x, y, x, y)
|
||||||
|
@ -332,12 +319,9 @@ class CanvasGraph(tk.Canvas):
|
||||||
|
|
||||||
return selected
|
return selected
|
||||||
|
|
||||||
def click_release(self, event):
|
def click_release(self, event: tk.Event):
|
||||||
"""
|
"""
|
||||||
Draw a node or finish drawing an edge according to the current graph mode
|
Draw a node or finish drawing an edge according to the current graph mode
|
||||||
|
|
||||||
:param event: mouse event
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
logging.debug("click release")
|
logging.debug("click release")
|
||||||
x, y = self.canvas_xy(event)
|
x, y = self.canvas_xy(event)
|
||||||
|
@ -380,7 +364,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.mode = GraphMode.NODE
|
self.mode = GraphMode.NODE
|
||||||
self.selected = None
|
self.selected = None
|
||||||
|
|
||||||
def handle_edge_release(self, event):
|
def handle_edge_release(self, event: tk.Event):
|
||||||
edge = self.drawing_edge
|
edge = self.drawing_edge
|
||||||
self.drawing_edge = None
|
self.drawing_edge = None
|
||||||
|
|
||||||
|
@ -417,7 +401,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
node_dst.edges.add(edge)
|
node_dst.edges.add(edge)
|
||||||
self.core.create_link(edge, node_src, node_dst)
|
self.core.create_link(edge, node_src, node_dst)
|
||||||
|
|
||||||
def select_object(self, object_id, choose_multiple=False):
|
def select_object(self, object_id: int, choose_multiple: bool = False):
|
||||||
"""
|
"""
|
||||||
create a bounding box when a node is selected
|
create a bounding box when a node is selected
|
||||||
"""
|
"""
|
||||||
|
@ -441,19 +425,17 @@ class CanvasGraph(tk.Canvas):
|
||||||
def clear_selection(self):
|
def clear_selection(self):
|
||||||
"""
|
"""
|
||||||
Clear current selection boxes.
|
Clear current selection boxes.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
for _id in self.selection.values():
|
for _id in self.selection.values():
|
||||||
self.delete(_id)
|
self.delete(_id)
|
||||||
self.selection.clear()
|
self.selection.clear()
|
||||||
|
|
||||||
def move_selection(self, object_id, x_offset, y_offset):
|
def move_selection(self, object_id: int, x_offset: float, y_offset: float):
|
||||||
select_id = self.selection.get(object_id)
|
select_id = self.selection.get(object_id)
|
||||||
if select_id is not None:
|
if select_id is not None:
|
||||||
self.move(select_id, x_offset, y_offset)
|
self.move(select_id, x_offset, y_offset)
|
||||||
|
|
||||||
def delete_selection_objects(self):
|
def delete_selection_objects(self) -> List[CanvasNode]:
|
||||||
edges = set()
|
edges = set()
|
||||||
nodes = []
|
nodes = []
|
||||||
for object_id in self.selection:
|
for object_id in self.selection:
|
||||||
|
@ -499,7 +481,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.selection.clear()
|
self.selection.clear()
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
def zoom(self, event, factor=None):
|
def zoom(self, event: tk.Event, factor: float = None):
|
||||||
if not factor:
|
if not factor:
|
||||||
factor = ZOOM_IN if event.delta > 0 else ZOOM_OUT
|
factor = ZOOM_IN if event.delta > 0 else ZOOM_OUT
|
||||||
event.x, event.y = self.canvasx(event.x), self.canvasy(event.y)
|
event.x, event.y = self.canvasx(event.x), self.canvasy(event.y)
|
||||||
|
@ -517,12 +499,9 @@ class CanvasGraph(tk.Canvas):
|
||||||
if self.wallpaper:
|
if self.wallpaper:
|
||||||
self.redraw_wallpaper()
|
self.redraw_wallpaper()
|
||||||
|
|
||||||
def click_press(self, event):
|
def click_press(self, event: tk.Event):
|
||||||
"""
|
"""
|
||||||
Start drawing an edge if mouse click is on a node
|
Start drawing an edge if mouse click is on a node
|
||||||
|
|
||||||
:param event: mouse event
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
x, y = self.canvas_xy(event)
|
x, y = self.canvas_xy(event)
|
||||||
if not self.inside_canvas(x, y):
|
if not self.inside_canvas(x, y):
|
||||||
|
@ -581,7 +560,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.select_box = shape
|
self.select_box = shape
|
||||||
self.clear_selection()
|
self.clear_selection()
|
||||||
|
|
||||||
def ctrl_click(self, event):
|
def ctrl_click(self, event: tk.Event):
|
||||||
# update cursor location
|
# update cursor location
|
||||||
x, y = self.canvas_xy(event)
|
x, y = self.canvas_xy(event)
|
||||||
if not self.inside_canvas(x, y):
|
if not self.inside_canvas(x, y):
|
||||||
|
@ -599,12 +578,9 @@ class CanvasGraph(tk.Canvas):
|
||||||
):
|
):
|
||||||
self.select_object(selected, choose_multiple=True)
|
self.select_object(selected, choose_multiple=True)
|
||||||
|
|
||||||
def click_motion(self, event):
|
def click_motion(self, event: tk.Event):
|
||||||
"""
|
"""
|
||||||
Redraw drawing edge according to the current position of the mouse
|
Redraw drawing edge according to the current position of the mouse
|
||||||
|
|
||||||
:param event: mouse event
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
x, y = self.canvas_xy(event)
|
x, y = self.canvas_xy(event)
|
||||||
if not self.inside_canvas(x, y):
|
if not self.inside_canvas(x, y):
|
||||||
|
@ -658,7 +634,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
if self.select_box and self.mode == GraphMode.SELECT:
|
if self.select_box and self.mode == GraphMode.SELECT:
|
||||||
self.select_box.shape_motion(x, y)
|
self.select_box.shape_motion(x, y)
|
||||||
|
|
||||||
def click_context(self, event):
|
def click_context(self, event: tk.Event):
|
||||||
logging.info("context event: %s", self.context)
|
logging.info("context event: %s", self.context)
|
||||||
if not self.context:
|
if not self.context:
|
||||||
selected = self.get_selected(event)
|
selected = self.get_selected(event)
|
||||||
|
@ -670,24 +646,22 @@ class CanvasGraph(tk.Canvas):
|
||||||
else:
|
else:
|
||||||
self.hide_context()
|
self.hide_context()
|
||||||
|
|
||||||
def press_delete(self, event):
|
def press_delete(self, event: tk.Event):
|
||||||
"""
|
"""
|
||||||
delete selected nodes and any data that relates to it
|
delete selected nodes and any data that relates to it
|
||||||
:param event:
|
|
||||||
:return:
|
|
||||||
"""
|
"""
|
||||||
logging.debug("press delete key")
|
logging.debug("press delete key")
|
||||||
nodes = self.delete_selection_objects()
|
nodes = self.delete_selection_objects()
|
||||||
self.core.delete_graph_nodes(nodes)
|
self.core.delete_graph_nodes(nodes)
|
||||||
|
|
||||||
def double_click(self, event):
|
def double_click(self, event: tk.Event):
|
||||||
selected = self.get_selected(event)
|
selected = self.get_selected(event)
|
||||||
if selected is not None and selected in self.shapes:
|
if selected is not None and selected in self.shapes:
|
||||||
shape = self.shapes[selected]
|
shape = self.shapes[selected]
|
||||||
dialog = ShapeDialog(self.app, self.app, shape)
|
dialog = ShapeDialog(self.app, self.app, shape)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def add_node(self, x, y):
|
def add_node(self, x: float, y: float) -> CanvasNode:
|
||||||
if self.selected is None or self.selected in self.shapes:
|
if self.selected is None or self.selected in self.shapes:
|
||||||
actual_x, actual_y = self.get_actual_coords(x, y)
|
actual_x, actual_y = self.get_actual_coords(x, y)
|
||||||
core_node = self.core.create_node(
|
core_node = self.core.create_node(
|
||||||
|
@ -701,26 +675,25 @@ class CanvasGraph(tk.Canvas):
|
||||||
def width_and_height(self):
|
def width_and_height(self):
|
||||||
"""
|
"""
|
||||||
retrieve canvas width and height in pixels
|
retrieve canvas width and height in pixels
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
x0, y0, x1, y1 = self.coords(self.grid)
|
x0, y0, x1, y1 = self.coords(self.grid)
|
||||||
canvas_w = abs(x0 - x1)
|
canvas_w = abs(x0 - x1)
|
||||||
canvas_h = abs(y0 - y1)
|
canvas_h = abs(y0 - y1)
|
||||||
return canvas_w, canvas_h
|
return canvas_w, canvas_h
|
||||||
|
|
||||||
def get_wallpaper_image(self):
|
def get_wallpaper_image(self) -> Image.Image:
|
||||||
width = int(self.wallpaper.width * self.ratio)
|
width = int(self.wallpaper.width * self.ratio)
|
||||||
height = int(self.wallpaper.height * self.ratio)
|
height = int(self.wallpaper.height * self.ratio)
|
||||||
image = self.wallpaper.resize((width, height), Image.ANTIALIAS)
|
image = self.wallpaper.resize((width, height), Image.ANTIALIAS)
|
||||||
return image
|
return image
|
||||||
|
|
||||||
def draw_wallpaper(self, image, x=None, y=None):
|
def draw_wallpaper(
|
||||||
|
self, image: ImageTk.PhotoImage, x: float = None, y: float = None
|
||||||
|
):
|
||||||
if x is None and y is None:
|
if x is None and y is None:
|
||||||
x1, y1, x2, y2 = self.bbox(self.grid)
|
x1, y1, x2, y2 = self.bbox(self.grid)
|
||||||
x = (x1 + x2) / 2
|
x = (x1 + x2) / 2
|
||||||
y = (y1 + y2) / 2
|
y = (y1 + y2) / 2
|
||||||
|
|
||||||
self.wallpaper_id = self.create_image((x, y), image=image, tags=tags.WALLPAPER)
|
self.wallpaper_id = self.create_image((x, y), image=image, tags=tags.WALLPAPER)
|
||||||
self.wallpaper_drawn = image
|
self.wallpaper_drawn = image
|
||||||
|
|
||||||
|
@ -748,8 +721,6 @@ class CanvasGraph(tk.Canvas):
|
||||||
def wallpaper_center(self):
|
def wallpaper_center(self):
|
||||||
"""
|
"""
|
||||||
place the image at the center of canvas
|
place the image at the center of canvas
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.delete(self.wallpaper_id)
|
self.delete(self.wallpaper_id)
|
||||||
|
|
||||||
|
@ -773,8 +744,6 @@ class CanvasGraph(tk.Canvas):
|
||||||
def wallpaper_scaled(self):
|
def wallpaper_scaled(self):
|
||||||
"""
|
"""
|
||||||
scale image based on canvas dimension
|
scale image based on canvas dimension
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.delete(self.wallpaper_id)
|
self.delete(self.wallpaper_id)
|
||||||
canvas_w, canvas_h = self.width_and_height()
|
canvas_w, canvas_h = self.width_and_height()
|
||||||
|
@ -788,7 +757,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.redraw_canvas((image.width(), image.height()))
|
self.redraw_canvas((image.width(), image.height()))
|
||||||
self.draw_wallpaper(image)
|
self.draw_wallpaper(image)
|
||||||
|
|
||||||
def redraw_canvas(self, dimensions=None):
|
def redraw_canvas(self, dimensions: Tuple[int, int] = None):
|
||||||
logging.info("redrawing canvas to dimensions: %s", dimensions)
|
logging.info("redrawing canvas to dimensions: %s", dimensions)
|
||||||
|
|
||||||
# reset scale and move back to original position
|
# reset scale and move back to original position
|
||||||
|
@ -836,7 +805,7 @@ class CanvasGraph(tk.Canvas):
|
||||||
else:
|
else:
|
||||||
self.itemconfig(tags.GRIDLINE, state=tk.HIDDEN)
|
self.itemconfig(tags.GRIDLINE, state=tk.HIDDEN)
|
||||||
|
|
||||||
def set_wallpaper(self, filename):
|
def set_wallpaper(self, filename: str):
|
||||||
logging.info("setting wallpaper: %s", filename)
|
logging.info("setting wallpaper: %s", filename)
|
||||||
if filename:
|
if filename:
|
||||||
img = Image.open(filename)
|
img = Image.open(filename)
|
||||||
|
@ -849,16 +818,12 @@ class CanvasGraph(tk.Canvas):
|
||||||
self.wallpaper = None
|
self.wallpaper = None
|
||||||
self.wallpaper_file = None
|
self.wallpaper_file = None
|
||||||
|
|
||||||
def is_selection_mode(self):
|
def is_selection_mode(self) -> bool:
|
||||||
return self.mode == GraphMode.SELECT
|
return self.mode == GraphMode.SELECT
|
||||||
|
|
||||||
def create_edge(self, source, dest):
|
def create_edge(self, source: CanvasNode, dest: CanvasNode):
|
||||||
"""
|
"""
|
||||||
create an edge between source node and destination node
|
create an edge between source node and destination node
|
||||||
|
|
||||||
:param CanvasNode source: source node
|
|
||||||
:param CanvasNode dest: destination node
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
if (source.id, dest.id) not in self.edges:
|
if (source.id, dest.id) not in self.edges:
|
||||||
pos0 = source.core_node.position
|
pos0 = source.core_node.position
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import font
|
from tkinter import font
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
|
||||||
|
@ -16,11 +17,22 @@ from core.gui.graph import tags
|
||||||
from core.gui.graph.tooltip import CanvasTooltip
|
from core.gui.graph.tooltip import CanvasTooltip
|
||||||
from core.gui.nodeutils import NodeUtils
|
from core.gui.nodeutils import NodeUtils
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from PIL.ImageTk import PhotoImage
|
||||||
|
|
||||||
NODE_TEXT_OFFSET = 5
|
NODE_TEXT_OFFSET = 5
|
||||||
|
|
||||||
|
|
||||||
class CanvasNode:
|
class CanvasNode:
|
||||||
def __init__(self, app, x, y, core_node, image):
|
def __init__(
|
||||||
|
self,
|
||||||
|
app: "Application",
|
||||||
|
x: float,
|
||||||
|
y: float,
|
||||||
|
core_node: core_pb2.Node,
|
||||||
|
image: "PhotoImage",
|
||||||
|
):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.canvas = app.canvas
|
self.canvas = app.canvas
|
||||||
self.image = image
|
self.image = image
|
||||||
|
@ -70,8 +82,6 @@ class CanvasNode:
|
||||||
def delete_antenna(self):
|
def delete_antenna(self):
|
||||||
"""
|
"""
|
||||||
delete one antenna
|
delete one antenna
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
if self.antennae:
|
if self.antennae:
|
||||||
antenna_id = self.antennae.pop()
|
antenna_id = self.antennae.pop()
|
||||||
|
@ -80,8 +90,6 @@ class CanvasNode:
|
||||||
def delete_antennae(self):
|
def delete_antennae(self):
|
||||||
"""
|
"""
|
||||||
delete all antennas
|
delete all antennas
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
for antenna_id in self.antennae:
|
for antenna_id in self.antennae:
|
||||||
self.canvas.delete(antenna_id)
|
self.canvas.delete(antenna_id)
|
||||||
|
@ -95,14 +103,14 @@ class CanvasNode:
|
||||||
image_box = self.canvas.bbox(self.id)
|
image_box = self.canvas.bbox(self.id)
|
||||||
return image_box[3] + NODE_TEXT_OFFSET
|
return image_box[3] + NODE_TEXT_OFFSET
|
||||||
|
|
||||||
def move(self, x, y):
|
def move(self, x: int, y: int):
|
||||||
x, y = self.canvas.get_scaled_coords(x, y)
|
x, y = self.canvas.get_scaled_coords(x, y)
|
||||||
current_x, current_y = self.canvas.coords(self.id)
|
current_x, current_y = self.canvas.coords(self.id)
|
||||||
x_offset = x - current_x
|
x_offset = x - current_x
|
||||||
y_offset = y - current_y
|
y_offset = y - current_y
|
||||||
self.motion(x_offset, y_offset, update=False)
|
self.motion(x_offset, y_offset, update=False)
|
||||||
|
|
||||||
def motion(self, x_offset, y_offset, update=True):
|
def motion(self, x_offset: int, y_offset: int, update: bool = True):
|
||||||
original_position = self.canvas.coords(self.id)
|
original_position = self.canvas.coords(self.id)
|
||||||
self.canvas.move(self.id, x_offset, y_offset)
|
self.canvas.move(self.id, x_offset, y_offset)
|
||||||
x, y = self.canvas.coords(self.id)
|
x, y = self.canvas.coords(self.id)
|
||||||
|
@ -144,7 +152,7 @@ class CanvasNode:
|
||||||
if self.app.core.is_runtime() and update:
|
if self.app.core.is_runtime() and update:
|
||||||
self.app.core.edit_node(self.core_node)
|
self.app.core.edit_node(self.core_node)
|
||||||
|
|
||||||
def on_enter(self, event):
|
def on_enter(self, event: tk.Event):
|
||||||
if self.app.core.is_runtime() and self.app.core.observer:
|
if self.app.core.is_runtime() and self.app.core.observer:
|
||||||
self.tooltip.text.set("waiting...")
|
self.tooltip.text.set("waiting...")
|
||||||
self.tooltip.on_enter(event)
|
self.tooltip.on_enter(event)
|
||||||
|
@ -154,16 +162,16 @@ class CanvasNode:
|
||||||
except grpc.RpcError as e:
|
except grpc.RpcError as e:
|
||||||
show_grpc_error(e)
|
show_grpc_error(e)
|
||||||
|
|
||||||
def on_leave(self, event):
|
def on_leave(self, event: tk.Event):
|
||||||
self.tooltip.on_leave(event)
|
self.tooltip.on_leave(event)
|
||||||
|
|
||||||
def double_click(self, event):
|
def double_click(self, event: tk.Event):
|
||||||
if self.app.core.is_runtime():
|
if self.app.core.is_runtime():
|
||||||
self.canvas.core.launch_terminal(self.core_node.id)
|
self.canvas.core.launch_terminal(self.core_node.id)
|
||||||
else:
|
else:
|
||||||
self.show_config()
|
self.show_config()
|
||||||
|
|
||||||
def create_context(self):
|
def create_context(self) -> tk.Menu:
|
||||||
is_wlan = self.core_node.type == NodeType.WIRELESS_LAN
|
is_wlan = self.core_node.type == NodeType.WIRELESS_LAN
|
||||||
is_emane = self.core_node.type == NodeType.EMANE
|
is_emane = self.core_node.type == NodeType.EMANE
|
||||||
context = tk.Menu(self.canvas)
|
context = tk.Menu(self.canvas)
|
||||||
|
@ -245,7 +253,7 @@ class CanvasNode:
|
||||||
dialog = NodeServiceDialog(self.app.master, self.app, self)
|
dialog = NodeServiceDialog(self.app.master, self.app, self)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def has_emane_link(self, interface_id):
|
def has_emane_link(self, interface_id: int) -> core_pb2.Node:
|
||||||
result = None
|
result = None
|
||||||
for edge in self.edges:
|
for edge in self.edges:
|
||||||
if self.id == edge.src:
|
if self.id == edge.src:
|
||||||
|
|
|
@ -1,23 +1,28 @@
|
||||||
import logging
|
import logging
|
||||||
|
from typing import TYPE_CHECKING, Dict, List, Union
|
||||||
|
|
||||||
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.shapeutils import ShapeType
|
from core.gui.graph.shapeutils import ShapeType
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.graph.graph import CanvasGraph
|
||||||
|
|
||||||
|
|
||||||
class AnnotationData:
|
class AnnotationData:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
text="",
|
text: str = "",
|
||||||
font="Arial",
|
font: str = "Arial",
|
||||||
font_size=12,
|
font_size: int = 12,
|
||||||
text_color="#000000",
|
text_color: str = "#000000",
|
||||||
fill_color="",
|
fill_color: str = "",
|
||||||
border_color="#000000",
|
border_color: str = "#000000",
|
||||||
border_width=1,
|
border_width: int = 1,
|
||||||
bold=False,
|
bold: bool = False,
|
||||||
italic=False,
|
italic: bool = False,
|
||||||
underline=False,
|
underline: bool = False,
|
||||||
):
|
):
|
||||||
self.text = text
|
self.text = text
|
||||||
self.font = font
|
self.font = font
|
||||||
|
@ -32,7 +37,17 @@ class AnnotationData:
|
||||||
|
|
||||||
|
|
||||||
class Shape:
|
class Shape:
|
||||||
def __init__(self, app, canvas, shape_type, x1, y1, x2=None, y2=None, data=None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
app: "Application",
|
||||||
|
canvas: "CanvasGraph",
|
||||||
|
shape_type: ShapeType,
|
||||||
|
x1: float,
|
||||||
|
y1: float,
|
||||||
|
x2: float = None,
|
||||||
|
y2: float = None,
|
||||||
|
data: AnnotationData = None,
|
||||||
|
):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.canvas = canvas
|
self.canvas = canvas
|
||||||
self.shape_type = shape_type
|
self.shape_type = shape_type
|
||||||
|
@ -99,7 +114,7 @@ class Shape:
|
||||||
logging.error("unknown shape type: %s", self.shape_type)
|
logging.error("unknown shape type: %s", self.shape_type)
|
||||||
self.created = True
|
self.created = True
|
||||||
|
|
||||||
def get_font(self):
|
def get_font(self) -> List[Union[int, str]]:
|
||||||
font = [self.shape_data.font, self.shape_data.font_size]
|
font = [self.shape_data.font, self.shape_data.font_size]
|
||||||
if self.shape_data.bold:
|
if self.shape_data.bold:
|
||||||
font.append("bold")
|
font.append("bold")
|
||||||
|
@ -123,10 +138,10 @@ class Shape:
|
||||||
font=font,
|
font=font,
|
||||||
)
|
)
|
||||||
|
|
||||||
def shape_motion(self, x1, y1):
|
def shape_motion(self, x1: float, y1: float):
|
||||||
self.canvas.coords(self.id, self.x1, self.y1, x1, y1)
|
self.canvas.coords(self.id, self.x1, self.y1, x1, y1)
|
||||||
|
|
||||||
def shape_complete(self, x, y):
|
def shape_complete(self, x: float, y: float):
|
||||||
for component in tags.ABOVE_SHAPE:
|
for component in tags.ABOVE_SHAPE:
|
||||||
self.canvas.tag_raise(component)
|
self.canvas.tag_raise(component)
|
||||||
s = ShapeDialog(self.app, self.app, self)
|
s = ShapeDialog(self.app, self.app, self)
|
||||||
|
@ -135,7 +150,7 @@ class Shape:
|
||||||
def disappear(self):
|
def disappear(self):
|
||||||
self.canvas.delete(self.id)
|
self.canvas.delete(self.id)
|
||||||
|
|
||||||
def motion(self, x_offset, y_offset):
|
def motion(self, x_offset: float, y_offset: float):
|
||||||
original_position = self.canvas.coords(self.id)
|
original_position = self.canvas.coords(self.id)
|
||||||
self.canvas.move(self.id, x_offset, y_offset)
|
self.canvas.move(self.id, x_offset, y_offset)
|
||||||
coords = self.canvas.coords(self.id)
|
coords = self.canvas.coords(self.id)
|
||||||
|
@ -151,7 +166,7 @@ class Shape:
|
||||||
self.canvas.delete(self.id)
|
self.canvas.delete(self.id)
|
||||||
self.canvas.delete(self.text_id)
|
self.canvas.delete(self.text_id)
|
||||||
|
|
||||||
def metadata(self):
|
def metadata(self) -> Dict[str, Union[str, int, bool]]:
|
||||||
coords = self.canvas.coords(self.id)
|
coords = self.canvas.coords(self.id)
|
||||||
# update coords to actual positions
|
# update coords to actual positions
|
||||||
if len(coords) == 4:
|
if len(coords) == 4:
|
||||||
|
|
|
@ -11,13 +11,13 @@ class ShapeType(enum.Enum):
|
||||||
SHAPES = {ShapeType.OVAL, ShapeType.RECTANGLE}
|
SHAPES = {ShapeType.OVAL, ShapeType.RECTANGLE}
|
||||||
|
|
||||||
|
|
||||||
def is_draw_shape(shape_type):
|
def is_draw_shape(shape_type: ShapeType) -> bool:
|
||||||
return shape_type in SHAPES
|
return shape_type in SHAPES
|
||||||
|
|
||||||
|
|
||||||
def is_shape_text(shape_type):
|
def is_shape_text(shape_type: ShapeType) -> bool:
|
||||||
return shape_type == ShapeType.TEXT
|
return shape_type == ShapeType.TEXT
|
||||||
|
|
||||||
|
|
||||||
def is_marker(shape_type):
|
def is_marker(shape_type: ShapeType) -> bool:
|
||||||
return shape_type == ShapeType.MARKER
|
return shape_type == ShapeType.MARKER
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.themes import Styles
|
from core.gui.themes import Styles
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.graph.graph import CanvasGraph
|
||||||
|
|
||||||
|
|
||||||
class CanvasTooltip:
|
class CanvasTooltip:
|
||||||
"""
|
"""
|
||||||
|
@ -19,7 +23,14 @@ class CanvasTooltip:
|
||||||
Alberto Vassena on 2016.12.10.
|
Alberto Vassena on 2016.12.10.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, canvas, *, pad=(5, 3, 5, 3), waittime=400, wraplength=600):
|
def __init__(
|
||||||
|
self,
|
||||||
|
canvas: "CanvasGraph",
|
||||||
|
*,
|
||||||
|
pad=(5, 3, 5, 3),
|
||||||
|
waittime: int = 400,
|
||||||
|
wraplength: int = 600
|
||||||
|
):
|
||||||
# in miliseconds, originally 500
|
# in miliseconds, originally 500
|
||||||
self.waittime = waittime
|
self.waittime = waittime
|
||||||
# in pixels, originally 180
|
# in pixels, originally 180
|
||||||
|
@ -30,10 +41,10 @@ class CanvasTooltip:
|
||||||
self.id = None
|
self.id = None
|
||||||
self.tw = None
|
self.tw = None
|
||||||
|
|
||||||
def on_enter(self, event=None):
|
def on_enter(self, event: tk.Event = None):
|
||||||
self.schedule()
|
self.schedule()
|
||||||
|
|
||||||
def on_leave(self, event=None):
|
def on_leave(self, event: tk.Event = None):
|
||||||
self.unschedule()
|
self.unschedule()
|
||||||
self.hide()
|
self.hide()
|
||||||
|
|
||||||
|
@ -47,7 +58,7 @@ class CanvasTooltip:
|
||||||
if id_:
|
if id_:
|
||||||
self.canvas.after_cancel(id_)
|
self.canvas.after_cancel(id_)
|
||||||
|
|
||||||
def show(self, event=None):
|
def show(self, event: tk.Event = None):
|
||||||
def tip_pos_calculator(canvas, label, *, tip_delta=(10, 5), pad=(5, 3, 5, 3)):
|
def tip_pos_calculator(canvas, label, *, tip_delta=(10, 5), pad=(5, 3, 5, 3)):
|
||||||
c = canvas
|
c = canvas
|
||||||
s_width, s_height = c.winfo_screenwidth(), c.winfo_screenheight()
|
s_width, s_height = c.winfo_screenwidth(), c.winfo_screenheight()
|
||||||
|
|
|
@ -9,7 +9,7 @@ class Images:
|
||||||
images = {}
|
images = {}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, file_path, width, height=None):
|
def create(cls, file_path: str, width: int, height: int = None):
|
||||||
if height is None:
|
if height is None:
|
||||||
height = width
|
height = width
|
||||||
image = Image.open(file_path)
|
image = Image.open(file_path)
|
||||||
|
@ -22,12 +22,12 @@ class Images:
|
||||||
cls.images[image.stem] = str(image)
|
cls.images[image.stem] = str(image)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, image_enum, width, height=None):
|
def get(cls, image_enum: Enum, width: int, height: int = None):
|
||||||
file_path = cls.images[image_enum.value]
|
file_path = cls.images[image_enum.value]
|
||||||
return cls.create(file_path, width, height)
|
return cls.create(file_path, width, height)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_custom(cls, name, width, height=None):
|
def get_custom(cls, name: str, width: int, height: int = None):
|
||||||
file_path = cls.images[name]
|
file_path = cls.images[name]
|
||||||
return cls.create(file_path, width, height)
|
return cls.create(file_path, width, height)
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,30 @@
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
from typing import TYPE_CHECKING, Set, Union
|
||||||
|
|
||||||
from netaddr import IPNetwork
|
from netaddr import IPNetwork
|
||||||
|
|
||||||
from core.gui.nodeutils import NodeUtils
|
from core.gui.nodeutils import NodeUtils
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.api.grpc import core_pb2
|
||||||
|
from core.gui.graph.node import CanvasNode
|
||||||
|
|
||||||
|
|
||||||
def random_mac():
|
def random_mac():
|
||||||
return ("{:02x}" * 6).format(*[random.randrange(256) for _ in range(6)])
|
return ("{:02x}" * 6).format(*[random.randrange(256) for _ in range(6)])
|
||||||
|
|
||||||
|
|
||||||
class InterfaceManager:
|
class InterfaceManager:
|
||||||
def __init__(self, app, address="10.0.0.0", mask=24):
|
def __init__(self, app: "Application", address: str = "10.0.0.0", mask: int = 24):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.mask = mask
|
self.mask = mask
|
||||||
self.base_prefix = max(self.mask - 8, 0)
|
self.base_prefix = max(self.mask - 8, 0)
|
||||||
self.subnets = IPNetwork(f"{address}/{self.base_prefix}")
|
self.subnets = IPNetwork(f"{address}/{self.base_prefix}")
|
||||||
self.current_subnet = None
|
self.current_subnet = None
|
||||||
|
|
||||||
def next_subnet(self):
|
def next_subnet(self) -> IPNetwork:
|
||||||
# define currently used subnets
|
# define currently used subnets
|
||||||
used_subnets = set()
|
used_subnets = set()
|
||||||
for edge in self.app.core.links.values():
|
for edge in self.app.core.links.values():
|
||||||
|
@ -38,17 +44,19 @@ class InterfaceManager:
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.current_subnet = None
|
self.current_subnet = None
|
||||||
|
|
||||||
def get_ips(self, node_id):
|
def get_ips(self, node_id: int) -> [str, str, int]:
|
||||||
ip4 = self.current_subnet[node_id]
|
ip4 = self.current_subnet[node_id]
|
||||||
ip6 = ip4.ipv6()
|
ip6 = ip4.ipv6()
|
||||||
prefix = self.current_subnet.prefixlen
|
prefix = self.current_subnet.prefixlen
|
||||||
return str(ip4), str(ip6), prefix
|
return str(ip4), str(ip6), prefix
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_subnet(cls, interface):
|
def get_subnet(cls, interface: "core_pb2.Interface") -> IPNetwork:
|
||||||
return IPNetwork(f"{interface.ip4}/{interface.ip4mask}").cidr
|
return IPNetwork(f"{interface.ip4}/{interface.ip4mask}").cidr
|
||||||
|
|
||||||
def determine_subnet(self, canvas_src_node, canvas_dst_node):
|
def determine_subnet(
|
||||||
|
self, canvas_src_node: "CanvasNode", canvas_dst_node: "CanvasNode"
|
||||||
|
):
|
||||||
src_node = canvas_src_node.core_node
|
src_node = canvas_src_node.core_node
|
||||||
dst_node = canvas_dst_node.core_node
|
dst_node = canvas_dst_node.core_node
|
||||||
is_src_container = NodeUtils.is_container_node(src_node.type)
|
is_src_container = NodeUtils.is_container_node(src_node.type)
|
||||||
|
@ -70,7 +78,9 @@ class InterfaceManager:
|
||||||
else:
|
else:
|
||||||
logging.info("ignoring subnet change for link between network nodes")
|
logging.info("ignoring subnet change for link between network nodes")
|
||||||
|
|
||||||
def find_subnet(self, canvas_node, visited=None):
|
def find_subnet(
|
||||||
|
self, canvas_node: "CanvasNode", visited: Set[int] = None
|
||||||
|
) -> Union[IPNetwork, None]:
|
||||||
logging.info("finding subnet for node: %s", canvas_node.core_node.name)
|
logging.info("finding subnet for node: %s", canvas_node.core_node.name)
|
||||||
canvas = self.app.canvas
|
canvas = self.app.canvas
|
||||||
cidr = None
|
cidr = None
|
||||||
|
|
|
@ -3,8 +3,10 @@ The actions taken when each menubar option is clicked
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import tkinter as tk
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from tkinter import filedialog, messagebox
|
from tkinter import filedialog, messagebox
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.appconfig import XMLS_PATH
|
from core.gui.appconfig import XMLS_PATH
|
||||||
from core.gui.dialogs.about import AboutDialog
|
from core.gui.dialogs.about import AboutDialog
|
||||||
|
@ -19,29 +21,24 @@ from core.gui.dialogs.sessions import SessionsDialog
|
||||||
from core.gui.dialogs.throughput import ThroughputDialog
|
from core.gui.dialogs.throughput import ThroughputDialog
|
||||||
from core.gui.task import BackgroundTask
|
from core.gui.task import BackgroundTask
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class MenuAction:
|
class MenuAction:
|
||||||
"""
|
def __init__(self, app: "Application", master: tk.Tk):
|
||||||
Actions performed when choosing menu items
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, app, master):
|
|
||||||
self.master = master
|
self.master = master
|
||||||
self.app = app
|
self.app = app
|
||||||
self.canvas = app.canvas
|
self.canvas = app.canvas
|
||||||
|
|
||||||
def cleanup_old_session(self, quitapp=False):
|
def cleanup_old_session(self):
|
||||||
logging.info("cleaning up old session")
|
logging.info("cleaning up old session")
|
||||||
self.app.core.stop_session()
|
self.app.core.stop_session()
|
||||||
self.app.core.delete_session()
|
self.app.core.delete_session()
|
||||||
# if quitapp:
|
|
||||||
# self.app.quit()
|
|
||||||
|
|
||||||
def prompt_save_running_session(self, quitapp=False):
|
def prompt_save_running_session(self, quitapp: bool = False):
|
||||||
"""
|
"""
|
||||||
Prompt use to stop running session before application is closed
|
Prompt use to stop running session before application is closed
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
result = True
|
result = True
|
||||||
if self.app.core.is_runtime():
|
if self.app.core.is_runtime():
|
||||||
|
@ -56,15 +53,13 @@ class MenuAction:
|
||||||
elif quitapp:
|
elif quitapp:
|
||||||
self.app.quit()
|
self.app.quit()
|
||||||
|
|
||||||
def on_quit(self, event=None):
|
def on_quit(self, event: tk.Event = None):
|
||||||
"""
|
"""
|
||||||
Prompt user whether so save running session, and then close the application
|
Prompt user whether so save running session, and then close the application
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.prompt_save_running_session(quitapp=True)
|
self.prompt_save_running_session(quitapp=True)
|
||||||
|
|
||||||
def file_save_as_xml(self, event=None):
|
def file_save_as_xml(self, event: tk.Event = None):
|
||||||
logging.info("menuaction.py file_save_as_xml()")
|
logging.info("menuaction.py file_save_as_xml()")
|
||||||
file_path = filedialog.asksaveasfilename(
|
file_path = filedialog.asksaveasfilename(
|
||||||
initialdir=str(XMLS_PATH),
|
initialdir=str(XMLS_PATH),
|
||||||
|
@ -75,7 +70,7 @@ class MenuAction:
|
||||||
if file_path:
|
if file_path:
|
||||||
self.app.core.save_xml(file_path)
|
self.app.core.save_xml(file_path)
|
||||||
|
|
||||||
def file_open_xml(self, event=None):
|
def file_open_xml(self, event: tk.Event = None):
|
||||||
logging.info("menuaction.py file_open_xml()")
|
logging.info("menuaction.py file_open_xml()")
|
||||||
file_path = filedialog.askopenfilename(
|
file_path = filedialog.askopenfilename(
|
||||||
initialdir=str(XMLS_PATH),
|
initialdir=str(XMLS_PATH),
|
||||||
|
@ -141,11 +136,11 @@ class MenuAction:
|
||||||
else:
|
else:
|
||||||
self.app.core.cancel_throughputs()
|
self.app.core.cancel_throughputs()
|
||||||
|
|
||||||
def copy(self, event=None):
|
def copy(self, event: tk.Event = None):
|
||||||
logging.debug("copy")
|
logging.debug("copy")
|
||||||
self.app.canvas.copy()
|
self.app.canvas.copy()
|
||||||
|
|
||||||
def paste(self, event=None):
|
def paste(self, event: tk.Event = None):
|
||||||
logging.debug("paste")
|
logging.debug("paste")
|
||||||
self.app.canvas.paste()
|
self.app.canvas.paste()
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import core.gui.menuaction as action
|
import core.gui.menuaction as action
|
||||||
from core.gui.coreclient import OBSERVERS
|
from core.gui.coreclient import OBSERVERS
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class Menubar(tk.Menu):
|
class Menubar(tk.Menu):
|
||||||
"""
|
"""
|
||||||
Core menubar
|
Core menubar
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, master, app, cnf={}, **kwargs):
|
def __init__(self, master: tk.Tk, app: "Application", cnf={}, **kwargs):
|
||||||
"""
|
"""
|
||||||
Create a CoreMenubar instance
|
Create a CoreMenubar instance
|
||||||
|
|
||||||
:param master:
|
|
||||||
:param tkinter.Menu menubar: menubar object
|
|
||||||
:param coretk.app.Application app: application object
|
|
||||||
"""
|
"""
|
||||||
super().__init__(master, cnf, **kwargs)
|
super().__init__(master, cnf, **kwargs)
|
||||||
self.master.config(menu=self)
|
self.master.config(menu=self)
|
||||||
|
@ -27,8 +27,6 @@ class Menubar(tk.Menu):
|
||||||
def draw(self):
|
def draw(self):
|
||||||
"""
|
"""
|
||||||
Create core menubar and bind the hot keys to their matching command
|
Create core menubar and bind the hot keys to their matching command
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.draw_file_menu()
|
self.draw_file_menu()
|
||||||
self.draw_edit_menu()
|
self.draw_edit_menu()
|
||||||
|
@ -42,8 +40,6 @@ class Menubar(tk.Menu):
|
||||||
def draw_file_menu(self):
|
def draw_file_menu(self):
|
||||||
"""
|
"""
|
||||||
Create file menu
|
Create file menu
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(self)
|
menu = tk.Menu(self)
|
||||||
menu.add_command(
|
menu.add_command(
|
||||||
|
@ -81,8 +77,6 @@ class Menubar(tk.Menu):
|
||||||
def draw_edit_menu(self):
|
def draw_edit_menu(self):
|
||||||
"""
|
"""
|
||||||
Create edit menu
|
Create edit menu
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(self)
|
menu = tk.Menu(self)
|
||||||
menu.add_command(label="Preferences", command=self.menuaction.gui_preferences)
|
menu.add_command(label="Preferences", command=self.menuaction.gui_preferences)
|
||||||
|
@ -112,8 +106,6 @@ class Menubar(tk.Menu):
|
||||||
def draw_canvas_menu(self):
|
def draw_canvas_menu(self):
|
||||||
"""
|
"""
|
||||||
Create canvas menu
|
Create canvas menu
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(self)
|
menu = tk.Menu(self)
|
||||||
menu.add_command(
|
menu.add_command(
|
||||||
|
@ -136,8 +128,6 @@ class Menubar(tk.Menu):
|
||||||
def draw_view_menu(self):
|
def draw_view_menu(self):
|
||||||
"""
|
"""
|
||||||
Create view menu
|
Create view menu
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
view_menu = tk.Menu(self)
|
view_menu = tk.Menu(self)
|
||||||
self.create_show_menu(view_menu)
|
self.create_show_menu(view_menu)
|
||||||
|
@ -149,12 +139,9 @@ class Menubar(tk.Menu):
|
||||||
view_menu.add_command(label="Zoom out", accelerator="-", state=tk.DISABLED)
|
view_menu.add_command(label="Zoom out", accelerator="-", state=tk.DISABLED)
|
||||||
self.add_cascade(label="View", menu=view_menu)
|
self.add_cascade(label="View", menu=view_menu)
|
||||||
|
|
||||||
def create_show_menu(self, view_menu):
|
def create_show_menu(self, view_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create the menu items in View/Show
|
Create the menu items in View/Show
|
||||||
|
|
||||||
:param tkinter.Menu view_menu: the view menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(view_menu)
|
menu = tk.Menu(view_menu)
|
||||||
menu.add_command(label="All", state=tk.DISABLED)
|
menu.add_command(label="All", state=tk.DISABLED)
|
||||||
|
@ -169,12 +156,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label="API Messages", state=tk.DISABLED)
|
menu.add_command(label="API Messages", state=tk.DISABLED)
|
||||||
view_menu.add_cascade(label="Show", menu=menu)
|
view_menu.add_cascade(label="Show", menu=menu)
|
||||||
|
|
||||||
def create_experimental_menu(self, tools_menu):
|
def create_experimental_menu(self, tools_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create experimental menu item and the sub menu items inside
|
Create experimental menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu tools_menu: tools menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(tools_menu)
|
menu = tk.Menu(tools_menu)
|
||||||
menu.add_command(label="Plugins...", state=tk.DISABLED)
|
menu.add_command(label="Plugins...", state=tk.DISABLED)
|
||||||
|
@ -182,12 +166,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label="Topology partitioning...", state=tk.DISABLED)
|
menu.add_command(label="Topology partitioning...", state=tk.DISABLED)
|
||||||
tools_menu.add_cascade(label="Experimental", menu=menu)
|
tools_menu.add_cascade(label="Experimental", menu=menu)
|
||||||
|
|
||||||
def create_random_menu(self, topology_generator_menu):
|
def create_random_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create random menu item and the sub menu items inside
|
Create random menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology generator menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
# list of number of random nodes to create
|
# list of number of random nodes to create
|
||||||
|
@ -197,12 +178,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label=label, state=tk.DISABLED)
|
menu.add_command(label=label, state=tk.DISABLED)
|
||||||
topology_generator_menu.add_cascade(label="Random", menu=menu)
|
topology_generator_menu.add_cascade(label="Random", menu=menu)
|
||||||
|
|
||||||
def create_grid_menu(self, topology_generator_menu):
|
def create_grid_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create grid menu item and the sub menu items inside
|
Create grid menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology_generator_menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
# list of number of nodes to create
|
# list of number of nodes to create
|
||||||
|
@ -212,12 +190,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label=label, state=tk.DISABLED)
|
menu.add_command(label=label, state=tk.DISABLED)
|
||||||
topology_generator_menu.add_cascade(label="Grid", menu=menu)
|
topology_generator_menu.add_cascade(label="Grid", menu=menu)
|
||||||
|
|
||||||
def create_connected_grid_menu(self, topology_generator_menu):
|
def create_connected_grid_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create connected grid menu items and the sub menu items inside
|
Create connected grid menu items and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology generator menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
for i in range(1, 11, 1):
|
for i in range(1, 11, 1):
|
||||||
|
@ -229,12 +204,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_cascade(label=label, menu=submenu)
|
menu.add_cascade(label=label, menu=submenu)
|
||||||
topology_generator_menu.add_cascade(label="Connected Grid", menu=menu)
|
topology_generator_menu.add_cascade(label="Connected Grid", menu=menu)
|
||||||
|
|
||||||
def create_chain_menu(self, topology_generator_menu):
|
def create_chain_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create chain menu item and the sub menu items inside
|
Create chain menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology generator menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
# number of nodes to create
|
# number of nodes to create
|
||||||
|
@ -244,12 +216,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label=label, state=tk.DISABLED)
|
menu.add_command(label=label, state=tk.DISABLED)
|
||||||
topology_generator_menu.add_cascade(label="Chain", menu=menu)
|
topology_generator_menu.add_cascade(label="Chain", menu=menu)
|
||||||
|
|
||||||
def create_star_menu(self, topology_generator_menu):
|
def create_star_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create star menu item and the sub menu items inside
|
Create star menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology generator menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
for i in range(3, 26, 1):
|
for i in range(3, 26, 1):
|
||||||
|
@ -257,12 +226,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label=label, state=tk.DISABLED)
|
menu.add_command(label=label, state=tk.DISABLED)
|
||||||
topology_generator_menu.add_cascade(label="Star", menu=menu)
|
topology_generator_menu.add_cascade(label="Star", menu=menu)
|
||||||
|
|
||||||
def create_cycle_menu(self, topology_generator_menu):
|
def create_cycle_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create cycle menu item and the sub items inside
|
Create cycle menu item and the sub items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology generator menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
for i in range(3, 25, 1):
|
for i in range(3, 25, 1):
|
||||||
|
@ -270,12 +236,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label=label, state=tk.DISABLED)
|
menu.add_command(label=label, state=tk.DISABLED)
|
||||||
topology_generator_menu.add_cascade(label="Cycle", menu=menu)
|
topology_generator_menu.add_cascade(label="Cycle", menu=menu)
|
||||||
|
|
||||||
def create_wheel_menu(self, topology_generator_menu):
|
def create_wheel_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create wheel menu item and the sub menu items inside
|
Create wheel menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology generator menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
for i in range(4, 26, 1):
|
for i in range(4, 26, 1):
|
||||||
|
@ -283,12 +246,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label=label, state=tk.DISABLED)
|
menu.add_command(label=label, state=tk.DISABLED)
|
||||||
topology_generator_menu.add_cascade(label="Wheel", menu=menu)
|
topology_generator_menu.add_cascade(label="Wheel", menu=menu)
|
||||||
|
|
||||||
def create_cube_menu(self, topology_generator_menu):
|
def create_cube_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create cube menu item and the sub menu items inside
|
Create cube menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology generator menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
for i in range(2, 7, 1):
|
for i in range(2, 7, 1):
|
||||||
|
@ -296,12 +256,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label=label, state=tk.DISABLED)
|
menu.add_command(label=label, state=tk.DISABLED)
|
||||||
topology_generator_menu.add_cascade(label="Cube", menu=menu)
|
topology_generator_menu.add_cascade(label="Cube", menu=menu)
|
||||||
|
|
||||||
def create_clique_menu(self, topology_generator_menu):
|
def create_clique_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create clique menu item and the sub menu items inside
|
Create clique menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology generator menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
for i in range(3, 25, 1):
|
for i in range(3, 25, 1):
|
||||||
|
@ -309,12 +266,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label=label, state=tk.DISABLED)
|
menu.add_command(label=label, state=tk.DISABLED)
|
||||||
topology_generator_menu.add_cascade(label="Clique", menu=menu)
|
topology_generator_menu.add_cascade(label="Clique", menu=menu)
|
||||||
|
|
||||||
def create_bipartite_menu(self, topology_generator_menu):
|
def create_bipartite_menu(self, topology_generator_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create bipartite menu item and the sub menu items inside
|
Create bipartite menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu topology_generator_menu: topology_generator_menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(topology_generator_menu)
|
menu = tk.Menu(topology_generator_menu)
|
||||||
temp = 24
|
temp = 24
|
||||||
|
@ -328,13 +282,9 @@ class Menubar(tk.Menu):
|
||||||
temp = temp - 1
|
temp = temp - 1
|
||||||
topology_generator_menu.add_cascade(label="Bipartite", menu=menu)
|
topology_generator_menu.add_cascade(label="Bipartite", menu=menu)
|
||||||
|
|
||||||
def create_topology_generator_menu(self, tools_menu):
|
def create_topology_generator_menu(self, tools_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create topology menu item and its sub menu items
|
Create topology menu item and its sub menu items
|
||||||
|
|
||||||
:param tkinter.Menu tools_menu: tools menu
|
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(tools_menu)
|
menu = tk.Menu(tools_menu)
|
||||||
self.create_random_menu(menu)
|
self.create_random_menu(menu)
|
||||||
|
@ -352,8 +302,6 @@ class Menubar(tk.Menu):
|
||||||
def draw_tools_menu(self):
|
def draw_tools_menu(self):
|
||||||
"""
|
"""
|
||||||
Create tools menu
|
Create tools menu
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(self)
|
menu = tk.Menu(self)
|
||||||
menu.add_command(label="Auto rearrange all", state=tk.DISABLED)
|
menu.add_command(label="Auto rearrange all", state=tk.DISABLED)
|
||||||
|
@ -371,12 +319,9 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label="Debugger...", state=tk.DISABLED)
|
menu.add_command(label="Debugger...", state=tk.DISABLED)
|
||||||
self.add_cascade(label="Tools", menu=menu)
|
self.add_cascade(label="Tools", menu=menu)
|
||||||
|
|
||||||
def create_observer_widgets_menu(self, widget_menu):
|
def create_observer_widgets_menu(self, widget_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create observer widget menu item and create the sub menu items inside
|
Create observer widget menu item and create the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu widget_menu: widget_menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
var = tk.StringVar(value="none")
|
var = tk.StringVar(value="none")
|
||||||
menu = tk.Menu(widget_menu)
|
menu = tk.Menu(widget_menu)
|
||||||
|
@ -409,12 +354,9 @@ class Menubar(tk.Menu):
|
||||||
)
|
)
|
||||||
widget_menu.add_cascade(label="Observer Widgets", menu=menu)
|
widget_menu.add_cascade(label="Observer Widgets", menu=menu)
|
||||||
|
|
||||||
def create_adjacency_menu(self, widget_menu):
|
def create_adjacency_menu(self, widget_menu: tk.Menu):
|
||||||
"""
|
"""
|
||||||
Create adjacency menu item and the sub menu items inside
|
Create adjacency menu item and the sub menu items inside
|
||||||
|
|
||||||
:param tkinter.Menu widget_menu: widget menu
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(widget_menu)
|
menu = tk.Menu(widget_menu)
|
||||||
menu.add_command(label="OSPFv2", state=tk.DISABLED)
|
menu.add_command(label="OSPFv2", state=tk.DISABLED)
|
||||||
|
@ -426,8 +368,6 @@ class Menubar(tk.Menu):
|
||||||
def draw_widgets_menu(self):
|
def draw_widgets_menu(self):
|
||||||
"""
|
"""
|
||||||
Create widget menu
|
Create widget menu
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(self)
|
menu = tk.Menu(self)
|
||||||
self.create_observer_widgets_menu(menu)
|
self.create_observer_widgets_menu(menu)
|
||||||
|
@ -443,8 +383,6 @@ class Menubar(tk.Menu):
|
||||||
def draw_session_menu(self):
|
def draw_session_menu(self):
|
||||||
"""
|
"""
|
||||||
Create session menu
|
Create session menu
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(self)
|
menu = tk.Menu(self)
|
||||||
menu.add_command(
|
menu.add_command(
|
||||||
|
@ -461,8 +399,6 @@ class Menubar(tk.Menu):
|
||||||
def draw_help_menu(self):
|
def draw_help_menu(self):
|
||||||
"""
|
"""
|
||||||
Create help menu
|
Create help menu
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
menu = tk.Menu(self)
|
menu = tk.Menu(self)
|
||||||
menu.add_command(
|
menu.add_command(
|
||||||
|
|
|
@ -1,22 +1,34 @@
|
||||||
|
from typing import TYPE_CHECKING, Optional, Set
|
||||||
|
|
||||||
from core.api.grpc.core_pb2 import NodeType
|
from core.api.grpc.core_pb2 import NodeType
|
||||||
from core.gui.images import ImageEnum, Images
|
from core.gui.images import ImageEnum, Images
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.api.grpc import core_pb2
|
||||||
|
|
||||||
ICON_SIZE = 48
|
ICON_SIZE = 48
|
||||||
ANTENNA_SIZE = 32
|
ANTENNA_SIZE = 32
|
||||||
|
|
||||||
|
|
||||||
class NodeDraw:
|
class NodeDraw:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.custom = False
|
self.custom: bool = False
|
||||||
self.image = None
|
self.image = None
|
||||||
self.image_enum = None
|
self.image_enum: Optional[ImageEnum] = None
|
||||||
self.image_file = None
|
self.image_file = None
|
||||||
self.node_type = None
|
self.node_type: core_pb2.NodeType = None
|
||||||
self.model = None
|
self.model: Optional[str] = None
|
||||||
self.services = set()
|
self.services: Set[str] = set()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_setup(cls, image_enum, node_type, label, model=None, tooltip=None):
|
def from_setup(
|
||||||
|
cls,
|
||||||
|
image_enum: ImageEnum,
|
||||||
|
node_type: "core_pb2.NodeType",
|
||||||
|
label: str,
|
||||||
|
model: str = None,
|
||||||
|
tooltip=None,
|
||||||
|
):
|
||||||
node_draw = NodeDraw()
|
node_draw = NodeDraw()
|
||||||
node_draw.image_enum = image_enum
|
node_draw.image_enum = image_enum
|
||||||
node_draw.image = Images.get(image_enum, ICON_SIZE)
|
node_draw.image = Images.get(image_enum, ICON_SIZE)
|
||||||
|
@ -27,7 +39,7 @@ class NodeDraw:
|
||||||
return node_draw
|
return node_draw
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_custom(cls, name, image_file, services):
|
def from_custom(cls, name: str, image_file: str, services: Set[str]):
|
||||||
node_draw = NodeDraw()
|
node_draw = NodeDraw()
|
||||||
node_draw.custom = True
|
node_draw.custom = True
|
||||||
node_draw.image_file = image_file
|
node_draw.image_file = image_file
|
||||||
|
@ -53,31 +65,31 @@ class NodeUtils:
|
||||||
ANTENNA_ICON = None
|
ANTENNA_ICON = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_ignore_node(cls, node_type):
|
def is_ignore_node(cls, node_type: NodeType) -> bool:
|
||||||
return node_type in cls.IGNORE_NODES
|
return node_type in cls.IGNORE_NODES
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_container_node(cls, node_type):
|
def is_container_node(cls, node_type: NodeType) -> bool:
|
||||||
return node_type in cls.CONTAINER_NODES
|
return node_type in cls.CONTAINER_NODES
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_model_node(cls, node_type):
|
def is_model_node(cls, node_type: NodeType) -> bool:
|
||||||
return node_type == NodeType.DEFAULT
|
return node_type == NodeType.DEFAULT
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_image_node(cls, node_type):
|
def is_image_node(cls, node_type: NodeType) -> bool:
|
||||||
return node_type in cls.IMAGE_NODES
|
return node_type in cls.IMAGE_NODES
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_wireless_node(cls, node_type):
|
def is_wireless_node(cls, node_type: NodeType) -> bool:
|
||||||
return node_type in cls.WIRELESS_NODES
|
return node_type in cls.WIRELESS_NODES
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_rj45_node(cls, node_type):
|
def is_rj45_node(cls, node_type: NodeType) -> bool:
|
||||||
return node_type in cls.RJ45_NODES
|
return node_type in cls.RJ45_NODES
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def node_icon(cls, node_type, model):
|
def node_icon(cls, node_type: NodeType, model: str) -> bool:
|
||||||
if model == "":
|
if model == "":
|
||||||
model = None
|
model = None
|
||||||
return cls.NODE_ICONS[(node_type, model)]
|
return cls.NODE_ICONS[(node_type, model)]
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
"status bar"
|
"""
|
||||||
|
status bar
|
||||||
|
"""
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from core.gui.dialogs.alerts import AlertsDialog
|
from core.gui.dialogs.alerts import AlertsDialog
|
||||||
from core.gui.themes import Styles
|
from core.gui.themes import Styles
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class StatusBar(ttk.Frame):
|
class StatusBar(ttk.Frame):
|
||||||
def __init__(self, master, app, **kwargs):
|
def __init__(self, master: "Application", app: "Application", **kwargs):
|
||||||
super().__init__(master, **kwargs)
|
super().__init__(master, **kwargs)
|
||||||
self.app = app
|
self.app = app
|
||||||
self.status = None
|
self.status = None
|
||||||
|
@ -68,9 +74,5 @@ class StatusBar(ttk.Frame):
|
||||||
dialog = AlertsDialog(self.app, self.app)
|
dialog = AlertsDialog(self.app, self.app)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
|
|
||||||
def set_status(self, message):
|
def set_status(self, message: str):
|
||||||
self.statusvar.set(message)
|
self.statusvar.set(message)
|
||||||
|
|
||||||
def stop_session_callback(self, cleanup_time):
|
|
||||||
self.progress_bar.stop()
|
|
||||||
self.statusvar.set(f"Stopped in {cleanup_time:.3f} seconds")
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
|
||||||
class BackgroundTask:
|
class BackgroundTask:
|
||||||
def __init__(self, master, task, callback=None, args=()):
|
def __init__(self, master: Any, task: Callable, callback: Callable = None, args=()):
|
||||||
self.master = master
|
self.master = master
|
||||||
self.args = args
|
self.args = args
|
||||||
self.task = task
|
self.task = task
|
||||||
|
|
|
@ -33,7 +33,7 @@ class Colors:
|
||||||
listboxbg = "#f2f1f0"
|
listboxbg = "#f2f1f0"
|
||||||
|
|
||||||
|
|
||||||
def load(style):
|
def load(style: ttk.Style):
|
||||||
style.theme_create(
|
style.theme_create(
|
||||||
THEME_DARK,
|
THEME_DARK,
|
||||||
"clam",
|
"clam",
|
||||||
|
@ -141,13 +141,13 @@ def load(style):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def theme_change_menu(event):
|
def theme_change_menu(event: tk.Event):
|
||||||
if not isinstance(event.widget, tk.Menu):
|
if not isinstance(event.widget, tk.Menu):
|
||||||
return
|
return
|
||||||
style_menu(event.widget)
|
style_menu(event.widget)
|
||||||
|
|
||||||
|
|
||||||
def style_menu(widget):
|
def style_menu(widget: tk.Widget):
|
||||||
style = ttk.Style()
|
style = ttk.Style()
|
||||||
bg = style.lookup(".", "background")
|
bg = style.lookup(".", "background")
|
||||||
fg = style.lookup(".", "foreground")
|
fg = style.lookup(".", "foreground")
|
||||||
|
@ -159,7 +159,7 @@ def style_menu(widget):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def style_listbox(widget):
|
def style_listbox(widget: tk.Widget):
|
||||||
style = ttk.Style()
|
style = ttk.Style()
|
||||||
bg = style.lookup(".", "background")
|
bg = style.lookup(".", "background")
|
||||||
fg = style.lookup(".", "foreground")
|
fg = style.lookup(".", "foreground")
|
||||||
|
@ -176,7 +176,7 @@ def style_listbox(widget):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def theme_change(event):
|
def theme_change(event: tk.Event):
|
||||||
style = ttk.Style()
|
style = ttk.Style()
|
||||||
style.configure(Styles.picker_button, font=("TkDefaultFont", 8, "normal"))
|
style.configure(Styles.picker_button, font=("TkDefaultFont", 8, "normal"))
|
||||||
style.configure(
|
style.configure(
|
||||||
|
|
|
@ -4,17 +4,23 @@ import tkinter as tk
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from tkinter import messagebox, ttk
|
from tkinter import messagebox, ttk
|
||||||
from tkinter.font import Font
|
from tkinter.font import Font
|
||||||
|
from typing import TYPE_CHECKING, Callable
|
||||||
|
|
||||||
|
from core.api.grpc import core_pb2
|
||||||
from core.gui.dialogs.customnodes import CustomNodesDialog
|
from core.gui.dialogs.customnodes import CustomNodesDialog
|
||||||
from core.gui.dialogs.marker import MarkerDialog
|
from core.gui.dialogs.marker import MarkerDialog
|
||||||
from core.gui.graph.enums import GraphMode
|
from core.gui.graph.enums import GraphMode
|
||||||
from core.gui.graph.shapeutils import ShapeType, is_marker
|
from core.gui.graph.shapeutils import ShapeType, is_marker
|
||||||
from core.gui.images import ImageEnum, Images
|
from core.gui.images import ImageEnum, Images
|
||||||
from core.gui.nodeutils import NodeUtils
|
from core.gui.nodeutils import NodeDraw, NodeUtils
|
||||||
from core.gui.task import BackgroundTask
|
from core.gui.task import BackgroundTask
|
||||||
from core.gui.themes import Styles
|
from core.gui.themes import Styles
|
||||||
from core.gui.tooltip import Tooltip
|
from core.gui.tooltip import Tooltip
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from PIL import ImageTk
|
||||||
|
|
||||||
TOOLBAR_SIZE = 32
|
TOOLBAR_SIZE = 32
|
||||||
PICKER_SIZE = 24
|
PICKER_SIZE = 24
|
||||||
|
|
||||||
|
@ -28,11 +34,9 @@ class Toolbar(ttk.Frame):
|
||||||
Core toolbar class
|
Core toolbar class
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, master, app, **kwargs):
|
def __init__(self, master: "Application", app: "Application", **kwargs):
|
||||||
"""
|
"""
|
||||||
Create a CoreToolbar instance
|
Create a CoreToolbar instance
|
||||||
|
|
||||||
:param tkinter.Frame edit_frame: edit frame
|
|
||||||
"""
|
"""
|
||||||
super().__init__(master, **kwargs)
|
super().__init__(master, **kwargs)
|
||||||
self.app = app
|
self.app = app
|
||||||
|
@ -100,7 +104,7 @@ class Toolbar(ttk.Frame):
|
||||||
self.create_network_button()
|
self.create_network_button()
|
||||||
self.create_annotation_button()
|
self.create_annotation_button()
|
||||||
|
|
||||||
def design_select(self, button):
|
def design_select(self, button: ttk.Button):
|
||||||
logging.info("selecting design button: %s", button)
|
logging.info("selecting design button: %s", button)
|
||||||
self.select_button.state(["!pressed"])
|
self.select_button.state(["!pressed"])
|
||||||
self.link_button.state(["!pressed"])
|
self.link_button.state(["!pressed"])
|
||||||
|
@ -109,7 +113,7 @@ class Toolbar(ttk.Frame):
|
||||||
self.annotation_button.state(["!pressed"])
|
self.annotation_button.state(["!pressed"])
|
||||||
button.state(["pressed"])
|
button.state(["pressed"])
|
||||||
|
|
||||||
def runtime_select(self, button):
|
def runtime_select(self, button: ttk.Button):
|
||||||
logging.info("selecting runtime button: %s", button)
|
logging.info("selecting runtime button: %s", button)
|
||||||
self.runtime_select_button.state(["!pressed"])
|
self.runtime_select_button.state(["!pressed"])
|
||||||
self.stop_button.state(["!pressed"])
|
self.stop_button.state(["!pressed"])
|
||||||
|
@ -185,7 +189,7 @@ class Toolbar(ttk.Frame):
|
||||||
0, lambda: self.show_picker(self.node_button, self.node_picker)
|
0, lambda: self.show_picker(self.node_button, self.node_picker)
|
||||||
)
|
)
|
||||||
|
|
||||||
def show_picker(self, button, picker):
|
def show_picker(self, button: ttk.Button, picker: ttk.Frame):
|
||||||
x = self.winfo_width() + 1
|
x = self.winfo_width() + 1
|
||||||
y = button.winfo_rooty() - picker.master.winfo_rooty() - 1
|
y = button.winfo_rooty() - picker.master.winfo_rooty() - 1
|
||||||
picker.place(x=x, y=y)
|
picker.place(x=x, y=y)
|
||||||
|
@ -195,7 +199,9 @@ class Toolbar(ttk.Frame):
|
||||||
self.wait_window(picker)
|
self.wait_window(picker)
|
||||||
self.app.unbind_all("<ButtonRelease-1>")
|
self.app.unbind_all("<ButtonRelease-1>")
|
||||||
|
|
||||||
def create_picker_button(self, image, func, frame, label):
|
def create_picker_button(
|
||||||
|
self, image: "ImageTk.PhotoImage", func: Callable, frame: ttk.Frame, label: str
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Create button and put it on the frame
|
Create button and put it on the frame
|
||||||
|
|
||||||
|
@ -203,7 +209,6 @@ class Toolbar(ttk.Frame):
|
||||||
:param func: the command that is executed when button is clicked
|
:param func: the command that is executed when button is clicked
|
||||||
:param tkinter.Frame frame: frame that contains the button
|
:param tkinter.Frame frame: frame that contains the button
|
||||||
:param str label: button label
|
:param str label: button label
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
button = ttk.Button(
|
button = ttk.Button(
|
||||||
frame, image=image, text=label, compound=tk.TOP, style=Styles.picker_button
|
frame, image=image, text=label, compound=tk.TOP, style=Styles.picker_button
|
||||||
|
@ -212,7 +217,13 @@ class Toolbar(ttk.Frame):
|
||||||
button.bind("<ButtonRelease-1>", lambda e: func())
|
button.bind("<ButtonRelease-1>", lambda e: func())
|
||||||
button.grid(pady=1)
|
button.grid(pady=1)
|
||||||
|
|
||||||
def create_button(self, frame, image, func, tooltip):
|
def create_button(
|
||||||
|
self,
|
||||||
|
frame: ttk.Frame,
|
||||||
|
image: "ImageTk.PhotoImage",
|
||||||
|
func: Callable,
|
||||||
|
tooltip: str,
|
||||||
|
):
|
||||||
button = ttk.Button(frame, image=image, command=func)
|
button = ttk.Button(frame, image=image, command=func)
|
||||||
button.image = image
|
button.image = image
|
||||||
button.grid(sticky="ew")
|
button.grid(sticky="ew")
|
||||||
|
@ -233,8 +244,6 @@ class Toolbar(ttk.Frame):
|
||||||
"""
|
"""
|
||||||
Start session handler redraw buttons, send node and link messages to grpc
|
Start session handler redraw buttons, send node and link messages to grpc
|
||||||
server.
|
server.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.app.canvas.hide_context()
|
self.app.canvas.hide_context()
|
||||||
self.app.statusbar.progress_bar.start(5)
|
self.app.statusbar.progress_bar.start(5)
|
||||||
|
@ -243,7 +252,7 @@ class Toolbar(ttk.Frame):
|
||||||
task = BackgroundTask(self, self.app.core.start_session, self.start_callback)
|
task = BackgroundTask(self, self.app.core.start_session, self.start_callback)
|
||||||
task.start()
|
task.start()
|
||||||
|
|
||||||
def start_callback(self, response):
|
def start_callback(self, response: core_pb2.StartSessionResponse):
|
||||||
self.app.statusbar.progress_bar.stop()
|
self.app.statusbar.progress_bar.stop()
|
||||||
total = time.perf_counter() - self.time
|
total = time.perf_counter() - self.time
|
||||||
message = f"Start ran for {total:.3f} seconds"
|
message = f"Start ran for {total:.3f} seconds"
|
||||||
|
@ -275,7 +284,7 @@ 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_draw):
|
def update_button(self, button: ttk.Button, image: "ImageTk", node_draw: NodeDraw):
|
||||||
logging.info("update button(%s): %s", button, node_draw)
|
logging.info("update button(%s): %s", button, node_draw)
|
||||||
self.hide_pickers()
|
self.hide_pickers()
|
||||||
button.configure(image=image)
|
button.configure(image=image)
|
||||||
|
@ -298,8 +307,6 @@ class Toolbar(ttk.Frame):
|
||||||
def create_node_button(self):
|
def create_node_button(self):
|
||||||
"""
|
"""
|
||||||
Create network layer button
|
Create network layer button
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
image = icon(ImageEnum.ROUTER)
|
image = icon(ImageEnum.ROUTER)
|
||||||
self.node_button = ttk.Button(
|
self.node_button = ttk.Button(
|
||||||
|
@ -312,8 +319,6 @@ class Toolbar(ttk.Frame):
|
||||||
def draw_network_picker(self):
|
def draw_network_picker(self):
|
||||||
"""
|
"""
|
||||||
Draw the options for link-layer button.
|
Draw the options for link-layer button.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.hide_pickers()
|
self.hide_pickers()
|
||||||
self.network_picker = ttk.Frame(self.master)
|
self.network_picker = ttk.Frame(self.master)
|
||||||
|
@ -337,8 +342,6 @@ class Toolbar(ttk.Frame):
|
||||||
"""
|
"""
|
||||||
Create link-layer node button and the options that represent different
|
Create link-layer node button and the options that represent different
|
||||||
link-layer node types.
|
link-layer node types.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
image = icon(ImageEnum.HUB)
|
image = icon(ImageEnum.HUB)
|
||||||
self.network_button = ttk.Button(
|
self.network_button = ttk.Button(
|
||||||
|
@ -351,8 +354,6 @@ class Toolbar(ttk.Frame):
|
||||||
def draw_annotation_picker(self):
|
def draw_annotation_picker(self):
|
||||||
"""
|
"""
|
||||||
Draw the options for marker button.
|
Draw the options for marker button.
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.hide_pickers()
|
self.hide_pickers()
|
||||||
self.annotation_picker = ttk.Frame(self.master)
|
self.annotation_picker = ttk.Frame(self.master)
|
||||||
|
@ -379,8 +380,6 @@ class Toolbar(ttk.Frame):
|
||||||
def create_annotation_button(self):
|
def create_annotation_button(self):
|
||||||
"""
|
"""
|
||||||
Create marker button and options that represent different marker types
|
Create marker button and options that represent different marker types
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
image = icon(ImageEnum.MARKER)
|
image = icon(ImageEnum.MARKER)
|
||||||
self.annotation_button = ttk.Button(
|
self.annotation_button = ttk.Button(
|
||||||
|
@ -417,8 +416,6 @@ class Toolbar(ttk.Frame):
|
||||||
def click_stop(self):
|
def click_stop(self):
|
||||||
"""
|
"""
|
||||||
redraw buttons on the toolbar, send node and link messages to grpc server
|
redraw buttons on the toolbar, send node and link messages to grpc server
|
||||||
|
|
||||||
:return: nothing
|
|
||||||
"""
|
"""
|
||||||
self.app.canvas.hide_context()
|
self.app.canvas.hide_context()
|
||||||
self.app.statusbar.progress_bar.start(5)
|
self.app.statusbar.progress_bar.start(5)
|
||||||
|
@ -426,7 +423,7 @@ class Toolbar(ttk.Frame):
|
||||||
task = BackgroundTask(self, self.app.core.stop_session, self.stop_callback)
|
task = BackgroundTask(self, self.app.core.stop_session, self.stop_callback)
|
||||||
task.start()
|
task.start()
|
||||||
|
|
||||||
def stop_callback(self, response):
|
def stop_callback(self, response: core_pb2.StopSessionResponse):
|
||||||
self.app.statusbar.progress_bar.stop()
|
self.app.statusbar.progress_bar.stop()
|
||||||
self.set_design()
|
self.set_design()
|
||||||
total = time.perf_counter() - self.time
|
total = time.perf_counter() - self.time
|
||||||
|
@ -436,7 +433,7 @@ class Toolbar(ttk.Frame):
|
||||||
if not response.result:
|
if not response.result:
|
||||||
messagebox.showerror("Stop Error", "Errors stopping session")
|
messagebox.showerror("Stop Error", "Errors stopping session")
|
||||||
|
|
||||||
def update_annotation(self, image, shape_type):
|
def update_annotation(self, image: "ImageTk.PhotoImage", shape_type: ShapeType):
|
||||||
logging.info("clicked annotation: ")
|
logging.info("clicked annotation: ")
|
||||||
self.hide_pickers()
|
self.hide_pickers()
|
||||||
self.annotation_button.configure(image=image)
|
self.annotation_button.configure(image=image)
|
||||||
|
@ -446,7 +443,7 @@ class Toolbar(ttk.Frame):
|
||||||
if is_marker(shape_type):
|
if is_marker(shape_type):
|
||||||
if self.marker_tool:
|
if self.marker_tool:
|
||||||
self.marker_tool.destroy()
|
self.marker_tool.destroy()
|
||||||
self.marker_tool = MarkerDialog(self.master, self.app)
|
self.marker_tool = MarkerDialog(self.app, self.app)
|
||||||
self.marker_tool.show()
|
self.marker_tool.show()
|
||||||
|
|
||||||
def click_run_button(self):
|
def click_run_button(self):
|
||||||
|
@ -462,7 +459,7 @@ class Toolbar(ttk.Frame):
|
||||||
self.app.canvas.annotation_type = ShapeType.MARKER
|
self.app.canvas.annotation_type = ShapeType.MARKER
|
||||||
if self.marker_tool:
|
if self.marker_tool:
|
||||||
self.marker_tool.destroy()
|
self.marker_tool.destroy()
|
||||||
self.marker_tool = MarkerDialog(self.master, self.app)
|
self.marker_tool = MarkerDialog(self.app, self.app)
|
||||||
self.marker_tool.show()
|
self.marker_tool.show()
|
||||||
|
|
||||||
def click_two_node_button(self):
|
def click_two_node_button(self):
|
||||||
|
|
|
@ -9,7 +9,7 @@ class Tooltip(object):
|
||||||
Create tool tip for a given widget
|
Create tool tip for a given widget
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, widget, text="widget info"):
|
def __init__(self, widget: tk.Widget, text: str = "widget info"):
|
||||||
self.widget = widget
|
self.widget = widget
|
||||||
self.text = text
|
self.text = text
|
||||||
self.widget.bind("<Enter>", self.on_enter)
|
self.widget.bind("<Enter>", self.on_enter)
|
||||||
|
@ -18,10 +18,10 @@ class Tooltip(object):
|
||||||
self.id = None
|
self.id = None
|
||||||
self.tw = None
|
self.tw = None
|
||||||
|
|
||||||
def on_enter(self, event=None):
|
def on_enter(self, event: tk.Event = None):
|
||||||
self.schedule()
|
self.schedule()
|
||||||
|
|
||||||
def on_leave(self, event=None):
|
def on_leave(self, event: tk.Event = None):
|
||||||
self.unschedule()
|
self.unschedule()
|
||||||
self.close(event)
|
self.close(event)
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ class Tooltip(object):
|
||||||
if id_:
|
if id_:
|
||||||
self.widget.after_cancel(id_)
|
self.widget.after_cancel(id_)
|
||||||
|
|
||||||
def enter(self, event=None):
|
def enter(self, event: tk.Event = None):
|
||||||
x, y, cx, cy = self.widget.bbox("insert")
|
x, y, cx, cy = self.widget.bbox("insert")
|
||||||
x += self.widget.winfo_rootx()
|
x += self.widget.winfo_rootx()
|
||||||
y += self.widget.winfo_rooty() + 32
|
y += self.widget.winfo_rooty() + 32
|
||||||
|
@ -50,6 +50,6 @@ class Tooltip(object):
|
||||||
label = ttk.Label(frame, text=self.text, style=Styles.tooltip)
|
label = ttk.Label(frame, text=self.text, style=Styles.tooltip)
|
||||||
label.grid()
|
label.grid()
|
||||||
|
|
||||||
def close(self, event=None):
|
def close(self, event: tk.Event = None):
|
||||||
if self.tw:
|
if self.tw:
|
||||||
self.tw.destroy()
|
self.tw.destroy()
|
||||||
|
|
|
@ -3,13 +3,17 @@ input validation
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
from netaddr import IPNetwork
|
from netaddr import IPNetwork
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
|
||||||
|
|
||||||
class InputValidation:
|
class InputValidation:
|
||||||
def __init__(self, app):
|
def __init__(self, app: "Application"):
|
||||||
self.master = app.master
|
self.master = app.master
|
||||||
self.positive_int = None
|
self.positive_int = None
|
||||||
self.positive_float = None
|
self.positive_float = None
|
||||||
|
@ -27,7 +31,7 @@ class InputValidation:
|
||||||
self.rgb = self.master.register(self.check_rbg)
|
self.rgb = self.master.register(self.check_rbg)
|
||||||
self.hex = self.master.register(self.check_hex)
|
self.hex = self.master.register(self.check_hex)
|
||||||
|
|
||||||
def ip_focus_out(self, event):
|
def ip_focus_out(self, event: tk.Event):
|
||||||
value = event.widget.get()
|
value = event.widget.get()
|
||||||
try:
|
try:
|
||||||
IPNetwork(value)
|
IPNetwork(value)
|
||||||
|
@ -35,12 +39,12 @@ class InputValidation:
|
||||||
event.widget.delete(0, tk.END)
|
event.widget.delete(0, tk.END)
|
||||||
event.widget.insert(tk.END, "invalid")
|
event.widget.insert(tk.END, "invalid")
|
||||||
|
|
||||||
def focus_out(self, event, default):
|
def focus_out(self, event: tk.Event, default: str):
|
||||||
value = event.widget.get()
|
value = event.widget.get()
|
||||||
if value == "":
|
if value == "":
|
||||||
event.widget.insert(tk.END, default)
|
event.widget.insert(tk.END, default)
|
||||||
|
|
||||||
def check_positive_int(self, s):
|
def check_positive_int(self, s: str) -> bool:
|
||||||
if len(s) == 0:
|
if len(s) == 0:
|
||||||
return True
|
return True
|
||||||
try:
|
try:
|
||||||
|
@ -51,7 +55,7 @@ class InputValidation:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_positive_float(self, s):
|
def check_positive_float(self, s: str) -> bool:
|
||||||
if len(s) == 0:
|
if len(s) == 0:
|
||||||
return True
|
return True
|
||||||
try:
|
try:
|
||||||
|
@ -62,7 +66,7 @@ class InputValidation:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_node_name(self, s):
|
def check_node_name(self, s: str) -> bool:
|
||||||
if len(s) < 0:
|
if len(s) < 0:
|
||||||
return False
|
return False
|
||||||
if len(s) == 0:
|
if len(s) == 0:
|
||||||
|
@ -72,7 +76,7 @@ class InputValidation:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def check_canvas_int(sefl, s):
|
def check_canvas_int(self, s: str) -> bool:
|
||||||
if len(s) == 0:
|
if len(s) == 0:
|
||||||
return True
|
return True
|
||||||
try:
|
try:
|
||||||
|
@ -83,7 +87,7 @@ class InputValidation:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_canvas_float(self, s):
|
def check_canvas_float(self, s: str) -> bool:
|
||||||
if not s:
|
if not s:
|
||||||
return True
|
return True
|
||||||
try:
|
try:
|
||||||
|
@ -94,7 +98,7 @@ class InputValidation:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_ip4(self, s):
|
def check_ip4(self, s: str) -> bool:
|
||||||
if not s:
|
if not s:
|
||||||
return True
|
return True
|
||||||
pat = re.compile("^([0-9]+[.])*[0-9]*$")
|
pat = re.compile("^([0-9]+[.])*[0-9]*$")
|
||||||
|
@ -113,7 +117,7 @@ class InputValidation:
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_rbg(self, s):
|
def check_rbg(self, s: str) -> bool:
|
||||||
if not s:
|
if not s:
|
||||||
return True
|
return True
|
||||||
if s.startswith("0") and len(s) >= 2:
|
if s.startswith("0") and len(s) >= 2:
|
||||||
|
@ -127,7 +131,7 @@ class InputValidation:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_hex(self, s):
|
def check_hex(self, s: str) -> bool:
|
||||||
if not s:
|
if not s:
|
||||||
return True
|
return True
|
||||||
pat = re.compile("^([#]([0-9]|[a-f])+)$|^[#]$")
|
pat = re.compile("^([#]([0-9]|[a-f])+)$|^[#]$")
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
import logging
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from pathlib import PosixPath
|
||||||
from tkinter import filedialog, font, ttk
|
from tkinter import filedialog, font, ttk
|
||||||
|
from typing import TYPE_CHECKING, Dict
|
||||||
|
|
||||||
from core.api.grpc import core_pb2
|
from core.api.grpc import core_pb2
|
||||||
from core.gui import themes
|
from core.gui import themes
|
||||||
from core.gui.themes import FRAME_PAD, PADX, PADY
|
from core.gui.themes import FRAME_PAD, PADX, PADY
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from core.gui.app import Application
|
||||||
|
from core.gui.dialogs.dialog import Dialog
|
||||||
|
|
||||||
INT_TYPES = {
|
INT_TYPES = {
|
||||||
core_pb2.ConfigOptionType.UINT8,
|
core_pb2.ConfigOptionType.UINT8,
|
||||||
core_pb2.ConfigOptionType.UINT16,
|
core_pb2.ConfigOptionType.UINT16,
|
||||||
|
@ -19,14 +25,14 @@ INT_TYPES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def file_button_click(value, parent):
|
def file_button_click(value: tk.StringVar, parent: tk.Widget):
|
||||||
file_path = filedialog.askopenfilename(title="Select File", parent=parent)
|
file_path = filedialog.askopenfilename(title="Select File", parent=parent)
|
||||||
if file_path:
|
if file_path:
|
||||||
value.set(file_path)
|
value.set(file_path)
|
||||||
|
|
||||||
|
|
||||||
class FrameScroll(ttk.Frame):
|
class FrameScroll(ttk.Frame):
|
||||||
def __init__(self, master, app, _cls=ttk.Frame, **kw):
|
def __init__(self, master: tk.Widget, app: "Application", _cls=ttk.Frame, **kw):
|
||||||
super().__init__(master, **kw)
|
super().__init__(master, **kw)
|
||||||
self.app = app
|
self.app = app
|
||||||
self.rowconfigure(0, weight=1)
|
self.rowconfigure(0, weight=1)
|
||||||
|
@ -49,13 +55,13 @@ class FrameScroll(ttk.Frame):
|
||||||
self.frame.bind("<Configure>", self._configure_frame)
|
self.frame.bind("<Configure>", self._configure_frame)
|
||||||
self.canvas.bind("<Configure>", self._configure_canvas)
|
self.canvas.bind("<Configure>", self._configure_canvas)
|
||||||
|
|
||||||
def _configure_frame(self, event):
|
def _configure_frame(self, event: tk.Event):
|
||||||
req_width = self.frame.winfo_reqwidth()
|
req_width = self.frame.winfo_reqwidth()
|
||||||
if req_width != self.canvas.winfo_reqwidth():
|
if req_width != self.canvas.winfo_reqwidth():
|
||||||
self.canvas.configure(width=req_width)
|
self.canvas.configure(width=req_width)
|
||||||
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
|
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
|
||||||
|
|
||||||
def _configure_canvas(self, event):
|
def _configure_canvas(self, event: tk.Event):
|
||||||
self.canvas.itemconfig(self.frame_id, width=event.width)
|
self.canvas.itemconfig(self.frame_id, width=event.width)
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
|
@ -64,7 +70,13 @@ class FrameScroll(ttk.Frame):
|
||||||
|
|
||||||
|
|
||||||
class ConfigFrame(ttk.Notebook):
|
class ConfigFrame(ttk.Notebook):
|
||||||
def __init__(self, master, app, config, **kw):
|
def __init__(
|
||||||
|
self,
|
||||||
|
master: tk.Widget,
|
||||||
|
app: "Application",
|
||||||
|
config: Dict[str, core_pb2.ConfigOption],
|
||||||
|
**kw
|
||||||
|
):
|
||||||
super().__init__(master, **kw)
|
super().__init__(master, **kw)
|
||||||
self.app = app
|
self.app = app
|
||||||
self.config = config
|
self.config = config
|
||||||
|
@ -174,7 +186,7 @@ class ConfigFrame(ttk.Notebook):
|
||||||
|
|
||||||
|
|
||||||
class ListboxScroll(ttk.Frame):
|
class ListboxScroll(ttk.Frame):
|
||||||
def __init__(self, master=None, **kw):
|
def __init__(self, master: tk.Widget = None, **kw):
|
||||||
super().__init__(master, **kw)
|
super().__init__(master, **kw)
|
||||||
self.columnconfigure(0, weight=1)
|
self.columnconfigure(0, weight=1)
|
||||||
self.rowconfigure(0, weight=1)
|
self.rowconfigure(0, weight=1)
|
||||||
|
@ -189,12 +201,12 @@ class ListboxScroll(ttk.Frame):
|
||||||
|
|
||||||
|
|
||||||
class CheckboxList(FrameScroll):
|
class CheckboxList(FrameScroll):
|
||||||
def __init__(self, master, app, clicked=None, **kw):
|
def __init__(self, master: ttk.Widget, app: "Application", clicked=None, **kw):
|
||||||
super().__init__(master, app, **kw)
|
super().__init__(master, app, **kw)
|
||||||
self.clicked = clicked
|
self.clicked = clicked
|
||||||
self.frame.columnconfigure(0, weight=1)
|
self.frame.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
def add(self, name, checked):
|
def add(self, name: str, checked: bool):
|
||||||
var = tk.BooleanVar(value=checked)
|
var = tk.BooleanVar(value=checked)
|
||||||
func = partial(self.clicked, name, var)
|
func = partial(self.clicked, name, var)
|
||||||
checkbox = ttk.Checkbutton(self.frame, text=name, variable=var, command=func)
|
checkbox = ttk.Checkbutton(self.frame, text=name, variable=var, command=func)
|
||||||
|
@ -207,7 +219,7 @@ class CodeFont(font.Font):
|
||||||
|
|
||||||
|
|
||||||
class CodeText(ttk.Frame):
|
class CodeText(ttk.Frame):
|
||||||
def __init__(self, master, **kwargs):
|
def __init__(self, master: tk.Widget, **kwargs):
|
||||||
super().__init__(master, **kwargs)
|
super().__init__(master, **kwargs)
|
||||||
self.rowconfigure(0, weight=1)
|
self.rowconfigure(0, weight=1)
|
||||||
self.columnconfigure(0, weight=1)
|
self.columnconfigure(0, weight=1)
|
||||||
|
@ -231,14 +243,14 @@ class CodeText(ttk.Frame):
|
||||||
|
|
||||||
|
|
||||||
class Spinbox(ttk.Entry):
|
class Spinbox(ttk.Entry):
|
||||||
def __init__(self, master=None, **kwargs):
|
def __init__(self, master: tk.Widget = None, **kwargs):
|
||||||
super().__init__(master, "ttk::spinbox", **kwargs)
|
super().__init__(master, "ttk::spinbox", **kwargs)
|
||||||
|
|
||||||
def set(self, value):
|
def set(self, value):
|
||||||
self.tk.call(self._w, "set", value)
|
self.tk.call(self._w, "set", value)
|
||||||
|
|
||||||
|
|
||||||
def image_chooser(parent, path):
|
def image_chooser(parent: "Dialog", path: PosixPath):
|
||||||
return filedialog.askopenfilename(
|
return filedialog.askopenfilename(
|
||||||
parent=parent,
|
parent=parent,
|
||||||
initialdir=str(path),
|
initialdir=str(path),
|
||||||
|
|
9
ns3/build/lib/corens3/__init__.py
Normal file
9
ns3/build/lib/corens3/__init__.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
"""
|
||||||
|
corens3
|
||||||
|
|
||||||
|
Python package containing CORE components for use
|
||||||
|
with the ns-3 simulator.
|
||||||
|
|
||||||
|
See http://code.google.com/p/coreemu/
|
||||||
|
for more information on CORE.
|
||||||
|
"""
|
550
ns3/build/lib/corens3/obj.py
Normal file
550
ns3/build/lib/corens3/obj.py
Normal file
|
@ -0,0 +1,550 @@
|
||||||
|
"""
|
||||||
|
ns3.py: defines classes for running emulations with ns-3 simulated networks.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
import ns.core
|
||||||
|
import ns.internet
|
||||||
|
import ns.lte
|
||||||
|
import ns.mobility
|
||||||
|
import ns.network
|
||||||
|
import ns.tap_bridge
|
||||||
|
import ns.wifi
|
||||||
|
import ns.wimax
|
||||||
|
|
||||||
|
from core import constants
|
||||||
|
from core.emulator.enumerations import EventTypes
|
||||||
|
from core.emulator.enumerations import LinkTypes
|
||||||
|
from core.emulator.enumerations import NodeTypes
|
||||||
|
from core.utils import make_tuple
|
||||||
|
from core.location.mobility import WayPointMobility
|
||||||
|
from core.nodes.base import CoreNode, CoreNetworkBase
|
||||||
|
from core.emulator.session import Session
|
||||||
|
|
||||||
|
ns.core.GlobalValue.Bind(
|
||||||
|
"SimulatorImplementationType",
|
||||||
|
ns.core.StringValue("ns3::RealtimeSimulatorImpl")
|
||||||
|
)
|
||||||
|
ns.core.GlobalValue.Bind(
|
||||||
|
"ChecksumEnabled",
|
||||||
|
ns.core.BooleanValue("true")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CoreNs3Node(CoreNode, ns.network.Node):
|
||||||
|
"""
|
||||||
|
The CoreNs3Node is both a CoreNode backed by a network namespace and
|
||||||
|
an ns-3 Node simulator object. When linked to simulated networks, the TunTap
|
||||||
|
device will be used.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
ns.network.Node.__init__(self)
|
||||||
|
# ns-3 ID starts at 0, CORE uses 1
|
||||||
|
_id = self.GetId() + 1
|
||||||
|
if '_id' not in kwds:
|
||||||
|
kwds['_id'] = _id
|
||||||
|
CoreNode.__init__(self, *args, **kwds)
|
||||||
|
|
||||||
|
def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None):
|
||||||
|
"""
|
||||||
|
Add a network interface. If we are attaching to a CoreNs3Net, this
|
||||||
|
will be a TunTap. Otherwise dispatch to CoreNode.newnetif().
|
||||||
|
"""
|
||||||
|
if not addrlist:
|
||||||
|
addrlist = []
|
||||||
|
|
||||||
|
if not isinstance(net, CoreNs3Net):
|
||||||
|
return CoreNode.newnetif(self, net, addrlist, hwaddr, ifindex, ifname)
|
||||||
|
ifindex = self.newtuntap(ifindex, ifname)
|
||||||
|
self.attachnet(ifindex, net)
|
||||||
|
netif = self.netif(ifindex)
|
||||||
|
netif.sethwaddr(hwaddr)
|
||||||
|
for addr in make_tuple(addrlist):
|
||||||
|
netif.addaddr(addr)
|
||||||
|
|
||||||
|
addrstr = netif.addrlist[0]
|
||||||
|
addr, mask = addrstr.split('/')
|
||||||
|
tap = net._tapdevs[netif]
|
||||||
|
tap.SetAttribute(
|
||||||
|
"IpAddress",
|
||||||
|
ns.network.Ipv4AddressValue(ns.network.Ipv4Address(addr))
|
||||||
|
)
|
||||||
|
tap.SetAttribute(
|
||||||
|
"Netmask",
|
||||||
|
ns.network.Ipv4MaskValue(ns.network.Ipv4Mask(f"/{mask}"))
|
||||||
|
)
|
||||||
|
ns.core.Simulator.Schedule(ns.core.Time("0"), netif.install)
|
||||||
|
return ifindex
|
||||||
|
|
||||||
|
def getns3position(self):
|
||||||
|
"""
|
||||||
|
Return the ns-3 (x, y, z) position of a node.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
mm = self.GetObject(ns.mobility.MobilityModel.GetTypeId())
|
||||||
|
pos = mm.GetPosition()
|
||||||
|
return pos.x, pos.y, pos.z
|
||||||
|
except AttributeError:
|
||||||
|
self.warn("ns-3 mobility model not found")
|
||||||
|
return 0, 0, 0
|
||||||
|
|
||||||
|
def setns3position(self, x, y, z):
|
||||||
|
"""
|
||||||
|
Set the ns-3 (x, y, z) position of a node.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
mm = self.GetObject(ns.mobility.MobilityModel.GetTypeId())
|
||||||
|
if z is None:
|
||||||
|
z = 0.0
|
||||||
|
mm.SetPosition(ns.core.Vector(x, y, z))
|
||||||
|
except AttributeError:
|
||||||
|
self.warn("ns-3 mobility model not found, not setting position")
|
||||||
|
|
||||||
|
|
||||||
|
class CoreNs3Net(CoreNetworkBase):
|
||||||
|
"""
|
||||||
|
The CoreNs3Net is a helper PyCoreNet object. Networks are represented
|
||||||
|
entirely in simulation with the TunTap device bridging the emulated and
|
||||||
|
simulated worlds.
|
||||||
|
"""
|
||||||
|
apitype = NodeTypes.WIRELESS_LAN.value
|
||||||
|
linktype = LinkTypes.WIRELESS.value
|
||||||
|
# icon used
|
||||||
|
type = "wlan"
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, session, _id=None, name=None, start=True, server=None
|
||||||
|
):
|
||||||
|
CoreNetworkBase.__init__(self, session, _id, name, start, server)
|
||||||
|
self.tapbridge = ns.tap_bridge.TapBridgeHelper()
|
||||||
|
self._ns3devs = {}
|
||||||
|
self._tapdevs = {}
|
||||||
|
|
||||||
|
def attach(self, netif):
|
||||||
|
"""
|
||||||
|
Invoked from netif.attach(). Create a TAP device using the TapBridge
|
||||||
|
object. Call getns3dev() to get model-specific device.
|
||||||
|
"""
|
||||||
|
self._netif[netif] = netif
|
||||||
|
self._linked[netif] = {}
|
||||||
|
ns3dev = self.getns3dev(netif.node)
|
||||||
|
tap = self.tapbridge.Install(netif.node, ns3dev)
|
||||||
|
tap.SetMode(ns.tap_bridge.TapBridge.CONFIGURE_LOCAL)
|
||||||
|
tap.SetAttribute(
|
||||||
|
"DeviceName",
|
||||||
|
ns.core.StringValue(netif.localname)
|
||||||
|
)
|
||||||
|
self._ns3devs[netif] = ns3dev
|
||||||
|
self._tapdevs[netif] = tap
|
||||||
|
|
||||||
|
def getns3dev(self, node):
|
||||||
|
"""
|
||||||
|
Implement depending on network helper. Install this network onto
|
||||||
|
the given node and return the device. Register the ns3 device into
|
||||||
|
self._ns3devs
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def findns3dev(self, node):
|
||||||
|
"""
|
||||||
|
Given a node, return the interface and ns3 device associated with
|
||||||
|
this network.
|
||||||
|
"""
|
||||||
|
for netif in node.netifs():
|
||||||
|
if netif in self._ns3devs:
|
||||||
|
return netif, self._ns3devs[netif]
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
"""
|
||||||
|
Session.shutdown() will invoke this.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def usecorepositions(self):
|
||||||
|
"""
|
||||||
|
Set position callbacks for interfaces on this net so the CORE GUI
|
||||||
|
can update the ns-3 node position when moved with the mouse.
|
||||||
|
"""
|
||||||
|
for netif in self.netifs():
|
||||||
|
netif.poshook = self.setns3position
|
||||||
|
|
||||||
|
def setns3position(self, netif, x, y, z):
|
||||||
|
logging.info("setns3position: %s (%s, %s, %s)", netif.node.name, x, y, z)
|
||||||
|
netif.node.setns3position(x, y, z)
|
||||||
|
|
||||||
|
|
||||||
|
class Ns3LteNet(CoreNs3Net):
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
"""
|
||||||
|
Uses a LteHelper to create an ns-3 based LTE network.
|
||||||
|
"""
|
||||||
|
CoreNs3Net.__init__(self, *args, **kwds)
|
||||||
|
self.lte = ns.lte.LteHelper()
|
||||||
|
# enhanced NodeB node list
|
||||||
|
self.enbnodes = []
|
||||||
|
self.dlsubchannels = None
|
||||||
|
self.ulsubchannels = None
|
||||||
|
|
||||||
|
def setsubchannels(self, downlink, uplink):
|
||||||
|
"""
|
||||||
|
Set the downlink/uplink subchannels, which are a list of ints.
|
||||||
|
These should be set prior to using CoreNs3Node.newnetif().
|
||||||
|
"""
|
||||||
|
self.dlsubchannels = downlink
|
||||||
|
self.ulsubchannels = uplink
|
||||||
|
|
||||||
|
def setnodeb(self, node):
|
||||||
|
"""
|
||||||
|
Mark the given node as a nodeb (base transceiver station)
|
||||||
|
"""
|
||||||
|
self.enbnodes.append(node)
|
||||||
|
|
||||||
|
def linknodeb(self, node, nodeb, mob, mobb):
|
||||||
|
"""
|
||||||
|
Register user equipment with a nodeb.
|
||||||
|
Optionally install mobility model while we have the ns-3 devs handy.
|
||||||
|
"""
|
||||||
|
_tmp, nodebdev = self.findns3dev(nodeb)
|
||||||
|
_tmp, dev = self.findns3dev(node)
|
||||||
|
if nodebdev is None or dev is None:
|
||||||
|
raise KeyError("ns-3 device for node not found")
|
||||||
|
self.lte.RegisterUeToTheEnb(dev, nodebdev)
|
||||||
|
if mob:
|
||||||
|
self.lte.AddMobility(dev.GetPhy(), mob)
|
||||||
|
if mobb:
|
||||||
|
self.lte.AddDownlinkChannelRealization(mobb, mob, dev.GetPhy())
|
||||||
|
|
||||||
|
def getns3dev(self, node):
|
||||||
|
"""
|
||||||
|
Get the ns3 NetDevice using the LteHelper.
|
||||||
|
"""
|
||||||
|
if node in self.enbnodes:
|
||||||
|
devtype = ns.lte.LteHelper.DEVICE_TYPE_ENODEB
|
||||||
|
else:
|
||||||
|
devtype = ns.lte.LteHelper.DEVICE_TYPE_USER_EQUIPMENT
|
||||||
|
nodes = ns.network.NodeContainer(node)
|
||||||
|
devs = self.lte.Install(nodes, devtype)
|
||||||
|
devs.Get(0).GetPhy().SetDownlinkSubChannels(self.dlsubchannels)
|
||||||
|
devs.Get(0).GetPhy().SetUplinkSubChannels(self.ulsubchannels)
|
||||||
|
return devs.Get(0)
|
||||||
|
|
||||||
|
def attach(self, netif):
|
||||||
|
"""
|
||||||
|
Invoked from netif.attach(). Create a TAP device using the TapBridge
|
||||||
|
object. Call getns3dev() to get model-specific device.
|
||||||
|
"""
|
||||||
|
self._netif[netif] = netif
|
||||||
|
self._linked[netif] = {}
|
||||||
|
ns3dev = self.getns3dev(netif.node)
|
||||||
|
self.tapbridge.SetAttribute("Mode", ns.core.StringValue("UseLocal"))
|
||||||
|
# self.tapbridge.SetAttribute("Mode",
|
||||||
|
# ns.core.IntegerValue(ns.tap_bridge.TapBridge.USE_LOCAL))
|
||||||
|
tap = self.tapbridge.Install(netif.node, ns3dev)
|
||||||
|
# tap.SetMode(ns.tap_bridge.TapBridge.USE_LOCAL)
|
||||||
|
logging.info("using TAP device %s for %s/%s", netif.localname, netif.node.name, netif.name)
|
||||||
|
subprocess.check_call(['tunctl', '-t', netif.localname, '-n'])
|
||||||
|
# check_call([IP_BIN, 'link', 'set', 'dev', netif.localname, \
|
||||||
|
# 'address', '%s' % netif.hwaddr])
|
||||||
|
subprocess.check_call([constants.IP_BIN, 'link', 'set', netif.localname, 'up'])
|
||||||
|
tap.SetAttribute("DeviceName", ns.core.StringValue(netif.localname))
|
||||||
|
self._ns3devs[netif] = ns3dev
|
||||||
|
self._tapdevs[netif] = tap
|
||||||
|
|
||||||
|
|
||||||
|
class Ns3WifiNet(CoreNs3Net):
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
"""
|
||||||
|
Uses a WifiHelper to create an ns-3 based Wifi network.
|
||||||
|
"""
|
||||||
|
rate = kwds.pop('rate', 'OfdmRate54Mbps')
|
||||||
|
CoreNs3Net.__init__(self, *args, **kwds)
|
||||||
|
self.wifi = ns.wifi.WifiHelper().Default()
|
||||||
|
self.wifi.SetStandard(ns.wifi.WIFI_PHY_STANDARD_80211a)
|
||||||
|
self.wifi.SetRemoteStationManager(
|
||||||
|
"ns3::ConstantRateWifiManager",
|
||||||
|
"DataMode",
|
||||||
|
ns.core.StringValue(rate),
|
||||||
|
"NonUnicastMode",
|
||||||
|
ns.core.StringValue(rate)
|
||||||
|
)
|
||||||
|
self.mac = ns.wifi.NqosWifiMacHelper.Default()
|
||||||
|
self.mac.SetType("ns3::AdhocWifiMac")
|
||||||
|
|
||||||
|
channel = ns.wifi.YansWifiChannelHelper.Default()
|
||||||
|
self.phy = ns.wifi.YansWifiPhyHelper.Default()
|
||||||
|
self.phy.SetChannel(channel.Create())
|
||||||
|
|
||||||
|
def getns3dev(self, node):
|
||||||
|
"""
|
||||||
|
Get the ns3 NetDevice using the WifiHelper.
|
||||||
|
"""
|
||||||
|
devs = self.wifi.Install(self.phy, self.mac, node)
|
||||||
|
return devs.Get(0)
|
||||||
|
|
||||||
|
|
||||||
|
class Ns3WimaxNet(CoreNs3Net):
|
||||||
|
def __init__(self, *args, **kwds):
|
||||||
|
CoreNs3Net.__init__(self, *args, **kwds)
|
||||||
|
self.wimax = ns.wimax.WimaxHelper()
|
||||||
|
self.scheduler = ns.wimax.WimaxHelper.SCHED_TYPE_SIMPLE
|
||||||
|
self.phy = ns.wimax.WimaxHelper.SIMPLE_PHY_TYPE_OFDM
|
||||||
|
# base station node list
|
||||||
|
self.bsnodes = []
|
||||||
|
|
||||||
|
def setbasestation(self, node):
|
||||||
|
self.bsnodes.append(node)
|
||||||
|
|
||||||
|
def getns3dev(self, node):
|
||||||
|
if node in self.bsnodes:
|
||||||
|
devtype = ns.wimax.WimaxHelper.DEVICE_TYPE_BASE_STATION
|
||||||
|
else:
|
||||||
|
devtype = ns.wimax.WimaxHelper.DEVICE_TYPE_SUBSCRIBER_STATION
|
||||||
|
nodes = ns.network.NodeContainer(node)
|
||||||
|
devs = self.wimax.Install(nodes, devtype, self.phy, self.scheduler)
|
||||||
|
if node not in self.bsnodes:
|
||||||
|
devs.Get(0).SetModulationType(ns.wimax.WimaxPhy.MODULATION_TYPE_QAM16_12)
|
||||||
|
# debug
|
||||||
|
self.wimax.EnableAscii("wimax-device-%s" % node.name, devs)
|
||||||
|
return devs.Get(0)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def ipv4netifaddr(netif):
|
||||||
|
for addr in netif.addrlist:
|
||||||
|
if ':' in addr:
|
||||||
|
# skip ipv6
|
||||||
|
continue
|
||||||
|
ip = ns.network.Ipv4Address(addr.split('/')[0])
|
||||||
|
mask = ns.network.Ipv4Mask('/' + addr.split('/')[1])
|
||||||
|
return ip, mask
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
def addflow(self, node1, node2, upclass, downclass):
|
||||||
|
"""
|
||||||
|
Add a Wimax service flow between two nodes.
|
||||||
|
"""
|
||||||
|
netif1, ns3dev1 = self.findns3dev(node1)
|
||||||
|
netif2, ns3dev2 = self.findns3dev(node2)
|
||||||
|
if not netif1 or not netif2:
|
||||||
|
raise ValueError("interface not found")
|
||||||
|
addr1, mask1 = self.ipv4netifaddr(netif1)
|
||||||
|
addr2, mask2 = self.ipv4netifaddr(netif2)
|
||||||
|
clargs1 = (addr1, mask1, addr2, mask2) + downclass
|
||||||
|
clargs2 = (addr2, mask2, addr1, mask1) + upclass
|
||||||
|
clrec1 = ns.wimax.IpcsClassifierRecord(*clargs1)
|
||||||
|
clrec2 = ns.wimax.IpcsClassifierRecord(*clargs2)
|
||||||
|
ns3dev1.AddServiceFlow(self.wimax.CreateServiceFlow(
|
||||||
|
ns.wimax.ServiceFlow.SF_DIRECTION_DOWN,
|
||||||
|
ns.wimax.ServiceFlow.SF_TYPE_RTPS, clrec1)
|
||||||
|
)
|
||||||
|
ns3dev1.AddServiceFlow(self.wimax.CreateServiceFlow(
|
||||||
|
ns.wimax.ServiceFlow.SF_DIRECTION_UP,
|
||||||
|
ns.wimax.ServiceFlow.SF_TYPE_RTPS, clrec2)
|
||||||
|
)
|
||||||
|
ns3dev2.AddServiceFlow(self.wimax.CreateServiceFlow(
|
||||||
|
ns.wimax.ServiceFlow.SF_DIRECTION_DOWN,
|
||||||
|
ns.wimax.ServiceFlow.SF_TYPE_RTPS, clrec2)
|
||||||
|
)
|
||||||
|
ns3dev2.AddServiceFlow(self.wimax.CreateServiceFlow(
|
||||||
|
ns.wimax.ServiceFlow.SF_DIRECTION_UP,
|
||||||
|
ns.wimax.ServiceFlow.SF_TYPE_RTPS, clrec1)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Ns3Session(Session):
|
||||||
|
"""
|
||||||
|
A Session that starts an ns-3 simulation thread.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, _id, persistent=False, duration=600):
|
||||||
|
self.duration = duration
|
||||||
|
self.nodes = ns.network.NodeContainer()
|
||||||
|
self.mobhelper = ns.mobility.MobilityHelper()
|
||||||
|
Session.__init__(self, _id)
|
||||||
|
|
||||||
|
def run(self, vis=False):
|
||||||
|
"""
|
||||||
|
Run the ns-3 simulation and return the simulator thread.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def runthread():
|
||||||
|
ns.core.Simulator.Stop(ns.core.Seconds(self.duration))
|
||||||
|
logging.info("running ns-3 simulation for %d seconds", self.duration)
|
||||||
|
if vis:
|
||||||
|
try:
|
||||||
|
import visualizer
|
||||||
|
except ImportError:
|
||||||
|
logging.exception("visualizer is not available")
|
||||||
|
ns.core.Simulator.Run()
|
||||||
|
else:
|
||||||
|
visualizer.start()
|
||||||
|
else:
|
||||||
|
ns.core.Simulator.Run()
|
||||||
|
|
||||||
|
# self.evq.run() # event queue may have WayPointMobility events
|
||||||
|
self.set_state(EventTypes.RUNTIME_STATE, send_event=True)
|
||||||
|
t = threading.Thread(target=runthread)
|
||||||
|
t.daemon = True
|
||||||
|
t.start()
|
||||||
|
return t
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
# TODO: the following line tends to segfault ns-3 (and therefore core-daemon)
|
||||||
|
ns.core.Simulator.Destroy()
|
||||||
|
Session.shutdown(self)
|
||||||
|
|
||||||
|
def addnode(self, name):
|
||||||
|
"""
|
||||||
|
A convenience helper for Session.addobj(), for adding CoreNs3Nodes
|
||||||
|
to this session. Keeps a NodeContainer for later use.
|
||||||
|
"""
|
||||||
|
n = self.create_node(cls=CoreNs3Node, name=name)
|
||||||
|
self.nodes.Add(n)
|
||||||
|
return n
|
||||||
|
|
||||||
|
def setupconstantmobility(self):
|
||||||
|
"""
|
||||||
|
Install a ConstantPositionMobilityModel.
|
||||||
|
"""
|
||||||
|
palloc = ns.mobility.ListPositionAllocator()
|
||||||
|
for i in xrange(self.nodes.GetN()):
|
||||||
|
(x, y, z) = ((100.0 * i) + 50, 200.0, 0.0)
|
||||||
|
palloc.Add(ns.core.Vector(x, y, z))
|
||||||
|
node = self.nodes.Get(i)
|
||||||
|
node.position.set(x, y, z)
|
||||||
|
self.mobhelper.SetPositionAllocator(palloc)
|
||||||
|
self.mobhelper.SetMobilityModel("ns3::ConstantPositionMobilityModel")
|
||||||
|
self.mobhelper.Install(self.nodes)
|
||||||
|
|
||||||
|
def setuprandomwalkmobility(self, bounds, time=10, speed=25.0):
|
||||||
|
"""
|
||||||
|
Set up the random walk mobility model within a bounding box.
|
||||||
|
- bounds is the max (x, y, z) boundary
|
||||||
|
- time is the number of seconds to maintain the current speed
|
||||||
|
and direction
|
||||||
|
- speed is the maximum speed, with node speed randomly chosen
|
||||||
|
from [0, speed]
|
||||||
|
"""
|
||||||
|
x, y, z = map(float, bounds)
|
||||||
|
self.mobhelper.SetPositionAllocator(
|
||||||
|
"ns3::RandomBoxPositionAllocator",
|
||||||
|
"X",
|
||||||
|
ns.core.StringValue("ns3::UniformRandomVariable[Min=0|Max=%s]" % x),
|
||||||
|
"Y",
|
||||||
|
ns.core.StringValue("ns3::UniformRandomVariable[Min=0|Max=%s]" % y),
|
||||||
|
"Z",
|
||||||
|
ns.core.StringValue("ns3::UniformRandomVariable[Min=0|Max=%s]" % z)
|
||||||
|
)
|
||||||
|
self.mobhelper.SetMobilityModel(
|
||||||
|
"ns3::RandomWalk2dMobilityModel",
|
||||||
|
"Mode", ns.core.StringValue("Time"),
|
||||||
|
"Time", ns.core.StringValue("%ss" % time),
|
||||||
|
"Speed",
|
||||||
|
ns.core.StringValue("ns3::UniformRandomVariable[Min=0|Max=%s]" % speed),
|
||||||
|
"Bounds", ns.core.StringValue("0|%s|0|%s" % (x, y))
|
||||||
|
)
|
||||||
|
self.mobhelper.Install(self.nodes)
|
||||||
|
|
||||||
|
def startns3mobility(self, refresh_ms=300):
|
||||||
|
"""
|
||||||
|
Start a thread that updates CORE nodes based on their ns-3
|
||||||
|
positions.
|
||||||
|
"""
|
||||||
|
self.set_state(EventTypes.INSTANTIATION_STATE)
|
||||||
|
self.mobilitythread = threading.Thread(
|
||||||
|
target=self.ns3mobilitythread,
|
||||||
|
args=(refresh_ms,))
|
||||||
|
self.mobilitythread.daemon = True
|
||||||
|
self.mobilitythread.start()
|
||||||
|
|
||||||
|
def ns3mobilitythread(self, refresh_ms):
|
||||||
|
"""
|
||||||
|
Thread target that updates CORE nodes every refresh_ms based on
|
||||||
|
their ns-3 positions.
|
||||||
|
"""
|
||||||
|
valid_states = (
|
||||||
|
EventTypes.RUNTIME_STATE.value,
|
||||||
|
EventTypes.INSTANTIATION_STATE.value
|
||||||
|
)
|
||||||
|
while self.state in valid_states:
|
||||||
|
for i in xrange(self.nodes.GetN()):
|
||||||
|
node = self.nodes.Get(i)
|
||||||
|
x, y, z = node.getns3position()
|
||||||
|
if (x, y, z) == node.position.get():
|
||||||
|
continue
|
||||||
|
# from WayPointMobility.setnodeposition(node, x, y, z)
|
||||||
|
node.position.set(x, y, z)
|
||||||
|
node_data = node.data(0)
|
||||||
|
self.broadcast_node(node_data)
|
||||||
|
self.sdt.updatenode(node.id, flags=0, x=x, y=y, z=z)
|
||||||
|
time.sleep(0.001 * refresh_ms)
|
||||||
|
|
||||||
|
def setupmobilitytracing(self, net, filename, nodes):
|
||||||
|
"""
|
||||||
|
Start a tracing thread using the ASCII output from the ns3
|
||||||
|
mobility helper.
|
||||||
|
"""
|
||||||
|
net.mobility = WayPointMobility(session=self, _id=net.id)
|
||||||
|
net.mobility.setendtime()
|
||||||
|
net.mobility.refresh_ms = 300
|
||||||
|
net.mobility.empty_queue_stop = False
|
||||||
|
of = ns.network.OutputStreamWrapper(filename, filemode=0o777)
|
||||||
|
self.mobhelper.EnableAsciiAll(of)
|
||||||
|
self.mobilitytracethread = threading.Thread(
|
||||||
|
target=self.mobilitytrace,
|
||||||
|
args=(net, filename, nodes)
|
||||||
|
)
|
||||||
|
self.mobilitytracethread.daemon = True
|
||||||
|
self.mobilitytracethread.start()
|
||||||
|
|
||||||
|
def mobilitytrace(self, net, filename, nodes, verbose):
|
||||||
|
nodemap = {}
|
||||||
|
# move nodes to initial positions
|
||||||
|
for node in nodes:
|
||||||
|
x, y, z = node.getns3position()
|
||||||
|
net.mobility.setnodeposition(node, x, y, z)
|
||||||
|
nodemap[node.GetId()] = node
|
||||||
|
|
||||||
|
logging.info("mobilitytrace opening '%s'", filename)
|
||||||
|
|
||||||
|
f = None
|
||||||
|
try:
|
||||||
|
f = open(filename)
|
||||||
|
f.seek(0, 2)
|
||||||
|
|
||||||
|
sleep = 0.001
|
||||||
|
kickstart = True
|
||||||
|
while True:
|
||||||
|
if self.state != EventTypes.RUNTIME_STATE.value:
|
||||||
|
break
|
||||||
|
line = f.readline()
|
||||||
|
if not line:
|
||||||
|
time.sleep(sleep)
|
||||||
|
if sleep < 1.0:
|
||||||
|
sleep += 0.001
|
||||||
|
continue
|
||||||
|
sleep = 0.001
|
||||||
|
items = dict(x.split("=") for x in line.split())
|
||||||
|
logging.info("trace: %s %s %s", items['node'], items['pos'], items['vel'])
|
||||||
|
x, y, z = map(float, items['pos'].split(':'))
|
||||||
|
vel = map(float, items['vel'].split(':'))
|
||||||
|
node = nodemap[int(items['node'])]
|
||||||
|
net.mobility.addwaypoint(time=0, nodenum=node.id, x=x, y=y, z=z, speed=vel)
|
||||||
|
if kickstart:
|
||||||
|
kickstart = False
|
||||||
|
self.event_loop.add_event(0, net.mobility.start)
|
||||||
|
self.event_loop.run()
|
||||||
|
else:
|
||||||
|
if net.mobility.state != net.mobility.STATE_RUNNING:
|
||||||
|
net.mobility.state = net.mobility.STATE_RUNNING
|
||||||
|
self.event_loop.add_event(0, net.mobility.runround)
|
||||||
|
except IOError:
|
||||||
|
logging.exception("mobilitytrace error opening: %s", filename)
|
||||||
|
finally:
|
||||||
|
if f:
|
||||||
|
f.close()
|
19
ns3/setup.py
Normal file
19
ns3/setup.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import glob
|
||||||
|
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
_EXAMPLES_DIR = "share/corens3/examples"
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="core-ns3",
|
||||||
|
version="5.5.2",
|
||||||
|
packages=[
|
||||||
|
"corens3",
|
||||||
|
],
|
||||||
|
data_files=[(_EXAMPLES_DIR, glob.glob("examples/*"))],
|
||||||
|
description="Python ns-3 components of CORE",
|
||||||
|
url="https://github.com/coreemu/core",
|
||||||
|
author="Boeing Research & Technology",
|
||||||
|
license="GPLv2",
|
||||||
|
long_description="Python scripts and modules for building virtual simulated networks."
|
||||||
|
)
|
Loading…
Add table
Reference in a new issue