- SYSTEM: You are a Python programmer tasked with refactoring code. You are proficient in Python 3.12 and use modern features where appropriate. Your code is clear, easy to read, and free of unnecessary verbosity. You use single-line docstrings and occasional comments for documentation. Often, the code you receive is just one part of a larger program. USER:
class App(tk.Tk):
    def __init__(self) -> None:
        super().__init__()
        self.setup_window()
        self.create_variables()
        self.create_widgets()
        self.setup_logging()
        self.process_start_time: float = 0.0

    def setup_window(self) -> None:
        self.set_dpi_awareness()
        self.title("Image Processor")
        self.geometry("2100x1180")
        self.default_font = ("Roboto Flex", 16)
        self.apply_default_font()

    def set_dpi_awareness(self) -> None:
        try:
            ctypes.windll.shcore.SetProcessDpiAwareness(1)
        except Exception as e:
            print(f"Could not set DPI awareness: {e}")

    def apply_default_font(self) -> None:
        self.option_add("*Font", self.default_font)
        self.style = ttk.Style()
        for widget_type in ["TButton", "TLabel", "TEntry", "TRadiobutton", "TNotebook.Tab"]:
            self.style.configure(widget_type, font=self.default_font)
        self.style.configure("TFrame", background="#fdfdfd")
        self.style.configure("TLabel", background="#fdfdfd")
        self.style.configure("TNotebook", background="#f8f8f8")
        self.style.configure("TRadiobutton", background="#fdfdfd")

    def create_variables(self) -> None:
        self.RESIZE_MODE = tk.StringVar(value=Config.DEFAULT_RESIZE_MODE)
        self.RESIZE_WIDTH = tk.StringVar(value=str(Config.DEFAULT_RESIZE_WIDTH))
        self.RESIZE_HEIGHT = tk.StringVar(value=str(Config.DEFAULT_RESIZE_HEIGHT))
        self.RESIZE_SIDE = tk.StringVar(value=str(Config.DEFAULT_RESIZE_SIDE))
        self.RESIZE_FORMAT = tk.StringVar(value=Config.DEFAULT_RESIZE_FORMAT)
        self.option_var = tk.StringVar(value="4")
        self.status_text = tk.StringVar(value="")

    def create_widgets(self) -> None:
        main_frame = ttk.Frame(self, padding="20")
        main_frame.pack(fill=tk.BOTH, expand=True)

        self.create_folder_selection_frames(main_frame)
        self.create_resize_options_frame(main_frame)
        self.create_options_and_start_frame(main_frame)
        self.create_text_frame(main_frame)
        self.create_progress_bar(main_frame)
        self.create_status_label(main_frame)

    def create_folder_selection_frames(self, parent: ttk.Frame) -> None:
        self.create_folder_frame(parent, "Input", Config.INPUT_FOLDER, self.set_default_input, self.select_input_folder)
        self.create_folder_frame(parent, "Output", Config.OUTPUT_FOLDER, self.set_default_output, self.select_output_folder)

    def create_folder_frame(self, parent: ttk.Frame, label: str, default_folder: Path, default_command: Callable[[], None], select_command: Callable[[], None]) -> None:
        frame = ttk.Frame(parent)
        frame.pack(fill=tk.X, pady=(0, 10))

        ttk.Button(frame, text="Default", command=default_command, width=10).pack(side=tk.LEFT, padx=(0, 10))

        entry = ttk.Entry(frame)
        entry.insert(0, str(default_folder))
        entry.pack(side=tk.LEFT, expand=True, fill=tk.X)

        ttk.Button(frame, text=f"Select {label} Folder", command=select_command, width=20).pack(side=tk.LEFT, padx=(10, 0))

        setattr(self, f"{label.lower()}_entry", entry)

    def create_resize_options_frame(self, parent: ttk.Frame) -> None:
        resize_frame = ttk.Frame(parent)
        resize_frame.pack(fill=tk.X, pady=(0, 10))

        ttk.Button(resize_frame, text="Default", command=self.reset_resize_options, width=10).pack(side=tk.LEFT, padx=(0, 10))

        self.create_resize_options(resize_frame)

        self.start_button = ttk.Button(resize_frame, text="Start", command=self.start_processing, width=20)
        self.start_button.pack(side=tk.RIGHT, padx=(10, 0))

    def create_resize_options(self, frame: ttk.Frame) -> None:
        ttk.Label(frame, text="Format:").pack(side=tk.LEFT, padx=(0, 5))
        ttk.Combobox(frame, textvariable=self.RESIZE_FORMAT, values=["png", "jpg"], state="readonly", width=4).pack(side=tk.LEFT, padx=(0, 10))

        ttk.Label(frame, text="Resize:").pack(side=tk.LEFT, padx=(0, 5))
        mode_combo = ttk.Combobox(frame, textvariable=self.RESIZE_MODE, values=["Shortest Side", "Fit"], state="readonly", width=12)
        mode_combo.pack(side=tk.LEFT, padx=(0, 10))
        mode_combo.bind("<<ComboboxSelected>>", self.update_resize_options)

        self.width_label = ttk.Label(frame, text="Width:")
        self.width_entry = ttk.Entry(frame, textvariable=self.RESIZE_WIDTH, width=5)
        self.height_label = ttk.Label(frame, text="Height:")
        self.height_entry = ttk.Entry(frame, textvariable=self.RESIZE_HEIGHT, width=5)
        self.maintain_aspect_ratio = tk.BooleanVar(value=False)
        self.maintain_aspect_ratio_label = ttk.Label(frame, text="Maintain Aspect Ratio", font=self.default_font)
        self.maintain_aspect_ratio_checkbox = ttk.Checkbutton(frame, variable=self.maintain_aspect_ratio)
        self.side_label = ttk.Label(frame, text="Side:")
        self.side_entry = ttk.Entry(frame, textvariable=self.RESIZE_SIDE, width=5)

        self.update_resize_options()

    def create_options_and_start_frame(self, parent: ttk.Frame) -> None:
        frame = ttk.Frame(parent)
        frame.pack(fill=tk.X, pady=(0, 10))

        self.create_options_frame(frame)
        self.create_stop_button(frame)

    def create_options_frame(self, parent: ttk.Frame) -> None:
        options_frame = ttk.Frame(parent)
        options_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)

        options = [
            ("Check For Corrupted Files", "4"),
            ("Analyze Images", "2"),
            ("Find Color Images", "3"),
            ("Resize Images", "1"),
            ("Automatic Color Correction", "5"),
            ("Automatic Color Correction + Resize", "6")
        ]

        for col, (text, value) in enumerate(options):
            ttk.Radiobutton(options_frame, text=text, variable=self.option_var, value=value).grid(row=0, column=col, sticky="ew")
            options_frame.columnconfigure(col, weight=1)

    def create_stop_button(self, parent: ttk.Frame) -> None:
        self.stop_button = ttk.Button(parent, text="Stop", command=self.stop_processing, width=20, state=tk.DISABLED)
        self.stop_button.pack(side=tk.RIGHT, padx=(10, 0))

    def create_text_frame(self, parent: ttk.Frame) -> None:
        text_frame = ttk.Frame(parent)
        text_frame.pack(fill=tk.BOTH, expand=True)

        self.notebook = ttk.Notebook(text_frame)
        self.notebook.pack(fill=tk.BOTH, expand=True)

        self.text_areas: dict[str, tk.Text] = {}
        for tab in ["Log", "Black", "White", "Color"]:
            frame = ttk.Frame(self.notebook)
            self.notebook.add(frame, text=tab)
            self.text_areas[tab] = self.create_text_area(frame)

    def create_text_area(self, parent: ttk.Frame) -> tk.Text:
        frame = ttk.Frame(parent)
        frame.pack(fill=tk.BOTH, expand=True)

        text_area = tk.Text(frame, wrap=tk.NONE, bg="#ffffff", state='disabled', padx=7, pady=7, fg="#000000", font=self.default_font)
        scrollbar_y = ttk.Scrollbar(frame, command=text_area.yview)
        scrollbar_x = ttk.Scrollbar(frame, command=text_area.xview, orient='horizontal')

        scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y)
        text_area.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
        scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)

        text_area.config(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)
        return text_area

    def create_progress_bar(self, parent: ttk.Frame) -> None:
        self.progress = ttk.Progressbar(parent, mode="determinate", maximum=Config.PROGRESS_BAR_LENGTH)
        self.progress.pack(pady=(30, 0), padx=20, fill="x")
        self.progress.config(value=0)

    def create_status_label(self, parent: ttk.Frame) -> None:
        ttk.Label(parent, textvariable=self.status_text).pack(pady=(10, 15))

    def setup_logging(self) -> None:
        self.queue_thread = threading.Thread(target=self.update_text_area_from_queue, daemon=True)
        self.queue_thread.start()

    def set_default_folder(self, entry: ttk.Entry, default_folder: Path) -> Path:
        entry.delete(0, tk.END)
        entry.insert(0, str(default_folder))
        return default_folder

    def set_default_input(self) -> None:
        Config.INPUT_FOLDER = self.set_default_folder(self.input_entry, Config.DEFAULT_INPUT_FOLDER)

    def set_default_output(self) -> None:
        Config.OUTPUT_FOLDER = self.set_default_folder(self.output_entry, Config.DEFAULT_OUTPUT_FOLDER)

    def select_folder(self, entry: ttk.Entry, config_attr: str) -> None:
        selected_folder = filedialog.askdirectory()
        if selected_folder:
            path = Path(selected_folder).resolve()
            entry.delete(0, tk.END)
            entry.insert(0, str(path))
            setattr(Config, config_attr, path)

    def select_input_folder(self) -> None:
        self.select_folder(self.input_entry, 'INPUT_FOLDER')

    def select_output_folder(self) -> None:
        self.select_folder(self.output_entry, 'OUTPUT_FOLDER')

    def update_status_text(self, text: str) -> None:
        self.status_text.set(text)
        self.update_idletasks()

    def update_progress(self, value: float) -> None:
        self.progress['value'] = min(value, Config.PROGRESS_BAR_LENGTH)
        self.update_idletasks()

    def update_text_area(self, content_key: Optional[str] = None) -> None:
        if content_key is not None:
            tab_keys = [content_key]
        else:
            tab_keys = self.text_areas.keys()

        for tab_key in tab_keys:
            text_area = self.text_areas[tab_key]
            text_area.configure(state='normal')
            text_area.delete("1.0", tk.END)
            content = "\n".join(shared_state.tab_messages[tab_key])
            text_area.insert(tk.END, content)
            text_area.configure(state='disabled')

    def start_processing(self) -> None:
        shared_state.stop_event.clear()
        self.start_button.config(state=tk.DISABLED)
        self.stop_button.config(state=tk.NORMAL)
        self.process_start_time = time.time()
        if hasattr(self, 'stopped_by_user'):
            delattr(self, 'stopped_by_user')
        choice = self.option_var.get()

        actions = {
            "1": ("1. Resize Images", resize_images, Config.INPUT_FOLDER, Config.OUTPUT_FOLDER, image_processor),
            "2": ("2. Analyze Images", analyze_images, image_processor),
            "3": ("3. Find Color Images", find_color_images, image_processor),
            "4": ("4. Check For Corrupted Files", check_for_corrupted_files),
            "5": ("5. Automatic Color Correction", process_folder, Config.INPUT_FOLDER, image_processor),
            "6": ("6. Automatic Color Correction + Resize", process_folder, Config.INPUT_FOLDER, image_processor, True),
        }

        label, func, *args = actions.get(choice, ("Unknown Action", lambda: None))
        log_and_execute(choice, label, func, *args)

    def stop_processing(self) -> None:
        shared_state.stop_event.set()
        self.stopped_by_user = True
        self.stop_button.config(state=tk.DISABLED)
        self.start_button.config(state=tk.NORMAL)

    def update_resize_options(self, event: Optional[tk.Event] = None) -> None:
        mode = self.RESIZE_MODE.get()
        if mode == "Fit":
            self.side_label.pack_forget()
            self.side_entry.pack_forget()
            self.width_label.pack(side=tk.LEFT, padx=(10, 5))
            self.width_entry.pack(side=tk.LEFT, padx=(0, 10))
            self.height_label.pack(side=tk.LEFT, padx=(0, 5))
            self.height_entry.pack(side=tk.LEFT, padx=(0, 10))
            self.maintain_aspect_ratio_label.pack(side=tk.LEFT, padx=(10, 0))
            self.maintain_aspect_ratio_checkbox.pack(side=tk.LEFT, padx=(0, 10))
        else:  # Shortest Side
            self.width_label.pack_forget()
            self.width_entry.pack_forget()
            self.height_label.pack_forget()
            self.height_entry.pack_forget()
            self.maintain_aspect_ratio_checkbox.pack_forget()
            self.maintain_aspect_ratio_label.pack_forget()
            self.side_label.pack(side=tk.LEFT, padx=(10, 5))
            self.side_entry.pack(side=tk.LEFT, padx=(0, 10))

    def reset_resize_options(self) -> None:
        self.RESIZE_MODE.set(Config.DEFAULT_RESIZE_MODE)
        self.RESIZE_WIDTH.set(str(Config.DEFAULT_RESIZE_WIDTH))
        self.RESIZE_HEIGHT.set(str(Config.DEFAULT_RESIZE_HEIGHT))
        self.RESIZE_SIDE.set(str(Config.DEFAULT_RESIZE_SIDE))
        self.RESIZE_FORMAT.set(Config.DEFAULT_RESIZE_FORMAT)
        self.update_resize_options()

    def update_text_area_from_queue(self) -> None:
        while True:
            if not shared_state.log_message_queue.empty():
                try:
                    item = shared_state.log_message_queue.get(block=True, timeout=0.1)
                    self.process_log_message(item)
                except:
                    pass
            else:
                time.sleep(0.1)

    def process_log_message(self, item: str) -> None:
        update_tabs = {"UPDATE_LOG": "Log", "UPDATE_BLACK": "Black", "UPDATE_WHITE": "White", "UPDATE_COLOR": "Color"}
        if item in update_tabs:
            self.after(0, self.update_text_area, update_tabs[item])
        else:
            self.update_tab_messages(item)

    def update_tab_messages(self, item: str) -> None:
        if "Modal Value Black" in item:
            shared_state.tab_messages["Black"].append(item)
        if "Modal Value White" in item:
            shared_state.tab_messages["White"].append(item)
        if "Filename" in item:
            shared_state.tab_messages["Color"].append(item)
        shared_state.tab_messages["Log"].append(item)

        for tab in ["Log", "Black", "White", "Color"]:
            self.after(0, self.update_text_area, tab)

    def update_final_status(self, execution_time: float) -> None:
        self.update_progress(100)
        end_time = time.time()
        start_time = self.process_start_time
        processed_count = shared_state.processed_files_count
        total_files = shared_state.total_files_count

        start_time_str = time.strftime('%H:%M:%S', time.localtime(start_time))
        elapsed_minutes = int(execution_time // 60)
        elapsed_seconds = int(execution_time % 60)
        elapsed_time_str = f"{elapsed_minutes:02d}:{elapsed_seconds:02d}"

        status_text = (
            f"Start Time: {start_time_str} - "
            f"Processed Files: {processed_count}/{total_files} - "
            f"Elapsed Time: {elapsed_time_str} Minutes"
        )

        if hasattr(self, 'stopped_by_user') and self.stopped_by_user:
            status_text += " - Processing stopped by user"
            delattr(self, 'stopped_by_user')

        padded_status_text = status_text.ljust(Config.PADDING_LENGTH)
        self.update_status_text(padded_status_text)
        self.reset_gui()

    def reset_gui(self) -> None:
        self.start_button.config(state=tk.NORMAL)
        self.stop_button.config(state=tk.DISABLED)