"""Implements the 'Data Config' frame"""
import tkinter as tk
from dataclasses import dataclass
from pathlib import Path
from tkinter import filedialog as fd
from tkinter import ttk
from typing import TYPE_CHECKING
from yaml import safe_dump, safe_load
from ...helpers.misc import PROJECT_BUILD_ROOT
if TYPE_CHECKING:
from .plot_gui import PlotFrame
@dataclass
class Column:
"""Container for a Column"""
column_name: str
column_type: str
class DataConfigFrame(ttk.Frame):
"""PlotConfig Frame"""
def __init__(self, parent: ttk.Notebook, root: "PlotFrame") -> None:
super().__init__(parent)
self.root = root
self.columns: list[Column] = []
self.columnconfigure(0, weight=1)
with open(
Path(__file__).parent.parent.parent
/ "cmd_plot"
/ "data_handling"
/ "schemas"
/ "csv_handler.json",
encoding="utf-8",
) as f:
valid_column_types = safe_load(f)["properties"]["columns"][
"additionalProperties"
]["enum"]
font_heading = ("TkDefaultFont", 10, "bold")
ttk.Style().configure("Multiline.TButton", justify="center")
ttk.Style().configure("heading.TButton", font=font_heading, justify="center")
file_frame = ttk.Frame(self, padding=(15, 5))
file_frame.grid(column=0, row=0, sticky="news")
file_frame.columnconfigure(1, weight=1)
file_path_label = ttk.Label(self, text="Data-Config File Path", width=20)
file_path_label.grid(
in_=file_frame, column=0, row=0, pady=(10, 0), sticky="news"
)
self.file_path_entry = ttk.Entry(self, width=10)
self.file_path_entry.insert(
tk.END, str(PROJECT_BUILD_ROOT / "gui" / "data_config.yaml")
)
self.file_path_entry.grid(
in_=file_frame, column=1, columnspan=2, row=0, pady=(10, 0), sticky="we"
)
self.file_path_button = ttk.Button(
self, text="Select File", command=self.open_file_cb
)
self.file_path_button.grid(
in_=file_frame, column=3, row=0, pady=(10, 0), sticky="news"
)
data_frame = ttk.Labelframe(self, text="Data Configuration", padding=(10, 5))
data_frame.grid(column=0, row=1, padx=10, pady=(5, 0), sticky="news")
data_frame.columnconfigure(1, weight=1)
data_frame.columnconfigure(2, weight=1)
skip_label = ttk.Label(self, text="Number of Lines to skip", width=23)
skip_label.grid(in_=data_frame, column=0, row=2, pady=(0, 5), sticky="news")
self.skip_entry = ttk.Entry(self, width=10)
self.skip_entry.insert(tk.END, "0")
self.skip_entry.grid(
in_=data_frame, column=1, row=2, padx=(0, 5), pady=(0, 5), sticky="news"
)
precision_label = ttk.Label(self, text="Precision of Data", width=23)
precision_label.grid(
in_=data_frame, column=0, row=3, pady=(0, 5), sticky="news"
)
self.precision_entry = ttk.Entry(self, width=10)
self.precision_entry.insert(tk.END, "2")
self.precision_entry.grid(
in_=data_frame, column=1, row=3, padx=(0, 5), pady=(0, 5), sticky="news"
)
columns_label = ttk.Label(self, text="Input Columns", width=23)
columns_label.grid(in_=data_frame, column=0, row=4, pady=5, sticky="news")
self.columns_header_entry = ttk.Entry(self, width=10)
self.columns_header_entry.grid(
in_=data_frame, column=1, row=4, padx=(0, 5), pady=5, sticky="news"
)
self.columns_type_entry = ttk.Combobox(
self, width=10, values=valid_column_types
)
self.columns_type_entry.grid(
in_=data_frame, column=2, row=4, pady=5, sticky="news"
)
self.columns_type_entry.current(0)
self.columns_header_entry.insert(tk.END, "name")
self.column_add_button = ttk.Button(
self, text="Add Column", command=self.add_column_cb
)
self.column_add_button.grid(
in_=data_frame, column=3, row=4, pady=5, sticky="news"
)
self.columns_treeview = ttk.Treeview(
self, columns=("column", "type"), show="headings", height=6
)
self.columns_treeview.heading("column", text="Column")
self.columns_treeview.heading("type", text="Type")
self.columns_treeview.grid(
in_=data_frame,
column=1,
columnspan=2,
row=5,
rowspan=6,
pady=5,
sticky="news",
)
button_frame_remove = ttk.Frame(self)
button_frame_remove.grid(
in_=data_frame,
column=3,
row=5,
rowspan=6,
padx=(5, 0),
pady=5,
sticky="news",
)
self.columns_remove_button = ttk.Button(
self,
text="Remove\nSelected\nColumn",
style="Multiline.TButton",
command=self.remove_column_cb,
)
self.columns_remove_button.pack(in_=button_frame_remove, side=tk.TOP)
button_frame_generate = ttk.Frame(self)
button_frame_generate.grid(column=0, row=3, sticky="news")
self.data_config_generate = ttk.Button(
self,
text="Generate\nData Configuration",
command=self.generate_data_config_cb,
style="heading.TButton",
)
self.data_config_generate.pack(in_=button_frame_generate, pady=(2, 2))
def add_column_cb(self) -> None:
"""Add column to List"""
column_name = str(self.columns_header_entry.get().strip())
column_type = str(self.columns_type_entry.get().strip())
if column_name in ("", "name"):
self.root.write_text("Column header has to be given\n")
return
if column_type == "":
self.root.write_text("Column type has to be given\n")
return
self.columns.append(Column(column_name, column_type))
self.columns_treeview.insert("", tk.END, values=(column_name, column_type))
self.columns_header_entry.delete(0, tk.END)
self.columns_type_entry.current(0)
def remove_column_cb(self) -> None:
"""Remove column from List"""
item = self.columns_treeview.focus()
if item == "":
self.root.write_text("Column has to be selected\n")
return
self.columns.pop(self.columns_treeview.index(item))
self.columns_treeview.delete(item)
def open_file_cb(self) -> None:
"""Open filedialog and print it in Entry widget"""
output_directory = fd.asksaveasfilename(
defaultextension=".yaml", filetypes=[("YAML File", "*.yaml")]
)
self.file_path_entry.delete(0, tk.END)
self.file_path_entry.insert(tk.END, output_directory)
def generate_data_config_cb(self) -> None:
"""Generate data configuration file"""
data_file_path = self.file_path_entry.get().strip()
if (data_file_path == "") or (" " in data_file_path):
self.root.write_text(
"Path of the Data-File has to be given as a valid path\n"
)
return
if len(self.columns) == 0:
self.root.write_text("Columns have to be given\n")
return
columns = {}
for column in self.columns:
columns[column.column_name] = column.column_type
try:
general = {
"skip": int(self.skip_entry.get().strip()),
"precision": int(self.precision_entry.get().strip()),
}
except ValueError:
self.root.write_text(
"Number of Lines to skip and Precision of Data have to be given as integers\n"
)
return
data_config = {"general": general, "columns": columns}
Path(data_file_path).parent.absolute().mkdir(parents=True, exist_ok=True)
with open(data_file_path, mode="w", encoding="utf-8") as f:
safe_dump(data_config, f)
self.root.write_text(
f"Data Configuration File has been saved in {data_file_path}\n"
)
self.root.tab_plot.data_config_entry.delete(0, tk.END)
self.root.tab_plot.data_config_entry.insert(tk.END, str(data_file_path))