fixed some UI, and temporarily did some better UX for µscan
This commit is contained in:
@@ -16,6 +16,19 @@ import queue
|
||||
import numpy as np
|
||||
import shutil
|
||||
|
||||
|
||||
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
|
||||
@@ -24,7 +37,6 @@ 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):
|
||||
self.title = title
|
||||
@@ -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):
|
||||
|
||||
@@ -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))
|
||||
|
||||
self.control_frame = ttk.Frame(self.main_frame)
|
||||
self.control_frame.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")
|
||||
title_label = ttk.Label(header_frame, text="µScan V2.3.0", style='Title.TLabel')
|
||||
title_label.pack(side="left")
|
||||
|
||||
self.info_frame = ttk.Frame(self.main_frame)
|
||||
self.info_frame.grid(row=0, column=2, padx=5, pady=5, sticky="nsew")
|
||||
status_label = ttk.Label(header_frame, text="Erweiterte Barcode-Scanner", style='Info.TLabel')
|
||||
status_label.pack(side="right")
|
||||
|
||||
self.camera_label = ttk.Label(self.camera_frame)
|
||||
self.camera_label.pack(expand=True, fill="both")
|
||||
# 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")
|
||||
|
||||
# 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)
|
||||
|
||||
# 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['values'] = [f"Kamera {i}" for i in available_devices]
|
||||
if available_devices:
|
||||
self.device_combo.current(0)
|
||||
self.device_combo.bind("<<ComboboxSelected>>", 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)
|
||||
|
||||
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()
|
||||
|
||||
def load_json(self):
|
||||
if os.path.exists(self.data_file):
|
||||
with open(self.data_file, 'r') as f:
|
||||
self.quantities = json.load(f)
|
||||
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)
|
||||
except Exception as e:
|
||||
print(f"Error in video preview: {e}")
|
||||
|
||||
self.window.after(10, self.update_preview) # ~100 FPS preview
|
||||
# Update session time
|
||||
self.update_statistics()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler in der Video-Vorschau: {e}")
|
||||
|
||||
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)
|
||||
|
||||
ttk.Label(self.product_win, text=f"Welches Produkt soll dem Barcode '{barcode_data}' zugeordnet werden?").pack(pady=5)
|
||||
# Center the window
|
||||
self.product_win.transient(self.window)
|
||||
self.product_win.grab_set()
|
||||
|
||||
# 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(
|
||||
"<Configure>",
|
||||
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
|
||||
self.save_json()
|
||||
self.product_win.destroy()
|
||||
else:
|
||||
messagebox.showwarning("Keine Auswahl", "Bitte ein Produkt auswählen.")
|
||||
product = selected_product.get()
|
||||
if product:
|
||||
# Update quantity
|
||||
self.quantities[product] = self.quantities.get(product, 0) + 1
|
||||
|
||||
ttk.Button(self.product_win, text="Bestätigen", command=confirm).pack(pady=5)
|
||||
# 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 wählen Sie ein Produkt aus.")
|
||||
|
||||
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('<Return>', lambda e: confirm())
|
||||
self.product_win.bind('<Escape>', 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():
|
||||
"""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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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}}
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
10
quantities.json
Normal file
10
quantities.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"Flaschen": 7,
|
||||
"Bierflasche": 2,
|
||||
"Kasten": 2,
|
||||
"Dose": 3,
|
||||
"Plastikflasche": 2,
|
||||
"Monster": 2,
|
||||
"Joghurt Glas": 2,
|
||||
"nigger": 1
|
||||
}
|
||||
Reference in New Issue
Block a user