diff --git a/coretk/coretk/app.py b/coretk/coretk/app.py index bc3ac9dd..185e5968 100644 --- a/coretk/coretk/app.py +++ b/coretk/coretk/app.py @@ -15,21 +15,16 @@ from coretk.toolbar import Toolbar class Application(tk.Frame): def __init__(self, master=None): super().__init__(master) - self.style = ttk.Style() - self.setup_theme() + # widgets self.menubar = None self.toolbar = None self.canvas = None self.statusbar = None - self.is_open_xml = False - self.size_and_scale = None - self.set_wallpaper = None - self.wallpaper_id = None - self.current_wallpaper = None - self.radiovar = tk.IntVar(value=1) - self.show_grid_var = tk.IntVar(value=1) - self.adjust_to_dim_var = tk.IntVar(value=0) + + # setup self.config = appconfig.read() + self.style = ttk.Style() + self.setup_theme() self.core = CoreClient(self) self.setup_app() self.draw() @@ -37,20 +32,27 @@ class Application(tk.Frame): def setup_theme(self): themes.load(self.style) - self.style.theme_use(themes.DARK) + self.style.theme_use(self.config["preferences"]["theme"]) func = partial(themes.update_menu, self.style) self.master.bind_class("Menu", "<>", func) - func = partial(themes.update_toplevel, self.style) - self.master.bind_class("Toplevel", "<>", func) def setup_app(self): self.master.title("CORE") - self.master.geometry("1000x800") + self.center() self.master.protocol("WM_DELETE_WINDOW", self.on_closing) image = Images.get(ImageEnum.CORE, 16) self.master.tk.call("wm", "iconphoto", self.master._w, image) self.pack(fill=tk.BOTH, expand=True) + def center(self): + width = 1000 + height = 800 + screen_width = self.master.winfo_screenwidth() + screen_height = self.master.winfo_screenheight() + x = int((screen_width / 2) - (width / 2)) + y = int((screen_height / 2) - (height / 2)) + self.master.geometry(f"{width}x{height}+{x}+{y}") + def draw(self): self.master.option_add("*tearOff", tk.FALSE) self.menubar = Menubar(self.master, self) diff --git a/coretk/coretk/appconfig.py b/coretk/coretk/appconfig.py index 67f97181..e8f5db6e 100644 --- a/coretk/coretk/appconfig.py +++ b/coretk/coretk/appconfig.py @@ -6,6 +6,8 @@ from pathlib import Path import yaml # gui home paths +from coretk import themes + HOME_PATH = Path.home().joinpath(".coretk") BACKGROUNDS_PATH = HOME_PATH.joinpath("backgrounds") CUSTOM_EMANE_PATH = HOME_PATH.joinpath("custom_emane") @@ -68,6 +70,7 @@ def check_directory(): editor = EDITORS[1] config = { "preferences": { + "theme": themes.DARK, "editor": editor, "terminal": terminal, "gui3d": "/usr/local/bin/std3d.sh", diff --git a/coretk/coretk/dialogs/canvasbackground.py b/coretk/coretk/dialogs/canvasbackground.py index 7b1dd186..8fb92c8b 100644 --- a/coretk/coretk/dialogs/canvasbackground.py +++ b/coretk/coretk/dialogs/canvasbackground.py @@ -1,27 +1,19 @@ """ set wallpaper """ -import enum import logging import tkinter as tk from tkinter import filedialog, ttk -from PIL import Image, ImageTk +from PIL import Image from coretk.appconfig import BACKGROUNDS_PATH from coretk.dialogs.dialog import Dialog +from coretk.images import Images PADX = 5 -class ScaleOption(enum.Enum): - NONE = 0 - UPPER_LEFT = 1 - CENTERED = 2 - SCALED = 3 - TILED = 4 - - class CanvasBackgroundDialog(Dialog): def __init__(self, master, app): """ @@ -31,11 +23,11 @@ class CanvasBackgroundDialog(Dialog): """ super().__init__(master, app, "Canvas Background", modal=True) self.canvas = self.app.canvas - self.radiovar = tk.IntVar(value=self.app.radiovar.get()) - self.show_grid_var = tk.IntVar(value=self.app.show_grid_var.get()) - self.adjust_to_dim_var = tk.IntVar(value=self.app.adjust_to_dim_var.get()) + self.scale_option = tk.IntVar(value=self.canvas.scale_option.get()) + self.show_grid = tk.BooleanVar(value=self.canvas.show_grid.get()) + self.adjust_to_dim = tk.BooleanVar(value=self.canvas.adjust_to_dim.get()) + self.filename = tk.StringVar(value=self.canvas.wallpaper_file) self.image_label = None - self.file_name = tk.StringVar() self.options = [] self.draw() @@ -57,6 +49,8 @@ class CanvasBackgroundDialog(Dialog): def draw_image_label(self): label = ttk.Label(self.top, text="Image filename: ") label.grid(row=1, column=0, sticky="ew") + if self.filename.get(): + self.draw_preview() def draw_image_selection(self): frame = ttk.Frame(self.top) @@ -65,7 +59,7 @@ class CanvasBackgroundDialog(Dialog): frame.columnconfigure(2, weight=1) frame.grid(row=2, column=0, sticky="ew") - entry = ttk.Entry(frame, textvariable=self.file_name) + entry = ttk.Entry(frame, textvariable=self.filename) entry.focus() entry.grid(row=0, column=0, sticky="ew", padx=PADX) @@ -84,42 +78,43 @@ class CanvasBackgroundDialog(Dialog): frame.grid(row=3, column=0, sticky="ew") button = ttk.Radiobutton( - frame, text="upper-left", value=1, variable=self.radiovar + frame, text="upper-left", value=1, variable=self.scale_option ) button.grid(row=0, column=0, sticky="ew") self.options.append(button) button = ttk.Radiobutton( - frame, text="centered", value=2, variable=self.radiovar + frame, text="centered", value=2, variable=self.scale_option ) button.grid(row=0, column=1, sticky="ew") self.options.append(button) - button = ttk.Radiobutton(frame, text="scaled", value=3, variable=self.radiovar) + button = ttk.Radiobutton( + frame, text="scaled", value=3, variable=self.scale_option + ) button.grid(row=0, column=2, sticky="ew") self.options.append(button) - button = ttk.Radiobutton(frame, text="titled", value=4, variable=self.radiovar) + button = ttk.Radiobutton( + frame, text="titled", value=4, variable=self.scale_option + ) button.grid(row=0, column=3, sticky="ew") self.options.append(button) def draw_additional_options(self): checkbutton = ttk.Checkbutton( - self.top, text="Show grid", variable=self.show_grid_var + self.top, text="Show grid", variable=self.show_grid ) checkbutton.grid(row=4, column=0, sticky="ew", padx=PADX) checkbutton = ttk.Checkbutton( self.top, text="Adjust canvas size to image dimensions", - variable=self.adjust_to_dim_var, + variable=self.adjust_to_dim, command=self.click_adjust_canvas, ) checkbutton.grid(row=5, column=0, sticky="ew", padx=PADX) - self.show_grid_var.set(1) - self.adjust_to_dim_var.set(0) - def draw_buttons(self): frame = ttk.Frame(self.top) frame.grid(row=6, column=0, pady=5, sticky="ew") @@ -142,13 +137,13 @@ class CanvasBackgroundDialog(Dialog): ), ) if filename: - self.file_name.set(filename) - width, height = 250, 135 - img = Image.open(filename) - img = img.resize((width, height), Image.ANTIALIAS) - tk_img = ImageTk.PhotoImage(img) - self.image_label.config(image=tk_img, width=width) - self.image_label.image = tk_img + self.filename.set(filename) + self.draw_preview() + + def draw_preview(self): + image = Images.create(self.filename.get(), 250, 135) + self.image_label.config(image=image) + self.image_label.image = image def click_clear(self): """ @@ -157,218 +152,47 @@ class CanvasBackgroundDialog(Dialog): :return: nothing """ # delete entry - self.file_name.set("") + self.filename.set("") # delete display image self.image_label.config(image="", width=32) + self.image_label.image = None def click_adjust_canvas(self): # deselect all radio buttons and grey them out - if self.adjust_to_dim_var.get() == 1: - self.radiovar.set(0) + if self.adjust_to_dim.get(): + self.scale_option.set(0) for option in self.options: option.config(state=tk.DISABLED) # turn back the radio button to active state so that user can choose again - elif self.adjust_to_dim_var.get() == 0: - self.radiovar.set(1) + else: + self.scale_option.set(1) for option in self.options: option.config(state=tk.NORMAL) - else: - logging.error("canvasbackground.py adjust_canvas_size invalid value") - - def delete_canvas_components(self, tag_list): - """ - delete canvas items whose tag is in the tag list - - :param list[string] tag_list: list of tags - :return: nothing - """ - for tag in tag_list: - for i in self.canvas.find_withtag(tag): - self.canvas.delete(i) - - def get_canvas_width_and_height(self): - """ - retrieve canvas width and height in pixels - - :return: nothing - """ - canvas = self.app.canvas - grid = canvas.find_withtag("rectangle")[0] - x0, y0, x1, y1 = canvas.coords(grid) - canvas_w = abs(x0 - x1) - canvas_h = abs(y0 - y1) - return canvas_w, canvas_h - - def determine_cropped_image_dimension(self): - """ - determine the dimension of the image after being cropped - - :return: nothing - """ - return - - def upper_left(self, img): - tk_img = ImageTk.PhotoImage(img) - - # crop image if it is bigger than canvas - canvas_w, canvas_h = self.get_canvas_width_and_height() - - cropx = img_w = tk_img.width() - cropy = img_h = tk_img.height() - - if img_w > canvas_w: - cropx -= img_w - canvas_w - if img_h > canvas_h: - cropy -= img_h - canvas_h - cropped = img.crop((0, 0, cropx, cropy)) - cropped_tk = ImageTk.PhotoImage(cropped) - - # place left corner of image to the left corner of the canvas - self.app.croppedwallpaper = cropped_tk - - self.delete_canvas_components(["wallpaper"]) - # self.delete_previous_wallpaper() - - wid = self.canvas.create_image( - (cropx / 2, cropy / 2), image=cropped_tk, tags="wallpaper" - ) - self.app.wallpaper_id = wid - - def center(self, img): - """ - place the image at the center of canvas - - :param Image img: image object - :return: nothing - """ - tk_img = ImageTk.PhotoImage(img) - canvas_w, canvas_h = self.get_canvas_width_and_height() - - cropx = img_w = tk_img.width() - cropy = img_h = tk_img.height() - - # dimension of the cropped image - if img_w > canvas_w: - cropx -= img_w - canvas_w - if img_h > canvas_h: - cropy -= img_h - canvas_h - - x0 = (img_w - cropx) / 2 - y0 = (img_h - cropy) / 2 - x1 = x0 + cropx - y1 = y0 + cropy - cropped = img.crop((x0, y0, x1, y1)) - cropped_tk = ImageTk.PhotoImage(cropped) - - # place the center of the image at the center of the canvas - self.app.croppedwallpaper = cropped_tk - self.delete_canvas_components(["wallpaper"]) - # self.delete_previous_wallpaper() - wid = self.canvas.create_image( - (canvas_w / 2, canvas_h / 2), image=cropped_tk, tags="wallpaper" - ) - self.app.wallpaper_id = wid - - def scaled(self, img): - """ - scale image based on canvas dimension - - :param Image img: image object - :return: nothing - """ - canvas_w, canvas_h = self.get_canvas_width_and_height() - resized_image = img.resize((int(canvas_w), int(canvas_h)), Image.ANTIALIAS) - image_tk = ImageTk.PhotoImage(resized_image) - self.app.croppedwallpaper = image_tk - - self.delete_canvas_components(["wallpaper"]) - # self.delete_previous_wallpaper() - - wid = self.canvas.create_image( - (canvas_w / 2, canvas_h / 2), image=image_tk, tags="wallpaper" - ) - self.app.wallpaper_id = wid - - def tiled(self, img): - return - - def draw_new_canvas(self, canvas_width, canvas_height): - """ - delete the old canvas and draw a new one - - :param int canvas_width: canvas width in pixel - :param int canvas_height: canvas height in pixel - :return: - """ - self.delete_canvas_components(["rectangle", "gridline"]) - self.canvas.draw_grid(canvas_width, canvas_height) - - def canvas_to_image_dimension(self, img): - image_tk = ImageTk.PhotoImage(img) - img_w = image_tk.width() - img_h = image_tk.height() - self.delete_canvas_components(["wallpaper"]) - self.draw_new_canvas(img_w, img_h) - wid = self.canvas.create_image((img_w / 2, img_h / 2), image=image_tk) - self.app.croppedwallpaper = image_tk - self.app.wallpaper_id = wid - - def show_grid(self): - """ - - :return: nothing - """ - self.app.adjust_to_dim_var.set(self.adjust_to_dim_var.get()) - - if self.show_grid_var.get() == 0: - for i in self.canvas.find_withtag("gridline"): - self.canvas.itemconfig(i, state=tk.HIDDEN) - elif self.show_grid_var.get() == 1: - for i in self.canvas.find_withtag("gridline"): - self.canvas.itemconfig(i, state=tk.NORMAL) - self.canvas.lift(i) - else: - logging.error("canvasbackground.py show_grid invalid value") - - def save_wallpaper_options(self): - self.app.radiovar.set(self.radiovar.get()) - self.app.show_grid_var.set(self.show_grid_var.get()) - self.app.adjust_to_dim_var.set(self.adjust_to_dim_var.get()) def click_apply(self): - filename = self.file_name.get() + self.canvas.scale_option.set(self.scale_option.get()) + self.canvas.show_grid.set(self.show_grid.get()) + self.canvas.adjust_to_dim.set(self.adjust_to_dim.get()) + self.canvas.update_grid() + + filename = self.filename.get() if not filename: - self.delete_canvas_components(["wallpaper"]) + self.canvas.delete(self.canvas.wallpaper_id) + self.canvas.wallpaper = None + self.canvas.wallpaper_file = None self.destroy() - self.app.current_wallpaper = None - self.save_wallpaper_options() return try: img = Image.open(filename) - self.app.current_wallpaper = img + self.canvas.wallpaper = img + self.canvas.wallpaper_file = filename + self.canvas.redraw() except FileNotFoundError: logging.error("invalid background: %s", filename) - if self.app.wallpaper_id: - self.canvas.delete(self.app.wallpaper_id) - self.destroy() - return + if self.canvas.wallpaper_id: + self.canvas.delete(self.canvas.wallpaper_id) + self.canvas.wallpaper_id = None + self.canvas.wallpaper_file = None - self.app.adjust_to_dim_var.set(self.adjust_to_dim_var.get()) - if self.adjust_to_dim_var.get() == 0: - self.app.radiovar.set(self.radiovar.get()) - option = ScaleOption(self.radiovar.get()) - if option == ScaleOption.UPPER_LEFT: - self.upper_left(img) - elif option == ScaleOption.CENTERED: - self.center(img) - elif option == ScaleOption.SCALED: - self.scaled(img) - elif option == ScaleOption.TILED: - print("not implemented yet") - - elif self.adjust_to_dim_var.get() == 1: - self.canvas_to_image_dimension(img) - - self.show_grid() self.destroy() diff --git a/coretk/coretk/dialogs/canvassizeandscale.py b/coretk/coretk/dialogs/canvassizeandscale.py index 09132aef..3a72389d 100644 --- a/coretk/coretk/dialogs/canvassizeandscale.py +++ b/coretk/coretk/dialogs/canvassizeandscale.py @@ -4,12 +4,9 @@ size and scale import tkinter as tk from tkinter import font, ttk -from coretk.dialogs.canvasbackground import ScaleOption from coretk.dialogs.dialog import Dialog -DRAW_OBJECT_TAGS = ["edge", "node", "nodename", "linkinfo", "antenna"] -FRAME_BAD = 5 -PAD = (0, 0, 5, 0) +FRAME_PAD = 5 PADX = 5 @@ -21,13 +18,13 @@ class SizeAndScaleDialog(Dialog): :param app: main application """ super().__init__(master, app, "Canvas Size and Scale", modal=True) - self.meter_per_pixel = self.app.canvas.meters_per_pixel + self.canvas = self.app.canvas + self.meter_per_pixel = self.canvas.meters_per_pixel self.section_font = font.Font(weight="bold") # get current canvas dimensions - canvas = self.app.canvas - plot = canvas.find_withtag("rectangle") - x0, y0, x1, y1 = canvas.bbox(plot[0]) + plot = self.canvas.find_withtag("rectangle") + x0, y0, x1, y1 = self.canvas.bbox(plot[0]) width = abs(x0 - x1) - 2 height = abs(y0 - y1) - 2 self.pixel_width = tk.IntVar(value=width) @@ -52,7 +49,7 @@ class SizeAndScaleDialog(Dialog): self.draw_buttons() def draw_size(self): - label_frame = ttk.Labelframe(self.top, text="Size", padding=FRAME_BAD) + label_frame = ttk.Labelframe(self.top, text="Size", padding=FRAME_PAD) label_frame.grid(sticky="ew") label_frame.columnconfigure(0, weight=1) @@ -89,7 +86,7 @@ class SizeAndScaleDialog(Dialog): label.grid(row=0, column=4, sticky="w") def draw_scale(self): - label_frame = ttk.Labelframe(self.top, text="Scale", padding=FRAME_BAD) + label_frame = ttk.Labelframe(self.top, text="Scale", padding=FRAME_PAD) label_frame.grid(sticky="ew") label_frame.columnconfigure(0, weight=1) @@ -105,7 +102,7 @@ class SizeAndScaleDialog(Dialog): def draw_reference_point(self): label_frame = ttk.Labelframe( - self.top, text="Reference Point", padding=FRAME_BAD + self.top, text="Reference Point", padding=FRAME_PAD ) label_frame.grid(sticky="ew") label_frame.columnconfigure(0, weight=1) @@ -122,14 +119,12 @@ class SizeAndScaleDialog(Dialog): label = ttk.Label(frame, text="X") label.grid(row=0, column=0, sticky="w", padx=PADX) - x_var = tk.StringVar(value=0) - entry = ttk.Entry(frame, textvariable=x_var) + entry = ttk.Entry(frame, textvariable=self.x) entry.grid(row=0, column=1, sticky="ew", padx=PADX) label = ttk.Label(frame, text="Y") label.grid(row=0, column=2, sticky="w", padx=PADX) - y_var = tk.StringVar(value=0) - entry = ttk.Entry(frame, textvariable=y_var) + entry = ttk.Entry(frame, textvariable=self.y) entry.grid(row=0, column=3, sticky="ew", padx=PADX) label = ttk.Label(label_frame, text="Translates To") @@ -174,47 +169,11 @@ class SizeAndScaleDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def redraw_grid(self): - """ - redraw grid with new dimension - - :return: nothing - """ - width, height = self.pixel_width.get(), self.pixel_height.get() - - canvas = self.app.canvas - canvas.config(scrollregion=(0, 0, width + 200, height + 200)) - - # delete old plot and redraw - for i in canvas.find_withtag("gridline"): - canvas.delete(i) - for i in canvas.find_withtag("rectangle"): - canvas.delete(i) - - canvas.draw_grid(width=width, height=height) - # lift anything that is drawn on the plot before - for tag in DRAW_OBJECT_TAGS: - for i in canvas.find_withtag(tag): - canvas.lift(i) - def click_apply(self): meter_per_pixel = float(self.scale.get()) / 100 - self.app.canvas.meters_per_pixel = meter_per_pixel - self.redraw_grid() - # if there is a current wallpaper showing, redraw it based on current wallpaper options - wallpaper_tool = self.app.set_wallpaper - current_wallpaper = self.app.current_wallpaper - if current_wallpaper: - if self.app.adjust_to_dim_var.get() == 0: - if self.app.radiovar.get() == ScaleOption.UPPER_LEFT.value: - wallpaper_tool.upper_left(current_wallpaper) - elif self.app.radiovar.get() == ScaleOption.CENTERED.value: - wallpaper_tool.center(current_wallpaper) - elif self.app.radiovar.get() == ScaleOption.SCALED.value: - wallpaper_tool.scaled(current_wallpaper) - elif self.app.radiovar.get() == ScaleOption.TILED.value: - print("not implemented") - elif self.app.adjust_to_dim_var.get() == 1: - wallpaper_tool.canvas_to_image_dimension(current_wallpaper) - wallpaper_tool.show_grid() + width, height = self.pixel_width.get(), self.pixel_height.get() + self.canvas.meters_per_pixel = meter_per_pixel + self.canvas.redraw_grid(width, height) + if self.canvas.wallpaper: + self.canvas.redraw() self.destroy() diff --git a/coretk/coretk/dialogs/customnodes.py b/coretk/coretk/dialogs/customnodes.py index 154b7868..f427f08b 100644 --- a/coretk/coretk/dialogs/customnodes.py +++ b/coretk/coretk/dialogs/customnodes.py @@ -35,7 +35,7 @@ class ServicesSelectDialog(Dialog): self.groups.listbox.selection_set(0) self.services = CheckboxList( - frame, text="Services", clicked=self.service_clicked + frame, self.app, text="Services", clicked=self.service_clicked ) self.services.grid(row=0, column=1, sticky="nsew") diff --git a/coretk/coretk/dialogs/emaneconfig.py b/coretk/coretk/dialogs/emaneconfig.py index 181e1264..d64e9089 100644 --- a/coretk/coretk/dialogs/emaneconfig.py +++ b/coretk/coretk/dialogs/emaneconfig.py @@ -93,7 +93,7 @@ class EmaneConfiguration(Dialog): self.emane_dialog.top.columnconfigure(0, weight=1) self.emane_dialog.top.rowconfigure(0, weight=1) self.emane_config_frame = ConfigFrame( - self.emane_dialog.top, config=self.options + self.emane_dialog.top, self.app, config=self.options ) self.emane_config_frame.draw_config() self.emane_config_frame.grid(sticky="nsew") @@ -167,7 +167,7 @@ class EmaneConfiguration(Dialog): self.model_options = response.config self.model_config_frame = ConfigFrame( - self.emane_model_dialog.top, config=self.model_options + self.emane_model_dialog.top, self.app, config=self.model_options ) self.model_config_frame.grid(sticky="nsew") self.model_config_frame.draw_config() diff --git a/coretk/coretk/dialogs/nodeservice.py b/coretk/coretk/dialogs/nodeservice.py index ce5a4715..9ced9205 100644 --- a/coretk/coretk/dialogs/nodeservice.py +++ b/coretk/coretk/dialogs/nodeservice.py @@ -38,7 +38,7 @@ class NodeService(Dialog): self.groups.listbox.selection_set(0) self.services = CheckboxList( - frame, text="Services", clicked=self.service_clicked + frame, self.app, text="Services", clicked=self.service_clicked ) self.services.grid(row=0, column=1, sticky="nsew") diff --git a/coretk/coretk/dialogs/preferences.py b/coretk/coretk/dialogs/preferences.py index 0c426d3c..148cb6bb 100644 --- a/coretk/coretk/dialogs/preferences.py +++ b/coretk/coretk/dialogs/preferences.py @@ -1,3 +1,4 @@ +import logging import tkinter as tk from tkinter import ttk @@ -10,41 +11,52 @@ class PreferencesDialog(Dialog): super().__init__(master, app, "Preferences", modal=True) preferences = self.app.config["preferences"] self.editor = tk.StringVar(value=preferences["editor"]) + self.theme = tk.StringVar(value=preferences["theme"]) self.terminal = tk.StringVar(value=preferences["terminal"]) self.gui3d = tk.StringVar(value=preferences["gui3d"]) self.draw() def draw(self): self.top.columnconfigure(0, weight=1) - self.draw_programs() + self.draw_preferences() self.draw_buttons() - def draw_programs(self): - frame = ttk.LabelFrame(self.top, text="Programs") + def draw_preferences(self): + frame = ttk.LabelFrame(self.top, text="Preferences") frame.grid(sticky="ew", pady=2) frame.columnconfigure(1, weight=1) - label = ttk.Label(frame, text="Editor") + label = ttk.Label(frame, text="Theme") label.grid(row=0, column=0, pady=2, padx=2, sticky="w") + themes = self.app.style.theme_names() + combobox = ttk.Combobox( + frame, textvariable=self.theme, values=themes, state="readonly" + ) + combobox.set(self.theme.get()) + combobox.grid(row=0, column=1, sticky="ew") + combobox.bind("<>", self.theme_change) + + label = ttk.Label(frame, text="Editor") + label.grid(row=1, column=0, pady=2, padx=2, sticky="w") combobox = ttk.Combobox( frame, textvariable=self.editor, values=appconfig.EDITORS, state="readonly" ) - combobox.grid(row=0, column=1, sticky="ew") + combobox.grid(row=1, column=1, sticky="ew") label = ttk.Label(frame, text="Terminal") - label.grid(row=1, column=0, pady=2, padx=2, sticky="w") + label.grid(row=2, column=0, pady=2, padx=2, sticky="w") combobox = ttk.Combobox( frame, textvariable=self.terminal, values=appconfig.TERMINALS, state="readonly", ) - combobox.grid(row=1, column=1, sticky="ew") + combobox.grid(row=2, column=1, sticky="ew") label = ttk.Label(frame, text="3D GUI") - label.grid(row=2, column=0, pady=2, padx=2, sticky="w") + label.grid(row=3, column=0, pady=2, padx=2, sticky="w") entry = ttk.Entry(frame, textvariable=self.gui3d) - entry.grid(row=2, column=1, sticky="ew") + entry.grid(row=3, column=1, sticky="ew") def draw_buttons(self): frame = ttk.Frame(self.top) @@ -58,10 +70,16 @@ class PreferencesDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") + def theme_change(self, event): + theme = self.theme.get() + logging.info("changing theme: %s", theme) + self.app.style.theme_use(theme) + def click_save(self): preferences = self.app.config["preferences"] preferences["terminal"] = self.terminal.get() preferences["editor"] = self.editor.get() preferences["gui3d"] = self.gui3d.get() + preferences["theme"] = self.theme.get() self.app.save_config() self.destroy() diff --git a/coretk/coretk/dialogs/sessionoptions.py b/coretk/coretk/dialogs/sessionoptions.py index 8cd7ad68..b2666015 100644 --- a/coretk/coretk/dialogs/sessionoptions.py +++ b/coretk/coretk/dialogs/sessionoptions.py @@ -22,7 +22,7 @@ class SessionOptionsDialog(Dialog): response = self.app.core.client.get_session_options(session_id) logging.info("session options: %s", response) - self.config_frame = ConfigFrame(self.top, config=response.config) + self.config_frame = ConfigFrame(self.top, self.app, config=response.config) self.config_frame.draw_config() self.config_frame.grid(sticky="nsew") diff --git a/coretk/coretk/graph.py b/coretk/coretk/graph.py index 5cdd2eec..e5bab65b 100644 --- a/coretk/coretk/graph.py +++ b/coretk/coretk/graph.py @@ -2,6 +2,8 @@ import enum import logging import tkinter as tk +from PIL import ImageTk + from core.api.grpc import core_pb2 from coretk.canvasaction import CanvasAction from coretk.canvastooltip import CanvasTooltip @@ -21,6 +23,14 @@ class GraphMode(enum.Enum): OTHER = 4 +class ScaleOption(enum.Enum): + NONE = 0 + UPPER_LEFT = 1 + CENTERED = 2 + SCALED = 3 + TILED = 4 + + CORE_NODES = ["router"] CORE_WIRED_NETWORK_NODES = [] CORE_WIRELESS_NODE = ["wlan"] @@ -43,11 +53,8 @@ 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() @@ -57,6 +64,15 @@ class CanvasGraph(tk.Canvas): self.wireless_draw = WirelessConnection(self, core) self.is_node_context_opened = False + # background related + self.wallpaper_id = None + self.wallpaper = None + self.wallpaper_drawn = None + self.wallpaper_file = "" + self.scale_option = tk.IntVar(value=1) + self.show_grid = tk.BooleanVar(value=True) + self.adjust_to_dim = tk.BooleanVar(value=False) + def setup_menus(self): self.node_context = tk.Menu(self.master) self.node_context.add_command( @@ -95,8 +111,6 @@ class CanvasGraph(tk.Canvas): self.drawing_edge = None self.draw_existing_component(session) - # self.grpc_manager.wlanconfig_management.load_wlan_configurations(self.core_grpc) - def setup_bindings(self): """ Bind any mouse events or hot keys to the matching action @@ -128,11 +142,12 @@ class CanvasGraph(tk.Canvas): width=1, tags="rectangle", ) - self.tag_lower(self.grid) for i in range(0, width, 27): self.create_line(i, 0, i, height, dash=(2, 4), 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.tag_lower(self.grid) def draw_existing_component(self, session): """ @@ -224,9 +239,8 @@ class CanvasGraph(tk.Canvas): if2 ) - # lift the nodes so they on top of the links - for i in self.find_withtag("node"): - self.lift(i) + # raise the nodes so they on top of the links + self.tag_raise("node") def canvas_xy(self, event): """ @@ -422,6 +436,132 @@ class CanvasGraph(tk.Canvas): self.core.add_graph_node(self.core.session_id, node.id, x, y, node_name) return node + def width_and_height(self): + """ + retrieve canvas width and height in pixels + + :return: nothing + """ + grid = self.find_withtag("rectangle")[0] + x0, y0, x1, y1 = self.coords(grid) + canvas_w = abs(x0 - x1) + canvas_h = abs(y0 - y1) + return canvas_w, canvas_h + + def wallpaper_upper_left(self): + tk_img = ImageTk.PhotoImage(self.wallpaper) + # crop image if it is bigger than canvas + canvas_w, canvas_h = self.width_and_height() + cropx = img_w = tk_img.width() + cropy = img_h = tk_img.height() + if img_w > canvas_w: + cropx -= img_w - canvas_w + if img_h > canvas_h: + cropy -= img_h - canvas_h + cropped = self.wallpaper.crop((0, 0, cropx, cropy)) + cropped_tk = ImageTk.PhotoImage(cropped) + 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" + ) + self.wallpaper_drawn = cropped_tk + + def wallpaper_center(self): + """ + place the image at the center of canvas + + :param Image img: image object + :return: nothing + """ + tk_img = ImageTk.PhotoImage(self.wallpaper) + canvas_w, canvas_h = self.width_and_height() + cropx = img_w = tk_img.width() + cropy = img_h = tk_img.height() + # dimension of the cropped image + if img_w > canvas_w: + cropx -= img_w - canvas_w + if img_h > canvas_h: + cropy -= img_h - canvas_h + x0 = (img_w - cropx) / 2 + y0 = (img_h - cropy) / 2 + x1 = x0 + cropx + y1 = y0 + cropy + cropped = self.wallpaper.crop((x0, y0, x1, y1)) + cropped_tk = ImageTk.PhotoImage(cropped) + # 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" + ) + self.wallpaper_drawn = cropped_tk + + def wallpaper_scaled(self): + """ + scale image based on canvas dimension + + :param Image img: image object + :return: nothing + """ + canvas_w, canvas_h = self.width_and_height() + 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" + ) + self.wallpaper_drawn = image + + def resize_to_wallpaper(self): + image_tk = ImageTk.PhotoImage(self.wallpaper) + 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.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): + """ + redraw grid with new dimension + + :return: nothing + """ + self.config(scrollregion=(0, 0, width + 200, height + 200)) + + # delete previous grid + self.delete("rectangle") + self.delete("gridline") + + # redraw + self.draw_grid(width=width, height=height) + + # hide/show grid + self.update_grid() + + def redraw(self): + if self.adjust_to_dim.get(): + self.resize_to_wallpaper() + else: + option = ScaleOption(self.scale_option.get()) + if option == ScaleOption.UPPER_LEFT: + self.wallpaper_upper_left() + elif option == ScaleOption.CENTERED: + self.wallpaper_center() + elif option == ScaleOption.SCALED: + self.wallpaper_scaled() + elif option == ScaleOption.TILED: + logging.warning("tiled background not implemented yet") + + 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") + else: + self.itemconfig("gridline", state=tk.HIDDEN) + class CanvasEdge: """ @@ -465,8 +605,6 @@ class CanvasEdge: self.link_info = None self.throughput = None self.wired = is_wired - # TODO resolve this - # self.canvas.tag_lower(self.id) def complete(self, dst, x, y): self.dst = dst @@ -474,8 +612,8 @@ class CanvasEdge: x1, y1, _, _ = self.canvas.coords(self.id) self.canvas.coords(self.id, x1, y1, x, y) self.canvas.helper.draw_wireless_case(self.src, self.dst, self) - self.canvas.lift(self.src) - self.canvas.lift(self.dst) + self.canvas.tag_raise(self.src) + self.canvas.tag_raise(self.dst) def delete(self): self.canvas.delete(self.id) diff --git a/coretk/coretk/icons/emane.gif b/coretk/coretk/icons/emane.gif index 8a3d3850..0531a932 100644 Binary files a/coretk/coretk/icons/emane.gif and b/coretk/coretk/icons/emane.gif differ diff --git a/coretk/coretk/icons/wlan.gif b/coretk/coretk/icons/wlan.gif index d72fe9c3..56618576 100644 Binary files a/coretk/coretk/icons/wlan.gif and b/coretk/coretk/icons/wlan.gif differ diff --git a/coretk/coretk/menuaction.py b/coretk/coretk/menuaction.py index 98b3d254..b47c6ef2 100644 --- a/coretk/coretk/menuaction.py +++ b/coretk/coretk/menuaction.py @@ -73,7 +73,6 @@ class MenuAction: def file_open_xml(self, event=None): logging.info("menuaction.py file_open_xml()") - self.app.is_open_xml = True file_path = filedialog.askopenfilename( initialdir=str(XML_PATH), title="Open", diff --git a/coretk/coretk/themes.py b/coretk/coretk/themes.py index b661b644..f6dced19 100644 --- a/coretk/coretk/themes.py +++ b/coretk/coretk/themes.py @@ -1,3 +1,4 @@ +import logging import tkinter as tk DARK = "black" @@ -6,6 +7,7 @@ DARK = "black" class Styles: tooltip = "Tooltip.TLabel" tooltip_frame = "Tooltip.TFrame" + service_checkbutton = "Service.TCheckbutton" class Colors: @@ -20,6 +22,7 @@ class Colors: selectfg = "#ffffff" white = "white" black = "black" + listboxbg = "#f2f1f0" def load(style): @@ -86,6 +89,7 @@ def load(style): "padding": (2, 0), } }, + "TLabelframe": {"configure": {"relief": tk.GROOVE}}, "TNotebook.Tab": { "configure": {"padding": (6, 2, 6, 2)}, "map": {"background": [("selected", Colors.lighter)]}, @@ -105,13 +109,18 @@ def load(style): "configure": {"justify": tk.LEFT, "relief": tk.SOLID, "borderwidth": 0} }, Styles.tooltip_frame: {"configure": {}}, + Styles.service_checkbutton: { + "configure": { + "background": Colors.listboxbg, + "foreground": Colors.black, + } + }, }, ) -def update_toplevel(style, event): - if not isinstance(event.widget, tk.Toplevel): - return +def update_bg(style, event): + logging.info("updating background: %s", event.widget) bg = style.lookup(".", "background") event.widget.config(background=bg) diff --git a/coretk/coretk/widgets.py b/coretk/coretk/widgets.py index 87651701..e825fdb4 100644 --- a/coretk/coretk/widgets.py +++ b/coretk/coretk/widgets.py @@ -18,11 +18,13 @@ INT_TYPES = { class FrameScroll(ttk.LabelFrame): - def __init__(self, master=None, _cls=tk.Frame, **kw): + def __init__(self, master, app, _cls=ttk.Frame, **kw): super().__init__(master, **kw) + self.app = app self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) - self.canvas = tk.Canvas(self, highlightthickness=0) + bg = self.app.style.lookup(".", "background") + self.canvas = tk.Canvas(self, highlightthickness=0, background=bg) self.canvas.grid(row=0, sticky="nsew", padx=2, pady=2) self.canvas.columnconfigure(0, weight=1) self.canvas.rowconfigure(0, weight=1) @@ -54,8 +56,8 @@ class FrameScroll(ttk.LabelFrame): class ConfigFrame(FrameScroll): - def __init__(self, master=None, config=None, **kw): - super().__init__(master, ttk.Notebook, **kw) + def __init__(self, master, app, config, **kw): + super().__init__(master, app, ttk.Notebook, **kw) self.config = config self.values = {} @@ -136,13 +138,14 @@ class ListboxScroll(ttk.LabelFrame): self.listbox = tk.Listbox( self, selectmode=tk.SINGLE, yscrollcommand=self.scrollbar.set ) + logging.info("listbox background: %s", self.listbox.cget("background")) self.listbox.grid(row=0, column=0, sticky="nsew") self.scrollbar.config(command=self.listbox.yview) class CheckboxList(FrameScroll): - def __init__(self, master=None, clicked=None, **kw): - super().__init__(master, **kw) + def __init__(self, master, app, clicked=None, **kw): + super().__init__(master, app, **kw) self.clicked = clicked self.frame.columnconfigure(0, weight=1)