コード例 #1
0
ファイル: node.py プロジェクト: tinchoa/core
class CanvasNode:
    def __init__(self, app, x, y, core_node, image):
        self.app = app
        self.canvas = app.canvas
        self.image = image
        self.core_node = core_node
        self.id = self.canvas.create_image(x,
                                           y,
                                           anchor=tk.CENTER,
                                           image=self.image,
                                           tags=tags.NODE)
        text_font = font.Font(family="TkIconFont", size=12)
        label_y = self._get_label_y()
        self.text_id = self.canvas.create_text(
            x,
            label_y,
            text=self.core_node.name,
            tags=tags.NODE_NAME,
            font=text_font,
            fill="#0000CD",
        )
        self.tooltip = CanvasTooltip(self.canvas)
        self.edges = set()
        self.interfaces = []
        self.wireless_edges = set()
        self.antennae = []
        self.setup_bindings()

    def setup_bindings(self):
        self.canvas.tag_bind(self.id, "<Double-Button-1>", self.double_click)
        self.canvas.tag_bind(self.id, "<Enter>", self.on_enter)
        self.canvas.tag_bind(self.id, "<Leave>", self.on_leave)

    def delete(self):
        self.canvas.delete(self.id)
        self.canvas.delete(self.text_id)
        self.delete_antennae()

    def add_antenna(self):
        x, y = self.canvas.coords(self.id)
        offset = len(self.antennae) * 8
        antenna_id = self.canvas.create_image(
            x - 16 + offset,
            y - 23,
            anchor=tk.CENTER,
            image=NodeUtils.ANTENNA_ICON,
            tags=tags.ANTENNA,
        )
        self.antennae.append(antenna_id)

    def delete_antenna(self):
        """
        delete one antenna

        :return: nothing
        """
        if self.antennae:
            antenna_id = self.antennae.pop()
            self.canvas.delete(antenna_id)

    def delete_antennae(self):
        """
        delete all antennas

        :return: nothing
        """
        for antenna_id in self.antennae:
            self.canvas.delete(antenna_id)
        self.antennae.clear()

    def redraw(self):
        self.canvas.itemconfig(self.id, image=self.image)
        self.canvas.itemconfig(self.text_id, text=self.core_node.name)

    def _get_label_y(self):
        image_box = self.canvas.bbox(self.id)
        return image_box[3] + NODE_TEXT_OFFSET

    def move(self, x, y):
        x, y = self.canvas.get_scaled_coords(x, y)
        current_x, current_y = self.canvas.coords(self.id)
        x_offset = x - current_x
        y_offset = y - current_y
        self.motion(x_offset, y_offset, update=False)

    def motion(self, x_offset, y_offset, update=True):
        original_position = self.canvas.coords(self.id)
        self.canvas.move(self.id, x_offset, y_offset)
        x, y = self.canvas.coords(self.id)

        # check new position
        bbox = self.canvas.bbox(self.id)
        if not self.canvas.valid_position(*bbox):
            self.canvas.coords(self.id, original_position)
            return

        # move test and selection box
        self.canvas.move(self.text_id, x_offset, y_offset)
        self.canvas.move_selection(self.id, x_offset, y_offset)

        # move antennae
        for antenna_id in self.antennae:
            self.canvas.move(antenna_id, x_offset, y_offset)

        # move edges
        for edge in self.edges:
            x1, y1, x2, y2 = self.canvas.coords(edge.id)
            if edge.src == self.id:
                self.canvas.coords(edge.id, x, y, x2, y2)
            else:
                self.canvas.coords(edge.id, x1, y1, x, y)
            edge.update_labels()

        for edge in self.wireless_edges:
            x1, y1, x2, y2 = self.canvas.coords(edge.id)
            if edge.src == self.id:
                self.canvas.coords(edge.id, x, y, x2, y2)
            else:
                self.canvas.coords(edge.id, x1, y1, x, y)

        # set actual coords for node and update core is running
        real_x, real_y = self.canvas.get_actual_coords(x, y)
        self.core_node.position.x = real_x
        self.core_node.position.y = real_y
        if self.app.core.is_runtime() and update:
            self.app.core.edit_node(self.core_node)

    def on_enter(self, event):
        if self.app.core.is_runtime() and self.app.core.observer:
            self.tooltip.text.set("waiting...")
            self.tooltip.on_enter(event)
            try:
                output = self.app.core.run(self.core_node.id)
                self.tooltip.text.set(output)
            except grpc.RpcError as e:
                show_grpc_error(e)

    def on_leave(self, event):
        self.tooltip.on_leave(event)

    def double_click(self, event):
        if self.app.core.is_runtime():
            self.canvas.core.launch_terminal(self.core_node.id)
        else:
            self.show_config()

    def create_context(self):
        is_wlan = self.core_node.type == NodeType.WIRELESS_LAN
        is_emane = self.core_node.type == NodeType.EMANE
        context = tk.Menu(self.canvas)
        themes.style_menu(context)
        if self.app.core.is_runtime():
            context.add_command(label="Configure", command=self.show_config)
            if NodeUtils.is_container_node(self.core_node.type):
                context.add_command(label="Services", state=tk.DISABLED)
            if is_wlan:
                context.add_command(label="WLAN Config",
                                    command=self.show_wlan_config)
            if is_wlan and self.core_node.id in self.app.core.mobility_players:
                context.add_command(label="Mobility Player",
                                    command=self.show_mobility_player)
            context.add_command(label="Select Adjacent", state=tk.DISABLED)
            context.add_command(label="Hide", state=tk.DISABLED)
            if NodeUtils.is_container_node(self.core_node.type):
                context.add_command(label="Shell Window", state=tk.DISABLED)
                context.add_command(label="Tcpdump", state=tk.DISABLED)
                context.add_command(label="Tshark", state=tk.DISABLED)
                context.add_command(label="Wireshark", state=tk.DISABLED)
                context.add_command(label="View Log", state=tk.DISABLED)
        else:
            context.add_command(label="Configure", command=self.show_config)
            if NodeUtils.is_container_node(self.core_node.type):
                context.add_command(label="Services",
                                    command=self.show_services)
            if is_emane:
                context.add_command(label="EMANE Config",
                                    command=self.show_emane_config)
            if is_wlan:
                context.add_command(label="WLAN Config",
                                    command=self.show_wlan_config)
                context.add_command(label="Mobility Config",
                                    command=self.show_mobility_config)
            if NodeUtils.is_wireless_node(self.core_node.type):
                context.add_command(label="Link To Selected",
                                    command=self.wireless_link_selected)
                context.add_command(label="Select Members", state=tk.DISABLED)
            context.add_command(label="Select Adjacent", state=tk.DISABLED)
            context.add_command(label="Create Link To", state=tk.DISABLED)
            context.add_command(label="Assign To", state=tk.DISABLED)
            context.add_command(label="Move To", state=tk.DISABLED)
            context.add_command(label="Cut", state=tk.DISABLED)
            context.add_command(label="Copy", state=tk.DISABLED)
            context.add_command(label="Paste", state=tk.DISABLED)
            context.add_command(label="Delete", state=tk.DISABLED)
            context.add_command(label="Hide", state=tk.DISABLED)
        return context

    def show_config(self):
        self.canvas.context = None
        dialog = NodeConfigDialog(self.app, self.app, self)
        dialog.show()

    def show_wlan_config(self):
        self.canvas.context = None
        dialog = WlanConfigDialog(self.app, self.app, self)
        dialog.show()

    def show_mobility_config(self):
        self.canvas.context = None
        dialog = MobilityConfigDialog(self.app, self.app, self)
        dialog.show()

    def show_mobility_player(self):
        self.canvas.context = None
        mobility_player = self.app.core.mobility_players[self.core_node.id]
        mobility_player.show()

    def show_emane_config(self):
        self.canvas.context = None
        dialog = EmaneConfigDialog(self.app, self.app, self)
        dialog.show()

    def show_services(self):
        self.canvas.context = None
        dialog = NodeServiceDialog(self.app.master, self.app, self)
        dialog.show()

    def has_emane_link(self, interface_id):
        result = None
        for edge in self.edges:
            if self.id == edge.src:
                other_id = edge.dst
                edge_interface_id = edge.src_interface.id
            else:
                other_id = edge.src
                edge_interface_id = edge.dst_interface.id
            if edge_interface_id != interface_id:
                continue
            other_node = self.canvas.nodes[other_id]
            if other_node.core_node.type == NodeType.EMANE:
                result = other_node.core_node
                break
        return result

    def wireless_link_selected(self):
        self.canvas.context = None
        for canvas_nid in [
                x for x in self.canvas.selection
                if "node" in self.canvas.gettags(x)
        ]:
            core_node = self.canvas.nodes[canvas_nid].core_node
            if core_node.type == core_pb2.NodeType.DEFAULT and core_node.model == "mdr":
                self.canvas.create_edge(self, self.canvas.nodes[canvas_nid])
        self.canvas.clear_selection()
コード例 #2
0
ファイル: node.py プロジェクト: lyma/core
class CanvasNode:
    def __init__(
        self,
        app: "Application",
        x: float,
        y: float,
        core_node: core_pb2.Node,
        image: "PhotoImage",
    ):
        self.app = app
        self.canvas = app.canvas
        self.image = image
        self.core_node = core_node
        self.id = self.canvas.create_image(
            x, y, anchor=tk.CENTER, image=self.image, tags=tags.NODE
        )
        label_y = self._get_label_y()
        self.text_id = self.canvas.create_text(
            x,
            label_y,
            text=self.core_node.name,
            tags=tags.NODE_NAME,
            font=self.app.icon_text_font,
            fill="#0000CD",
        )
        self.tooltip = CanvasTooltip(self.canvas)
        self.edges = set()
        self.interfaces = []
        self.wireless_edges = set()
        self.antennas = []
        self.antenna_images = {}
        self.setup_bindings()

    def setup_bindings(self):
        self.canvas.tag_bind(self.id, "<Double-Button-1>", self.double_click)
        self.canvas.tag_bind(self.id, "<Enter>", self.on_enter)
        self.canvas.tag_bind(self.id, "<Leave>", self.on_leave)

    def delete(self):
        logging.debug("Delete canvas node for %s", self.core_node)
        self.canvas.delete(self.id)
        self.canvas.delete(self.text_id)
        self.delete_antennas()

    def add_antenna(self):
        x, y = self.canvas.coords(self.id)
        offset = len(self.antennas) * 8 * self.app.app_scale
        img = Images.get(ImageEnum.ANTENNA, int(ANTENNA_SIZE * self.app.app_scale))
        antenna_id = self.canvas.create_image(
            x - 16 + offset,
            y - int(23 * self.app.app_scale),
            anchor=tk.CENTER,
            image=img,
            tags=tags.ANTENNA,
        )
        self.antennas.append(antenna_id)
        self.antenna_images[antenna_id] = img

    def delete_antenna(self):
        """
        delete one antenna
        """
        logging.debug("Delete an antenna on %s", self.core_node.name)
        if self.antennas:
            antenna_id = self.antennas.pop()
            self.canvas.delete(antenna_id)
            self.antenna_images.pop(antenna_id, None)

    def delete_antennas(self):
        """
        delete all antennas
        """
        logging.debug("Remove all antennas for %s", self.core_node.name)
        for antenna_id in self.antennas:
            self.canvas.delete(antenna_id)
        self.antennas.clear()
        self.antenna_images.clear()

    def redraw(self):
        self.canvas.itemconfig(self.id, image=self.image)
        self.canvas.itemconfig(self.text_id, text=self.core_node.name)
        for edge in self.edges:
            edge.redraw()

    def _get_label_y(self):
        image_box = self.canvas.bbox(self.id)
        return image_box[3] + NODE_TEXT_OFFSET

    def scale_text(self):
        text_bound = self.canvas.bbox(self.text_id)
        prev_y = (text_bound[3] + text_bound[1]) / 2
        new_y = self._get_label_y()
        self.canvas.move(self.text_id, 0, new_y - prev_y)

    def move(self, x: int, y: int):
        x, y = self.canvas.get_scaled_coords(x, y)
        current_x, current_y = self.canvas.coords(self.id)
        x_offset = x - current_x
        y_offset = y - current_y
        self.motion(x_offset, y_offset, update=False)

    def motion(self, x_offset: int, y_offset: int, update: bool = True):
        original_position = self.canvas.coords(self.id)
        self.canvas.move(self.id, x_offset, y_offset)
        x, y = self.canvas.coords(self.id)

        # check new position
        bbox = self.canvas.bbox(self.id)
        if not self.canvas.valid_position(*bbox):
            self.canvas.coords(self.id, original_position)
            return

        # move test and selection box
        self.canvas.move(self.text_id, x_offset, y_offset)
        self.canvas.move_selection(self.id, x_offset, y_offset)

        # move antennae
        for antenna_id in self.antennas:
            self.canvas.move(antenna_id, x_offset, y_offset)

        # move edges
        for edge in self.edges:
            x1, y1, x2, y2 = self.canvas.coords(edge.id)
            if edge.src == self.id:
                self.canvas.coords(edge.id, x, y, x2, y2)
            else:
                self.canvas.coords(edge.id, x1, y1, x, y)
            edge.update_labels()

        for edge in self.wireless_edges:
            x1, y1, x2, y2 = self.canvas.coords(edge.id)
            if edge.src == self.id:
                self.canvas.coords(edge.id, x, y, x2, y2)
            else:
                self.canvas.coords(edge.id, x1, y1, x, y)

        # set actual coords for node and update core is running
        real_x, real_y = self.canvas.get_actual_coords(x, y)
        self.core_node.position.x = real_x
        self.core_node.position.y = real_y
        if self.app.core.is_runtime() and update:
            self.app.core.edit_node(self.core_node)

    def on_enter(self, event: tk.Event):
        if self.app.core.is_runtime() and self.app.core.observer:
            self.tooltip.text.set("waiting...")
            self.tooltip.on_enter(event)
            try:
                output = self.app.core.run(self.core_node.id)
                self.tooltip.text.set(output)
            except grpc.RpcError as e:
                show_grpc_error(e, self.app, self.app)

    def on_leave(self, event: tk.Event):
        self.tooltip.on_leave(event)

    def double_click(self, event: tk.Event):
        if self.app.core.is_runtime():
            self.canvas.core.launch_terminal(self.core_node.id)
        else:
            self.show_config()

    def create_context(self) -> tk.Menu:
        is_wlan = self.core_node.type == NodeType.WIRELESS_LAN
        is_emane = self.core_node.type == NodeType.EMANE
        context = tk.Menu(self.canvas)
        themes.style_menu(context)
        if self.app.core.is_runtime():
            context.add_command(label="Configure", command=self.show_config)
            if NodeUtils.is_container_node(self.core_node.type):
                context.add_command(label="Services", state=tk.DISABLED)
                context.add_command(label="Config Services", state=tk.DISABLED)
            if is_wlan:
                context.add_command(label="WLAN Config", command=self.show_wlan_config)
            if is_wlan and self.core_node.id in self.app.core.mobility_players:
                context.add_command(
                    label="Mobility Player", command=self.show_mobility_player
                )
            context.add_command(label="Select Adjacent", state=tk.DISABLED)
            context.add_command(label="Hide", state=tk.DISABLED)
            if NodeUtils.is_container_node(self.core_node.type):
                context.add_command(label="Shell Window", state=tk.DISABLED)
                context.add_command(label="Tcpdump", state=tk.DISABLED)
                context.add_command(label="Tshark", state=tk.DISABLED)
                context.add_command(label="Wireshark", state=tk.DISABLED)
                context.add_command(label="View Log", state=tk.DISABLED)
        else:
            context.add_command(label="Configure", command=self.show_config)
            if NodeUtils.is_container_node(self.core_node.type):
                context.add_command(label="Services", command=self.show_services)
                context.add_command(
                    label="Config Services", command=self.show_config_services
                )
            if is_emane:
                context.add_command(
                    label="EMANE Config", command=self.show_emane_config
                )
            if is_wlan:
                context.add_command(label="WLAN Config", command=self.show_wlan_config)
                context.add_command(
                    label="Mobility Config", command=self.show_mobility_config
                )
            if NodeUtils.is_wireless_node(self.core_node.type):
                context.add_command(
                    label="Link To Selected", command=self.wireless_link_selected
                )
                context.add_command(label="Select Members", state=tk.DISABLED)
            edit_menu = tk.Menu(context)
            themes.style_menu(edit_menu)
            edit_menu.add_command(label="Cut", state=tk.DISABLED)
            edit_menu.add_command(label="Copy", command=self.canvas_copy)
            edit_menu.add_command(label="Delete", command=self.canvas_delete)
            edit_menu.add_command(label="Hide", state=tk.DISABLED)
            context.add_cascade(label="Edit", menu=edit_menu)
        return context

    def canvas_delete(self) -> None:
        self.canvas.clear_selection()
        self.canvas.selection[self.id] = self
        self.canvas.delete_selected_objects()

    def canvas_copy(self) -> None:
        self.canvas.clear_selection()
        self.canvas.selection[self.id] = self
        self.canvas.copy()

    def show_config(self):
        self.canvas.context = None
        dialog = NodeConfigDialog(self.app, self.app, self)
        dialog.show()

    def show_wlan_config(self):
        self.canvas.context = None
        dialog = WlanConfigDialog(self.app, self.app, self)
        if not dialog.has_error:
            dialog.show()

    def show_mobility_config(self):
        self.canvas.context = None
        dialog = MobilityConfigDialog(self.app, self.app, self)
        if not dialog.has_error:
            dialog.show()

    def show_mobility_player(self):
        self.canvas.context = None
        mobility_player = self.app.core.mobility_players[self.core_node.id]
        mobility_player.show()

    def show_emane_config(self):
        self.canvas.context = None
        dialog = EmaneConfigDialog(self.app, self.app, self)
        dialog.show()

    def show_services(self):
        self.canvas.context = None
        dialog = NodeServiceDialog(self.app.master, self.app, self)
        dialog.show()

    def show_config_services(self):
        self.canvas.context = None
        dialog = NodeConfigServiceDialog(self.app.master, self.app, self)
        dialog.show()

    def has_emane_link(self, interface_id: int) -> core_pb2.Node:
        result = None
        for edge in self.edges:
            if self.id == edge.src:
                other_id = edge.dst
                edge_interface_id = edge.src_interface.id
            else:
                other_id = edge.src
                edge_interface_id = edge.dst_interface.id
            if edge_interface_id != interface_id:
                continue
            other_node = self.canvas.nodes[other_id]
            if other_node.core_node.type == NodeType.EMANE:
                result = other_node.core_node
                break
        return result

    def wireless_link_selected(self):
        self.canvas.context = None
        for canvas_nid in [
            x for x in self.canvas.selection if "node" in self.canvas.gettags(x)
        ]:
            core_node = self.canvas.nodes[canvas_nid].core_node
            if core_node.type == core_pb2.NodeType.DEFAULT and core_node.model == "mdr":
                self.canvas.create_edge(self, self.canvas.nodes[canvas_nid])
        self.canvas.clear_selection()

    def scale_antennas(self):
        for i in range(len(self.antennas)):
            antenna_id = self.antennas[i]
            image = Images.get(
                ImageEnum.ANTENNA, int(ANTENNA_SIZE * self.app.app_scale)
            )
            self.canvas.itemconfig(antenna_id, image=image)
            self.antenna_images[antenna_id] = image
            node_x, node_y = self.canvas.coords(self.id)
            x, y = self.canvas.coords(antenna_id)
            dx = node_x - 16 + (i * 8 * self.app.app_scale) - x
            dy = node_y - int(23 * self.app.app_scale) - y
            self.canvas.move(antenna_id, dx, dy)
コード例 #3
0
class CanvasNode:
    def __init__(
        self,
        app: "Application",
        x: float,
        y: float,
        core_node: core_pb2.Node,
        image: "PhotoImage",
    ):
        self.app = app
        self.canvas = app.canvas
        self.image = image
        self.core_node = core_node
        self.id = self.canvas.create_image(x,
                                           y,
                                           anchor=tk.CENTER,
                                           image=self.image,
                                           tags=tags.NODE)
        label_y = self._get_label_y()
        self.text_id = self.canvas.create_text(
            x,
            label_y,
            text=self.core_node.name,
            tags=tags.NODE_LABEL,
            font=self.app.icon_text_font,
            fill="#0000CD",
            state=self.canvas.show_node_labels.state(),
        )
        self.tooltip = CanvasTooltip(self.canvas)
        self.edges = set()
        self.interfaces = {}
        self.wireless_edges = set()
        self.antennas = []
        self.antenna_images = {}
        # possible configurations
        self.emane_model_configs = {}
        self.wlan_config = {}
        self.mobility_config = {}
        self.service_configs = {}
        self.service_file_configs = {}
        self.config_service_configs = {}
        self.setup_bindings()
        self.context = tk.Menu(self.canvas)
        themes.style_menu(self.context)

    def next_interface_id(self) -> int:
        i = 0
        while i in self.interfaces:
            i += 1
        return i

    def setup_bindings(self):
        self.canvas.tag_bind(self.id, "<Double-Button-1>", self.double_click)
        self.canvas.tag_bind(self.id, "<Enter>", self.on_enter)
        self.canvas.tag_bind(self.id, "<Leave>", self.on_leave)
        self.canvas.tag_bind(self.id, "<ButtonRelease-3>", self.show_context)

    def delete(self):
        logging.debug("Delete canvas node for %s", self.core_node)
        self.canvas.delete(self.id)
        self.canvas.delete(self.text_id)
        self.delete_antennas()

    def add_antenna(self):
        x, y = self.canvas.coords(self.id)
        offset = len(self.antennas) * 8 * self.app.app_scale
        img = self.app.get_icon(ImageEnum.ANTENNA, ANTENNA_SIZE)
        antenna_id = self.canvas.create_image(
            x - 16 + offset,
            y - int(23 * self.app.app_scale),
            anchor=tk.CENTER,
            image=img,
            tags=tags.ANTENNA,
        )
        self.antennas.append(antenna_id)
        self.antenna_images[antenna_id] = img

    def delete_antenna(self):
        """
        delete one antenna
        """
        logging.debug("Delete an antenna on %s", self.core_node.name)
        if self.antennas:
            antenna_id = self.antennas.pop()
            self.canvas.delete(antenna_id)
            self.antenna_images.pop(antenna_id, None)

    def delete_antennas(self):
        """
        delete all antennas
        """
        logging.debug("Remove all antennas for %s", self.core_node.name)
        for antenna_id in self.antennas:
            self.canvas.delete(antenna_id)
        self.antennas.clear()
        self.antenna_images.clear()

    def redraw(self):
        self.canvas.itemconfig(self.id, image=self.image)
        self.canvas.itemconfig(self.text_id, text=self.core_node.name)
        for edge in self.edges:
            edge.redraw()

    def _get_label_y(self):
        image_box = self.canvas.bbox(self.id)
        return image_box[3] + NODE_TEXT_OFFSET

    def scale_text(self):
        text_bound = self.canvas.bbox(self.text_id)
        prev_y = (text_bound[3] + text_bound[1]) / 2
        new_y = self._get_label_y()
        self.canvas.move(self.text_id, 0, new_y - prev_y)

    def move(self, x: int, y: int):
        x, y = self.canvas.get_scaled_coords(x, y)
        current_x, current_y = self.canvas.coords(self.id)
        x_offset = x - current_x
        y_offset = y - current_y
        self.motion(x_offset, y_offset, update=False)

    def motion(self, x_offset: int, y_offset: int, update: bool = True):
        original_position = self.canvas.coords(self.id)
        self.canvas.move(self.id, x_offset, y_offset)
        pos = self.canvas.coords(self.id)

        # check new position
        bbox = self.canvas.bbox(self.id)
        if not self.canvas.valid_position(*bbox):
            self.canvas.coords(self.id, original_position)
            return

        # move test and selection box
        self.canvas.move(self.text_id, x_offset, y_offset)
        self.canvas.move_selection(self.id, x_offset, y_offset)

        # move antennae
        for antenna_id in self.antennas:
            self.canvas.move(antenna_id, x_offset, y_offset)

        # move edges
        for edge in self.edges:
            edge.move_node(self.id, pos)
        for edge in self.wireless_edges:
            edge.move_node(self.id, pos)

        # set actual coords for node and update core is running
        real_x, real_y = self.canvas.get_actual_coords(*pos)
        self.core_node.position.x = real_x
        self.core_node.position.y = real_y
        if self.app.core.is_runtime() and update:
            self.app.core.edit_node(self.core_node)

    def on_enter(self, event: tk.Event):
        if self.app.core.is_runtime() and self.app.core.observer:
            self.tooltip.text.set("waiting...")
            self.tooltip.on_enter(event)
            try:
                output = self.app.core.run(self.core_node.id)
                self.tooltip.text.set(output)
            except grpc.RpcError as e:
                self.app.show_grpc_exception("Observer Error", e)

    def on_leave(self, event: tk.Event):
        self.tooltip.on_leave(event)

    def double_click(self, event: tk.Event):
        if self.app.core.is_runtime():
            self.canvas.core.launch_terminal(self.core_node.id)
        else:
            self.show_config()

    def show_context(self, event: tk.Event) -> None:
        # clear existing menu
        self.context.delete(0, tk.END)
        is_wlan = self.core_node.type == NodeType.WIRELESS_LAN
        is_emane = self.core_node.type == NodeType.EMANE
        if self.app.core.is_runtime():
            self.context.add_command(label="Configure",
                                     command=self.show_config)
            if is_wlan:
                self.context.add_command(label="WLAN Config",
                                         command=self.show_wlan_config)
            if is_wlan and self.core_node.id in self.app.core.mobility_players:
                self.context.add_command(label="Mobility Player",
                                         command=self.show_mobility_player)
        else:
            self.context.add_command(label="Configure",
                                     command=self.show_config)
            if NodeUtils.is_container_node(self.core_node.type):
                self.context.add_command(label="Services",
                                         command=self.show_services)
                self.context.add_command(label="Config Services",
                                         command=self.show_config_services)
            if is_emane:
                self.context.add_command(label="EMANE Config",
                                         command=self.show_emane_config)
            if is_wlan:
                self.context.add_command(label="WLAN Config",
                                         command=self.show_wlan_config)
                self.context.add_command(label="Mobility Config",
                                         command=self.show_mobility_config)
            if NodeUtils.is_wireless_node(self.core_node.type):
                self.context.add_command(label="Link To Selected",
                                         command=self.wireless_link_selected)
            unlink_menu = tk.Menu(self.context)
            for edge in self.edges:
                other_id = edge.src
                if self.id == other_id:
                    other_id = edge.dst
                other_node = self.canvas.nodes[other_id]
                func_unlink = functools.partial(self.click_unlink, edge)
                unlink_menu.add_command(label=other_node.core_node.name,
                                        command=func_unlink)
            themes.style_menu(unlink_menu)
            self.context.add_cascade(label="Unlink", menu=unlink_menu)
            edit_menu = tk.Menu(self.context)
            themes.style_menu(edit_menu)
            edit_menu.add_command(label="Cut", command=self.click_cut)
            edit_menu.add_command(label="Copy", command=self.canvas_copy)
            edit_menu.add_command(label="Delete", command=self.canvas_delete)
            self.context.add_cascade(label="Edit", menu=edit_menu)
        self.context.tk_popup(event.x_root, event.y_root)

    def click_cut(self) -> None:
        self.canvas_copy()
        self.canvas_delete()

    def click_unlink(self, edge: CanvasEdge) -> None:
        self.canvas.delete_edge(edge)

    def canvas_delete(self) -> None:
        self.canvas.clear_selection()
        self.canvas.selection[self.id] = self
        self.canvas.delete_selected_objects()

    def canvas_copy(self) -> None:
        self.canvas.clear_selection()
        self.canvas.selection[self.id] = self
        self.canvas.copy()

    def show_config(self):
        dialog = NodeConfigDialog(self.app, self)
        dialog.show()

    def show_wlan_config(self):
        dialog = WlanConfigDialog(self.app, self)
        if not dialog.has_error:
            dialog.show()

    def show_mobility_config(self):
        dialog = MobilityConfigDialog(self.app, self)
        if not dialog.has_error:
            dialog.show()

    def show_mobility_player(self):
        mobility_player = self.app.core.mobility_players[self.core_node.id]
        mobility_player.show()

    def show_emane_config(self):
        dialog = EmaneConfigDialog(self.app, self)
        dialog.show()

    def show_services(self):
        dialog = NodeServiceDialog(self.app, self)
        dialog.show()

    def show_config_services(self):
        dialog = NodeConfigServiceDialog(self.app, self)
        dialog.show()

    def has_emane_link(self, interface_id: int) -> core_pb2.Node:
        result = None
        for edge in self.edges:
            if self.id == edge.src:
                other_id = edge.dst
                edge_interface_id = edge.src_interface.id
            else:
                other_id = edge.src
                edge_interface_id = edge.dst_interface.id
            if edge_interface_id != interface_id:
                continue
            other_node = self.canvas.nodes[other_id]
            if other_node.core_node.type == NodeType.EMANE:
                result = other_node.core_node
                break
        return result

    def wireless_link_selected(self):
        nodes = [x for x in self.canvas.selection if x in self.canvas.nodes]
        for node_id in nodes:
            canvas_node = self.canvas.nodes[node_id]
            self.canvas.create_edge(self, canvas_node)
        self.canvas.clear_selection()

    def scale_antennas(self):
        for i in range(len(self.antennas)):
            antenna_id = self.antennas[i]
            image = self.app.get_icon(ImageEnum.ANTENNA, ANTENNA_SIZE)
            self.canvas.itemconfig(antenna_id, image=image)
            self.antenna_images[antenna_id] = image
            node_x, node_y = self.canvas.coords(self.id)
            x, y = self.canvas.coords(antenna_id)
            dx = node_x - 16 + (i * 8 * self.app.app_scale) - x
            dy = node_y - int(23 * self.app.app_scale) - y
            self.canvas.move(antenna_id, dx, dy)