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()