diff --git a/coretk/coretk/app.py b/coretk/coretk/app.py index 2dd2c5ca..4d51cd1e 100644 --- a/coretk/coretk/app.py +++ b/coretk/coretk/app.py @@ -98,6 +98,9 @@ class Application(tk.Frame): def save_config(self): appconfig.save(self.guiconfig) + def close(self): + self.master.destroy() + if __name__ == "__main__": log_format = "%(asctime)s - %(levelname)s - %(module)s:%(funcName)s - %(message)s" diff --git a/coretk/coretk/coreclient.py b/coretk/coretk/coreclient.py index b1414649..72ab7cdc 100644 --- a/coretk/coretk/coreclient.py +++ b/coretk/coretk/coreclient.py @@ -6,29 +6,20 @@ import logging import os import time from pathlib import Path +from tkinter import messagebox + +import grpc from core.api.grpc import client, core_pb2 from coretk import appconfig from coretk.dialogs.mobilityplayer import MobilityPlayer from coretk.dialogs.sessions import SessionsDialog +from coretk.graph import tags from coretk.graph.shape import AnnotationData, Shape from coretk.graph.shapeutils import ShapeType from coretk.interface import InterfaceManager from coretk.nodeutils import NodeDraw, NodeUtils -LIFT_ORDER = [ - "wallpaper", - "shape", - "gridline", - "shapetext", - "text", - "edge", - "antenna", - "nodename", - "linkinfo", - "node", -] - OBSERVERS = { "processes": "ps", "ifconfig": "ifconfig", @@ -285,11 +276,17 @@ class CoreClient: response = self.client.get_session_metadata(self.session_id) self.parse_metadata(response.config) + # update ui to represent current state if self.is_runtime(): self.app.toolbar.runtime_frame.tkraise() + self.app.toolbar.click_runtime_selection() else: self.app.toolbar.design_frame.tkraise() - self.app.toolbar.select_button.invoke() + # <<<<<<< HEAD + # self.app.toolbar.select_button.invoke() + # ======= + self.app.toolbar.click_selection() + # >>>>>>> coretk self.app.statusbar.progress_bar.stop() def is_runtime(self): @@ -301,12 +298,26 @@ class CoreClient: logging.info("canvas metadata: %s", canvas_config) if canvas_config: canvas_config = json.loads(canvas_config) - wallpaper_style = canvas_config["wallpaper-style"] + + gridlines = canvas_config.get("gridlines", True) + self.app.canvas.show_grid.set(gridlines) + + fit_image = canvas_config.get("fit_image", False) + self.app.canvas.adjust_to_dim.set(fit_image) + + wallpaper_style = canvas_config.get("wallpaper-style", 1) self.app.canvas.scale_option.set(wallpaper_style) - wallpaper = canvas_config["wallpaper"] + + width = self.app.guiconfig["preferences"]["width"] + height = self.app.guiconfig["preferences"]["height"] + width, height = canvas_config.get("dimensions", [width, height]) + self.app.canvas.redraw_canvas(width, height) + + wallpaper = canvas_config.get("wallpaper") if wallpaper: wallpaper = str(appconfig.BACKGROUNDS_PATH.joinpath(wallpaper)) self.app.canvas.set_wallpaper(wallpaper) + self.app.canvas.update_grid() # load saved shapes shapes_config = config.get("shapes") @@ -337,7 +348,7 @@ class CoreClient: except ValueError: logging.exception("unknown shape: %s", shape_type) - for tag in LIFT_ORDER: + for tag in tags.ABOVE_WALLPAPER_TAGS: self.app.canvas.tag_raise(tag) def create_new_session(self): @@ -372,28 +383,36 @@ class CoreClient: :return: existing sessions """ - self.client.connect() + try: + self.client.connect() - # get service information - response = self.client.get_services() - for service in response.services: - group_services = self.services.setdefault(service.group, set()) - group_services.add(service.name) + # get service information + response = self.client.get_services() + for service in response.services: + group_services = self.services.setdefault(service.group, set()) + group_services.add(service.name) - # if there are no sessions, create a new session, else join a session - response = self.client.get_sessions() - logging.info("current sessions: %s", response) - sessions = response.sessions - if len(sessions) == 0: - self.create_new_session() - else: - dialog = SessionsDialog(self.app, self.app) - dialog.show() + # if there are no sessions, create a new session, else join a session + response = self.client.get_sessions() + logging.info("current sessions: %s", response) + sessions = response.sessions + if len(sessions) == 0: + self.create_new_session() + else: + dialog = SessionsDialog(self.app, self.app) + dialog.show() - response = self.client.get_service_defaults(self.session_id) - self.default_services = { - x.node_type: set(x.services) for x in response.defaults - } + response = self.client.get_service_defaults(self.session_id) + self.default_services = { + x.node_type: set(x.services) for x in response.defaults + } + except grpc.RpcError as e: + if e.code() == grpc.StatusCode.UNAVAILABLE: + + messagebox.showerror("Server Error", "CORE Daemon Unavailable") + else: + messagebox.showerror("GRPC Error", e.details()) + self.app.close() def get_session_state(self): response = self.client.get_session(self.session_id) @@ -460,6 +479,9 @@ class CoreClient: canvas_config = { "wallpaper": Path(self.app.canvas.wallpaper_file).name, "wallpaper-style": self.app.canvas.scale_option.get(), + "gridlines": self.app.canvas.show_grid.get(), + "fit_image": self.app.canvas.adjust_to_dim.get(), + "dimensions": self.app.canvas.width_and_height(), } canvas_config = json.dumps(canvas_config) diff --git a/coretk/coretk/dialogs/canvassizeandscale.py b/coretk/coretk/dialogs/canvassizeandscale.py index 770d40df..669c0732 100644 --- a/coretk/coretk/dialogs/canvassizeandscale.py +++ b/coretk/coretk/dialogs/canvassizeandscale.py @@ -196,7 +196,7 @@ class SizeAndScaleDialog(Dialog): def click_apply(self): width, height = self.pixel_width.get(), self.pixel_height.get() - self.canvas.redraw_grid(width, height) + self.canvas.redraw_canvas(width, height) if self.canvas.wallpaper: self.canvas.redraw() location = self.app.core.location diff --git a/coretk/coretk/dialogs/dialog.py b/coretk/coretk/dialogs/dialog.py index 29362960..d814a47c 100644 --- a/coretk/coretk/dialogs/dialog.py +++ b/coretk/coretk/dialogs/dialog.py @@ -9,6 +9,7 @@ DIALOG_PAD = 5 class Dialog(tk.Toplevel): def __init__(self, master, app, title, modal=False): super().__init__(master) + self.geometry("800x600") self.withdraw() self.app = app self.modal = modal diff --git a/coretk/coretk/dialogs/shapemod.py b/coretk/coretk/dialogs/shapemod.py index 0410a8ae..e0085a8c 100644 --- a/coretk/coretk/dialogs/shapemod.py +++ b/coretk/coretk/dialogs/shapemod.py @@ -5,6 +5,7 @@ import tkinter as tk from tkinter import colorchooser, font, ttk from coretk.dialogs.dialog import Dialog +from coretk.graph import tags from coretk.graph.shapeutils import is_draw_shape, is_shape_text FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72] @@ -230,7 +231,7 @@ class ShapeDialog(Dialog): text=shape_text, fill=self.text_color, font=text_font, - tags="shapetext", + tags=tags.SHAPE_TEXT, ) self.shape.created = True else: diff --git a/coretk/coretk/graph/edges.py b/coretk/coretk/graph/edges.py index 27d1e3ba..94d847bc 100644 --- a/coretk/coretk/graph/edges.py +++ b/coretk/coretk/graph/edges.py @@ -1,5 +1,6 @@ import tkinter as tk +from coretk.graph import tags from coretk.nodeutils import NodeUtils @@ -10,7 +11,7 @@ class CanvasWirelessEdge: self.dst = dst self.canvas = canvas self.id = self.canvas.create_line( - *position, tags="wireless", width=1.5, fill="#009933" + *position, tags=tags.WIRELESS_EDGE, width=1.5, fill="#009933" ) def delete(self): @@ -40,7 +41,7 @@ class CanvasEdge: self.dst_interface = None self.canvas = canvas self.id = self.canvas.create_line( - x1, y1, x2, y2, tags="edge", width=self.width, fill="#ff0000" + x1, y1, x2, y2, tags=tags.EDGE, width=self.width, fill="#ff0000" ) self.token = None self.link_info = None diff --git a/coretk/coretk/graph/graph.py b/coretk/coretk/graph/graph.py index 084c3160..8b1084e4 100644 --- a/coretk/coretk/graph/graph.py +++ b/coretk/coretk/graph/graph.py @@ -5,6 +5,7 @@ from PIL import Image, ImageTk from core.api.grpc import core_pb2 from coretk.dialogs.shapemod import ShapeDialog +from coretk.graph import tags from coretk.graph.edges import CanvasEdge, CanvasWirelessEdge from coretk.graph.enums import GraphMode, ScaleOption from coretk.graph.linkinfo import LinkInfo, Throughput @@ -14,20 +15,6 @@ from coretk.graph.shapeutils import is_draw_shape from coretk.images import Images from coretk.nodeutils import NodeUtils -ABOVE_WALLPAPER = ["edge", "linkinfo", "wireless", "antenna", "nodename", "node"] -CANVAS_COMPONENT_TAGS = [ - "edge", - "node", - "nodename", - "wallpaper", - "linkinfo", - "antenna", - "wireless", - "selectednodes", - "shape", - "shapetext", -] - class CanvasGraph(tk.Canvas): def __init__(self, master, core, width, height, cnf=None, **kwargs): @@ -49,10 +36,11 @@ class CanvasGraph(tk.Canvas): self.drawing_edge = None self.grid = None self.setup_bindings() - self.draw_grid(width, height) self.core = core self.throughput_draw = Throughput(self, core) self.shape_drawing = False + self.default_width = width + self.default_height = height # background related self.wallpaper_id = None @@ -63,6 +51,22 @@ class CanvasGraph(tk.Canvas): self.show_grid = tk.BooleanVar(value=True) self.adjust_to_dim = tk.BooleanVar(value=False) + # draw base canvas + self.draw_canvas() + self.draw_grid() + + def draw_canvas(self): + self.grid = self.create_rectangle( + 0, + 0, + self.default_width, + self.default_height, + outline="#000000", + fill="#ffffff", + width=1, + tags="rectangle", + ) + def reset_and_redraw(self, session): """ Reset the private variables CanvasGraph object, redraw nodes given the new grpc @@ -72,7 +76,7 @@ class CanvasGraph(tk.Canvas): :return: nothing """ # delete any existing drawn items - for tag in CANVAS_COMPONENT_TAGS: + for tag in tags.COMPONENT_TAGS: self.delete(tag) # set the private variables to default value @@ -101,7 +105,7 @@ class CanvasGraph(tk.Canvas): self.bind("", self.ctrl_click) self.bind("", self.double_click) - def draw_grid(self, width=1000, height=800): + def draw_grid(self): """ Create grid @@ -110,21 +114,14 @@ class CanvasGraph(tk.Canvas): :return: nothing """ - self.grid = self.create_rectangle( - 0, - 0, - width, - height, - outline="#000000", - fill="#ffffff", - width=1, - tags="rectangle", - ) + width, height = self.width_and_height() + width = int(width) + height = int(height) for i in range(0, width, 27): - self.create_line(i, 0, i, height, dash=(2, 4), tags="gridline") + self.create_line(i, 0, i, height, dash=(2, 4), tags=tags.GRIDLINE) for i in range(0, height, 27): - self.create_line(0, i, width, i, dash=(2, 4), tags="gridline") - self.tag_lower("gridline") + self.create_line(0, i, width, i, dash=(2, 4), tags=tags.GRIDLINE) + self.tag_lower(tags.GRIDLINE) self.tag_lower(self.grid) def add_wireless_edge(self, src, dst): @@ -195,7 +192,7 @@ class CanvasGraph(tk.Canvas): canvas_node_two.interfaces.append(link.interface_two) # raise the nodes so they on top of the links - self.tag_raise("node") + self.tag_raise(tags.NODE) def canvas_xy(self, event): """ @@ -317,7 +314,7 @@ class CanvasGraph(tk.Canvas): (x0 - 6, y0 - 6, x1 + 6, y1 + 6), activedash=True, dash="-", - tags="selectednodes", + tags=tags.SELECTION, ) self.selection[object_id] = selection_id else: @@ -534,7 +531,7 @@ class CanvasGraph(tk.Canvas): self.delete(self.wallpaper_id) # place left corner of image to the left corner of the canvas self.wallpaper_id = self.create_image( - (cropx / 2, cropy / 2), image=cropped_tk, tags="wallpaper" + (cropx / 2, cropy / 2), image=cropped_tk, tags=tags.WALLPAPER ) self.wallpaper_drawn = cropped_tk @@ -562,7 +559,7 @@ class CanvasGraph(tk.Canvas): # place the center of the image at the center of the canvas self.delete(self.wallpaper_id) self.wallpaper_id = self.create_image( - (canvas_w / 2, canvas_h / 2), image=cropped_tk, tags="wallpaper" + (canvas_w / 2, canvas_h / 2), image=cropped_tk, tags=tags.WALLPAPER ) self.wallpaper_drawn = cropped_tk @@ -576,7 +573,7 @@ class CanvasGraph(tk.Canvas): image = Images.create(self.wallpaper_file, int(canvas_w), int(canvas_h)) self.delete(self.wallpaper_id) self.wallpaper_id = self.create_image( - (canvas_w / 2, canvas_h / 2), image=image, tags="wallpaper" + (canvas_w / 2, canvas_h / 2), image=image, tags=tags.WALLPAPER ) self.wallpaper_drawn = image @@ -585,36 +582,31 @@ class CanvasGraph(tk.Canvas): img_w = image_tk.width() img_h = image_tk.height() self.delete(self.wallpaper_id) - self.delete("rectangle") - self.delete("gridline") - self.draw_grid(img_w, img_h) + self.redraw_canvas(img_w, img_h) self.wallpaper_id = self.create_image((img_w / 2, img_h / 2), image=image_tk) self.wallpaper_drawn = image_tk - def redraw_grid(self, width, height): + def redraw_canvas(self, width, height): """ redraw grid with new dimension :return: nothing """ + # resize canvas and scrollregion self.config(scrollregion=(0, 0, width + 200, height + 200)) + self.coords(self.grid, 0, 0, width, height) - # delete previous grid - self.delete("rectangle") - self.delete("gridline") - - # redraw - self.draw_grid(width=width, height=height) - - # hide/show grid + # redraw gridlines to new canvas size + self.delete(tags.GRIDLINE) + self.draw_grid() self.update_grid() def redraw(self): if self.adjust_to_dim.get(): self.resize_to_wallpaper() else: - print(self.scale_option.get()) option = ScaleOption(self.scale_option.get()) + logging.info("canvas scale option: %s", option) if option == ScaleOption.UPPER_LEFT: self.wallpaper_upper_left() elif option == ScaleOption.CENTERED: @@ -624,13 +616,16 @@ class CanvasGraph(tk.Canvas): elif option == ScaleOption.TILED: logging.warning("tiled background not implemented yet") + # raise items above wallpaper + for component in tags.ABOVE_WALLPAPER_TAGS: + self.tag_raise(component) + def update_grid(self): logging.info("updating grid show: %s", self.show_grid.get()) if self.show_grid.get(): - self.itemconfig("gridline", state=tk.NORMAL) - self.tag_raise("gridline") + self.itemconfig(tags.GRIDLINE, state=tk.NORMAL) else: - self.itemconfig("gridline", state=tk.HIDDEN) + self.itemconfig(tags.GRIDLINE, state=tk.HIDDEN) def set_wallpaper(self, filename): logging.info("setting wallpaper: %s", filename) @@ -639,8 +634,6 @@ class CanvasGraph(tk.Canvas): self.wallpaper = img self.wallpaper_file = filename self.redraw() - for component in ABOVE_WALLPAPER: - self.tag_raise(component) else: if self.wallpaper_id is not None: self.delete(self.wallpaper_id) diff --git a/coretk/coretk/graph/linkinfo.py b/coretk/coretk/graph/linkinfo.py index 15c08e45..decca3bb 100644 --- a/coretk/coretk/graph/linkinfo.py +++ b/coretk/coretk/graph/linkinfo.py @@ -5,6 +5,7 @@ import tkinter as tk from tkinter import font from core.api.grpc import core_pb2 +from coretk.graph import tags TEXT_DISTANCE = 0.30 @@ -52,10 +53,20 @@ class LinkInfo: f"{self.link.interface_two.ip6}/{self.link.interface_two.ip6mask}\n" ) self.id1 = self.canvas.create_text( - x1, y1, text=label_one, justify=tk.CENTER, font=self.font, tags="linkinfo" + x1, + y1, + text=label_one, + justify=tk.CENTER, + font=self.font, + tags=tags.LINK_INFO, ) self.id2 = self.canvas.create_text( - x2, y2, text=label_two, justify=tk.CENTER, font=self.font, tags="linkinfo" + x2, + y2, + text=label_two, + justify=tk.CENTER, + font=self.font, + tags=tags.LINK_INFO, ) def recalculate_info(self): diff --git a/coretk/coretk/graph/node.py b/coretk/coretk/graph/node.py index 27f6a5dc..a3b29c13 100644 --- a/coretk/coretk/graph/node.py +++ b/coretk/coretk/graph/node.py @@ -7,6 +7,7 @@ from coretk.dialogs.emaneconfig import EmaneConfigDialog from coretk.dialogs.mobilityconfig import MobilityConfigDialog from coretk.dialogs.nodeconfig import NodeConfigDialog from coretk.dialogs.wlanconfig import WlanConfigDialog +from coretk.graph import tags from coretk.graph.enums import GraphMode from coretk.graph.tooltip import CanvasTooltip from coretk.nodeutils import NodeUtils @@ -23,7 +24,7 @@ class CanvasNode: x = self.core_node.position.x y = self.core_node.position.y self.id = self.canvas.create_image( - x, y, anchor=tk.CENTER, image=self.image, tags="node" + x, y, anchor=tk.CENTER, image=self.image, tags=tags.NODE ) image_box = self.canvas.bbox(self.id) y = image_box[3] + NODE_TEXT_OFFSET @@ -32,7 +33,7 @@ class CanvasNode: x, y, text=self.core_node.name, - tags="nodename", + tags=tags.NODE_NAME, font=text_font, fill="#0000CD", ) @@ -62,13 +63,12 @@ class CanvasNode: 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="antenna", + tags=tags.ANTENNA, ) self.antennae.append(antenna_id) diff --git a/coretk/coretk/graph/shape.py b/coretk/coretk/graph/shape.py index 22c676db..2928a695 100644 --- a/coretk/coretk/graph/shape.py +++ b/coretk/coretk/graph/shape.py @@ -1,10 +1,9 @@ import logging from coretk.dialogs.shapemod import ShapeDialog +from coretk.graph import tags from coretk.graph.shapeutils import ShapeType -ABOVE_COMPONENT = ["gridline", "edge", "linkinfo", "antenna", "node", "nodename"] - class AnnotationData: def __init__( @@ -70,7 +69,7 @@ class Shape: self.y1, self.x2, self.y2, - tags="shape", + tags=tags.SHAPE, dash=dash, fill=self.shape_data.fill_color, outline=self.shape_data.border_color, @@ -83,7 +82,7 @@ class Shape: self.y1, self.x2, self.y2, - tags="shape", + tags=tags.SHAPE, dash=dash, fill=self.shape_data.fill_color, outline=self.shape_data.border_color, @@ -95,7 +94,7 @@ class Shape: self.id = self.canvas.create_text( self.x1, self.y1, - tags="shapetext", + tags=tags.SHAPE_TEXT, text=self.shape_data.text, fill=self.shape_data.text_color, font=font, @@ -122,7 +121,7 @@ class Shape: self.text_id = self.canvas.create_text( x, y, - tags="shapetext", + tags=tags.SHAPE_TEXT, text=self.shape_data.text, fill=self.shape_data.text_color, font=font, @@ -132,7 +131,7 @@ class Shape: self.canvas.coords(self.id, self.x1, self.y1, x1, y1) def shape_complete(self, x, y): - for component in ABOVE_COMPONENT: + for component in tags.ABOVE_SHAPE: self.canvas.tag_raise(component) s = ShapeDialog(self.app, self.app, self) s.show() diff --git a/coretk/coretk/graph/tags.py b/coretk/coretk/graph/tags.py new file mode 100644 index 00000000..42f4ff5f --- /dev/null +++ b/coretk/coretk/graph/tags.py @@ -0,0 +1,35 @@ +GRIDLINE = "gridline" +SHAPE = "shape" +SHAPE_TEXT = "shapetext" +EDGE = "edge" +LINK_INFO = "linkinfo" +WIRELESS_EDGE = "wireless" +ANTENNA = "antenna" +NODE_NAME = "nodename" +NODE = "node" +WALLPAPER = "wallpaper" +SELECTION = "selectednodes" +ABOVE_WALLPAPER_TAGS = [ + GRIDLINE, + SHAPE, + SHAPE_TEXT, + EDGE, + LINK_INFO, + WIRELESS_EDGE, + ANTENNA, + NODE, + NODE_NAME, +] +ABOVE_SHAPE = [GRIDLINE, EDGE, LINK_INFO, WIRELESS_EDGE, ANTENNA, NODE, NODE_NAME] +COMPONENT_TAGS = [ + EDGE, + NODE, + NODE_NAME, + WALLPAPER, + LINK_INFO, + ANTENNA, + WIRELESS_EDGE, + SELECTION, + SHAPE, + SHAPE_TEXT, +] diff --git a/coretk/coretk/menuaction.py b/coretk/coretk/menuaction.py index 08298727..a051c3e5 100644 --- a/coretk/coretk/menuaction.py +++ b/coretk/coretk/menuaction.py @@ -109,7 +109,6 @@ class MenuAction: self.app.statusbar.progress_bar.start(5) thread = threading.Thread(target=self.app.core.open_xml, args=([file_path])) thread.start() - # self.app.core.open_xml(file_path) def gui_preferences(self): dialog = PreferencesDialog(self.app, self.app) diff --git a/coretk/coretk/statusbar.py b/coretk/coretk/statusbar.py index c4263e85..8910c742 100644 --- a/coretk/coretk/statusbar.py +++ b/coretk/coretk/statusbar.py @@ -25,33 +25,42 @@ class StatusBar(ttk.Frame): self.columnconfigure(3, weight=1) self.columnconfigure(4, weight=1) + frame = ttk.Frame(self, borderwidth=1, relief=tk.RIDGE) + frame.grid(row=0, column=0, sticky="ew") + frame.columnconfigure(0, weight=1) self.progress_bar = ttk.Progressbar( - self, orient="horizontal", mode="indeterminate" + frame, orient="horizontal", mode="indeterminate" ) - self.progress_bar.grid(row=0, column=0, sticky="ew") + self.progress_bar.grid(sticky="ew") - self.status = ttk.Label(self, textvariable=self.statusvar, anchor=tk.CENTER) - self.statusvar.set("status") + self.status = ttk.Label( + self, + textvariable=self.statusvar, + anchor=tk.CENTER, + borderwidth=1, + relief=tk.RIDGE, + ) self.status.grid(row=0, column=1, sticky="ew") - self.zoom = ttk.Label(self, text="zoom", anchor=tk.CENTER) + self.zoom = ttk.Label( + self, text="ZOOM TBD", anchor=tk.CENTER, borderwidth=1, relief=tk.RIDGE + ) self.zoom.grid(row=0, column=2, sticky="ew") - self.cpu_usage = ttk.Label(self, text="cpu usage", anchor=tk.CENTER) + self.cpu_usage = ttk.Label( + self, text="CPU TBD", anchor=tk.CENTER, borderwidth=1, relief=tk.RIDGE + ) self.cpu_usage.grid(row=0, column=3, sticky="ew") - self.emulation_light = ttk.Label(self, text="emulation light", anchor=tk.CENTER) + self.emulation_light = ttk.Label( + self, text="CEL TBD", anchor=tk.CENTER, borderwidth=1, relief=tk.RIDGE + ) self.emulation_light.grid(row=0, column=4, sticky="ew") def start_session_callback(self, process_time): - num_nodes = len(self.app.core.canvas_nodes) - num_links = len(self.app.core.links) self.progress_bar.stop() - self.statusvar.set( - "Network topology instantiated in %s seconds (%s node(s) and %s link(s))" - % ("%.3f" % process_time, num_nodes, num_links) - ) + self.statusvar.set(f"Session started in {process_time:.3f} seconds") def stop_session_callback(self, cleanup_time): self.progress_bar.stop() - self.statusvar.set("Cleanup completed in %s seconds" % "%.3f" % cleanup_time) + self.statusvar.set(f"Stopped session in {cleanup_time:.3f} seconds") diff --git a/coretk/coretk/themes.py b/coretk/coretk/themes.py index ad91276a..b80caf87 100644 --- a/coretk/coretk/themes.py +++ b/coretk/coretk/themes.py @@ -88,6 +88,14 @@ def load(style): }, "map": {"fieldbackground": [("disabled", Colors.frame)]}, }, + "TSpinbox": { + "configure": { + "fieldbackground": Colors.white, + "foreground": Colors.black, + "padding": (2, 0), + }, + "map": {"fieldbackground": [("disabled", Colors.frame)]}, + }, "TCombobox": { "configure": { "fieldbackground": Colors.white, diff --git a/coretk/coretk/toolbar.py b/coretk/coretk/toolbar.py index 1f1e734f..a47cac5e 100644 --- a/coretk/coretk/toolbar.py +++ b/coretk/coretk/toolbar.py @@ -5,6 +5,7 @@ from functools import partial from tkinter import ttk from coretk.dialogs.customnodes import CustomNodesDialog +from coretk.graph import tags from coretk.graph.enums import GraphMode from coretk.graph.shapeutils import ShapeType from coretk.images import ImageEnum, Images @@ -41,6 +42,7 @@ class Toolbar(ttk.Frame): self.annotation_button = None # runtime buttons + self.runtime_select_button = None # frames self.design_frame = None @@ -91,6 +93,11 @@ class Toolbar(ttk.Frame): self.annotation_button.state(["!pressed"]) button.state(["pressed"]) + def runtime_select(self, button): + logging.info("selecting runtime button: %s", button) + self.runtime_select_button.state(["!pressed"]) + button.state(["pressed"]) + def draw_runtime_frame(self): self.runtime_frame = ttk.Frame(self) self.runtime_frame.grid(row=0, column=0, sticky="nsew") @@ -102,10 +109,10 @@ class Toolbar(ttk.Frame): self.click_stop, "stop the session", ) - self.create_button( + self.runtime_select_button = self.create_button( self.runtime_frame, icon(ImageEnum.SELECT), - self.click_selection, + self.click_runtime_selection, "selection tool", ) # self.create_observe_button() @@ -190,6 +197,11 @@ class Toolbar(ttk.Frame): self.design_select(self.select_button) self.app.canvas.mode = GraphMode.SELECT + def click_runtime_selection(self): + logging.debug("clicked selection tool") + self.runtime_select(self.runtime_select_button) + self.app.canvas.mode = GraphMode.SELECT + def click_start(self): """ Start session handler redraw buttons, send node and link messages to grpc @@ -202,6 +214,7 @@ class Toolbar(ttk.Frame): thread = threading.Thread(target=self.app.core.start_session) thread.start() self.runtime_frame.tkraise() + self.click_runtime_selection() def click_link(self): logging.debug("Click LINK button") @@ -357,11 +370,9 @@ class Toolbar(ttk.Frame): self.app.statusbar.progress_bar.start(5) thread = threading.Thread(target=self.app.core.stop_session) thread.start() - for cid in self.app.canvas.find_withtag("wireless"): - self.app.canvas.itemconfig(cid, state="hidden") - # self.app.canvas.delete("wireless") - + self.app.canvas.delete(tags.WIRELESS_EDGE) self.design_frame.tkraise() + self.click_selection() def update_annotation(self, image, shape_type): logging.info("clicked annotation: ") diff --git a/coretk/coretk/widgets.py b/coretk/coretk/widgets.py index 2fddbf9f..521edc98 100644 --- a/coretk/coretk/widgets.py +++ b/coretk/coretk/widgets.py @@ -1,7 +1,7 @@ import logging import tkinter as tk from functools import partial -from tkinter import font, ttk +from tkinter import filedialog, font, ttk from tkinter.scrolledtext import ScrolledText from core.api.grpc import core_pb2 @@ -20,6 +20,12 @@ INT_TYPES = { PAD = 5 +def file_button_click(value): + file_path = filedialog.askopenfilename(title="Select File") + if file_path: + value.set(file_path) + + class FrameScroll(ttk.LabelFrame): def __init__(self, master, app, _cls=ttk.Frame, **kw): super().__init__(master, **kw) @@ -104,8 +110,18 @@ class ConfigFrame(FrameScroll): combobox.grid(row=index, column=1, sticky="ew", pady=pady) elif option.type == core_pb2.ConfigOptionType.STRING: value.set(option.value) - entry = ttk.Entry(frame, textvariable=value) - entry.grid(row=index, column=1, sticky="ew", pady=pady) + if "file" in option.label: + file_frame = ttk.Frame(frame) + file_frame.grid(row=index, column=1, sticky="ew", pady=pady) + file_frame.columnconfigure(0, weight=1) + entry = ttk.Entry(file_frame, textvariable=value) + entry.grid(row=0, column=0, sticky="ew", padx=padx) + func = partial(file_button_click, value) + button = ttk.Button(file_frame, text="...", command=func) + button.grid(row=0, column=1) + else: + entry = ttk.Entry(frame, textvariable=value) + entry.grid(row=index, column=1, sticky="ew", pady=pady) elif option.type in INT_TYPES: value.set(option.value) entry = ttk.Entry( @@ -193,3 +209,11 @@ class CodeText(ScrolledText): relief=tk.FLAT, **kwargs ) + + +class Spinbox(ttk.Entry): + def __init__(self, master=None, **kwargs): + super().__init__(master, "ttk::spinbox", **kwargs) + + def set(self, value): + self.tk.call(self._w, "set", value)