import customtkinter as ctk from tkinter import filedialog, messagebox ctk.set_appearance_mode("System") ctk.set_default_color_theme("dark-blue") class MainApp(ctk.CTk): def __init__(self): super().__init__() self.title("Liskod") self.geometry("400x403") self.resizable(False, False) # --- Data State --- self.current_file_path = None self.list_in_memory = [] self.set_in_memory = set() self.any_change = False # --- Grid Configuration --- self.grid_rowconfigure(0, weight=0) self.grid_rowconfigure(1, weight=0) self.grid_rowconfigure(2, weight=0) self.grid_columnconfigure(0, weight=1) # ============================================================ # ROW 0: File Input & Mode Checkbox # ============================================================ self.row1_frame = ctk.CTkFrame(self) self.row1_frame.grid(row=0, column=0, padx=20, pady=(15, 10), sticky="ew") self.row1_frame.grid_columnconfigure(0, weight=1) self.row1_frame.grid_columnconfigure(1, weight=0) self.var_edit_mode = ctk.BooleanVar(value=False) self.chk_mode = ctk.CTkCheckBox( self.row1_frame, text="Edit Existing File", font=("Arial", 14), variable=self.var_edit_mode, command=self.toggle_confirm ) self.chk_mode.grid(row=0, column=0, padx=10, pady=(15, 5), sticky="w") self.sort_toggle = ctk.BooleanVar(value=False) self.chk_mode2 = ctk.CTkCheckBox( self.row1_frame, text="Sort Out", font=("Arial", 14), variable=self.sort_toggle, command=None ) self.chk_mode2.grid(row=0, column=1, pady=(15, 5), sticky="e") self.entry_filepath = ctk.CTkEntry( self.row1_frame, text_color="gray", font=("Arial", 14), placeholder_text="New File (Save As)", height=35 ) self.entry_filepath.grid(row=1, column=0, padx=(10, 0), pady=5, sticky="ew") self.entry_filepath.configure(state="disabled") self.btn_browse = ctk.CTkButton( self.row1_frame, text="Browse", command=self.browse_file, width=80, height=35, font=("Arial", 14), ) self.btn_browse.grid(row=1, column=1, padx=(10, 10), pady=10) # ============================================================ # ROW 1: Preview Window # ============================================================ self.preview_frame = ctk.CTkFrame(self) self.preview_frame.grid(row=1, column=0, padx=20, pady=(5, 10), sticky="ew") self.preview_frame.grid_columnconfigure(0, weight=1) self.lbl_preview = ctk.CTkLabel(self.preview_frame, text="Preview", font=("Arial", 14)) self.lbl_preview.grid(row=0, column=0, padx=10, pady=(5, 0), sticky="w") self.txt_preview = ctk.CTkTextbox(self.preview_frame, height=110) self.txt_preview.grid(row=1, column=0, padx=10, pady=(5, 10), sticky="ew") self.txt_preview.configure(state="disabled") self.input_container = ctk.CTkFrame(self.preview_frame, fg_color="transparent") self.input_container.grid(row=2, column=0, padx=10, pady=(5, 10), sticky="ew") self.input_container.grid_columnconfigure(0, weight=1) self.entry_text = ctk.CTkEntry(self.input_container, placeholder_text="Type content here...", height=35, font=("Arial", 14)) self.entry_text.grid(row=0, column=0, padx=(0, 10), sticky="ew") self.entry_text.bind("", lambda event: self.add_line_to_memory()) self.btn_add = ctk.CTkButton( self.input_container, text="Add", width=80, font=("Arial", 14), command=self.add_line_to_memory, height=35 ) self.btn_add.grid(row=0, column=1, sticky="e") # ============================================================ # ROW 2: Save Button # ============================================================ self.row3_frame = ctk.CTkFrame(self, fg_color="transparent") self.row3_frame.grid(row=3, column=0, padx=20, pady=(5, 20), sticky="ew") self.row3_frame.grid_columnconfigure(0, weight=0) self.row3_frame.grid_columnconfigure(1, weight=0) self.last_status = ctk.CTkLabel( self.row3_frame, height=35, width=290, fg_color="#282828", corner_radius=4, text_color="gray", text="Status", font=("Arial", 14), ) self.last_status.grid(row=0, column=0, padx=(0, 10), sticky="ew") self.btn_save = ctk.CTkButton( self.row3_frame, text="Save", height=35, width=60, font=("Arial", 14), fg_color="green", hover_color="darkgreen", command=self.save_action ) self.btn_save.grid(row=0, column=1, sticky="e") self.toggle_mode() self.protocol("WM_DELETE_WINDOW", self.on_closing) # --- Logic Methods --- def toggle_confirm(self): if len(self.list_in_memory) > 0: new_state = self.var_edit_mode.get() action = "enable" if new_state else "disable" answer = messagebox.askyesno( title="Confirmation", message=f"Are you sure you want to {action} this setting?" ) if answer: self.delete_memory() self.update_preview() self.toggle_mode() else: self.var_edit_mode.set(not new_state) else: self.toggle_mode() self.update_preview() def toggle_mode(self): new_state = self.var_edit_mode.get() if new_state: # Checked: Edit Existing Mode self.btn_browse.configure(state="normal") self.entry_filepath.configure(state="normal") self.entry_filepath.delete(0, "end") if self.current_file_path: self.entry_filepath.insert(0, f"...{self.current_file_path[-30:]}") else: self.entry_filepath.insert(0, "No file loaded...") self.btn_add.configure(state="disabled") self.entry_filepath.configure(state="disabled") else: # Unchecked: New File Mode self.current_file_path = None self.btn_browse.configure(state="disabled") self.btn_add.configure(state="normal") self.entry_filepath.configure(state="normal") self.entry_filepath.delete(0, "end") self.entry_filepath.insert(0, "Create New File") self.entry_filepath.configure(state="disabled") def browse_file(self): filename = filedialog.askopenfilename( title="Select a Text File", filetypes=[("Text files", "*.txt")] ) if filename: if not filename.lower().endswith('.txt'): messagebox.showerror("Error", "Only .txt files are allowed!") return self.current_file_path = filename self.entry_filepath.configure(state="normal") self.entry_filepath.delete(0, "end") self.entry_filepath.insert(0, f"...{filename[-30:]}") self.entry_filepath.configure(state="disabled") try: with open(filename, "r", encoding="utf-8") as f: self.list_in_memory = [line.strip() for line in f] self.set_in_memory = set(self.list_in_memory) duplicates = len(self.list_in_memory) - len(self.set_in_memory) self.last_status.configure( text=f"File loaded, {duplicates} duplicate(s) removed", text_color="#9efc90" ) self.list_in_memory = list(dict.fromkeys(self.list_in_memory)) self.btn_add.configure(state="normal") self.update_preview() except Exception as e: messagebox.showerror("Error", f"Could not read file:\n{e}") self.last_status.configure( text=f"Could not read file:\n{e}", text_color="#ff9d9d" ) def add_line_to_memory(self): text = self.entry_text.get() if not text: return clean_text = text.replace('\n', '').replace('\r', '').strip() if clean_text in self.set_in_memory: self.entry_text.delete(0, "end") self.last_status.configure( text=f"Line \"{clean_text[:10]}\" already exists!", text_color="#ff9d9d" ) else: if not len(clean_text) == 0: self.any_change = True self.list_in_memory.append(clean_text) self.set_in_memory.add(clean_text) self.entry_text.delete(0, "end") self.update_preview() self.last_status.configure( text=f"Line \"{clean_text[:10]}\" added!", text_color="#9efc90" ) self.entry_text.delete(0, "end") def delete_memory(self): self.list_in_memory = [] self.set_in_memory = set() def update_preview(self): self.txt_preview.configure(state="normal") self.txt_preview.delete("1.0", "end") if not self.list_in_memory: display_text = "" elif len(self.list_in_memory) > 5: last_five = ["..."] + self.list_in_memory[-5:] display_text = "\n".join(last_five) else: last_five = self.list_in_memory[-5:] display_text = "\n".join(last_five) self.txt_preview.insert("1.0", display_text) self.txt_preview.configure(state="disabled") def save_action(self): if len(self.list_in_memory) <= 0: return target_path = filedialog.asksaveasfilename( title="Save As", defaultextension=".txt", filetypes=[("Text files", "*.txt")] ) if not target_path: return try: with open(target_path, "w", encoding="utf-8") as f: to_be_saved = self.list_in_memory if self.sort_toggle.get(): to_be_saved = sorted(self.list_in_memory) full_content = "\n".join(to_be_saved) f.write(full_content) self.any_change = False self.last_status.configure( text="File saved successfully!", text_color="#9efc90" ) self.var_edit_mode.set(True) self.current_file_path = target_path self.entry_filepath.configure(state="normal") self.entry_filepath.delete(0, "end") self.entry_filepath.insert(0, f"...{self.current_file_path[-30:]}") self.entry_filepath.configure(state="disabled") except Exception as e: self.last_status.configure( text=f"Could not save file:\n{e}", text_color="#ff9d9d" ) def on_closing(self): if self.any_change: if messagebox.askokcancel("Quit", "There are unsaved changes, are you sure you want to quit?"): self.destroy() return else: self.destroy() if __name__ == "__main__": app = MainApp() app.mainloop()