diff --git a/coretk/coretk/dialogs/shapemod.py b/coretk/coretk/dialogs/shapemod.py new file mode 100644 index 00000000..b76fc0d1 --- /dev/null +++ b/coretk/coretk/dialogs/shapemod.py @@ -0,0 +1,126 @@ +""" +shape input dialog +""" +import tkinter as tk +from tkinter import colorchooser, font, ttk + +from coretk.dialogs.dialog import Dialog + +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] + + +class ShapeDialog(Dialog): + def __init__(self, master, app): + super().__init__(master, app, "Add a new shape", modal=True) + self.shape_text = tk.StringVar(value="") + self.font = tk.StringVar(value="Arial") + self.font_size = tk.IntVar(value=12) + self.text_color = "#000000" + self.fill_color = "#CFCFFF" + self.border_color = "black" + self.border_width = tk.IntVar(value=0) + + self.fill = None + self.border = None + + self.top.columnconfigure(0, weight=1) + self.draw() + + def draw(self): + frame = ttk.Frame(self.top) + frame.columnconfigure(0, weight=1) + frame.columnconfigure(1, weight=2) + label = ttk.Label(frame, text="Text for top of shape: ") + label.grid(row=0, column=0, sticky="nsew") + entry = ttk.Entry(frame, textvariable=self.shape_text) + entry.grid(row=0, column=1, sticky="nsew") + frame.grid(row=0, column=0, sticky="nsew", padx=3, pady=3) + + frame = ttk.Frame(self.top) + frame.columnconfigure(0, weight=1) + frame.columnconfigure(1, weight=1) + frame.columnconfigure(2, weight=1) + combobox = ttk.Combobox( + frame, + textvariable=self.font, + values=sorted(font.families()), + state="readonly", + ) + combobox.grid(row=0, column=0, sticky="nsew") + combobox = ttk.Combobox( + frame, textvariable=self.font_size, values=FONT_SIZES, state="readonly" + ) + combobox.grid(row=0, column=1, padx=3, sticky="nsew") + button = ttk.Button(frame, text="Text color", command=self.choose_text_color) + button.grid(row=0, column=2, sticky="nsew") + frame.grid(row=1, column=0, sticky="nsew", padx=3, pady=3) + + frame = ttk.Frame(self.top) + button = ttk.Checkbutton(frame, text="Bold") + button.grid(row=0, column=0) + button = ttk.Checkbutton(frame, text="Italic") + button.grid(row=0, column=1, padx=3) + button = ttk.Checkbutton(frame, text="Underline") + button.grid(row=0, column=2) + frame.grid(row=2, column=0, sticky="nsew", padx=3, pady=3) + + frame = ttk.Frame(self.top) + frame.columnconfigure(0, weight=1) + frame.columnconfigure(1, weight=1) + frame.columnconfigure(2, weight=1) + label = ttk.Label(frame, text="Fill color") + label.grid(row=0, column=0, sticky="nsew") + self.fill = ttk.Label(frame, text=self.fill_color, background="#CFCFFF") + self.fill.grid(row=0, column=1, sticky="nsew", padx=3) + button = ttk.Button(frame, text="Color", command=self.choose_fill_color) + button.grid(row=0, column=2, sticky="nsew") + frame.grid(row=3, column=0, sticky="nsew", padx=3, pady=3) + + frame = ttk.Frame(self.top) + frame.columnconfigure(0, weight=1) + frame.columnconfigure(1, weight=1) + frame.columnconfigure(2, weight=1) + label = ttk.Label(frame, text="Border color:") + label.grid(row=0, column=0, sticky="nsew") + self.border = ttk.Label( + frame, text=self.border_color, background=self.fill_color + ) + self.border.grid(row=0, column=1, sticky="nsew", padx=3) + button = ttk.Button(frame, text="Color", command=self.choose_border_color) + button.grid(row=0, column=2, sticky="nsew") + frame.grid(row=4, column=0, sticky="nsew", padx=3, pady=3) + + frame = ttk.Frame(self.top) + frame.columnconfigure(0, weight=1) + frame.columnconfigure(1, weight=2) + label = ttk.Label(frame, text="Border width:") + label.grid(row=0, column=0, sticky="nsew") + combobox = ttk.Combobox( + frame, textvariable=self.border_width, values=BORDER_WIDTH, state="readonly" + ) + combobox.grid(row=0, column=1, sticky="nsew") + frame.grid(row=5, column=0, sticky="nsew", padx=3, pady=3) + + frame = ttk.Frame(self.top) + frame.columnconfigure(0, weight=1) + frame.columnconfigure(1, weight=1) + button = ttk.Button(frame, text="Add shape") + button.grid(row=0, column=0, sticky="e", padx=3) + button = ttk.Button(frame, text="Cancel", command=self.destroy) + button.grid(row=0, column=1, sticky="w", pady=3) + frame.grid(row=6, column=0, sticky="nsew", padx=3, pady=3) + + def choose_text_color(self): + color = colorchooser.askcolor(color="black") + self.text_color = color[1] + + def choose_fill_color(self): + color = colorchooser.askcolor(color=self.fill_color) + self.fill_color = color[1] + self.fill.config(background=color[1], text=color[1]) + + def choose_border_color(self): + color = colorchooser.askcolor(color="black") + self.border_color = color[1] + self.border.config(background=color[1], text=color[1]) diff --git a/coretk/coretk/graph.py b/coretk/coretk/graph.py index 942dfce8..c8c5ceba 100644 --- a/coretk/coretk/graph.py +++ b/coretk/coretk/graph.py @@ -68,6 +68,7 @@ class CanvasGraph(tk.Canvas): self.core = core self.helper = GraphHelper(self, core) self.throughput_draw = Throughput(self, core) + self.shape_drawing = False # background related self.wallpaper_id = None @@ -144,6 +145,7 @@ class CanvasGraph(tk.Canvas): self.bind("", self.click_motion) self.bind("", self.click_context) self.bind("", self.press_delete) + self.bind("", self.ctrl_click) def draw_grid(self, width=1000, height=800): """ @@ -275,6 +277,9 @@ class CanvasGraph(tk.Canvas): selected = _id break + if _id in self.shapes: + selected = _id + return selected def click_release(self, event): @@ -290,8 +295,10 @@ class CanvasGraph(tk.Canvas): else: if self.mode == GraphMode.ANNOTATION: if self.annotation_type in [ImageEnum.OVAL, ImageEnum.RECTANGLE]: + self.focus_set() x, y = self.canvas_xy(event) self.shapes[self.selected].shape_complete(x, y) + self.shape_drawing = False else: self.focus_set() self.selected = self.get_selected(event) @@ -355,12 +362,28 @@ class CanvasGraph(tk.Canvas): if self.mode == GraphMode.EDGE and is_node: x, y = self.coords(selected) self.drawing_edge = CanvasEdge(x, y, x, y, selected, self) - if self.mode == GraphMode.ANNOTATION: - if self.annotation_type in [ImageEnum.OVAL, ImageEnum.RECTANGLE]: - x, y = self.canvas_xy(event) - shape = Shape(self.app, self, x, y) - self.selected = shape.id - self.shapes[shape.id] = shape + if ( + self.mode == GraphMode.ANNOTATION + and self.annotation_type in [ImageEnum.OVAL, ImageEnum.RECTANGLE] + and selected is None + ): + x, y = self.canvas_xy(event) + shape = Shape(self.app, self, x, y) + self.selected = shape.id + self.shapes[shape.id] = shape + self.shape_drawing = True + if self.mode == GraphMode.SELECT and "shape" in self.gettags(selected): + x, y = self.canvas_xy(event) + self.shapes[selected].cursor_x = x + self.shapes[selected].cursor_y = y + self.canvas_management.node_select(self.shapes[selected]) + self.selected = selected + + def ctrl_click(self, event): + logging.debug("Control left click %s", event) + selected = self.get_selected(event) + if self.mode == GraphMode.SELECT and "shape" in self.gettags(selected): + self.canvas_management.node_select(self.shapes[selected], True) def click_motion(self, event): """ @@ -374,9 +397,14 @@ class CanvasGraph(tk.Canvas): x1, y1, _, _ = self.coords(self.drawing_edge.id) self.coords(self.drawing_edge.id, x1, y1, x2, y2) if self.mode == GraphMode.ANNOTATION: - if self.annotation_type in [ImageEnum.OVAL, ImageEnum.RECTANGLE]: + if ( + self.annotation_type in [ImageEnum.OVAL, ImageEnum.RECTANGLE] + and self.shape_drawing + ): x, y = self.canvas_xy(event) self.shapes[self.selected].shape_motion(x, y) + if self.mode == GraphMode.SELECT and "shape" in self.gettags(self.selected): + self.shapes[self.selected].motion(event) def click_context(self, event): logging.info("context event: %s", self.context) @@ -399,11 +427,12 @@ class CanvasGraph(tk.Canvas): :param event: :return: """ + logging.debug("press delete key") nodes = self.canvas_management.delete_selected_nodes() self.core.delete_graph_nodes(nodes) def add_node(self, x, y): - if self.selected is None: + if self.selected is None or "shape" in self.gettags(self.selected): core_node = self.core.create_node( int(x), int(y), self.node_draw.node_type, self.node_draw.model ) @@ -536,6 +565,9 @@ class CanvasGraph(tk.Canvas): else: self.itemconfig("gridline", state=tk.HIDDEN) + def is_selection_mode(self): + return self.mode == GraphMode.SELECT + class CanvasWirelessEdge: def __init__(self, token, position, src, dst, canvas): @@ -701,6 +733,7 @@ class CanvasNode: logging.debug(f"node click press {self.core_node.name}: {event}") self.moving = self.canvas.canvas_xy(event) self.canvas.canvas_management.node_select(self) + self.canvas.selected = self.id def click_release(self, event): logging.debug(f"node click release {self.core_node.name}: {event}") diff --git a/coretk/coretk/nodedelete.py b/coretk/coretk/nodedelete.py index a4de5f0d..8ea1500e 100644 --- a/coretk/coretk/nodedelete.py +++ b/coretk/coretk/nodedelete.py @@ -47,41 +47,49 @@ class CanvasComponentManagement: def delete_selected_nodes(self): edges = set() nodes = [] + for node_id in self.selected: + if "node" in self.canvas.gettags(node_id): + bbox_id = self.selected[node_id] + canvas_node = self.canvas.nodes.pop(node_id) + nodes.append(canvas_node) + self.canvas.delete(node_id) + self.canvas.delete(bbox_id) + self.canvas.delete(canvas_node.text_id) - for node_id in list(self.selected): - bbox_id = self.selected[node_id] - canvas_node = self.canvas.nodes.pop(node_id) - nodes.append(canvas_node) - self.canvas.delete(node_id) - self.canvas.delete(bbox_id) - self.canvas.delete(canvas_node.text_id) - - # delete antennas - is_wireless = NodeUtils.is_wireless_node(canvas_node.core_node.type) - if is_wireless: - canvas_node.antenna_draw.delete_antennas() - - # delete related edges - for edge in canvas_node.edges: - if edge in edges: - continue - edges.add(edge) - self.canvas.edges.pop(edge.token) - self.canvas.delete(edge.id) - self.canvas.delete(edge.link_info.id1) - self.canvas.delete(edge.link_info.id2) - other_id = edge.src - other_interface = edge.src_interface - if edge.src == node_id: - other_id = edge.dst - other_interface = edge.dst_interface - other_node = self.canvas.nodes[other_id] - other_node.edges.remove(edge) - try: - other_node.interfaces.remove(other_interface) - except ValueError: - pass + # delete antennas + is_wireless = NodeUtils.is_wireless_node(canvas_node.core_node.type) if is_wireless: - other_node.antenna_draw.delete_antenna() + canvas_node.antenna_draw.delete_antennas() + + # delete related edges + for edge in canvas_node.edges: + if edge in edges: + continue + edges.add(edge) + self.canvas.edges.pop(edge.token) + self.canvas.delete(edge.id) + self.canvas.delete(edge.link_info.id1) + self.canvas.delete(edge.link_info.id2) + other_id = edge.src + other_interface = edge.src_interface + if edge.src == node_id: + other_id = edge.dst + other_interface = edge.dst_interface + other_node = self.canvas.nodes[other_id] + other_node.edges.remove(edge) + try: + other_node.interfaces.remove(other_interface) + except ValueError: + pass + if is_wireless: + other_node.antenna_draw.delete_antenna() + + for shape_id in self.selected: + if "shape" in self.canvas.gettags(shape_id): + bbox_id = self.selected[node_id] + self.canvas.delete(shape_id) + self.canvas.delete(bbox_id) + self.canvas.shapes.pop(shape_id) + self.selected.clear() return nodes diff --git a/coretk/coretk/shape.py b/coretk/coretk/shape.py index 76e76972..696f1e5e 100644 --- a/coretk/coretk/shape.py +++ b/coretk/coretk/shape.py @@ -3,6 +3,7 @@ class for shapes """ import logging +from coretk.dialogs.shapemod import ShapeDialog from coretk.images import ImageEnum ABOVE_COMPONENT = ["gridline", "edge", "linkinfo", "antenna", "node", "nodename"] @@ -16,6 +17,7 @@ class Shape: self.y0 = top_y self.cursor_x = None self.cursor_y = None + canvas.delete(canvas.find_withtag("selectednodes")) annotation_type = self.canvas.annotation_type if annotation_type == ImageEnum.OVAL: self.id = canvas.create_oval( @@ -25,9 +27,8 @@ class Shape: self.id = canvas.create_rectangle( top_x, top_y, top_x, top_y, tags="shape", dash="-" ) - self.canvas.tag_bind(self.id, "", self.click_press) self.canvas.tag_bind(self.id, "", self.click_release) - self.canvas.tag_bind(self.id, "", self.motion) + # self.canvas.tag_bind(self.id, "", self.motion) def shape_motion(self, x1, y1): self.canvas.coords(self.id, self.x0, self.y0, x1, y1) @@ -36,11 +37,8 @@ class Shape: self.canvas.itemconfig(self.id, width=0, fill="#ccccff") for component in ABOVE_COMPONENT: self.canvas.tag_raise(component) - - def click_press(self, event): - logging.debug("Click on shape %s", self.id) - self.cursor_x = event.x - self.cursor_y = event.y + s = ShapeDialog(self.app, self.app) + s.show() def click_release(self, event): logging.debug("Click release on shape %s", self.id) @@ -53,5 +51,6 @@ class Shape: self.canvas.coords( self.id, x0 + delta_x, y0 + delta_y, x1 + delta_x, y1 + delta_y ) + self.canvas.canvas_management.node_drag(self, delta_x, delta_y) self.cursor_x = event.x self.cursor_y = event.y