This commit is contained in:
Huy Pham 2020-01-14 11:06:52 -08:00
parent b9b8e3a5f1
commit 6c8a2526d9
40 changed files with 219 additions and 141 deletions

View file

@ -1,6 +1,5 @@
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import Optional
from core.gui import appconfig, themes from core.gui import appconfig, themes
from core.gui.coreclient import CoreClient from core.gui.coreclient import CoreClient
@ -18,7 +17,7 @@ HEIGHT = 800
class Application(tk.Frame): class Application(tk.Frame):
def __init__(self, master: Optional[tk.Widget] = None): def __init__(self, master: tk.Widget = None):
super().__init__(master) super().__init__(master)
# load node icons # load node icons
NodeUtils.setup() NodeUtils.setup()

View file

@ -5,7 +5,7 @@ import json
import logging import logging
import os import os
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional from typing import TYPE_CHECKING, Dict, List, Optional
import grpc import grpc
@ -22,6 +22,9 @@ from core.gui.graph.shapeutils import ShapeType
from core.gui.interface import InterfaceManager from core.gui.interface import InterfaceManager
from core.gui.nodeutils import NodeDraw, NodeUtils from core.gui.nodeutils import NodeDraw, NodeUtils
if TYPE_CHECKING:
from core.gui.app import Application
GUI_SOURCE = "gui" GUI_SOURCE = "gui"
OBSERVERS = { OBSERVERS = {
"processes": "ps", "processes": "ps",
@ -50,7 +53,7 @@ class Observer:
class CoreClient: class CoreClient:
def __init__(self, app): def __init__(self, app: "Application"):
""" """
Create a CoreGrpc instance Create a CoreGrpc instance
""" """
@ -137,7 +140,7 @@ class CoreClient:
def handle_events(self, event: core_pb2.Event): def handle_events(self, event: core_pb2.Event):
if event.session_id != self.session_id: if event.session_id != self.session_id:
logging.warn( logging.warning(
"ignoring event session(%s) current(%s)", "ignoring event session(%s) current(%s)",
event.session_id, event.session_id,
self.session_id, self.session_id,

View file

@ -35,7 +35,7 @@ THE POSSIBILITY OF SUCH DAMAGE.\
class AboutDialog(Dialog): class AboutDialog(Dialog):
def __init__(self, master: tk.Widget, app: "Application"): def __init__(self, master: "Application", app: "Application"):
super().__init__(master, app, "About CORE", modal=True) super().__init__(master, app, "About CORE", modal=True)
self.draw() self.draw()

View file

@ -15,7 +15,7 @@ if TYPE_CHECKING:
class AlertsDialog(Dialog): class AlertsDialog(Dialog):
def __init__(self, master: tk.Widget, app: "Application"): def __init__(self, master: "Application", app: "Application"):
super().__init__(master, app, "Alerts", modal=True) super().__init__(master, app, "Alerts", modal=True)
self.app = app self.app = app
self.tree = None self.tree = None
@ -124,7 +124,7 @@ class AlertsDialog(Dialog):
class DaemonLog(Dialog): class DaemonLog(Dialog):
def __init__(self, master, app): def __init__(self, master: tk.Widget, app: "Application"):
super().__init__(master, app, "core-daemon log", modal=True) super().__init__(master, app, "core-daemon log", modal=True)
self.columnconfigure(0, weight=1) self.columnconfigure(0, weight=1)
self.path = tk.StringVar(value="/var/log/core-daemon.log") self.path = tk.StringVar(value="/var/log/core-daemon.log")

View file

@ -17,11 +17,9 @@ if TYPE_CHECKING:
class CanvasWallpaperDialog(Dialog): class CanvasWallpaperDialog(Dialog):
def __init__(self, master: tk.Widget, app: "Application"): def __init__(self, master: "Application", app: "Application"):
""" """
create an instance of CanvasWallpaper object create an instance of CanvasWallpaper object
:param coretk.app.Application app: root application
""" """
super().__init__(master, app, "Canvas Background", modal=True) super().__init__(master, app, "Canvas Background", modal=True)
self.canvas = self.app.canvas self.canvas = self.app.canvas

View file

@ -4,7 +4,7 @@ custom color picker
import logging import logging
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Any, Optional
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
@ -14,10 +14,7 @@ if TYPE_CHECKING:
class ColorPickerDialog(Dialog): class ColorPickerDialog(Dialog):
def __init__( def __init__(
self, self, master: Any, app: "Application", initcolor: Optional[str] = "#000000"
master: tk.Widget,
app: "Application",
initcolor: Optional[str] = "#000000",
): ):
super().__init__(master, app, "color picker", modal=True) super().__init__(master, app, "color picker", modal=True)
self.red_entry = None self.red_entry = None

View file

@ -5,7 +5,7 @@ copy service config dialog
import logging import logging
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, Tuple from typing import TYPE_CHECKING, Any, Tuple
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.themes import FRAME_PAD, PADX from core.gui.themes import FRAME_PAD, PADX
@ -13,11 +13,10 @@ from core.gui.widgets import CodeText
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
from core.gui.dialogs.serviceconfig import ServiceConfigDialog
class CopyServiceConfigDialog(Dialog): class CopyServiceConfigDialog(Dialog):
def __init__(self, master: "ServiceConfigDialog", app: "Application", node_id: int): def __init__(self, master: Any, app: "Application", node_id: int):
super().__init__(master, app, f"Copy services to node {node_id}", modal=True) super().__init__(master, app, f"Copy services to node {node_id}", modal=True)
self.parent = master self.parent = master
self.app = app self.app = app
@ -133,6 +132,7 @@ class CopyServiceConfigDialog(Dialog):
def click_view(self): def click_view(self):
selected = self.tree.selection() selected = self.tree.selection()
data = ""
if selected: if selected:
item = self.tree.item(selected[0]) item = self.tree.item(selected[0])
if "file" in item["tags"]: if "file" in item["tags"]:
@ -161,7 +161,7 @@ class CopyServiceConfigDialog(Dialog):
class ViewConfigDialog(Dialog): class ViewConfigDialog(Dialog):
def __init__(self, master, app, node_id, data): def __init__(self, master: Any, app: "Application", node_id: int, data: bytes):
super().__init__(master, app, f"n{node_id} config data", modal=True) super().__init__(master, app, f"n{node_id} config data", modal=True)
self.data = data self.data = data
self.service_data = None self.service_data = None

View file

@ -2,7 +2,7 @@ import logging
import tkinter as tk import tkinter as tk
from pathlib import Path from pathlib import Path
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, Set from typing import TYPE_CHECKING, Any, Set
from core.gui import nodeutils from core.gui import nodeutils
from core.gui.appconfig import ICONS_PATH from core.gui.appconfig import ICONS_PATH
@ -17,7 +17,7 @@ if TYPE_CHECKING:
class ServicesSelectDialog(Dialog): class ServicesSelectDialog(Dialog):
def __init__(self, master, app: "Application", current_services: Set[str]): def __init__(self, master: Any, app: "Application", current_services: Set[str]):
super().__init__(master, app, "Node Services", modal=True) super().__init__(master, app, "Node Services", modal=True)
self.groups = None self.groups = None
self.services = None self.services = None
@ -100,7 +100,7 @@ class ServicesSelectDialog(Dialog):
class CustomNodesDialog(Dialog): class CustomNodesDialog(Dialog):
def __init__(self, master, app: "Application"): def __init__(self, master: "Application", app: "Application"):
super().__init__(master, app, "Custom Nodes", modal=True) super().__init__(master, app, "Custom Nodes", modal=True)
self.edit_button = None self.edit_button = None
self.delete_button = None self.delete_button = None
@ -245,7 +245,7 @@ class CustomNodesDialog(Dialog):
self.nodes_list.listbox.selection_clear(0, tk.END) self.nodes_list.listbox.selection_clear(0, tk.END)
self.nodes_list.listbox.event_generate("<<ListboxSelect>>") self.nodes_list.listbox.event_generate("<<ListboxSelect>>")
def handle_node_select(self, event): def handle_node_select(self, event: tk.Event):
selection = self.nodes_list.listbox.curselection() selection = self.nodes_list.listbox.curselection()
if selection: if selection:
self.selected_index = selection[0] self.selected_index = selection[0]

View file

@ -5,7 +5,7 @@ import logging
import tkinter as tk import tkinter as tk
import webbrowser import webbrowser
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Any, Optional
import grpc import grpc
@ -22,7 +22,7 @@ if TYPE_CHECKING:
class GlobalEmaneDialog(Dialog): class GlobalEmaneDialog(Dialog):
def __init__(self, master, app: "Application"): def __init__(self, master: Any, app: "Application"):
super().__init__(master, app, "EMANE Configuration", modal=True) super().__init__(master, app, "EMANE Configuration", modal=True)
self.config_frame = None self.config_frame = None
self.draw() self.draw()
@ -55,7 +55,7 @@ class GlobalEmaneDialog(Dialog):
class EmaneModelDialog(Dialog): class EmaneModelDialog(Dialog):
def __init__( def __init__(
self, self,
master, master: Any,
app: "Application", app: "Application",
node: core_pb2.Node, node: core_pb2.Node,
model: str, model: str,
@ -104,7 +104,9 @@ class EmaneModelDialog(Dialog):
class EmaneConfigDialog(Dialog): class EmaneConfigDialog(Dialog):
def __init__(self, master, app: "Application", canvas_node: "CanvasNode"): def __init__(
self, master: "Application", app: "Application", canvas_node: "CanvasNode"
):
super().__init__( super().__init__(
master, app, f"{canvas_node.core_node.name} EMANE Configuration", modal=True master, app, f"{canvas_node.core_node.name} EMANE Configuration", modal=True
) )

View file

@ -1,6 +1,6 @@
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Union
from core.api.grpc import core_pb2 from core.api.grpc import core_pb2
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
@ -12,7 +12,7 @@ if TYPE_CHECKING:
class HookDialog(Dialog): class HookDialog(Dialog):
def __init__(self, master, app: "Application"): def __init__(self, master: Union[tk.Widget, Dialog], app: "Application"):
super().__init__(master, app, "Hook", modal=True) super().__init__(master, app, "Hook", modal=True)
self.name = tk.StringVar() self.name = tk.StringVar()
self.codetext = None self.codetext = None
@ -88,7 +88,7 @@ class HookDialog(Dialog):
class HooksDialog(Dialog): class HooksDialog(Dialog):
def __init__(self, master, app: "Application"): def __init__(self, master: "Application", app: "Application"):
super().__init__(master, app, "Hooks", modal=True) super().__init__(master, app, "Hooks", modal=True)
self.listbox = None self.listbox = None
self.edit_button = None self.edit_button = None

View file

@ -13,7 +13,7 @@ from core.gui.themes import PADX, PADY
if TYPE_CHECKING: if TYPE_CHECKING:
from core.gui.app import Application from core.gui.app import Application
from core.gui.graph.edges import CanvasEdge from core.gui.graph.graph import CanvasGraph, CanvasEdge
def get_int(var: tk.StringVar) -> Union[int, None]: def get_int(var: tk.StringVar) -> Union[int, None]:
@ -33,7 +33,7 @@ def get_float(var: tk.StringVar) -> Union[float, None]:
class LinkConfigurationDialog(Dialog): class LinkConfigurationDialog(Dialog):
def __init__(self, master, app: "Application", edge: "CanvasEdge"): def __init__(self, master: "CanvasGraph", app: "Application", edge: "CanvasEdge"):
super().__init__(master, app, "Link Configuration", modal=True) super().__init__(master, app, "Link Configuration", modal=True)
self.app = app self.app = app
self.edge = edge self.edge = edge

View file

@ -17,7 +17,10 @@ MARKER_THICKNESS = [3, 5, 8, 10]
class MarkerDialog(Dialog): class MarkerDialog(Dialog):
def __init__( def __init__(
self, master, app: "Application", initcolor: Optional[str] = "#000000" self,
master: "Application",
app: "Application",
initcolor: Optional[str] = "#000000",
): ):
super().__init__(master, app, "marker tool", modal=False) super().__init__(master, app, "marker tool", modal=False)
self.app = app self.app = app

View file

@ -17,7 +17,9 @@ if TYPE_CHECKING:
class MobilityConfigDialog(Dialog): class MobilityConfigDialog(Dialog):
def __init__(self, master, app: "Application", canvas_node: "CanvasNode"): def __init__(
self, master: "Application", app: "Application", canvas_node: "CanvasNode"
):
super().__init__( super().__init__(
master, master,
app, app,

View file

@ -18,7 +18,13 @@ ICON_SIZE = 16
class MobilityPlayer: class MobilityPlayer:
def __init__(self, master, app: "Application", canvas_node: "CanvasNode", config): def __init__(
self,
master: "Application",
app: "Application",
canvas_node: "CanvasNode",
config,
):
self.master = master self.master = master
self.app = app self.app = app
self.canvas_node = canvas_node self.canvas_node = canvas_node
@ -62,7 +68,9 @@ class MobilityPlayer:
class MobilityPlayerDialog(Dialog): class MobilityPlayerDialog(Dialog):
def __init__(self, master, app, canvas_node, config): def __init__(
self, master: Dialog, app: "Application", canvas_node: "CanvasNode", config
):
super().__init__( super().__init__(
master, app, f"{canvas_node.core_node.name} Mobility Player", modal=False master, app, f"{canvas_node.core_node.name} Mobility Player", modal=False
) )

View file

@ -18,7 +18,7 @@ if TYPE_CHECKING:
from core.gui.graph.node import CanvasNode from core.gui.graph.node import CanvasNode
def mac_auto(is_auto, entry: ttk.Entry): def mac_auto(is_auto, entry):
logging.info("mac auto clicked") logging.info("mac auto clicked")
if is_auto.get(): if is_auto.get():
logging.info("disabling mac") logging.info("disabling mac")
@ -38,13 +38,11 @@ class InterfaceData:
class NodeConfigDialog(Dialog): class NodeConfigDialog(Dialog):
def __init__(self, master, app: "Application", canvas_node: "CanvasNode"): def __init__(
self, master: "Application", app: "Application", canvas_node: "CanvasNode"
):
""" """
create an instance of node configuration create an instance of node configuration
:param master: dialog master
:param coretk.app.Application: main app
:param coretk.graph.CanvasNode canvas_node: canvas node object
""" """
super().__init__( super().__init__(
master, app, f"{canvas_node.core_node.name} Configuration", modal=True master, app, f"{canvas_node.core_node.name} Configuration", modal=True

View file

@ -3,7 +3,7 @@ core node services
""" """
import tkinter as tk import tkinter as tk
from tkinter import messagebox, ttk from tkinter import messagebox, ttk
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Any
from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.dialog import Dialog
from core.gui.dialogs.serviceconfig import ServiceConfigDialog from core.gui.dialogs.serviceconfig import ServiceConfigDialog
@ -17,7 +17,7 @@ if TYPE_CHECKING:
class NodeServiceDialog(Dialog): class NodeServiceDialog(Dialog):
def __init__( def __init__(
self, master, app: "Application", canvas_node: "CanvasNode", services=None self, master: Any, app: "Application", canvas_node: "CanvasNode", services=None
): ):
title = f"{canvas_node.core_node.name} Services" title = f"{canvas_node.core_node.name} Services"
super().__init__(master, app, title, modal=True) super().__init__(master, app, title, modal=True)

View file

@ -12,7 +12,7 @@ if TYPE_CHECKING:
class ObserverDialog(Dialog): class ObserverDialog(Dialog):
def __init__(self, master, app: "Application"): def __init__(self, master: "Application", app: "Application"):
super().__init__(master, app, "Observer Widgets", modal=True) super().__init__(master, app, "Observer Widgets", modal=True)
self.observers = None self.observers = None
self.save_button = None self.save_button = None

View file

@ -12,7 +12,7 @@ if TYPE_CHECKING:
class PreferencesDialog(Dialog): class PreferencesDialog(Dialog):
def __init__(self, master, app: "Application"): def __init__(self, master: "Application", app: "Application"):
super().__init__(master, app, "Preferences", modal=True) super().__init__(master, app, "Preferences", modal=True)
preferences = self.app.guiconfig["preferences"] preferences = self.app.guiconfig["preferences"]
self.editor = tk.StringVar(value=preferences["editor"]) self.editor = tk.StringVar(value=preferences["editor"])

View file

@ -16,7 +16,7 @@ DEFAULT_PORT = 50051
class ServersDialog(Dialog): class ServersDialog(Dialog):
def __init__(self, master, app: "Application"): def __init__(self, master: "Application", app: "Application"):
super().__init__(master, app, "CORE Servers", modal=True) super().__init__(master, app, "CORE Servers", modal=True)
self.name = tk.StringVar(value=DEFAULT_NAME) self.name = tk.StringVar(value=DEFAULT_NAME)
self.address = tk.StringVar(value=DEFAULT_ADDRESS) self.address = tk.StringVar(value=DEFAULT_ADDRESS)

View file

@ -1,7 +1,9 @@
"Service configuration dialog" """
Service configuration dialog
"""
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING, List from typing import TYPE_CHECKING, Any, List
import grpc import grpc
@ -18,7 +20,9 @@ if TYPE_CHECKING:
class ServiceConfigDialog(Dialog): class ServiceConfigDialog(Dialog):
def __init__(self, master, app: "Application", service_name: str, node_id: int): def __init__(
self, master: Any, app: "Application", service_name: str, node_id: int
):
title = f"{service_name} Service" title = f"{service_name} Service"
super().__init__(master, app, title, modal=True) super().__init__(master, app, title, modal=True)
self.master = master self.master = master
@ -229,7 +233,7 @@ class ServiceConfigDialog(Dialog):
for i in range(3): for i in range(3):
tab.rowconfigure(i, weight=1) tab.rowconfigure(i, weight=1)
self.notebook.add(tab, text="Startup/Shutdown") self.notebook.add(tab, text="Startup/Shutdown")
commands = []
# tab 3 # tab 3
for i in range(3): for i in range(3):
label_frame = None label_frame = None

View file

@ -14,7 +14,7 @@ if TYPE_CHECKING:
class SessionOptionsDialog(Dialog): class SessionOptionsDialog(Dialog):
def __init__(self, master, app: "Application"): def __init__(self, master: "Application", app: "Application"):
super().__init__(master, app, "Session Options", modal=True) super().__init__(master, app, "Session Options", modal=True)
self.config_frame = None self.config_frame = None
self.config = self.get_config() self.config = self.get_config()

View file

@ -17,7 +17,7 @@ if TYPE_CHECKING:
class SessionsDialog(Dialog): class SessionsDialog(Dialog):
def __init__(self, master, app: "Application"): def __init__(self, master: "Application", app: "Application"):
super().__init__(master, app, "Sessions", modal=True) super().__init__(master, app, "Sessions", modal=True)
self.selected = False self.selected = False
self.selected_id = None self.selected_id = None

View file

@ -20,7 +20,7 @@ BORDER_WIDTH = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
class ShapeDialog(Dialog): class ShapeDialog(Dialog):
def __init__(self, master, app: "Application", shape: "Shape"): def __init__(self, master: "Application", app: "Application", shape: "Shape"):
if is_draw_shape(shape.shape_type): if is_draw_shape(shape.shape_type):
title = "Add Shape" title = "Add Shape"
else: else:

View file

@ -14,7 +14,7 @@ if TYPE_CHECKING:
class ThroughputDialog(Dialog): class ThroughputDialog(Dialog):
def __init__(self, master, app: "Application"): def __init__(self, master: "Application", app: "Application"):
super().__init__(master, app, "Throughput Config", modal=False) super().__init__(master, app, "Throughput Config", modal=False)
self.app = app self.app = app
self.canvas = app.canvas self.canvas = app.canvas

View file

@ -18,7 +18,9 @@ if TYPE_CHECKING:
class WlanConfigDialog(Dialog): class WlanConfigDialog(Dialog):
def __init__(self, master, app: "Application", canvas_node: "CanvasNode"): def __init__(
self, master: "Application", app: "Application", canvas_node: "CanvasNode"
):
super().__init__( super().__init__(
master, app, f"{canvas_node.core_node.name} Wlan Configuration", modal=True master, app, f"{canvas_node.core_node.name} Wlan Configuration", modal=True
) )

View file

@ -1,9 +1,11 @@
from tkinter import messagebox from tkinter import messagebox
from typing import TYPE_CHECKING
import grpc if TYPE_CHECKING:
import grpc
def show_grpc_error(e: grpc.RpcError): def show_grpc_error(e: "grpc.RpcError"):
title = [x.capitalize() for x in e.code().name.lower().split("_")] title = [x.capitalize() for x in e.code().name.lower().split("_")]
title = " ".join(title) title = " ".join(title)
title = f"GRPC {title}" title = f"GRPC {title}"

View file

@ -1,13 +1,16 @@
import logging import logging
import tkinter as tk import tkinter as tk
from tkinter.font import Font from tkinter.font import Font
from typing import Tuple from typing import TYPE_CHECKING, Any, Tuple
from core.gui import themes from core.gui import themes
from core.gui.dialogs.linkconfig import LinkConfigurationDialog from core.gui.dialogs.linkconfig import LinkConfigurationDialog
from core.gui.graph import tags from core.gui.graph import tags
from core.gui.nodeutils import NodeUtils from core.gui.nodeutils import NodeUtils
if TYPE_CHECKING:
from core.gui.graph.graph import CanvasGraph
TEXT_DISTANCE = 0.30 TEXT_DISTANCE = 0.30
EDGE_WIDTH = 3 EDGE_WIDTH = 3
EDGE_COLOR = "#ff0000" EDGE_COLOR = "#ff0000"
@ -16,11 +19,11 @@ EDGE_COLOR = "#ff0000"
class CanvasWirelessEdge: class CanvasWirelessEdge:
def __init__( def __init__(
self, self,
token: Tuple[int, int], token: Tuple[Any, ...],
position: Tuple[int, int, int, int], position: Tuple[float, float, float, float],
src: int, src: int,
dst: int, dst: int,
canvas, canvas: "CanvasGraph",
): ):
self.token = token self.token = token
self.src = src self.src = src
@ -39,10 +42,17 @@ class CanvasEdge:
Canvas edge class Canvas edge class
""" """
def __init__(self, x1: int, y1: int, x2: int, y2: int, src: int, canvas): def __init__(
self,
x1: float,
y1: float,
x2: float,
y2: float,
src: int,
canvas: "CanvasGraph",
):
""" """
Create an instance of canvas edge object Create an instance of canvas edge object
:param coretk.graph.graph.GraphCanvas canvas: canvas object
""" """
self.src = src self.src = src
self.dst = None self.dst = None
@ -69,7 +79,7 @@ class CanvasEdge:
self.link = link self.link = link
self.draw_labels() self.draw_labels()
def get_coordinates(self) -> [int, int, int, int]: def get_coordinates(self) -> [float, float, float, float]:
x1, y1, x2, y2 = self.canvas.coords(self.id) x1, y1, x2, y2 = self.canvas.coords(self.id)
v1 = x2 - x1 v1 = x2 - x1
v2 = y2 - y1 v2 = y2 - y1

View file

@ -1,6 +1,6 @@
import logging import logging
import tkinter as tk import tkinter as tk
from typing import List, Optional from typing import TYPE_CHECKING, List, Optional, Tuple
from PIL import Image, ImageTk from PIL import Image, ImageTk
@ -16,12 +16,15 @@ from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker
from core.gui.images import Images from core.gui.images import Images
from core.gui.nodeutils import NodeUtils from core.gui.nodeutils import NodeUtils
if TYPE_CHECKING:
from core.gui.coreclient import CoreClient
ZOOM_IN = 1.1 ZOOM_IN = 1.1
ZOOM_OUT = 0.9 ZOOM_OUT = 0.9
class CanvasGraph(tk.Canvas): class CanvasGraph(tk.Canvas):
def __init__(self, master, core, width, height): def __init__(self, master, core: "CoreClient", width: int, height: int):
super().__init__(master, highlightthickness=0, background="#cccccc") super().__init__(master, highlightthickness=0, background="#cccccc")
self.app = master self.app = master
self.core = core self.core = core
@ -68,7 +71,7 @@ class CanvasGraph(tk.Canvas):
self.draw_canvas() self.draw_canvas()
self.draw_grid() self.draw_grid()
def draw_canvas(self, dimensions=None): def draw_canvas(self, dimensions: Optional[Tuple[int, int]] = None):
if self.grid is not None: if self.grid is not None:
self.delete(self.grid) self.delete(self.grid)
if not dimensions: if not dimensions:
@ -185,9 +188,6 @@ class CanvasGraph(tk.Canvas):
def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode): def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode):
""" """
add a wireless edge between 2 canvas nodes add a wireless edge between 2 canvas nodes
:param CanvasNode src: source node
:param CanvasNode dst: destination node
""" """
token = tuple(sorted((src.id, dst.id))) token = tuple(sorted((src.id, dst.id)))
x1, y1 = self.coords(src.id) x1, y1 = self.coords(src.id)
@ -319,9 +319,6 @@ class CanvasGraph(tk.Canvas):
def click_release(self, event: tk.Event): def click_release(self, event: tk.Event):
""" """
Draw a node or finish drawing an edge according to the current graph mode Draw a node or finish drawing an edge according to the current graph mode
:param event: mouse event
:return: nothing
""" """
logging.debug("click release") logging.debug("click release")
x, y = self.canvas_xy(event) x, y = self.canvas_xy(event)
@ -760,7 +757,7 @@ class CanvasGraph(tk.Canvas):
self.redraw_canvas((image.width(), image.height())) self.redraw_canvas((image.width(), image.height()))
self.draw_wallpaper(image) self.draw_wallpaper(image)
def redraw_canvas(self, dimensions: Optional[List[int]] = None): def redraw_canvas(self, dimensions: Tuple[int, int] = None):
logging.info("redrawing canvas to dimensions: %s", dimensions) logging.info("redrawing canvas to dimensions: %s", dimensions)
# reset scale and move back to original position # reset scale and move back to original position

View file

@ -253,7 +253,7 @@ class CanvasNode:
dialog = NodeServiceDialog(self.app.master, self.app, self) dialog = NodeServiceDialog(self.app.master, self.app, self)
dialog.show() dialog.show()
def has_emane_link(self, interface_id: int) -> bool: def has_emane_link(self, interface_id: int) -> core_pb2.Node:
result = None result = None
for edge in self.edges: for edge in self.edges:
if self.id == edge.src: if self.id == edge.src:

View file

@ -1,24 +1,28 @@
import logging import logging
from typing import List, Optional, Union from typing import TYPE_CHECKING, Dict, List, Union
from core.gui.dialogs.shapemod import ShapeDialog from core.gui.dialogs.shapemod import ShapeDialog
from core.gui.graph import tags from core.gui.graph import tags
from core.gui.graph.shapeutils import ShapeType from core.gui.graph.shapeutils import ShapeType
if TYPE_CHECKING:
from core.gui.app import Application
from core.gui.graph.graph import CanvasGraph
class AnnotationData: class AnnotationData:
def __init__( def __init__(
self, self,
text: Optional[str] = "", text: str = "",
font: Optional[str] = "Arial", font: str = "Arial",
font_size: Optional[int] = 12, font_size: int = 12,
text_color: Optional[str] = "#000000", text_color: str = "#000000",
fill_color: Optional[str] = "", fill_color: str = "",
border_color: Optional[str] = "#000000", border_color: str = "#000000",
border_width: Optional[int] = 1, border_width: int = 1,
bold: Optional[bool] = False, bold: bool = False,
italic: Optional[bool] = False, italic: bool = False,
underline: Optional[bool] = False, underline: bool = False,
): ):
self.text = text self.text = text
self.font = font self.font = font
@ -33,7 +37,17 @@ class AnnotationData:
class Shape: class Shape:
def __init__(self, app, canvas, shape_type, x1, y1, x2=None, y2=None, data=None): def __init__(
self,
app: "Application",
canvas: "CanvasGraph",
shape_type: ShapeType,
x1: float,
y1: float,
x2: float = None,
y2: float = None,
data: AnnotationData = None,
):
self.app = app self.app = app
self.canvas = canvas self.canvas = canvas
self.shape_type = shape_type self.shape_type = shape_type
@ -152,7 +166,7 @@ class Shape:
self.canvas.delete(self.id) self.canvas.delete(self.id)
self.canvas.delete(self.text_id) self.canvas.delete(self.text_id)
def metadata(self): def metadata(self) -> Dict[str, Union[str, int, bool]]:
coords = self.canvas.coords(self.id) coords = self.canvas.coords(self.id)
# update coords to actual positions # update coords to actual positions
if len(coords) == 4: if len(coords) == 4:

View file

@ -1,9 +1,12 @@
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import Optional from typing import TYPE_CHECKING
from core.gui.themes import Styles from core.gui.themes import Styles
if TYPE_CHECKING:
from core.gui.graph.graph import CanvasGraph
class CanvasTooltip: class CanvasTooltip:
""" """
@ -20,7 +23,14 @@ class CanvasTooltip:
Alberto Vassena on 2016.12.10. Alberto Vassena on 2016.12.10.
""" """
def __init__(self, canvas, *, pad=(5, 3, 5, 3), waittime=400, wraplength=600): def __init__(
self,
canvas: "CanvasGraph",
*,
pad=(5, 3, 5, 3),
waittime: int = 400,
wraplength: int = 600
):
# in miliseconds, originally 500 # in miliseconds, originally 500
self.waittime = waittime self.waittime = waittime
# in pixels, originally 180 # in pixels, originally 180
@ -31,10 +41,10 @@ class CanvasTooltip:
self.id = None self.id = None
self.tw = None self.tw = None
def on_enter(self, event: Optional[tk.Event] = None): def on_enter(self, event: tk.Event = None):
self.schedule() self.schedule()
def on_leave(self, event: Optional[tk.Event] = None): def on_leave(self, event: tk.Event = None):
self.unschedule() self.unschedule()
self.hide() self.hide()
@ -48,7 +58,7 @@ class CanvasTooltip:
if id_: if id_:
self.canvas.after_cancel(id_) self.canvas.after_cancel(id_)
def show(self, event: Optional[tk.Event] = None): def show(self, event: tk.Event = None):
def tip_pos_calculator(canvas, label, *, tip_delta=(10, 5), pad=(5, 3, 5, 3)): def tip_pos_calculator(canvas, label, *, tip_delta=(10, 5), pad=(5, 3, 5, 3)):
c = canvas c = canvas
s_width, s_height = c.winfo_screenwidth(), c.winfo_screenheight() s_width, s_height = c.winfo_screenwidth(), c.winfo_screenheight()

View file

@ -1,21 +1,23 @@
import logging import logging
import random import random
from typing import Optional, Set from typing import TYPE_CHECKING, Set, Union
from netaddr import IPNetwork from netaddr import IPNetwork
from core.gui.graph.node import CanvasNode
from core.gui.nodeutils import NodeUtils from core.gui.nodeutils import NodeUtils
if TYPE_CHECKING:
from core.gui.app import Application
from core.api.grpc import core_pb2
from core.gui.graph.node import CanvasNode
def random_mac(): def random_mac():
return ("{:02x}" * 6).format(*[random.randrange(256) for _ in range(6)]) return ("{:02x}" * 6).format(*[random.randrange(256) for _ in range(6)])
class InterfaceManager: class InterfaceManager:
def __init__( def __init__(self, app: "Application", address: str = "10.0.0.0", mask: int = 24):
self, app, address: Optional[str] = "10.0.0.0", mask: Optional[int] = 24
):
self.app = app self.app = app
self.mask = mask self.mask = mask
self.base_prefix = max(self.mask - 8, 0) self.base_prefix = max(self.mask - 8, 0)
@ -49,11 +51,11 @@ class InterfaceManager:
return str(ip4), str(ip6), prefix return str(ip4), str(ip6), prefix
@classmethod @classmethod
def get_subnet(cls, interface): def get_subnet(cls, interface: "core_pb2.Interface") -> IPNetwork:
return IPNetwork(f"{interface.ip4}/{interface.ip4mask}").cidr return IPNetwork(f"{interface.ip4}/{interface.ip4mask}").cidr
def determine_subnet( def determine_subnet(
self, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode self, canvas_src_node: "CanvasNode", canvas_dst_node: "CanvasNode"
): ):
src_node = canvas_src_node.core_node src_node = canvas_src_node.core_node
dst_node = canvas_dst_node.core_node dst_node = canvas_dst_node.core_node
@ -76,7 +78,9 @@ class InterfaceManager:
else: else:
logging.info("ignoring subnet change for link between network nodes") logging.info("ignoring subnet change for link between network nodes")
def find_subnet(self, canvas_node: CanvasNode, visited: Optional[Set[int]] = None): def find_subnet(
self, canvas_node: "CanvasNode", visited: Set[int] = None
) -> Union[IPNetwork, None]:
logging.info("finding subnet for node: %s", canvas_node.core_node.name) logging.info("finding subnet for node: %s", canvas_node.core_node.name)
canvas = self.app.canvas canvas = self.app.canvas
cidr = None cidr = None

View file

@ -6,7 +6,7 @@ import logging
import tkinter as tk import tkinter as tk
import webbrowser import webbrowser
from tkinter import filedialog, messagebox from tkinter import filedialog, messagebox
from typing import Optional from typing import TYPE_CHECKING, Optional
from core.gui.appconfig import XMLS_PATH from core.gui.appconfig import XMLS_PATH
from core.gui.dialogs.about import AboutDialog from core.gui.dialogs.about import AboutDialog
@ -21,13 +21,16 @@ from core.gui.dialogs.sessions import SessionsDialog
from core.gui.dialogs.throughput import ThroughputDialog from core.gui.dialogs.throughput import ThroughputDialog
from core.gui.task import BackgroundTask from core.gui.task import BackgroundTask
if TYPE_CHECKING:
from core.gui.app import Application
class MenuAction: class MenuAction:
""" """
Actions performed when choosing menu items Actions performed when choosing menu items
""" """
def __init__(self, app, master): def __init__(self, app: "Application", master: tk.Tk):
self.master = master self.master = master
self.app = app self.app = app
self.canvas = app.canvas self.canvas = app.canvas
@ -39,11 +42,9 @@ class MenuAction:
# if quitapp: # if quitapp:
# self.app.quit() # self.app.quit()
def prompt_save_running_session(self, quitapp: Optional[bool] = False): def prompt_save_running_session(self, quitapp: bool = False):
""" """
Prompt use to stop running session before application is closed Prompt use to stop running session before application is closed
:return: nothing
""" """
result = True result = True
if self.app.core.is_runtime(): if self.app.core.is_runtime():
@ -58,15 +59,13 @@ class MenuAction:
elif quitapp: elif quitapp:
self.app.quit() self.app.quit()
def on_quit(self, event: Optional[tk.Event] = None): def on_quit(self, event: tk.Event = None):
""" """
Prompt user whether so save running session, and then close the application Prompt user whether so save running session, and then close the application
:return: nothing
""" """
self.prompt_save_running_session(quitapp=True) self.prompt_save_running_session(quitapp=True)
def file_save_as_xml(self, event: Optional[tk.Event] = None): def file_save_as_xml(self, event: tk.Event = None):
logging.info("menuaction.py file_save_as_xml()") logging.info("menuaction.py file_save_as_xml()")
file_path = filedialog.asksaveasfilename( file_path = filedialog.asksaveasfilename(
initialdir=str(XMLS_PATH), initialdir=str(XMLS_PATH),

View file

@ -1,16 +1,20 @@
import tkinter as tk import tkinter as tk
from functools import partial from functools import partial
from typing import TYPE_CHECKING
import core.gui.menuaction as action import core.gui.menuaction as action
from core.gui.coreclient import OBSERVERS from core.gui.coreclient import OBSERVERS
if TYPE_CHECKING:
from core.gui.app import Application
class Menubar(tk.Menu): class Menubar(tk.Menu):
""" """
Core menubar Core menubar
""" """
def __init__(self, master, app, cnf={}, **kwargs): def __init__(self, master: tk.Tk, app: "Application", cnf={}, **kwargs):
""" """
Create a CoreMenubar instance Create a CoreMenubar instance
""" """

View file

@ -3,13 +3,17 @@ status bar
""" """
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import TYPE_CHECKING
from core.gui.dialogs.alerts import AlertsDialog from core.gui.dialogs.alerts import AlertsDialog
from core.gui.themes import Styles from core.gui.themes import Styles
if TYPE_CHECKING:
from core.gui.app import Application
class StatusBar(ttk.Frame): class StatusBar(ttk.Frame):
def __init__(self, master: tk.Widget, app, **kwargs): def __init__(self, master: tk.Widget, app: "Application", **kwargs):
super().__init__(master, **kwargs) super().__init__(master, **kwargs)
self.app = app self.app = app
self.status = None self.status = None

View file

@ -159,7 +159,7 @@ def style_menu(widget: tk.Widget):
) )
def style_listbox(widget: ttk.Widget): def style_listbox(widget: tk.Widget):
style = ttk.Style() style = ttk.Style()
bg = style.lookup(".", "background") bg = style.lookup(".", "background")
fg = style.lookup(".", "foreground") fg = style.lookup(".", "foreground")

View file

@ -4,9 +4,7 @@ import tkinter as tk
from functools import partial from functools import partial
from tkinter import messagebox, ttk from tkinter import messagebox, ttk
from tkinter.font import Font from tkinter.font import Font
from typing import Callable from typing import TYPE_CHECKING, Callable
from PIL import ImageTk
from core.api.grpc import core_pb2 from core.api.grpc import core_pb2
from core.gui.dialogs.customnodes import CustomNodesDialog from core.gui.dialogs.customnodes import CustomNodesDialog
@ -14,11 +12,15 @@ from core.gui.dialogs.marker import MarkerDialog
from core.gui.graph.enums import GraphMode from core.gui.graph.enums import GraphMode
from core.gui.graph.shapeutils import ShapeType, is_marker from core.gui.graph.shapeutils import ShapeType, is_marker
from core.gui.images import ImageEnum, Images from core.gui.images import ImageEnum, Images
from core.gui.nodeutils import NodeUtils from core.gui.nodeutils import NodeDraw, NodeUtils
from core.gui.task import BackgroundTask from core.gui.task import BackgroundTask
from core.gui.themes import Styles from core.gui.themes import Styles
from core.gui.tooltip import Tooltip from core.gui.tooltip import Tooltip
if TYPE_CHECKING:
from core.gui.app import Application
from PIL import ImageTk
TOOLBAR_SIZE = 32 TOOLBAR_SIZE = 32
PICKER_SIZE = 24 PICKER_SIZE = 24
@ -32,11 +34,9 @@ class Toolbar(ttk.Frame):
Core toolbar class Core toolbar class
""" """
def __init__(self, master, app, **kwargs): def __init__(self, master: "Application", app: "Application", **kwargs):
""" """
Create a CoreToolbar instance Create a CoreToolbar instance
:param tkinter.Frame edit_frame: edit frame
""" """
super().__init__(master, **kwargs) super().__init__(master, **kwargs)
self.app = app self.app = app
@ -200,7 +200,7 @@ class Toolbar(ttk.Frame):
self.app.unbind_all("<ButtonRelease-1>") self.app.unbind_all("<ButtonRelease-1>")
def create_picker_button( def create_picker_button(
self, image: ImageTk.PhotoImage, func: Callable, frame: ttk.Frame, label: str self, image: "ImageTk.PhotoImage", func: Callable, frame: ttk.Frame, label: str
): ):
""" """
Create button and put it on the frame Create button and put it on the frame
@ -218,7 +218,11 @@ class Toolbar(ttk.Frame):
button.grid(pady=1) button.grid(pady=1)
def create_button( def create_button(
self, frame: ttk.Frame, image: ImageTk.PhotoImage, func: Callable, tooltip: str self,
frame: ttk.Frame,
image: "ImageTk.PhotoImage",
func: Callable,
tooltip: str,
): ):
button = ttk.Button(frame, image=image, command=func) button = ttk.Button(frame, image=image, command=func)
button.image = image button.image = image
@ -280,7 +284,7 @@ class Toolbar(ttk.Frame):
dialog = CustomNodesDialog(self.app, self.app) dialog = CustomNodesDialog(self.app, self.app)
dialog.show() dialog.show()
def update_button(self, button: ttk.Button, image, node_draw): def update_button(self, button: ttk.Button, image: "ImageTk", node_draw: NodeDraw):
logging.info("update button(%s): %s", button, node_draw) logging.info("update button(%s): %s", button, node_draw)
self.hide_pickers() self.hide_pickers()
button.configure(image=image) button.configure(image=image)
@ -429,7 +433,7 @@ class Toolbar(ttk.Frame):
if not response.result: if not response.result:
messagebox.showerror("Stop Error", "Errors stopping session") messagebox.showerror("Stop Error", "Errors stopping session")
def update_annotation(self, image: ImageTk.PhotoImage, shape_type: str): def update_annotation(self, image: "ImageTk.PhotoImage", shape_type: ShapeType):
logging.info("clicked annotation: ") logging.info("clicked annotation: ")
self.hide_pickers() self.hide_pickers()
self.annotation_button.configure(image=image) self.annotation_button.configure(image=image)
@ -439,7 +443,7 @@ class Toolbar(ttk.Frame):
if is_marker(shape_type): if is_marker(shape_type):
if self.marker_tool: if self.marker_tool:
self.marker_tool.destroy() self.marker_tool.destroy()
self.marker_tool = MarkerDialog(self.master, self.app) self.marker_tool = MarkerDialog(self.app, self.app)
self.marker_tool.show() self.marker_tool.show()
def click_run_button(self): def click_run_button(self):
@ -455,7 +459,7 @@ class Toolbar(ttk.Frame):
self.app.canvas.annotation_type = ShapeType.MARKER self.app.canvas.annotation_type = ShapeType.MARKER
if self.marker_tool: if self.marker_tool:
self.marker_tool.destroy() self.marker_tool.destroy()
self.marker_tool = MarkerDialog(self.master, self.app) self.marker_tool = MarkerDialog(self.app, self.app)
self.marker_tool.show() self.marker_tool.show()
def click_two_node_button(self): def click_two_node_button(self):

View file

@ -1,6 +1,5 @@
import tkinter as tk import tkinter as tk
from tkinter import ttk from tkinter import ttk
from typing import Optional
from core.gui.themes import Styles from core.gui.themes import Styles
@ -19,10 +18,10 @@ class Tooltip(object):
self.id = None self.id = None
self.tw = None self.tw = None
def on_enter(self, event: Optional[tk.Event] = None): def on_enter(self, event: tk.Event = None):
self.schedule() self.schedule()
def on_leave(self, event: Optional[tk.Event] = None): def on_leave(self, event: tk.Event = None):
self.unschedule() self.unschedule()
self.close(event) self.close(event)
@ -36,7 +35,7 @@ class Tooltip(object):
if id_: if id_:
self.widget.after_cancel(id_) self.widget.after_cancel(id_)
def enter(self, event: Optional[tk.Event] = None): def enter(self, event: tk.Event = None):
x, y, cx, cy = self.widget.bbox("insert") x, y, cx, cy = self.widget.bbox("insert")
x += self.widget.winfo_rootx() x += self.widget.winfo_rootx()
y += self.widget.winfo_rooty() + 32 y += self.widget.winfo_rooty() + 32
@ -51,6 +50,6 @@ class Tooltip(object):
label = ttk.Label(frame, text=self.text, style=Styles.tooltip) label = ttk.Label(frame, text=self.text, style=Styles.tooltip)
label.grid() label.grid()
def close(self, event: Optional[tk.Event] = None): def close(self, event: tk.Event = None):
if self.tw: if self.tw:
self.tw.destroy() self.tw.destroy()

View file

@ -3,13 +3,17 @@ input validation
""" """
import re import re
import tkinter as tk import tkinter as tk
from typing import TYPE_CHECKING
import netaddr import netaddr
from netaddr import IPNetwork from netaddr import IPNetwork
if TYPE_CHECKING:
from core.gui.app import Application
class InputValidation: class InputValidation:
def __init__(self, app): def __init__(self, app: "Application"):
self.master = app.master self.master = app.master
self.positive_int = None self.positive_int = None
self.positive_float = None self.positive_float = None

View file

@ -3,11 +3,16 @@ import tkinter as tk
from functools import partial from functools import partial
from pathlib import PosixPath from pathlib import PosixPath
from tkinter import filedialog, font, ttk from tkinter import filedialog, font, ttk
from typing import TYPE_CHECKING, Dict, Optional
from core.api.grpc import core_pb2 from core.api.grpc import core_pb2
from core.gui import themes from core.gui import themes
from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.themes import FRAME_PAD, PADX, PADY
if TYPE_CHECKING:
from core.gui.app import Application
from core.gui.dialogs.dialog import Dialog
INT_TYPES = { INT_TYPES = {
core_pb2.ConfigOptionType.UINT8, core_pb2.ConfigOptionType.UINT8,
core_pb2.ConfigOptionType.UINT16, core_pb2.ConfigOptionType.UINT16,
@ -27,7 +32,7 @@ def file_button_click(value: tk.StringVar, parent: tk.Widget):
class FrameScroll(ttk.Frame): class FrameScroll(ttk.Frame):
def __init__(self, master, app, _cls=ttk.Frame, **kw): def __init__(self, master: ttk.Widget, app: "Application", _cls=ttk.Frame, **kw):
super().__init__(master, **kw) super().__init__(master, **kw)
self.app = app self.app = app
self.rowconfigure(0, weight=1) self.rowconfigure(0, weight=1)
@ -65,7 +70,13 @@ class FrameScroll(ttk.Frame):
class ConfigFrame(ttk.Notebook): class ConfigFrame(ttk.Notebook):
def __init__(self, master, app, config, **kw): def __init__(
self,
master: ttk.Widget,
app: "Application",
config: Dict[str, core_pb2.ConfigOption],
**kw
):
super().__init__(master, **kw) super().__init__(master, **kw)
self.app = app self.app = app
self.config = config self.config = config
@ -175,7 +186,7 @@ class ConfigFrame(ttk.Notebook):
class ListboxScroll(ttk.Frame): class ListboxScroll(ttk.Frame):
def __init__(self, master=None, **kw): def __init__(self, master: Optional[ttk.Widget] = None, **kw):
super().__init__(master, **kw) super().__init__(master, **kw)
self.columnconfigure(0, weight=1) self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1) self.rowconfigure(0, weight=1)
@ -190,7 +201,7 @@ class ListboxScroll(ttk.Frame):
class CheckboxList(FrameScroll): class CheckboxList(FrameScroll):
def __init__(self, master, app, clicked=None, **kw): def __init__(self, master: ttk.Widget, app: "Application", clicked=None, **kw):
super().__init__(master, app, **kw) super().__init__(master, app, **kw)
self.clicked = clicked self.clicked = clicked
self.frame.columnconfigure(0, weight=1) self.frame.columnconfigure(0, weight=1)
@ -208,7 +219,7 @@ class CodeFont(font.Font):
class CodeText(ttk.Frame): class CodeText(ttk.Frame):
def __init__(self, master, **kwargs): def __init__(self, master: ttk.Widget, **kwargs):
super().__init__(master, **kwargs) super().__init__(master, **kwargs)
self.rowconfigure(0, weight=1) self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1) self.columnconfigure(0, weight=1)
@ -232,14 +243,14 @@ class CodeText(ttk.Frame):
class Spinbox(ttk.Entry): class Spinbox(ttk.Entry):
def __init__(self, master=None, **kwargs): def __init__(self, master: Optional[ttk.Widget] = None, **kwargs):
super().__init__(master, "ttk::spinbox", **kwargs) super().__init__(master, "ttk::spinbox", **kwargs)
def set(self, value): def set(self, value):
self.tk.call(self._w, "set", value) self.tk.call(self._w, "set", value)
def image_chooser(parent: tk.Widget, path: PosixPath): def image_chooser(parent: "Dialog", path: PosixPath):
return filedialog.askopenfilename( return filedialog.askopenfilename(
parent=parent, parent=parent,
initialdir=str(path), initialdir=str(path),