UIpdate image script
93
ui/button_frame.py
Normal file
@@ -0,0 +1,93 @@
|
||||
import customtkinter as ctk
|
||||
from PIL import Image
|
||||
from tkinter import StringVar
|
||||
import os
|
||||
import sys
|
||||
|
||||
def resource_path(relative_path):
|
||||
""" Get the absolute path to a resource, whether we're running in development or a PyInstaller package. """
|
||||
try:
|
||||
# PyInstaller stores files in _MEIPASS when built
|
||||
base_path = sys._MEIPASS
|
||||
except Exception:
|
||||
base_path = os.path.abspath(".")
|
||||
|
||||
return os.path.join(base_path, 'ui/images/'+ relative_path +'.png')
|
||||
class ButtonFrame:
|
||||
"""
|
||||
Class for creating and managing the button frame.
|
||||
"""
|
||||
|
||||
def __init__(self, parent_frame, controller, log_window):
|
||||
"""
|
||||
Initialize the ButtonFrame.
|
||||
|
||||
Args:
|
||||
parent_frame (ctk.CTkFrame): The parent frame where the buttons will be placed.
|
||||
controller (AppController): The controller to handle logic and updates.
|
||||
log_window (LogWindow): The log window to display log messages.
|
||||
"""
|
||||
self.parent_frame = parent_frame
|
||||
self.controller = controller
|
||||
self.log_window = log_window
|
||||
# self.log = self.log_window.log_message
|
||||
|
||||
self.buttons = {"directory", "file", "wp_image", "product", "all_products"}
|
||||
self.source_buttons = {}
|
||||
self.selected_button = StringVar(value="") # To store the selected button
|
||||
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
"""
|
||||
Set up the UI for the button frame.
|
||||
"""
|
||||
row = 0
|
||||
self.create_buttons(self.parent_frame, self.buttons, self.source_buttons, row)
|
||||
|
||||
def create_buttons(self, frame, button_data, button_store, row):
|
||||
"""
|
||||
Create buttons from the button_data list and store them in button_store.
|
||||
"""
|
||||
|
||||
col_index = 0
|
||||
for label in button_data:
|
||||
path = resource_path(label)
|
||||
display_label = label.replace("_", " ").title()
|
||||
icon_path = path
|
||||
if icon_path:
|
||||
icon_image = ctk.CTkImage(
|
||||
light_image=Image.open(icon_path), size=(24, 24)
|
||||
)
|
||||
else:
|
||||
icon_image = None
|
||||
|
||||
button = ctk.CTkButton(
|
||||
frame,
|
||||
image=icon_image,
|
||||
text=display_label,
|
||||
font=("Helvetica", 12, "bold"),
|
||||
command=lambda l=label, s=button_store: self.set_active_button(l, s),
|
||||
fg_color="#666666",
|
||||
hover_color="#0f4d0f",
|
||||
compound="top",
|
||||
width=100,
|
||||
|
||||
)
|
||||
button.grid(row=row, column=col_index,columnspan=1, padx=5, pady=5, sticky="ew")
|
||||
button_store[label] = button
|
||||
col_index += 1
|
||||
|
||||
def set_active_button(self, active_label, button_store):
|
||||
"""
|
||||
Set the clicked button to green and the rest to gray for a specific button store.
|
||||
Also update the description and input fields based on the active button.
|
||||
"""
|
||||
if self.controller.status != "started":
|
||||
for label, button in button_store.items():
|
||||
if label == active_label:
|
||||
self.controller.update_options(active_label)
|
||||
button.configure(fg_color="#008000")
|
||||
else:
|
||||
button.configure(fg_color="#666666")
|
||||
|
||||
60
ui/frame_info.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# info_frame.py
|
||||
|
||||
import customtkinter as ctk
|
||||
from tkinter import filedialog
|
||||
|
||||
|
||||
class InfoFrame:
|
||||
"""
|
||||
Class for managing the info frame where descriptions and input fields are shown.
|
||||
"""
|
||||
|
||||
def __init__(self, parent_frame):
|
||||
"""
|
||||
Initialize the InfoFrame.
|
||||
|
||||
Args:
|
||||
parent_frame (ctk.CTkFrame): The parent frame for the info section.
|
||||
log_window (LogWindow): The log window to display log messages.
|
||||
"""
|
||||
self.parent_frame = parent_frame
|
||||
|
||||
self.selected_button_label = None
|
||||
self.description_label = None
|
||||
self.input_field = None
|
||||
self.input_button = None
|
||||
self.prev_button = None
|
||||
self.next_button = None
|
||||
self.destination_button = None
|
||||
self.destination_label = None
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
"""
|
||||
Set up the UI for the info frame.
|
||||
"""
|
||||
# Label to display the selected button name
|
||||
self.selected_button_label = ctk.CTkLabel(
|
||||
self.parent_frame, text="", font=("Helvetica", 12, "bold")
|
||||
)
|
||||
self.selected_button_label.grid(row=0, column=0, columnspan=12, padx=5, pady=5, sticky="w")
|
||||
|
||||
# Description label to provide info about the selected button
|
||||
self.description_label = ctk.CTkLabel(
|
||||
self.parent_frame, text="", font=("Helvetica", 10)
|
||||
)
|
||||
self.description_label.grid(row=1, column=0, columnspan=3, padx=5, pady=5, sticky="w")
|
||||
|
||||
def process_product(self, product_id):
|
||||
# Handle product processing logic here
|
||||
self.log(f"Processing product with ID: {product_id}")
|
||||
|
||||
def browse_file(self):
|
||||
# Open file dialog to select a file
|
||||
file_path = filedialog.askopenfilename()
|
||||
|
||||
|
||||
def browse_directory(self):
|
||||
# Open directory dialog to select a directory
|
||||
directory_path = filedialog.askdirectory()
|
||||
|
||||
BIN
ui/images/all_products.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
ui/images/cogs.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
ui/images/directory.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
ui/images/file.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
ui/images/filters.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
ui/images/house-user-solid.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
ui/images/play.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
ui/images/product.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
ui/images/save.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
ui/images/trash.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
ui/images/wp_image.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -1,423 +0,0 @@
|
||||
import tempfile
|
||||
import threading
|
||||
import customtkinter as ctk
|
||||
from tkinter.scrolledtext import ScrolledText
|
||||
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
|
||||
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, log_window):
|
||||
"""
|
||||
Initialize the LocalProcessingTab.
|
||||
|
||||
Args:
|
||||
tab_parent (ctk.CTkFrame): The parent frame widget.
|
||||
log_window (LogWindow): The log window frame.
|
||||
"""
|
||||
key = b"u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI="
|
||||
|
||||
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
|
||||
self.config = ConfigEncryptor(key)
|
||||
|
||||
self.canvas_width = 900
|
||||
self.canvas_height = 900
|
||||
self.template = "{slug}_{sku}_{width}x{height}"
|
||||
self.delete_images = False
|
||||
self.transparent = True
|
||||
self.background_color = "#000000"
|
||||
self.image_format = "AUTO"
|
||||
self.image_size = "contain"
|
||||
self.load_config()
|
||||
self.source_type = StringVar(value="directory")
|
||||
self.checkbox_var = BooleanVar(value=False)
|
||||
self.file = FileProcessor()
|
||||
self.image = ImageProcessor()
|
||||
# Automatically open the options window with default options
|
||||
|
||||
self.setup_ui()
|
||||
self.update_options()
|
||||
|
||||
def load_config(self):
|
||||
config = self.config.load_config()
|
||||
if config:
|
||||
if options := config.get("options"):
|
||||
self.canvas_width = options.get("canvas_width", 900)
|
||||
self.canvas_height = options.get("canvas_height", 900)
|
||||
self.template = options.get("template", "{slug}_{sku}_{width}x{height}")
|
||||
self.delete_images = options.get("delete_images", False)
|
||||
self.transparent = options.get("transparent", True)
|
||||
self.background_color = options.get("background_color", "#000000")
|
||||
self.image_format = options.get("image_format", "AUTO")
|
||||
self.image_size = options.get("image_size", "contain")
|
||||
|
||||
def setup_ui(self):
|
||||
"""
|
||||
Set up the user interface for the tab.
|
||||
"""
|
||||
current_row = 0
|
||||
start_options_frame = ctk.CTkFrame(self.tab, bg_color="gray30")
|
||||
start_options_frame.grid(row=current_row, column=0, columnspan=6, padx=5, pady=5, sticky="ew")
|
||||
|
||||
self.options_button = ctk.CTkButton(
|
||||
start_options_frame, text="Options", command=self.open_options_window
|
||||
)
|
||||
self.options_button.grid(row=0, column=0, columnspan=2, padx=5, pady=5, sticky="w")
|
||||
|
||||
self.button_start = ctk.CTkButton(
|
||||
start_options_frame, text="Start Processing", command=self.start_processing
|
||||
)
|
||||
self.button_start.grid(row=0, column=2, columnspan=2, padx=5, pady=5, sticky="w")
|
||||
|
||||
# Image previews section
|
||||
current_row += 1
|
||||
|
||||
# Source selection section
|
||||
source_frame = ctk.CTkFrame(self.tab, bg_color="gray20")
|
||||
source_frame.grid(row=current_row, column=0, columnspan=6, padx=5, pady=5, sticky="ew")
|
||||
|
||||
source_label = ctk.CTkLabel(source_frame, anchor="w", text="Source Type:")
|
||||
source_label.grid(row=0, column=0, columnspan=6, padx=5, pady=5, sticky="w")
|
||||
|
||||
self.source_dropdown = ctk.CTkComboBox(
|
||||
source_frame,
|
||||
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, columnspan=2, padx=5, pady=5, sticky="w")
|
||||
self.source_dropdown.bind(
|
||||
"<<ComboboxSelected>>", lambda e: self.update_options()
|
||||
)
|
||||
|
||||
self.browse_button = ctk.CTkButton(
|
||||
source_frame, text="Browse directory", command=self.browse_directory_command
|
||||
)
|
||||
self.browse_button.grid(row=2, column=0, columnspan=2, padx=5, pady=5, sticky="w")
|
||||
|
||||
self.browse_file_button = ctk.CTkButton(
|
||||
source_frame, text="Browse file", command=self.browse_file_command
|
||||
)
|
||||
self.browse_file_button.grid(row=2, column=2, columnspan=2, padx=5, pady=5, sticky="w")
|
||||
|
||||
self.product_id_button = ctk.CTkButton(source_frame, text="Get", width=25)
|
||||
self.product_id_button.grid(row=2, column=4, columnspan=1, padx=5, pady=5, sticky="w")
|
||||
|
||||
self.product_id_entry = ctk.CTkEntry(source_frame)
|
||||
self.product_id_entry.grid(row=2, column=5, columnspan=2, padx=5, pady=5, sticky="w")
|
||||
|
||||
self.additional_name_label = ctk.CTkLabel(source_frame, text="Add suffix:")
|
||||
self.additional_name_label.grid(row=2, column=7, padx=5, pady=5, sticky="w")
|
||||
|
||||
self.additional_name_entry = ctk.CTkEntry(source_frame)
|
||||
self.additional_name_entry.grid(row=2, column=8, padx=5, pady=5, sticky="w")
|
||||
|
||||
# Destination selection section
|
||||
current_row += 1
|
||||
# destination_frame = ctk.CTkFrame(self.tab, bg_color="gray25")
|
||||
# destination_frame.grid(row=current_row, column=0, columnspan=6, padx=5, pady=5, sticky="ew")
|
||||
|
||||
# destination_label = ctk.CTkLabel(destination_frame, anchor="w", text="Destination Type:")
|
||||
# destination_label.grid(row=0, column=0, columnspan=6, padx=5, pady=5, sticky="w")
|
||||
|
||||
# self.destination_dropdown = ctk.CTkComboBox(
|
||||
# destination_frame,
|
||||
# variable=self.source_type,
|
||||
# values=["auto", "directory", "file", "wp_image", "product"],
|
||||
# state="readonly",
|
||||
# command=self.update_options
|
||||
# )
|
||||
# self.destination_dropdown.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky="w")
|
||||
|
||||
# # Start and Options section
|
||||
# current_row += 1
|
||||
|
||||
preview_frame = ctk.CTkFrame(self.tab, bg_color="gray35")
|
||||
preview_frame.grid(row=current_row, column=0, columnspan=6, padx=5, pady=5, sticky="ew")
|
||||
|
||||
self.before_label = ctk.CTkLabel(preview_frame, text="Before:")
|
||||
self.before_label.grid(row=0, column=0, padx=5, pady=5, sticky="w")
|
||||
|
||||
self.after_label = ctk.CTkLabel(preview_frame, text="After:")
|
||||
self.after_label.grid(row=0, column=3, padx=5, pady=5, sticky="w")
|
||||
|
||||
self.before_image_label = ctk.CTkLabel(preview_frame, text="")
|
||||
self.before_image_label.grid(row=1, column=0, columnspan=3, padx=5, pady=5, sticky="w")
|
||||
|
||||
self.after_image_label = ctk.CTkLabel(preview_frame, text="")
|
||||
self.after_image_label.grid(row=1, column=3, columnspan=3, padx=5, pady=5, sticky="w")
|
||||
|
||||
# Configure grid weights to make frames span the full width
|
||||
self.tab.grid_columnconfigure(0, weight=1)
|
||||
source_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
start_options_frame.grid_columnconfigure(0, weight=1)
|
||||
preview_frame.grid_columnconfigure(0, weight=1)
|
||||
|
||||
def update_options(self, text=None):
|
||||
"""
|
||||
Update the UI elements based on the selected source type.
|
||||
"""
|
||||
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()
|
||||
elif self.source_type.get() == "product":
|
||||
self.product_id_button.grid()
|
||||
self.product_id_entry.grid()
|
||||
elif self.source_type.get() == "file":
|
||||
self.browse_file_button.grid()
|
||||
self.update_previews()
|
||||
|
||||
def update_previews(self, before_path=None, after_path=None):
|
||||
"""
|
||||
Update the image previews.
|
||||
|
||||
Args:
|
||||
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 before_path and after_path:
|
||||
before_img = Image.open(before_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(after_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
|
||||
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((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):
|
||||
"""
|
||||
Set the image preview for a given label.
|
||||
|
||||
Args:
|
||||
image_path (str): The path to the image file.
|
||||
label (ctk.CTkLabel): The label to set the image on.
|
||||
"""
|
||||
img = Image.open(image_path)
|
||||
img.thumbnail((150, 150))
|
||||
photo = ImageTk.PhotoImage(img)
|
||||
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:
|
||||
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.
|
||||
"""
|
||||
self.image.set_canvas_size(self.canvas_width, self.canvas_height)
|
||||
|
||||
def apply_image_size(self):
|
||||
"""
|
||||
Apply the canvas size settings and update previews.
|
||||
"""
|
||||
self.image.set_image_size(self.image_size)
|
||||
|
||||
def apply_background_color(self):
|
||||
"""
|
||||
Apply the canvas size settings and update previews.
|
||||
"""
|
||||
self.image.set_background_color(self.background_color)
|
||||
|
||||
def get_options(self) -> dict:
|
||||
"""
|
||||
Get the current processing options.
|
||||
|
||||
Returns:
|
||||
dict: The current processing options.
|
||||
"""
|
||||
options = {
|
||||
"selected_directory": self.browse_button.cget("text"),
|
||||
"canvas_width": self.canvas_width,
|
||||
"canvas_height": self.canvas_height,
|
||||
"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(),
|
||||
"template": self.template,
|
||||
"delete_images": self.delete_images,
|
||||
"background_color": self.background_color,
|
||||
"image_format": self.image_format,
|
||||
"image_size": self.image_size,
|
||||
}
|
||||
return options
|
||||
|
||||
def open_options_window(self):
|
||||
"""
|
||||
Open the options window.
|
||||
"""
|
||||
current_options = {
|
||||
"canvas_width": {
|
||||
"type": "number",
|
||||
"label": "Width:",
|
||||
"default": self.canvas_width,
|
||||
"min": 1,
|
||||
"max": 2540,
|
||||
},
|
||||
"canvas_height": {
|
||||
"type": "number",
|
||||
"label": "Height:",
|
||||
"default": self.canvas_height,
|
||||
"min": 1,
|
||||
"max": 2540,
|
||||
},
|
||||
"template": {
|
||||
"type": "text",
|
||||
"label": "Filename Template:",
|
||||
"default": self.template,
|
||||
},
|
||||
"delete_images": {
|
||||
"type": "checkbox",
|
||||
"label": "Delete image when done",
|
||||
"default": self.delete_images,
|
||||
},
|
||||
"background_color": {
|
||||
"type": "color",
|
||||
"label": "Background Color:",
|
||||
"default": self.background_color
|
||||
},
|
||||
"image_format": {
|
||||
"type": "dropdown",
|
||||
"label": "Image Format:",
|
||||
"options": ["AUTO", "JPEG", "PNG", "GIF", "DZI"],
|
||||
"default": self.image_format
|
||||
},
|
||||
"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):
|
||||
"""
|
||||
Apply the selected options from the options window.
|
||||
|
||||
Args:
|
||||
options (dict): The options to apply.
|
||||
"""
|
||||
if self.log_window:
|
||||
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"]
|
||||
self.delete_images = options["delete_images"]
|
||||
self.background_color = options["background_color"]
|
||||
self.image_size = options["image_size"]
|
||||
self.image_format = options["image_format"]
|
||||
self.apply_canvas_size()
|
||||
self.apply_background_color()
|
||||
self.apply_image_size()
|
||||
key = b"u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI="
|
||||
self.config.save_options(self.get_options())
|
||||
self.update_previews()
|
||||
|
||||
def pprint_log_message(self, obj):
|
||||
"""
|
||||
Log a formatted message to the log window using pprint.
|
||||
|
||||
Args:
|
||||
obj (object): The object to format and log.
|
||||
"""
|
||||
formatted_message = pformat(obj)
|
||||
self.log(formatted_message)
|
||||
|
||||
def start_processing(self):
|
||||
"""
|
||||
Start the image processing based on the selected options.
|
||||
"""
|
||||
source = self.source_type.get()
|
||||
options = self.get_options()
|
||||
|
||||
if source == "directory":
|
||||
threading.Thread(
|
||||
target=self.file.process_directory_with_logging, args=(options,)
|
||||
).start()
|
||||
elif source == "product":
|
||||
threading.Thread(
|
||||
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,
|
||||
args=(options,)
|
||||
).start()
|
||||
self.update_previews()
|
||||
@@ -1,5 +1,5 @@
|
||||
import customtkinter as ctk
|
||||
|
||||
from datetime import datetime
|
||||
class LogWindow:
|
||||
def __init__(self, parent):
|
||||
self.frame = ctk.CTkFrame(parent)
|
||||
@@ -14,8 +14,18 @@ class LogWindow:
|
||||
self.log_text.configure(yscrollcommand=self.scrollbar.set)
|
||||
|
||||
def log_message(self, message):
|
||||
"""
|
||||
Log a message to the log window with the current timestamp.
|
||||
"""
|
||||
# Get the current time in the desired format (e.g., HH:MM:SS)
|
||||
current_time = datetime.now().strftime("%H:%M:%S")
|
||||
|
||||
# Prepend the current time to the message
|
||||
full_message = f"[{current_time}] {message}"
|
||||
|
||||
# Log the message
|
||||
self.log_text.configure(state="normal")
|
||||
self.log_text.insert(ctk.END, message + "\n")
|
||||
self.log_text.insert(ctk.END, full_message + "\n")
|
||||
self.log_text.see(ctk.END)
|
||||
self.log_text.configure(state="disabled")
|
||||
self.log_text.update_idletasks()
|
||||
103
ui/menu.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from PIL import Image
|
||||
import customtkinter as ctk
|
||||
import os
|
||||
import sys
|
||||
|
||||
def resource_path(relative_path):
|
||||
""" Get the absolute path to a resource, whether we're running in development or a PyInstaller package. """
|
||||
try:
|
||||
# PyInstaller stores files in _MEIPASS when built
|
||||
base_path = sys._MEIPASS
|
||||
except Exception:
|
||||
base_path = os.path.abspath(".")
|
||||
|
||||
return os.path.join(base_path, 'ui/images/'+ relative_path +'.png')
|
||||
|
||||
class MenuBar:
|
||||
def __init__(self, parent, controller):
|
||||
"""
|
||||
Initialize the MenuBar.
|
||||
|
||||
Args:
|
||||
parent (ctk.CTkFrame): The parent frame for the menu.
|
||||
controller (AppController): The controller instance to manage the app.
|
||||
"""
|
||||
self.parent = parent
|
||||
self.controller = controller
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
|
||||
button_width = 40
|
||||
icon_size = 24
|
||||
# Create menu frame
|
||||
self.menu_frame = ctk.CTkFrame(self.parent)
|
||||
self.menu_frame.pack(side="top", fill="x")
|
||||
|
||||
# Create the buttons with icons
|
||||
self.create_menu_button(
|
||||
"house-user-solid",
|
||||
"#363636",
|
||||
"",
|
||||
self.controller.show_local_processing_tab,
|
||||
button_width,
|
||||
icon_size,
|
||||
)
|
||||
self.create_menu_button(
|
||||
"filters",
|
||||
"#363636",
|
||||
"",
|
||||
self.controller.show_local_processing_options,
|
||||
button_width,
|
||||
icon_size,
|
||||
)
|
||||
self.create_menu_button(
|
||||
"cogs",
|
||||
"#363636",
|
||||
"",
|
||||
self.controller.show_settings_tab,
|
||||
button_width,
|
||||
icon_size,
|
||||
)
|
||||
|
||||
self.start_button = self.create_menu_button(
|
||||
"play",
|
||||
"#008000",
|
||||
"Start",
|
||||
self.controller.start_processing,
|
||||
button_width,
|
||||
icon_size,
|
||||
side="right",
|
||||
)
|
||||
|
||||
|
||||
def create_menu_button(
|
||||
self, icon_path, bg_color, text, command, button_width, icon_size, side="left"
|
||||
):
|
||||
"""
|
||||
Create a button with an icon for the menu.
|
||||
|
||||
Args:
|
||||
icon_path (str): Path to the icon.
|
||||
command (callable): The function to call when the button is pressed.
|
||||
button_width (int): The width of the button.
|
||||
icon_size (int): The size of the icon.
|
||||
side (str): Where to place the button ('left' or 'right').
|
||||
"""
|
||||
|
||||
if icon_path:
|
||||
path = resource_path(icon_path)
|
||||
icon_image = ctk.CTkImage(
|
||||
light_image=Image.open(path), size=(icon_size, icon_size)
|
||||
)
|
||||
|
||||
button = ctk.CTkButton(
|
||||
self.menu_frame,
|
||||
image=icon_image,
|
||||
text=text,
|
||||
fg_color=bg_color,
|
||||
command=command,
|
||||
width=button_width,
|
||||
)
|
||||
button.pack(side=side, padx=5, pady=5)
|
||||
return button
|
||||
84
ui/preview_frame.py
Normal file
@@ -0,0 +1,84 @@
|
||||
import customtkinter as ctk
|
||||
from PIL import Image
|
||||
|
||||
class PreviewFrame:
|
||||
"""
|
||||
Class to handle the preview frames (Before and After) for image processing.
|
||||
"""
|
||||
|
||||
def __init__(self, parent):
|
||||
"""
|
||||
Initialize the PreviewFrame.
|
||||
|
||||
Args:
|
||||
parent (ctk.CTkFrame): The parent frame where the preview frames will be placed.
|
||||
"""
|
||||
|
||||
self.parent = parent
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
"""
|
||||
Set up the user interface for the preview frames.
|
||||
"""
|
||||
row = 0
|
||||
start_row = row
|
||||
# Creating the main preview frame
|
||||
preview_frame = ctk.CTkFrame(self.parent, bg_color="gray35")
|
||||
preview_frame.grid(row=row, column=0, columnspan=6, padx=5, pady=5, sticky="ew")
|
||||
|
||||
# Ensure the preview_frame expands to the full width
|
||||
preview_frame.grid_columnconfigure(0, weight=1)
|
||||
preview_frame.grid_columnconfigure(1, weight=1)
|
||||
|
||||
# Creating the "Before" frame
|
||||
before_frame = ctk.CTkFrame(preview_frame)
|
||||
before_frame.grid(row=row, column=0, padx=5, pady=5, sticky="ew")
|
||||
|
||||
# Adding "Before" label and image label to the before_frame
|
||||
self.before_label = ctk.CTkLabel(before_frame, text="Before:")
|
||||
self.before_label.grid(row=row, column=0, padx=5, pady=5, sticky="w")
|
||||
row += 1
|
||||
self.before_image_label = ctk.CTkLabel(before_frame, text="", height=175)
|
||||
self.before_image_label.grid(row=row, column=0, padx=5, pady=5, sticky="w")
|
||||
row += 1
|
||||
# Adding a filename label under the "Before" image label
|
||||
self.before_filename_label = ctk.CTkLabel(before_frame, text="Filename")
|
||||
self.before_filename_label.grid(row=row, column=0, padx=5, pady=5, sticky="w")
|
||||
|
||||
# Creating the "After" frame
|
||||
after_frame = ctk.CTkFrame(preview_frame)
|
||||
after_frame.grid(row=start_row, column=1, padx=5, pady=5, sticky="ew")
|
||||
|
||||
# Adding "After" label and image label to the after_frame
|
||||
self.after_label = ctk.CTkLabel(after_frame, text="After:")
|
||||
self.after_label.grid(row=start_row, column=0, padx=5, pady=5, sticky="w")
|
||||
start_row += 1
|
||||
self.after_image_label = ctk.CTkLabel(after_frame, text="", height=175)
|
||||
self.after_image_label.grid(row=start_row, column=0, padx=5, pady=5, sticky="w")
|
||||
start_row += 1
|
||||
# Adding a filename label under the "After" image label
|
||||
self.after_filename_label = ctk.CTkLabel(after_frame, text="Filename")
|
||||
self.after_filename_label.grid(row=start_row, column=0, padx=5, pady=5, sticky="w")
|
||||
|
||||
# def update_before_image(self, image, filename=""):
|
||||
# """
|
||||
# Update the before image and filename label.
|
||||
|
||||
# Args:
|
||||
# image (PIL.Image): The image to display.
|
||||
# filename (str): The filename to display.
|
||||
# """
|
||||
# self.before_image_label.config(image=image)
|
||||
# self.before_filename_label.config(text=filename)
|
||||
|
||||
# def update_after_image(self, image, filename=""):
|
||||
# """
|
||||
# Update the after image and filename label.
|
||||
|
||||
# Args:
|
||||
# image (PIL.Image): The image to display.
|
||||
# filename (str): The filename to display.
|
||||
# """
|
||||
# self.after_image_label.config(image=image)
|
||||
# self.after_filename_label.config(text=filename)
|
||||
@@ -1,58 +1,234 @@
|
||||
import customtkinter as ctk
|
||||
from api.woocommerce_api import save_credentials, load_credentials
|
||||
from api.woocommerce_api import (
|
||||
load_credentials,
|
||||
save_active_credential_set,
|
||||
)
|
||||
from config.encrypt_config import ConfigEncryptor
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
import os
|
||||
import sys
|
||||
KEY = b"u4xTBY5Ns4WYdLvqMjEr138mpMmDEhhqTszKCcDy2cI="
|
||||
def resource_path(relative_path):
|
||||
""" Get the absolute path to a resource, whether we're running in development or a PyInstaller package. """
|
||||
try:
|
||||
# PyInstaller stores files in _MEIPASS when built
|
||||
base_path = sys._MEIPASS
|
||||
except Exception:
|
||||
base_path = os.path.abspath(".")
|
||||
|
||||
return os.path.join(base_path, 'ui/images/'+ relative_path +'.png')
|
||||
|
||||
class SettingsTab:
|
||||
def __init__(self, tab_parent):
|
||||
def __init__(self, tab_parent, controller):
|
||||
self.tab = ctk.CTkFrame(tab_parent)
|
||||
self.tab.grid(row=0, column=0, sticky="nsew")
|
||||
self.credentials = load_credentials()
|
||||
# Initialize an instance of ConfigEncryptor
|
||||
self.config_encryptor = ConfigEncryptor(KEY) # Ensure you pass any required arguments in the constructor if necessary
|
||||
config = self.config_encryptor.load_config()
|
||||
self.credentials_list = []
|
||||
if config:
|
||||
self.credentials_list = self.config_encryptor.load_config().get('credentials')
|
||||
self.active_credential_set = (
|
||||
self.get_active_credential_set()
|
||||
) # Fetch active credentials
|
||||
else:
|
||||
self.active_credential_set = {
|
||||
'nice_name': "Default"
|
||||
}
|
||||
self.inputs = {}
|
||||
self.setup_ui()
|
||||
|
||||
def setup_ui(self):
|
||||
if self.credentials:
|
||||
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": "*"}
|
||||
}
|
||||
else:
|
||||
settings_options = {
|
||||
"url": {"type": "text", "label": "WooCommerce URL:", "default": ""},
|
||||
"consumer_key": {"type": "text", "label": "Consumer Key:", "default": ""},
|
||||
"consumer_secret": {"type": "text", "label": "Consumer Secret:", "default": ""},
|
||||
"username": {"type": "text", "label": "Username:", "default": ""},
|
||||
"password": {"type": "text", "label": "Password:", "default": "", "show": "*"}
|
||||
}
|
||||
def get_active_credential_set(self):
|
||||
"""Retrieve active credential set from saved data and convert to new format if needed."""
|
||||
if isinstance(self.credentials_list, list) and all(
|
||||
isinstance(cred, dict) for cred in self.credentials_list
|
||||
):
|
||||
for cred in self.credentials_list:
|
||||
if cred.get("active", False):
|
||||
return cred
|
||||
return self.credentials_list[0] if self.credentials_list else None
|
||||
|
||||
row_index = 0
|
||||
elif isinstance(self.credentials_list, dict):
|
||||
self.credentials_list = [self.convert_to_new_format(self.credentials_list)]
|
||||
return self.credentials_list[0]
|
||||
|
||||
elif isinstance(self.credentials_list, str):
|
||||
self.credentials_list = [
|
||||
self.convert_to_new_format({"url": self.credentials_list})
|
||||
]
|
||||
return self.credentials_list[0]
|
||||
|
||||
return None
|
||||
|
||||
def convert_to_new_format(self, old_credential):
|
||||
return {
|
||||
"url": old_credential.get("url", ""),
|
||||
"consumer_key": old_credential.get("consumer_key", ""),
|
||||
"consumer_secret": old_credential.get("consumer_secret", ""),
|
||||
"username": old_credential.get("username", ""),
|
||||
"password": old_credential.get("password", ""),
|
||||
"name": old_credential.get("name", "Default Credential Set"),
|
||||
"nice_name": old_credential.get("nice_name", old_credential.get("url", "Unnamed Credential")),
|
||||
"active": True,
|
||||
}
|
||||
|
||||
def setup_ui(self):
|
||||
# Dropdown to select active credentials
|
||||
self.credential_var = ctk.StringVar()
|
||||
self.credential_var.set(
|
||||
self.active_credential_set.get("nice_name", "Default")
|
||||
)
|
||||
credential_options = [
|
||||
cred.get("nice_name", "Unnamed Credential")
|
||||
for cred in self.credentials_list
|
||||
]
|
||||
|
||||
dropdown_label = ctk.CTkLabel(self.tab, text="Select Active Credentials:")
|
||||
dropdown_label.grid(row=0, column=0, padx=5, columnspan=2 , pady=5, sticky="w")
|
||||
self.credential_dropdown = ctk.CTkComboBox(
|
||||
self.tab,
|
||||
variable=self.credential_var,
|
||||
values=credential_options,
|
||||
command=self.load_selected_credential,
|
||||
)
|
||||
self.credential_dropdown.grid(row=0, column=2, columnspan=2, padx=5, pady=5, sticky="w")
|
||||
|
||||
# Show fields for credentials
|
||||
self.create_credentials_form(self.active_credential_set, row_index=1)
|
||||
|
||||
icon_path = resource_path("save")
|
||||
icon_image = ctk.CTkImage(light_image=Image.open(icon_path), size=(24, 24)) if icon_path else None
|
||||
|
||||
save_button = ctk.CTkButton(
|
||||
self.tab, width=100,fg_color="green",image=icon_image, text="Save", command=self.save_credentials
|
||||
)
|
||||
save_button.grid(row=7, column=0, columnspan=1, pady=10)
|
||||
|
||||
new_button = ctk.CTkButton(
|
||||
self.tab, width=100, fg_color="green", image=icon_image, text="New", command=self.add_new_credential_set
|
||||
)
|
||||
new_button.grid(row=7, column=1, columnspan=1, pady=10)
|
||||
|
||||
# Trash icon for delete button
|
||||
trash_icon_path = resource_path("trash")
|
||||
trash_icon_image = ctk.CTkImage(light_image=Image.open(trash_icon_path), size=(24, 24)) if trash_icon_path else None
|
||||
|
||||
delete_button = ctk.CTkButton(
|
||||
self.tab, width=100, fg_color="red", image=trash_icon_image, text="Delete", command=self.delete_selected_credential
|
||||
)
|
||||
delete_button.grid(row=7, column=2, columnspan=1, pady=10)
|
||||
|
||||
def create_credentials_form(self, credentials, row_index):
|
||||
settings_options = {
|
||||
"url": {
|
||||
"type": "text",
|
||||
"label": "WooCommerce URL:",
|
||||
"default": credentials.get("url", ""),
|
||||
},
|
||||
"consumer_key": {
|
||||
"type": "text",
|
||||
"label": "Consumer Key:",
|
||||
"default": credentials.get("consumer_key", ""),
|
||||
},
|
||||
"consumer_secret": {
|
||||
"type": "text",
|
||||
"label": "Consumer Secret:",
|
||||
"default": credentials.get("consumer_secret", ""),
|
||||
},
|
||||
"username": {
|
||||
"type": "text",
|
||||
"label": "Username:",
|
||||
"default": credentials.get("username", ""),
|
||||
},
|
||||
"password": {
|
||||
"type": "text",
|
||||
"label": "Password:",
|
||||
"default": credentials.get("password", ""),
|
||||
"show": "*",
|
||||
},
|
||||
"nice_name": {
|
||||
"type": "text",
|
||||
"label": "Nice Name:",
|
||||
"default": credentials.get("nice_name", "Unnamed Credential"),
|
||||
},
|
||||
}
|
||||
|
||||
self.inputs = {} # Reset inputs for new credentials set
|
||||
for name, details in settings_options.items():
|
||||
self.create_setting(name, details, row_index)
|
||||
row_index += 1
|
||||
|
||||
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)
|
||||
|
||||
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")
|
||||
lbl.grid(row=row_index, column=0,columnspan=2, padx=5, pady=5, sticky="w")
|
||||
|
||||
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
|
||||
entry = ctk.CTkEntry(self.tab, show=details.get("show", None))
|
||||
entry.insert(0, details["default"])
|
||||
entry.grid(row=row_index, column=2,columnspan=2, padx=5, pady=5, sticky="w")
|
||||
self.inputs[name] = entry
|
||||
|
||||
def load_selected_credential(self, selected_name):
|
||||
for cred in self.credentials_list:
|
||||
if cred.get("nice_name", "Unnamed Credential") == selected_name:
|
||||
self.active_credential_set = cred
|
||||
self.create_credentials_form(self.active_credential_set, row_index=1)
|
||||
break
|
||||
|
||||
def save_credentials(self):
|
||||
save_credentials(
|
||||
self.inputs["url"].get(),
|
||||
self.inputs["consumer_key"].get(),
|
||||
self.inputs["consumer_secret"].get(),
|
||||
self.inputs["username"].get(),
|
||||
self.inputs["password"].get()
|
||||
credentials = {
|
||||
"url": self.inputs["url"].get(),
|
||||
"consumer_key": self.inputs["consumer_key"].get(),
|
||||
"consumer_secret": self.inputs["consumer_secret"].get(),
|
||||
"username": self.inputs["username"].get(),
|
||||
"password": self.inputs["password"].get(),
|
||||
"name": self.inputs["nice_name"].get(),
|
||||
"nice_name": self.inputs["nice_name"].get(),
|
||||
"active": True,
|
||||
}
|
||||
|
||||
ConfigEncryptor(KEY).save_credentials(credentials)
|
||||
save_active_credential_set(credentials["name"])
|
||||
|
||||
self.credentials_list.append(credentials)
|
||||
self.credential_dropdown.configure(
|
||||
values=[cred.get("nice_name", "Unnamed Credential") for cred in self.credentials_list]
|
||||
)
|
||||
|
||||
def add_new_credential_set(self):
|
||||
self.active_credential_set = {
|
||||
"url": "",
|
||||
"consumer_key": "",
|
||||
"consumer_secret": "",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"name": "New Credential Set",
|
||||
"nice_name": "New Credential Set",
|
||||
"active": False,
|
||||
}
|
||||
self.create_credentials_form(self.active_credential_set, row_index=1)
|
||||
self.credential_var.set(self.active_credential_set["nice_name"])
|
||||
|
||||
def delete_selected_credential(self):
|
||||
selected_name = self.credential_var.get()
|
||||
|
||||
# Find and remove the selected credential from the list
|
||||
self.credentials_list = [
|
||||
cred for cred in self.credentials_list if cred.get("nice_name") != selected_name
|
||||
]
|
||||
|
||||
# Save updated credentials list to storage
|
||||
ConfigEncryptor(KEY).delete_credentials(selected_name)
|
||||
|
||||
# Update the dropdown and form after deletion
|
||||
if self.credentials_list:
|
||||
self.active_credential_set = self.credentials_list[0] # Load first available credential
|
||||
self.credential_var.set(self.active_credential_set["nice_name"])
|
||||
self.create_credentials_form(self.active_credential_set, row_index=1)
|
||||
else:
|
||||
self.active_credential_set = {}
|
||||
self.create_credentials_form({}, row_index=1) # Clear form if no credentials are left
|
||||
|
||||
self.credential_dropdown.configure(
|
||||
values=[cred.get("nice_name", "Unnamed Credential") for cred in self.credentials_list]
|
||||
)
|
||||
|
||||