SYSTEM: You are a Python 3.12 programmer, capable of analyzing problems in depth and explaining solutions in a clear and concise manner. Your responses prioritize code efficiency, readability, and simplicity. You leverage the latest Python features. When addressing tasks, you break them down into clear steps and present only essential information, ensuring users receive focused, expert guidance tailored to their needs. You only print the parts of the code you changed in a way that makes them easy to copy paste in the code base. USER: Implement + symbols next to all categories that allow to create tables under these categories. For example if i have the categories: 1, 2, 3, 4 and i press the + button next to 1 it should create a table between 1 and 2. Python 3.12 Code: import ctypes
import csv
import json
import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog, ttk
from pathlib import Path
from tksheet import Sheet

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:
    """Table Editor application with a sidebar and table management."""

    def __init__(self, root: tk.Tk):
        """Initialize the TableApp."""
        self.root = root
        self.config_dimensions()
        self.config_fonts()
        self.config_colors()
        self.initialize_data()
        self.category_labels: list[tk.Label] = []
        self.setup_ui()
        self.load_categories()
        self.load_tables()

    #--- Configuration ---

    def config_dimensions(self):
        """Configure application dimensions."""
        self.root.title("Database")
        self.root.geometry("1920x1080")
        self.root.minsize(1200, 560)

        self.sidebar_width = 400
        self.static_frame_width = 100
        self.header_height = 190

    def config_fonts(self):
        """Configure application fonts."""
        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}")

    def config_colors(self):
        """Configure application colors."""
        self.text_color = "#333333"
        self.background_color = "#FFFFFF"
        self.sidebar_color = "#F7F8FC"
        self.button_color = "#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 with navigation and actions."""
        sidebar = self._create_frame(
            parent=self.root,
            width=self.sidebar_width,
            bg=self.sidebar_color,
            side='left',
            anchor='nw',
            fill='y'
        )
        
        self._create_frame(
            parent=self.root,
            width=self.static_frame_width,
            bg=self.background_color,
            side='left',
            anchor='nw',
            fill='y'
        )
        sidebar.pack_propagate(False)

        self._add_normal_buttons(sidebar)
        self._add_scrollable_canvas(sidebar)
        self._add_delete_table_section(sidebar)

    def _create_frame(
        self,
        parent: tk.Widget,
        width: int | None,
        bg: str,
        side: str,
        anchor: str,
        fill: str,
        expand: bool = False
    ) -> tk.Frame:
        """Create and pack a frame with specified properties."""
        frame = tk.Frame(parent, width=width, bg=bg, bd=0, relief='flat')
        frame.pack(side=side, anchor=anchor, fill=fill, expand=expand)
        return frame

    def _add_normal_buttons(self, sidebar: tk.Frame):
        """Add navigation buttons to the sidebar."""
        normal_buttons_frame = self._create_frame(
            parent=sidebar,
            width=None,
            bg=self.sidebar_color,
            side='top',
            anchor='nw',
            fill='x',
        )

        create_table_label = self._create_clickable_label(
            parent=normal_buttons_frame,
            text="Create New Table",
            command=self.create_new_table
        )
        create_table_label.pack(fill='x')
        self.add_custom_hover(create_table_label)
        
        create_category_label = self._create_clickable_label(
            parent=normal_buttons_frame,
            text="Create New Category",
            command=self.create_new_category
        )
        create_category_label.pack(fill='x')
        self.add_custom_hover(create_category_label)

        self._add_separator(normal_buttons_frame)

    def _add_scrollable_canvas(self, sidebar: tk.Frame):
        """Add a scrollable canvas to display table and category labels."""
        scrollbar_frame = self._create_frame(
            parent=sidebar,
            width=None,
            bg=self.sidebar_color,
            side='top',
            anchor='nw',
            fill='both',
            expand=True
        )

        canvas = tk.Canvas(scrollbar_frame, bg=self.sidebar_color, highlightthickness=0, width=self.sidebar_width - 15)
        canvas.pack(side="left", fill="both", expand=True)

        self.config_scrollbar_style()

        scrollbar = ttk.Scrollbar(
            scrollbar_frame,
            orient="vertical",
            command=canvas.yview,
            style="Custom.Vertical.TScrollbar"
        )
        scrollbar.pack(side="right", fill="y")

        canvas.configure(yscrollcommand=scrollbar.set)

        self.table_labels_frame = tk.Frame(canvas, bg=self.sidebar_color)
        canvas.create_window((0, 0), window=self.table_labels_frame, anchor="nw")

        self._bind_scrollbar_events(canvas, scrollbar, scrollbar_frame)

    def _bind_scrollbar_events(self, canvas: tk.Canvas, scrollbar: ttk.Scrollbar, scrollbar_frame: tk.Frame):
        """Manage scrollbar visibility based on content size."""
        def configure_scrollbar(event: tk.Event | None = None):
            canvas.update_idletasks()
            canvas_height = scrollbar_frame.winfo_height()
            content_height = self.table_labels_frame.winfo_reqheight()

            if content_height > canvas_height:
                scrollbar.pack(side="right", fill="y")
            else:
                scrollbar.pack_forget()

            canvas.configure(scrollregion=canvas.bbox("all"))

        self.table_labels_frame.bind("<Configure>", configure_scrollbar)
        scrollbar_frame.bind("<Configure>", configure_scrollbar)

    def _add_delete_table_section(self, sidebar: tk.Frame):
        """Add the delete table option to the sidebar."""
        self._add_separator(sidebar)

        delete_table_label = self._create_clickable_label(
            parent=sidebar,
            text="🗑 Delete Selected Table",
            command=self.delete_table
        )
        delete_table_label.pack(side='bottom', anchor='center')
        self.add_custom_hover(delete_table_label)

    def _add_separator(self, parent: tk.Widget):
        """Add a horizontal separator to the parent widget."""
        separator = ttk.Separator(parent, orient='horizontal')
        separator.pack(fill='x')

    def _create_clickable_label(self, parent: tk.Widget, text: str, command, allow_rename: bool = False) -> tk.Label:
        """Create a clickable label with optional rename capability."""
        label = tk.Label(
            parent,
            text=text,
            bg=self.button_color,
            fg=self.text_color,
            cursor="hand2" if not allow_rename else "arrow",
            anchor='w',
        )
        label.bind("<Button-1>", lambda _: command())
        if allow_rename:
            label.bind("<Double-1>", lambda _: self.begin_rename(text))
        return label

    def create_main_frame(self):
        """Create the main frame for displaying tables."""
        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 with filter and a settings button using the same design as sidebar buttons."""
        header_frame = tk.Frame(self.main_frame, height=self.header_height, bg=self.background_color)
        header_frame.pack(fill='x')
        header_frame.pack_propagate(False)
    
        tk.Label(header_frame, text="Filter:", bg=self.background_color, fg=self.text_color).pack(side='left')
    
        self.filter_entry = tk.Entry(header_frame, fg=self.text_color)
        self.filter_entry.pack(side='left')
        self.filter_entry.bind("<KeyRelease>", lambda _: self.apply_filter())
    
        settings_label = tk.Label(
            header_frame,
            text="...",
            bg=self.background_color,
            fg=self.text_color,
            cursor="hand2",
            anchor='w'
        )
        settings_label.place(relx=1.0, rely=0.0, anchor='ne', x=-40, y=20)
    
        settings_menu = tk.Menu(settings_label, tearoff=0)
        settings_menu.add_command(label="Export Table", command=self.export_table)
        settings_menu.add_command(label="Import Table", command=self.import_file)
        settings_menu.add_command(label="Rename Headers", command=self.rename_headers)
    
        def show_settings_menu(event):
            settings_menu.post(event.x_root, event.y_root)
    
        settings_label.bind("<Button-1>", show_settings_menu)
    
        def on_enter_settings(_):
            settings_label.config(bg=self.button_color_selected)
    
        def on_leave_settings(_):
            settings_label.config(bg=self.background_color)
    
        settings_label.bind("<Enter>", on_enter_settings)
        settings_label.bind("<Leave>", on_leave_settings)

    def create_table_frame(self):
        """Create the frame to hold the active 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 load from the data file."""
        self.data_file = Path("data.json")
        self.data: dict = {"categories": [], "tables": {}}
        self.load_data()

        self.tables: dict[str, Sheet] = {}
        self.table_labels: dict[str, tk.Label] = {}
        self.headers: dict[str, list[str]] = {}
        self.current_table_name: str | None = None
        self.rename_entry: tk.Entry | None = None
        self.selected_table_label: tk.Label | None = None

    def load_data(self):
        """Load all data (categories and tables) from the data file."""
        if self.data_file.exists():
            try:
                with self.data_file.open('r', encoding='utf-8') as f:
                    self.data = json.load(f)
                    self.data.setdefault("categories", [])
                    self.data.setdefault("tables", {})
            except Exception as e:
                messagebox.showerror("Error Loading Data", f"Failed to load data: {e}")

    def save_data_file(self):
        """Save all data (categories and tables) to the data file."""
        try:
            with self.data_file.open('w', encoding='utf-8') as f:
                json.dump(self.data, f, indent=4)
        except Exception as e:
            messagebox.showerror("Error Saving Data", f"Failed to save data: {e}")

    def load_categories(self):
        """Load categories from the data."""
        for category_name in self.data.get("categories", []):
            self.add_category_label(category_name)

    def save_categories(self):
        """Save categories to the data file."""
        category_names = [label.cget("text") for label in self.category_labels]
        self.data["categories"] = category_names
        self.save_data_file()

    def load_tables(self):
        """Load existing tables from the data."""
        for table_name, table_content in self.data.get("tables", {}).items():
            headers = table_content.get("headers", [])
            table_data = table_content.get("data", [])
            self.add_table(table_name, table_data)
            self.headers[table_name] = headers

    def create_new_category(self):
        """Create a new category in the sidebar with a default name."""
        category_name = self.ensure_unique_category_name("Untitled")
        self.add_category_label(category_name)

    def ensure_unique_category_name(self, category_name: str) -> str:
        """Ensure the category name is unique by appending suffixes if necessary."""
        existing_names = [label.cget("text") for label in self.category_labels]
        original_name = category_name
        counter = 1
        while category_name in existing_names:
            category_name = f"{original_name} {counter}"
            counter += 1
        return category_name

    def add_category_label(self, category_name: str):
        """Add a category label to the sidebar."""
        category_label = self._create_clickable_label(
            parent=self.table_labels_frame,
            text=category_name,
            command=lambda: None,
            allow_rename=True
        )
        category_label.pack(fill='x', pady=(10, 0))
        self.category_labels.append(category_label)
        self.add_custom_hover(category_label)
        self.save_categories()

    def create_new_table(self):
        """Create a new table and select it."""
        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 table from a CSV or JSON file."""
        file_path = filedialog.askopenfilename(
            title="Import 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: str, headers: list[str], table_data: list[list[str]]):
        """Add a table using existing data from a file."""
        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.save_table_data(table_name)
        self.load_selected_table(table_name)

    def ensure_unique_table_name(self, table_name: str) -> str:
        """Ensure the table name is unique by appending suffixes if necessary."""
        original_name = table_name
        counter = 1
        while table_name in self.tables:
            table_name = f"{original_name} (New {counter})"
            counter += 1
        return table_name

    def save_table_data(self, table_name: str):
        """Save the current data of a table to the data file."""
        data = {
            "headers": self.headers[table_name],
            "data": self.tables[table_name].get_sheet_data()
        }
        self.data["tables"][table_name] = data
        self.save_data_file()
        print(f"Data for {table_name} saved successfully.")

    def save_data(self, table_name: str):
        """Save the current data of a table."""
        self.save_table_data(table_name)

    def add_table(self, table_name: str, data: list[list[str]] | None = None):
        """Add a new table to the application."""
        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 _: self.save_data(table_name))])
        sheet.pack_forget()

        self.tables[table_name] = sheet
        self.headers.setdefault(table_name, sheet.headers())
        self.data["tables"].setdefault(table_name, {"headers": self.headers[table_name], "data": data or default_data})

        self.create_table_label(table_name)

    def create_table_label(self, table_name: str):
        """Create a label for the table in the sidebar."""
        label = self._create_clickable_label(
            parent=self.table_labels_frame,
            text=table_name,
            command=lambda: self.load_selected_table(table_name),
            allow_rename=True
        )
        label.pack(fill='x')
        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: str):
        """Remove a table from the application."""
        self.tables[table_name].destroy()
        self.table_labels[table_name].destroy()
        del self.tables[table_name]
        del self.headers[table_name]
        del self.table_labels[table_name]
        self.data["tables"].pop(table_name, None)
        self.save_data_file()
        self.current_table_name = None
        self.selected_table_label = None

    def load_selected_table(self, table_name: str):
        """Display the selected table and hide others."""
        for name, sheet in self.tables.items():
            if name == table_name:
                sheet.pack(expand=True, fill='both')
                self.current_table_name = name
                sheet.headers(self.headers[name])
                self.apply_filter()
                self.selected_table_label = self.table_labels[name]
                self.selected_table_label.config(bg=self.button_color_selected)
            else:
                sheet.pack_forget()
                self.table_labels[name].config(bg=self.button_color)

    def apply_filter(self):
        """Filter table data based on the filter entry."""
        if not self.current_table_name:
            return

        term = self.filter_entry.get().lower()
        original_data = self.data["tables"][self.current_table_name]["data"]
        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):
        """Rename the headers of the 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, item_name: str):
        """Start renaming an item by replacing its label with an entry."""
        if item_name in self.table_labels:
            label = self.table_labels[item_name]
        else:
            label = next((lbl for lbl in self.category_labels if lbl.cget("text") == item_name), None)
    
        if not label:
            messagebox.showerror("Error", f"Item '{item_name}' not found.")
            return
    
        self.cancel_rename()
    
        self.rename_entry = tk.Entry(
            self.table_labels_frame,
            bg=label.cget("bg"),
            fg=self.text_color,
            relief='flat'
        )
        self.rename_entry.insert(0, item_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(item_name))
        self.rename_entry.bind('<FocusOut>', lambda _: self.cancel_rename())

    def end_rename(self, old_name: str):
        """Finalize the renaming of an item."""
        new_name = self.rename_entry.get().strip()

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

        if new_name != old_name:
            if old_name in self.table_labels:
                self.rename_table(old_name, new_name)
            else:
                self.rename_category(old_name, new_name)

        self.cancel_rename()

    def rename_category(self, old_name: str, new_name: str):
        """Rename a category."""
        label = next((lbl for lbl in self.category_labels if lbl.cget("text") == old_name), None)
        if label:
            label.config(text=new_name)
            label.bind("<Double-1>", lambda _: self.begin_rename(new_name))
            self.save_categories()

    def rename_table(self, old_name: str, new_name: str):
        """Rename a table and update the data file."""
        self.tables[new_name] = self.tables.pop(old_name)
        self.headers[new_name] = self.headers.pop(old_name)
        self.table_labels[new_name] = self.table_labels.pop(old_name)
        self.data["tables"][new_name] = self.data["tables"].pop(old_name)
        
        label = self.table_labels[new_name]
        label.config(text=new_name)
        label.bind("<Button-1>", lambda _: self.load_selected_table(new_name))
        label.bind("<Double-1>", lambda _: self.begin_rename(new_name))

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

        self.save_data_file()

    def cancel_rename(self):
        """Cancel the ongoing rename operation."""
        if self.rename_entry:
            self.rename_entry.destroy()
            self.rename_entry = None

    def add_custom_hover(self, label: tk.Label):
        """Add hover effects to a label."""
        def on_enter(_: tk.Event):
            if label != self.selected_table_label:
                label.config(bg=self.button_color_selected)

        def on_leave(_: tk.Event):
            if label != self.selected_table_label:
                label.config(bg=self.button_color)

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

    #--- Custom Designs --- 
        
    def config_scrollbar_style(self):
        """Configure the custom scrollbar style."""
        self.style = ttk.Style()
        self.style.theme_use("default")
        
        self.style.configure("Custom.Vertical.TScrollbar",
                             troughcolor=self.sidebar_color,
                             background="#c0c1c4",
                             borderwidth=0,        
                             arrowcolor=self.text_color)

        self.style.map("Custom.Vertical.TScrollbar",
                       background=[('active', '#878789')])
        
        self.style.layout("Custom.Vertical.TScrollbar",
                          [
                              ('Vertical.Scrollbar.trough',
                               {'children': [('Vertical.Scrollbar.thumb',
                                              {'expand': '1', 'sticky': 'ns'})],
                                'sticky': 'ns'})
                          ])

    #--- Export & Import Methods ---

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

        file_path = filedialog.asksaveasfilename(
            title="Export File",
            defaultextension=".json",
            filetypes=[("JSON files", "*.json"), ("CSV files", "*.csv")],
            initialfile=self.current_table_name
        )
        if not file_path:
            return

        try:
            headers = self.headers[self.current_table_name]
            data = [headers] + self.tables[self.current_table_name].get_sheet_data()
            if file_path.endswith('.csv'):
                self.save_table_data_as_csv(file_path, data)
            elif file_path.endswith('.json'):
                self.save_table_data_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_table_data_as_csv(self, file_path: str, data: list[list[str]]):
        """Save table data in CSV format."""
        with open(file_path, 'w', newline='', encoding='utf-8') as file:
            csv.writer(file).writerows(data)

    def save_table_data_as_json(self, file_path: str, data: list[list[str]]):
        """Save table data in JSON format."""
        with open(file_path, 'w', encoding='utf-8') as file:
            json.dump(data, file, indent=4)

#--- Main ---

if __name__ == "__main__":
    set_dpi_awareness()
    root = tk.Tk()
    app = TableApp(root)
    root.mainloop()