add an option to change the data type of a column in tksheet in the gui. Python 3.12 code: import tkinter as tk
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)
    
        for text, command in [("Create New Table", self.create_new_table),
                              ("Import Table", self.import_file),
                              ("Export Table", self.export_table)]:
            label = self.create_clickable_label(normal_buttons_frame, text, command)
            label.pack(padx=0, pady=10, fill='x')
            self.add_custom_hover(label)
    
        self.table_labels_frame = tk.Frame(sidebar, bg=self.SIDEBAR_COLOR)
        self.table_labels_frame.pack(expand=True, fill='both', padx=0, pady=10)
    
        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')

    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."""
        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.cancel_rename()

    def update_table_name(self, old_name, new_name):
        """Update table name and its associated resources."""
        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)
        self.table_labels[new_name] = self.table_labels.pop(old_name)
        self.table_labels[new_name].config(text=new_name)

        if self.selected_table_label == self.table_labels[new_name]:
            self.selected_table_label.config(bg=self.BUTTON_COLOR_SELECTED)
        
        current_file, new_file = self.save_file_dir / f"{old_name}.json", self.save_file_dir / f"{new_name}.json"
        current_file.rename(new_file)

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