Better UI, edit tags
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Gitea/kapitanbooru-uploader/pipeline/head This commit looks good
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Gitea/kapitanbooru-uploader/pipeline/head This commit looks good
				
			This commit is contained in:
		| @@ -1,64 +1,96 @@ | ||||
| from .common import open_tag_wiki_url, open_webbrowser | ||||
| from .ProgressFile import ProgressFile | ||||
| from .autocomplete import TagManager | ||||
| from .settings import Settings | ||||
| from .tag_processing import TAG_FIXES, parse_parameters, process_tag | ||||
| from .tagger_cache import TaggerCache | ||||
| from .TagsRepo import TagsRepo | ||||
|  | ||||
|  | ||||
| import networkx as nx | ||||
| import requests | ||||
| import wdtagger as wdt | ||||
| from PIL import Image, ImageTk, PngImagePlugin | ||||
|  | ||||
|  | ||||
| import glob | ||||
| import hashlib | ||||
| import inspect | ||||
| import os | ||||
| import queue | ||||
| import threading | ||||
| import tkinter as tk | ||||
| from tkinter import filedialog, messagebox, ttk | ||||
| from typing import Tuple | ||||
|  | ||||
| import networkx as nx | ||||
| import requests | ||||
| from PIL import Image, ImageTk, PngImagePlugin | ||||
|  | ||||
| import wdtagger as wdt | ||||
| from .ProgressFile import ProgressFile | ||||
| from .TagsRepo import TagsRepo | ||||
| from .autocomplete import TagManager | ||||
| from .common import get_auth_token, login, open_tag_wiki_url, open_webbrowser | ||||
| from .settings import Settings | ||||
| from .tag_processing import TAG_FIXES, parse_parameters, process_tag | ||||
| from .tagger_cache import TaggerCache | ||||
|  | ||||
|  | ||||
| class ProcessingDialog: | ||||
|     def __init__(self, root, target_function, *args): | ||||
|         self.root = root | ||||
|         self.top = tk.Toplevel(root)  # Create a top-level window | ||||
|         self.top = tk.Toplevel(root) | ||||
|         self.top.title("Processing...") | ||||
|         self.top.geometry("300x150") | ||||
|         self.top.protocol("WM_DELETE_WINDOW", self.on_close)  # Handle close event | ||||
|         self.top.protocol("WM_DELETE_WINDOW", self.on_close) | ||||
|  | ||||
|         # Create Label and Progress Bar (or rotating animation) | ||||
|         self.label = tk.Label(self.top, text="Processing, please wait...") | ||||
|         self.label.pack(pady=10) | ||||
|  | ||||
|         # Start with indeterminate progress bar | ||||
|         self.progress = ttk.Progressbar(self.top, mode="indeterminate") | ||||
|         self.progress.pack(pady=10, fill="x") | ||||
|         self.progress.start(10)  # Start animation | ||||
|         self.progress.start(10) | ||||
|  | ||||
|         # Create a thread for the target function | ||||
|         # Setup communication queue and periodic checker | ||||
|         self.queue = queue.Queue() | ||||
|         self.running = True | ||||
|         self.thread = threading.Thread( | ||||
|             target=self.run_task, args=(target_function, *args) | ||||
|         ) | ||||
|         self.thread.start() | ||||
|         self.top.after(100, self.process_queue) | ||||
|  | ||||
|     def process_queue(self): | ||||
|         """Process messages from the background thread""" | ||||
|         while self.running: | ||||
|             try: | ||||
|                 msg = self.queue.get_nowait() | ||||
|  | ||||
|                 if msg[0] == "mode": | ||||
|                     self.progress.config(mode=msg[1]) | ||||
|                     if msg[1] == "determinate": | ||||
|                         self.progress["value"] = 0 | ||||
|                 elif msg[0] == "max": | ||||
|                     self.progress["maximum"] = msg[1] | ||||
|                 elif msg[0] == "progress": | ||||
|                     self.progress["value"] = msg[1] | ||||
|                 elif msg[0] == "label": | ||||
|                     self.label.config(text=msg[1]) | ||||
|  | ||||
|                 self.top.update_idletasks() | ||||
|             except queue.Empty: | ||||
|                 break | ||||
|  | ||||
|         if self.running: | ||||
|             self.top.after(100, self.process_queue) | ||||
|  | ||||
|     def run_task(self, target_function, *args): | ||||
|         """Execute target function with progress queue if supported""" | ||||
|         try: | ||||
|             target_function(*args)  # Run the function | ||||
|             sig = inspect.signature(target_function) | ||||
|             if "progress_queue" in sig.parameters: | ||||
|                 target_function(*args, progress_queue=self.queue) | ||||
|             else: | ||||
|                 target_function(*args) | ||||
|         finally: | ||||
|             self.close_dialog()  # Close when done | ||||
|             self.close_dialog() | ||||
|  | ||||
|     def close_dialog(self): | ||||
|         """Safely close the dialog.""" | ||||
|         """Safely close the dialog""" | ||||
|         if self.running: | ||||
|             self.running = False | ||||
|             self.top.after(0, self.top.destroy)  # Ensure UI updates on the main thread | ||||
|             self.progress.stop() | ||||
|             self.top.after(0, self.top.destroy) | ||||
|  | ||||
|     def on_close(self): | ||||
|         """User manually closed the dialog, terminate thread.""" | ||||
|         """Handle manual window closure""" | ||||
|         self.running = False | ||||
|         self.top.destroy() | ||||
|  | ||||
| @@ -271,12 +303,42 @@ class ImageBrowser(tk.Tk): | ||||
|     def create_menu(self): | ||||
|         menubar = tk.Menu(self) | ||||
|         self.config(menu=menubar) | ||||
|         menubar.add_command(label="Ustawienia", command=self.open_settings) | ||||
|         menubar.add_command(label="Wyczyść cache Taggera", command=self.clear_cache) | ||||
|         menubar.add_command(label="Wrzuć wszystko", command=self.upload_all_files) | ||||
|         menubar.add_command( | ||||
|  | ||||
|         # Create file menu and store it as instance variable | ||||
|         self.file_menu = tk.Menu(menubar, tearoff=0) | ||||
|  | ||||
|         # File menu items - create references for items we need to control | ||||
|         self.file_menu.add_command(label="Otwórz folder", command=self.select_folder) | ||||
|         self.file_menu.add_separator() | ||||
|         self.file_menu.add_command( | ||||
|             label="Wyślij", command=self.upload_current_image, state=tk.DISABLED | ||||
|         ) | ||||
|         self.file_menu.add_command( | ||||
|             label="Wyślij wszystko", command=self.upload_all_files, state=tk.DISABLED | ||||
|         ) | ||||
|         self.file_menu.add_separator() | ||||
|         self.file_menu.add_command( | ||||
|             label="Podmień tagi", command=self.edit_current_image, state=tk.DISABLED | ||||
|         ) | ||||
|         self.file_menu.add_command( | ||||
|             label="Otwórz post", command=self.view_current_post, state=tk.DISABLED | ||||
|         ) | ||||
|         self.file_menu.add_separator() | ||||
|         self.file_menu.add_command(label="Zakończ", command=self.quit) | ||||
|  | ||||
|         menubar.add_cascade(label="Plik", menu=self.file_menu) | ||||
|  | ||||
|         # Options menu | ||||
|         options_menu = tk.Menu(menubar, tearoff=0) | ||||
|         options_menu.add_command(label="Ustawienia", command=self.open_settings) | ||||
|         options_menu.add_separator() | ||||
|         options_menu.add_command( | ||||
|             label="Wyczyść cache Taggera", command=self.clear_cache | ||||
|         ) | ||||
|         options_menu.add_command( | ||||
|             label="Zregeneruj bazę tagów", command=self.regenerate_tags_db | ||||
|         ) | ||||
|         menubar.add_cascade(label="Opcje", menu=options_menu) | ||||
|  | ||||
|     def regenerate_tags_db(self): | ||||
|         self.processing_dialog = ProcessingDialog(self, self.tags_repo.regenerate_db) | ||||
| @@ -362,7 +424,7 @@ class ImageBrowser(tk.Tk): | ||||
|         main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) | ||||
|         # Lewa kolumna – lista plików | ||||
|         left_frame = tk.Frame(main_frame) | ||||
|         left_frame.grid(row=0, column=0, sticky="ns") | ||||
|         left_frame.grid(row=0, column=0, sticky=tk.NS) | ||||
|         self.listbox = tk.Listbox(left_frame, width=30) | ||||
|         self.listbox.pack(side=tk.LEFT, fill=tk.Y) | ||||
|         self.listbox.bind("<<ListboxSelect>>", self.on_listbox_select) | ||||
| @@ -374,7 +436,7 @@ class ImageBrowser(tk.Tk): | ||||
|  | ||||
|         # Środkowa kolumna – podgląd obrazu | ||||
|         center_frame = tk.Frame(main_frame) | ||||
|         center_frame.grid(row=0, column=1, sticky="nsew", padx=10) | ||||
|         center_frame.grid(row=0, column=1, sticky=tk.NSEW, padx=10) | ||||
|         main_frame.grid_columnconfigure(1, weight=1) | ||||
|         main_frame.grid_rowconfigure(0, weight=1) | ||||
|         main_frame.grid_rowconfigure(1, weight=0) | ||||
| @@ -384,7 +446,7 @@ class ImageBrowser(tk.Tk): | ||||
|  | ||||
|         # Prawa kolumna – panel tagów i uploadu (ograniczona szerokość) | ||||
|         right_frame = tk.Frame(main_frame, width=300) | ||||
|         right_frame.grid(row=0, column=2, sticky="nsew", padx=5) | ||||
|         right_frame.grid(row=0, column=2, sticky=tk.NSEW, padx=5) | ||||
|         right_frame.grid_propagate(False) | ||||
|         right_frame.grid_columnconfigure(0, weight=1) | ||||
|         # Ustal wiersze: | ||||
| @@ -396,33 +458,33 @@ class ImageBrowser(tk.Tk): | ||||
|  | ||||
|         # PNG Tags – widget Text z scrollbar | ||||
|         png_frame = tk.LabelFrame(right_frame, text="PNG Tags") | ||||
|         png_frame.grid(row=0, column=0, sticky="ew", padx=5, pady=5) | ||||
|         png_frame.grid(row=0, column=0, sticky=tk.EW, padx=5, pady=5) | ||||
|         png_frame.grid_columnconfigure(0, weight=1) | ||||
|         self.png_tags_text = tk.Text(png_frame, wrap="word") | ||||
|         self.png_tags_text.grid(row=0, column=0, sticky="ew") | ||||
|         self.png_tags_text = tk.Text(png_frame, wrap=tk.WORD) | ||||
|         self.png_tags_text.grid(row=0, column=0, sticky=tk.EW) | ||||
|         scrollbar_png = tk.Scrollbar(png_frame, command=self.png_tags_text.yview) | ||||
|         scrollbar_png.grid(row=0, column=1, sticky="ns") | ||||
|         scrollbar_png.grid(row=0, column=1, sticky=tk.NS) | ||||
|         self.png_tags_text.config( | ||||
|             yscrollcommand=scrollbar_png.set, state="disabled", height=4 | ||||
|             yscrollcommand=scrollbar_png.set, state=tk.DISABLED, height=4 | ||||
|         ) | ||||
|  | ||||
|         # Tagger Tags – widget Text z scrollbar | ||||
|         tagger_frame = tk.LabelFrame(right_frame, text="Tagger Tags") | ||||
|         tagger_frame.grid(row=1, column=0, sticky="ew", padx=5, pady=5) | ||||
|         tagger_frame.grid(row=1, column=0, sticky=tk.EW, padx=5, pady=5) | ||||
|         tagger_frame.grid_columnconfigure(0, weight=1) | ||||
|         self.tagger_tags_text = tk.Text(tagger_frame, wrap="word") | ||||
|         self.tagger_tags_text.grid(row=0, column=0, sticky="ew") | ||||
|         self.tagger_tags_text = tk.Text(tagger_frame, wrap=tk.WORD) | ||||
|         self.tagger_tags_text.grid(row=0, column=0, sticky=tk.EW) | ||||
|         scrollbar_tagger = tk.Scrollbar( | ||||
|             tagger_frame, command=self.tagger_tags_text.yview | ||||
|         ) | ||||
|         scrollbar_tagger.grid(row=0, column=1, sticky="ns") | ||||
|         scrollbar_tagger.grid(row=0, column=1, sticky=tk.NS) | ||||
|         self.tagger_tags_text.config( | ||||
|             yscrollcommand=scrollbar_tagger.set, state="disabled", height=4 | ||||
|             yscrollcommand=scrollbar_tagger.set, state=tk.DISABLED, height=4 | ||||
|         ) | ||||
|  | ||||
|         # Manual Tags – Entry (stała wysokość) | ||||
|         manual_frame = tk.LabelFrame(right_frame, text="Manual Tags") | ||||
|         manual_frame.grid(row=2, column=0, sticky="nsew", padx=5, pady=5) | ||||
|         manual_frame.grid(row=2, column=0, sticky=tk.NSEW, padx=5, pady=5) | ||||
|         self.manual_tags_manager = TagManager( | ||||
|             manual_frame, self.settings, self.tags_repo | ||||
|         ) | ||||
| @@ -436,18 +498,18 @@ class ImageBrowser(tk.Tk): | ||||
|  | ||||
|         # Final Tags – widget Text z scrollbar, który rozszerza się | ||||
|         final_frame = tk.LabelFrame(right_frame, text="Final Tags") | ||||
|         final_frame.grid(row=3, column=0, sticky="nsew", padx=5, pady=5) | ||||
|         final_frame.grid(row=3, column=0, sticky=tk.NSEW, padx=5, pady=5) | ||||
|         final_frame.grid_rowconfigure(0, weight=1) | ||||
|         final_frame.grid_columnconfigure(0, weight=1) | ||||
|         self.final_tags_text = tk.Text(final_frame, state="disabled", wrap="word") | ||||
|         self.final_tags_text.grid(row=0, column=0, sticky="nsew") | ||||
|         self.final_tags_text = tk.Text(final_frame, state=tk.DISABLED, wrap=tk.WORD) | ||||
|         self.final_tags_text.grid(row=0, column=0, sticky=tk.NSEW) | ||||
|         scrollbar_final = tk.Scrollbar(final_frame, command=self.final_tags_text.yview) | ||||
|         scrollbar_final.grid(row=0, column=1, sticky="ns") | ||||
|         scrollbar_final.grid(row=0, column=1, sticky=tk.NS) | ||||
|         self.final_tags_text.config(yscrollcommand=scrollbar_final.set) | ||||
|  | ||||
|         # Panel uploadu i rating – nie zmienia rozmiaru pionowo | ||||
|         upload_frame = tk.Frame(right_frame) | ||||
|         upload_frame.grid(row=4, column=0, sticky="ew", padx=5, pady=5) | ||||
|         upload_frame.grid(row=4, column=0, sticky=tk.EW, padx=5, pady=5) | ||||
|         self.rating_var = tk.StringVar(value="Unrated") | ||||
|         rating_options = ["General", "Sensitive", "Questionable", "Explicit", "Unrated"] | ||||
|         self.rating_dropdown = tk.OptionMenu( | ||||
| @@ -458,21 +520,18 @@ class ImageBrowser(tk.Tk): | ||||
|             upload_frame, text="Upload", command=self.upload_current_image | ||||
|         ) | ||||
|         self.upload_button.pack(side=tk.LEFT, padx=5) | ||||
|         self.progress_var = tk.IntVar(value=0) | ||||
|         self.progress_bar = ttk.Progressbar( | ||||
|             upload_frame, | ||||
|             orient="horizontal", | ||||
|             mode="determinate", | ||||
|             variable=self.progress_var, | ||||
|             maximum=100, | ||||
|         self.upload_button.config(state=tk.DISABLED) | ||||
|         self.view_post_button = tk.Button( | ||||
|             upload_frame, text="Wyświetl", command=self.view_current_post | ||||
|         ) | ||||
|         self.progress_bar.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) | ||||
|         self.view_post_button.pack(side=tk.LEFT, padx=5) | ||||
|         self.view_post_button.config(state=tk.DISABLED) | ||||
|  | ||||
|         # Na końcu okna głównego dodaj status bar: | ||||
|         self.status_label = tk.Label( | ||||
|             main_frame, text="", bd=1, relief=tk.SUNKEN, anchor=tk.W | ||||
|         ) | ||||
|         self.status_label.grid(row=1, column=0, columnspan=3, sticky="ew") | ||||
|         self.status_label.grid(row=1, column=0, columnspan=3, sticky=tk.EW) | ||||
|  | ||||
|     def update_status_bar(self): | ||||
|         status_text = ( | ||||
| @@ -585,31 +644,75 @@ class ImageBrowser(tk.Tk): | ||||
|                         ) | ||||
|                         # Jeśli aktualnie wybrany plik, zmień przycisk | ||||
|                         if self.current_index == idx: | ||||
|                             self.after(0, self.set_upload_button_to_view_or_upload) | ||||
|                             self.after(0, self.update_button_states) | ||||
|                     else: | ||||
|                         self.uploaded[file_path] = False | ||||
|                     self.after(0, self.update_status_bar) | ||||
|             except Exception as e: | ||||
|                 print("Błąd podczas sprawdzania paczki uploadu:", e) | ||||
|  | ||||
|     def set_upload_button_to_view_or_upload(self): | ||||
|     def update_button_states(self): | ||||
|         """ | ||||
|         Ustawia przycisk uploadu na "Wyświetl" lub "Upload" w zależności od stan | ||||
|         uploadu dla aktualnie wybranego pliku. | ||||
|         Update the state of UI elements based on current application state. | ||||
|  | ||||
|         Synchronizes the enabled/disabled states of menu items and buttons with: | ||||
|         - Whether an image is currently selected (current_index exists) | ||||
|         - Whether the selected image has an existing post (post_id exists in uploaded) | ||||
|  | ||||
|         State transitions: | ||||
|         - When no image is selected (current_index is None): | ||||
|             * Disables all upload-related buttons and menu items | ||||
|         - When an image is selected: | ||||
|             * Always enables 'Wyślij wszystko' (Send All) | ||||
|             * Enables 'Wyślij' (Send) if no post exists for the image | ||||
|             * Enables 'Podmień tagi' (Replace Tags) and 'Otwórz post' (Open Post) | ||||
|             if a post exists for the image | ||||
|             * Updates the upload button's text and command based on post existence | ||||
|  | ||||
|         Modifies: | ||||
|             - File menu items: 'Wyślij', 'Wyślij wszystko', 'Podmień tagi', 'Otwórz post' | ||||
|             - Buttons: upload_button, view_post_button | ||||
|  | ||||
|         Dependencies: | ||||
|             - Relies on self.current_index to determine selection state | ||||
|             - Checks self.uploaded against self.image_files for post existence | ||||
|         """ | ||||
|         has_current = self.current_index is not None | ||||
|         post_id = ( | ||||
|             self.uploaded.get(self.image_files[self.current_index]) | ||||
|             if has_current | ||||
|             else None | ||||
|         ) | ||||
|  | ||||
|         # Common state for all elements | ||||
|         send_all_state = tk.NORMAL if has_current else tk.DISABLED | ||||
|         post_ops_state = tk.NORMAL if post_id else tk.DISABLED | ||||
|  | ||||
|         # Update menu items | ||||
|         self.file_menu.entryconfig("Wyślij wszystko", state=send_all_state) | ||||
|         self.file_menu.entryconfig("Podmień tagi", state=post_ops_state) | ||||
|         self.file_menu.entryconfig("Otwórz post", state=post_ops_state) | ||||
|  | ||||
|         # Update buttons | ||||
|         self.upload_button.config( | ||||
|             state=tk.NORMAL if has_current else tk.DISABLED, | ||||
|             text="Podmień tagi" if post_id else "Wyślij", | ||||
|             command=self.edit_current_image if post_id else self.upload_current_image, | ||||
|         ) | ||||
|         self.view_post_button.config(state=post_ops_state) | ||||
|  | ||||
|         # Special case for "Wyślij" menu item | ||||
|         wyślij_state = tk.DISABLED if post_id else tk.NORMAL | ||||
|         self.file_menu.entryconfig( | ||||
|             "Wyślij", state=wyślij_state if has_current else tk.DISABLED | ||||
|         ) | ||||
|  | ||||
|     def view_current_post(self): | ||||
|         """Otwiera w przeglądarce URL posta.""" | ||||
|         if self.current_index is None: | ||||
|             return | ||||
|         post_id = self.uploaded.get(self.image_files[self.current_index]) | ||||
|         if post_id: | ||||
|             self.upload_button.config( | ||||
|                 text="Wyświetl", command=lambda: self.view_post(post_id) | ||||
|             ) | ||||
|         else: | ||||
|             self.upload_button.config(text="Upload", command=self.upload_current_image) | ||||
|  | ||||
|     def view_post(self, post_id): | ||||
|         """Otwiera w przeglądarce URL posta.""" | ||||
|  | ||||
|             url = self.settings.base_url.rstrip("/") + "/post/view/" + str(post_id) | ||||
|             open_webbrowser(url, self.settings) | ||||
|  | ||||
| @@ -635,7 +738,7 @@ class ImageBrowser(tk.Tk): | ||||
|         index = self.listbox.curselection()[0] | ||||
|         self.current_index = index | ||||
|         self.show_image(index) | ||||
|         self.set_upload_button_to_view_or_upload() | ||||
|         self.update_button_states() | ||||
|  | ||||
|     def upload_current_image(self): | ||||
|         """ | ||||
| @@ -646,13 +749,10 @@ class ImageBrowser(tk.Tk): | ||||
|         file_path = self.image_files[self.current_index] | ||||
|         if self.uploaded.get(file_path, False): | ||||
|             # Jeśli plik już uploadowany, ustaw przycisk na "Wyświetl" | ||||
|             self.set_upload_button_to_view_or_upload() | ||||
|             self.update_button_states() | ||||
|             return | ||||
|         self.upload_button.config(state="disabled") | ||||
|         self.progress_var.set(0) | ||||
|         threading.Thread( | ||||
|             target=self.upload_file, args=(file_path,), daemon=True | ||||
|         ).start() | ||||
|         self.upload_button.config(state=tk.DISABLED) | ||||
|         self.processing_dialog = ProcessingDialog(self, self.upload_file, file_path) | ||||
|  | ||||
|     def show_image(self, index): | ||||
|         """ | ||||
| @@ -678,6 +778,18 @@ class ImageBrowser(tk.Tk): | ||||
|         except Exception as e: | ||||
|             messagebox.showerror("Błąd", f"Nie można załadować obrazka:\n{e}") | ||||
|  | ||||
|     def edit_current_image(self): | ||||
|         """ | ||||
|         Modyfikuje obrazek na serwerze. | ||||
|         Wysyła POST z md5 obrazka, tagami i ratingiem. | ||||
|         """ | ||||
|         file_path = self.image_files[self.current_index] | ||||
|         if not self.uploaded.get(file_path, False): | ||||
|             self.update_button_states() | ||||
|             return | ||||
|         self.upload_button.config(state=tk.DISABLED) | ||||
|         self.processing_dialog = ProcessingDialog(self, self.edit_file, file_path) | ||||
|  | ||||
|     def update_display_image(self): | ||||
|         """ | ||||
|         Aktualizuje obrazek na podstawie aktualnego rozmiaru okna. | ||||
| @@ -756,7 +868,7 @@ class ImageBrowser(tk.Tk): | ||||
|         self.listbox.select_set(new_index) | ||||
|         self.listbox.activate(new_index) | ||||
|         self.show_image(new_index) | ||||
|         self.set_upload_button_to_view_or_upload() | ||||
|         self.update_button_states() | ||||
|  | ||||
|     def show_next(self): | ||||
|         """ | ||||
| @@ -772,7 +884,7 @@ class ImageBrowser(tk.Tk): | ||||
|         self.listbox.select_set(new_index) | ||||
|         self.listbox.activate(new_index) | ||||
|         self.show_image(new_index) | ||||
|         self.set_upload_button_to_view_or_upload() | ||||
|         self.update_button_states() | ||||
|  | ||||
|     # --- Metody obsługujące widgety z tagami --- | ||||
|  | ||||
| @@ -781,7 +893,7 @@ class ImageBrowser(tk.Tk): | ||||
|         Aktualizuje widget z tagami z PNG. | ||||
|         Tworzy tagi jako klikalne, zaznaczone lub niezaznaczone. | ||||
|         """ | ||||
|         self.png_tags_text.config(state="normal") | ||||
|         self.png_tags_text.config(state=tk.NORMAL) | ||||
|         self.png_tags_text.delete("1.0", tk.END) | ||||
|         self.png_tags_states = {} | ||||
|         for tag in tags_list: | ||||
| @@ -794,7 +906,7 @@ class ImageBrowser(tk.Tk): | ||||
|             self.png_tags_text.tag_configure(tag_name, foreground="blue") | ||||
|             self.png_tags_text.tag_bind(tag_name, "<Button-1>", self.toggle_png_tag) | ||||
|             self.png_tags_text.insert(tk.INSERT, " ") | ||||
|         self.png_tags_text.config(state="disabled") | ||||
|         self.png_tags_text.config(state=tk.DISABLED) | ||||
|         self.adjust_text_widget_height(self.png_tags_text) | ||||
|         self.update_final_tags() | ||||
|  | ||||
| @@ -933,9 +1045,9 @@ class ImageBrowser(tk.Tk): | ||||
|             self.png_tags_states[tag] = tag in common | ||||
|             tag_name = "png_" + tag | ||||
|             color = "blue" if self.png_tags_states[tag] else "black" | ||||
|             self.png_tags_text.config(state="normal") | ||||
|             self.png_tags_text.config(state=tk.NORMAL) | ||||
|             self.png_tags_text.tag_configure(tag_name, foreground=color) | ||||
|             self.png_tags_text.config(state="disabled") | ||||
|             self.png_tags_text.config(state=tk.DISABLED) | ||||
|  | ||||
|         for tag in self.tagger_tags_states: | ||||
|             if tag.startswith("character:"): | ||||
| @@ -956,7 +1068,7 @@ class ImageBrowser(tk.Tk): | ||||
|         Poprawia wysokość widgetu. | ||||
|         Aktualizuje listę finalnych tagów. | ||||
|         """ | ||||
|         self.tagger_tags_text.config(state="normal") | ||||
|         self.tagger_tags_text.config(state=tk.NORMAL) | ||||
|         self.tagger_tags_text.delete("1.0", tk.END) | ||||
|         visible_tags = self.get_visible_tags() | ||||
|  | ||||
| @@ -974,7 +1086,7 @@ class ImageBrowser(tk.Tk): | ||||
|             ) | ||||
|             self.tagger_tags_text.insert(tk.INSERT, " ") | ||||
|  | ||||
|         self.tagger_tags_text.config(state="disabled") | ||||
|         self.tagger_tags_text.config(state=tk.DISABLED) | ||||
|  | ||||
|         self.adjust_text_widget_height(self.tagger_tags_text) | ||||
|         self.update_final_tags() | ||||
| @@ -1035,7 +1147,7 @@ class ImageBrowser(tk.Tk): | ||||
|             final_set.update(manual) | ||||
|         final_list = sorted(final_set) | ||||
|  | ||||
|         self.final_tags_text.config(state="normal") | ||||
|         self.final_tags_text.config(state=tk.NORMAL) | ||||
|         self.final_tags_text.delete("1.0", tk.END) | ||||
|         for tag in final_list: | ||||
|             _, deprecated = process_tag(tag, self.tags_repo) | ||||
| @@ -1061,7 +1173,7 @@ class ImageBrowser(tk.Tk): | ||||
|                 tag_name, "<Button-1>", self.open_final_tag_wiki_url | ||||
|             ) | ||||
|             self.final_tags_text.insert(tk.INSERT, " ") | ||||
|         self.final_tags_text.config(state="disabled") | ||||
|         self.final_tags_text.config(state=tk.DISABLED) | ||||
|  | ||||
|     def open_final_tag_wiki_url(self, event): | ||||
|         """Otwiera w przeglądarce URL strony wiki dla klikniętego tagu. | ||||
| @@ -1101,10 +1213,10 @@ class ImageBrowser(tk.Tk): | ||||
|         if self.current_index is None: | ||||
|             return | ||||
|         # Ustaw komunikat, że Tagger pracuje | ||||
|         self.tagger_tags_text.config(state="normal") | ||||
|         self.tagger_tags_text.config(state=tk.NORMAL) | ||||
|         self.tagger_tags_text.delete("1.0", tk.END) | ||||
|         self.tagger_tags_text.insert("1.0", "Tagger przetwarza...") | ||||
|         self.tagger_tags_text.config(state="disabled") | ||||
|         self.tagger_tags_text.config(state=tk.DISABLED) | ||||
|         file_path = self.image_files[self.current_index] | ||||
|         result = self.get_tagger_results(file_path) | ||||
|         new_rating = self.map_tagger_rating(result) | ||||
| @@ -1112,7 +1224,14 @@ class ImageBrowser(tk.Tk): | ||||
|         self.after(0, lambda: self.update_tagger_tags_widget(result)) | ||||
|         self.update_listbox_item_color_by_rating(file_path, result.rating) | ||||
|  | ||||
|     def upload_file(self, file_path, final_tags=None, final_rating=None): | ||||
|     def upload_file( | ||||
|         self, file_path, final_tags=None, final_rating=None, progress_queue=None | ||||
|     ): | ||||
|         base_file_name = os.path.basename(file_path) | ||||
|         if progress_queue: | ||||
|             progress_queue.put(("mode", "determinate")) | ||||
|             progress_queue.put(("max", 100)) | ||||
|             progress_queue.put(("label", f"Wysyłam plik {base_file_name}...")) | ||||
|         url = self.settings.base_url.rstrip("/") + "/api/danbooru/add_post" | ||||
|         tags = ( | ||||
|             self.final_tags_text.get("1.0", tk.END).strip() | ||||
| @@ -1134,16 +1253,16 @@ class ImageBrowser(tk.Tk): | ||||
|             total_size = os.path.getsize(file_path) | ||||
|  | ||||
|             def progress_callback(bytes_read, total_size): | ||||
|                 if progress_queue: | ||||
|                     percentage = int(bytes_read / total_size * 100) | ||||
|                 self.progress_bar.after(0, lambda: self.progress_var.set(percentage)) | ||||
|                     progress_queue.put(("progress", percentage)) | ||||
|  | ||||
|             with open(file_path, "rb") as f: | ||||
|                 wrapped_file = ProgressFile(f, progress_callback, total_size) | ||||
|                 files = { | ||||
|                     "file": (os.path.basename(file_path), wrapped_file, "image/png") | ||||
|                 } | ||||
|                 files = {"file": (base_file_name, wrapped_file, "image/png")} | ||||
|                 response = requests.post(url, data=fields, files=files) | ||||
|             self.progress_bar.after(0, lambda: self.progress_var.set(100)) | ||||
|             if progress_queue: | ||||
|                 progress_queue.put(("progress", 100)) | ||||
|             show_warn = False | ||||
|             post_url = None | ||||
|             if response.status_code in (200, 201): | ||||
| @@ -1156,9 +1275,6 @@ class ImageBrowser(tk.Tk): | ||||
|             else: | ||||
|                 message = f"Upload zakończony błędem.\nStatus: {response.status_code}\nTreść: {response.text}" | ||||
|                 show_warn = True | ||||
|             self.upload_button.after( | ||||
|                 0, lambda: self.upload_button.config(state="normal") | ||||
|             ) | ||||
|             # Aktualizacja wyglądu listy – musimy użyć domyślnych argumentów w lambdzie, aby zachować bieżący indeks | ||||
|             if show_warn: | ||||
|                 if not final_tags: | ||||
| @@ -1177,13 +1293,101 @@ class ImageBrowser(tk.Tk): | ||||
|                 self.uploaded[file_path] = post_id | ||||
|                 self.uploaded_count += 1 | ||||
|                 self.after(0, self.update_status_bar) | ||||
|                 self.set_upload_button_to_view_or_upload() | ||||
|             self.after(0, self.update_button_states) | ||||
|         except Exception as e: | ||||
|             self.upload_button.after( | ||||
|                 0, lambda: self.upload_button.config(state="normal") | ||||
|             ) | ||||
|             self.upload_button.after(0, self.update_button_states) | ||||
|             messagebox.showerror("Błąd uploadu", str(e)) | ||||
|  | ||||
|     def edit_file( | ||||
|         self, file_path, final_tags=None, final_rating=None, progress_queue=None | ||||
|     ): | ||||
|         """ | ||||
|         Update tags and rating for an existing post without uploading the file. | ||||
|  | ||||
|         Args: | ||||
|             file_path: Path to the image file associated with the post | ||||
|             final_tags: Optional override tags (space-separated string) | ||||
|             final_rating: Optional override rating | ||||
|             progress_queue: Progress communication queue | ||||
|  | ||||
|         Requires: | ||||
|             - The file must have an existing post ID in self.uploaded | ||||
|         """ | ||||
|         base_file_name = os.path.basename(file_path) | ||||
|         post_id = self.uploaded.get(file_path) | ||||
|  | ||||
|         if not post_id: | ||||
|             messagebox.showerror( | ||||
|                 "Błąd edycji", "Post nie został znaleziony dla tego pliku" | ||||
|             ) | ||||
|             return | ||||
|  | ||||
|         if progress_queue: | ||||
|             progress_queue.put(("mode", "determinate")) | ||||
|             progress_queue.put(("max", 100)) | ||||
|             progress_queue.put(("label", f"Aktualizuję tagi dla {base_file_name}...")) | ||||
|  | ||||
|         try: | ||||
|             # Get authentication session and token | ||||
|             session = login(self.settings) | ||||
|             auth_token = get_auth_token(session, self.settings) | ||||
|  | ||||
|             # Prepare tags and rating | ||||
|             tags = ( | ||||
|                 self.final_tags_text.get("1.0", tk.END).strip() | ||||
|                 if final_tags is None | ||||
|                 else final_tags | ||||
|             ) | ||||
|  | ||||
|             rating_value = self.rating_map.get( | ||||
|                 self.rating_var.get() if final_rating is None else final_rating, "?" | ||||
|             ) | ||||
|  | ||||
|             # Prepare API request | ||||
|             url = self.settings.base_url.rstrip("/") + "/post/set" | ||||
|             payload = { | ||||
|                 "auth_token": auth_token, | ||||
|                 "image_id": post_id, | ||||
|                 "title": base_file_name, | ||||
|                 "owner": self.settings.username, | ||||
|                 "tags": tags, | ||||
|                 "source": "", | ||||
|                 "rating": rating_value, | ||||
|             } | ||||
|  | ||||
|             if progress_queue: | ||||
|                 progress_queue.put(("progress", 50)) | ||||
|  | ||||
|             # Send update request | ||||
|             response = session.post(url, data=payload, allow_redirects=False) | ||||
|  | ||||
|             # Handle 302 redirect as success case | ||||
|             if response.status_code == 302: | ||||
|                 if progress_queue: | ||||
|                     progress_queue.put(("progress", 100)) | ||||
|  | ||||
|                 message = "Tagi zostały zaktualizowane!" | ||||
|                 if not final_tags:  # Only show success if not bulk operation | ||||
|                     messagebox.showinfo("Sukces edycji", message) | ||||
|  | ||||
|                 # Update UI state | ||||
|                 self.after(0, self.update_button_states) | ||||
|                 return | ||||
|  | ||||
|             # Handle other status codes | ||||
|             error_msg = ( | ||||
|                 f"Błąd podczas aktualizacji tagów\nStatus: {response.status_code}" | ||||
|             ) | ||||
|             if response.text: | ||||
|                 error_msg += f"\nTreść: {response.text}" | ||||
|             messagebox.showerror("Błąd edycji", error_msg) | ||||
|  | ||||
|         except Exception as e: | ||||
|             messagebox.showerror("Krytyczny błąd edycji", str(e)) | ||||
|         finally: | ||||
|             if progress_queue: | ||||
|                 progress_queue.put(("progress", 100)) | ||||
|  | ||||
|     def upload_all_files(self): | ||||
|         """ | ||||
|         Metoda, która po potwierdzeniu przez użytkownika uploaduje wszystkie niewrzucone pliki. | ||||
|   | ||||
| @@ -179,20 +179,24 @@ class TagManager(tk.Frame): | ||||
|     based on custom logic. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, master, settings: Settings, tags_repo: TagsRepo, *args, **kwargs): | ||||
|     def __init__( | ||||
|         self, master, settings: Settings, tags_repo: TagsRepo, *args, **kwargs | ||||
|     ): | ||||
|         super().__init__(master, *args, **kwargs) | ||||
|         self.tags_repo = tags_repo | ||||
|         self.settings = settings | ||||
|         self.manual_tags = []  # List to hold manually entered tags | ||||
|  | ||||
|         # Entry for new tags (with autocompletion) | ||||
|         self.entry = AutocompleteEntry(self, callback=self.add_tag, tags_repo=self.tags_repo) | ||||
|         self.entry = AutocompleteEntry( | ||||
|             self, callback=self.add_tag, tags_repo=self.tags_repo | ||||
|         ) | ||||
|         self.entry.pack(fill=tk.X, padx=5, pady=5) | ||||
|  | ||||
|         # Text widget for displaying already entered tags | ||||
|         self.tags_display = tk.Text(self, wrap="word", height=4) | ||||
|         self.tags_display = tk.Text(self, wrap=tk.WORD, height=4) | ||||
|         self.tags_display.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) | ||||
|         self.tags_display.config(state="disabled") | ||||
|         self.tags_display.config(state=tk.DISABLED) | ||||
|         # (Optional: add a scrollbar if needed) | ||||
|  | ||||
|     def add_tag(self, tag): | ||||
| @@ -229,7 +233,7 @@ class TagManager(tk.Frame): | ||||
|             self.tags_display.tag_bind(tag_name, "<Button-1>", self.remove_tag) | ||||
|             self.tags_display.tag_bind(tag_name, "<Button-3>", self.open_tag_wiki_url) | ||||
|             self.tags_display.insert(tk.INSERT, " ") | ||||
|         self.tags_display.config(state="disabled") | ||||
|         self.tags_display.config(state=tk.DISABLED) | ||||
|  | ||||
|     def remove_tag(self, event): | ||||
|         """Remove the clicked tag from the list and update the display.""" | ||||
|   | ||||
| @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" | ||||
|  | ||||
| [project] | ||||
| name = "kapitanbooru-uploader" | ||||
| version = "0.1.0" | ||||
| version = "0.2.0" | ||||
| description = "A GUI application for uploading images to KapitanBooru" | ||||
| authors = [ | ||||
|     {name = "Michał Leśniak", email = "kapitan@mlesniak.pl"} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user