diff --git a/api/woocommerce_api.py b/api/woocommerce_api.py index aea126b..28bcb43 100644 --- a/api/woocommerce_api.py +++ b/api/woocommerce_api.py @@ -55,7 +55,7 @@ def load_credentials(): with open("config.enc", "rb") as file: encrypted = file.read() decrypted = fernet.decrypt(encrypted).decode() - return json.loads(decrypted) + return json.loads(decrypted).get("credentials") def get_wcapi(): diff --git a/config/encrypt_config.py b/config/encrypt_config.py index 7a56889..074f25d 100644 --- a/config/encrypt_config.py +++ b/config/encrypt_config.py @@ -13,6 +13,7 @@ class ConfigEncryptor: encrypted_data = self.fernet.encrypt(json_data.encode()) with open(self.filename, "wb") as encrypted_file: encrypted_file.write(encrypted_data) + print("Credentials saved") def get_key(self): return self.key.decode() @@ -38,7 +39,11 @@ class ConfigEncryptor: with open(self.filename, "rb") as encrypted_file: encrypted_data = encrypted_file.read() decrypted_data = self.fernet.decrypt(encrypted_data).decode() - return json.loads(decrypted_data) + config = json.loads(decrypted_data) + + # Filter only relevant keys + keys_to_return = ["credentials", "options"] + return {key: config[key] for key in keys_to_return if key in config} except FileNotFoundError: return None diff --git a/images/image-7.jpg b/images/image-7.jpg new file mode 100644 index 0000000..504d5c9 Binary files /dev/null and b/images/image-7.jpg differ diff --git a/main.py b/main.py index 85d63d8..7ec7128 100644 --- a/main.py +++ b/main.py @@ -2,14 +2,12 @@ Main module for the Image Processor application. """ -import tkinter as tk -from tkinter import ttk -import customtkinter +import customtkinter as ctk from ui.log_window import LogWindow from ui.local_processing_tab import LocalProcessingTab from ui.settings_tab import SettingsTab from config.decrypt_config import ConfigDecryptor, DECRYPTION_KEY - +from config.encrypt_config import ConfigEncryptor class ImageProcessorApp: """ @@ -21,42 +19,69 @@ class ImageProcessorApp: Initialize the ImageProcessorApp. Args: - root (tk.Tk): The root Tkinter window. + root (ctk.CTk): The root CustomTkinter window. """ self.root = root self.root.title("Image Processor") - self.root.geometry("500x450") + self.root.geometry("520x600") - self.tab_parent = ttk.Notebook(self.root) - self.log_window = None + # Create menu frame at the top + menu_frame = ctk.CTkFrame(self.root) + menu_frame.pack(side="top", fill="x") - self.local_processing_tab = LocalProcessingTab( - self.tab_parent, "Local Processing", self.open_log_window - ) - self.settings_tab = SettingsTab(self.tab_parent, "Settings") + local_processing_button = ctk.CTkButton(menu_frame, text="Local Processing", command=self.show_local_processing_tab) + local_processing_button.pack(side="left", padx=5, pady=5) - self.tab_parent.pack(expand=True, fill="both") + settings_button = ctk.CTkButton(menu_frame, text="Settings", command=self.show_settings_tab) + settings_button.pack(side="left", padx=5, pady=5) - def open_log_window(self): + # Create main frame to hold tabs and log window + main_frame = ctk.CTkFrame(self.root) + main_frame.pack(expand=True, fill="both") + + self.tab_parent = ctk.CTkFrame(main_frame) + self.tab_parent.grid(row=0, column=0, sticky="nsew") + + self.log_frame = ctk.CTkFrame(main_frame) + self.log_frame.grid(row=1, column=0, sticky="nsew") + + main_frame.grid_rowconfigure(0, weight=3) + main_frame.grid_rowconfigure(1, weight=1) + main_frame.grid_columnconfigure(0, weight=1) + + self.log_window = LogWindow(self.log_frame) + + self.local_processing_tab = LocalProcessingTab(self.tab_parent, self.log_window) + self.settings_tab = SettingsTab(self.tab_parent) + + self.local_processing_tab.tab.grid(row=0, column=0, sticky="nsew") + self.settings_tab.tab.grid(row=0, column=0, sticky="nsew") + + self.show_local_processing_tab() + + def show_local_processing_tab(self): """ - Open the log window. If it already exists, bring it to the front. + Show the Local Processing tab. """ - if self.log_window is None or not self.log_window.winfo_exists(): - self.log_window = LogWindow(self.root) - else: - self.log_window.lift() + self.local_processing_tab.tab.tkraise() + + def show_settings_tab(self): + """ + Show the Settings tab. + """ + self.settings_tab.tab.tkraise() def run(self): """ - Run the Tkinter main loop. + Run the CustomTkinter main loop. """ self.root.mainloop() if __name__ == "__main__": try: - decryptor = ConfigDecryptor(DECRYPTION_KEY) - config = decryptor.decrypt() + decryptor = ConfigEncryptor(DECRYPTION_KEY) + config = decryptor.load_config() wc_url = config["url"] wc_consumer_key = config["consumer_key"] wc_consumer_secret = config["consumer_secret"] @@ -65,6 +90,8 @@ if __name__ == "__main__": except FileNotFoundError as e: print(f"File not found: {e}") - root = customtkinter.CTk() + root = ctk.CTk() + ctk.set_appearance_mode("dark") + ctk.set_default_color_theme("blue") app = ImageProcessorApp(root) app.run() diff --git a/ui/local_processing_tab.py b/ui/local_processing_tab.py index cb9fdbe..94a9337 100644 --- a/ui/local_processing_tab.py +++ b/ui/local_processing_tab.py @@ -4,40 +4,39 @@ Module for the Local Processing Tab in the Image Processor application. import tempfile import threading -import tkinter as tk -from tkinter import ttk +import customtkinter as ctk from tkinter.scrolledtext import ScrolledText -from tkinter import Tk, Label, Button, Entry, Toplevel, StringVar, BooleanVar +from tkinter import StringVar, BooleanVar from PIL import Image, ImageTk from utils.file_operations import FileProcessor from utils.image_processing import ImageProcessor from api.woocommerce_api import process_product_images, process_all_products from ui.options_window import OptionsWindow -from pprint import pformat, pprint +from pprint import pformat from config.encrypt_config import ConfigEncryptor +import os + class LocalProcessingTab: """ Class for the Local Processing Tab in the Image Processor application. """ - def __init__(self, tab_parent, text, log): + def __init__(self, tab_parent, log_window): """ Initialize the LocalProcessingTab. Args: - tab_parent (ttk.Notebook): The parent notebook widget. - text (str): The text to display on the tab. - log (function): The function to log messages. + tab_parent (ctk.CTkFrame): The parent frame widget. + log_window (LogWindow): The log window frame. """ key = b"u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI=" - - self.log = log - self.tab = ttk.Frame(tab_parent) + + self.log_window = log_window + self.log = self.log_window.log_message + self.tab = ctk.CTkFrame(tab_parent) self.root = self.tab.winfo_toplevel() # Store the root window reference - tab_parent.add(self.tab, text=text) self.config = ConfigEncryptor(key) - self.log_window = None self.canvas_width = 900 self.canvas_height = 900 @@ -48,7 +47,7 @@ class LocalProcessingTab: self.image_format = "AUTO" self.image_size = "contain" self.load_config() - self.source_type = StringVar(value="local") + self.source_type = StringVar(value="directory") self.checkbox_var = BooleanVar(value=False) self.file = FileProcessor() self.image = ImageProcessor() @@ -58,8 +57,8 @@ class LocalProcessingTab: self.update_options() def load_config(self): - config = self.config.load_config() + print(config) if config: if options := config.get("options"): self.canvas_width = options.get("canvas_width", 900) @@ -71,125 +70,126 @@ class LocalProcessingTab: self.image_format = options.get("image_format", "AUTO") self.image_size = options.get("image_size", "contain") - - def create_log_window(self): - """ - Create and display the log window. - """ - if self.log_window and Toplevel.winfo_exists(self.log_window): - return - self.log_window = Toplevel() - self.log_window.title("Processing Log") - self.log_text = ScrolledText( - self.log_window, state="disabled", wrap="word", height=20, width=80 - ) - - self.log_text.pack(expand=True, fill="both") - - def log_message(self, message): - """ - Log a message to the log window. - - Args: - message (str): The message to log. - """ - if self.log_window: - self.log_text.config(state="normal") - self.log_text.insert(tk.END, message + "\n") - self.log_text.see(tk.END) - self.log_text.config(state="disabled") - self.log_text.update_idletasks() - def setup_ui(self): """ Set up the user interface for the tab. """ - # Source selection section - self.source_label = Label(self.tab, text="Source Type:") - self.source_label.grid(row=0, column=0, padx=5, pady=5, sticky="w") + current_row = 0 - self.source_dropdown = ttk.Combobox( + # Source selection section + self.source_label = ctk.CTkLabel(self.tab, anchor="w", width=500, text="Source Type:") + self.source_label.grid(row=current_row, column=0, columnspan=6, padx=5, pady=5, sticky="w") + + current_row += 1 + + self.source_dropdown = ctk.CTkComboBox( self.tab, - textvariable=self.source_type, - values=["local", "product", "all_products"], + variable=self.source_type, + values=["directory", "file", "wp_image", "product", "all_products"], state="readonly", + command=self.update_options ) - self.source_dropdown.grid(row=1, column=0, padx=5, pady=5, sticky="w") + self.source_dropdown.grid(row=current_row, column=0, columnspan=2, padx=5, pady=5, sticky="w") self.source_dropdown.bind( "<>", lambda e: self.update_options() ) - - self.browse_button = ttk.Button( - self.tab, text="Browse", command=self.browse_directory_command - ) - self.browse_button.grid(row=2, column=0, padx=5, pady=5, sticky="w") - - # WooCommerce Product ID section - self.product_id_label = Label(self.tab, text="Product ID:") - self.product_id_label.grid(row=2, column=0, padx=5, pady=5, sticky="w") - - self.product_id_entry = Entry(self.tab) - self.product_id_entry.grid(row=3, column=0, padx=5, pady=5, sticky="w") - - # SKU section - self.additional_name_label = Label(self.tab, text="Add suffix:") - self.additional_name_label.grid( - row=2, column=1, padx=5, pady=5, sticky="w") - - self.additional_name_entry = Entry(self.tab) - self.additional_name_entry.grid( - row=2, column=2, padx=5, pady=5, sticky="w") - - # Options button - self.options_button = ttk.Button( + # Options button + self.options_button = ctk.CTkButton( self.tab, text="Options", command=self.open_options_window ) - self.options_button.grid(row=2, column=3, columnspan=2, padx=5, pady=5, sticky="w") + self.options_button.grid(row=current_row, column=4, columnspan=2, padx=5, pady=5, sticky="w") - self.button_start = Button( + current_row += 1 + + self.button_start = ctk.CTkButton( self.tab, text="Start Processing", command=self.start_processing ) self.button_start.grid( - row=1, column=3, columnspan=2, padx=5, pady=5, sticky="w" + row=current_row, column=4, columnspan=2, padx=5, pady=5, sticky="w" ) + self.browse_button = ctk.CTkButton( + self.tab, text="Browse directory", command=self.browse_directory_command + ) + self.browse_button.grid(row=current_row, column=0, columnspan=2, padx=5, pady=5, sticky="w") + + self.browse_file_button = ctk.CTkButton( + self.tab, text="Browse file", command=self.browse_file_command + ) + self.browse_file_button.grid(row=current_row, column=0, columnspan=2, padx=5, pady=5, sticky="w") + + # WooCommerce Product ID section + self.product_id_button = ctk.CTkButton(self.tab, text="Get", width=25) + self.product_id_button.grid(row=2, column=2, columnspan=1, padx=5, pady=5, sticky="w") + + self.product_id_entry = ctk.CTkEntry(self.tab) + self.product_id_entry.grid(row=current_row, column=0, columnspan=2,padx=5, pady=5, sticky="w") + + # SKU section + self.additional_name_label = ctk.CTkLabel(self.tab, text="Add suffix:") + self.additional_name_label.grid( + row=current_row, column=1, padx=5, pady=5, sticky="w") + + self.additional_name_entry = ctk.CTkEntry(self.tab) + self.additional_name_entry.grid( + row=current_row, column=2, padx=5, pady=5, sticky="w") + + current_row += 1 + + # Destination selection section + self.destionation_label = ctk.CTkLabel(self.tab, anchor="w", width=500, text="Destination Type:") + self.destionation_label.grid(row=current_row, column=0, columnspan=6, padx=5, pady=5, sticky="w") + + current_row += 1 + + self.destionation_dropdown = ctk.CTkComboBox( + self.tab, + variable=self.source_type, + values=["auto", "directory", "file", "wp_image", "product"], + state="readonly", + command=self.update_options + ) + self.destionation_dropdown.grid(row=current_row, column=0, columnspan=2, padx=5, pady=5, sticky="w") + self.destionation_dropdown.bind( + "<>", lambda e: self.update_options() + ) + + current_row += 1 + # Image previews - self.before_label = Label(self.tab, text="Before:") - self.before_label.grid(row=5, column=0, padx=5, pady=5, sticky="w") - self.before_image_label = Label(self.tab) - self.before_image_label.grid( - row=6, column=0, padx=5, pady=5, sticky="w") + self.before_label = ctk.CTkLabel(self.tab, text="Before:") + self.before_label.grid(row=current_row, column=0, padx=5, pady=5, sticky="w") - self.after_label = Label(self.tab, text="After:") - self.after_label.grid(row=5, column=1, padx=5, pady=5, sticky="w") - self.after_image_label = Label(self.tab) + self.after_label = ctk.CTkLabel(self.tab, text="After:") + self.after_label.grid(row=current_row, column=3, padx=5, pady=5, sticky="w") + + current_row += 1 + + self.after_image_label = ctk.CTkLabel(self.tab, text="") self.after_image_label.grid( - row=6, column=1, padx=5, pady=5, sticky="w") + row=current_row, column=3, columnspan=3, padx=5, pady=5, sticky="w") + + self.before_image_label = ctk.CTkLabel(self.tab, text="") + self.before_image_label.grid( + row=current_row, column=0, columnspan=3, padx=5, pady=5, sticky="w") - def update_options(self): + def update_options(self, text=None): """ Update the UI elements based on the selected source type. """ - if self.source_type.get() == "local": + self.product_id_button.grid_remove() + self.product_id_entry.grid_remove() + self.additional_name_label.grid_remove() + self.additional_name_entry.grid_remove() + self.browse_button.grid_remove() + self.browse_file_button.grid_remove() + if self.source_type.get() == "directory": self.browse_button.grid() - self.product_id_label.grid_remove() - self.product_id_entry.grid_remove() - self.additional_name_label.grid_remove() - self.additional_name_entry.grid_remove() elif self.source_type.get() == "product": - self.browse_button.grid_remove() - self.product_id_label.grid() + self.product_id_button.grid() self.product_id_entry.grid() - self.additional_name_label.grid_remove() - self.additional_name_entry.grid_remove() - else: - self.browse_button.grid_remove() - self.product_id_label.grid_remove() - self.product_id_entry.grid_remove() - self.product_id_label.grid() - self.product_id_entry.grid() - self.additional_name_label.grid_remove() - self.additional_name_entry.grid_remove() + elif self.source_type.get() == "file": + self.browse_file_button.grid() self.update_previews() def update_previews(self, before_path=None, after_path=None): @@ -200,39 +200,40 @@ class LocalProcessingTab: before_path (str, optional): The path to the 'before' image. after_path (str, optional): The path to the 'after' image. """ + first_image_path = self.file.get_first_image_path() + if not before_path and not first_image_path: + first_image_path = "images/image-7.jpg" # Set the path to your image here if before_path and after_path: before_img = Image.open(before_path) - before_img.thumbnail((150, 150)) + before_img.thumbnail((200, 200)) before_photo = ImageTk.PhotoImage(before_img) - self.before_image_label.config(image=before_photo) + self.before_image_label.configure(image=before_photo) self.before_image_label.image = before_photo after_img = Image.open(after_path) - after_img.thumbnail((150, 150)) + after_img.thumbnail((200, 200)) after_photo = ImageTk.PhotoImage(after_img) - self.after_image_label.config(image=after_photo) + self.after_image_label.configure(image=after_photo) self.after_image_label.image = after_photo - else: - first_image_path = self.file.get_first_image_path() - if first_image_path: - with tempfile.NamedTemporaryFile( - suffix=".jpg", delete=False - ) as temp_file: - output_path = temp_file.name - self.image.resize_image( - first_image_path, output_path, self.get_options() - ) - before_img = Image.open(first_image_path) - before_img.thumbnail((150, 150)) - before_photo = ImageTk.PhotoImage(before_img) - self.before_image_label.config(image=before_photo) - self.before_image_label.image = before_photo + elif first_image_path: + with tempfile.NamedTemporaryFile( + suffix=".jpg", delete=False + ) as temp_file: + output_path = temp_file.name + self.image.resize_image( + first_image_path, output_path, self.get_options() + ) + before_img = Image.open(first_image_path) + before_img.thumbnail((200, 200)) + before_photo = ImageTk.PhotoImage(before_img) + self.before_image_label.configure(image=before_photo) + self.before_image_label.image = before_photo - after_img = Image.open(output_path) - after_img.thumbnail((150, 150)) - after_photo = ImageTk.PhotoImage(after_img) - self.after_image_label.config(image=after_photo) - self.after_image_label.image = after_photo + after_img = Image.open(output_path) + after_img.thumbnail((200, 200)) + after_photo = ImageTk.PhotoImage(after_img) + self.after_image_label.configure(image=after_photo) + self.after_image_label.image = after_photo def set_image_preview(self, image_path, label): """ @@ -240,24 +241,41 @@ class LocalProcessingTab: Args: image_path (str): The path to the image file. - label (Label): The label to set the image on. + label (ctk.CTkLabel): The label to set the image on. """ img = Image.open(image_path) img.thumbnail((150, 150)) photo = ImageTk.PhotoImage(img) - label.config(image=photo) + label.configure(image=photo) label.image = photo + def browse_file_command(self): + """ + Command to browse for a file. + """ + file = self.file.browse_files() + if file: + file_name = os.path.basename(file) + if len(file_name) > 20: + file_name = f"...{file_name[-20:]}" + self.browse_file_button.configure(text=file_name) + self.apply_options(self.get_options()) + self.update_previews() + def browse_directory_command(self): """ Command to browse for a directory. """ directory = self.file.browse_directory() if directory: - self.browse_button.config(text=directory) + dir_name = os.path.basename(directory) + if len(dir_name) > 20: + dir_name = f"...{dir_name[-20:]}" + self.browse_button.configure(text=dir_name) self.apply_options(self.get_options()) self.update_previews() + def apply_canvas_size(self): """ Apply the canvas size settings and update previews. @@ -275,7 +293,6 @@ class LocalProcessingTab: Apply the canvas size settings and update previews. """ self.image.set_background_color(self.background_color) - def get_options(self) -> dict: """ @@ -288,7 +305,7 @@ class LocalProcessingTab: "selected_directory": self.browse_button.cget("text"), "canvas_width": self.canvas_width, "canvas_height": self.canvas_height, - "log_message": self.log_message, + "log_message": self.log, # Use the log method from the log_window "format_log_message": self.pprint_log_message, "update_previews": self.update_previews, "product_id": self.product_id_entry.get(), @@ -330,25 +347,25 @@ class LocalProcessingTab: "default": self.delete_images, }, "background_color": { - "type": "color", - "label": "Background Color:", + "type": "color", + "label": "Background Color:", "default": self.background_color - }, + }, "image_format": { - "type": "dropdown", - "label": "Image Format:", + "type": "dropdown", + "label": "Image Format:", "options": ["AUTO", "JPEG", "PNG", "GIF", "DZI"], "default": self.image_format }, "image_size": { - "type": "dropdown", - "label": "Image Size:", + "type": "dropdown", + "label": "Image Size:", "options": ["contain", "cover"], "default": self.image_size } } - + OptionsWindow(self.root, self.apply_options, current_options) def apply_options(self, options): @@ -359,8 +376,7 @@ class LocalProcessingTab: options (dict): The options to apply. """ if self.log_window: - self.log_window.destroy() - self.log_window = None + self.log_window.clear() # Clear the log window if it exists self.canvas_width = options["canvas_width"] self.canvas_height = options["canvas_height"] self.template = options["template"] @@ -383,17 +399,16 @@ class LocalProcessingTab: obj (object): The object to format and log. """ formatted_message = pformat(obj) - self.log_message(formatted_message) + self.log(formatted_message) def start_processing(self): """ Start the image processing based on the selected options. """ - self.create_log_window() source = self.source_type.get() options = self.get_options() - if source == "local": + if source == "directory": threading.Thread( target=self.file.process_directory_with_logging, args=(options,) ).start() @@ -402,6 +417,11 @@ class LocalProcessingTab: target=process_product_images, args=(options,) ).start() + elif source == "file": + threading.Thread( + target=self.file.proces_single_image, + args=(options,) + ).start() elif source == "all_products": threading.Thread( target=process_all_products, diff --git a/ui/log_window.py b/ui/log_window.py index 9486c0d..06e7e0f 100644 --- a/ui/log_window.py +++ b/ui/log_window.py @@ -1,18 +1,37 @@ -from tkinter import Toplevel, Text -from pprint import pprint +import customtkinter as ctk -class LogWindow(Toplevel): - def __init__(self, master=None, **kwargs): - super().__init__(master, **kwargs) - self.title("Log Window") - self.geometry("500x300") - self.text = Text(self) - self.text.pack(expand=True, fill="both") - self.protocol("WM_DELETE_WINDOW", self.hide) +class LogWindow: + def __init__(self, parent): + self.frame = ctk.CTkFrame(parent) + self.frame.pack(expand=True, fill="both") - def log(self, message): - self.text.insert("end", pprint(message) + "\n") - self.text.see("end") + self.log_text = ctk.CTkTextbox(self.frame, state="disabled", wrap="word", height=10) + self.log_text.pack(side="left", expand=True, fill="both") - def hide(self): - self.withdraw() + self.scrollbar = ctk.CTkScrollbar(self.frame, command=self.log_text.yview) + self.scrollbar.pack(side="right", fill="y") + + self.log_text.configure(yscrollcommand=self.scrollbar.set) + + def log_message(self, message): + self.log_text.configure(state="normal") + self.log_text.insert(ctk.END, message + "\n") + self.log_text.see(ctk.END) + self.log_text.configure(state="disabled") + self.log_text.update_idletasks() + + def clear(self): + self.log_text.configure(state="normal") + self.log_text.delete('1.0', ctk.END) + self.log_text.configure(state="disabled") + +# Example usage +if __name__ == "__main__": + root = ctk.CTk() + log_window = LogWindow(root) + + # Example log messages + log_window.log_message("This is a test message.") + log_window.log_message("Another test message.") + + root.mainloop() diff --git a/ui/options_window.py b/ui/options_window.py index 4a9831b..71b41b9 100644 --- a/ui/options_window.py +++ b/ui/options_window.py @@ -1,7 +1,8 @@ -import tkinter as tk -from tkinter import ttk, colorchooser, messagebox -from pprint import pprint -class OptionsWindow(tk.Toplevel): +import customtkinter as ctk +from tkinter import colorchooser, messagebox + + +class OptionsWindow(ctk.CTkToplevel): def __init__(self, parent, apply_callback, current_options): super().__init__(parent) self.title("Options") @@ -12,6 +13,8 @@ class OptionsWindow(tk.Toplevel): self.inputs = {} self.setup_ui() + self.attributes('-topmost', True) # Ensure the window stays on top + self.lift() # Bring the window to the front def setup_ui(self): """ @@ -19,27 +22,33 @@ class OptionsWindow(tk.Toplevel): """ self.row_index = 0 for name, details in self.options.items(): - if details["type"] == "number": - self.add_number_input( - name, - details["label"], - details["default"], - details["min"], - details["max"], - ) - elif details["type"] == "text": - self.add_text_input(name, details["label"], details["default"]) - elif details["type"] == "checkbox": - self.add_checkbox(name, details["label"], details["default"]) - elif details["type"] == "dropdown": - self.add_dropdown( - name, details["label"], details["options"], details["default"] - ) - elif details["type"] == "color": - self.add_color_picker(name, details["label"], details["default"]) + self.create_option(name, details) self.create_apply_button() + def create_option(self, name, details): + """ + Create an option based on its type. + """ + if details["type"] == "number": + self.add_number_input( + name, + details["label"], + details["default"], + details["min"], + details["max"], + ) + elif details["type"] == "text": + self.add_text_input(name, details["label"], details["default"]) + elif details["type"] == "checkbox": + self.add_checkbox(name, details["label"], details["default"]) + elif details["type"] == "dropdown": + self.add_dropdown( + name, details["label"], details["options"], details["default"] + ) + elif details["type"] == "color": + self.add_color_picker(name, details["label"], details["default"]) + def add_number_input(self, name, label, default, min_val, max_val): """ Add a number input field. @@ -51,10 +60,10 @@ class OptionsWindow(tk.Toplevel): min_val (int): The minimum value. max_val (int): The maximum value. """ - lbl = tk.Label(self, text=label) - lbl.grid(row=self.row_index, columnspan=1,column=0, padx=5, pady=5, sticky="w") + lbl = ctk.CTkLabel(self, text=label) + lbl.grid(row=self.row_index, columnspan=1, column=0, padx=5, pady=5, sticky="w") - entry = tk.Entry(self) + entry = ctk.CTkEntry(self) entry.insert(0, str(default)) entry.grid(row=self.row_index, columnspan=2, column=1, padx=5, pady=5, sticky="w") @@ -75,10 +84,10 @@ class OptionsWindow(tk.Toplevel): label (str): The label for the input field. default (str): The default value. """ - lbl = tk.Label(self, text=label) + lbl = ctk.CTkLabel(self, text=label) lbl.grid(row=self.row_index, column=0, padx=5, pady=5, sticky="w") - entry = tk.Entry(self) + entry = ctk.CTkEntry(self) entry.insert(0, default) entry.grid(row=self.row_index, columnspan=2, column=1, padx=5, pady=5, sticky="w") @@ -94,8 +103,8 @@ class OptionsWindow(tk.Toplevel): label (str): The label for the input field. default (bool): The default value. """ - var = tk.BooleanVar(value=default) - chk = tk.Checkbutton(self, text=label, variable=var) + var = ctk.BooleanVar(value=default) + chk = ctk.CTkCheckBox(self, text=label, variable=var) chk.grid(row=self.row_index, column=0, columnspan=2, padx=5, pady=5, sticky="w") @@ -112,32 +121,24 @@ class OptionsWindow(tk.Toplevel): options (list): The list of options. default (str): The default value. """ - lbl = tk.Label(self, text=label) + lbl = ctk.CTkLabel(self, text=label) lbl.grid(row=self.row_index, column=0, padx=5, pady=5, sticky="w") - combo = ttk.Combobox(self, values=options, state="readonly") + combo = ctk.CTkComboBox(self, values=options, state="readonly") combo.set(default) - combo.grid(row=self.row_index,columnspan=2, column=1, padx=5, pady=5, sticky="w") + combo.grid(row=self.row_index, columnspan=2, column=1, padx=5, pady=5, sticky="w") self.inputs[name] = {"type": "dropdown", "widget": combo} self.row_index += 1 - def check_transparent(self, var, color_entry, pick_button, color_preview): - if var.get(): - color_entry.config(state="disabled") - pick_button.config(state="disabled") - color_preview.config(bg="white") - else: - color_entry.config(state="normal") - pick_button.config(state="normal") - color_preview.config(bg=color_entry.get()) + def pick_color(self, button): + self.attributes('-topmost', False) # Temporarily disable topmost to allow colorchooser to be on top + color_code = colorchooser.askcolor(parent=self, title="Choose color")[1] + self.attributes('-topmost', True) # Re-enable topmost for this window - def pick_color(self, color_entry, color_preview): - color_code = colorchooser.askcolor(title="Choose color")[1] if color_code: - color_entry.delete(0, tk.END) - color_entry.insert(0, color_code) - color_preview.config(bg=color_code) + button.configure(fg_color=color_code) + self.inputs[button.name]["color"] = color_code def add_color_picker(self, name, label, default): """ @@ -148,36 +149,32 @@ class OptionsWindow(tk.Toplevel): label (str): The label for the color picker. default (str): The default color. """ - if default == "transparent": - default = "#ffffff" - var = tk.BooleanVar(value=True) - else: - var = tk.BooleanVar(value=False) - lbl = tk.Label(self, text=label) + lbl = ctk.CTkLabel(self, text=label) lbl.grid(row=self.row_index, column=0, padx=5, pady=5, sticky="w") - color_preview = tk.Label(self, bg=default, width=2, height=1) - color_preview.grid(row=self.row_index, column=1, padx=5, pady=5, sticky="w") + color_button = ctk.CTkButton(self, text="", width=30, command=lambda: self.pick_color(color_button)) + color_button.name = name + color_button.configure(fg_color=default) + color_button.grid(row=self.row_index, column=1, padx=5, pady=5, sticky="w") - color_entry = tk.Entry(self) - color_entry.insert(0, default) - color_entry.grid(row=self.row_index, column=2, padx=5, pady=5, sticky="w") + chk_var = ctk.BooleanVar(value=(default == "transparent")) + chk = ctk.CTkCheckBox(self, text="Transparent", variable=chk_var, command=lambda: self.check_transparent(chk_var, color_button)) + chk.grid(row=self.row_index, column=2, padx=5, pady=5, sticky="w") - pick_button = tk.Button(self, text="Pick", command=lambda: self.pick_color(color_entry, color_preview)) - pick_button.grid(row=self.row_index, column=3, padx=5, pady=5, sticky="w") - - - chk = tk.Checkbutton(self, text="Transparent", variable=var, command=lambda: self.check_transparent(var, color_entry, pick_button, color_preview)) - chk.grid(row=self.row_index, column=4, padx=5, pady=5, sticky="w") - - self.inputs[name] = {"type": "color", "entry": color_entry, "transparent_var": var} + self.inputs[name] = {"type": "color", "button": color_button, "transparent_var": chk_var, "color": default} self.row_index += 1 + def check_transparent(self, var, button): + if var.get(): + button.configure(state="disabled", fg_color="#ffffff") + else: + button.configure(state="normal") + def create_apply_button(self): """ Create the apply button. """ - apply_button = tk.Button( + apply_button = ctk.CTkButton( self, text="Apply", command=self.apply_options) apply_button.grid(row=self.row_index, column=0, columnspan=2, pady=10) @@ -205,10 +202,10 @@ class OptionsWindow(tk.Toplevel): elif details["type"] == "dropdown": options[name] = details["widget"].get() elif details["type"] == "color": - if "value" in details: - options[name] = details["value"] - else: + if details["transparent_var"].get(): options[name] = "transparent" + else: + options[name] = details["color"] self.apply_callback(options) self.destroy() @@ -222,33 +219,7 @@ class OptionsWindow(tk.Toplevel): condition (function): The condition function that returns a boolean. """ if condition(): - if self.inputs[name]["type"] == "number": - self.add_number_input( - name, - self.inputs[name]["label"], - self.inputs[name]["default"], - self.inputs[name]["min"], - self.inputs[name]["max"], - ) - elif self.inputs[name]["type"] == "text": - self.add_text_input( - name, self.inputs[name]["label"], self.inputs[name]["default"] - ) - elif self.inputs[name]["type"] == "checkbox": - self.add_checkbox( - name, self.inputs[name]["label"], self.inputs[name]["default"] - ) - elif self.inputs[name]["type"] == "dropdown": - self.add_dropdown( - name, - self.inputs[name]["label"], - self.inputs[name]["options"], - self.inputs[name]["default"], - ) - elif self.inputs[name]["type"] == "color": - self.add_color_picker( - name, self.inputs[name]["label"], self.inputs[name]["default"] - ) + self.create_option(name, self.inputs[name]) # Example usage @@ -256,7 +227,7 @@ if __name__ == "__main__": def apply_options(options): print(options) - root = tk.Tk() + root = ctk.CTk() current_options = { "canvas_width": {"type": "number", "label": "Width:", "default": 900, "min": 1, "max": 2540}, "canvas_height": {"type": "number", "label": "Height:", "default": 900, "min": 1, "max": 2540}, diff --git a/ui/settings_tab.py b/ui/settings_tab.py index 72468c3..af198fa 100644 --- a/ui/settings_tab.py +++ b/ui/settings_tab.py @@ -1,60 +1,49 @@ -import tkinter as tk -from tkinter import ttk +import customtkinter as ctk from api.woocommerce_api import save_credentials, load_credentials - class SettingsTab: - def __init__(self, tab_parent, text): - self.tab = ttk.Frame(tab_parent) - tab_parent.add(self.tab, text=text) - + def __init__(self, tab_parent): + self.tab = ctk.CTkFrame(tab_parent) + self.tab.grid(row=0, column=0, sticky="nsew") self.credentials = load_credentials() + self.inputs = {} self.setup_ui() def setup_ui(self): - url_label = tk.Label(self.tab, text="WooCommerce URL:") - url_label.pack(pady=5) + settings_options = { + "url": {"type": "text", "label": "WooCommerce URL:", "default": self.credentials.get('url', '')}, + "consumer_key": {"type": "text", "label": "Consumer Key:", "default": self.credentials.get('consumer_key', '')}, + "consumer_secret": {"type": "text", "label": "Consumer Secret:", "default": self.credentials.get('consumer_secret', '')}, + "username": {"type": "text", "label": "Username:", "default": self.credentials.get('username', '')}, + "password": {"type": "text", "label": "Password:", "default": self.credentials.get('password', ''), "show": "*"} + } - self.url_entry = tk.Entry(self.tab) - self.url_entry.insert(0, self.credentials.get('url', '')) - self.url_entry.pack(pady=5) + row_index = 0 + for name, details in settings_options.items(): + self.create_setting(name, details, row_index) + row_index += 1 - consumer_key_label = tk.Label(self.tab, text="Consumer Key:") - consumer_key_label.pack(pady=5) + save_button = ctk.CTkButton(self.tab, text="Save Credentials", command=self.save_credentials) + save_button.grid(row=row_index, column=0, columnspan=2, pady=10) - self.consumer_key_entry = tk.Entry(self.tab) - self.consumer_key_entry.insert(0, self.credentials.get('consumer_key', '')) - self.consumer_key_entry.pack(pady=5) + def create_setting(self, name, details, row_index): + """ + Create a setting based on its type. + """ + lbl = ctk.CTkLabel(self.tab, text=details["label"]) + lbl.grid(row=row_index, column=0, padx=5, pady=5, sticky="w") - consumer_secret_label = tk.Label(self.tab, text="Consumer Secret:") - consumer_secret_label.pack(pady=5) - - self.consumer_secret_entry = tk.Entry(self.tab, show="*") - self.consumer_secret_entry.insert(0, self.credentials.get('consumer_secret', '')) - self.consumer_secret_entry.pack(pady=5) - - username_label = tk.Label(self.tab, text="Username:") - username_label.pack(pady=5) - - self.username_entry = tk.Entry(self.tab) - self.username_entry.insert(0, self.credentials.get('username', '')) - self.username_entry.pack(pady=5) - - password_label = tk.Label(self.tab, text="Password:") - password_label.pack(pady=5) - - self.password_entry = tk.Entry(self.tab, show="*") - self.password_entry.insert(0, self.credentials.get('password', '')) - self.password_entry.pack(pady=5) - - save_button = tk.Button(self.tab, text="Save Credentials", command=self.save_credentials) - save_button.pack(pady=5) + if details["type"] == "text": + entry = ctk.CTkEntry(self.tab, show=details.get("show", None)) + entry.insert(0, details["default"]) + entry.grid(row=row_index, column=1, padx=5, pady=5, sticky="w") + self.inputs[name] = entry def save_credentials(self): save_credentials( - self.url_entry.get(), - self.consumer_key_entry.get(), - self.consumer_secret_entry.get(), - self.username_entry.get(), - self.password_entry.get() - ) \ No newline at end of file + self.inputs["url"].get(), + self.inputs["consumer_key"].get(), + self.inputs["consumer_secret"].get(), + self.inputs["username"].get(), + self.inputs["password"].get() + ) diff --git a/utils/file_operations.py b/utils/file_operations.py index c40e270..308ea0c 100644 --- a/utils/file_operations.py +++ b/utils/file_operations.py @@ -11,6 +11,7 @@ class FileProcessor: """ def __init__(self): + self.selected_file = "" self.selected_directory = "" def browse_directory(self): @@ -23,6 +24,16 @@ class FileProcessor: self.selected_directory = filedialog.askdirectory() return self.selected_directory + def browse_files(self): + """ + Open a dialog to select a directory. + + Returns: + str: The selected directory path. + """ + self.selected_file = filedialog.askopenfilename() + return self.selected_file + def get_first_image_path(self): """ Get the path of the first image in the selected directory. @@ -30,6 +41,9 @@ class FileProcessor: Returns: str: The path to the first image, or None if no images found. """ + if self.selected_file: + return self.selected_file + if not self.selected_directory: return None @@ -159,6 +173,31 @@ class FileProcessor: os.remove(file_path) self.log_message(f"Processed: {file_path}", log) + def proces_single_image(self, options): + """ + Process images in the selected directory with logging. + + Args: + options (dict): Processing options. + """ + if not self.selected_file: + messagebox.showwarning( + "No File", "Please select a file.") + return + log = options.get("log_message", None) + self.log_message( + f"Processing started for file: {self.selected_file}", log + ) + + output_directory = self.create_output_directory(log) + image_paths = [self.selected_file] + + self.process_images(image_paths, output_directory, options, log) + + messagebox.showinfo("Process Complete", + "Image processing is complete.") + self.log_message("Processing complete.", log) + def generate_output_path(self, output_directory, file_path, options, product = None): """ Generate the output path for resized images based on a template.