Merge branch 'coretk' into coretk-config

This commit is contained in:
Huy Pham 2019-11-06 14:37:19 -08:00
commit a0039d3991
14 changed files with 463 additions and 96 deletions

View file

@ -15,7 +15,11 @@ jobs:
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install pipenv pip install pipenv
cd coretk cd daemon
cp setup.py.in setup.py
cp core/constants.py.in core/constants.py
sed -i 's/True/False/g' core/constants.py
cd ../coretk
pipenv install --dev pipenv install --dev
- name: isort - name: isort
run: | run: |

View file

@ -42,15 +42,15 @@ def check_directory():
for background in LOCAL_BACKGROUND_PATH.glob("*"): for background in LOCAL_BACKGROUND_PATH.glob("*"):
new_background = BACKGROUNDS_PATH.joinpath(background.name) new_background = BACKGROUNDS_PATH.joinpath(background.name)
shutil.copy(background, new_background) shutil.copy(background, new_background)
with CONFIG_PATH.open("w") as f: config = {"servers": [{"name": "example", "address": "127.0.0.1", "port": 50051}]}
yaml.dump( save_config(config)
{"servers": [{"name": "example", "address": "127.0.0.1", "port": 50051}]},
f,
Dumper=IndentDumper,
default_flow_style=False,
)
def read_config(): def read_config():
with CONFIG_PATH.open("r") as f: with CONFIG_PATH.open("r") as f:
return yaml.load(f, Loader=yaml.SafeLoader) return yaml.load(f, Loader=yaml.SafeLoader)
def save_config(config):
with CONFIG_PATH.open("w") as f:
yaml.dump(config, f, Dumper=IndentDumper, default_flow_style=False)

View file

@ -75,6 +75,7 @@ class CoreClient:
self.app = app self.app = app
self.master = app.master self.master = app.master
self.interface_helper = None self.interface_helper = None
self.services = {}
# distributed server data # distributed server data
self.servers = {} self.servers = {}
@ -228,9 +229,16 @@ class CoreClient:
:return: existing sessions :return: existing sessions
""" """
self.client.connect() self.client.connect()
response = self.client.get_sessions()
# get service information
response = self.client.get_services()
for service in response.services:
group_services = self.services.setdefault(service.group, [])
group_services.append(service)
# if there are no sessions, create a new session, else join a session # if there are no sessions, create a new session, else join a session
response = self.client.get_sessions()
logging.info("current sessions: %s", response)
sessions = response.sessions sessions = response.sessions
if len(sessions) == 0: if len(sessions) == 0:
self.create_new_session() self.create_new_session()

View file

@ -543,7 +543,9 @@ class CoreMenubar(object):
observer_widget_menu.add_command( observer_widget_menu.add_command(
label="PIM neighbors", command=action.sub_menu_items label="PIM neighbors", command=action.sub_menu_items
) )
observer_widget_menu.add_command(label="Edit...", command=action.sub_menu_items) observer_widget_menu.add_command(
label="Edit...", command=self.menu_action.edit_observer_widgets
)
widget_menu.add_cascade(label="Observer Widgets", menu=observer_widget_menu) widget_menu.add_cascade(label="Observer Widgets", menu=observer_widget_menu)

View file

@ -1,8 +1,8 @@
import logging import logging
import tkinter as tk import tkinter as tk
# from core.api.grpc import core_pb2
from coretk.coretoolbarhelp import CoreToolbarHelp from coretk.coretoolbarhelp import CoreToolbarHelp
from coretk.dialogs.customnodes import CustomNodesDialog
from coretk.graph import GraphMode from coretk.graph import GraphMode
from coretk.images import ImageEnum, Images from coretk.images import ImageEnum, Images
from coretk.tooltip import CreateToolTip from coretk.tooltip import CreateToolTip
@ -233,11 +233,12 @@ class CoreToolbar(object):
self.canvas.draw_node_image = Images.get(ImageEnum.OVS) self.canvas.draw_node_image = Images.get(ImageEnum.OVS)
self.canvas.draw_node_name = "OVS" self.canvas.draw_node_name = "OVS"
# TODO what graph node is this
def pick_editnode(self, main_button): def pick_editnode(self, main_button):
self.network_layer_option_menu.destroy() self.network_layer_option_menu.destroy()
main_button.configure(image=Images.get(ImageEnum.EDITNODE)) main_button.configure(image=Images.get(ImageEnum.EDITNODE))
logging.debug("Pick editnode option") logging.debug("Pick editnode option")
dialog = CustomNodesDialog(self.app, self.app)
dialog.show()
def draw_network_layer_options(self, network_layer_button): def draw_network_layer_options(self, network_layer_button):
""" """

View file

@ -0,0 +1,158 @@
import tkinter as tk
from coretk.dialogs.dialog import Dialog
from coretk.dialogs.nodeicon import IconDialog
from coretk.widgets import CheckboxList, ListboxScroll
class ServicesSelectDialog(Dialog):
def __init__(self, master, app):
super().__init__(master, app, "Node Services", modal=True)
self.groups = None
self.services = None
self.current = None
self.current_services = set()
self.draw()
def draw(self):
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
frame = tk.Frame(self)
frame.grid(stick="nsew")
frame.rowconfigure(0, weight=1)
for i in range(3):
frame.columnconfigure(i, weight=1)
self.groups = ListboxScroll(frame, text="Groups")
self.groups.grid(row=0, column=0, 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.services = CheckboxList(
frame, text="Services", clicked=self.service_clicked
)
self.services.grid(row=0, column=1, sticky="nsew")
self.current = ListboxScroll(frame, text="Selected")
self.current.grid(row=0, column=2, sticky="nsew")
frame = tk.Frame(self)
frame.grid(stick="ew")
for i in range(2):
frame.columnconfigure(i, weight=1)
button = tk.Button(frame, text="Save")
button.grid(row=0, column=0, sticky="ew")
button = tk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="ew")
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 service in sorted(self.app.core.services[group], key=lambda x: x.name):
self.services.add(service.name)
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)
class CustomNodesDialog(Dialog):
def __init__(self, master, app):
super().__init__(master, app, "Custom Nodes", modal=True)
self.save_button = None
self.delete_button = None
self.name = tk.StringVar()
self.image_button = None
self.image = None
self.draw()
def draw(self):
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.draw_node_config()
self.draw_node_buttons()
self.draw_buttons()
def draw_node_config(self):
frame = tk.Frame(self)
frame.grid(sticky="nsew")
frame.columnconfigure(0, weight=1)
frame.rowconfigure(0, weight=1)
scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL)
scrollbar.grid(row=0, column=1, sticky="ns")
listbox = tk.Listbox(frame, selectmode=tk.SINGLE, yscrollcommand=scrollbar.set)
listbox.grid(row=0, column=0, sticky="nsew")
scrollbar.config(command=listbox.yview)
frame = tk.Frame(frame)
frame.grid(row=0, column=2, sticky="nsew")
frame.columnconfigure(0, weight=1)
entry = tk.Entry(frame, textvariable=self.name)
entry.grid(sticky="ew")
self.image_button = tk.Button(frame, text="Icon", command=self.click_icon)
self.image_button.grid(sticky="ew")
button = tk.Button(frame, text="Services", command=self.click_services)
button.grid(sticky="ew")
def draw_node_buttons(self):
frame = tk.Frame(self)
frame.grid(pady=2, sticky="ew")
for i in range(3):
frame.columnconfigure(i, weight=1)
button = tk.Button(frame, text="Create", command=self.click_create)
button.grid(row=0, column=0, sticky="ew")
self.save_button = tk.Button(
frame, text="Save", state=tk.DISABLED, command=self.click_save
)
self.save_button.grid(row=0, column=1, sticky="ew")
self.delete_button = tk.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 = tk.Frame(self)
frame.grid(sticky="ew")
for i in range(2):
frame.columnconfigure(i, weight=1)
button = tk.Button(frame, text="Save", command=self.click_save)
button.grid(row=0, column=0, sticky="ew")
button = tk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="ew")
def click_icon(self):
dialog = IconDialog(self, self.app, self.name.get(), self.image)
dialog.show()
if dialog.image:
self.image = dialog.image
self.image_button.config(image=self.image)
def click_services(self):
dialog = ServicesSelectDialog(self, self.app)
dialog.show()
def click_create(self):
pass
def click_save(self):
pass
def click_delete(self):
pass

View file

@ -2,7 +2,7 @@ import tkinter as tk
from tkinter import ttk from tkinter import ttk
from coretk.dialogs.dialog import Dialog from coretk.dialogs.dialog import Dialog
from coretk.dialogs.nodeicon import NodeIconDialog from coretk.dialogs.nodeicon import IconDialog
from coretk.dialogs.nodeservice import NodeServicesDialog from coretk.dialogs.nodeservice import NodeServicesDialog
NETWORKNODETYPES = ["switch", "hub", "wlan", "rj45", "tunnel"] NETWORKNODETYPES = ["switch", "hub", "wlan", "rj45", "tunnel"]
@ -91,7 +91,9 @@ class NodeConfigDialog(Dialog):
dialog.show() dialog.show()
def click_icon(self): def click_icon(self):
dialog = NodeIconDialog(self, self.app, self.canvas_node) dialog = IconDialog(
self, self.app, self.canvas_node.name, self.canvas_node.image
)
dialog.show() dialog.show()
if dialog.image: if dialog.image:
self.image = dialog.image self.image = dialog.image

View file

@ -6,18 +6,12 @@ from coretk.dialogs.dialog import Dialog
from coretk.images import Images from coretk.images import Images
class NodeIconDialog(Dialog): class IconDialog(Dialog):
def __init__(self, master, app, canvas_node): def __init__(self, master, app, name, image):
""" super().__init__(master, app, f"{name} Icon", modal=True)
create an instance of ImageModification
:param master: dialog master
:param coretk.app.Application: main app
:param coretk.graph.CanvasNode canvas_node: node object
"""
super().__init__(master, app, f"{canvas_node.name} Icon", modal=True)
self.file_path = tk.StringVar() self.file_path = tk.StringVar()
self.image_label = None self.image_label = None
self.image = canvas_node.image self.image = image
self.draw() self.draw()
def draw(self): def draw(self):

View file

@ -6,58 +6,6 @@ from tkinter import messagebox
from coretk.dialogs.dialog import Dialog from coretk.dialogs.dialog import Dialog
CORE_DEFAULT_GROUPS = ["EMANE", "FRR", "ProtoSvc", "Quagga", "Security", "Utility"]
DEFAULT_GROUP_RADIO_VALUE = {
"EMANE": 1,
"FRR": 2,
"ProtoSvc": 3,
"Quagga": 4,
"Security": 5,
"Utility": 6,
}
DEFAULT_GROUP_SERVICES = {
"EMANE": ["transportd"],
"FRR": [
"FRRBable",
"FRRBGP",
"FRROSPFv2",
"FRROSPFv3",
"FRRpimd",
"FRRRIP",
"FRRRIPNG",
"FRRzebra",
],
"ProtoSvc": ["MGEN_Sink", "MgenActor", "SMF"],
"Quagga": [
"Babel",
"BGP",
"OSPFv2",
"OSPFv3",
"OSPFv3MDR",
"RIP",
"RIPNG",
"Xpimd",
"zebra",
],
"Security": ["Firewall", "IPsec", "NAT", "VPNClient", "VPNServer"],
"Utility": [
"atd",
"DefaultMulticastRoute",
"DefaultRoute",
"DHCP",
"DHCPClient",
"FTP",
"HTTP",
"IPForward ",
"pcap",
"radvd",
"SSH",
"StaticRoute",
"ucarp",
"UserDefined",
],
}
class NodeServicesDialog(Dialog): class NodeServicesDialog(Dialog):
def __init__(self, master, app, canvas_node): def __init__(self, master, app, canvas_node):
@ -66,6 +14,7 @@ class NodeServicesDialog(Dialog):
self.core_groups = [] self.core_groups = []
self.service_to_config = None self.service_to_config = None
self.config_frame = None self.config_frame = None
self.services_list = None
self.draw() self.draw()
def draw(self): def draw(self):
@ -110,7 +59,7 @@ class NodeServicesDialog(Dialog):
listbox.grid(row=1, column=0, sticky="nsew") listbox.grid(row=1, column=0, sticky="nsew")
listbox.bind("<<ListboxSelect>>", self.handle_group_change) listbox.bind("<<ListboxSelect>>", self.handle_group_change)
for group in CORE_DEFAULT_GROUPS: for group in sorted(self.app.core.services):
listbox.insert(tk.END, group) listbox.insert(tk.END, group)
scrollbar.config(command=listbox.yview) scrollbar.config(command=listbox.yview)
@ -127,7 +76,7 @@ class NodeServicesDialog(Dialog):
scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL) scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL)
scrollbar.grid(row=1, column=1, sticky="ns") scrollbar.grid(row=1, column=1, sticky="ns")
listbox = tk.Listbox( self.services_list = tk.Listbox(
frame, frame,
selectmode=tk.SINGLE, selectmode=tk.SINGLE,
yscrollcommand=scrollbar.set, yscrollcommand=scrollbar.set,
@ -135,10 +84,10 @@ class NodeServicesDialog(Dialog):
highlightthickness=0.5, highlightthickness=0.5,
bd=0, bd=0,
) )
listbox.grid(row=1, column=0, sticky="nsew") self.services_list.grid(row=1, column=0, sticky="nsew")
listbox.bind("<<ListboxSelect>>", self.handle_service_change) self.services_list.bind("<<ListboxSelect>>", self.handle_service_change)
scrollbar.config(command=listbox.yview) scrollbar.config(command=self.services_list.yview)
def draw_current_services(self): def draw_current_services(self):
frame = tk.Frame(self.config_frame) frame = tk.Frame(self.config_frame)
@ -188,11 +137,9 @@ class NodeServicesDialog(Dialog):
self.display_group_services(s) self.display_group_services(s)
def display_group_services(self, group_name): def display_group_services(self, group_name):
group_services_frame = self.config_frame.grid_slaves(row=0, column=1)[0] self.services_list.delete(0, tk.END)
listbox = group_services_frame.grid_slaves(row=1, column=0)[0] for service in sorted(self.app.core.services[group_name], key=lambda x: x.name):
listbox.delete(0, tk.END) self.services_list.insert(tk.END, service.name)
for s in DEFAULT_GROUP_SERVICES[group_name]:
listbox.insert(tk.END, s)
def handle_service_change(self, event): def handle_service_change(self, event):
print("select group service") print("select group service")

View file

@ -0,0 +1,148 @@
import tkinter as tk
from coretk.dialogs.dialog import Dialog
class Widget:
def __init__(self, name, command):
self.name = name
self.command = command
class ObserverWidgetsDialog(Dialog):
def __init__(self, master, app):
super().__init__(master, app, "Observer Widgets", modal=True)
self.config_widgets = {}
self.widgets = None
self.save_button = None
self.delete_button = None
self.selected = None
self.selected_index = None
self.name = tk.StringVar()
self.command = tk.StringVar()
self.draw()
def draw(self):
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.draw_widgets()
self.draw_widget_fields()
self.draw_widget_buttons()
self.draw_apply_buttons()
def draw_widgets(self):
frame = tk.Frame(self)
frame.grid(sticky="nsew")
frame.columnconfigure(0, weight=1)
frame.rowconfigure(0, weight=1)
scrollbar = tk.Scrollbar(frame, orient=tk.VERTICAL)
scrollbar.grid(row=0, column=1, sticky="ns")
self.widgets = tk.Listbox(
frame, selectmode=tk.SINGLE, yscrollcommand=scrollbar.set
)
self.widgets.grid(row=0, column=0, sticky="nsew")
self.widgets.bind("<<ListboxSelect>>", self.handle_widget_change)
scrollbar.config(command=self.widgets.yview)
def draw_widget_fields(self):
frame = tk.Frame(self)
frame.grid(sticky="ew")
frame.columnconfigure(1, weight=1)
label = tk.Label(frame, text="Name")
label.grid(row=0, column=0, sticky="w")
entry = tk.Entry(frame, textvariable=self.name)
entry.grid(row=0, column=1, sticky="ew")
label = tk.Label(frame, text="Command")
label.grid(row=1, column=0, sticky="w")
entry = tk.Entry(frame, textvariable=self.command)
entry.grid(row=1, column=1, sticky="ew")
def draw_widget_buttons(self):
frame = tk.Frame(self)
frame.grid(pady=2, sticky="ew")
for i in range(3):
frame.columnconfigure(i, weight=1)
button = tk.Button(frame, text="Create", command=self.click_create)
button.grid(row=0, column=0, sticky="ew")
self.save_button = tk.Button(
frame, text="Save", state=tk.DISABLED, command=self.click_save
)
self.save_button.grid(row=0, column=1, sticky="ew")
self.delete_button = tk.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 = tk.Frame(self)
frame.grid(sticky="ew")
for i in range(2):
frame.columnconfigure(i, weight=1)
button = tk.Button(
frame, text="Save Configuration", command=self.click_save_configuration
)
button.grid(row=0, column=0, sticky="ew")
button = tk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky="ew")
def click_save_configuration(self):
pass
def click_create(self):
name = self.name.get()
if name not in self.config_widgets:
command = self.command.get()
widget = Widget(name, command)
self.config_widgets[name] = widget
self.widgets.insert(tk.END, name)
def click_save(self):
name = self.name.get()
if self.selected:
previous_name = self.selected
self.selected = name
widget = self.config_widgets.pop(previous_name)
widget.name = name
widget.command = self.command.get()
self.config_widgets[name] = widget
self.widgets.delete(self.selected_index)
self.widgets.insert(self.selected_index, name)
self.widgets.selection_set(self.selected_index)
def click_delete(self):
if self.selected:
self.widgets.delete(self.selected_index)
del self.config_widgets[self.selected]
self.selected = None
self.selected_index = None
self.name.set("")
self.command.set("")
self.widgets.selection_clear(0, tk.END)
self.save_button.config(state=tk.DISABLED)
self.delete_button.config(state=tk.DISABLED)
def handle_widget_change(self, event):
selection = self.widgets.curselection()
if selection:
self.selected_index = selection[0]
self.selected = self.widgets.get(self.selected_index)
widget = self.config_widgets[self.selected]
self.name.set(widget.name)
self.command.set(widget.command)
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

@ -1,5 +1,6 @@
import tkinter as tk import tkinter as tk
from coretk import appdirs
from coretk.coreclient import CoreServer from coretk.coreclient import CoreServer
from coretk.dialogs.dialog import Dialog from coretk.dialogs.dialog import Dialog
@ -39,12 +40,7 @@ class ServersDialog(Dialog):
scrollbar.grid(row=0, column=1, sticky="ns") scrollbar.grid(row=0, column=1, sticky="ns")
self.servers = tk.Listbox( self.servers = tk.Listbox(
frame, frame, selectmode=tk.SINGLE, yscrollcommand=scrollbar.set
selectmode=tk.SINGLE,
yscrollcommand=scrollbar.set,
relief=tk.FLAT,
highlightthickness=0.5,
bd=0,
) )
self.servers.grid(row=0, column=0, sticky="nsew") self.servers.grid(row=0, column=0, sticky="nsew")
self.servers.bind("<<ListboxSelect>>", self.handle_server_change) self.servers.bind("<<ListboxSelect>>", self.handle_server_change)
@ -113,7 +109,14 @@ class ServersDialog(Dialog):
button.grid(row=0, column=1, sticky="ew") button.grid(row=0, column=1, sticky="ew")
def click_save_configuration(self): def click_save_configuration(self):
pass 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.config["servers"] = servers
appdirs.save_config(self.app.config)
def click_create(self): def click_create(self):
name = self.name.get() name = self.name.get()
@ -126,7 +129,7 @@ class ServersDialog(Dialog):
def click_save(self): def click_save(self):
name = self.name.get() name = self.name.get()
if self.selected and name not in self.app.core.servers: if self.selected:
previous_name = self.selected previous_name = self.selected
self.selected = name self.selected = name
server = self.app.core.servers.pop(previous_name) server = self.app.core.servers.pop(previous_name)

View file

@ -5,7 +5,7 @@ wlan configuration
import tkinter as tk import tkinter as tk
from coretk.dialogs.dialog import Dialog from coretk.dialogs.dialog import Dialog
from coretk.dialogs.nodeicon import NodeIconDialog from coretk.dialogs.nodeicon import IconDialog
class WlanConfigDialog(Dialog): class WlanConfigDialog(Dialog):
@ -167,7 +167,9 @@ class WlanConfigDialog(Dialog):
button.grid(row=0, column=1, padx=2, sticky="ew") button.grid(row=0, column=1, padx=2, sticky="ew")
def click_icon(self): def click_icon(self):
dialog = NodeIconDialog(self, self.app, self.canvas_node) dialog = IconDialog(
self, self.app, self.canvas_node.name, self.canvas_node.image
)
dialog.show() dialog.show()
if dialog.image: if dialog.image:
self.image = dialog.image self.image = dialog.image

View file

@ -11,6 +11,7 @@ from coretk.appdirs import XML_PATH
from coretk.dialogs.canvasbackground import CanvasBackgroundDialog from coretk.dialogs.canvasbackground import CanvasBackgroundDialog
from coretk.dialogs.canvassizeandscale import SizeAndScaleDialog from coretk.dialogs.canvassizeandscale import SizeAndScaleDialog
from coretk.dialogs.hooks import HooksDialog from coretk.dialogs.hooks import HooksDialog
from coretk.dialogs.observerwidgets import ObserverWidgetsDialog
from coretk.dialogs.servers import ServersDialog from coretk.dialogs.servers import ServersDialog
from coretk.dialogs.sessionoptions import SessionOptionsDialog from coretk.dialogs.sessionoptions import SessionOptionsDialog
from coretk.dialogs.sessions import SessionsDialog from coretk.dialogs.sessions import SessionsDialog
@ -403,3 +404,7 @@ class MenuAction:
logging.debug("Click session emulation servers") logging.debug("Click session emulation servers")
dialog = ServersDialog(self.app, self.app) dialog = ServersDialog(self.app, self.app)
dialog.show() dialog.show()
def edit_observer_widgets(self):
dialog = ObserverWidgetsDialog(self.app, self.app)
dialog.show()

93
coretk/coretk/widgets.py Normal file
View file

@ -0,0 +1,93 @@
import tkinter as tk
from functools import partial
class FrameScroll(tk.LabelFrame):
def __init__(self, master=None, cnf={}, **kw):
super().__init__(master, cnf, **kw)
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=1)
self.canvas = tk.Canvas(self, highlightthickness=0)
self.canvas.grid(row=0, columnspan=2, sticky="nsew", padx=2, pady=2)
self.canvas.columnconfigure(0, weight=1)
self.canvas.rowconfigure(0, weight=1)
self.scrollbar = tk.Scrollbar(
self, orient="vertical", command=self.canvas.yview
)
self.scrollbar.grid(row=0, column=2, sticky="ns")
self.frame = tk.Frame(self.canvas, padx=2, pady=2)
self.frame.columnconfigure(0, weight=1)
self.frame_id = self.canvas.create_window(0, 0, anchor="nw", window=self.frame)
self.canvas.update_idletasks()
self.canvas.configure(
scrollregion=self.canvas.bbox("all"), yscrollcommand=self.scrollbar.set
)
self.frame.bind(
"<Configure>",
lambda event: self.canvas.configure(scrollregion=self.canvas.bbox("all")),
)
self.canvas.bind(
"<Configure>",
lambda event: self.canvas.itemconfig(self.frame_id, width=event.width),
)
def clear(self):
for widget in self.frame.winfo_children():
widget.destroy()
class ListboxScroll(tk.LabelFrame):
def __init__(self, master=None, cnf={}, **kw):
super().__init__(master, cnf, **kw)
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.scrollbar = tk.Scrollbar(self, orient=tk.VERTICAL)
self.scrollbar.grid(row=0, column=1, sticky="ns")
self.listbox = tk.Listbox(
self, selectmode=tk.SINGLE, yscrollcommand=self.scrollbar.set
)
self.listbox.grid(row=0, column=0, sticky="nsew")
self.scrollbar.config(command=self.listbox.yview)
class CheckboxList(tk.LabelFrame):
def __init__(self, master=None, cnf={}, clicked=None, **kw):
super().__init__(master, cnf, **kw)
self.clicked = clicked
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=1)
self.canvas = tk.Canvas(self, highlightthickness=0)
self.canvas.grid(row=0, columnspan=2, sticky="nsew", padx=2, pady=2)
self.canvas.columnconfigure(0, weight=1)
self.canvas.rowconfigure(0, weight=1)
self.scrollbar = tk.Scrollbar(
self, orient="vertical", command=self.canvas.yview
)
self.scrollbar.grid(row=0, column=2, sticky="ns")
self.frame = tk.Frame(self.canvas, padx=2, pady=2)
self.frame.columnconfigure(0, weight=1)
self.frame_id = self.canvas.create_window(0, 0, anchor="nw", window=self.frame)
self.canvas.update_idletasks()
self.canvas.configure(
scrollregion=self.canvas.bbox("all"), yscrollcommand=self.scrollbar.set
)
self.frame.bind(
"<Configure>",
lambda event: self.canvas.configure(scrollregion=self.canvas.bbox("all")),
)
self.canvas.bind(
"<Configure>",
lambda event: self.canvas.itemconfig(self.frame_id, width=event.width),
)
def clear(self):
for widget in self.frame.winfo_children():
widget.destroy()
def add(self, name):
var = tk.BooleanVar()
func = partial(self.clicked, name, var)
checkbox = tk.Checkbutton(self.frame, text=name, variable=var, command=func)
checkbox.grid(sticky="w")