From 718736a72a4c4037fac60c486c9832ac86f66f11 Mon Sep 17 00:00:00 2001 From: Sylphiann Date: Wed, 11 Mar 2026 12:13:57 +0700 Subject: [PATCH] [0.1] Source code push --- .gitignore | 6 + assets/favicon.ico | Bin 0 -> 15406 bytes main.py | 327 +++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | Bin 0 -> 390 bytes 4 files changed, 333 insertions(+) create mode 100644 .gitignore create mode 100644 assets/favicon.ico create mode 100644 main.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5df195b --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +test/ +dist/ +build/ +.venv/ +*.zip +main.spec \ No newline at end of file diff --git a/assets/favicon.ico b/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e5c9f4a9edc09a80bac5464a3d4f599c04331574 GIT binary patch literal 15406 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", 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() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..7753ca12f81670eec866ff269ac28cd618a1844f GIT binary patch literal 390 zcmZvYOAdlS5Cz}b#G`0*RK$gcLBLN$A%HPlUaf9HU6=_y2~%DDc|H;qYIRVpM3s)( za%P%pt~GI`u|`UDQv+4%LOkoFD|b$g(JQ#%pRpO98Ybi2n3Om;W2l)ME2tDIp|aTy z&IET2M6Nw=1rEy(WCkrUm?bFcJ)P%UcQ%R9@2(=KxwC`sA30&-l5=Lh2_2O_S&eh| rQ`KBw%iSw3=)p2tb?hxCf0)vCvGDF1{