2020-05-03 20:42:56 +01:00
|
|
|
import logging
|
2020-03-02 18:18:37 +00:00
|
|
|
import math
|
2019-09-16 00:00:01 +01:00
|
|
|
import tkinter as tk
|
2020-05-16 07:23:07 +01:00
|
|
|
from tkinter import PhotoImage, font, ttk
|
2020-05-03 18:41:36 +01:00
|
|
|
from tkinter.ttk import Progressbar
|
2019-09-16 00:00:01 +01:00
|
|
|
|
2020-05-03 20:42:56 +01:00
|
|
|
import grpc
|
|
|
|
|
2019-12-19 17:30:21 +00:00
|
|
|
from core.gui import appconfig, themes
|
|
|
|
from core.gui.coreclient import CoreClient
|
2020-05-03 20:42:56 +01:00
|
|
|
from core.gui.dialogs.error import ErrorDialog
|
2019-12-19 17:30:21 +00:00
|
|
|
from core.gui.graph.graph import CanvasGraph
|
|
|
|
from core.gui.images import ImageEnum, Images
|
|
|
|
from core.gui.menubar import Menubar
|
|
|
|
from core.gui.nodeutils import NodeUtils
|
|
|
|
from core.gui.statusbar import StatusBar
|
|
|
|
from core.gui.toolbar import Toolbar
|
2019-09-16 00:00:01 +01:00
|
|
|
|
2019-12-11 22:09:50 +00:00
|
|
|
WIDTH = 1000
|
|
|
|
HEIGHT = 800
|
|
|
|
|
2019-09-16 00:00:01 +01:00
|
|
|
|
2020-05-03 07:51:42 +01:00
|
|
|
class Application(ttk.Frame):
|
2020-05-12 16:31:53 +01:00
|
|
|
def __init__(self, proxy: bool) -> None:
|
|
|
|
super().__init__()
|
2019-11-16 07:31:41 +00:00
|
|
|
# load node icons
|
|
|
|
NodeUtils.setup()
|
|
|
|
|
2019-11-14 19:26:20 +00:00
|
|
|
# widgets
|
2019-09-16 00:00:01 +01:00
|
|
|
self.menubar = None
|
2019-11-07 23:58:02 +00:00
|
|
|
self.toolbar = None
|
2020-05-03 07:51:42 +01:00
|
|
|
self.right_frame = None
|
2019-11-08 05:46:40 +00:00
|
|
|
self.canvas = None
|
|
|
|
self.statusbar = None
|
2020-05-03 18:41:36 +01:00
|
|
|
self.progress = None
|
2019-11-14 19:26:20 +00:00
|
|
|
|
2020-02-17 19:10:13 +00:00
|
|
|
# fonts
|
|
|
|
self.fonts_size = None
|
|
|
|
self.icon_text_font = None
|
|
|
|
self.edge_font = None
|
|
|
|
|
2019-11-14 19:26:20 +00:00
|
|
|
# setup
|
2019-11-22 06:03:07 +00:00
|
|
|
self.guiconfig = appconfig.read()
|
2020-05-08 08:16:25 +01:00
|
|
|
self.app_scale = self.guiconfig.scale
|
2020-02-17 19:10:13 +00:00
|
|
|
self.setup_scaling()
|
2019-11-14 19:26:20 +00:00
|
|
|
self.style = ttk.Style()
|
|
|
|
self.setup_theme()
|
2020-01-15 06:15:00 +00:00
|
|
|
self.core = CoreClient(self, proxy)
|
2019-11-01 17:45:47 +00:00
|
|
|
self.setup_app()
|
2019-11-07 23:58:02 +00:00
|
|
|
self.draw()
|
2020-03-05 04:42:40 +00:00
|
|
|
self.core.setup()
|
2019-10-02 00:25:26 +01:00
|
|
|
|
2020-05-12 16:31:53 +01:00
|
|
|
def setup_scaling(self) -> None:
|
2020-02-17 19:10:13 +00:00
|
|
|
self.fonts_size = {name: font.nametofont(name)["size"] for name in font.names()}
|
2020-03-02 18:18:37 +00:00
|
|
|
text_scale = self.app_scale if self.app_scale < 1 else math.sqrt(self.app_scale)
|
2020-02-17 23:14:52 +00:00
|
|
|
themes.scale_fonts(self.fonts_size, self.app_scale)
|
2020-03-02 18:18:37 +00:00
|
|
|
self.icon_text_font = font.Font(family="TkIconFont", size=int(12 * text_scale))
|
2020-05-02 07:47:37 +01:00
|
|
|
self.edge_font = font.Font(
|
|
|
|
family="TkDefaultFont", size=int(8 * text_scale), weight=font.BOLD
|
|
|
|
)
|
2020-02-17 19:10:13 +00:00
|
|
|
|
2020-05-12 16:31:53 +01:00
|
|
|
def setup_theme(self) -> None:
|
2019-11-13 18:45:43 +00:00
|
|
|
themes.load(self.style)
|
2019-12-16 22:30:38 +00:00
|
|
|
self.master.bind_class("Menu", "<<ThemeChanged>>", themes.theme_change_menu)
|
|
|
|
self.master.bind("<<ThemeChanged>>", themes.theme_change)
|
2020-05-08 08:16:25 +01:00
|
|
|
self.style.theme_use(self.guiconfig.preferences.theme)
|
2019-11-13 18:45:43 +00:00
|
|
|
|
2020-05-12 16:31:53 +01:00
|
|
|
def setup_app(self) -> None:
|
2019-09-16 07:45:13 +01:00
|
|
|
self.master.title("CORE")
|
2019-11-14 19:26:20 +00:00
|
|
|
self.center()
|
2019-11-01 20:57:41 +00:00
|
|
|
self.master.protocol("WM_DELETE_WINDOW", self.on_closing)
|
2019-11-13 01:32:34 +00:00
|
|
|
image = Images.get(ImageEnum.CORE, 16)
|
2019-09-16 07:45:13 +01:00
|
|
|
self.master.tk.call("wm", "iconphoto", self.master._w, image)
|
2020-05-03 07:51:42 +01:00
|
|
|
self.master.option_add("*tearOff", tk.FALSE)
|
2020-05-12 16:31:53 +01:00
|
|
|
self.setup_file_dialogs()
|
|
|
|
|
|
|
|
def setup_file_dialogs(self) -> None:
|
|
|
|
"""
|
|
|
|
Hack code that needs to initialize a bad dialog so that we can apply,
|
|
|
|
global settings for dialogs to not show hidden files by default and display
|
|
|
|
the hidden file toggle.
|
|
|
|
|
|
|
|
:return: nothing
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
self.master.tk.call("tk_getOpenFile", "-foobar")
|
|
|
|
except tk.TclError:
|
|
|
|
pass
|
|
|
|
self.master.tk.call("set", "::tk::dialog::file::showHiddenBtn", "1")
|
|
|
|
self.master.tk.call("set", "::tk::dialog::file::showHiddenVar", "0")
|
|
|
|
|
|
|
|
def center(self) -> None:
|
2019-11-14 19:26:20 +00:00
|
|
|
screen_width = self.master.winfo_screenwidth()
|
|
|
|
screen_height = self.master.winfo_screenheight()
|
2020-02-17 23:14:52 +00:00
|
|
|
x = int((screen_width / 2) - (WIDTH * self.app_scale / 2))
|
|
|
|
y = int((screen_height / 2) - (HEIGHT * self.app_scale / 2))
|
2020-02-17 19:10:13 +00:00
|
|
|
self.master.geometry(
|
|
|
|
f"{int(WIDTH * self.app_scale)}x{int(HEIGHT * self.app_scale)}+{x}+{y}"
|
|
|
|
)
|
2019-11-14 19:26:20 +00:00
|
|
|
|
2020-05-12 16:31:53 +01:00
|
|
|
def draw(self) -> None:
|
2020-05-03 07:51:42 +01:00
|
|
|
self.master.rowconfigure(0, weight=1)
|
|
|
|
self.master.columnconfigure(0, weight=1)
|
|
|
|
self.rowconfigure(0, weight=1)
|
|
|
|
self.columnconfigure(1, weight=1)
|
|
|
|
self.grid(sticky="nsew")
|
2020-05-15 19:43:54 +01:00
|
|
|
self.toolbar = Toolbar(self)
|
2020-05-03 07:51:42 +01:00
|
|
|
self.toolbar.grid(sticky="ns")
|
|
|
|
self.right_frame = ttk.Frame(self)
|
|
|
|
self.right_frame.columnconfigure(0, weight=1)
|
|
|
|
self.right_frame.rowconfigure(0, weight=1)
|
|
|
|
self.right_frame.grid(row=0, column=1, sticky="nsew")
|
2019-11-08 05:46:40 +00:00
|
|
|
self.draw_canvas()
|
|
|
|
self.draw_status()
|
2020-05-03 18:41:36 +01:00
|
|
|
self.progress = Progressbar(self.right_frame, mode="indeterminate")
|
2020-05-15 22:46:35 +01:00
|
|
|
self.menubar = Menubar(self)
|
|
|
|
self.master.config(menu=self.menubar)
|
2019-09-16 00:00:01 +01:00
|
|
|
|
2020-05-12 16:31:53 +01:00
|
|
|
def draw_canvas(self) -> None:
|
2020-05-03 07:51:42 +01:00
|
|
|
canvas_frame = ttk.Frame(self.right_frame)
|
|
|
|
canvas_frame.rowconfigure(0, weight=1)
|
|
|
|
canvas_frame.columnconfigure(0, weight=1)
|
|
|
|
canvas_frame.grid(sticky="nsew", pady=1)
|
2020-05-15 22:46:35 +01:00
|
|
|
self.canvas = CanvasGraph(canvas_frame, self, self.core)
|
2020-05-03 07:51:42 +01:00
|
|
|
self.canvas.grid(sticky="nsew")
|
|
|
|
scroll_y = ttk.Scrollbar(canvas_frame, command=self.canvas.yview)
|
|
|
|
scroll_y.grid(row=0, column=1, sticky="ns")
|
2019-11-13 01:32:34 +00:00
|
|
|
scroll_x = ttk.Scrollbar(
|
2020-05-03 07:51:42 +01:00
|
|
|
canvas_frame, orient=tk.HORIZONTAL, command=self.canvas.xview
|
2019-09-16 00:00:01 +01:00
|
|
|
)
|
2020-05-03 07:51:42 +01:00
|
|
|
scroll_x.grid(row=1, column=0, sticky="ew")
|
2019-09-16 00:00:01 +01:00
|
|
|
self.canvas.configure(xscrollcommand=scroll_x.set)
|
|
|
|
self.canvas.configure(yscrollcommand=scroll_y.set)
|
|
|
|
|
2020-05-12 16:31:53 +01:00
|
|
|
def draw_status(self) -> None:
|
2020-05-03 07:51:42 +01:00
|
|
|
self.statusbar = StatusBar(self.right_frame, self)
|
|
|
|
self.statusbar.grid(sticky="ew")
|
2019-09-16 00:00:01 +01:00
|
|
|
|
2020-05-03 20:42:56 +01:00
|
|
|
def show_grpc_exception(self, title: str, e: grpc.RpcError) -> None:
|
|
|
|
logging.exception("app grpc exception", exc_info=e)
|
|
|
|
message = e.details()
|
|
|
|
self.show_error(title, message)
|
|
|
|
|
|
|
|
def show_exception(self, title: str, e: Exception) -> None:
|
|
|
|
logging.exception("app exception", exc_info=e)
|
|
|
|
self.show_error(title, str(e))
|
|
|
|
|
|
|
|
def show_error(self, title: str, message: str) -> None:
|
2020-05-05 06:50:59 +01:00
|
|
|
self.after(0, lambda: ErrorDialog(self, title, message).show())
|
2020-05-03 20:42:56 +01:00
|
|
|
|
2020-05-12 16:31:53 +01:00
|
|
|
def on_closing(self) -> None:
|
2020-05-16 09:14:48 +01:00
|
|
|
if self.toolbar.picker:
|
|
|
|
self.toolbar.picker.destroy()
|
2020-04-18 08:33:22 +01:00
|
|
|
self.menubar.prompt_save_running_session(True)
|
2019-10-19 00:42:00 +01:00
|
|
|
|
2020-05-12 16:31:53 +01:00
|
|
|
def save_config(self) -> None:
|
2019-11-22 06:03:07 +00:00
|
|
|
appconfig.save(self.guiconfig)
|
2019-11-11 23:35:48 +00:00
|
|
|
|
2020-05-12 16:31:53 +01:00
|
|
|
def joined_session_update(self) -> None:
|
2019-12-31 00:34:44 +00:00
|
|
|
if self.core.is_runtime():
|
|
|
|
self.toolbar.set_runtime()
|
|
|
|
else:
|
|
|
|
self.toolbar.set_design()
|
|
|
|
|
2020-05-16 07:23:07 +01:00
|
|
|
def get_icon(self, image_enum: ImageEnum, width: int) -> PhotoImage:
|
|
|
|
return Images.get(image_enum, int(width * self.app_scale))
|
|
|
|
|
2020-05-16 09:14:48 +01:00
|
|
|
def get_custom_icon(self, image_file: str, width: int) -> PhotoImage:
|
|
|
|
return Images.get_custom(image_file, int(width * self.app_scale))
|
|
|
|
|
2020-05-12 16:31:53 +01:00
|
|
|
def close(self) -> None:
|
2019-12-10 00:23:09 +00:00
|
|
|
self.master.destroy()
|