diff --git a/PfandApplication/main.py b/PfandApplication/main.py index 899aca7..e172569 100644 --- a/PfandApplication/main.py +++ b/PfandApplication/main.py @@ -16,14 +16,26 @@ import queue import numpy as np import shutil -# @LocalImportStructure ; ref@todo/main.py , Element 3 -from PfandApplication.wiki import main as wiki -from PfandApplication.pfand_scanner import launch_pfand_scanner -from PfandApplication.updater import open_updater as open_updater, run_silent_update -from PfandApplication.tgtg_orderchecker import main as tgtg -from PfandApplication.tgtg_orderchecker import setupkey as tgtg_kt -from PfandApplication.todo.main import todo_instance +if __name__ == "__main__": + try: + from wiki import main as wiki + from pfand_scanner import launch_pfand_scanner + from updater import open_updater as open_updater, run_silent_update + from tgtg_orderchecker import main as tgtg + from tgtg_orderchecker import setupkey as tgtg_kt + from todo.main import todo_instance + except ImportError: + print("tried to import without package name. failed, trying to import using package name now") + pass +else: + # @LocalImportStructure ; ref@todo/main.py , Element 3 + from PfandApplication.wiki import main as wiki + from PfandApplication.pfand_scanner import launch_pfand_scanner + from PfandApplication.updater import open_updater as open_updater, run_silent_update + from PfandApplication.tgtg_orderchecker import main as tgtg + from PfandApplication.tgtg_orderchecker import setupkey as tgtg_kt + from PfandApplication.todo.main import todo_instance class Achievement: def __init__(self, title, description, condition_type, condition_value): @@ -523,7 +535,8 @@ class PfandCalculator: about_window, text="Load Todo", command=self.create_todo_list ) ) - todo_button.grid(row=2, column=3, padx=10, pady=10, sticky="ew") + + todo_button.grid(row=3, column=0, columnspan=2, padx=10, pady=10, sticky="ew") # ref@todo/main.py def create_todo_list(self): diff --git a/PfandApplication/pfand_scanner.py b/PfandApplication/pfand_scanner.py index 7b66633..ccde75b 100644 --- a/PfandApplication/pfand_scanner.py +++ b/PfandApplication/pfand_scanner.py @@ -1,4 +1,4 @@ -# µScan V2.2.2 +# µScan V2.3.0 - Verbesserte Deutsche Version import tkinter as tk from tkinter import ttk, simpledialog, messagebox import cv2 @@ -10,28 +10,39 @@ import queue import json import os import time +import numpy as np class PfandScanner: def __init__(self, window, window_title): self.window = window self.window.title(window_title) - self.window.geometry("1920x1080") - self.window.minsize(960, 540) + self.window.geometry("1600x900") + self.window.minsize(1200, 700) + self.window.configure(bg='#f0f0f0') + + # Configure main window grid self.window.columnconfigure(0, weight=1) self.window.rowconfigure(0, weight=1) + # Style configuration + self.setup_styles() + self.data_file = "quantities.json" - self.load_json() + self.products_file = "products.json" + self.load_data() self.barcode_times = {} self.prompted_barcodes = set() + self.current_barcodes = [] # Store current frame's barcodes for outlining self.selected_device_index = tk.IntVar(value=0) self.last_process_time = time.time() - # FPS Einstellung ist hier! - # FPS Setting is here! - self.process_interval = 0.30 # 30 FPS + # FPS Setting + self.process_interval = 0.15 # Improved processing speed + + # Collapsible scan list state + self.scan_list_collapsed = tk.BooleanVar(value=False) self.init_gui() self.init_camera() @@ -47,44 +58,275 @@ class PfandScanner: self.window.protocol("WM_DELETE_WINDOW", self.on_closing) self.process_queue() + def setup_styles(self): + """Setup custom styles for a modern look""" + self.style = ttk.Style() + self.style.theme_use('clam') + + # Configure colors + self.colors = { + 'primary': '#2c3e50', + 'secondary': '#3498db', + 'success': '#27ae60', + 'warning': '#f39c12', + 'danger': '#e74c3c', + 'light': '#ecf0f1', + 'dark': '#34495e' + } + + # Configure custom styles + self.style.configure('Title.TLabel', font=('Segoe UI', 16, 'bold'), foreground=self.colors['primary']) + self.style.configure('Heading.TLabel', font=('Segoe UI', 12, 'bold'), foreground=self.colors['dark']) + self.style.configure('Info.TLabel', font=('Segoe UI', 10), foreground=self.colors['dark']) + self.style.configure('Success.TLabel', font=('Segoe UI', 10, 'bold'), foreground=self.colors['success']) + + self.style.configure('Custom.TLabelFrame', relief='solid', borderwidth=1) + self.style.configure('Camera.TFrame', relief='solid', borderwidth=2) + + def load_data(self): + """Load products from products.json and quantities from quantities.json""" + # Load products + if os.path.exists(self.products_file): + try: + with open(self.products_file, 'r', encoding='utf-8') as f: + products_data = json.load(f) + self.products = products_data.get("products", []) + self.prices = products_data.get("prices", {}) + except Exception as e: + print(f"Fehler beim Laden der Produkte: {e}") + self.products = [] + self.prices = {} + else: + self.products = [] + self.prices = {} + + # Load quantities + if os.path.exists(self.data_file): + try: + with open(self.data_file, 'r', encoding='utf-8') as f: + self.quantities = json.load(f) + except Exception as e: + print(f"Fehler beim Laden der Mengen: {e}") + self.quantities = {} + else: + self.quantities = {} + + def save_json(self): + """Save quantities to quantities.json""" + try: + with open(self.data_file, 'w', encoding='utf-8') as f: + json.dump(self.quantities, f, indent=4, ensure_ascii=False) + except Exception as e: + print(f"Fehler beim Speichern der Mengen: {e}") + def init_gui(self): - self.main_frame = ttk.Frame(self.window) + # Main container + self.main_frame = ttk.Frame(self.window, padding="10") self.main_frame.grid(sticky="nsew") - self.main_frame.columnconfigure(0, weight=3) - self.main_frame.columnconfigure(1, weight=1) - self.main_frame.columnconfigure(2, weight=3) - self.main_frame.rowconfigure(0, weight=1) + self.main_frame.columnconfigure(1, weight=2) # Camera gets more space + self.main_frame.columnconfigure(0, weight=1) # Controls + self.main_frame.columnconfigure(2, weight=1) # Info + self.main_frame.rowconfigure(1, weight=1) - self.camera_frame = ttk.Frame(self.main_frame) - self.camera_frame.grid(row=0, column=0, padx=5, pady=5, sticky="nsew") + # Header + header_frame = ttk.Frame(self.main_frame) + header_frame.grid(row=0, column=0, columnspan=3, sticky="ew", pady=(0, 10)) + + title_label = ttk.Label(header_frame, text="µScan V2.3.0", style='Title.TLabel') + title_label.pack(side="left") + + status_label = ttk.Label(header_frame, text="Erweiterte Barcode-Scanner", style='Info.TLabel') + status_label.pack(side="right") - self.control_frame = ttk.Frame(self.main_frame) - self.control_frame.grid(row=0, column=1, padx=5, pady=5, sticky="nsew") + # Control Panel (Left) + self.control_frame = ttk.LabelFrame(self.main_frame, text="Steuerung", padding="10") + self.control_frame.grid(row=1, column=0, padx=(0, 10), sticky="nsew") - self.info_frame = ttk.Frame(self.main_frame) - self.info_frame.grid(row=0, column=2, padx=5, pady=5, sticky="nsew") + # Camera View (Center) + self.camera_frame = ttk.LabelFrame(self.main_frame, text="Kameraansicht", padding="5") + self.camera_frame.grid(row=1, column=1, padx=5, sticky="nsew") + self.camera_frame.columnconfigure(0, weight=1) + self.camera_frame.rowconfigure(0, weight=1) - self.camera_label = ttk.Label(self.camera_frame) - self.camera_label.pack(expand=True, fill="both") + # Info Panel (Right) - now collapsible + self.info_frame = ttk.LabelFrame(self.main_frame, text="Scan-Ergebnisse", padding="10") + self.info_frame.grid(row=1, column=2, padx=(10, 0), sticky="nsew") + + # Camera display + camera_container = ttk.Frame(self.camera_frame, relief='solid', borderwidth=2) + camera_container.grid(sticky="nsew", padx=5, pady=5) + camera_container.columnconfigure(0, weight=1) + camera_container.rowconfigure(0, weight=1) + + self.camera_label = ttk.Label(camera_container, text="Kamera wird geladen...", anchor="center") + self.camera_label.grid(sticky="nsew") self.init_device_selector() self.init_controls() self.init_treeview() + self.init_statistics() def init_device_selector(self): - device_frame = ttk.LabelFrame(self.control_frame, text="Video Device") - device_frame.pack(pady=5, padx=5, fill="x") + device_frame = ttk.LabelFrame(self.control_frame, text="Kamera-Einstellungen", padding="10") + device_frame.pack(fill="x", pady=(0, 10)) - ttk.Label(device_frame, text="Choose Camera:").pack(anchor="w", padx=5) + ttk.Label(device_frame, text="Kamera auswählen:", style='Heading.TLabel').pack(anchor="w") self.device_combo = ttk.Combobox(device_frame, state="readonly") - self.device_combo.pack(fill="x", padx=5) + self.device_combo.pack(fill="x", pady=(5, 10)) available_devices = self.list_video_devices() - self.device_combo['values'] = [f"Camera {i}" for i in available_devices] - self.device_combo.current(0) + self.device_combo['values'] = [f"Kamera {i}" for i in available_devices] + if available_devices: + self.device_combo.current(0) self.device_combo.bind("<>", self.change_camera) + # Camera status + self.camera_status = ttk.Label(device_frame, text="Status: Initialisierung...", style='Info.TLabel') + self.camera_status.pack(anchor="w") + + def init_controls(self): + # Focus controls + focus_frame = ttk.LabelFrame(self.control_frame, text="Fokus-Steuerung", padding="10") + focus_frame.pack(fill="x", pady=(0, 10)) + + self.autofocus_var = tk.BooleanVar(value=True) + self.autofocus_check = ttk.Checkbutton( + focus_frame, text="Auto-Fokus", variable=self.autofocus_var, command=self.toggle_autofocus) + self.autofocus_check.pack(anchor="w", pady=(0, 5)) + + ttk.Label(focus_frame, text="Manueller Fokus:").pack(anchor="w") + self.focus_slider = ttk.Scale(focus_frame, from_=0, to=255, orient="horizontal") + self.focus_slider.set(0) + self.focus_slider.pack(fill="x", pady=(0, 5)) + + # Image processing controls + process_frame = ttk.LabelFrame(self.control_frame, text="Bildverbesserung", padding="10") + process_frame.pack(fill="x", pady=(0, 10)) + + ttk.Label(process_frame, text="Helligkeit:").pack(anchor="w") + self.brightness_slider = ttk.Scale(process_frame, from_=0, to=100, orient="horizontal") + self.brightness_slider.set(50) + self.brightness_slider.pack(fill="x", pady=(0, 5)) + + ttk.Label(process_frame, text="Kontrast:").pack(anchor="w") + self.contrast_slider = ttk.Scale(process_frame, from_=0, to=100, orient="horizontal") + self.contrast_slider.set(50) + self.contrast_slider.pack(fill="x", pady=(0, 10)) + + # Scan options + scan_frame = ttk.LabelFrame(self.control_frame, text="Scan-Optionen", padding="10") + scan_frame.pack(fill="x") + + self.outline_var = tk.BooleanVar(value=True) + ttk.Checkbutton(scan_frame, text="Barcodes umranden", variable=self.outline_var).pack(anchor="w") + + self.beep_var = tk.BooleanVar(value=True) + ttk.Checkbutton(scan_frame, text="Ton bei Scan", variable=self.beep_var).pack(anchor="w") + + def init_statistics(self): + """Initialize statistics panel""" + stats_frame = ttk.LabelFrame(self.info_frame, text="Statistiken", padding="10") + stats_frame.pack(fill="x", pady=(0, 10)) + + self.total_scans_label = ttk.Label(stats_frame, text="Gesamte Scans: 0", style='Info.TLabel') + self.total_scans_label.pack(anchor="w") + + self.total_value_label = ttk.Label(stats_frame, text="Gesamtwert: €0,00", style='Success.TLabel') + self.total_value_label.pack(anchor="w") + + self.session_time_label = ttk.Label(stats_frame, text="Sitzungsdauer: 00:00", style='Info.TLabel') + self.session_time_label.pack(anchor="w") + + self.session_start = datetime.now() + self.total_scans = 0 + self.total_value = 0.0 + + def init_treeview(self): + # Header frame with collapse button + tree_header_frame = ttk.Frame(self.info_frame) + tree_header_frame.pack(fill="x", pady=(0, 5)) + + # Collapse button + self.collapse_button = ttk.Button( + tree_header_frame, + text="▼ Scan-Liste", + command=self.toggle_scan_list, + width=15 + ) + self.collapse_button.pack(side="left") + + # Clear button + clear_button = ttk.Button( + tree_header_frame, + text="Liste löschen", + command=self.clear_scan_list + ) + clear_button.pack(side="right") + + # Treeview frame (collapsible) + self.tree_container = ttk.Frame(self.info_frame) + self.tree_container.pack(fill="both", expand=True) + + self.tree = ttk.Treeview( + self.tree_container, + columns=("Zeit", "Barcode", "Typ", "Pfand"), + show="headings", + height=15 + ) + + # Configure columns + self.tree.heading("Zeit", text="Zeit") + self.tree.heading("Barcode", text="Barcode") + self.tree.heading("Typ", text="Typ") + self.tree.heading("Pfand", text="Pfand") + + self.tree.column("Zeit", width=100, minwidth=80) + self.tree.column("Barcode", width=120, minwidth=100) + self.tree.column("Typ", width=80, minwidth=60) + self.tree.column("Pfand", width=70, minwidth=50) + + # Scrollbars + v_scrollbar = ttk.Scrollbar(self.tree_container, orient="vertical", command=self.tree.yview) + h_scrollbar = ttk.Scrollbar(self.tree_container, orient="horizontal", command=self.tree.xview) + + self.tree.configure(yscrollcommand=v_scrollbar.set, xscrollcommand=h_scrollbar.set) + + # Grid layout + self.tree.grid(row=0, column=0, sticky="nsew") + v_scrollbar.grid(row=0, column=1, sticky="ns") + h_scrollbar.grid(row=1, column=0, sticky="ew") + + self.tree_container.columnconfigure(0, weight=1) + self.tree_container.rowconfigure(0, weight=1) + + # Alternate row colors + self.tree.tag_configure('oddrow', background='#f9f9f9') + self.tree.tag_configure('evenrow', background='white') + + def toggle_scan_list(self): + """Toggle the visibility of the scan list""" + if self.scan_list_collapsed.get(): + # Expand + self.tree_container.pack(fill="both", expand=True) + self.collapse_button.configure(text="▼ Scan-Liste") + self.scan_list_collapsed.set(False) + # Resize window columns + self.main_frame.columnconfigure(2, weight=1) + else: + # Collapse + self.tree_container.pack_forget() + self.collapse_button.configure(text="▶ Scan-Liste") + self.scan_list_collapsed.set(True) + # Make info panel smaller + self.main_frame.columnconfigure(2, weight=0, minsize=200) + + def clear_scan_list(self): + """Clear all items from the scan list""" + if messagebox.askyesno("Liste löschen", "Möchten Sie wirklich alle Einträge aus der Scan-Liste löschen?"): + for item in self.tree.get_children(): + self.tree.delete(item) + def list_video_devices(self, max_devices=10): available = [] for i in range(max_devices): @@ -99,65 +341,21 @@ class PfandScanner: self.selected_device_index.set(index) self.init_camera() - def init_controls(self): - focus_frame = ttk.LabelFrame(self.control_frame, text="Camera Controls") - focus_frame.pack(pady=5, padx=5, fill="x") - - ttk.Label(focus_frame, text="Focus:").pack(pady=2) - self.focus_slider = ttk.Scale(focus_frame, from_=0, to=255, orient="horizontal") - self.focus_slider.set(0) - self.focus_slider.pack(pady=2, padx=5, fill="x") - - self.autofocus_var = tk.BooleanVar(value=True) - self.autofocus_check = ttk.Checkbutton( - focus_frame, text="Autofocus", variable=self.autofocus_var, command=self.toggle_autofocus) - self.autofocus_check.pack(pady=2) - - process_frame = ttk.LabelFrame(self.control_frame, text="Image Processing") - process_frame.pack(pady=5, padx=5, fill="x") - - ttk.Label(process_frame, text="Brightness:").pack(pady=2) - self.brightness_slider = ttk.Scale(process_frame, from_=0, to=100, orient="horizontal") - self.brightness_slider.set(50) - self.brightness_slider.pack(pady=2, padx=5, fill="x") - - ttk.Label(process_frame, text="Contrast:").pack(pady=2) - self.contrast_slider = ttk.Scale(process_frame, from_=0, to=100, orient="horizontal") - self.contrast_slider.set(50) - self.contrast_slider.pack(pady=2, padx=5, fill="x") - - def init_treeview(self): - self.tree = ttk.Treeview(self.info_frame, columns=("Time", "Barcode", "Type", "Deposit"), show="headings") - for col in ["Time", "Barcode", "Type", "Deposit"]: - self.tree.heading(col, text=col) - self.tree.column("Time", width=150) - self.tree.column("Barcode", width=200) - self.tree.column("Type", width=100) - self.tree.column("Deposit", width=100) - self.tree.pack(fill="both", expand=True) - - scrollbar = ttk.Scrollbar(self.info_frame, orient="vertical", command=self.tree.yview) - scrollbar.pack(side="right", fill="y") - self.tree.configure(yscrollcommand=scrollbar.set) def init_camera(self): if hasattr(self, 'cap') and self.cap and self.cap.isOpened(): self.cap.release() + device_index = self.selected_device_index.get() self.cap = cv2.VideoCapture(device_index, cv2.CAP_DSHOW if os.name == 'nt' else 0) - self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) - self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) - self.toggle_autofocus() - - def load_json(self): - if os.path.exists(self.data_file): - with open(self.data_file, 'r') as f: - self.quantities = json.load(f) + + if self.cap.isOpened(): + self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) + self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) + self.cap.set(cv2.CAP_PROP_FPS, 30) + self.toggle_autofocus() + self.camera_status.configure(text="Status: Verbunden ✓", foreground=self.colors['success']) else: - self.quantities = {} - - def save_json(self): - with open(self.data_file, 'w') as f: - json.dump(self.quantities, f, indent=4) + self.camera_status.configure(text="Status: Fehler ✗", foreground=self.colors['danger']) def toggle_autofocus(self): if self.cap: @@ -178,8 +376,53 @@ class PfandScanner: return cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2) + def draw_barcode_outline(self, frame, barcodes): + if not self.outline_var.get(): + return frame + + for barcode in barcodes: + # Get barcode polygon points + points = barcode.polygon + if len(points) == 4: + # Convert to numpy array + pts = np.array([[point.x, point.y] for point in points], np.int32) + pts = pts.reshape((-1, 1, 2)) + + # Draw colored outline based on barcode type + barcode_data = barcode.data.decode('utf-8') + if len(barcode_data) == 13: + color = (0, 255, 0) # Green for EINWEG + elif len(barcode_data) == 8: + color = (255, 0, 0) # Blue for MEHRWEG + else: + color = (0, 165, 255) # Orange for DOSE + + # Draw outline + cv2.polylines(frame, [pts], True, color, 3) + + # Add label + rect = cv2.boundingRect(pts) + label_pos = (rect[0], rect[1] - 10) + pfand_type = "EINWEG" if len(barcode_data) == 13 else "MEHRWEG" if len(barcode_data) == 8 else "DOSE" + deposit = self.pfand_values.get(pfand_type, 0.00) + label = f"{pfand_type}: €{deposit:.2f}" + + # Draw label background + (text_width, text_height), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2) + cv2.rectangle(frame, (label_pos[0], label_pos[1] - text_height - 5), + (label_pos[0] + text_width, label_pos[1] + 5), color, -1) + + # Draw label text + cv2.putText(frame, label, label_pos, cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2) + + return frame + def update_preview(self): try: + if not hasattr(self, 'cap') or not self.cap.isOpened(): + self.window.after(100, self.update_preview) + return + ret, frame = self.cap.read() if ret: if not self.autofocus_var.get(): @@ -189,50 +432,192 @@ class PfandScanner: if current_time - self.last_process_time >= self.process_interval: processed_frame = self.adjust_image(frame) barcodes = decode(processed_frame) or decode(frame) + self.current_barcodes = barcodes + for barcode in barcodes: barcode_data = barcode.data.decode('utf-8') self.queue.put(barcode_data) self.last_process_time = current_time - cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA) + # Draw barcode outlines + frame_with_outlines = self.draw_barcode_outline(frame.copy(), self.current_barcodes) + + # Convert and display + cv2image = cv2.cvtColor(frame_with_outlines, cv2.COLOR_BGR2RGBA) img = Image.fromarray(cv2image) + + # Resize to fit label while maintaining aspect ratio + label_width = self.camera_label.winfo_width() + label_height = self.camera_label.winfo_height() + if label_width > 1 and label_height > 1: + img.thumbnail((label_width, label_height), Image.Resampling.LANCZOS) + imgtk = ImageTk.PhotoImage(image=img) self.camera_label.imgtk = imgtk self.camera_label.configure(image=imgtk) + + # Update session time + self.update_statistics() + except Exception as e: - print(f"Error in video preview: {e}") + print(f"Fehler in der Video-Vorschau: {e}") - self.window.after(10, self.update_preview) # ~100 FPS preview + self.window.after(10, self.update_preview) + + def update_statistics(self): + session_time = datetime.now() - self.session_start + hours, remainder = divmod(int(session_time.total_seconds()), 3600) + minutes, _ = divmod(remainder, 60) + time_str = f"{hours:02d}:{minutes:02d}" + + self.session_time_label.configure(text=f"Sitzungsdauer: {time_str}") + self.total_scans_label.configure(text=f"Gesamte Scans: {self.total_scans}") + self.total_value_label.configure(text=f"Gesamtwert: €{self.total_value:.2f}") def show_product_selection(self, barcode_data): if hasattr(self, 'product_win') and self.product_win.winfo_exists(): return self.product_win = tk.Toplevel(self.window) - self.product_win.title("Produktwahl") + self.product_win.title("Produkt auswählen") + self.product_win.geometry("500x400") + self.product_win.resizable(True, True) + + # Center the window + self.product_win.transient(self.window) + self.product_win.grab_set() - ttk.Label(self.product_win, text=f"Welches Produkt soll dem Barcode '{barcode_data}' zugeordnet werden?").pack(pady=5) + # Configure grid weights + self.product_win.columnconfigure(0, weight=1) + self.product_win.rowconfigure(1, weight=1) + + # Header frame + header_frame = ttk.Frame(self.product_win, padding="20") + header_frame.grid(row=0, column=0, sticky="ew") + + ttk.Label(header_frame, text="Produkt für Barcode auswählen:", style='Heading.TLabel').pack(pady=(0, 5)) + ttk.Label(header_frame, text=f"'{barcode_data}'", style='Info.TLabel', font=('Courier', 10)).pack(pady=(0, 10)) + + # Main content frame + content_frame = ttk.Frame(self.product_win, padding="20") + content_frame.grid(row=1, column=0, sticky="nsew") + content_frame.columnconfigure(0, weight=1) + content_frame.rowconfigure(0, weight=1) selected_product = tk.StringVar() - for prod in self.quantities: - ttk.Radiobutton(self.product_win, text=prod, variable=selected_product, value=prod).pack(anchor='w') + + if self.products: + # Create scrollable frame for products + canvas = tk.Canvas(content_frame, bg='white') + scrollbar = ttk.Scrollbar(content_frame, orient="vertical", command=canvas.yview) + scrollable_frame = ttk.Frame(canvas) + + scrollable_frame.bind( + "", + lambda e: canvas.configure(scrollregion=canvas.bbox("all")) + ) + + canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") + canvas.configure(yscrollcommand=scrollbar.set) + + for i, product in enumerate(self.products): + current_quantity = self.quantities.get(product, 0) + price = self.prices.get(product, 0.00) + + product_frame = ttk.Frame(scrollable_frame, padding="5") + product_frame.pack(fill="x", pady=2) + + ttk.Radiobutton( + product_frame, + text=f"{product}", + variable=selected_product, + value=product + ).pack(side="left") + + info_text = f"(Aktuell: {current_quantity}, Preis: €{price:.2f})" + ttk.Label(product_frame, text=info_text, style='Info.TLabel').pack(side="right") + + canvas.grid(row=0, column=0, sticky="nsew") + scrollbar.grid(row=0, column=1, sticky="ns") + else: + ttk.Label(content_frame, text="Noch keine Produkte definiert.", style='Info.TLabel').pack(pady=20) + + # Button frame + button_frame = ttk.Frame(self.product_win, padding="20") + button_frame.grid(row=2, column=0, sticky="ew") + + # Configure button frame columns + button_frame.columnconfigure(0, weight=1) + button_frame.columnconfigure(1, weight=0) + button_frame.columnconfigure(2, weight=0) def confirm(): - prod = selected_product.get() - if prod: - self.quantities[prod] = self.quantities.get(prod, 0) + 1 + product = selected_product.get() + if product: + # Update quantity + self.quantities[product] = self.quantities.get(product, 0) + 1 + + # Update total value with actual product price + product_price = self.prices.get(product, 0.00) + self.total_value += product_price + self.save_json() + self.update_statistics() self.product_win.destroy() + + # Show confirmation message + messagebox.showinfo("Erfolgreich", f"Produkt '{product}' wurde hinzugefügt!") else: - messagebox.showwarning("Keine Auswahl", "Bitte ein Produkt auswählen.") + messagebox.showwarning("Keine Auswahl", "Bitte wählen Sie ein Produkt aus.") - ttk.Button(self.product_win, text="Bestätigen", command=confirm).pack(pady=5) + def cancel(): + self.product_win.destroy() + + # Add new product button + def add_new_product(): + new_product = simpledialog.askstring("Neues Produkt", "Name des neuen Produkts:") + if new_product and new_product.strip(): + new_product = new_product.strip() + if new_product not in self.products: + # Ask for price + price_str = simpledialog.askstring("Preis", f"Preis für '{new_product}' (€):") + try: + price = float(price_str.replace(',', '.')) if price_str else 0.00 + except: + price = 0.00 + + self.products.append(new_product) + self.prices[new_product] = price + + # Save products + try: + products_data = {"products": self.products, "prices": self.prices} + with open(self.products_file, 'w', encoding='utf-8') as f: + json.dump(products_data, f, indent=4, ensure_ascii=False) + except Exception as e: + print(f"Fehler beim Speichern der Produkte: {e}") + + # Refresh the dialog + self.product_win.destroy() + self.window.after(0, self.show_product_selection, barcode_data) + else: + messagebox.showwarning("Produkt existiert", "Dieses Produkt ist bereits vorhanden.") + + # Buttons with better layout + ttk.Button(button_frame, text="Neues Produkt", command=add_new_product).grid(row=0, column=0, sticky="w", padx=5) + ttk.Button(button_frame, text="Abbrechen", command=cancel).grid(row=0, column=1, padx=5) + ttk.Button(button_frame, text="Bestätigen", command=confirm).grid(row=0, column=2, padx=5) + + # Bind Enter key to confirm + self.product_win.bind('', lambda e: confirm()) + self.product_win.bind('', lambda e: cancel()) def process_queue(self): try: barcode_data = self.queue.get(timeout=0.1) now = datetime.now() + # Rate limiting timestamps = self.barcode_times.get(barcode_data, []) timestamps = [t for t in timestamps if now - t <= timedelta(seconds=5)] if len(timestamps) >= 3: @@ -240,12 +625,25 @@ class PfandScanner: timestamps.append(now) self.barcode_times[barcode_data] = timestamps - current_time = now.strftime("%Y-%m-%d %H:%M:%S") + current_time = now.strftime("%H:%M:%S") pfand_type = "EINWEG" if len(barcode_data) == 13 else "MEHRWEG" if len(barcode_data) == 8 else "DOSE" deposit = self.pfand_values.get(pfand_type, 0.00) - self.tree.insert("", 0, values=(current_time, barcode_data, pfand_type, f"{deposit:.2f}")) + # Determine row color + row_count = len(self.tree.get_children()) + tag = 'evenrow' if row_count % 2 == 0 else 'oddrow' + self.tree.insert("", 0, values=(current_time, barcode_data, pfand_type, f"€{deposit:.2f}"), tags=(tag,)) + + # Update statistics + self.total_scans += 1 + # Note: Total value is updated in product selection dialog + + # Sound notification + if self.beep_var.get(): + self.window.bell() + + # Show product selection dialog if barcode_data not in self.prompted_barcodes: self.prompted_barcodes.add(barcode_data) self.window.after(0, self.show_product_selection, barcode_data) @@ -253,16 +651,32 @@ class PfandScanner: except queue.Empty: pass except Exception as e: - print(f"Error in queue processing: {e}") + print(f"Fehler in der Warteschlangenverarbeitung: {e}") finally: self.window.after(100, self.process_queue) def on_closing(self): - if self.cap and self.cap.isOpened(): - self.cap.release() + """Clean shutdown of the application""" + self.running = False + + # Wait for camera thread to finish + if self.camera_thread and self.camera_thread.is_alive(): + self.camera_thread.join(timeout=1.0) + + try: + if hasattr(self, 'cap') and self.cap and self.cap.isOpened(): + self.cap.release() + cv2.destroyAllWindows() + except: + pass self.window.destroy() if __name__ != "__main__": def launch_pfand_scanner(): scanner_window = tk.Toplevel() - PfandScanner(scanner_window, "µScan V2.2.2") + PfandScanner(scanner_window, "µScan V2.3.0 - Verbesserte Deutsche Version") +else: + # For standalone testing + root = tk.Tk() + app = PfandScanner(root, "µScan V2.3.0 - Verbesserte Deutsche Version") + root.mainloop() \ No newline at end of file diff --git a/PfandApplication/updater.py b/PfandApplication/updater.py index a1b0817..d878bac 100644 --- a/PfandApplication/updater.py +++ b/PfandApplication/updater.py @@ -10,7 +10,7 @@ import tempfile import traceback import threading -GITHUB_REPO_ZIP = "https://github.com/ZockerKatze/pfand_PKG/archive/refs/heads/main.zip" +GITHUB_REPO_ZIP = "https://rattatwinko.servecounterstrike.com/gitea/rattatwinko/pfand_PKG/archive/main.zip" IGNORED_FILES = {"key.py"} class GitHubUpdater(tk.Toplevel): @@ -49,7 +49,7 @@ class GitHubUpdater(tk.Toplevel): header = ttk.Label(self, text="Pfand Updater", style="Header.TLabel") header.pack(pady=(20, 5)) - self.status_label = ttk.Label(self, text="🔍 Suche nach Updates...", style="Status.TLabel") + self.status_label = ttk.Label(self, text="Suche nach Updates …", style="Status.TLabel") self.status_label.pack(pady=(0, 10)) self.frame = ttk.Frame(self) @@ -73,14 +73,14 @@ class GitHubUpdater(tk.Toplevel): button_frame = ttk.Frame(self) button_frame.pack(pady=15) - self.back_button = ttk.Button(button_frame, text="⬅️ Zurück", command=self.show_root_view) + self.back_button = ttk.Button(button_frame, text="Zurück", command=self.show_root_view) self.back_button.pack(side="left", padx=10) self.back_button.pack_forget() - self.update_button = ttk.Button(button_frame, text="⬆️ Dateien aktualisieren", command=self.perform_update, state='disabled') + self.update_button = ttk.Button(button_frame, text="Dateien aktualisieren", command=self.perform_update, state='disabled') self.update_button.pack(side="left", padx=10) - self.toggle_debug_btn = ttk.Button(self, text="🐞 Fehlerdetails anzeigen", command=self.toggle_debug_output) + self.toggle_debug_btn = ttk.Button(self, text="Fehlerdetails anzeigen", command=self.toggle_debug_output) self.toggle_debug_btn.pack() self.toggle_debug_btn.pack_forget() @@ -93,10 +93,10 @@ class GitHubUpdater(tk.Toplevel): self.debug_visible = not self.debug_visible if self.debug_visible: self.debug_output.pack() - self.toggle_debug_btn.config(text="🔽 Fehlerdetails verbergen") + self.toggle_debug_btn.config(text="Fehlerdetails verbergen") else: self.debug_output.pack_forget() - self.toggle_debug_btn.config(text="🐞 Fehlerdetails anzeigen") + self.toggle_debug_btn.config(text="Fehlerdetails anzeigen") def show_root_view(self): self.current_view = "root" @@ -125,7 +125,7 @@ class GitHubUpdater(tk.Toplevel): def check_for_updates(self): try: - self.status_label.config(text="⬇️ Lade Update herunter...", foreground="#ffb300") + self.status_label.config(text="Lade Update herunter...", foreground="#ffb300") self.update_idletasks() response = requests.get(GITHUB_REPO_ZIP) @@ -137,13 +137,13 @@ class GitHubUpdater(tk.Toplevel): if self.file_differences: self.structure = self.build_structure(self.file_differences) - self.status_label.config(text="⚠️ Updates verfügbar", foreground="#e53935") + self.status_label.config(text="Updates verfügbar", foreground="#e53935") self.display_structure(self.structure) self.update_button.config(state='normal') else: - self.status_label.config(text="✅ Alles ist aktuell", foreground="#43a047") + self.status_label.config(text="Alles ist aktuell", foreground="#43a047") except Exception: - self.status_label.config(text="❌ Fehler beim Laden", foreground="#e53935") + self.status_label.config(text="Fehler beim Laden", foreground="#e53935") self.toggle_debug_btn.pack() self.debug_output.insert("1.0", traceback.format_exc()) @@ -183,7 +183,7 @@ class GitHubUpdater(tk.Toplevel): def perform_update(self): self.update_button.config(state='disabled') - self.status_label.config(text="🚧 Update läuft...", foreground="#fb8c00") + self.status_label.config(text="Update läuft…", foreground="#fb8c00") self.update_idletasks() try: @@ -199,10 +199,10 @@ class GitHubUpdater(tk.Toplevel): os.makedirs(os.path.dirname(dest_path), exist_ok=True) shutil.copy2(src_path, dest_path) - messagebox.showinfo("✅ Aktualisiert", "Dateien wurden erfolgreich aktualisiert.") + messagebox.showinfo("Aktualisiert", "Dateien wurden erfolgreich aktualisiert.") self.destroy() except Exception as e: - messagebox.showerror("❌ Fehler", str(e)) + messagebox.showerror("Fehler", str(e)) self.toggle_debug_btn.pack() self.debug_output.insert("1.0", traceback.format_exc()) @@ -238,7 +238,7 @@ def run_silent_update(master=None): file_differences.append(rel_path) if file_differences: - result = messagebox.askyesno("🔄 Update verfügbar", "Es sind Updates verfügbar. Möchten Sie aktualisieren?") + result = messagebox.askyesno("Update verfügbar", "Es sind Updates verfügbar. Möchten Sie aktualisieren?") if result: updater = GitHubUpdater(master) updater.grab_set() diff --git a/products.json b/products.json index 630f483..410df66 100644 --- a/products.json +++ b/products.json @@ -1 +1,22 @@ -{"products": ["Flaschen", "Bierflasche", "Kasten", "Dose", "Plastikflasche", "Monster", "Joghurt Glas"], "prices": {"Flaschen": 0.25, "Bierflasche": 0.2, "Kasten": 3.0, "Dose": 0.25, "Plastikflasche": 0.25, "Monster": 0.25, "Joghurt Glas": 0.17}} \ No newline at end of file +{ + "products": [ + "Flaschen", + "Bierflasche", + "Kasten", + "Dose", + "Plastikflasche", + "Monster", + "Joghurt Glas", + "nigger" + ], + "prices": { + "Flaschen": 0.25, + "Bierflasche": 0.2, + "Kasten": 3.0, + "Dose": 0.25, + "Plastikflasche": 0.25, + "Monster": 0.25, + "Joghurt Glas": 0.17, + "nigger": 200.0 + } +} \ No newline at end of file diff --git a/quantities.json b/quantities.json new file mode 100644 index 0000000..54e6832 --- /dev/null +++ b/quantities.json @@ -0,0 +1,10 @@ +{ + "Flaschen": 7, + "Bierflasche": 2, + "Kasten": 2, + "Dose": 3, + "Plastikflasche": 2, + "Monster": 2, + "Joghurt Glas": 2, + "nigger": 1 +} \ No newline at end of file