pygui: refactored images.py and fixed issue with recreating a default config.yaml every time the gui was started

This commit is contained in:
Blake Harnden 2021-02-18 21:04:16 -08:00
parent 47ac4c850d
commit a6fadb76cc
16 changed files with 96 additions and 103 deletions

View file

@ -7,7 +7,7 @@ from typing import Any, Dict, Optional, Type
import grpc import grpc
from core.gui import appconfig from core.gui import appconfig, images
from core.gui import nodeutils as nutils from core.gui import nodeutils as nutils
from core.gui import themes from core.gui import themes
from core.gui.appconfig import GuiConfig from core.gui.appconfig import GuiConfig
@ -16,7 +16,7 @@ from core.gui.dialogs.error import ErrorDialog
from core.gui.frames.base import InfoFrameBase from core.gui.frames.base import InfoFrameBase
from core.gui.frames.default import DefaultInfoFrame from core.gui.frames.default import DefaultInfoFrame
from core.gui.graph.manager import CanvasManager from core.gui.graph.manager import CanvasManager
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum
from core.gui.menubar import Menubar from core.gui.menubar import Menubar
from core.gui.statusbar import StatusBar from core.gui.statusbar import StatusBar
from core.gui.themes import PADY from core.gui.themes import PADY
@ -78,7 +78,7 @@ class Application(ttk.Frame):
self.master.title("CORE") self.master.title("CORE")
self.center() self.center()
self.master.protocol("WM_DELETE_WINDOW", self.on_closing) self.master.protocol("WM_DELETE_WINDOW", self.on_closing)
image = Images.get(ImageEnum.CORE, 16) image = images.from_enum(ImageEnum.CORE, width=images.DIALOG_SIZE)
self.master.tk.call("wm", "iconphoto", self.master._w, image) self.master.tk.call("wm", "iconphoto", self.master._w, image)
self.master.option_add("*tearOff", tk.FALSE) self.master.option_add("*tearOff", tk.FALSE)
self.setup_file_dialogs() self.setup_file_dialogs()
@ -197,10 +197,10 @@ class Application(ttk.Frame):
self.toolbar.set_design() self.toolbar.set_design()
def get_icon(self, image_enum: ImageEnum, width: int) -> PhotoImage: def get_icon(self, image_enum: ImageEnum, width: int) -> PhotoImage:
return Images.get(image_enum, int(width * self.app_scale)) return images.from_enum(image_enum, width=width, scale=self.app_scale)
def get_custom_icon(self, image_file: str, width: int) -> PhotoImage: def get_custom_icon(self, image_file: str, width: int) -> PhotoImage:
return Images.get_custom(image_file, int(width * self.app_scale)) return images.from_name(image_file, width=width, scale=self.app_scale)
def close(self) -> None: def close(self) -> None:
self.master.destroy() self.master.destroy()

View file

@ -206,20 +206,19 @@ def check_directory() -> None:
MOBILITY_PATH.mkdir(exist_ok=True) MOBILITY_PATH.mkdir(exist_ok=True)
XMLS_PATH.mkdir(exist_ok=True) XMLS_PATH.mkdir(exist_ok=True)
SCRIPT_PATH.mkdir(exist_ok=True) SCRIPT_PATH.mkdir(exist_ok=True)
copy_files(LOCAL_ICONS_PATH, ICONS_PATH) copy_files(LOCAL_ICONS_PATH, ICONS_PATH)
copy_files(LOCAL_BACKGROUND_PATH, BACKGROUNDS_PATH) copy_files(LOCAL_BACKGROUND_PATH, BACKGROUNDS_PATH)
copy_files(LOCAL_XMLS_PATH, XMLS_PATH) copy_files(LOCAL_XMLS_PATH, XMLS_PATH)
copy_files(LOCAL_MOBILITY_PATH, MOBILITY_PATH) copy_files(LOCAL_MOBILITY_PATH, MOBILITY_PATH)
if not CONFIG_PATH.exists():
terminal = find_terminal() terminal = find_terminal()
if "EDITOR" in os.environ: if "EDITOR" in os.environ:
editor = EDITORS[0] editor = EDITORS[0]
else: else:
editor = EDITORS[1] editor = EDITORS[1]
preferences = PreferencesConfig(editor, terminal) preferences = PreferencesConfig(editor, terminal)
config = GuiConfig(preferences=preferences) config = GuiConfig(preferences=preferences)
save(config) save(config)
def read() -> GuiConfig: def read() -> GuiConfig:

View file

@ -6,10 +6,10 @@ import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, List, Optional from typing import TYPE_CHECKING, List, Optional
from core.gui import images
from core.gui.appconfig import BACKGROUNDS_PATH from core.gui.appconfig import BACKGROUNDS_PATH
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.graph.graph import CanvasGraph from core.gui.graph.graph import CanvasGraph
from core.gui.images import Images
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
from core.gui.widgets import image_chooser from core.gui.widgets import image_chooser
@ -132,7 +132,7 @@ class CanvasWallpaperDialog(Dialog):
self.draw_preview() self.draw_preview()
def draw_preview(self) -> None: def draw_preview(self) -> None:
image = Images.create(self.filename.get(), 250, 135) image = images.from_file(self.filename.get(), width=250, height=135)
self.image_label.config(image=image) self.image_label.config(image=image)
self.image_label.image = image self.image_label.image = image

View file

@ -6,10 +6,9 @@ from typing import TYPE_CHECKING, Optional, Set
from PIL.ImageTk import PhotoImage from PIL.ImageTk import PhotoImage
from core.gui import nodeutils from core.gui import images
from core.gui.appconfig import ICONS_PATH, CustomNode from core.gui.appconfig import ICONS_PATH, CustomNode
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.images import Images
from core.gui.nodeutils import NodeDraw from core.gui.nodeutils import NodeDraw
from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import CheckboxList, ListboxScroll, image_chooser from core.gui.widgets import CheckboxList, ListboxScroll, image_chooser
@ -190,7 +189,7 @@ class CustomNodesDialog(Dialog):
def click_icon(self) -> None: def click_icon(self) -> None:
file_path = image_chooser(self, ICONS_PATH) file_path = image_chooser(self, ICONS_PATH)
if file_path: if file_path:
image = Images.create(file_path, nodeutils.ICON_SIZE) image = images.from_file(file_path, width=images.NODE_SIZE)
self.image = image self.image = image
self.image_file = file_path self.image_file = file_path
self.image_button.config(image=self.image) self.image_button.config(image=self.image)

View file

@ -2,7 +2,8 @@ import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from core.gui.images import ImageEnum, Images from core.gui import images
from core.gui.images import ImageEnum
from core.gui.themes import DIALOG_PAD from core.gui.themes import DIALOG_PAD
if TYPE_CHECKING: if TYPE_CHECKING:
@ -25,7 +26,7 @@ class Dialog(tk.Toplevel):
self.modal: bool = modal self.modal: bool = modal
self.title(title) self.title(title)
self.protocol("WM_DELETE_WINDOW", self.destroy) self.protocol("WM_DELETE_WINDOW", self.destroy)
image = Images.get(ImageEnum.CORE, 16) image = images.from_enum(ImageEnum.CORE, width=images.DIALOG_SIZE)
self.tk.call("wm", "iconphoto", self._w, image) self.tk.call("wm", "iconphoto", self._w, image)
self.columnconfigure(0, weight=1) self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1) self.rowconfigure(0, weight=1)

View file

@ -9,8 +9,9 @@ from typing import TYPE_CHECKING, Dict, List, Optional
import grpc import grpc
from core.api.grpc.wrappers import ConfigOption, Node from core.api.grpc.wrappers import ConfigOption, Node
from core.gui import images
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame from core.gui.widgets import ConfigFrame
@ -143,7 +144,7 @@ class EmaneConfigDialog(Dialog):
) )
label.grid(pady=PADY) label.grid(pady=PADY)
image = Images.get(ImageEnum.EDITNODE, 16) image = images.from_enum(ImageEnum.EDITNODE, width=images.BUTTON_SIZE)
button = ttk.Button( button = ttk.Button(
self.top, self.top,
image=image, image=image,
@ -181,7 +182,7 @@ class EmaneConfigDialog(Dialog):
for i in range(2): for i in range(2):
frame.columnconfigure(i, weight=1) frame.columnconfigure(i, weight=1)
image = Images.get(ImageEnum.EDITNODE, 16) image = images.from_enum(ImageEnum.EDITNODE, width=images.BUTTON_SIZE)
self.emane_model_button = ttk.Button( self.emane_model_button = ttk.Button(
frame, frame,
text=f"{self.emane_model.get()} options", text=f"{self.emane_model.get()} options",
@ -192,7 +193,7 @@ class EmaneConfigDialog(Dialog):
self.emane_model_button.image = image self.emane_model_button.image = image
self.emane_model_button.grid(row=0, column=0, padx=PADX, sticky=tk.EW) self.emane_model_button.grid(row=0, column=0, padx=PADX, sticky=tk.EW)
image = Images.get(ImageEnum.EDITNODE, 16) image = images.from_enum(ImageEnum.EDITNODE, width=images.BUTTON_SIZE)
button = ttk.Button( button = ttk.Button(
frame, frame,
text="EMANE options", text="EMANE options",

View file

@ -2,8 +2,9 @@ import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from core.gui import images
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum
from core.gui.themes import PADY from core.gui.themes import PADY
from core.gui.widgets import CodeText from core.gui.widgets import CodeText
@ -22,7 +23,7 @@ class ErrorDialog(Dialog):
def draw(self) -> None: def draw(self) -> None:
self.top.columnconfigure(0, weight=1) self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(1, weight=1) self.top.rowconfigure(1, weight=1)
image = Images.get(ImageEnum.ERROR, 24) image = images.from_enum(ImageEnum.ERROR, width=images.ERROR_SIZE)
label = ttk.Label( label = ttk.Label(
self.top, text=self.title, image=image, compound=tk.LEFT, anchor=tk.CENTER self.top, text=self.title, image=image, compound=tk.LEFT, anchor=tk.CENTER
) )

View file

@ -8,12 +8,12 @@ import netaddr
from PIL.ImageTk import PhotoImage from PIL.ImageTk import PhotoImage
from core.api.grpc.wrappers import Interface, Node from core.api.grpc.wrappers import Interface, Node
from core.gui import images
from core.gui import nodeutils as nutils from core.gui import nodeutils as nutils
from core.gui import validation from core.gui import validation
from core.gui.appconfig import ICONS_PATH from core.gui.appconfig import ICONS_PATH
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.dialogs.emaneconfig import EmaneModelDialog from core.gui.dialogs.emaneconfig import EmaneModelDialog
from core.gui.images import Images
from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import ListboxScroll, image_chooser from core.gui.widgets import ListboxScroll, image_chooser
@ -371,7 +371,7 @@ class NodeConfigDialog(Dialog):
def click_icon(self) -> None: def click_icon(self) -> None:
file_path = image_chooser(self, ICONS_PATH) file_path = image_chooser(self, ICONS_PATH)
if file_path: if file_path:
self.image = Images.create(file_path, nutils.ICON_SIZE) self.image = images.from_file(file_path, width=images.NODE_SIZE)
self.image_button.config(image=self.image) self.image_button.config(image=self.image)
self.image_file = file_path self.image_file = file_path

View file

@ -8,9 +8,10 @@ import grpc
from PIL.ImageTk import PhotoImage from PIL.ImageTk import PhotoImage
from core.api.grpc.wrappers import Node, NodeServiceData, ServiceValidationMode from core.api.grpc.wrappers import Node, NodeServiceData, ServiceValidationMode
from core.gui import images
from core.gui.dialogs.copyserviceconfig import CopyServiceConfigDialog from core.gui.dialogs.copyserviceconfig import CopyServiceConfigDialog
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum
from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import CodeText, ListboxScroll from core.gui.widgets import CodeText, ListboxScroll
@ -179,7 +180,7 @@ class ServiceConfigDialog(Dialog):
button.grid(row=0, column=0, sticky=tk.W, padx=PADX) button.grid(row=0, column=0, sticky=tk.W, padx=PADX)
entry = ttk.Entry(frame, state=tk.DISABLED) entry = ttk.Entry(frame, state=tk.DISABLED)
entry.grid(row=0, column=1, sticky=tk.EW, padx=PADX) entry.grid(row=0, column=1, sticky=tk.EW, padx=PADX)
image = Images.get(ImageEnum.FILEOPEN, 16) image = images.from_enum(ImageEnum.FILEOPEN, width=images.BUTTON_SIZE)
button = ttk.Button(frame, image=image) button = ttk.Button(frame, image=image)
button.image = image button.image = image
button.grid(row=0, column=2) button.grid(row=0, column=2)
@ -194,11 +195,11 @@ class ServiceConfigDialog(Dialog):
value=2, value=2,
) )
button.grid(row=0, column=0, sticky=tk.EW) button.grid(row=0, column=0, sticky=tk.EW)
image = Images.get(ImageEnum.FILEOPEN, 16) image = images.from_enum(ImageEnum.FILEOPEN, width=images.BUTTON_SIZE)
button = ttk.Button(frame, image=image) button = ttk.Button(frame, image=image)
button.image = image button.image = image
button.grid(row=0, column=1) button.grid(row=0, column=1)
image = Images.get(ImageEnum.DOCUMENTSAVE, 16) image = images.from_enum(ImageEnum.DOCUMENTSAVE, width=images.BUTTON_SIZE)
button = ttk.Button(frame, image=image) button = ttk.Button(frame, image=image)
button.image = image button.image = image
button.grid(row=0, column=2) button.grid(row=0, column=2)

View file

@ -6,8 +6,9 @@ from typing import TYPE_CHECKING, List, Optional
import grpc import grpc
from core.api.grpc.wrappers import SessionState, SessionSummary from core.api.grpc.wrappers import SessionState, SessionSummary
from core.gui import images
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum
from core.gui.task import ProgressTask from core.gui.task import ProgressTask
from core.gui.themes import PADX, PADY from core.gui.themes import PADX, PADY
@ -108,14 +109,14 @@ class SessionsDialog(Dialog):
frame.columnconfigure(i, weight=1) frame.columnconfigure(i, weight=1)
frame.grid(sticky=tk.EW) frame.grid(sticky=tk.EW)
image = Images.get(ImageEnum.DOCUMENTNEW, 16) image = images.from_enum(ImageEnum.DOCUMENTNEW, width=images.BUTTON_SIZE)
b = ttk.Button( b = ttk.Button(
frame, image=image, text="New", compound=tk.LEFT, command=self.click_new frame, image=image, text="New", compound=tk.LEFT, command=self.click_new
) )
b.image = image b.image = image
b.grid(row=0, padx=PADX, sticky=tk.EW) b.grid(row=0, padx=PADX, sticky=tk.EW)
image = Images.get(ImageEnum.FILEOPEN, 16) image = images.from_enum(ImageEnum.FILEOPEN, width=images.BUTTON_SIZE)
self.connect_button = ttk.Button( self.connect_button = ttk.Button(
frame, frame,
image=image, image=image,
@ -127,7 +128,7 @@ class SessionsDialog(Dialog):
self.connect_button.image = image self.connect_button.image = image
self.connect_button.grid(row=0, column=1, padx=PADX, sticky=tk.EW) self.connect_button.grid(row=0, column=1, padx=PADX, sticky=tk.EW)
image = Images.get(ImageEnum.DELETE, 16) image = images.from_enum(ImageEnum.DELETE, width=images.BUTTON_SIZE)
self.delete_button = ttk.Button( self.delete_button = ttk.Button(
frame, frame,
image=image, image=image,
@ -139,7 +140,7 @@ class SessionsDialog(Dialog):
self.delete_button.image = image self.delete_button.image = image
self.delete_button.grid(row=0, column=2, padx=PADX, sticky=tk.EW) self.delete_button.grid(row=0, column=2, padx=PADX, sticky=tk.EW)
image = Images.get(ImageEnum.CANCEL, 16) image = images.from_enum(ImageEnum.CANCEL, width=images.BUTTON_SIZE)
if self.is_start_app: if self.is_start_app:
b = ttk.Button( b = ttk.Button(
frame, frame,

View file

@ -5,6 +5,7 @@ from tkinter import BooleanVar, messagebox, ttk
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, ValuesView from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, ValuesView
from core.api.grpc.wrappers import Link, LinkType, Node, Session, ThroughputsEvent from core.api.grpc.wrappers import Link, LinkType, Node, Session, ThroughputsEvent
from core.gui import images
from core.gui import nodeutils as nutils from core.gui import nodeutils as nutils
from core.gui.graph import tags from core.gui.graph import tags
from core.gui.graph.edges import ( from core.gui.graph.edges import (
@ -18,7 +19,7 @@ from core.gui.graph.graph import CanvasGraph
from core.gui.graph.node import CanvasNode from core.gui.graph.node import CanvasNode
from core.gui.graph.shapeutils import ShapeType from core.gui.graph.shapeutils import ShapeType
from core.gui.images import ImageEnum from core.gui.images import ImageEnum
from core.gui.nodeutils import ICON_SIZE, NodeDraw from core.gui.nodeutils import NodeDraw
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -303,7 +304,7 @@ class CanvasManager:
# if the gui can't find node's image, default to the "edit-node" image # if the gui can't find node's image, default to the "edit-node" image
image = nutils.get_icon(core_node, self.app.guiconfig, self.app.app_scale) image = nutils.get_icon(core_node, self.app.guiconfig, self.app.app_scale)
if not image: if not image:
image = self.app.get_icon(ImageEnum.EDITNODE, ICON_SIZE) image = self.app.get_icon(ImageEnum.EDITNODE, images.NODE_SIZE)
x = core_node.position.x x = core_node.position.x
y = core_node.position.y y = core_node.position.y
node = CanvasNode(self.app, canvas, x, y, core_node, image) node = CanvasNode(self.app, canvas, x, y, core_node, image)

View file

@ -9,6 +9,7 @@ from PIL.ImageTk import PhotoImage
from core.api.grpc.services_pb2 import ServiceAction from core.api.grpc.services_pb2 import ServiceAction
from core.api.grpc.wrappers import Interface, Node, NodeType from core.api.grpc.wrappers import Interface, Node, NodeType
from core.gui import images
from core.gui import nodeutils as nutils from core.gui import nodeutils as nutils
from core.gui import themes from core.gui import themes
from core.gui.dialogs.emaneconfig import EmaneConfigDialog from core.gui.dialogs.emaneconfig import EmaneConfigDialog
@ -21,8 +22,7 @@ from core.gui.frames.node import NodeInfoFrame
from core.gui.graph import tags from core.gui.graph import tags
from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge
from core.gui.graph.tooltip import CanvasTooltip from core.gui.graph.tooltip import CanvasTooltip
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum
from core.gui.nodeutils import ANTENNA_SIZE, ICON_SIZE
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
@ -95,7 +95,7 @@ class CanvasNode:
def add_antenna(self) -> None: def add_antenna(self) -> None:
x, y = self.position() x, y = self.position()
offset = len(self.antennas) * 8 * self.app.app_scale offset = len(self.antennas) * 8 * self.app.app_scale
img = self.app.get_icon(ImageEnum.ANTENNA, ANTENNA_SIZE) img = self.app.get_icon(ImageEnum.ANTENNA, images.ANTENNA_SIZE)
antenna_id = self.canvas.create_image( antenna_id = self.canvas.create_image(
x - 16 + offset, x - 16 + offset,
y - int(23 * self.app.app_scale), y - int(23 * self.app.app_scale),
@ -389,7 +389,7 @@ class CanvasNode:
def scale_antennas(self) -> None: def scale_antennas(self) -> None:
for i in range(len(self.antennas)): for i in range(len(self.antennas)):
antenna_id = self.antennas[i] antenna_id = self.antennas[i]
image = self.app.get_icon(ImageEnum.ANTENNA, ANTENNA_SIZE) image = self.app.get_icon(ImageEnum.ANTENNA, images.ANTENNA_SIZE)
self.canvas.itemconfig(antenna_id, image=image) self.canvas.itemconfig(antenna_id, image=image)
self.antenna_images[antenna_id] = image self.antenna_images[antenna_id] = image
node_x, node_y = self.canvas.coords(self.id) node_x, node_y = self.canvas.coords(self.id)
@ -403,7 +403,7 @@ class CanvasNode:
logging.error(f"node icon does not exist: {icon_path}") logging.error(f"node icon does not exist: {icon_path}")
return return
self.core_node.icon = icon_path self.core_node.icon = icon_path
self.image = Images.create(icon_path) self.image = images.from_file(icon_path, width=images.NODE_SIZE)
self.canvas.itemconfig(self.id, image=self.image) self.canvas.itemconfig(self.id, image=self.image)
def is_linkable(self, node: "CanvasNode") -> bool: def is_linkable(self, node: "CanvasNode") -> bool:
@ -492,7 +492,7 @@ class ShadowNode:
self.node: "CanvasNode" = node self.node: "CanvasNode" = node
self.id: Optional[int] = None self.id: Optional[int] = None
self.text_id: Optional[int] = None self.text_id: Optional[int] = None
self.image: PhotoImage = self.app.get_icon(ImageEnum.SHADOW, ICON_SIZE) self.image: PhotoImage = self.app.get_icon(ImageEnum.SHADOW, images.NODE_SIZE)
self.draw() self.draw()
self.setup_bindings() self.setup_bindings()

View file

@ -1,5 +1,4 @@
from enum import Enum from enum import Enum
from tkinter import messagebox
from typing import Dict, Optional, Tuple from typing import Dict, Optional, Tuple
from PIL import Image from PIL import Image
@ -8,52 +7,43 @@ from PIL.ImageTk import PhotoImage
from core.api.grpc.wrappers import NodeType from core.api.grpc.wrappers import NodeType
from core.gui.appconfig import LOCAL_ICONS_PATH from core.gui.appconfig import LOCAL_ICONS_PATH
ICON_SIZE: int = 48 NODE_SIZE: int = 48
ANTENNA_SIZE: int = 32
BUTTON_SIZE: int = 16
ERROR_SIZE: int = 24
DIALOG_SIZE: int = 16
IMAGES: Dict[str, str] = {}
class Images: def load_all() -> None:
images: Dict[str, str] = {} for image in LOCAL_ICONS_PATH.glob("*"):
IMAGES[image.stem] = str(image)
@classmethod
def create(
cls, file_path: str, width: int = None, height: int = None
) -> PhotoImage:
if width is None:
width = ICON_SIZE
if height is None:
height = width
image = Image.open(file_path)
image = image.resize((width, height), Image.ANTIALIAS)
return PhotoImage(image)
@classmethod def from_file(
def load_all(cls) -> None: file_path: str, *, width: int, height: int = None, scale: float = 1.0
for image in LOCAL_ICONS_PATH.glob("*"): ) -> PhotoImage:
cls.images[image.stem] = str(image) if height is None:
height = width
width = int(width * scale)
height = int(height * scale)
image = Image.open(file_path)
image = image.resize((width, height), Image.ANTIALIAS)
return PhotoImage(image)
@classmethod
def get(cls, image_enum: Enum, width: int, height: int = None) -> PhotoImage:
file_path = cls.images[image_enum.value]
return cls.create(file_path, width, height)
@classmethod def from_enum(
def get_with_image_file( image_enum: "ImageEnum", *, width: int, height: int = None, scale: float = 1.0
cls, stem: str, width: int, height: int = None ) -> PhotoImage:
) -> PhotoImage: file_path = IMAGES[image_enum.value]
file_path = cls.images[stem] return from_file(file_path, width=width, height=height, scale=scale)
return cls.create(file_path, width, height)
@classmethod
def get_custom(cls, name: str, width: int, height: int = None) -> PhotoImage: def from_name(
try: name: str, *, width: int, height: int = None, scale: float = 1.0
file_path = cls.images[name] ) -> PhotoImage:
return cls.create(file_path, width, height) file_path = IMAGES[name]
except KeyError: return from_file(file_path, width=width, height=height, scale=scale)
messagebox.showwarning(
"Missing image file",
f"{name}.png is missing at daemon/core/gui/data/icons, drop image "
f"file at daemon/core/gui/data/icons and restart the gui",
)
class ImageEnum(Enum): class ImageEnum(Enum):

View file

@ -6,6 +6,7 @@ from functools import partial
from tkinter import filedialog, messagebox from tkinter import filedialog, messagebox
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from core.gui import images
from core.gui.coreclient import CoreClient from core.gui.coreclient import CoreClient
from core.gui.dialogs.about import AboutDialog from core.gui.dialogs.about import AboutDialog
from core.gui.dialogs.canvassizeandscale import SizeAndScaleDialog from core.gui.dialogs.canvassizeandscale import SizeAndScaleDialog
@ -23,7 +24,6 @@ from core.gui.dialogs.sessionoptions import SessionOptionsDialog
from core.gui.dialogs.sessions import SessionsDialog from core.gui.dialogs.sessions import SessionsDialog
from core.gui.dialogs.throughput import ThroughputDialog from core.gui.dialogs.throughput import ThroughputDialog
from core.gui.graph.manager import CanvasManager from core.gui.graph.manager import CanvasManager
from core.gui.nodeutils import ICON_SIZE
from core.gui.observers import ObserversMenu from core.gui.observers import ObserversMenu
from core.gui.task import ProgressTask from core.gui.task import ProgressTask
@ -461,8 +461,8 @@ class Menubar(tk.Menu):
def click_autogrid(self) -> None: def click_autogrid(self) -> None:
width, height = self.manager.current_dimensions width, height = self.manager.current_dimensions
padding = (ICON_SIZE / 2) + 10 padding = (images.NODE_SIZE / 2) + 10
layout_size = padding + ICON_SIZE layout_size = padding + images.NODE_SIZE
col_count = width // layout_size col_count = width // layout_size
logging.info( logging.info(
"auto grid layout: dimension(%s, %s) col(%s)", width, height, col_count "auto grid layout: dimension(%s, %s) col(%s)", width, height, col_count

View file

@ -4,11 +4,9 @@ from typing import List, Optional, Set
from PIL.ImageTk import PhotoImage from PIL.ImageTk import PhotoImage
from core.api.grpc.wrappers import Node, NodeType from core.api.grpc.wrappers import Node, NodeType
from core.gui import images
from core.gui.appconfig import CustomNode, GuiConfig from core.gui.appconfig import CustomNode, GuiConfig
from core.gui.images import ImageEnum, Images, TypeToImage from core.gui.images import ImageEnum, TypeToImage
ICON_SIZE: int = 48
ANTENNA_SIZE: int = 32
NODES: List["NodeDraw"] = [] NODES: List["NodeDraw"] = []
NETWORK_NODES: List["NodeDraw"] = [] NETWORK_NODES: List["NodeDraw"] = []
@ -52,7 +50,7 @@ def setup() -> None:
node_draw = NodeDraw.from_setup(image_enum, node_type, label) node_draw = NodeDraw.from_setup(image_enum, node_type, label)
NETWORK_NODES.append(node_draw) NETWORK_NODES.append(node_draw)
NODE_ICONS[(node_type, None)] = node_draw.image NODE_ICONS[(node_type, None)] = node_draw.image
ANTENNA_ICON = Images.get(ImageEnum.ANTENNA, ANTENNA_SIZE) ANTENNA_ICON = images.from_enum(ImageEnum.ANTENNA, width=images.ANTENNA_SIZE)
def is_bridge(node: Node) -> bool: def is_bridge(node: Node) -> bool:
@ -114,19 +112,21 @@ def get_icon(node: Node, config: GuiConfig, scale: float) -> Optional[PhotoImage
# node has defined a custom icon # node has defined a custom icon
if node.icon: if node.icon:
try: try:
image = Images.create(node.icon, int(ICON_SIZE * scale)) image = images.from_file(node.icon, width=images.NODE_SIZE, scale=scale)
except OSError: except OSError:
logging.error("invalid icon: %s", node.icon) logging.error("invalid icon: %s", node.icon)
else: else:
# attempt to find default type/model image # attempt to find default type/model image
image_enum = TypeToImage.get(node.type, node.model) image_enum = TypeToImage.get(node.type, node.model)
if image_enum: if image_enum:
image = Images.get(image_enum, int(ICON_SIZE * scale)) image = images.from_enum(image_enum, width=images.NODE_SIZE, scale=scale)
# attempt to find custom image file # attempt to find custom image file
else: else:
image_file = _get_image_file(config, node.model) image_file = _get_image_file(config, node.model)
if image_file: if image_file:
image = Images.get_with_image_file(image_file, int(ICON_SIZE * scale)) image = images.from_name(
image_file, width=images.NODE_SIZE, scale=scale
)
return image return image
@ -152,7 +152,7 @@ class NodeDraw:
) -> "NodeDraw": ) -> "NodeDraw":
node_draw = NodeDraw() node_draw = NodeDraw()
node_draw.image_enum = image_enum node_draw.image_enum = image_enum
node_draw.image = Images.get(image_enum, ICON_SIZE) node_draw.image = images.from_enum(image_enum, width=images.NODE_SIZE)
node_draw.node_type = node_type node_draw.node_type = node_type
node_draw.label = label node_draw.label = label
node_draw.model = model node_draw.model = model
@ -164,7 +164,7 @@ class NodeDraw:
node_draw = NodeDraw() node_draw = NodeDraw()
node_draw.custom = True node_draw.custom = True
node_draw.image_file = custom_node.image node_draw.image_file = custom_node.image
node_draw.image = Images.get_custom(custom_node.image, ICON_SIZE) node_draw.image = images.from_name(custom_node.image, width=images.NODE_SIZE)
node_draw.node_type = NodeType.DEFAULT node_draw.node_type = NodeType.DEFAULT
node_draw.services = custom_node.services node_draw.services = custom_node.services
node_draw.label = custom_node.name node_draw.label = custom_node.name

View file

@ -3,9 +3,8 @@ import argparse
import logging import logging
from logging.handlers import TimedRotatingFileHandler from logging.handlers import TimedRotatingFileHandler
from core.gui import appconfig from core.gui import appconfig, images
from core.gui.app import Application from core.gui.app import Application
from core.gui.images import Images
if __name__ == "__main__": if __name__ == "__main__":
# parse flags # parse flags
@ -28,6 +27,6 @@ if __name__ == "__main__":
logging.getLogger("PIL").setLevel(logging.ERROR) logging.getLogger("PIL").setLevel(logging.ERROR)
# start app # start app
Images.load_all() images.load_all()
app = Application(args.proxy, args.session) app = Application(args.proxy, args.session)
app.mainloop() app.mainloop()