Source code for autojob.coordinator.gui.widgets
"""This module defines various specialized ``ttk.Frame`` s for convenience.
The :class:`~RadiobuttonPanel` class defines a :class`.ttk.LabelFrame`
containing :class:`.ttk.Radiobutton` s.
The :class:`ListboxFrame` class defines a :class:`.ttk.Frame` containing a
:class:`tkinter.Listbox`.
The :class:`TreeviewFrame` class defines a :class:`.ttk.Frame` containing a
:class:`.ttk.Treeview`.
"""
import math
import tkinter as tk
from tkinter import ttk
from autojob.coordinator import validation
from autojob.coordinator.classification import ImplementableEnum
[docs]
class RadiobuttonPanel(ttk.LabelFrame):
"""A :class:`~ttk.LabelFrame` containing :class:`~ttk.Radiobutton` s.
This GUI element is designed to allow for the selection of a `Radiobutton`
corresponding to an `ImplementableEnum`.
Attributes:
parent: The :class:`~ttk.Frame` in which the ``RadiobuttonPanel``
resides.
frame: The :class:`~ttk.Frame` in which the `ttk.Radiobutton` s will
reside.
implementable_enum: An :class:`~ImplementableEnum`.
columns: The number of columns used to display to selection options.
rb_var: A :class:`tkinter.IntVar` recording the selected ``Radiobutton``.
rbs: A dictionary mappping ``ImplementableEnum`` s to their
representative ``Radiobutton`` s.
"""
def __init__(
self,
parent: ttk.Frame,
title: str,
implementable_enum: ImplementableEnum,
columns: int,
) -> None:
"""Initialize a ``RadiobuttonPanel``.
Args:
parent: The :class:`~ttk.Frame` in which the ``RadiobuttonPanel``
resides.
title: The title for the ``RadiobuttonPanel``.
implementable_enum: The :class:`.classification.ImplementableEnum`
that will be enumerated by the ``Radiobutton`` s.
columns: The number of columns to be used to display the
``Radiobutton`` s.
"""
super().__init__(parent, text=title)
self.parent: ttk.Frame = parent
self.frame = ttk.Frame(self)
self.frame.pack()
self.implementable_enum = implementable_enum
self.columns = columns
self.rb_var = tk.IntVar()
self.rbs: dict[ImplementableEnum, ttk.Radiobutton] = self.creation()
self.placement()
self.set_states()
[docs]
def creation(
self,
) -> dict[ImplementableEnum, ttk.Radiobutton]:
"""Create ``ttk.Radiobutton`` s for each ``ImplementableEnum``.
Returns:
A dictionary mappping ``ImplementableEnum`` s to their representative
``Radiobutton`` s.
"""
rbs: dict[ImplementableEnum, ttk.Radiobutton] = {}
for i, member in enumerate(self.implementable_enum):
rb = ttk.Radiobutton(
self.frame,
text=member.value.title(),
variable=self.rb_var,
value=i,
)
rbs[member] = rb
return rbs
[docs]
def placement(self) -> None:
"""Organize ``RadiobuttonPanel`` ``ttk.Radiobutton`` s."""
for i, key in enumerate(self.rbs):
self.rbs[key].grid(
column=i % self.columns,
padx=10,
pady=10,
row=math.floor(i / self.columns),
sticky=tk.W,
)
[docs]
def set_states(self) -> None:
"""Set states of ``ttk.Radiobutton`` s based on implementation."""
for key in self.rbs:
if key.is_implemented():
self.rbs[key].configure(state=tk.NORMAL)
if key.is_default():
self.rbs[key].invoke()
else:
self.rbs[key].configure(state=tk.DISABLED)
[docs]
class ListboxFrame(ttk.Frame):
"""A ``ttk.Frame`` preconfigured with a :class:`tkinter.Listbox`.
Attributes:
parent: The :class:`.ttk.Frame` in which the ``ListboxFrame`` will reside.
x_stretch: Whether or not the ``ListboxFrame`` will expand horizontally
to fill the frame.
listbox: The :class:`tkinter.Listbox` within the ``ListboxFrame``.
yscroll: The :class:`tkinter.ttk.Scrollbar` controlling y-scrolling.
xscroll: The :class:`tkinter.ttk.Scrollbar` controlling x-scrolling.
"""
def __init__(self, parent: ttk.Frame, *, x_stretch: bool = False) -> None:
"""Initialize a ``ListboxFrame``.
Args:
parent: The ``ttk.Frame`` in which the ``ListboxFrame`` will reside.
x_stretch: Whether or not the ``ListboxFrame`` will expand horizontally
to fill the frame.. Defaults to False.
"""
super().__init__(parent)
self.parent = parent
self.x_stretch = x_stretch
self.listbox: tk.Listbox = self.create_listbox()
self.yscroll: ttk.Scrollbar = self.create_yscrollbar()
self.xscroll: ttk.Scrollbar = self.create_xscrollbar()
self.organize()
[docs]
def create_listbox(self) -> tk.Listbox:
"""Creates a ``Listbox``."""
return tk.Listbox(self, height=10, selectmode=tk.EXTENDED)
[docs]
def create_yscrollbar(self) -> ttk.Scrollbar:
"""Creates a y-``Scrollbar``."""
return ttk.Scrollbar(self, command=self.listbox.yview)
[docs]
def create_xscrollbar(self) -> ttk.Scrollbar:
"""Creates an x-``Scrollbar``."""
return ttk.Scrollbar(
self, command=self.listbox.xview, orient=tk.HORIZONTAL
)
[docs]
def organize(self) -> None:
"""Organize the ``ListboxFrame`` elements."""
if self.x_stretch:
self.xscroll.pack(fill=tk.X, side=tk.BOTTOM)
self.listbox.configure(xscrollcommand=self.xscroll.set)
self.yscroll.pack(fill=tk.Y, side=tk.RIGHT)
self.listbox.configure(height=15, yscrollcommand=self.yscroll.set)
self.listbox.pack(expand=True, side=tk.LEFT, fill=tk.X)
else:
self.listbox.grid(column=0, row=0)
self.yscroll.grid(column=1, row=0, sticky=tk.N + tk.S)
self.listbox.configure(yscrollcommand=self.yscroll.set)
[docs]
def clear_listbox(self) -> None:
"""Clear the ``Listbox``."""
self.listbox.delete(0, self.listbox.size() - 1)
@property
def items(self) -> tuple[float | int | str | None]:
"""Return the elements of the ``Listbox`` as a list."""
return validation.iter_to_native(
self.listbox.get(0, self.listbox.size() - 1)
)
[docs]
class TreeviewFrame(ttk.Frame):
"""A ``ttk.Frame`` preconfigured with a ``ttk.Treeview``.
Attributes:
parent: The ``ttk.Frame`` in which the ``TreeviewFrame`` will reside.
treeview: The :class:`tkinter.ttk.Treeview` within the ``TreeviewFrame``.
yscroll: The :class:`tkinter.ttk.Scrollbar` controlling y-scrolling.
xscroll: The :class:`tkinter.ttk.Scrollbar` controlling x-scrolling.
"""
def __init__(self, parent: ttk.Frame) -> None:
"""Initialize a ``ListboxFrame``.
Args:
parent: The ``ttk.Frame`` in which the ``ListboxFrame`` will reside.
"""
super().__init__(parent)
self.parent: ttk.Frame = parent
self.treeview: ttk.Treeview = self.create_treeview()
self.yscroll: ttk.Scrollbar = self.create_yscrollbar()
self.xscroll: tk.Scrollbar = self.create_xscrollbar()
self.organize()
[docs]
def create_treeview(self) -> ttk.Treeview:
"""Creates a ``Treeview``."""
return ttk.Treeview(
self, height=10, selectmode=tk.EXTENDED, show="tree"
)
[docs]
def create_yscrollbar(self) -> ttk.Scrollbar:
"""Creates a y-``Scrollbar``."""
return ttk.Scrollbar(self, command=self.treeview.yview)
[docs]
def create_xscrollbar(self) -> ttk.Scrollbar:
"""Creates an x-``Scrollbar``."""
return ttk.Scrollbar(
self, command=self.treeview.xview, orient=tk.HORIZONTAL
)
[docs]
def organize(self) -> None:
"""Organize the ``TreeviewFrame`` elements."""
self.xscroll.pack(side=tk.BOTTOM, fill=tk.X)
self.treeview.configure(xscrollcommand=self.xscroll.set)
self.yscroll.pack(side=tk.RIGHT, fill=tk.Y)
self.treeview.configure(height=15, yscrollcommand=self.yscroll.set)
self.treeview.pack(expand=True, side=tk.LEFT, fill=tk.X)
[docs]
def clear_treeview(self) -> None:
"""Deletes all items in the treeview."""
for item in self.treeview.get_children():
self.treeview.delete(item)
@property
def items(self) -> dict[str, dict]:
"""Return the elements of the ``Treeview`` as a nested dictionary."""
items = {}
def get_descendants(iid: str) -> dict:
descendants = {}
children = self.treeview.get_children(iid)
for child in children:
key = self.treeview.item(child, "text")
if key in items:
msg = f"Duplicate items in treeview: {key}."
raise ValueError(msg)
descendants[key] = get_descendants(child)
return descendants
for top_level_item in self.treeview.get_children():
key = self.treeview.item(top_level_item, "text")
if key in items:
msg = f"Duplicate items in treeview: {key}."
raise ValueError(msg)
items[key] = get_descendants(top_level_item)
return items