moved coretk under daemon/core/gui

This commit is contained in:
Blake Harnden 2019-12-19 09:30:21 -08:00
parent f13c62a1c9
commit 0b5c94778c
118 changed files with 505 additions and 432 deletions

View file

View file

@ -0,0 +1,44 @@
import tkinter as tk
from core.gui.dialogs.dialog import Dialog
from core.gui.widgets import CodeText
LICENSE = """\
Copyright (c) 2005-2020, the Boeing Company.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.\
"""
class AboutDialog(Dialog):
def __init__(self, master, app):
super().__init__(master, app, "About CORE", modal=True)
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
codetext = CodeText(self.top)
codetext.text.insert("1.0", LICENSE)
codetext.text.config(state=tk.DISABLED)
codetext.grid(sticky="nsew")

View file

@ -0,0 +1,171 @@
"""
check engine light
"""
import tkinter as tk
from tkinter import ttk
from grpc import RpcError
from core.api.grpc import core_pb2
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY
from core.gui.widgets import CodeText
class AlertsDialog(Dialog):
def __init__(self, master, app):
super().__init__(master, app, "Alerts", modal=True)
self.app = app
self.tree = None
self.codetext = None
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
self.top.rowconfigure(1, weight=1)
frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1)
frame.rowconfigure(0, weight=1)
frame.grid(sticky="nsew", pady=PADY)
self.tree = ttk.Treeview(
frame,
columns=("time", "level", "session_id", "node", "source"),
show="headings",
)
self.tree.grid(row=0, column=0, sticky="nsew")
self.tree.column("time", stretch=tk.YES)
self.tree.heading("time", text="Time")
self.tree.column("level", stretch=tk.YES)
self.tree.heading("level", text="Level")
self.tree.column("session_id", stretch=tk.YES)
self.tree.heading("session_id", text="Session ID")
self.tree.column("node", stretch=tk.YES)
self.tree.heading("node", text="Node")
self.tree.column("source", stretch=tk.YES)
self.tree.heading("source", text="Source")
self.tree.bind("<<TreeviewSelect>>", self.click_select)
for alarm in self.app.statusbar.core_alarms:
level = self.get_level(alarm.level)
self.tree.insert(
"",
tk.END,
text=str(alarm.date),
values=(
alarm.date,
level + " (%s)" % alarm.level,
alarm.session_id,
alarm.node_id,
alarm.source,
),
tags=(level,),
)
self.tree.tag_configure("ERROR", background="#ff6666")
self.tree.tag_configure("FATAL", background="#d9d9d9")
self.tree.tag_configure("WARNING", background="#ffff99")
self.tree.tag_configure("NOTICE", background="#85e085")
yscrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.tree.yview)
yscrollbar.grid(row=0, column=1, sticky="ns")
self.tree.configure(yscrollcommand=yscrollbar.set)
xscrollbar = ttk.Scrollbar(frame, orient="horizontal", command=self.tree.xview)
xscrollbar.grid(row=1, sticky="ew")
self.tree.configure(xscrollcommand=xscrollbar.set)
self.codetext = CodeText(self.top)
self.codetext.text.config(state=tk.DISABLED)
self.codetext.grid(sticky="nsew", pady=PADY)
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
frame.columnconfigure(2, weight=1)
frame.columnconfigure(3, weight=1)
button = ttk.Button(frame, text="Reset", command=self.reset_alerts)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Daemon Log", command=self.daemon_log)
button.grid(row=0, column=1, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Node Log")
button.grid(row=0, column=2, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Close", command=self.destroy)
button.grid(row=0, column=3, sticky="ew")
def reset_alerts(self):
self.codetext.text.delete("1.0", tk.END)
for item in self.tree.get_children():
self.tree.delete(item)
self.app.statusbar.core_alarms.clear()
def daemon_log(self):
dialog = DaemonLog(self, self.app)
dialog.show()
def get_level(self, level):
if level == core_pb2.ExceptionLevel.ERROR:
return "ERROR"
if level == core_pb2.ExceptionLevel.FATAL:
return "FATAL"
if level == core_pb2.ExceptionLevel.WARNING:
return "WARNING"
if level == core_pb2.ExceptionLevel.NOTICE:
return "NOTICE"
def click_select(self, event):
current = self.tree.selection()
values = self.tree.item(current)["values"]
time = values[0]
level = values[1]
session_id = values[2]
node_id = values[3]
source = values[4]
text = "DATE: %s\nLEVEL: %s\nNODE: %s (%s)\nSESSION: %s\nSOURCE: %s\n\n" % (
time,
level,
node_id,
self.app.core.canvas_nodes[node_id].core_node.name,
session_id,
source,
)
try:
sid = self.app.core.session_id
self.app.core.client.get_node(sid, node_id)
text = text + "node created"
except RpcError:
text = text + "node not created"
self.codetext.text.delete("1.0", "end")
self.codetext.text.insert("1.0", text)
class DaemonLog(Dialog):
def __init__(self, master, app):
super().__init__(master, app, "core-daemon log", modal=True)
self.columnconfigure(0, weight=1)
self.path = tk.StringVar(value="/var/log/core-daemon.log")
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(1, weight=1)
frame = ttk.Frame(self.top)
frame.grid(row=0, column=0, sticky="ew", pady=PADY)
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=9)
label = ttk.Label(frame, text="File", anchor="w")
label.grid(row=0, column=0, sticky="ew")
entry = ttk.Entry(frame, textvariable=self.path, state="disabled")
entry.grid(row=0, column=1, sticky="ew")
try:
file = open("/var/log/core-daemon.log", "r")
log = file.readlines()
except FileNotFoundError:
log = "Log file not found"
codetext = CodeText(self.top)
codetext.text.insert("1.0", log)
codetext.text.see("end")
codetext.text.config(state=tk.DISABLED)
codetext.grid(row=1, column=0, sticky="nsew")

View file

@ -0,0 +1,254 @@
"""
size and scale
"""
import tkinter as tk
from tkinter import font, ttk
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import FRAME_PAD, PADX, PADY
PIXEL_SCALE = 100
class SizeAndScaleDialog(Dialog):
def __init__(self, master, app):
"""
create an instance for size and scale object
:param app: main application
"""
super().__init__(master, app, "Canvas Size and Scale", modal=True)
self.canvas = self.app.canvas
self.validation = app.validation
self.section_font = font.Font(weight="bold")
width, height = self.canvas.current_dimensions
self.pixel_width = tk.IntVar(value=width)
self.pixel_height = tk.IntVar(value=height)
location = self.app.core.location
self.x = tk.DoubleVar(value=location.x)
self.y = tk.DoubleVar(value=location.y)
self.lat = tk.DoubleVar(value=location.lat)
self.lon = tk.DoubleVar(value=location.lon)
self.alt = tk.DoubleVar(value=location.alt)
self.scale = tk.DoubleVar(value=location.scale)
self.meters_width = tk.IntVar(value=width / PIXEL_SCALE * location.scale)
self.meters_height = tk.IntVar(value=height / PIXEL_SCALE * location.scale)
self.save_default = tk.BooleanVar(value=False)
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.draw_size()
self.draw_scale()
self.draw_reference_point()
self.draw_save_as_default()
self.draw_spacer()
self.draw_buttons()
def draw_size(self):
label_frame = ttk.Labelframe(self.top, text="Size", padding=FRAME_PAD)
label_frame.grid(sticky="ew")
label_frame.columnconfigure(0, weight=1)
# draw size row 1
frame = ttk.Frame(label_frame)
frame.grid(sticky="ew", pady=PADY)
frame.columnconfigure(1, weight=1)
frame.columnconfigure(3, weight=1)
label = ttk.Label(frame, text="Width")
label.grid(row=0, column=0, sticky="w", padx=PADX)
entry = ttk.Entry(
frame,
textvariable=self.pixel_width,
validate="key",
validatecommand=(self.validation.positive_int, "%P"),
)
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
entry.grid(row=0, column=1, sticky="ew", padx=PADX)
label = ttk.Label(frame, text="x Height")
label.grid(row=0, column=2, sticky="w", padx=PADX)
entry = ttk.Entry(
frame,
textvariable=self.pixel_height,
validate="key",
validatecommand=(self.validation.positive_int, "%P"),
)
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
entry.grid(row=0, column=3, sticky="ew", padx=PADX)
label = ttk.Label(frame, text="Pixels")
label.grid(row=0, column=4, sticky="w")
# draw size row 2
frame = ttk.Frame(label_frame)
frame.grid(sticky="ew", pady=PADY)
frame.columnconfigure(1, weight=1)
frame.columnconfigure(3, weight=1)
label = ttk.Label(frame, text="Width")
label.grid(row=0, column=0, sticky="w", padx=PADX)
entry = ttk.Entry(
frame,
textvariable=self.meters_width,
validate="key",
validatecommand=(self.validation.positive_float, "%P"),
)
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
entry.grid(row=0, column=1, sticky="ew", padx=PADX)
label = ttk.Label(frame, text="x Height")
label.grid(row=0, column=2, sticky="w", padx=PADX)
entry = ttk.Entry(
frame,
textvariable=self.meters_height,
validate="key",
validatecommand=(self.validation.positive_float, "%P"),
)
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
entry.grid(row=0, column=3, sticky="ew", padx=PADX)
label = ttk.Label(frame, text="Meters")
label.grid(row=0, column=4, sticky="w")
def draw_scale(self):
label_frame = ttk.Labelframe(self.top, text="Scale", padding=FRAME_PAD)
label_frame.grid(sticky="ew")
label_frame.columnconfigure(0, weight=1)
frame = ttk.Frame(label_frame)
frame.grid(sticky="ew")
frame.columnconfigure(1, weight=1)
label = ttk.Label(frame, text=f"{PIXEL_SCALE} Pixels =")
label.grid(row=0, column=0, sticky="w", padx=PADX)
entry = ttk.Entry(
frame,
textvariable=self.scale,
validate="key",
validatecommand=(self.validation.positive_float, "%P"),
)
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
entry.grid(row=0, column=1, sticky="ew", padx=PADX)
label = ttk.Label(frame, text="Meters")
label.grid(row=0, column=2, sticky="w")
def draw_reference_point(self):
label_frame = ttk.Labelframe(
self.top, text="Reference Point", padding=FRAME_PAD
)
label_frame.grid(sticky="ew")
label_frame.columnconfigure(0, weight=1)
label = ttk.Label(
label_frame, text="Default is (0, 0), the upper left corner of the canvas"
)
label.grid()
frame = ttk.Frame(label_frame)
frame.grid(sticky="ew", pady=PADY)
frame.columnconfigure(1, weight=1)
frame.columnconfigure(3, weight=1)
label = ttk.Label(frame, text="X")
label.grid(row=0, column=0, sticky="w", padx=PADX)
entry = ttk.Entry(
frame,
textvariable=self.x,
validate="key",
validatecommand=(self.validation.positive_float, "%P"),
)
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
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)
entry = ttk.Entry(
frame,
textvariable=self.y,
validate="key",
validatecommand=(self.validation.positive_float, "%P"),
)
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
entry.grid(row=0, column=3, sticky="ew", padx=PADX)
label = ttk.Label(label_frame, text="Translates To")
label.grid()
frame = ttk.Frame(label_frame)
frame.grid(sticky="ew", pady=PADY)
frame.columnconfigure(1, weight=1)
frame.columnconfigure(3, weight=1)
frame.columnconfigure(5, weight=1)
label = ttk.Label(frame, text="Lat")
label.grid(row=0, column=0, sticky="w", padx=PADX)
entry = ttk.Entry(
frame,
textvariable=self.lat,
validate="key",
validatecommand=(self.validation.positive_float, "%P"),
)
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
entry.grid(row=0, column=1, sticky="ew", padx=PADX)
label = ttk.Label(frame, text="Lon")
label.grid(row=0, column=2, sticky="w", padx=PADX)
entry = ttk.Entry(
frame,
textvariable=self.lon,
validate="key",
validatecommand=(self.validation.positive_float, "%P"),
)
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
entry.grid(row=0, column=3, sticky="ew", padx=PADX)
label = ttk.Label(frame, text="Alt")
label.grid(row=0, column=4, sticky="w", padx=PADX)
entry = ttk.Entry(
frame,
textvariable=self.alt,
validate="key",
validatecommand=(self.validation.positive_float, "%P"),
)
entry.bind("<FocusOut>", lambda event: self.validation.focus_out(event, "0"))
entry.grid(row=0, column=5, sticky="ew")
def draw_save_as_default(self):
button = ttk.Checkbutton(
self.top, text="Save as default?", variable=self.save_default
)
button.grid(sticky="w", pady=PADY)
def draw_buttons(self):
frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
frame.grid(sticky="ew")
button = ttk.Button(frame, text="Apply", command=self.click_apply)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="ew")
def click_apply(self):
width, height = self.pixel_width.get(), self.pixel_height.get()
self.canvas.redraw_canvas((width, height))
if self.canvas.wallpaper:
self.canvas.redraw_wallpaper()
location = self.app.core.location
location.x = self.x.get()
location.y = self.y.get()
location.lat = self.lat.get()
location.lon = self.lon.get()
location.alt = self.alt.get()
location.scale = self.scale.get()
if self.save_default.get():
location_config = self.app.guiconfig["location"]
location_config["x"] = location.x
location_config["y"] = location.y
location_config["z"] = location.z
location_config["lat"] = location.lat
location_config["lon"] = location.lon
location_config["alt"] = location.alt
location_config["scale"] = location.scale
preferences = self.app.guiconfig["preferences"]
preferences["width"] = width
preferences["height"] = height
self.app.save_config()
self.destroy()

View file

@ -0,0 +1,179 @@
"""
set wallpaper
"""
import logging
import tkinter as tk
from tkinter import ttk
from core.gui.appconfig import BACKGROUNDS_PATH
from core.gui.dialogs.dialog import Dialog
from core.gui.images import Images
from core.gui.themes import PADX, PADY
from core.gui.widgets import image_chooser
class CanvasBackgroundDialog(Dialog):
def __init__(self, master, app):
"""
create an instance of CanvasWallpaper object
:param coretk.app.Application app: root application
"""
super().__init__(master, app, "Canvas Background", modal=True)
self.canvas = self.app.canvas
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.options = []
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.draw_image()
self.draw_image_label()
self.draw_image_selection()
self.draw_options()
self.draw_additional_options()
self.draw_spacer()
self.draw_buttons()
def draw_image(self):
self.image_label = ttk.Label(
self.top, text="(image preview)", width=32, anchor=tk.CENTER
)
self.image_label.grid(pady=PADY)
def draw_image_label(self):
label = ttk.Label(self.top, text="Image filename: ")
label.grid(sticky="ew")
if self.filename.get():
self.draw_preview()
def draw_image_selection(self):
frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=2)
frame.columnconfigure(1, weight=1)
frame.columnconfigure(2, weight=1)
frame.grid(sticky="ew")
entry = ttk.Entry(frame, textvariable=self.filename)
entry.focus()
entry.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="...", command=self.click_open_image)
button.grid(row=0, column=1, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Clear", command=self.click_clear)
button.grid(row=0, column=2, sticky="ew")
def draw_options(self):
frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
frame.columnconfigure(2, weight=1)
frame.columnconfigure(3, weight=1)
frame.grid(sticky="ew")
button = ttk.Radiobutton(
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.scale_option
)
button.grid(row=0, column=1, sticky="ew")
self.options.append(button)
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.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
)
checkbutton.grid(sticky="ew", padx=PADX)
checkbutton = ttk.Checkbutton(
self.top,
text="Adjust canvas size to image dimensions",
variable=self.adjust_to_dim,
command=self.click_adjust_canvas,
)
checkbutton.grid(sticky="ew", padx=PADX)
def draw_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(pady=PADY, sticky="ew")
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
button = ttk.Button(frame, text="Apply", command=self.click_apply)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="ew")
def click_open_image(self):
filename = image_chooser(self, BACKGROUNDS_PATH)
if filename:
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):
"""
delete like shown in image link entry if there is any
:return: nothing
"""
# delete entry
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.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
else:
self.scale_option.set(1)
for option in self.options:
option.config(state=tk.NORMAL)
def click_apply(self):
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:
filename = None
try:
self.canvas.set_wallpaper(filename)
except FileNotFoundError:
logging.error("invalid background: %s", filename)
self.destroy()

View file

@ -0,0 +1,251 @@
"""
custom color picker
"""
import logging
import tkinter as tk
from tkinter import ttk
from core.gui.dialogs.dialog import Dialog
class ColorPicker(Dialog):
def __init__(self, master, app, initcolor="#000000"):
super().__init__(master, app, "color picker", modal=True)
self.red_entry = None
self.blue_entry = None
self.green_entry = None
self.hex_entry = None
self.red_label = None
self.green_label = None
self.blue_label = None
self.display = None
self.color = initcolor
red, green, blue = self.get_rgb(initcolor)
self.red = tk.IntVar(value=red)
self.blue = tk.IntVar(value=blue)
self.green = tk.IntVar(value=green)
self.hex = tk.StringVar(value=initcolor)
self.red_scale = tk.IntVar(value=red)
self.green_scale = tk.IntVar(value=green)
self.blue_scale = tk.IntVar(value=blue)
self.draw()
self.set_bindings()
def askcolor(self):
self.show()
return self.color
def draw(self):
self.top.columnconfigure(0, weight=1)
# rgb frames
frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
frame.columnconfigure(2, weight=6)
frame.columnconfigure(3, weight=2)
label = ttk.Label(frame, text="R: ")
label.grid(row=0, column=0)
self.red_entry = ttk.Entry(
frame,
width=4,
textvariable=self.red,
validate="key",
validatecommand=(self.app.validation.rgb, "%P"),
)
self.red_entry.grid(row=0, column=1, sticky="nsew")
scale = ttk.Scale(
frame,
from_=0,
to=255,
value=0,
# length=200,
orient=tk.HORIZONTAL,
variable=self.red_scale,
command=lambda x: self.scale_callback(self.red_scale, self.red),
)
scale.grid(row=0, column=2, sticky="nsew")
self.red_label = ttk.Label(
frame, background="#%02x%02x%02x" % (self.red.get(), 0, 0), width=5
)
self.red_label.grid(row=0, column=3, sticky="nsew")
frame.grid(row=0, column=0, sticky="nsew")
frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
frame.columnconfigure(2, weight=6)
frame.columnconfigure(3, weight=2)
label = ttk.Label(frame, text="G: ")
label.grid(row=0, column=0)
self.green_entry = ttk.Entry(
frame,
width=4,
textvariable=self.green,
validate="key",
validatecommand=(self.app.validation.rgb, "%P"),
)
self.green_entry.grid(row=0, column=1, sticky="nsew")
scale = ttk.Scale(
frame,
from_=0,
to=255,
value=0,
# length=200,
orient=tk.HORIZONTAL,
variable=self.green_scale,
command=lambda x: self.scale_callback(self.green_scale, self.green),
)
scale.grid(row=0, column=2, sticky="nsew")
self.green_label = ttk.Label(
frame, background="#%02x%02x%02x" % (0, self.green.get(), 0), width=5
)
self.green_label.grid(row=0, column=3, sticky="nsew")
frame.grid(row=1, column=0, sticky="nsew")
frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
frame.columnconfigure(2, weight=6)
frame.columnconfigure(3, weight=2)
label = ttk.Label(frame, text="B: ")
label.grid(row=0, column=0)
self.blue_entry = ttk.Entry(
frame,
width=4,
textvariable=self.blue,
validate="key",
validatecommand=(self.app.validation.rgb, "%P"),
)
self.blue_entry.grid(row=0, column=1, sticky="nsew")
scale = ttk.Scale(
frame,
from_=0,
to=255,
value=0,
# length=200,
orient=tk.HORIZONTAL,
variable=self.blue_scale,
command=lambda x: self.scale_callback(self.blue_scale, self.blue),
)
scale.grid(row=0, column=2, sticky="nsew")
self.blue_label = ttk.Label(
frame, background="#%02x%02x%02x" % (0, 0, self.blue.get()), width=5
)
self.blue_label.grid(row=0, column=3, sticky="nsew")
frame.grid(row=2, column=0, sticky="nsew")
# hex code and color display
frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1)
label = ttk.Label(frame, text="Selection: ")
label.grid(row=0, column=0, sticky="nsew")
self.hex_entry = ttk.Entry(
frame,
textvariable=self.hex,
validate="key",
validatecommand=(self.app.validation.hex, "%P"),
)
self.hex_entry.grid(row=1, column=0, sticky="nsew")
self.display = tk.Frame(frame, background=self.color, width=100, height=100)
self.display.grid(row=2, column=0)
frame.grid(row=3, column=0, sticky="nsew")
# button frame
frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
button = ttk.Button(frame, text="OK", command=self.button_ok)
button.grid(row=0, column=0, sticky="nsew")
button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="nsew")
frame.grid(row=4, column=0, sticky="nsew")
def set_bindings(self):
self.red_entry.bind("<FocusIn>", lambda x: self.current_focus("rgb"))
self.green_entry.bind("<FocusIn>", lambda x: self.current_focus("rgb"))
self.blue_entry.bind("<FocusIn>", lambda x: self.current_focus("rgb"))
self.hex_entry.bind("<FocusIn>", lambda x: self.current_focus("hex"))
self.red.trace_add("write", self.update_color)
self.green.trace_add("write", self.update_color)
self.blue.trace_add("write", self.update_color)
self.hex.trace_add("write", self.update_color)
def button_ok(self):
logging.debug("not implemented")
self.color = self.hex.get()
self.destroy()
def get_hex(self):
"""
convert current RGB values into hex color
:rtype: str
:return: hex color
"""
red = self.red_entry.get()
blue = self.blue_entry.get()
green = self.green_entry.get()
return "#%02x%02x%02x" % (int(red), int(green), int(blue))
def current_focus(self, focus):
self.focus = focus
def update_color(self, arg1=None, arg2=None, arg3=None):
if self.focus == "rgb":
red = self.red_entry.get()
blue = self.blue_entry.get()
green = self.green_entry.get()
self.set_scale(red, green, blue)
if red and blue and green:
hex_code = "#%02x%02x%02x" % (int(red), int(green), int(blue))
self.hex.set(hex_code)
self.display.config(background=hex_code)
self.set_label(red, green, blue)
elif self.focus == "hex":
hex_code = self.hex.get()
if len(hex_code) == 4 or len(hex_code) == 7:
red, green, blue = self.get_rgb(hex_code)
else:
return
self.set_entry(red, green, blue)
self.set_scale(red, green, blue)
self.display.config(background=hex_code)
self.set_label(red, green, blue)
def scale_callback(self, var, color_var):
color_var.set(var.get())
self.focus = "rgb"
self.update_color()
def set_scale(self, red, green, blue):
self.red_scale.set(red)
self.green_scale.set(green)
self.blue_scale.set(blue)
def set_entry(self, red, green, blue):
self.red.set(red)
self.green.set(green)
self.blue.set(blue)
def set_label(self, red, green, blue):
self.red_label.configure(background="#%02x%02x%02x" % (int(red), 0, 0))
self.green_label.configure(background="#%02x%02x%02x" % (0, int(green), 0))
self.blue_label.configure(background="#%02x%02x%02x" % (0, 0, int(blue)))
def get_rgb(self, hex_code):
"""
convert a valid hex code to RGB values
:param string hex_code: color in hex
:rtype: tuple(int, int, int)
:return: the RGB values
"""
if len(hex_code) == 4:
red = hex_code[1]
green = hex_code[2]
blue = hex_code[3]
else:
red = hex_code[1:3]
green = hex_code[3:5]
blue = hex_code[5:]
return int(red, 16), int(green, 16), int(blue, 16)

View file

@ -0,0 +1,261 @@
import logging
import tkinter as tk
from pathlib import Path
from tkinter import ttk
from core.gui import nodeutils
from core.gui.appconfig import ICONS_PATH
from core.gui.dialogs.dialog import Dialog
from core.gui.images import Images
from core.gui.nodeutils import NodeDraw
from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import CheckboxList, ListboxScroll, image_chooser
class ServicesSelectDialog(Dialog):
def __init__(self, master, app, current_services):
super().__init__(master, app, "Node Services", modal=True)
self.groups = None
self.services = None
self.current = None
self.current_services = set(current_services)
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
frame = ttk.LabelFrame(self.top)
frame.grid(stick="nsew", pady=PADY)
frame.rowconfigure(0, weight=1)
for i in range(3):
frame.columnconfigure(i, weight=1)
label_frame = ttk.LabelFrame(frame, text="Groups", padding=FRAME_PAD)
label_frame.grid(row=0, column=0, sticky="nsew")
label_frame.rowconfigure(0, weight=1)
label_frame.columnconfigure(0, weight=1)
self.groups = ListboxScroll(label_frame)
self.groups.grid(sticky="nsew")
for group in sorted(self.app.core.services):
self.groups.listbox.insert(tk.END, group)
self.groups.listbox.bind("<<ListboxSelect>>", self.handle_group_change)
self.groups.listbox.selection_set(0)
label_frame = ttk.LabelFrame(frame, text="Services")
label_frame.grid(row=0, column=1, sticky="nsew")
label_frame.columnconfigure(0, weight=1)
label_frame.rowconfigure(0, weight=1)
self.services = CheckboxList(
label_frame, self.app, clicked=self.service_clicked, padding=FRAME_PAD
)
self.services.grid(sticky="nsew")
label_frame = ttk.LabelFrame(frame, text="Selected", padding=FRAME_PAD)
label_frame.grid(row=0, column=2, sticky="nsew")
label_frame.rowconfigure(0, weight=1)
label_frame.columnconfigure(0, weight=1)
self.current = ListboxScroll(label_frame)
self.current.grid(sticky="nsew")
for service in sorted(self.current_services):
self.current.listbox.insert(tk.END, service)
frame = ttk.Frame(self.top)
frame.grid(stick="ew")
for i in range(2):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Save", command=self.destroy)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=self.click_cancel)
button.grid(row=0, column=1, sticky="ew")
# trigger group change
self.groups.listbox.event_generate("<<ListboxSelect>>")
def handle_group_change(self, event):
selection = self.groups.listbox.curselection()
if selection:
index = selection[0]
group = self.groups.listbox.get(index)
self.services.clear()
for name in sorted(self.app.core.services[group]):
checked = name in self.current_services
self.services.add(name, checked)
def service_clicked(self, name, var):
if var.get() and name not in self.current_services:
self.current_services.add(name)
elif not var.get() and name in self.current_services:
self.current_services.remove(name)
self.current.listbox.delete(0, tk.END)
for name in sorted(self.current_services):
self.current.listbox.insert(tk.END, name)
def click_cancel(self):
self.current_services = None
self.destroy()
class CustomNodesDialog(Dialog):
def __init__(self, master, app):
super().__init__(master, app, "Custom Nodes", modal=True)
self.edit_button = None
self.delete_button = None
self.nodes_list = None
self.name = tk.StringVar()
self.image_button = None
self.image = None
self.image_file = None
self.services = set()
self.selected = None
self.selected_index = None
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
self.draw_node_config()
self.draw_node_buttons()
self.draw_buttons()
def draw_node_config(self):
frame = ttk.LabelFrame(self.top, text="Nodes", padding=FRAME_PAD)
frame.grid(sticky="nsew", pady=PADY)
frame.columnconfigure(0, weight=1)
frame.rowconfigure(0, weight=1)
self.nodes_list = ListboxScroll(frame)
self.nodes_list.grid(row=0, column=0, sticky="nsew", padx=PADX)
self.nodes_list.listbox.bind("<<ListboxSelect>>", self.handle_node_select)
for name in sorted(self.app.core.custom_nodes):
self.nodes_list.listbox.insert(tk.END, name)
frame = ttk.Frame(frame)
frame.grid(row=0, column=2, sticky="nsew")
frame.columnconfigure(0, weight=1)
entry = ttk.Entry(frame, textvariable=self.name)
entry.grid(sticky="ew")
self.image_button = ttk.Button(
frame, text="Icon", compound=tk.LEFT, command=self.click_icon
)
self.image_button.grid(sticky="ew")
button = ttk.Button(frame, text="Services", command=self.click_services)
button.grid(sticky="ew")
def draw_node_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="ew", pady=PADY)
for i in range(3):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Create", command=self.click_create)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
self.edit_button = ttk.Button(
frame, text="Edit", state=tk.DISABLED, command=self.click_edit
)
self.edit_button.grid(row=0, column=1, sticky="ew", padx=PADX)
self.delete_button = ttk.Button(
frame, text="Delete", state=tk.DISABLED, command=self.click_delete
)
self.delete_button.grid(row=0, column=2, sticky="ew")
def draw_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
for i in range(2):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Save", command=self.click_save)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="ew")
def reset_values(self):
self.name.set("")
self.image = None
self.image_file = None
self.services = set()
self.image_button.config(image="")
def click_icon(self):
file_path = image_chooser(self, ICONS_PATH)
if file_path:
image = Images.create(file_path, nodeutils.ICON_SIZE)
self.image = image
self.image_file = file_path
self.image_button.config(image=self.image)
def click_services(self):
dialog = ServicesSelectDialog(self, self.app, self.services)
dialog.show()
if dialog.current_services is not None:
self.services.clear()
self.services.update(dialog.current_services)
def click_save(self):
self.app.guiconfig["nodes"].clear()
for name in sorted(self.app.core.custom_nodes):
node_draw = self.app.core.custom_nodes[name]
self.app.guiconfig["nodes"].append(
{
"name": name,
"image": node_draw.image_file,
"services": list(node_draw.services),
}
)
logging.info("saving custom nodes: %s", self.app.guiconfig["nodes"])
self.app.save_config()
self.destroy()
def click_create(self):
name = self.name.get()
if name not in self.app.core.custom_nodes:
image_file = Path(self.image_file).stem
node_draw = NodeDraw.from_custom(name, image_file, set(self.services))
self.app.core.custom_nodes[name] = node_draw
self.nodes_list.listbox.insert(tk.END, name)
self.reset_values()
def click_edit(self):
name = self.name.get()
if self.selected:
previous_name = self.selected
self.selected = name
node_draw = self.app.core.custom_nodes.pop(previous_name)
node_draw.model = name
node_draw.image_file = Path(self.image_file).stem
node_draw.image = self.image
node_draw.services = self.services
self.app.core.custom_nodes[name] = node_draw
self.nodes_list.listbox.delete(self.selected_index)
self.nodes_list.listbox.insert(self.selected_index, name)
self.nodes_list.listbox.selection_set(self.selected_index)
def click_delete(self):
if self.selected and self.selected in self.app.core.custom_nodes:
self.nodes_list.listbox.delete(self.selected_index)
del self.app.core.custom_nodes[self.selected]
self.reset_values()
self.nodes_list.listbox.selection_clear(0, tk.END)
self.nodes_list.listbox.event_generate("<<ListboxSelect>>")
def handle_node_select(self, event):
selection = self.nodes_list.listbox.curselection()
if selection:
self.selected_index = selection[0]
self.selected = self.nodes_list.listbox.get(self.selected_index)
node_draw = self.app.core.custom_nodes[self.selected]
self.name.set(node_draw.model)
self.services = node_draw.services
self.image = node_draw.image
self.image_file = node_draw.image_file
self.image_button.config(image=self.image)
self.edit_button.config(state=tk.NORMAL)
self.delete_button.config(state=tk.NORMAL)
else:
self.selected = None
self.selected_index = None
self.edit_button.config(state=tk.DISABLED)
self.delete_button.config(state=tk.DISABLED)

View file

@ -0,0 +1,37 @@
import tkinter as tk
from tkinter import ttk
from core.gui.images import ImageEnum, Images
from core.gui.themes import DIALOG_PAD
class Dialog(tk.Toplevel):
def __init__(self, master, app, title, modal=False):
super().__init__(master)
self.withdraw()
self.app = app
self.modal = modal
self.title(title)
self.protocol("WM_DELETE_WINDOW", self.destroy)
image = Images.get(ImageEnum.CORE, 16)
self.tk.call("wm", "iconphoto", self._w, image)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.top = ttk.Frame(self, padding=DIALOG_PAD)
self.top.grid(sticky="nsew")
def show(self):
self.transient(self.master)
self.focus_force()
self.update()
self.deiconify()
if self.modal:
self.wait_visibility()
self.grab_set()
self.wait_window()
def draw_spacer(self, row=None):
frame = ttk.Frame(self.top)
frame.grid(row=row, sticky="nsew")
frame.rowconfigure(0, weight=1)
self.top.rowconfigure(frame.grid_info()["row"], weight=1)

View file

@ -0,0 +1,235 @@
"""
emane configuration
"""
import logging
import tkinter as tk
import webbrowser
from tkinter import ttk
import grpc
from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.images import ImageEnum, Images
from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame
class GlobalEmaneDialog(Dialog):
def __init__(self, master, app):
super().__init__(master, app, "EMANE Configuration", modal=True)
self.config_frame = None
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
self.config_frame = ConfigFrame(self.top, self.app, self.app.core.emane_config)
self.config_frame.draw_config()
self.config_frame.grid(sticky="nsew", pady=PADY)
self.draw_spacer()
self.draw_buttons()
def draw_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
for i in range(2):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Apply", command=self.click_apply)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="ew")
def click_apply(self):
self.config_frame.parse_config()
self.destroy()
class EmaneModelDialog(Dialog):
def __init__(self, master, app, node, model, interface=None):
super().__init__(master, app, f"{node.name} {model} Configuration", modal=True)
self.node = node
self.model = f"emane_{model}"
self.interface = interface
self.config_frame = None
try:
self.config = self.app.core.get_emane_model_config(
self.node.id, self.model, self.interface
)
except grpc.RpcError as e:
show_grpc_error(e)
self.destroy()
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
self.config_frame = ConfigFrame(self.top, self.app, self.config)
self.config_frame.draw_config()
self.config_frame.grid(sticky="nsew", pady=PADY)
self.draw_spacer()
self.draw_buttons()
def draw_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
for i in range(2):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Apply", command=self.click_apply)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="ew")
def click_apply(self):
self.config_frame.parse_config()
self.app.core.set_emane_model_config(
self.node.id, self.model, self.config, self.interface
)
self.destroy()
class EmaneConfigDialog(Dialog):
def __init__(self, master, app, canvas_node):
super().__init__(
master, app, f"{canvas_node.core_node.name} EMANE Configuration", modal=True
)
self.app = app
self.canvas_node = canvas_node
self.node = canvas_node.core_node
self.radiovar = tk.IntVar()
self.radiovar.set(1)
self.emane_models = [x.split("_")[1] for x in self.app.core.emane_models]
self.emane_model = tk.StringVar(value=self.node.emane.split("_")[1])
self.emane_model_button = None
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.draw_emane_configuration()
self.draw_emane_models()
self.draw_emane_buttons()
self.draw_spacer()
self.draw_apply_and_cancel()
def draw_emane_configuration(self):
"""
draw the main frame for emane configuration
:return: nothing
"""
label = ttk.Label(
self.top,
text="The EMANE emulation system provides more complex wireless radio emulation "
"\nusing pluggable MAC and PHY modules. Refer to the wiki for configuration option details",
justify=tk.CENTER,
)
label.grid(pady=PADY)
image = Images.get(ImageEnum.EDITNODE, 16)
button = ttk.Button(
self.top,
image=image,
text="EMANE Wiki",
compound=tk.RIGHT,
command=lambda: webbrowser.open_new(
"https://github.com/adjacentlink/emane/wiki"
),
)
button.image = image
button.grid(sticky="ew", pady=PADY)
def draw_emane_models(self):
"""
create a combobox that has all the known emane models
:return: nothing
"""
frame = ttk.Frame(self.top)
frame.grid(sticky="ew", pady=PADY)
frame.columnconfigure(1, weight=1)
label = ttk.Label(frame, text="Model")
label.grid(row=0, column=0, sticky="w")
# create combo box and its binding
combobox = ttk.Combobox(
frame,
textvariable=self.emane_model,
values=self.emane_models,
state="readonly",
)
combobox.grid(row=0, column=1, sticky="ew")
combobox.bind("<<ComboboxSelected>>", self.emane_model_change)
def draw_emane_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="ew", pady=PADY)
for i in range(2):
frame.columnconfigure(i, weight=1)
image = Images.get(ImageEnum.EDITNODE, 16)
self.emane_model_button = ttk.Button(
frame,
text=f"{self.emane_model.get()} options",
image=image,
compound=tk.RIGHT,
command=self.click_model_config,
)
self.emane_model_button.image = image
self.emane_model_button.grid(row=0, column=0, padx=PADX, sticky="ew")
image = Images.get(ImageEnum.EDITNODE, 16)
button = ttk.Button(
frame,
text="EMANE options",
image=image,
compound=tk.RIGHT,
command=self.click_emane_config,
)
button.image = image
button.grid(row=0, column=1, sticky="ew")
def draw_apply_and_cancel(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
for i in range(2):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Apply", command=self.click_apply)
button.grid(row=0, column=0, padx=PADX, sticky="ew")
button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="ew")
def click_emane_config(self):
dialog = GlobalEmaneDialog(self, self.app)
dialog.show()
def click_model_config(self):
"""
draw emane model configuration
:return: nothing
"""
model_name = self.emane_model.get()
logging.info("configuring emane model: %s", model_name)
dialog = EmaneModelDialog(
self, self.app, self.canvas_node.core_node, model_name
)
dialog.show()
def emane_model_change(self, event):
"""
update emane model options button
:param event:
:return: nothing
"""
model_name = self.emane_model.get()
self.emane_model_button.config(text=f"{model_name} options")
def click_apply(self):
self.node.emane = f"emane_{self.emane_model.get()}"
self.destroy()

View file

@ -0,0 +1,152 @@
import tkinter as tk
from tkinter import ttk
from core.api.grpc import core_pb2
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY
from core.gui.widgets import CodeText, ListboxScroll
class HookDialog(Dialog):
def __init__(self, master, app):
super().__init__(master, app, "Hook", modal=True)
self.name = tk.StringVar()
self.codetext = None
self.hook = core_pb2.Hook()
self.state = tk.StringVar()
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(1, weight=1)
# name and states
frame = ttk.Frame(self.top)
frame.grid(sticky="ew", pady=PADY)
frame.columnconfigure(0, weight=2)
frame.columnconfigure(1, weight=7)
frame.columnconfigure(2, weight=1)
label = ttk.Label(frame, text="Name")
label.grid(row=0, column=0, sticky="ew", padx=PADX)
entry = ttk.Entry(frame, textvariable=self.name)
entry.grid(row=0, column=1, sticky="ew", padx=PADX)
values = tuple(x for x in core_pb2.SessionState.Enum.keys() if x != "NONE")
initial_state = core_pb2.SessionState.Enum.Name(core_pb2.SessionState.RUNTIME)
self.state.set(initial_state)
self.name.set(f"{initial_state.lower()}_hook.sh")
combobox = ttk.Combobox(
frame, textvariable=self.state, values=values, state="readonly"
)
combobox.grid(row=0, column=2, sticky="ew")
combobox.bind("<<ComboboxSelected>>", self.state_change)
# data
self.codetext = CodeText(self.top)
self.codetext.text.insert(
1.0,
(
"#!/bin/sh\n"
"# session hook script; write commands here to execute on the host at the\n"
"# specified state\n"
),
)
self.codetext.grid(sticky="nsew", pady=PADY)
# button row
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
for i in range(2):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Save", command=lambda: self.save())
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=lambda: self.destroy())
button.grid(row=0, column=1, sticky="ew")
def state_change(self, event):
state_name = self.state.get()
self.name.set(f"{state_name.lower()}_hook.sh")
def set(self, hook):
self.hook = hook
self.name.set(hook.file)
self.codetext.text.delete(1.0, tk.END)
self.codetext.text.insert(tk.END, hook.data)
state_name = core_pb2.SessionState.Enum.Name(hook.state)
self.state.set(state_name)
def save(self):
data = self.codetext.text.get("1.0", tk.END).strip()
state_value = core_pb2.SessionState.Enum.Value(self.state.get())
self.hook.file = self.name.get()
self.hook.data = data
self.hook.state = state_value
self.destroy()
class HooksDialog(Dialog):
def __init__(self, master, app):
super().__init__(master, app, "Hooks", modal=True)
self.listbox = None
self.edit_button = None
self.delete_button = None
self.selected = None
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
listbox_scroll = ListboxScroll(self.top)
listbox_scroll.grid(sticky="nsew", pady=PADY)
self.listbox = listbox_scroll.listbox
self.listbox.bind("<<ListboxSelect>>", self.select)
for hook_file in self.app.core.hooks:
self.listbox.insert(tk.END, hook_file)
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
for i in range(4):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Create", command=self.click_create)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
self.edit_button = ttk.Button(
frame, text="Edit", state=tk.DISABLED, command=self.click_edit
)
self.edit_button.grid(row=0, column=1, sticky="ew", padx=PADX)
self.delete_button = ttk.Button(
frame, text="Delete", state=tk.DISABLED, command=self.click_delete
)
self.delete_button.grid(row=0, column=2, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=lambda: self.destroy())
button.grid(row=0, column=3, sticky="ew")
def click_create(self):
dialog = HookDialog(self, self.app)
dialog.show()
hook = dialog.hook
if hook:
self.app.core.hooks[hook.file] = hook
self.listbox.insert(tk.END, hook.file)
def click_edit(self):
hook = self.app.core.hooks[self.selected]
dialog = HookDialog(self, self.app)
dialog.set(hook)
dialog.show()
def click_delete(self):
del self.app.core.hooks[self.selected]
self.listbox.delete(tk.ANCHOR)
self.edit_button.config(state=tk.DISABLED)
self.delete_button.config(state=tk.DISABLED)
def select(self, event):
if self.listbox.curselection():
index = self.listbox.curselection()[0]
self.selected = self.listbox.get(index)
self.edit_button.config(state=tk.NORMAL)
self.delete_button.config(state=tk.NORMAL)
else:
self.selected = None
self.edit_button.config(state=tk.DISABLED)
self.delete_button.config(state=tk.DISABLED)

View file

@ -0,0 +1,362 @@
"""
link configuration
"""
import logging
import tkinter as tk
from tkinter import ttk
from core.api.grpc import core_pb2
from core.gui.dialogs.colorpicker import ColorPicker
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY
def get_int(var):
value = var.get()
if value != "":
return int(value)
else:
return None
def get_float(var):
value = var.get()
if value != "":
return float(value)
else:
return None
class LinkConfiguration(Dialog):
def __init__(self, master, app, edge):
super().__init__(master, app, "Link Configuration", modal=True)
self.app = app
self.edge = edge
self.is_symmetric = edge.link.options.unidirectional is False
if self.is_symmetric:
self.symmetry_var = tk.StringVar(value=">>")
else:
self.symmetry_var = tk.StringVar(value="<<")
self.bandwidth = tk.StringVar()
self.delay = tk.StringVar()
self.jitter = tk.StringVar()
self.loss = tk.StringVar()
self.duplicate = tk.StringVar()
self.down_bandwidth = tk.StringVar()
self.down_delay = tk.StringVar()
self.down_jitter = tk.StringVar()
self.down_loss = tk.StringVar()
self.down_duplicate = tk.StringVar()
self.color = tk.StringVar(value="#000000")
self.color_button = None
self.width = tk.DoubleVar()
self.load_link_config()
self.symmetric_frame = None
self.asymmetric_frame = None
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
source_name = self.app.canvas.nodes[self.edge.src].core_node.name
dest_name = self.app.canvas.nodes[self.edge.dst].core_node.name
label = ttk.Label(
self.top, text=f"Link from {source_name} to {dest_name}", anchor=tk.CENTER
)
label.grid(row=0, column=0, sticky="ew", pady=PADY)
frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
frame.grid(row=1, column=0, sticky="ew", pady=PADY)
button = ttk.Button(frame, text="Unlimited")
button.grid(row=0, column=0, sticky="ew", padx=PADX)
if self.is_symmetric:
button = ttk.Button(
frame, textvariable=self.symmetry_var, command=self.change_symmetry
)
else:
button = ttk.Button(
frame, textvariable=self.symmetry_var, command=self.change_symmetry
)
button.grid(row=0, column=1, sticky="ew")
if self.is_symmetric:
self.symmetric_frame = self.get_frame()
self.symmetric_frame.grid(row=2, column=0, sticky="ew", pady=PADY)
else:
self.asymmetric_frame = self.get_frame()
self.asymmetric_frame.grid(row=2, column=0, sticky="ew", pady=PADY)
self.draw_spacer(row=3)
frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
frame.grid(row=4, column=0, sticky="ew")
button = ttk.Button(frame, text="Apply", command=self.click_apply)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="ew")
def get_frame(self):
frame = ttk.Frame(self.top)
frame.columnconfigure(1, weight=1)
if self.is_symmetric:
label_name = "Symmetric Link Effects"
else:
label_name = "Asymmetric Effects: Downstream / Upstream "
row = 0
label = ttk.Label(frame, text=label_name, anchor=tk.CENTER)
label.grid(row=row, column=0, columnspan=2, sticky="ew", pady=PADY)
row = row + 1
label = ttk.Label(frame, text="Bandwidth (bps)")
label.grid(row=row, column=0, sticky="ew")
entry = ttk.Entry(
frame,
textvariable=self.bandwidth,
validate="key",
validatecommand=(self.app.validation.positive_int, "%P"),
)
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
if not self.is_symmetric:
entry = ttk.Entry(
frame,
textvariable=self.down_bandwidth,
validate="key",
validatecommand=(self.app.validation.positive_int, "%P"),
)
entry.grid(row=row, column=2, sticky="ew", pady=PADY)
row = row + 1
label = ttk.Label(frame, text="Delay (us)")
label.grid(row=row, column=0, sticky="ew")
entry = ttk.Entry(
frame,
textvariable=self.delay,
validate="key",
validatecommand=(self.app.validation.positive_int, "%P"),
)
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
if not self.is_symmetric:
entry = ttk.Entry(
frame,
textvariable=self.down_delay,
validate="key",
validatecommand=(self.app.validation.positive_int, "%P"),
)
entry.grid(row=row, column=2, sticky="ew", pady=PADY)
row = row + 1
label = ttk.Label(frame, text="Jitter (us)")
label.grid(row=row, column=0, sticky="ew")
entry = ttk.Entry(
frame,
textvariable=self.jitter,
validate="key",
validatecommand=(self.app.validation.positive_int, "%P"),
)
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
if not self.is_symmetric:
entry = ttk.Entry(
frame,
textvariable=self.down_jitter,
validate="key",
validatecommand=(self.app.validation.positive_int, "%P"),
)
entry.grid(row=row, column=2, sticky="ew", pady=PADY)
row = row + 1
label = ttk.Label(frame, text="Loss (%)")
label.grid(row=row, column=0, sticky="ew")
entry = ttk.Entry(
frame,
textvariable=self.loss,
validate="key",
validatecommand=(self.app.validation.positive_float, "%P"),
)
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
if not self.is_symmetric:
entry = ttk.Entry(
frame,
textvariable=self.down_loss,
validate="key",
validatecommand=(self.app.validation.positive_float, "%P"),
)
entry.grid(row=row, column=2, sticky="ew", pady=PADY)
row = row + 1
label = ttk.Label(frame, text="Duplicate (%)")
label.grid(row=row, column=0, sticky="ew")
entry = ttk.Entry(
frame,
textvariable=self.duplicate,
validate="key",
validatecommand=(self.app.validation.positive_int, "%P"),
)
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
if not self.is_symmetric:
entry = ttk.Entry(
frame,
textvariable=self.down_duplicate,
validate="key",
validatecommand=(self.app.validation.positive_int, "%P"),
)
entry.grid(row=row, column=2, sticky="ew", pady=PADY)
row = row + 1
label = ttk.Label(frame, text="Color")
label.grid(row=row, column=0, sticky="ew")
self.color_button = tk.Button(
frame,
textvariable=self.color,
background=self.color.get(),
bd=0,
relief=tk.FLAT,
highlightthickness=0,
command=self.click_color,
)
self.color_button.grid(row=row, column=1, sticky="ew", pady=PADY)
row = row + 1
label = ttk.Label(frame, text="Width")
label.grid(row=row, column=0, sticky="ew")
entry = ttk.Entry(
frame,
textvariable=self.width,
validate="key",
validatecommand=(self.app.validation.positive_float, "%P"),
)
entry.grid(row=row, column=1, sticky="ew", pady=PADY)
return frame
def click_color(self):
dialog = ColorPicker(self, self.app, self.color.get())
color = dialog.askcolor()
self.color.set(color)
self.color_button.config(background=color)
def click_apply(self):
logging.debug("click apply")
self.app.canvas.itemconfigure(self.edge.id, width=self.width.get())
self.app.canvas.itemconfigure(self.edge.id, fill=self.color.get())
link = self.edge.link
bandwidth = get_int(self.bandwidth)
jitter = get_int(self.jitter)
delay = get_int(self.delay)
duplicate = get_int(self.duplicate)
loss = get_float(self.loss)
options = core_pb2.LinkOptions(
bandwidth=bandwidth, jitter=jitter, delay=delay, dup=duplicate, per=loss
)
link.options.CopyFrom(options)
interface_one = None
if link.HasField("interface_one"):
interface_one = link.interface_one.id
interface_two = None
if link.HasField("interface_two"):
interface_two = link.interface_two.id
if not self.is_symmetric:
link.options.unidirectional = True
asym_interface_one = None
if interface_one:
asym_interface_one = core_pb2.Interface(id=interface_one)
asym_interface_two = None
if interface_two:
asym_interface_two = core_pb2.Interface(id=interface_two)
down_bandwidth = get_int(self.down_bandwidth)
down_jitter = get_int(self.down_jitter)
down_delay = get_int(self.down_delay)
down_duplicate = get_int(self.down_duplicate)
down_loss = get_float(self.down_loss)
options = core_pb2.LinkOptions(
bandwidth=down_bandwidth,
jitter=down_jitter,
delay=down_delay,
dup=down_duplicate,
per=down_loss,
unidirectional=True,
)
self.edge.asymmetric_link = core_pb2.Link(
node_one_id=link.node_two_id,
node_two_id=link.node_one_id,
interface_one=asym_interface_one,
interface_two=asym_interface_two,
options=options,
)
else:
link.options.unidirectional = False
self.edge.asymmetric_link = None
if self.app.core.is_runtime() and link.HasField("options"):
session_id = self.app.core.session_id
self.app.core.client.edit_link(
session_id,
link.node_one_id,
link.node_two_id,
link.options,
interface_one,
interface_two,
)
if self.edge.asymmetric_link:
self.app.core.client.edit_link(
session_id,
link.node_two_id,
link.node_one_id,
self.edge.asymmetric_link.options,
interface_one,
interface_two,
)
self.destroy()
def change_symmetry(self):
logging.debug("change symmetry")
if self.is_symmetric:
self.is_symmetric = False
self.symmetry_var.set("<<")
if not self.asymmetric_frame:
self.asymmetric_frame = self.get_frame()
self.symmetric_frame.grid_forget()
self.asymmetric_frame.grid(row=2, column=0)
else:
self.is_symmetric = True
self.symmetry_var.set(">>")
if not self.symmetric_frame:
self.symmetric_frame = self.get_frame()
self.asymmetric_frame.grid_forget()
self.symmetric_frame.grid(row=2, column=0)
def load_link_config(self):
"""
populate link config to the table
:return: nothing
"""
width = self.app.canvas.itemcget(self.edge.id, "width")
self.width.set(width)
color = self.app.canvas.itemcget(self.edge.id, "fill")
self.color.set(color)
link = self.edge.link
if link.HasField("options"):
self.bandwidth.set(str(link.options.bandwidth))
self.jitter.set(str(link.options.jitter))
self.duplicate.set(str(link.options.dup))
self.loss.set(str(link.options.per))
self.delay.set(str(link.options.delay))
if not self.is_symmetric:
asym_link = self.edge.asymmetric_link
self.down_bandwidth.set(str(asym_link.options.bandwidth))
self.down_jitter.set(str(asym_link.options.jitter))
self.down_duplicate.set(str(asym_link.options.dup))
self.down_loss.set(str(asym_link.options.per))
self.down_delay.set(str(asym_link.options.delay))

View file

@ -0,0 +1,73 @@
"""
marker dialog
"""
import logging
import tkinter as tk
from tkinter import ttk
from core.gui.dialogs.colorpicker import ColorPicker
from core.gui.dialogs.dialog import Dialog
MARKER_THICKNESS = [3, 5, 8, 10]
class Marker(Dialog):
def __init__(self, master, app, initcolor="#000000"):
super().__init__(master, app, "marker tool", modal=False)
self.app = app
self.color = initcolor
self.radius = MARKER_THICKNESS[0]
self.marker_thickness = tk.IntVar(value=MARKER_THICKNESS[0])
self.draw()
self.top.bind("<Destroy>", self.close_marker)
def draw(self):
button = ttk.Button(self.top, text="clear", command=self.clear_marker)
button.grid(row=0, column=0, sticky="nsew")
frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=4)
frame.grid(row=1, column=0, sticky="nsew")
label = ttk.Label(frame, text="Thickness: ")
label.grid(row=0, column=0, sticky="nsew")
combobox = ttk.Combobox(
frame,
textvariable=self.marker_thickness,
values=MARKER_THICKNESS,
state="readonly",
)
combobox.grid(row=0, column=1, sticky="nsew")
combobox.bind("<<ComboboxSelected>>", self.change_thickness)
frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=4)
frame.grid(row=2, column=0, sticky="nsew")
label = ttk.Label(frame, text="Color: ")
label.grid(row=0, column=0, sticky="nsew")
label = ttk.Label(frame, background=self.color)
label.grid(row=0, column=1, sticky="nsew")
label.bind("<Button-1>", self.change_color)
def clear_marker(self):
canvas = self.app.canvas
for i in canvas.find_withtag("marker"):
canvas.delete(i)
def change_color(self, event):
color_picker = ColorPicker(self, self.app, self.color)
color = color_picker.askcolor()
event.widget.configure(background=color)
self.color = color
def change_thickness(self, event):
self.radius = self.marker_thickness.get()
def close_marker(self, event):
logging.debug("destroy marker dialog")
self.app.toolbar.marker_tool = None
def position(self):
print(self.winfo_width(), self.winfo_height())
self.geometry("+{}+{}".format(self.app.master.winfo_x, self.app.master.winfo_y))

View file

@ -0,0 +1,55 @@
"""
mobility configuration
"""
from tkinter import ttk
import grpc
from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame
class MobilityConfigDialog(Dialog):
def __init__(self, master, app, canvas_node):
super().__init__(
master,
app,
f"{canvas_node.core_node.name} Mobility Configuration",
modal=True,
)
self.canvas_node = canvas_node
self.node = canvas_node.core_node
self.config_frame = None
try:
self.config = self.app.core.get_mobility_config(self.node.id)
except grpc.RpcError as e:
show_grpc_error(e)
self.destroy()
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
self.config_frame = ConfigFrame(self.top, self.app, self.config)
self.config_frame.draw_config()
self.config_frame.grid(sticky="nsew", pady=PADY)
self.draw_apply_buttons()
def draw_apply_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
for i in range(2):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Apply", command=self.click_apply)
button.grid(row=0, column=0, padx=PADX, sticky="ew")
button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="ew")
def click_apply(self):
self.config_frame.parse_config()
self.app.core.mobility_configs[self.node.id] = self.config
self.destroy()

View file

@ -0,0 +1,163 @@
import tkinter as tk
from tkinter import ttk
import grpc
from core.api.grpc.core_pb2 import MobilityAction
from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.images import ImageEnum, Images
from core.gui.themes import PADX, PADY
ICON_SIZE = 16
class MobilityPlayer:
def __init__(self, master, app, canvas_node, config):
self.master = master
self.app = app
self.canvas_node = canvas_node
self.config = config
self.dialog = None
self.state = None
def show(self):
if self.dialog:
self.dialog.destroy()
self.dialog = MobilityPlayerDialog(
self.master, self.app, self.canvas_node, self.config
)
self.dialog.protocol("WM_DELETE_WINDOW", self.handle_close)
if self.state == MobilityAction.START:
self.set_play()
elif self.state == MobilityAction.PAUSE:
self.set_pause()
else:
self.set_stop()
self.dialog.show()
def handle_close(self):
self.dialog.destroy()
self.dialog = None
def set_play(self):
self.state = MobilityAction.START
if self.dialog:
self.dialog.set_play()
def set_pause(self):
self.state = MobilityAction.PAUSE
if self.dialog:
self.dialog.set_pause()
def set_stop(self):
self.state = MobilityAction.STOP
if self.dialog:
self.dialog.set_stop()
class MobilityPlayerDialog(Dialog):
def __init__(self, master, app, canvas_node, config):
super().__init__(
master, app, f"{canvas_node.core_node.name} Mobility Player", modal=False
)
self.resizable(False, False)
self.geometry("")
self.canvas_node = canvas_node
self.node = canvas_node.core_node
self.config = config
self.play_button = None
self.pause_button = None
self.stop_button = None
self.progressbar = None
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
file_name = self.config["file"].value
label = ttk.Label(self.top, text=file_name)
label.grid(sticky="ew", pady=PADY)
self.progressbar = ttk.Progressbar(self.top, mode="indeterminate")
self.progressbar.grid(sticky="ew", pady=PADY)
frame = ttk.Frame(self.top)
frame.grid(sticky="ew", pady=PADY)
for i in range(3):
frame.columnconfigure(i, weight=1)
image = Images.get(ImageEnum.START, width=ICON_SIZE)
self.play_button = ttk.Button(frame, image=image, command=self.click_play)
self.play_button.image = image
self.play_button.grid(row=0, column=0, sticky="ew", padx=PADX)
image = Images.get(ImageEnum.PAUSE, width=ICON_SIZE)
self.pause_button = ttk.Button(frame, image=image, command=self.click_pause)
self.pause_button.image = image
self.pause_button.grid(row=0, column=1, sticky="ew", padx=PADX)
image = Images.get(ImageEnum.STOP, width=ICON_SIZE)
self.stop_button = ttk.Button(frame, image=image, command=self.click_stop)
self.stop_button.image = image
self.stop_button.grid(row=0, column=2, sticky="ew", padx=PADX)
loop = tk.IntVar(value=int(self.config["loop"].value == "1"))
checkbutton = ttk.Checkbutton(
frame, text="Loop?", variable=loop, state=tk.DISABLED
)
checkbutton.grid(row=0, column=3, padx=PADX)
rate = self.config["refresh_ms"].value
label = ttk.Label(frame, text=f"rate {rate} ms")
label.grid(row=0, column=4)
def clear_buttons(self):
self.play_button.state(["!pressed"])
self.pause_button.state(["!pressed"])
self.stop_button.state(["!pressed"])
def set_play(self):
self.clear_buttons()
self.play_button.state(["pressed"])
self.progressbar.start()
def set_pause(self):
self.clear_buttons()
self.pause_button.state(["pressed"])
self.progressbar.stop()
def set_stop(self):
self.clear_buttons()
self.stop_button.state(["pressed"])
self.progressbar.stop()
def click_play(self):
self.set_play()
session_id = self.app.core.session_id
try:
self.app.core.client.mobility_action(
session_id, self.node.id, MobilityAction.START
)
except grpc.RpcError as e:
show_grpc_error(e)
def click_pause(self):
self.set_pause()
session_id = self.app.core.session_id
try:
self.app.core.client.mobility_action(
session_id, self.node.id, MobilityAction.PAUSE
)
except grpc.RpcError as e:
show_grpc_error(e)
def click_stop(self):
self.set_stop()
session_id = self.app.core.session_id
try:
self.app.core.client.mobility_action(
session_id, self.node.id, MobilityAction.STOP
)
except grpc.RpcError as e:
show_grpc_error(e)

View file

@ -0,0 +1,237 @@
import logging
import tkinter as tk
from functools import partial
from tkinter import ttk
from core.gui import nodeutils
from core.gui.appconfig import ICONS_PATH
from core.gui.dialogs.dialog import Dialog
from core.gui.dialogs.emaneconfig import EmaneModelDialog
from core.gui.images import Images
from core.gui.nodeutils import NodeUtils
from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import image_chooser
def mac_auto(is_auto, entry):
logging.info("mac auto clicked")
if is_auto.get():
logging.info("disabling mac")
entry.var.set("")
entry.config(state=tk.DISABLED)
else:
entry.var.set("00:00:00:00:00:00")
entry.config(state=tk.NORMAL)
class InterfaceData:
def __init__(self, is_auto, mac, ip4, ip6):
self.is_auto = is_auto
self.mac = mac
self.ip4 = ip4
self.ip6 = ip6
class NodeConfigDialog(Dialog):
def __init__(self, master, app, canvas_node):
"""
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__(
master, app, f"{canvas_node.core_node.name} Configuration", modal=True
)
self.canvas_node = canvas_node
self.node = canvas_node.core_node
self.image = canvas_node.image
self.image_file = None
self.image_button = None
self.name = tk.StringVar(value=self.node.name)
self.type = tk.StringVar(value=self.node.model)
self.container_image = tk.StringVar(value=self.node.image)
server = "localhost"
if self.node.server:
server = self.node.server
self.server = tk.StringVar(value=server)
self.interfaces = {}
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
row = 0
# field frame
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
frame.columnconfigure(1, weight=1)
# icon field
label = ttk.Label(frame, text="Icon")
label.grid(row=row, column=0, sticky="ew", padx=PADX, pady=PADY)
self.image_button = ttk.Button(
frame,
text="Icon",
image=self.image,
compound=tk.NONE,
command=self.click_icon,
)
self.image_button.grid(row=row, column=1, sticky="ew")
row += 1
# name field
label = ttk.Label(frame, text="Name")
label.grid(row=row, column=0, sticky="ew", padx=PADX, pady=PADY)
entry = ttk.Entry(
frame,
textvariable=self.name,
validate="key",
validatecommand=(self.app.validation.name, "%P"),
)
entry.bind(
"<FocusOut>", lambda event: self.app.validation.focus_out(event, "noname")
)
entry.grid(row=row, column=1, sticky="ew")
row += 1
# node type field
if NodeUtils.is_model_node(self.node.type):
label = ttk.Label(frame, text="Type")
label.grid(row=row, column=0, sticky="ew", padx=PADX, pady=PADY)
combobox = ttk.Combobox(
frame,
textvariable=self.type,
values=list(NodeUtils.NODE_MODELS),
state="readonly",
)
combobox.grid(row=row, column=1, sticky="ew")
row += 1
# container image field
if NodeUtils.is_image_node(self.node.type):
label = ttk.Label(frame, text="Image")
label.grid(row=row, column=0, sticky="ew", padx=PADX, pady=PADY)
entry = ttk.Entry(frame, textvariable=self.container_image)
entry.grid(row=row, column=1, sticky="ew")
row += 1
if NodeUtils.is_container_node(self.node.type):
# server
frame.grid(sticky="ew")
frame.columnconfigure(1, weight=1)
label = ttk.Label(frame, text="Server")
label.grid(row=row, column=0, sticky="ew", padx=PADX, pady=PADY)
servers = ["localhost"]
servers.extend(list(sorted(self.app.core.servers.keys())))
combobox = ttk.Combobox(
frame, textvariable=self.server, values=servers, state="readonly"
)
combobox.grid(row=row, column=1, sticky="ew")
row += 1
# interfaces
if self.canvas_node.interfaces:
self.draw_interfaces()
self.draw_spacer()
self.draw_buttons()
def draw_interfaces(self):
notebook = ttk.Notebook(self.top)
notebook.grid(sticky="nsew", pady=PADY)
self.top.rowconfigure(notebook.grid_info()["row"], weight=1)
for interface in self.canvas_node.interfaces:
logging.info("interface: %s", interface)
tab = ttk.Frame(notebook, padding=FRAME_PAD)
tab.grid(sticky="nsew", pady=PADY)
tab.columnconfigure(1, weight=1)
tab.columnconfigure(2, weight=1)
notebook.add(tab, text=interface.name)
row = 0
emane_node = self.canvas_node.has_emane_link(interface.id)
if emane_node:
emane_model = emane_node.emane.split("_")[1]
button = ttk.Button(
tab,
text=f"Configure EMANE {emane_model}",
command=lambda: self.click_emane_config(emane_model, interface.id),
)
button.grid(row=row, sticky="ew", columnspan=3, pady=PADY)
row += 1
label = ttk.Label(tab, text="MAC")
label.grid(row=row, column=0, padx=PADX, pady=PADY)
is_auto = tk.BooleanVar(value=True)
checkbutton = ttk.Checkbutton(tab, text="Auto?", variable=is_auto)
checkbutton.var = is_auto
checkbutton.grid(row=row, column=1, padx=PADX)
mac = tk.StringVar(value=interface.mac)
entry = ttk.Entry(tab, textvariable=mac, state=tk.DISABLED)
entry.grid(row=row, column=2, sticky="ew")
func = partial(mac_auto, is_auto, entry)
checkbutton.config(command=func)
row += 1
label = ttk.Label(tab, text="IPv4")
label.grid(row=row, column=0, padx=PADX, pady=PADY)
ip4 = tk.StringVar(value=f"{interface.ip4}/{interface.ip4mask}")
entry = ttk.Entry(tab, textvariable=ip4)
entry.bind("<FocusOut>", self.app.validation.ip_focus_out)
entry.grid(row=row, column=1, columnspan=2, sticky="ew")
row += 1
label = ttk.Label(tab, text="IPv6")
label.grid(row=row, column=0, padx=PADX, pady=PADY)
ip6 = tk.StringVar(value=f"{interface.ip6}/{interface.ip6mask}")
entry = ttk.Entry(tab, textvariable=ip6)
entry.bind("<FocusOut>", self.app.validation.ip_focus_out)
entry.grid(row=row, column=1, columnspan=2, sticky="ew")
self.interfaces[interface.id] = InterfaceData(is_auto, mac, ip4, ip6)
def draw_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
button = ttk.Button(frame, text="Apply", command=self.config_apply)
button.grid(row=0, column=0, padx=PADX, sticky="ew")
button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="ew")
def click_emane_config(self, emane_model, interface_id):
dialog = EmaneModelDialog(self, self.app, self.node, emane_model, interface_id)
dialog.show()
def click_icon(self):
file_path = image_chooser(self, ICONS_PATH)
if file_path:
self.image = Images.create(file_path, nodeutils.ICON_SIZE)
self.image_button.config(image=self.image)
self.image_file = file_path
def config_apply(self):
# update core node
self.node.name = self.name.get()
if NodeUtils.is_image_node(self.node.type):
self.node.image = self.container_image.get()
server = self.server.get()
if NodeUtils.is_container_node(self.node.type) and server != "localhost":
self.node.server = server
# set custom icon
if self.image_file:
self.node.icon = self.image_file
# update canvas node
self.canvas_node.image = self.image
# redraw
self.canvas_node.redraw()
self.destroy()

View file

@ -0,0 +1,134 @@
"""
core node services
"""
import tkinter as tk
from tkinter import messagebox, ttk
from core.gui.dialogs.dialog import Dialog
from core.gui.dialogs.serviceconfiguration import ServiceConfiguration
from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import CheckboxList, ListboxScroll
class NodeService(Dialog):
def __init__(self, master, app, canvas_node, services=None):
title = f"{canvas_node.core_node.name} Services"
super().__init__(master, app, title, modal=True)
self.app = app
self.canvas_node = canvas_node
self.node_id = canvas_node.core_node.id
self.groups = None
self.services = None
self.current = None
if services is None:
services = canvas_node.core_node.services
model = canvas_node.core_node.model
if len(services) == 0:
services = set(self.app.core.default_services[model])
else:
services = set(services)
self.current_services = services
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
frame = ttk.Frame(self.top)
frame.grid(stick="nsew", pady=PADY)
frame.rowconfigure(0, weight=1)
for i in range(3):
frame.columnconfigure(i, weight=1)
label_frame = ttk.LabelFrame(frame, text="Groups", padding=FRAME_PAD)
label_frame.grid(row=0, column=0, sticky="nsew")
label_frame.rowconfigure(0, weight=1)
label_frame.columnconfigure(0, weight=1)
self.groups = ListboxScroll(label_frame)
self.groups.grid(sticky="nsew")
for group in sorted(self.app.core.services):
self.groups.listbox.insert(tk.END, group)
self.groups.listbox.bind("<<ListboxSelect>>", self.handle_group_change)
self.groups.listbox.selection_set(0)
label_frame = ttk.LabelFrame(frame, text="Services")
label_frame.grid(row=0, column=1, sticky="nsew")
label_frame.columnconfigure(0, weight=1)
label_frame.rowconfigure(0, weight=1)
self.services = CheckboxList(
label_frame, self.app, clicked=self.service_clicked, padding=FRAME_PAD
)
self.services.grid(sticky="nsew")
label_frame = ttk.LabelFrame(frame, text="Selected", padding=FRAME_PAD)
label_frame.grid(row=0, column=2, sticky="nsew")
label_frame.rowconfigure(0, weight=1)
label_frame.columnconfigure(0, weight=1)
self.current = ListboxScroll(label_frame)
self.current.grid(sticky="nsew")
for service in sorted(self.current_services):
self.current.listbox.insert(tk.END, service)
frame = ttk.Frame(self.top)
frame.grid(stick="ew")
for i in range(3):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Configure", command=self.click_configure)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Save", command=self.click_save)
button.grid(row=0, column=1, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=self.click_cancel)
button.grid(row=0, column=2, sticky="ew")
# trigger group change
self.groups.listbox.event_generate("<<ListboxSelect>>")
def handle_group_change(self, event):
selection = self.groups.listbox.curselection()
if selection:
index = selection[0]
group = self.groups.listbox.get(index)
self.services.clear()
for name in sorted(self.app.core.services[group]):
checked = name in self.current_services
self.services.add(name, checked)
def service_clicked(self, name, var):
if var.get() and name not in self.current_services:
self.current_services.add(name)
elif not var.get() and name in self.current_services:
self.current_services.remove(name)
self.current.listbox.delete(0, tk.END)
for name in sorted(self.current_services):
self.current.listbox.insert(tk.END, name)
self.canvas_node.core_node.services[:] = self.current_services
def click_configure(self):
current_selection = self.current.listbox.curselection()
if len(current_selection):
dialog = ServiceConfiguration(
master=self,
app=self.app,
service_name=self.current.listbox.get(current_selection[0]),
node_id=self.node_id,
)
dialog.show()
else:
messagebox.showinfo(
"Node service configuration", "Select a service to configure"
)
def click_save(self):
if (
self.current_services
!= self.app.core.default_services[self.canvas_node.core_node.model]
):
self.canvas_node.core_node.services[:] = self.current_services
else:
if len(self.canvas_node.core_node.services) > 0:
self.canvas_node.core_node.services[:] = []
self.destroy()
def click_cancel(self):
self.current_services = None
self.destroy()

View file

@ -0,0 +1,143 @@
import tkinter as tk
from tkinter import ttk
from core.gui.coreclient import Observer
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY
from core.gui.widgets import ListboxScroll
class ObserverDialog(Dialog):
def __init__(self, master, app):
super().__init__(master, app, "Observer Widgets", modal=True)
self.observers = None
self.save_button = None
self.delete_button = None
self.selected = None
self.selected_index = None
self.name = tk.StringVar()
self.cmd = tk.StringVar()
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
self.draw_listbox()
self.draw_form_fields()
self.draw_config_buttons()
self.draw_apply_buttons()
def draw_listbox(self):
listbox_scroll = ListboxScroll(self.top)
listbox_scroll.grid(sticky="nsew", pady=PADY)
listbox_scroll.columnconfigure(0, weight=1)
listbox_scroll.rowconfigure(0, weight=1)
self.observers = listbox_scroll.listbox
self.observers.grid(row=0, column=0, sticky="nsew")
self.observers.bind("<<ListboxSelect>>", self.handle_observer_change)
for name in sorted(self.app.core.custom_observers):
self.observers.insert(tk.END, name)
def draw_form_fields(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="ew", pady=PADY)
frame.columnconfigure(1, weight=1)
label = ttk.Label(frame, text="Name")
label.grid(row=0, column=0, sticky="w", padx=PADX)
entry = ttk.Entry(frame, textvariable=self.name)
entry.grid(row=0, column=1, sticky="ew")
label = ttk.Label(frame, text="Command")
label.grid(row=1, column=0, sticky="w", padx=PADX)
entry = ttk.Entry(frame, textvariable=self.cmd)
entry.grid(row=1, column=1, sticky="ew")
def draw_config_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="ew", pady=PADY)
for i in range(3):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Create", command=self.click_create)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
self.save_button = ttk.Button(
frame, text="Save", state=tk.DISABLED, command=self.click_save
)
self.save_button.grid(row=0, column=1, sticky="ew", padx=PADX)
self.delete_button = ttk.Button(
frame, text="Delete", state=tk.DISABLED, command=self.click_delete
)
self.delete_button.grid(row=0, column=2, sticky="ew")
def draw_apply_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
for i in range(2):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Save", command=self.click_save_config)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="ew")
def click_save_config(self):
observers = []
for name in sorted(self.app.core.custom_observers):
observer = self.app.core.custom_observers[name]
observers.append({"name": observer.name, "cmd": observer.cmd})
self.app.guiconfig["observers"] = observers
self.app.save_config()
self.destroy()
def click_create(self):
name = self.name.get()
if name not in self.app.core.custom_observers:
cmd = self.cmd.get()
observer = Observer(name, cmd)
self.app.core.custom_observers[name] = observer
self.observers.insert(tk.END, name)
def click_save(self):
name = self.name.get()
if self.selected:
previous_name = self.selected
self.selected = name
observer = self.app.core.custom_observers.pop(previous_name)
observer.name = name
observer.cmd = self.cmd.get()
self.app.core.custom_observers[name] = observer
self.observers.delete(self.selected_index)
self.observers.insert(self.selected_index, name)
self.observers.selection_set(self.selected_index)
def click_delete(self):
if self.selected:
self.observers.delete(self.selected_index)
del self.app.core.custom_observers[self.selected]
self.selected = None
self.selected_index = None
self.name.set("")
self.cmd.set("")
self.observers.selection_clear(0, tk.END)
self.save_button.config(state=tk.DISABLED)
self.delete_button.config(state=tk.DISABLED)
def handle_observer_change(self, event):
selection = self.observers.curselection()
if selection:
self.selected_index = selection[0]
self.selected = self.observers.get(self.selected_index)
observer = self.app.core.custom_observers[self.selected]
self.name.set(observer.name)
self.cmd.set(observer.cmd)
self.save_button.config(state=tk.NORMAL)
self.delete_button.config(state=tk.NORMAL)
else:
self.selected_index = None
self.selected = None
self.save_button.config(state=tk.DISABLED)
self.delete_button.config(state=tk.DISABLED)

View file

@ -0,0 +1,87 @@
import logging
import tkinter as tk
from tkinter import ttk
from core.gui import appconfig
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import FRAME_PAD, PADX, PADY
class PreferencesDialog(Dialog):
def __init__(self, master, app):
super().__init__(master, app, "Preferences", modal=True)
preferences = self.app.guiconfig["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.top.rowconfigure(0, weight=1)
self.draw_preferences()
self.draw_buttons()
def draw_preferences(self):
frame = ttk.LabelFrame(self.top, text="Preferences", padding=FRAME_PAD)
frame.grid(sticky="nsew", pady=PADY)
frame.columnconfigure(1, weight=1)
label = ttk.Label(frame, text="Theme")
label.grid(row=0, column=0, pady=PADY, padx=PADX, 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("<<ComboboxSelected>>", self.theme_change)
label = ttk.Label(frame, text="Editor")
label.grid(row=1, column=0, pady=PADY, padx=PADX, sticky="w")
combobox = ttk.Combobox(
frame, textvariable=self.editor, values=appconfig.EDITORS, state="readonly"
)
combobox.grid(row=1, column=1, sticky="ew")
label = ttk.Label(frame, text="Terminal")
label.grid(row=2, column=0, pady=PADY, padx=PADX, sticky="w")
combobox = ttk.Combobox(
frame,
textvariable=self.terminal,
values=appconfig.TERMINALS,
state="readonly",
)
combobox.grid(row=2, column=1, sticky="ew")
label = ttk.Label(frame, text="3D GUI")
label.grid(row=3, column=0, pady=PADY, padx=PADX, sticky="w")
entry = ttk.Entry(frame, textvariable=self.gui3d)
entry.grid(row=3, column=1, sticky="ew")
def draw_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
for i in range(2):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Save", command=self.click_save)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
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.guiconfig["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()

View file

@ -0,0 +1,173 @@
import tkinter as tk
from tkinter import ttk
from core.gui.coreclient import CoreServer
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import ListboxScroll
DEFAULT_NAME = "example"
DEFAULT_ADDRESS = "127.0.0.1"
DEFAULT_PORT = 50051
class ServersDialog(Dialog):
def __init__(self, master, app):
super().__init__(master, app, "CORE Servers", modal=True)
self.name = tk.StringVar(value=DEFAULT_NAME)
self.address = tk.StringVar(value=DEFAULT_ADDRESS)
self.port = tk.IntVar(value=DEFAULT_PORT)
self.servers = None
self.selected_index = None
self.selected = None
self.save_button = None
self.delete_button = None
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
self.draw_servers()
self.draw_servers_buttons()
self.draw_server_configuration()
self.draw_apply_buttons()
def draw_servers(self):
listbox_scroll = ListboxScroll(self.top)
listbox_scroll.grid(pady=PADY, sticky="nsew")
listbox_scroll.columnconfigure(0, weight=1)
listbox_scroll.rowconfigure(0, weight=1)
self.servers = listbox_scroll.listbox
self.servers.grid(row=0, column=0, sticky="nsew")
self.servers.bind("<<ListboxSelect>>", self.handle_server_change)
for server in self.app.core.servers:
self.servers.insert(tk.END, server)
def draw_server_configuration(self):
frame = ttk.LabelFrame(self.top, text="Server Configuration", padding=FRAME_PAD)
frame.grid(pady=PADY, sticky="ew")
frame.columnconfigure(1, weight=1)
frame.columnconfigure(3, weight=1)
frame.columnconfigure(5, weight=1)
label = ttk.Label(frame, text="Name")
label.grid(row=0, column=0, sticky="w", padx=PADX, pady=PADY)
entry = ttk.Entry(frame, textvariable=self.name)
entry.grid(row=0, column=1, sticky="ew")
label = ttk.Label(frame, text="Address")
label.grid(row=0, column=2, sticky="w", padx=PADX, pady=PADY)
entry = ttk.Entry(frame, textvariable=self.address)
entry.grid(row=0, column=3, sticky="ew")
label = ttk.Label(frame, text="Port")
label.grid(row=0, column=4, sticky="w", padx=PADX, pady=PADY)
entry = ttk.Entry(
frame,
textvariable=self.port,
validate="key",
validatecommand=(self.app.validation.positive_int, "%P"),
)
entry.bind(
"<FocusOut>", lambda event: self.app.validation.focus_out(event, "50051")
)
entry.grid(row=0, column=5, sticky="ew")
def draw_servers_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(pady=PADY, sticky="ew")
for i in range(3):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Create", command=self.click_create)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
self.save_button = ttk.Button(
frame, text="Save", state=tk.DISABLED, command=self.click_save
)
self.save_button.grid(row=0, column=1, sticky="ew", padx=PADX)
self.delete_button = ttk.Button(
frame, text="Delete", state=tk.DISABLED, command=self.click_delete
)
self.delete_button.grid(row=0, column=2, sticky="ew")
def draw_apply_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
for i in range(2):
frame.columnconfigure(i, weight=1)
button = ttk.Button(
frame, text="Save Configuration", command=self.click_save_configuration
)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="ew")
def click_save_configuration(self):
servers = []
for name in sorted(self.app.core.servers):
server = self.app.core.servers[name]
servers.append(
{"name": server.name, "address": server.address, "port": server.port}
)
self.app.guiconfig["servers"] = servers
self.app.save_config()
self.destroy()
def click_create(self):
name = self.name.get()
if name not in self.app.core.servers:
address = self.address.get()
port = self.port.get()
server = CoreServer(name, address, port)
self.app.core.servers[name] = server
self.servers.insert(tk.END, name)
def click_save(self):
name = self.name.get()
if self.selected:
previous_name = self.selected
self.selected = name
server = self.app.core.servers.pop(previous_name)
server.name = name
server.address = self.address.get()
server.port = self.port.get()
self.app.core.servers[name] = server
self.servers.delete(self.selected_index)
self.servers.insert(self.selected_index, name)
self.servers.selection_set(self.selected_index)
def click_delete(self):
if self.selected:
self.servers.delete(self.selected_index)
del self.app.core.servers[self.selected]
self.selected = None
self.selected_index = None
self.name.set(DEFAULT_NAME)
self.address.set(DEFAULT_ADDRESS)
self.port.set(DEFAULT_PORT)
self.servers.selection_clear(0, tk.END)
self.save_button.config(state=tk.DISABLED)
self.delete_button.config(state=tk.DISABLED)
def handle_server_change(self, event):
selection = self.servers.curselection()
if selection:
self.selected_index = selection[0]
self.selected = self.servers.get(self.selected_index)
server = self.app.core.servers[self.selected]
self.name.set(server.name)
self.address.set(server.address)
self.port.set(server.port)
self.save_button.config(state=tk.NORMAL)
self.delete_button.config(state=tk.NORMAL)
else:
self.selected_index = None
self.selected = None
self.save_button.config(state=tk.DISABLED)
self.delete_button.config(state=tk.DISABLED)

View file

@ -0,0 +1,453 @@
"Service configuration dialog"
import logging
import tkinter as tk
from tkinter import ttk
import grpc
from core.api.grpc import core_pb2
from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.images import ImageEnum, Images
from core.gui.themes import FRAME_PAD, PADX, PADY
from core.gui.widgets import CodeText, ListboxScroll
class ServiceConfiguration(Dialog):
def __init__(self, master, app, service_name, node_id):
title = f"{service_name} Service"
super().__init__(master, app, title, modal=True)
self.app = app
self.core = app.core
self.node_id = node_id
self.service_name = service_name
self.radiovar = tk.IntVar()
self.radiovar.set(2)
self.metadata = ""
self.filenames = []
self.dependencies = []
self.executables = []
self.startup_commands = []
self.validation_commands = []
self.shutdown_commands = []
self.validation_mode = None
self.validation_time = None
self.validation_period = None
self.documentnew_img = Images.get(ImageEnum.DOCUMENTNEW, 16)
self.editdelete_img = Images.get(ImageEnum.EDITDELETE, 16)
self.notebook = None
self.metadata_entry = None
self.filename_combobox = None
self.startup_commands_listbox = None
self.shutdown_commands_listbox = None
self.validate_commands_listbox = None
self.validation_time_entry = None
self.validation_mode_entry = None
self.service_file_data = None
self.validation_period_entry = None
self.original_service_files = {}
self.temp_service_files = {}
self.modified_files = set()
self.load()
self.draw()
def load(self):
try:
self.app.core.create_nodes_and_links()
service_configs = self.app.core.service_configs
if (
self.node_id in service_configs
and self.service_name in service_configs[self.node_id]
):
service_config = self.app.core.service_configs[self.node_id][
self.service_name
]
else:
service_config = self.app.core.get_node_service(
self.node_id, self.service_name
)
self.dependencies = [x for x in service_config.dependencies]
self.executables = [x for x in service_config.executables]
self.metadata = service_config.meta
self.filenames = [x for x in service_config.configs]
self.startup_commands = [x for x in service_config.startup]
self.validation_commands = [x for x in service_config.validate]
self.shutdown_commands = [x for x in service_config.shutdown]
self.validation_mode = service_config.validation_mode
self.validation_time = service_config.validation_timer
self.original_service_files = {
x: self.app.core.get_node_service_file(
self.node_id, self.service_name, x
)
for x in self.filenames
}
self.temp_service_files = {
x: self.original_service_files[x] for x in self.original_service_files
}
file_configs = self.app.core.file_configs
if (
self.node_id in file_configs
and self.service_name in file_configs[self.node_id]
):
for file, data in file_configs[self.node_id][self.service_name].items():
self.temp_service_files[file] = data
except grpc.RpcError as e:
show_grpc_error(e)
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(1, weight=1)
# draw metadata
frame = ttk.Frame(self.top)
frame.grid(sticky="ew", pady=PADY)
frame.columnconfigure(1, weight=1)
label = ttk.Label(frame, text="Meta-data")
label.grid(row=0, column=0, sticky="w", padx=PADX)
self.metadata_entry = ttk.Entry(frame, textvariable=self.metadata)
self.metadata_entry.grid(row=0, column=1, sticky="ew")
# draw notebook
self.notebook = ttk.Notebook(self.top)
self.notebook.grid(sticky="nsew", pady=PADY)
self.draw_tab_files()
self.draw_tab_directories()
self.draw_tab_startstop()
self.draw_tab_configuration()
button = ttk.Button(self.top, text="Only Save Changes")
button.grid(sticky="ew", pady=PADY)
self.draw_buttons()
def draw_tab_files(self):
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
tab.grid(sticky="nsew")
tab.columnconfigure(0, weight=1)
self.notebook.add(tab, text="Files")
label = ttk.Label(
tab, text="Config files and scripts that are generated for this service."
)
label.grid()
frame = ttk.Frame(tab)
frame.grid(sticky="ew", pady=PADY)
frame.columnconfigure(1, weight=1)
label = ttk.Label(frame, text="File Name")
label.grid(row=0, column=0, padx=PADX, sticky="w")
self.filename_combobox = ttk.Combobox(
frame, values=self.filenames, state="readonly"
)
self.filename_combobox.bind(
"<<ComboboxSelected>>", self.display_service_file_data
)
self.filename_combobox.grid(row=0, column=1, sticky="ew", padx=PADX)
button = ttk.Button(frame, image=self.documentnew_img, state="disabled")
button.bind("<Button-1>", self.add_filename)
button.grid(row=0, column=2, padx=PADX)
button = ttk.Button(frame, image=self.editdelete_img, state="disabled")
button.bind("<Button-1>", self.delete_filename)
button.grid(row=0, column=3)
frame = ttk.Frame(tab)
frame.grid(sticky="ew", pady=PADY)
frame.columnconfigure(1, weight=1)
button = ttk.Radiobutton(
frame,
variable=self.radiovar,
text="Copy Source File",
value=1,
state=tk.DISABLED,
)
button.grid(row=0, column=0, sticky="w", padx=PADX)
entry = ttk.Entry(frame, state=tk.DISABLED)
entry.grid(row=0, column=1, sticky="ew", padx=PADX)
image = Images.get(ImageEnum.FILEOPEN, 16)
button = ttk.Button(frame, image=image)
button.image = image
button.grid(row=0, column=2)
frame = ttk.Frame(tab)
frame.grid(sticky="ew", pady=PADY)
frame.columnconfigure(0, weight=1)
button = ttk.Radiobutton(
frame,
variable=self.radiovar,
text="Use text below for file contents",
value=2,
)
button.grid(row=0, column=0, sticky="ew")
image = Images.get(ImageEnum.FILEOPEN, 16)
button = ttk.Button(frame, image=image)
button.image = image
button.grid(row=0, column=1)
image = Images.get(ImageEnum.DOCUMENTSAVE, 16)
button = ttk.Button(frame, image=image)
button.image = image
button.grid(row=0, column=2)
self.service_file_data = CodeText(tab)
self.service_file_data.grid(sticky="nsew")
tab.rowconfigure(self.service_file_data.grid_info()["row"], weight=1)
if len(self.filenames) > 0:
self.filename_combobox.current(0)
self.service_file_data.text.delete(1.0, "end")
self.service_file_data.text.insert(
"end", self.temp_service_files[self.filenames[0]]
)
self.service_file_data.text.bind(
"<FocusOut>", self.update_temp_service_file_data
)
def draw_tab_directories(self):
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
tab.grid(sticky="nsew")
tab.columnconfigure(0, weight=1)
self.notebook.add(tab, text="Directories")
label = ttk.Label(
tab,
text="Directories required by this service that are unique for each node.",
)
label.grid()
def draw_tab_startstop(self):
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
tab.grid(sticky="nsew")
tab.columnconfigure(0, weight=1)
for i in range(3):
tab.rowconfigure(i, weight=1)
self.notebook.add(tab, text="Startup/Shutdown")
# tab 3
for i in range(3):
label_frame = None
if i == 0:
label_frame = ttk.LabelFrame(
tab, text="Startup Commands", padding=FRAME_PAD
)
commands = self.startup_commands
elif i == 1:
label_frame = ttk.LabelFrame(
tab, text="Shutdown Commands", padding=FRAME_PAD
)
commands = self.shutdown_commands
elif i == 2:
label_frame = ttk.LabelFrame(
tab, text="Validation Commands", padding=FRAME_PAD
)
commands = self.validation_commands
label_frame.columnconfigure(0, weight=1)
label_frame.rowconfigure(1, weight=1)
label_frame.grid(row=i, column=0, sticky="nsew", pady=PADY)
frame = ttk.Frame(label_frame)
frame.grid(row=0, column=0, sticky="nsew", pady=PADY)
frame.columnconfigure(0, weight=1)
entry = ttk.Entry(frame, textvariable=tk.StringVar())
entry.grid(row=0, column=0, stick="ew", padx=PADX)
button = ttk.Button(frame, image=self.documentnew_img)
button.bind("<Button-1>", self.add_command)
button.grid(row=0, column=1, sticky="ew", padx=PADX)
button = ttk.Button(frame, image=self.editdelete_img)
button.grid(row=0, column=2, sticky="ew")
button.bind("<Button-1>", self.delete_command)
listbox_scroll = ListboxScroll(label_frame)
listbox_scroll.listbox.bind("<<ListboxSelect>>", self.update_entry)
for command in commands:
listbox_scroll.listbox.insert("end", command)
listbox_scroll.listbox.config(height=4)
listbox_scroll.grid(row=1, column=0, sticky="nsew")
if i == 0:
self.startup_commands_listbox = listbox_scroll.listbox
elif i == 1:
self.shutdown_commands_listbox = listbox_scroll.listbox
elif i == 2:
self.validate_commands_listbox = listbox_scroll.listbox
def draw_tab_configuration(self):
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
tab.grid(sticky="nsew")
tab.columnconfigure(0, weight=1)
self.notebook.add(tab, text="Configuration", sticky="nsew")
frame = ttk.Frame(tab)
frame.grid(sticky="ew", pady=PADY)
frame.columnconfigure(1, weight=1)
label = ttk.Label(frame, text="Validation Time")
label.grid(row=0, column=0, sticky="w", padx=PADX)
self.validation_time_entry = ttk.Entry(frame)
self.validation_time_entry.insert("end", self.validation_time)
self.validation_time_entry.config(state=tk.DISABLED)
self.validation_time_entry.grid(row=0, column=1, sticky="ew", pady=PADY)
label = ttk.Label(frame, text="Validation Mode")
label.grid(row=1, column=0, sticky="w", padx=PADX)
if self.validation_mode == core_pb2.ServiceValidationMode.BLOCKING:
mode = "BLOCKING"
elif self.validation_mode == core_pb2.ServiceValidationMode.NON_BLOCKING:
mode = "NON_BLOCKING"
else:
mode = "TIMER"
self.validation_mode_entry = ttk.Entry(
frame, textvariable=tk.StringVar(value=mode)
)
self.validation_mode_entry.insert("end", mode)
self.validation_mode_entry.config(state=tk.DISABLED)
self.validation_mode_entry.grid(row=1, column=1, sticky="ew", pady=PADY)
label = ttk.Label(frame, text="Validation Period")
label.grid(row=2, column=0, sticky="w", padx=PADX)
self.validation_period_entry = ttk.Entry(
frame, state=tk.DISABLED, textvariable=tk.StringVar()
)
self.validation_period_entry.grid(row=2, column=1, sticky="ew", pady=PADY)
label_frame = ttk.LabelFrame(tab, text="Executables", padding=FRAME_PAD)
label_frame.grid(sticky="nsew", pady=PADY)
label_frame.columnconfigure(0, weight=1)
label_frame.rowconfigure(0, weight=1)
listbox_scroll = ListboxScroll(label_frame)
listbox_scroll.grid(sticky="nsew")
tab.rowconfigure(listbox_scroll.grid_info()["row"], weight=1)
for executable in self.executables:
listbox_scroll.listbox.insert("end", executable)
label_frame = ttk.LabelFrame(tab, text="Dependencies", padding=FRAME_PAD)
label_frame.grid(sticky="nsew", pady=PADY)
label_frame.columnconfigure(0, weight=1)
label_frame.rowconfigure(0, weight=1)
listbox_scroll = ListboxScroll(label_frame)
listbox_scroll.grid(sticky="nsew")
tab.rowconfigure(listbox_scroll.grid_info()["row"], weight=1)
for dependency in self.dependencies:
listbox_scroll.listbox.insert("end", dependency)
def draw_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
for i in range(4):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Apply", command=self.click_apply)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(
frame, text="Defaults", command=self.click_defaults, state="disabled"
)
button.grid(row=0, column=1, sticky="ew", padx=PADX)
button = ttk.Button(
frame, text="Copy...", command=self.click_copy, state="disabled"
)
button.grid(row=0, column=2, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=3, sticky="ew")
def add_filename(self, event):
# not worry about it for now
return
frame_contains_button = event.widget.master
combobox = frame_contains_button.grid_slaves(row=0, column=1)[0]
filename = combobox.get()
if filename not in combobox["values"]:
combobox["values"] += (filename,)
def delete_filename(self, event):
# not worry about it for now
return
frame_comntains_button = event.widget.master
combobox = frame_comntains_button.grid_slaves(row=0, column=1)[0]
filename = combobox.get()
if filename in combobox["values"]:
combobox["values"] = tuple([x for x in combobox["values"] if x != filename])
combobox.set("")
def add_command(self, event):
frame_contains_button = event.widget.master
listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox
command_to_add = frame_contains_button.grid_slaves(row=0, column=0)[0].get()
if command_to_add == "":
return
for cmd in listbox.get(0, tk.END):
if cmd == command_to_add:
return
listbox.insert(tk.END, command_to_add)
def update_entry(self, event):
listbox = event.widget
current_selection = listbox.curselection()
if len(current_selection) > 0:
cmd = listbox.get(current_selection[0])
entry = listbox.master.master.grid_slaves(row=0, column=0)[0].grid_slaves(
row=0, column=0
)[0]
entry.delete(0, "end")
entry.insert(0, cmd)
def delete_command(self, event):
button = event.widget
frame_contains_button = button.master
listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox
current_selection = listbox.curselection()
if len(current_selection) > 0:
listbox.delete(current_selection[0])
entry = frame_contains_button.grid_slaves(row=0, column=0)[0]
entry.delete(0, tk.END)
def click_apply(self):
service_configs = self.app.core.service_configs
startup_commands = self.startup_commands_listbox.get(0, "end")
shutdown_commands = self.shutdown_commands_listbox.get(0, "end")
validate_commands = self.validate_commands_listbox.get(0, "end")
try:
config = self.core.set_node_service(
self.node_id,
self.service_name,
startup_commands,
validate_commands,
shutdown_commands,
)
if self.node_id not in service_configs:
service_configs[self.node_id] = {}
if self.service_name not in service_configs[self.node_id]:
self.app.core.service_configs[self.node_id][self.service_name] = config
for file in self.modified_files:
file_configs = self.app.core.file_configs
if self.node_id not in file_configs:
file_configs[self.node_id] = {}
if self.service_name not in file_configs[self.node_id]:
file_configs[self.node_id][self.service_name] = {}
file_configs[self.node_id][self.service_name][
file
] = self.temp_service_files[file]
self.app.core.set_node_service_file(
self.node_id, self.service_name, file, self.temp_service_files[file]
)
except grpc.RpcError as e:
show_grpc_error(e)
self.destroy()
def display_service_file_data(self, event):
combobox = event.widget
filename = combobox.get()
self.service_file_data.text.delete(1.0, "end")
self.service_file_data.text.insert("end", self.temp_service_files[filename])
def update_temp_service_file_data(self, event):
scrolledtext = event.widget
filename = self.filename_combobox.get()
self.temp_service_files[filename] = scrolledtext.get(1.0, "end")
if self.temp_service_files[filename] != self.original_service_files[filename]:
self.modified_files.add(filename)
else:
self.modified_files.discard(filename)
def click_defaults(self):
logging.info("not implemented")
def click_copy(self):
logging.info("not implemented")
def click_cancel(self):
logging.info("not implemented")

View file

@ -0,0 +1,53 @@
import logging
from tkinter import ttk
import grpc
from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame
class SessionOptionsDialog(Dialog):
def __init__(self, master, app):
super().__init__(master, app, "Session Options", modal=True)
self.config_frame = None
self.config = self.get_config()
self.draw()
def get_config(self):
try:
session_id = self.app.core.session_id
response = self.app.core.client.get_session_options(session_id)
return response.config
except grpc.RpcError as e:
show_grpc_error(e)
self.destroy()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
self.config_frame = ConfigFrame(self.top, self.app, config=self.config)
self.config_frame.draw_config()
self.config_frame.grid(sticky="nsew", pady=PADY)
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
for i in range(2):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Save", command=self.save)
button.grid(row=0, column=0, padx=PADX, sticky="ew")
button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, padx=PADX, sticky="ew")
def save(self):
config = self.config_frame.parse_config()
try:
session_id = self.app.core.session_id
response = self.app.core.client.set_session_options(session_id, config)
logging.info("saved session config: %s", response)
except grpc.RpcError as e:
show_grpc_error(e)
self.destroy()

View file

@ -0,0 +1,181 @@
import logging
import threading
import tkinter as tk
from tkinter import ttk
import grpc
from core.api.grpc import core_pb2
from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.images import ImageEnum, Images
from core.gui.themes import PADX, PADY
class SessionsDialog(Dialog):
def __init__(self, master, app):
super().__init__(master, app, "Sessions", modal=True)
self.selected = False
self.selected_id = None
self.tree = None
self.sessions = self.get_sessions()
self.draw()
def get_sessions(self):
try:
response = self.app.core.client.get_sessions()
logging.info("sessions: %s", response)
return response.sessions
except grpc.RpcError as e:
show_grpc_error(e)
self.destroy()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(1, weight=1)
self.draw_description()
self.draw_tree()
self.draw_buttons()
def draw_description(self):
"""
write a short description
:return: nothing
"""
label = ttk.Label(
self.top,
text="Below is a list of active CORE sessions. Double-click to \n"
"connect to an existing session. Usually, only sessions in \n"
"the RUNTIME state persist in the daemon, except for the \n"
"one you might be concurrently editting.",
justify=tk.CENTER,
)
label.grid(pady=PADY)
def draw_tree(self):
frame = ttk.Frame(self.top)
frame.columnconfigure(0, weight=1)
frame.rowconfigure(0, weight=1)
frame.grid(sticky="nsew", pady=PADY)
self.tree = ttk.Treeview(
frame, columns=("id", "state", "nodes"), show="headings"
)
self.tree.grid(sticky="nsew")
self.tree.column("id", stretch=tk.YES)
self.tree.heading("id", text="ID")
self.tree.column("state", stretch=tk.YES)
self.tree.heading("state", text="State")
self.tree.column("nodes", stretch=tk.YES)
self.tree.heading("nodes", text="Node Count")
for index, session in enumerate(self.sessions):
state_name = core_pb2.SessionState.Enum.Name(session.state)
self.tree.insert(
"",
tk.END,
text=str(session.id),
values=(session.id, state_name, session.nodes),
)
self.tree.bind("<Double-1>", self.on_selected)
self.tree.bind("<<TreeviewSelect>>", self.click_select)
yscrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.tree.yview)
yscrollbar.grid(row=0, column=1, sticky="ns")
self.tree.configure(yscrollcommand=yscrollbar.set)
xscrollbar = ttk.Scrollbar(frame, orient="horizontal", command=self.tree.xview)
xscrollbar.grid(row=1, sticky="ew")
self.tree.configure(xscrollcommand=xscrollbar.set)
def draw_buttons(self):
frame = ttk.Frame(self.top)
for i in range(4):
frame.columnconfigure(i, weight=1)
frame.grid(sticky="ew")
image = Images.get(ImageEnum.DOCUMENTNEW, 16)
b = ttk.Button(
frame, image=image, text="New", compound=tk.LEFT, command=self.click_new
)
b.image = image
b.grid(row=0, padx=PADX, sticky="ew")
image = Images.get(ImageEnum.FILEOPEN, 16)
b = ttk.Button(
frame,
image=image,
text="Connect",
compound=tk.LEFT,
command=self.click_connect,
)
b.image = image
b.grid(row=0, column=1, padx=PADX, sticky="ew")
image = Images.get(ImageEnum.EDITDELETE, 16)
b = ttk.Button(
frame,
image=image,
text="Shutdown",
compound=tk.LEFT,
command=self.click_shutdown,
)
b.image = image
b.grid(row=0, column=2, padx=PADX, sticky="ew")
b = ttk.Button(frame, text="Cancel", command=self.click_new)
b.grid(row=0, column=3, sticky="ew")
def click_new(self):
self.app.core.create_new_session()
self.destroy()
def click_select(self, event):
item = self.tree.selection()
session_id = int(self.tree.item(item, "text"))
self.selected = True
self.selected_id = session_id
def click_connect(self):
"""
if no session is selected yet, create a new one else join that session
:return: nothing
"""
if self.selected and self.selected_id is not None:
self.join_session(self.selected_id)
elif not self.selected and self.selected_id is None:
self.click_new()
else:
logging.error("sessions invalid state")
def click_shutdown(self):
"""
if no session is currently selected create a new session else shut the selected
session down.
:return: nothing
"""
if self.selected and self.selected_id is not None:
self.shutdown_session(self.selected_id)
elif not self.selected and self.selected_id is None:
self.click_new()
else:
logging.error("querysessiondrawing.py invalid state")
def join_session(self, session_id):
self.app.statusbar.progress_bar.start(5)
thread = threading.Thread(
target=self.app.core.join_session, args=([session_id])
)
thread.start()
self.destroy()
def on_selected(self, event):
item = self.tree.selection()
sid = int(self.tree.item(item, "text"))
self.join_session(sid)
def shutdown_session(self, sid):
self.app.core.stop_session(sid)
self.click_new()
self.destroy()

View file

@ -0,0 +1,252 @@
"""
shape input dialog
"""
import tkinter as tk
from tkinter import font, ttk
from core.gui.dialogs.colorpicker import ColorPicker
from core.gui.dialogs.dialog import Dialog
from core.gui.graph import tags
from core.gui.graph.shapeutils import is_draw_shape, is_shape_text
from core.gui.themes import FRAME_PAD, PADX, PADY
FONT_SIZES = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72]
BORDER_WIDTH = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
class ShapeDialog(Dialog):
def __init__(self, master, app, shape):
if is_draw_shape(shape.shape_type):
title = "Add Shape"
else:
title = "Add Text"
super().__init__(master, app, title, modal=True)
self.canvas = app.canvas
self.fill = None
self.border = None
self.shape = shape
data = shape.shape_data
self.shape_text = tk.StringVar(value=data.text)
self.font = tk.StringVar(value=data.font)
self.font_size = tk.IntVar(value=data.font_size)
self.text_color = data.text_color
fill_color = data.fill_color
if not fill_color:
fill_color = "#CFCFFF"
self.fill_color = fill_color
self.border_color = data.border_color
self.border_width = tk.IntVar(value=0)
self.bold = tk.BooleanVar(value=data.bold)
self.italic = tk.BooleanVar(value=data.italic)
self.underline = tk.BooleanVar(value=data.underline)
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.draw_label_options()
if is_draw_shape(self.shape.shape_type):
self.draw_shape_options()
self.draw_spacer()
self.draw_buttons()
def draw_label_options(self):
label_frame = ttk.LabelFrame(self.top, text="Label", padding=FRAME_PAD)
label_frame.grid(sticky="ew")
label_frame.columnconfigure(0, weight=1)
entry = ttk.Entry(label_frame, textvariable=self.shape_text)
entry.grid(sticky="ew", pady=PADY)
# font options
frame = ttk.Frame(label_frame)
frame.grid(sticky="nsew", pady=PADY)
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
frame.columnconfigure(2, weight=1)
combobox = ttk.Combobox(
frame,
textvariable=self.font,
values=sorted(font.families()),
state="readonly",
)
combobox.grid(row=0, column=0, sticky="nsew")
combobox = ttk.Combobox(
frame, textvariable=self.font_size, values=FONT_SIZES, state="readonly"
)
combobox.grid(row=0, column=1, padx=PADX, sticky="nsew")
button = ttk.Button(frame, text="Color", command=self.choose_text_color)
button.grid(row=0, column=2, sticky="nsew")
# style options
frame = ttk.Frame(label_frame)
frame.grid(sticky="ew")
for i in range(3):
frame.columnconfigure(i, weight=1)
button = ttk.Checkbutton(frame, variable=self.bold, text="Bold")
button.grid(row=0, column=0, sticky="ew")
button = ttk.Checkbutton(frame, variable=self.italic, text="Italic")
button.grid(row=0, column=1, padx=PADX, sticky="ew")
button = ttk.Checkbutton(frame, variable=self.underline, text="Underline")
button.grid(row=0, column=2, sticky="ew")
def draw_shape_options(self):
label_frame = ttk.LabelFrame(self.top, text="Shape", padding=FRAME_PAD)
label_frame.grid(sticky="ew", pady=PADY)
label_frame.columnconfigure(0, weight=1)
frame = ttk.Frame(label_frame)
frame.grid(sticky="ew")
for i in range(1, 3):
frame.columnconfigure(i, weight=1)
label = ttk.Label(frame, text="Fill Color")
label.grid(row=0, column=0, padx=PADX, sticky="w")
self.fill = ttk.Label(frame, text=self.fill_color, background=self.fill_color)
self.fill.grid(row=0, column=1, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Color", command=self.choose_fill_color)
button.grid(row=0, column=2, sticky="ew")
label = ttk.Label(frame, text="Border Color")
label.grid(row=1, column=0, sticky="w", padx=PADX)
self.border = ttk.Label(
frame, text=self.border_color, background=self.border_color
)
self.border.grid(row=1, column=1, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Color", command=self.choose_border_color)
button.grid(row=1, column=2, sticky="ew")
frame = ttk.Frame(label_frame)
frame.grid(sticky="ew", pady=PADY)
frame.columnconfigure(1, weight=1)
label = ttk.Label(frame, text="Border Width")
label.grid(row=0, column=0, sticky="w", padx=PADX)
combobox = ttk.Combobox(
frame, textvariable=self.border_width, values=BORDER_WIDTH, state="readonly"
)
combobox.grid(row=0, column=1, sticky="nsew")
def draw_buttons(self):
frame = ttk.Frame(self.top)
frame.grid(sticky="nsew")
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
button = ttk.Button(frame, text="Add shape", command=self.click_add)
button.grid(row=0, column=0, sticky="ew", padx=PADX)
button = ttk.Button(frame, text="Cancel", command=self.cancel)
button.grid(row=0, column=1, sticky="ew")
def choose_text_color(self):
color_picker = ColorPicker(self, self.app, "#000000")
color = color_picker.askcolor()
self.text_color = color
def choose_fill_color(self):
color_picker = ColorPicker(self, self.app, self.fill_color)
color = color_picker.askcolor()
self.fill_color = color
self.fill.config(background=color, text=color)
def choose_border_color(self):
color_picker = ColorPicker(self, self.app, self.border_color)
color = color_picker.askcolor()
self.border_color = color
self.border.config(background=color, text=color)
def cancel(self):
self.shape.delete()
self.canvas.shapes.pop(self.shape.id)
self.destroy()
def click_add(self):
if is_draw_shape(self.shape.shape_type):
self.add_shape()
elif is_shape_text(self.shape.shape_type):
self.add_text()
self.destroy()
def make_font(self):
"""
create font for text or shape label
:return: list(font specifications)
"""
size = int(self.font_size.get())
text_font = [self.font.get(), size]
if self.bold.get():
text_font.append("bold")
if self.italic.get():
text_font.append("italic")
if self.underline.get():
text_font.append("underline")
return text_font
def save_text(self):
"""
save info related to text or shape label
:return: nothing
"""
data = self.shape.shape_data
data.text = self.shape_text.get()
data.font = self.font.get()
data.font_size = int(self.font_size.get())
data.text_color = self.text_color
data.bold = self.bold.get()
data.italic = self.italic.get()
data.underline = self.underline.get()
def save_shape(self):
"""
save info related to shape
:return: nothing
"""
data = self.shape.shape_data
data.fill_color = self.fill_color
data.border_color = self.border_color
data.border_width = int(self.border_width.get())
def add_text(self):
"""
add text to canvas
:return: nothing
"""
text = self.shape_text.get()
text_font = self.make_font()
self.canvas.itemconfig(
self.shape.id, text=text, fill=self.text_color, font=text_font
)
self.save_text()
def add_shape(self):
self.canvas.itemconfig(
self.shape.id,
fill=self.fill_color,
dash="",
outline=self.border_color,
width=int(self.border_width.get()),
)
shape_text = self.shape_text.get()
size = int(self.font_size.get())
x0, y0, x1, y1 = self.canvas.bbox(self.shape.id)
_y = y0 + 1.5 * size
_x = (x0 + x1) / 2
text_font = self.make_font()
if self.shape.text_id is None:
self.shape.text_id = self.canvas.create_text(
_x,
_y,
text=shape_text,
fill=self.text_color,
font=text_font,
tags=tags.SHAPE_TEXT,
)
self.shape.created = True
else:
self.canvas.itemconfig(
self.shape.text_id,
text=shape_text,
fill=self.text_color,
font=text_font,
)
self.save_text()
self.save_shape()

View file

@ -0,0 +1,66 @@
"""
wlan configuration
"""
from tkinter import ttk
import grpc
from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame
class WlanConfigDialog(Dialog):
def __init__(self, master, app, canvas_node):
super().__init__(
master, app, f"{canvas_node.core_node.name} Wlan Configuration", modal=True
)
self.canvas_node = canvas_node
self.node = canvas_node.core_node
self.config_frame = None
try:
self.config = self.app.core.get_wlan_config(self.node.id)
except grpc.RpcError as e:
show_grpc_error(e)
self.destroy()
self.draw()
def draw(self):
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
self.config_frame = ConfigFrame(self.top, self.app, self.config)
self.config_frame.draw_config()
self.config_frame.grid(sticky="nsew", pady=PADY)
self.draw_apply_buttons()
def draw_apply_buttons(self):
"""
create node configuration options
:return: nothing
"""
frame = ttk.Frame(self.top)
frame.grid(sticky="ew")
for i in range(2):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Apply", command=self.click_apply)
button.grid(row=0, column=0, padx=PADX, sticky="ew")
button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="ew")
def click_apply(self):
"""
retrieve user's wlan configuration and store the new configuration values
:return: nothing
"""
config = self.config_frame.parse_config()
self.app.core.wlan_configs[self.node.id] = self.config
if self.app.core.is_runtime():
session_id = self.app.core.session_id
self.app.core.client.set_wlan_config(session_id, self.node.id, config)
self.destroy()