diff --git a/coretk/coretk/coreclient.py b/coretk/coretk/coreclient.py index da75d3bd..f018a348 100644 --- a/coretk/coretk/coreclient.py +++ b/coretk/coretk/coreclient.py @@ -289,7 +289,6 @@ class CoreClient: logging.info("delete nodes %s", response) def delete_links(self, delete_session=None): - # sid = None if delete_session is None: sid = self.session_id else: @@ -312,7 +311,10 @@ class CoreClient: mobility_configs = self.get_mobility_configs_proto() emane_model_configs = self.get_emane_model_configs_proto() hooks = list(self.hooks.values()) - emane_config = {x: self.emane_config[x].value for x in self.emane_config} + if self.emane_config: + emane_config = {x: self.emane_config[x].value for x in self.emane_config} + else: + emane_config = None response = self.client.start_session( self.session_id, nodes, @@ -407,23 +409,6 @@ class CoreClient: else: return self.reusable.pop(0) - # def add_node(self, node_type, model, x, y, name, node_id): - # position = core_pb2.Position(x=x, y=y) - # node = core_pb2.Node(id=node_id, type=node_type, position=position, model=model) - # self.node_ids.append(node_id) - # response = self.client.add_node(self.session_id, node) - # logging.info("created node: %s", response) - # if node_type == core_pb2.NodeType.WIRELESS_LAN: - # d = OrderedDict() - # d["basic_range"] = "275" - # d["bandwidth"] = "54000000" - # d["jitter"] = "0" - # d["delay"] = "20000" - # d["error"] = "0" - # r = self.client.set_wlan_config(self.session_id, node_id, d) - # logging.debug("set wlan config %s", r) - # return response.node_id - def add_graph_node(self, session_id, canvas_id, x, y, name): """ Add node, with information filled in, to grpc manager @@ -478,6 +463,59 @@ class CoreClient: name, ) + def delete_wanted_graph_nodes(self, canvas_ids, tokens): + """ + remove the nodes selected by the user and anything related to that node + such as link, configurations, interfaces + + :param list(int) canvas_ids: list of canvas node ids + :return: nothing + """ + # keep reference to the core ids + core_node_ids = [self.nodes[x].node_id for x in canvas_ids] + node_interface_pairs = [] + + # delete the nodes + for i in canvas_ids: + try: + n = self.nodes.pop(i) + self.reusable.append(n.node_id) + except KeyError: + logging.error("coreclient.py INVALID NODE CANVAS ID") + + self.reusable.sort() + + # delete the edges and interfaces + for i in tokens: + try: + e = self.edges.pop(i) + if e.interface_1 is not None: + node_interface_pairs.append(tuple([e.id1, e.interface_1.id])) + if e.interface_2 is not None: + node_interface_pairs.append(tuple([e.id2, e.interface_2.id])) + + except KeyError: + logging.error("coreclient.py invalid edge token ") + + # delete global emane config if there no longer exist any emane cloud + if core_pb2.NodeType.EMANE not in [x.type for x in self.nodes.values()]: + self.emane_config = None + + # delete any mobility configuration, wlan configuration + for i in core_node_ids: + if i in self.mobilityconfig_management.configurations: + self.mobilityconfig_management.configurations.pop(i) + if i in self.wlanconfig_management.configurations: + self.wlanconfig_management.configurations.pop(i) + + # delete emane configurations + for i in node_interface_pairs: + if i in self.emaneconfig_management.configurations: + self.emaneconfig_management.configurations.pop(i) + for i in core_node_ids: + if tuple([i, None]) in self.emaneconfig_management.configurations: + self.emaneconfig_management.configurations.pop(tuple([i, None])) + def add_preexisting_node(self, canvas_node, session_id, core_node, name): """ Add preexisting nodes to grpc manager @@ -531,20 +569,6 @@ class CoreClient: self.preexisting.clear() logging.debug("Next id: %s, Reusable: %s", self.id, self.reusable) - def delete_node(self, canvas_id): - """ - Delete a node from the session - - :param int canvas_id: node's id in the canvas - :return: thing - """ - try: - self.nodes.pop(canvas_id) - self.reusable.append(canvas_id) - self.reusable.sort() - except KeyError: - logging.error("grpcmanagement.py INVALID NODE CANVAS ID") - def create_interface(self, node_type, gui_interface): """ create a protobuf interface given the interface object stored by the programmer diff --git a/coretk/coretk/dialogs/wlanconfig.py b/coretk/coretk/dialogs/wlanconfig.py index dc40e6c7..78dc2c22 100644 --- a/coretk/coretk/dialogs/wlanconfig.py +++ b/coretk/coretk/dialogs/wlanconfig.py @@ -24,7 +24,7 @@ class WlanConfigDialog(Dialog): self.config = config self.name = tk.StringVar(value=canvas_node.name) - self.range_var = tk.StringVar(value=config["basic_range"]) + self.range_var = tk.StringVar(value=config["range"]) self.bandwidth_var = tk.StringVar(value=config["bandwidth"]) self.delay_var = tk.StringVar(value=config["delay"]) self.loss_var = tk.StringVar(value=config["error"]) diff --git a/coretk/coretk/graph.py b/coretk/coretk/graph.py index eb2362c6..19491180 100644 --- a/coretk/coretk/graph.py +++ b/coretk/coretk/graph.py @@ -8,6 +8,7 @@ from coretk.graph_helper import GraphHelper, WlanAntennaManager from coretk.images import Images from coretk.interface import Interface from coretk.linkinfo import LinkInfo, Throughput +from coretk.nodedelete import CanvasComponentManagement from coretk.wirelessconnection import WirelessConnection @@ -41,7 +42,11 @@ class CanvasGraph(tk.Canvas): self.drawing_edge = None self.grid = None self.meters_per_pixel = 1.5 + + self.canvas_management = CanvasComponentManagement(self, core) + self.canvas_action = CanvasAction(master, self) + self.setup_menus() self.setup_bindings() self.draw_grid() @@ -101,6 +106,7 @@ class CanvasGraph(tk.Canvas): self.bind("", self.click_release) self.bind("", self.click_motion) self.bind("", self.context) + self.bind("", self.press_delete) def draw_grid(self, width=1000, height=800): """ @@ -377,6 +383,37 @@ class CanvasGraph(tk.Canvas): self.node_context.unpost() self.is_node_context_opened = False + # TODO rather than delete, might move the data to somewhere else in order to reuse + # TODO when the user undo + def press_delete(self, event): + """ + delete selected nodes and any data that relates to it + :param event: + :return: + """ + # hide nodes, links, link information that shows on the GUI + to_delete_nodes, to_delete_edge_tokens = ( + self.canvas_management.delete_selected_nodes() + ) + + # delete nodes and link info stored in CanvasGraph object + for nid in to_delete_nodes: + self.nodes.pop(nid) + for token in to_delete_edge_tokens: + self.edges.pop(token) + + # delete the edge data inside of canvas node + canvas_node_link_to_delete = [] + for canvas_id, node in self.nodes.items(): + for e in node.edges: + if e.token in to_delete_edge_tokens: + canvas_node_link_to_delete.append(tuple([canvas_id, e])) + for nid, edge in canvas_node_link_to_delete: + self.nodes[nid].edges.remove(edge) + + # delete the related data from core + self.core.delete_wanted_graph_nodes(to_delete_nodes, to_delete_edge_tokens) + def add_node(self, x, y, image, node_name): plot_id = self.find_all()[0] logging.info("add node event: %s - %s", plot_id, self.selected) @@ -474,11 +511,15 @@ class CanvasNode: self.canvas.tag_bind(self.id, "", self.motion) self.canvas.tag_bind(self.id, "", self.context) self.canvas.tag_bind(self.id, "", self.double_click) + self.canvas.tag_bind(self.id, "", self.select_multiple) self.edges = set() self.wlans = [] self.moving = None + def click(self, event): + print("click") + def double_click(self, event): node_id = self.canvas.core.nodes[self.id].node_id state = self.canvas.core.get_session_state() @@ -501,7 +542,8 @@ class CanvasNode: def click_press(self, event): logging.debug(f"node click press {self.name}: {event}") self.moving = self.canvas.canvas_xy(event) - # return "break" + + self.canvas.canvas_management.node_select(self) def click_release(self, event): logging.debug(f"node click release {self.name}: {event}") @@ -521,6 +563,7 @@ class CanvasNode: self.canvas.move(self.id, offset_x, offset_y) self.canvas.move(self.text_id, offset_x, offset_y) self.antenna_draw.update_antennas_position(offset_x, offset_y) + self.canvas.canvas_management.node_drag(self, offset_x, offset_y) new_x, new_y = self.canvas.coords(self.id) @@ -540,5 +583,8 @@ class CanvasNode: old_x, old_y, new_x, new_y, self.wlans ) + def select_multiple(self, event): + self.canvas.canvas_management.node_select(self, True) + def context(self, event): logging.debug(f"context click {self.name}: {event}") diff --git a/coretk/coretk/mobilitynodeconfig.py b/coretk/coretk/mobilitynodeconfig.py index e79e58ac..4a94f573 100644 --- a/coretk/coretk/mobilitynodeconfig.py +++ b/coretk/coretk/mobilitynodeconfig.py @@ -10,6 +10,10 @@ from core.api.grpc import core_pb2 class MobilityNodeConfig: def __init__(self): + """ + create an instance of MobilityConfig object + """ + # dict that maps node id to mobility configuration self.configurations = {} def set_default_configuration(self, node_type, node_id): diff --git a/coretk/coretk/nodedelete.py b/coretk/coretk/nodedelete.py new file mode 100644 index 00000000..6ce3a6f9 --- /dev/null +++ b/coretk/coretk/nodedelete.py @@ -0,0 +1,61 @@ +""" +manage deletion +""" + + +class CanvasComponentManagement: + def __init__(self, canvas, core): + self.app = core + self.canvas = canvas + + # dictionary that maps node to box + self.selected = {} + + def node_select(self, canvas_node, choose_multiple=False): + """ + create a bounding box when a node is selected + + :param coretk.graph.CanvasNode canvas_node: canvas node object + :return: nothing + """ + + if not choose_multiple: + self.delete_current_bbox() + + # draw a bounding box if node hasn't been selected yet + if canvas_node.id not in self.selected: + x0, y0, x1, y1 = self.canvas.bbox(canvas_node.id) + bbox_id = self.canvas.create_rectangle( + (x0 - 6, y0 - 6, x1 + 6, y1 + 6), activedash=True, dash="-" + ) + self.selected[canvas_node.id] = bbox_id + + def node_drag(self, canvas_node, offset_x, offset_y): + self.canvas.move(self.selected[canvas_node.id], offset_x, offset_y) + + def delete_current_bbox(self): + for bbid in self.selected.values(): + self.canvas.delete(bbid) + self.selected.clear() + + def delete_selected_nodes(self): + selected_nodes = list(self.selected.keys()) + edges = set() + for n in selected_nodes: + edges = edges.union(self.canvas.nodes[n].edges) + edge_canvas_ids = [x.id for x in edges] + edge_tokens = [x.token for x in edges] + link_infos = [x.link_info.id1 for x in edges] + [x.link_info.id2 for x in edges] + + for i in edge_canvas_ids: + self.canvas.itemconfig(i, state="hidden") + + for i in link_infos: + self.canvas.itemconfig(i, state="hidden") + + for cnid, bbid in self.selected.items(): + self.canvas.itemconfig(cnid, state="hidden") + self.canvas.itemconfig(bbid, state="hidden") + self.canvas.itemconfig(self.canvas.nodes[cnid].text_id, state="hidden") + self.selected.clear() + return selected_nodes, edge_tokens