Add a button that creates renameable categories in the sidebar the categories are just there to order the tables in to make it easier to navigate. Create them named "Untitled". The user should be able to drag tables in these categories to order them in the category. To visually distinguish categories and tables indent the tables by 5. Python 3.12 code: import tkinter as tk
import tkinter.ttk as ttk
from tkinter import simpledialog, messagebox, filedialog
from tksheet import Sheet
import json
import csv
from pathlib import Path
import ctypes

def set_dpi_awareness():
"""Enable DPI awareness for high DPI settings."""
try:
ctypes.windll.shcore.SetProcessDpiAwareness(1)
except Exception as e:
print(f"Error setting DPI awareness: {e}")

class TableApp:
def init(self, root):
"""Initialize the Table Editor application."""
self.root = root
self.root.title("Table Editor with Sidebar")
self.root.geometry("1280x720")
self.config_fonts_and_colors()
self.initialize_data()
self.setup_ui()
self.load_tables()

def config_fonts_and_colors(self):
    """Configure fonts and colors."""
    font_name, font_size, font_style = "Literata", 12, "normal"
    self.default_font = (font_name, font_size, font_style)
    self.root.option_add("*Font", f"{font_name} {font_size}")
    self.TEXT_COLOR, self.BACKGROUND_COLOR = '#333333', '#FFFFFF'
    self.SIDEBAR_COLOR, self.BUTTON_COLOR = '#F7F8FC', '#F7F8FC'
    self.BUTTON_COLOR_SELECTED = '#EAEDEF'

def setup_ui(self):
    """Set up the user interface components."""
    self.create_sidebar()
    self.create_main_frame()
    self.create_header_frame()
    self.create_table_frame()

def create_sidebar(self):
    """Create the sidebar and its components."""
    sidebar = tk.Frame(self.root, width=200, bg=self.SIDEBAR_COLOR, bd=0, relief='flat')
    sidebar.pack(expand=False, fill='y', side='left', anchor='nw')
    tk.Frame(self.root, width=100, bg=self.BACKGROUND_COLOR, bd=0, relief='flat').pack(expand=False, fill='y', side='left', anchor='nw')
    
    # Create a frame for normal buttons
    normal_buttons_frame = tk.Frame(sidebar, bg=self.SIDEBAR_COLOR)
    normal_buttons_frame.pack(expand=False, fill='x', pady=10)
    
    # Create the "Create New Table" button
    create_table_label = self.create_clickable_label(normal_buttons_frame, "Create New Table", self.create_new_table)
    create_table_label.pack(padx=0, pady=10, fill='x')
    self.add_custom_hover(create_table_label)

    # Add a horizontal separator below the "Create New Table" button
    horiz_separator_create = ttk.Separator(normal_buttons_frame, orient='horizontal')
    horiz_separator_create.pack(fill='x', pady=5)

    self.table_labels_frame = tk.Frame(sidebar, bg=self.SIDEBAR_COLOR)
    self.table_labels_frame.pack(expand=True, fill='both', padx=0, pady=10)

    # Add a horizontal separator above the "Delete Selected Table" button
    horiz_separator_delete = ttk.Separator(sidebar, orient='horizontal')
    horiz_separator_delete.pack(expand=False, fill='x', pady=0)

    delete_table_label = self.create_clickable_label(
        sidebar, "Delete Selected Table", self.delete_table)
    delete_table_label.pack(padx=0, pady=10, fill='x', side='bottom')
    self.add_custom_hover(delete_table_label)

def create_main_frame(self):
    """Create the main frame of the application."""
    self.main_frame = tk.Frame(self.root)
    self.main_frame.pack(expand=True, fill='both', side='right')

def create_header_frame(self):
    """Create the header frame with filter and rename options."""
    header_frame = tk.Frame(self.main_frame, height=50, bg=self.BACKGROUND_COLOR)
    header_frame.pack(fill='x')

    tk.Label(header_frame, text="Filter:", bg=self.BACKGROUND_COLOR, fg=self.TEXT_COLOR).pack(side='left', padx=5, pady=10)
    self.filter_entry = tk.Entry(header_frame, fg=self.TEXT_COLOR)
    self.filter_entry.pack(side='left', padx=5, pady=10)
    self.filter_entry.bind("<KeyRelease>", lambda event: self.apply_filter())

    rename_headers = self.create_clickable_label(header_frame, "Rename Headers", self.rename_headers)
    rename_headers.pack(side='left', padx=5, pady=10, fill='x')

    import_table = self.create_clickable_label(header_frame, "Import Table", self.import_file)
    import_table.pack(side='right', padx=5, pady=10, fill='x')

    export_table = self.create_clickable_label(header_frame, "Export Table", self.export_table)
    export_table.pack(side='right', padx=5, pady=10, fill='x')

def create_table_frame(self):
    """Create the frame to hold the table."""
    self.table_frame = tk.Frame(self.main_frame, bg=self.BACKGROUND_COLOR)
    self.table_frame.pack(expand=True, fill='both')

def initialize_data(self):
    """Initialize data structures and variables."""
    self.save_file_dir = Path("tables")
    self.tables = {}
    self.table_labels = {}
    self.original_data = {}
    self.headers = {}
    self.current_table_name = None
    self.rename_entry = None
    self.selected_table_label = None
    self.save_file_dir.mkdir(exist_ok=True)

def create_clickable_label(self, parent, text, command, allow_rename=True):
    """Create a clickable Label widget."""
    label = tk.Label(
        parent, text=text, bg=self.BUTTON_COLOR, fg=self.TEXT_COLOR,
        cursor='arrow', anchor='w', padx=10, pady=5
    )
    label.bind("<Button-1>", lambda e: command())
    if allow_rename:
        label.bind("<Double-1>", lambda e: self.begin_rename(text))
    return label

def create_new_table(self):
    """Create a new table and add it to the available tables list."""
    table_name = f"Table {len(self.tables) + 1}"
    self.add_table(table_name)
    self.load_selected_table(table_name)

def import_file(self):
    """Import a CSV or JSON file to create a new table."""
    file_path = filedialog.askopenfilename(
        title="Open File",
        filetypes=[("JSON files", "*.json"), ("CSV files", "*.csv")]
    )
    if not file_path:
        return

    try:
        if file_path.endswith('.csv'):
            with open(file_path, newline='', encoding='utf-8') as csvfile:
                data = list(csv.reader(csvfile))
        elif file_path.endswith('.json'):
            with open(file_path, 'r', encoding='utf-8') as jsonfile:
                data = json.load(jsonfile)
        else:
            raise ValueError("Unsupported file format.")

        headers, table_data = data[0], data[1:]
        self.add_table_from_data(file_path, headers, table_data)

    except Exception as e:
        messagebox.showerror("File Import Error", str(e))

def add_table_from_data(self, file_path, headers, table_data):
    """Add a table with existing data."""
    table_name = Path(file_path).stem
    table_name = self.ensure_unique_table_name(table_name)

    self.add_table(table_name, table_data)
    self.headers[table_name] = headers
    self.load_selected_table(table_name)
    self.save_data(table_name)

def ensure_unique_table_name(self, table_name):
    """Ensure that the table name is unique."""
    if table_name in self.tables:
        table_name += " (New)"
        while table_name in self.tables:
            table_name += " (New)"
    return table_name

def export_table(self):
    """Prompt user to export the current table to CSV or JSON."""
    if not self.current_table_name:
        messagebox.showwarning("No Table Selected", "Please select a table to export.")
        return

    file_path = filedialog.asksaveasfilename(
        title="Save As",
        defaultextension=".json",
        filetypes=[("JSON files", "*.json"), ("CSV files", "*.csv")],
        initialfile=self.current_table_name
    )

    if not file_path:
        return

    try:
        data = [self.headers[self.current_table_name]] + self.tables[self.current_table_name].get_sheet_data()
        if file_path.endswith('.csv'):
            self.save_as_csv(file_path, data)
        elif file_path.endswith('.json'):
            self.save_as_json(file_path, data)
        else:
            raise ValueError("Unsupported file format.")
        
        messagebox.showinfo("Export Successful", f"Table '{self.current_table_name}' has been exported successfully.")

    except Exception as e:
        messagebox.showerror("Export Failed", f"An error occurred during export: {e}")

def save_as_csv(self, file_path, data):
    """Save data as a CSV file."""
    with open(file_path, 'w', newline='', encoding='utf-8') as csvfile:
        csv.writer(csvfile).writerows(data)

def save_as_json(self, file_path, data):
    """Save data as a JSON file."""
    with open(file_path, 'w', encoding='utf-8') as jsonfile:
        json.dump(data, jsonfile, indent=4)

def add_table(self, table_name, data=None):
    """Add a table to the UI and internal storage."""
    default_data = [["" for _ in range(5)] for _ in range(10)]
    sheet = Sheet(
        self.table_frame,
        data=data or default_data,
        show_table=True,
        headers=None,
        font=self.default_font,
        header_font=self.default_font,
        auto_resize_columns=True
    )
    sheet.set_options(table_fg=self.TEXT_COLOR, header_fg=self.TEXT_COLOR)
    sheet.enable_bindings()
    sheet.extra_bindings([("end_edit_cell", lambda _, tn=table_name: self.save_data(tn))])
    sheet.pack_forget()

    self.tables[table_name] = sheet
    self.original_data[table_name] = sheet.get_sheet_data()
    self.headers.setdefault(table_name, sheet.headers())

    self.create_table_label(table_name)

def create_table_label(self, table_name):
    """Create a clickable label for the table in the sidebar."""
    label = self.create_clickable_label(
        self.table_labels_frame, table_name,
        lambda name=table_name: self.load_selected_table(name)
    )
    label.pack(fill='x', pady=2)
    self.table_labels[table_name] = label
    self.add_custom_hover(label)

def delete_table(self):
    """Delete the currently selected table."""
    if not self.current_table_name:
        messagebox.showwarning("No Table Selected", "Please select a table to delete.")
        return

    if messagebox.askyesno("Delete Table", f"Are you sure you want to delete the table '{self.current_table_name}'?"):
        self.remove_table(self.current_table_name)

def remove_table(self, table_name):
    """Remove a table by its name."""
    self.tables[table_name].destroy()
    self.table_labels[table_name].destroy()
    del self.tables[table_name], self.original_data[table_name]
    del self.headers[table_name], self.table_labels[table_name]
    (self.save_file_dir / f"{table_name}.json").unlink(missing_ok=True)
    self.current_table_name, self.selected_table_label = None, None

def load_tables(self):
    """Load existing tables from the directory."""
    for file_path in self.save_file_dir.glob("*.json"):
        table_name = file_path.stem
        with open(file_path, 'r') as file:
            data = json.load(file)
            
        headers, table_data = data.pop(0), data
        self.add_table(table_name, table_data)
        self.headers[table_name] = headers

def load_selected_table(self, table_name):
    """Display the selected table and hide others."""
    for name, sheet in self.tables.items():
        is_selected = name == table_name
        sheet.pack(expand=is_selected, fill='both') if is_selected else sheet.pack_forget()
        if is_selected:
            self.current_table_name = table_name
            sheet.headers(self.headers[name])
            self.apply_filter()
            selected_label, self.selected_table_label = self.table_labels[name], self.table_labels[name]
            selected_label.config(bg=self.BUTTON_COLOR_SELECTED)
        else:
            self.table_labels[name].config(bg=self.BUTTON_COLOR)
    
def save_data(self, table_name):
    """Save current data of a table to a file."""
    data = [self.headers[table_name]] + self.tables[table_name].get_sheet_data()
    file_path = self.save_file_dir / f"{table_name}.json"
    with file_path.open('w') as file:
        json.dump(data, file)
    print(f"Data for {table_name} saved successfully.")

def apply_filter(self):
    """Apply filtering based on the entered term."""
    if not self.current_table_name:
        return

    term = self.filter_entry.get().lower()
    original_data = self.original_data[self.current_table_name]
    filtered_data = [row for row in original_data if any(term in str(cell).lower() for cell in row)]
    self.tables[self.current_table_name].set_sheet_data(filtered_data)

def rename_headers(self):
    """Prompt user to rename headers of the currently selected table."""
    if not self.current_table_name:
        messagebox.showwarning("No Table Selected", "Please select a table to rename headers.")
        return

    current_headers = self.headers[self.current_table_name]
    new_headers = simpledialog.askstring(
        "Rename Headers",
        "Enter new headers (comma-separated):",
        initialvalue=", ".join(current_headers)
    )

    if new_headers:
        header_list = [header.strip() for header in new_headers.split(',')]
        self.headers[self.current_table_name] = header_list
        self.tables[self.current_table_name].headers(header_list)
        self.save_data(self.current_table_name)

def begin_rename(self, table_name):
    """Begin renaming of the table with a double click."""
    if table_name not in self.table_labels:
        messagebox.showerror("Error", f"Table '{table_name}' not found.")
        return

    label = self.table_labels[table_name]
    if self.rename_entry:
        self.rename_entry.destroy()

    self.rename_entry = tk.Entry(self.table_labels_frame, bg=label.cget("bg"), fg=self.TEXT_COLOR, relief='flat')
    self.rename_entry.insert(0, table_name)
    self.rename_entry.place(x=label.winfo_x(), y=label.winfo_y(), width=label.winfo_width(), height=label.winfo_height())
    self.rename_entry.focus()
    self.rename_entry.select_range(0, tk.END)
    self.rename_entry.bind('<Return>', lambda _: self.end_rename(table_name))
    self.rename_entry.bind('<FocusOut>', lambda _: self.cancel_rename())

def end_rename(self, old_name):
    """Finish the renaming process for the specified table."""
    new_name = self.rename_entry.get().strip()

    if not new_name:
        messagebox.showerror("Invalid Name", "Table name cannot be empty.")
        self.rename_entry.focus_set()
        return

    if new_name in self.tables and new_name != old_name:
        messagebox.showerror("Duplicate Name", f"A table named '{new_name}' already exists.")
        self.rename_entry.focus_set()
        return

    if new_name != old_name:
        self.update_table_name(old_name, new_name)
        self.load_selected_table(new_name)

    self.cancel_rename()

def update_table_name(self, old_name, new_name):
    """Update table name and its associated resources."""
    if new_name not in self.tables:
        self.tables[new_name] = self.tables.pop(old_name)
        self.original_data[new_name] = self.original_data.pop(old_name)
        self.headers[new_name] = self.headers.pop(old_name)

        label = self.table_labels.pop(old_name)
        label.config(text=new_name)
        label.bind("<Button-1>", lambda e, tn=new_name: self.load_selected_table(tn))
        label.bind("<Double-1>", lambda e, tn=new_name: self.begin_rename(tn))  # Update to bind new name

        self.table_labels[new_name] = label

        if self.selected_table_label == label:
            self.selected_table_label.config(bg=self.BUTTON_COLOR_SELECTED)

        current_file = self.save_file_dir / f"{old_name}.json"
        new_file = self.save_file_dir / f"{new_name}.json"
        current_file.rename(new_file)

        self.load_selected_table(new_name)

def cancel_rename(self):
    """Cancel the renaming entry."""
    if self.rename_entry:
        self.rename_entry.destroy()
        self.rename_entry = None

def add_custom_hover(self, label):
    """Add custom hover effects to a label."""
    def on_enter(e):
        if label != self.selected_table_label:
            label.config(bg=self.BUTTON_COLOR_SELECTED)

    def on_leave(e):
        if label != self.selected_table_label:
            label.config(bg=self.BUTTON_COLOR)

    label.bind("<Enter>", on_enter)
    label.bind("<Leave>", on_leave)

if name == "main":
set_dpi_awareness()
root = tk.Tk()
app = TableApp(root)
root.mainloop()