327 lines
12 KiB
Python
327 lines
12 KiB
Python
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("<Return>", 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() |