Files
pfand_PKG/PfandApplication/main.py
2025-04-15 21:59:56 +02:00

1578 lines
70 KiB
Python

import tkinter as tk
import tkinter
import webbrowser
from tkinter import ttk, messagebox, filedialog
import json
from PIL import Image, ImageTk
import os
import subprocess
from datetime import datetime
from tkcalendar import DateEntry
import csv
import cv2
from pyzbar.pyzbar import decode
import threading
import queue
import numpy as np
import shutil
# Local Dependencies
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
class Achievement:
def __init__(self, title, description, condition_type, condition_value):
self.title = title
self.description = description
self.condition_type = condition_type
self.condition_value = condition_value
self.unlocked = False
self.unlock_date = None
class PfandCalculator:
def __init__(self, root):
self.root = root
self.root.title("Österreichischer Pfandrechner")
# Load products and prices from JSON
self.load_products()
self.quantities = {}
self.images = {}
self.spinboxes = {} # Store spinbox references
self.deposit_history = self.load_deposit_history()
self.scanned_barcodes = set()
self.barcode_history = [] # Store barcode scan history
self.achievements = self.initialize_achievements()
self.load_achievements()
if not os.path.exists('PfandApplication/images'):
os.makedirs('images')
self.create_menu()
self.load_quantities()
self.create_widgets()
# Scanner window
self.scanner_window = None
self.cap = None
self.scanning = False
self.achievement_image = self.load_achievement_image()
def load_image(self, product_name):
try:
# Use Flaschen icon for Bierflasche
if product_name == "Bierflasche":
product_name = "Flaschen"
image_path = f"PfandApplication/images/{product_name.lower()}.png"
if os.path.exists(image_path):
try:
image = Image.open(image_path)
image = image.resize((100, 100), Image.Resampling.LANCZOS)
return ImageTk.PhotoImage(image)
except Exception as e:
print(f"Error processing image {image_path}: {e}")
return None
else:
print(f"Image not found: {image_path}")
return None
except Exception as e:
print(f"Error loading image for {product_name}: {e}")
return None
def load_achievement_image(self):
try:
image_path = "PfandApplication/images/auszeichnung.png"
if os.path.exists(image_path):
try:
image = Image.open(image_path)
image = image.resize((50, 50), Image.Resampling.LANCZOS)
# Store both normal and gray versions
self.achievement_image = ImageTk.PhotoImage(image)
# Create grayscale version while preserving transparency
gray_image = Image.new('RGBA', image.size)
for x in range(image.width):
for y in range(image.height):
r, g, b, a = image.getpixel((x, y))
# Convert to grayscale while preserving alpha
gray = int(0.299 * r + 0.587 * g + 0.114 * b)
# Make it lighter
gray = min(255, gray + 100)
gray_image.putpixel((x, y), (gray, gray, gray, a))
self.achievement_image_gray = ImageTk.PhotoImage(gray_image)
return self.achievement_image
except Exception as e:
print(f"Error processing achievement image: {e}")
return None
print(f"Achievement image not found: {image_path}")
return None
except Exception as e:
print(f"Error loading achievement image: {e}")
return None
def initialize_achievements(self):
return {
"each_100": Achievement("Krass, Weiter So!", "Du hast bis jetzt 100 von jedem Element gesammelt!", "each_element", 100),
"each_500": Achievement("Adlersson wäre neidisch!", "Adlersson wäre neidisch auf dich! Du hast 500 von jedem Element gesammelt!", "each_element", 500),
"each_1000": Achievement("Arbeitslos I", "Arbeitsamt hat angerufen! Du hast 1000 von jedem Element gesammelt!", "each_element", 1000),
"total_2000": Achievement("Arbeitslos II", "Das Arbeitsamt hat angst vor dir! Du hast 2000 totale Elemente gesammelt!", "total_elements", 2000),
"total_3000": Achievement("Arbeitslos III", "Drachenlord hat angst vor dir! Du hast mehr wie 3000 Elemente gesammelt!", "total_elements", 3000),
"total_over_3000": Achievement("Krankhafte Sucht!", "Du hast echt einen Vogel! Pfandangel #1! Du hast >3000 gesammelt!", "total_elements", 3001),
"first_deposit": Achievement("Depositer!", "Guter Anfang!", "deposits", 1),
"deposits_10": Achievement("Depositer I", "Cool, Weiter So!", "deposits", 10),
"deposits_50": Achievement("Depositer II", "WoW, Echt viele Abgaben!", "deposits", 50),
"deposits_100": Achievement("Depositer III", "Du bist der Meister der Abgaben!", "deposits", 100),
"deposits_150": Achievement("Meister Depositer", "Der Pfandautomat hat Angst vor dir, so viel wie du Abgegeben hast müsstest du eine Villa besitzen!", "deposits", 150),
# New scanner achievements
"first_scan": Achievement("Scanner Neuling", "Du hast deinen ersten Barcode gescannt!", "scans", 1),
"scans_50": Achievement("Scanner Pro", "50 Barcodes gescannt - du kennst dich aus!", "scans", 50),
"scans_100": Achievement("Scanner Meister", "100 Barcodes gescannt - der Profi ist da!", "scans", 100),
"scans_500": Achievement("Scanner Legende", "500 Barcodes gescannt - legendärer Scanner Status erreicht!", "scans", 500),
"daily_10": Achievement("Tages Champion", "10 Barcodes an einem Tag gescannt!", "daily_scans", 10),
"daily_25": Achievement("Tages Meister", "25 Barcodes an einem Tag gescannt - sehr fleißig!", "daily_scans", 25)
}
def load_achievements(self):
try:
with open('achievements.json', 'r') as f:
data = json.load(f)
for key, achievement_data in data.items():
if key in self.achievements:
self.achievements[key].unlocked = achievement_data['unlocked']
self.achievements[key].unlock_date = achievement_data['unlock_date']
except FileNotFoundError:
pass
def save_achievements(self):
data = {
key: {
'unlocked': achievement.unlocked,
'unlock_date': achievement.unlock_date
}
for key, achievement in self.achievements.items()
}
with open('achievements.json', 'w') as f:
json.dump(data, f)
def check_achievements(self):
total_elements = sum(self.deposit_history[-1]['quantities'].values()) if self.deposit_history else 0
all_time_total = sum(sum(d['quantities'].values()) for d in self.deposit_history)
deposits_count = len(self.deposit_history)
for achievement in ["each_100", "each_500", "each_1000"]:
if not self.achievements[achievement].unlocked:
if all(self.deposit_history[-1]['quantities'][product] >= self.achievements[achievement].condition_value
for product in self.products):
self.unlock_achievement(achievement)
for achievement in ["total_2000", "total_3000", "total_over_3000"]:
if not self.achievements[achievement].unlocked and all_time_total >= self.achievements[achievement].condition_value:
self.unlock_achievement(achievement)
deposit_achievements = {
1: "first_deposit",
10: "deposits_10",
50: "deposits_50",
100: "deposits_100",
150: "deposits_150"
}
for count, achievement_key in deposit_achievements.items():
if not self.achievements[achievement_key].unlocked and deposits_count >= count:
self.unlock_achievement(achievement_key)
def unlock_achievement(self, achievement_key):
achievement = self.achievements[achievement_key]
if not achievement.unlocked:
achievement.unlocked = True
achievement.unlock_date = datetime.now().strftime("%d.%m.%Y")
self.save_achievements()
messagebox.showinfo("Auszeichnung freigeschaltet!",
f"Neue Auszeichnung: {achievement.title}\n\n{achievement.description}")
def show_achievements(self):
achievements_window = tk.Toplevel(self.root)
achievements_window.title("Auszeichnungen")
achievements_window.geometry("800x600")
notebook = ttk.Notebook(achievements_window)
notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
all_frame = ttk.Frame(notebook)
notebook.add(all_frame, text="Alle Auszeichnungen")
unlocked_frame = ttk.Frame(notebook)
notebook.add(unlocked_frame, text="Freigeschaltete")
self._create_achievements_view(all_frame, show_all=True)
self._create_achievements_view(unlocked_frame, show_all=False)
def _create_achievements_view(self, parent_frame, show_all=True):
canvas = tk.Canvas(parent_frame)
scrollbar = ttk.Scrollbar(parent_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)
def _on_mousewheel(event):
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
def _bind_mousewheel(widget):
widget.bind('<MouseWheel>', _on_mousewheel)
for child in widget.winfo_children():
_bind_mousewheel(child)
canvas.bind('<MouseWheel>', _on_mousewheel)
_bind_mousewheel(scrollable_frame)
sammeln_achievements = {
"each_100": self.achievements["each_100"],
"each_500": self.achievements["each_500"],
"each_1000": self.achievements["each_1000"],
"total_2000": self.achievements["total_2000"],
"total_3000": self.achievements["total_3000"],
"total_over_3000": self.achievements["total_over_3000"]
}
abgeben_achievements = {
"first_deposit": self.achievements["first_deposit"],
"deposits_10": self.achievements["deposits_10"],
"deposits_50": self.achievements["deposits_50"],
"deposits_100": self.achievements["deposits_100"],
"deposits_150": self.achievements["deposits_150"]
}
scanner_achievements = {
"first_scan": self.achievements["first_scan"],
"scans_50": self.achievements["scans_50"],
"scans_100": self.achievements["scans_100"],
"scans_500": self.achievements["scans_500"],
"daily_10": self.achievements["daily_10"],
"daily_25": self.achievements["daily_25"]
}
row = 0
def add_group_header(title):
nonlocal row
header_frame = ttk.Frame(scrollable_frame)
header_frame.grid(row=row, column=0, sticky='ew', padx=5, pady=(15, 5))
header_label = ttk.Label(header_frame, text=title,
font=('TkDefaultFont', 12, 'bold'))
header_label.pack(anchor='w')
separator = ttk.Separator(scrollable_frame, orient='horizontal')
row += 1
separator.grid(row=row, column=0, sticky='ew', pady=2)
row += 1
def add_achievement(key, achievement):
nonlocal row
if not show_all and not achievement.unlocked:
return
frame = ttk.Frame(scrollable_frame)
frame.grid(row=row, column=0, sticky='ew', padx=5, pady=5)
if self.achievement_image:
if achievement.unlocked:
image_label = ttk.Label(frame, image=self.achievement_image)
else:
image_label = ttk.Label(frame, image=self.achievement_image_gray)
image_label.grid(row=0, column=0, rowspan=2, padx=(5, 10), pady=5)
content_frame = ttk.Frame(frame)
content_frame.grid(row=0, column=1, sticky='nsew', pady=5)
title_label = ttk.Label(content_frame, text=achievement.title,
font=('TkDefaultFont', 10, 'bold'))
title_label.grid(row=0, column=0, sticky='w')
if achievement.unlocked:
date_label = ttk.Label(content_frame,
text=f"Freigeschaltet am: {achievement.unlock_date}",
font=('TkDefaultFont', 8))
date_label.grid(row=0, column=1, padx=(20, 0))
desc_label = ttk.Label(content_frame, text=achievement.description,
wraplength=600)
desc_label.grid(row=1, column=0, columnspan=2, sticky='w', pady=(2, 0))
content_frame.grid_columnconfigure(0, weight=1)
content_frame.grid_columnconfigure(1, weight=0)
separator = ttk.Separator(scrollable_frame, orient='horizontal')
row += 1
separator.grid(row=row, column=0, sticky='ew', pady=5)
row += 1
_bind_mousewheel(frame)
add_group_header("Sammeln")
for key, achievement in sammeln_achievements.items():
add_achievement(key, achievement)
add_group_header("Abgeben")
for key, achievement in abgeben_achievements.items():
add_achievement(key, achievement)
add_group_header("Scanner")
for key, achievement in scanner_achievements.items():
add_achievement(key, achievement)
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# Credit Section (Overall made nicer in Version 7.04.001) || Changed up a bit in V7.04.101
def create_credits(self):
about_window = tk.Toplevel(self.root)
about_window.title("Über Programm")
about_window.resizable(True, True)
# Configure grid weights so widgets expand properly | This is some new Stuff!
about_window.grid_columnconfigure(0, weight=1)
about_window.grid_columnconfigure(1, weight=1)
about_window.grid_rowconfigure(0, weight=1)
label = tk.Label(about_window,
text=(
"PfandApp V.8.04.301-PKG1\n\n"
"Erstellt mit TKinter, CV2, Numpy, PyZbar, TGTG-API, TKCalendar, Datetime\n"
"Eigene Module: Updater, TGTG_OC, Wiki, BuildUtil\n"
),
padx=10,
pady=10,
justify="center",
anchor="center")
label.grid(row=0, column=0, columnspan=2, pady=10, sticky="nsew")
url = "https://zockerkatze.github.io/ZockerKatze/"
# Website button
website_button = tk.Button(about_window, text="WebSite", command=lambda: webbrowser.open(url))
website_button.grid(row=1, column=0, padx=10, pady=10, sticky="ew")
# Close button
close_button = tk.Button(about_window, text="Close", command=about_window.destroy)
close_button.grid(row=1, column=1, padx=10, pady=10, sticky="ew")
# TGTG Credits
def TGTG_credits(self):
about_tgtg = tk.Toplevel(self.root)
about_tgtg.title("Über TGTG-OrderChecker")
about_tgtg.resizable(True, True)
about_tgtg.grid_columnconfigure(0, weight=1)
about_tgtg.grid_columnconfigure(1, weight=1)
about_tgtg.grid_rowconfigure(0, weight=1)
label_TGTG = tk.Label(
about_tgtg,
text="TooGoodToGo OrderChecker\nOriginalerweiße: https://github.com/ZockerKatze/tgtg_orderchecker\nV1.102.202 (PfandVersion-PKG)",
padx=10,
pady=10,
justify="center",
anchor="center"
)
label_TGTG.grid(row=0, column=0, columnspan=2, pady=10, sticky="nsew")
url_TGTG = "https://github.com/ZockerKatze/tgtg_orderchecker"
# Website button
website_button_TGTG = tk.Button(about_tgtg, text="View Repo In Browser", command=lambda: webbrowser.open(url_TGTG))
website_button_TGTG.grid(row=1, column=0, padx=10, pady=10, sticky="ew")
# Close button
close_button = tk.Button(about_tgtg, text="Close", command=about_tgtg.destroy)
close_button.grid(row=1, column=1, padx=10, pady=10, sticky="ew")
def update_credits(self): # Credits for the Updater Application (not some update function for some credits) || Rewrote this in Version 7.04.101 => Inconsistency is key
about_update = tk.Toplevel(self.root)
about_update.title("Über UpdaterApp")
about_update.geometry("650x190")
about_update.grid_columnconfigure(0, weight=1) # horizont
about_update.grid_rowconfigure(0, weight=1) # vertically
about_update.grid_rowconfigure(1, weight=0) # tight :3 (OwO)
# Text content
label_update_app = tk.Label(
about_update,
text=(
"Updater App für PfandApp\n"
"Version 1.200.000\n"
"Diese Updater App nutzt das GitHub Repository, um die App zu updaten.\n"
"Nach Updates sollte die App neugestartet (oder reloaded, bei UI) werden.\n"
"Beim Starten der App wird nach Updates gesucht!"
),
justify="left", anchor="center"
)
label_update_app.grid(row=0, column=0, sticky='nsew', padx=10, pady=10)
# Close button at the bottom (like u) :3
close_button = tk.Button(about_update, text="Close", command=about_update.destroy)
close_button.grid(row=1, column=0, sticky='ew', padx=10, pady=(0, 10))
def µScan_credits(self):
about_µScan = tk.Toplevel(self.root)
about_μScan.title("Über µScan")
about_μScan.geometry("650x190")
about_μScan.grid_columnconfigure(0, weight=1)
about_μScan.grid_rowconfigure(0, weight=1)
about_μScan.grid_rowconfigure(1, weight=0)
label_µScan_app = tk.Label(
about_μScan,
text=(
"µScan - Der bessere Barcode Scanner\n"
"Version 1.1.0\n"
"µScan erfordert einen UI Reload (Strg+R) in der Root Anwendung\n"
"µScan ist für mehrere Barcodes gemacht, die in einer kurzen Zeit gescannt werden sollten\n"
),
justify="left", anchor="center"
)
label_μScan_app.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
close_button = tk.Button(about_µScan, text="Close", command=about_μScan.destroy)
close_button.grid(row=1, column=0, sticky='ew', padx=10, pady=(0, 10))
def create_menu(self):
self.menubar = tk.Menu(self.root)
self.root.config(menu=self.menubar)
# "Datei" Menu
file_menu = tk.Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label="Datei", menu=file_menu)
file_menu.add_command(label="Speichern", command=self.save_quantities, accelerator="Strg+S")
file_menu.add_command(label="Ordner öffnen", command=self.open_file_location, accelerator="Strg+O")
file_menu.add_command(label="Speicherdatei löschen", command=self.remove_save_file, accelerator="Strg+Shift+F1")
file_menu.add_separator()
file_menu.add_command(label="Neulanden der UI", command=self.recreate_widgets, accelerator="Strg+R")
file_menu.add_command(label="Updater", command=open_updater, accelerator="Strg+U") # Added this to the File Menu too!
file_menu.add_separator()
file_menu.add_command(label="Öffne PfandListe", command=WIKI.select_file, accelerator="Strg+L")
file_menu.add_command(label="Beenden", command=self.root.quit, accelerator="Strg+Q")
file_menu.add_separator()
file_menu.add_command(label="Über Programm", command=self.create_credits, accelerator="Strg+F10")
# Deposit Menu
deposit_menu = tk.Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label="Pfand", menu=deposit_menu)
deposit_menu.add_command(label="Pfand Abgeben", command=self.quick_deposit, accelerator="Strg+D")
deposit_menu.add_command(label="Abgabe Historie", command=self.show_deposit_history, accelerator="Strg+H")
deposit_menu.add_separator()
deposit_menu.add_command(label="Historie Exportieren (CSV)", command=self.export_history_csv, accelerator="Strg+E")
deposit_menu.add_command(label="Historie Löschen", command=self.clear_deposit_history, accelerator="Strg+Shift+F2")
# Scanner Menu
scanner_menu = tk.Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label="Scanner", menu=scanner_menu)
scanner_menu.add_command(label="Scanner öffnen", command=self.open_scanner_window, accelerator="Strg+B")
scanner_menu.add_separator()
scanner_menu.add_command(label="Öffne µScan", command=launch_pfand_scanner, accelerator="Strg+Shift+B") #µScan
scanner_menu.add_command(label="Über µScan", command=self.µScan_credits) #µScan credits
scanner_menu.add_separator()
scanner_menu.add_command(label="Barcodes Exportieren (CSV)", command=self.export_barcodes_csv, accelerator="Strg+Shift+E")
# Achivements Menu
achievements_menu = tk.Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label="Auszeichnungen", menu=achievements_menu)
achievements_menu.add_command(label="Auszeichnungen anzeigen", command=self.show_achievements, accelerator="Strg+F6")
achievements_menu.add_command(label="Auszeichnungen löschen", command=self.delete_achievements, accelerator="Strg+F7")
# Add custom products menu
products_menu = tk.Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label="Produkte", menu=products_menu)
products_menu.add_command(label="Produkt hinzufügen", command=self.show_add_product_window, accelerator="Strg+P")
products_menu.add_command(label="Produkte verwalten", command=self.show_manage_products_window, accelerator="Strg+Shift+P")
# TGTG Menu (used to not exist)
tgtg_menu = tk.Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label="TGTG", menu=tgtg_menu)
tgtg_menu.add_command(label="Öffne TGTG-OC", command=tgtg.start_tgtg, accelerator="Strg+F12")
tgtg_menu.add_command(label="Öffne KeyTool", command=tgtg_kt.ask_for_tokens, accelerator="Strg+F11")
tgtg_menu.add_separator()
tgtg_menu.add_command(label="Über TGTG", command=self.TGTG_credits) # No keybind , why tf would you need one anyways
update_menu = tk.Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label="Updater", menu=update_menu)
update_menu.add_command(label="Öffne Updater", command=open_updater, accelerator="Strg+U") # Version (7.4.000 Updater Version)
update_menu.add_separator()
update_menu.add_command(label="Über Updater", command=self.update_credits) # Also no keybind here, same reason as the tgtg one #V7.4.001
# Manage Keybinds
self.root.bind('<Control-s>', lambda e: self.save_quantities())
self.root.bind('<Control-o>', lambda e: self.open_file_location())
self.root.bind('<Control-q>', lambda e: self.root.quit())
self.root.bind('<Control-d>', lambda e: self.quick_deposit())
self.root.bind('<Control-h>', lambda e: self.show_deposit_history())
self.root.bind('<Control-e>', lambda e: self.export_history_csv())
self.root.bind('<Control-b>', lambda e: self.open_scanner_window())
self.root.bind('<Control-Shift-B>', lambda e: launch_pfand_scanner()) #µScan
self.root.bind('<Control-F1>', lambda e: self.handle_shift_f1(e))
self.root.bind('<Control-F2>', lambda e: self.handle_shift_f2(e))
self.root.bind('<Control-F6>', lambda e: self.show_achievements())
self.root.bind('<Control-F7>', lambda e: self.delete_achievements())
self.root.bind('<Control-F12>', lambda e: tgtg.start_tgtg(self.root))
self.root.bind('<Control-F11>', lambda e: tgtg_kt.ask_for_tokens())
self.root.bind('<Control-F10>', lambda e: self.create_credits())
self.root.bind('<Control-E>', lambda e: self.export_barcodes_csv() if e.state & 0x1 else self.export_history_csv())
self.root.bind('<Control-p>', lambda e: self.show_add_product_window())
self.root.bind('<Control-P>', lambda e: self.show_manage_products_window() if e.state & 0x1 else self.show_add_product_window())
self.root.bind('<Control-l>', lambda e: WIKI.select_file())
self.root.bind('<Control-r>', lambda e: self.recreate_widgets())
self.root.bind('<Control-u>', lambda e: open_updater()) # New Update Feature (Version 7.4.000 UPDATER)
def open_file_location(self):
current_dir = os.getcwd()
if os.name == 'nt': # Windows
os.startfile(current_dir)
else: # Linux/Mac
try:
subprocess.run(['xdg-open', current_dir])
except:
subprocess.run(['open', current_dir])
def remove_save_file(self):
if os.path.exists('quantities.json'):
if messagebox.askyesno("Löschen bestätigen", "Sind Sie sicher, dass Sie die Speicherdatei löschen möchten?"):
try:
os.remove('quantities.json')
messagebox.showinfo("Erfolg", "Speicherdatei wurde erfolgreich gelöscht!")
self.quantities = {product: 0 for product in self.products}
self.update_total()
for widget in self.root.winfo_children():
if isinstance(widget, ttk.Frame):
for child in widget.winfo_children():
if isinstance(child, ttk.Frame):
for grandchild in child.winfo_children():
if isinstance(grandchild, ttk.Spinbox):
grandchild.set("0")
except Exception as e:
messagebox.showerror("Fehler", f"Datei konnte nicht gelöscht werden: {str(e)}")
else:
messagebox.showinfo("Info", "Keine Speicherdatei vorhanden.")
def load_quantities(self):
try:
with open('quantities.json', 'r') as f:
self.quantities = json.load(f)
except FileNotFoundError:
self.quantities = {product: 0 for product in self.products}
def save_quantities(self):
try:
with open('quantities.json', 'w') as f:
json.dump(self.quantities, f)
messagebox.showinfo("Erfolg", "Mengen wurden erfolgreich gespeichert!")
except Exception as e:
messagebox.showerror("Fehler", f"Fehler beim Speichern der Mengen: {str(e)}")
def create_widgets(self):
main_frame = ttk.Frame(self.root)
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
for i, product in enumerate(self.products):
frame = ttk.Frame(main_frame)
frame.grid(row=0, column=i, padx=10, pady=5)
image = self.load_image(product)
if image:
self.images[product] = image
label = ttk.Label(frame, image=self.images[product])
label.grid(row=0, column=0, pady=5)
else:
canvas = tk.Canvas(frame, width=100, height=100, bg='white')
canvas.grid(row=0, column=0, pady=5)
canvas.create_text(50, 50, text=f"Kein {product}\nBild gefunden")
ttk.Label(frame, text=product).grid(row=1, column=0, pady=2)
ttk.Label(frame, text=f"{self.PRICES[product]:.2f}").grid(row=2, column=0, pady=2)
quantity_var = tk.StringVar(value=str(self.quantities.get(product, 0)))
spinbox = ttk.Spinbox(
frame,
from_=0,
to=100,
width=5,
textvariable=quantity_var,
command=lambda p=product, v=quantity_var: self.update_quantity(p, v)
)
spinbox.grid(row=3, column=0, pady=5)
# Store spinbox reference
self.spinboxes[product] = spinbox
spinbox.bind('<Return>', lambda event, p=product, v=quantity_var: self.update_quantity(p, v))
spinbox.bind('<FocusOut>', lambda event, p=product, v=quantity_var: self.update_quantity(p, v))
self.total_label = ttk.Label(main_frame, text="Gesamt: €0.00", font=('TkDefaultFont', 10, 'bold'))
self.total_label.grid(row=1, column=0, columnspan=len(self.products), pady=10)
self.update_total()
def update_quantity(self, product, var, event=None):
try:
quantity = int(var.get())
self.quantities[product] = quantity
self.update_total()
except ValueError:
var.set(str(self.quantities.get(product, 0)))
def update_total(self):
total = sum(self.quantities[product] * self.PRICES[product] for product in self.products) # get total
self.total_label.config(text=f"Gesamt: €{total:.2f}")
def load_deposit_history(self):
try:
with open('deposit_history.json', 'r') as f:
return json.load(f)
except FileNotFoundError:
return []
def save_deposit_history(self):
try:
with open('deposit_history.json', 'w') as f:
json.dump(self.deposit_history, f)
except Exception as e:
messagebox.showerror("Fehler", f"Fehler beim Speichern der Historie: {str(e)}")
# Changed in Version 7.4.101
def show_deposit_history(self):
try:
with open("deposit_history.json", "r", encoding="utf-8") as file:
deposit_history = json.load(file)
except (FileNotFoundError, json.JSONDecodeError) as e:
print("error while loading file")
return
history_window = tk.Toplevel(self.root)
history_window.title("Pfand Abgabe Historie")
history_window.geometry("900x500")
main_frame = ttk.Frame(history_window)
main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
all_items = set()
for deposit in deposit_history:
all_items.update(deposit['quantities'].keys())
item_columns = sorted(all_items)
columns = ['Datum'] + item_columns + ['Gesamt']
tree = ttk.Treeview(main_frame, columns=columns, show='headings')
for col in columns:
tree.heading(col, text=col, anchor='center')
width = 100 if col == 'Datum' else 80
tree.column(col, width=width, anchor='center' if col != 'Gesamt' else 'e')
scrollbar = ttk.Scrollbar(main_frame, orient=tk.VERTICAL, command=tree.yview)
tree.configure(yscrollcommand=scrollbar.set)
tree.grid(row=0, column=0, sticky='nsew')
scrollbar.grid(row=0, column=1, sticky='ns')
main_frame.grid_columnconfigure(0, weight=1)
main_frame.grid_rowconfigure(0, weight=1)
totals = {item: 0 for item in item_columns}
total_amount = 0
for deposit in deposit_history:
quantities = deposit.get('quantities', {})
row = [deposit.get('date', '')]
for item in item_columns:
count = quantities.get(item, 0)
row.append(count)
totals[item] += count
amount = deposit.get('total', 0.0)
row.append(f"{amount:.2f}")
total_amount += amount
tree.insert('', tk.END, values=row)
totals_frame = ttk.Frame(main_frame)
totals_frame.grid(row=1, column=0, sticky='ew', pady=(5, 0))
separator = ttk.Separator(main_frame, orient='horizontal')
separator.grid(row=2, column=0, sticky='ew', pady=2)
bold_font = ('TkDefaultFont', 9, 'bold')
row = ["Gesamt:"]
for item in item_columns:
row.append(f"{totals[item]} {item}")
row.append(f"{total_amount:.2f}")
for idx, value in enumerate(row):
ttk.Label(totals_frame, text=value, font=bold_font).grid(row=0, column=idx, sticky='w', padx=5)
separator.grid(row=3, column=0, sticky='ew', pady=5)
def make_deposit(self):
deposit_dialog = tk.Toplevel(self.root)
deposit_dialog.title("Pfand Abgeben")
deposit_dialog.geometry("300x150")
deposit_dialog.transient(self.root)
deposit_dialog.grab_set()
ttk.Label(deposit_dialog, text="Abgabe Datum:").pack(pady=5)
date_picker = DateEntry(deposit_dialog, width=12, background='darkblue',
foreground='white', borderwidth=2, locale='de_DE')
date_picker.pack(pady=5)
def confirm_deposit():
date = date_picker.get_date().strftime("%d.%m.%Y")
current_total = sum(self.quantities[product] * self.PRICES[product]
for product in self.products)
deposit_record = {
'date': date,
'quantities': dict(self.quantities),
'total': current_total
}
self.deposit_history.append(deposit_record)
self.save_deposit_history()
self.quantities = {product: 0 for product in self.products}
self.save_quantities()
self.update_total()
for widget in self.root.winfo_children():
if isinstance(widget, ttk.Frame):
for child in widget.winfo_children():
if isinstance(child, ttk.Frame):
for grandchild in child.winfo_children():
if isinstance(grandchild, ttk.Spinbox):
grandchild.set("0")
messagebox.showinfo("Erfolg", "Pfand wurde erfolgreich abgegeben!")
deposit_dialog.destroy()
button_frame = ttk.Frame(deposit_dialog)
button_frame.pack(pady=20)
ttk.Button(button_frame, text="Abgeben", command=confirm_deposit).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="Abbrechen", command=deposit_dialog.destroy).pack(side=tk.LEFT, padx=5)
def quick_deposit(self):
if sum(self.quantities.values()) == 0:
messagebox.showinfo("Info", "Keine Mengen zum Abgeben vorhanden.")
return
if messagebox.askyesno("Pfand Abgeben", "Möchten Sie den Pfand mit dem heutigen Datum abgeben?"):
current_date = datetime.now().strftime("%d.%m.%Y")
current_total = sum(self.quantities[product] * self.PRICES[product]
for product in self.products)
deposit_record = {
'date': current_date,
'quantities': dict(self.quantities),
'total': current_total
}
self.deposit_history.append(deposit_record)
self.save_deposit_history()
self.check_achievements()
self.quantities = {product: 0 for product in self.products}
self.save_quantities()
self.update_total()
for widget in self.root.winfo_children():
if isinstance(widget, ttk.Frame):
for child in widget.winfo_children():
if isinstance(child, ttk.Frame):
for grandchild in child.winfo_children():
if isinstance(grandchild, ttk.Spinbox):
grandchild.set("0")
messagebox.showinfo("Erfolg", "Pfand wurde erfolgreich abgegeben!")
else:
self.make_deposit()
def export_history_csv(self):
if not self.deposit_history:
messagebox.showinfo("Info", "Keine Historie zum Exportieren vorhanden.")
return
try:
file_path = filedialog.asksaveasfilename(
defaultextension=".csv",
filetypes=[("CSV Dateien", "*.csv")],
initialfile="pfand_historie.csv"
)
if not file_path:
return
with open(file_path, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile, delimiter=';')
# Create header with all products
header = ['Datum'] + self.products + ['Gesamt (€)']
writer.writerow(header)
for deposit in self.deposit_history:
# Create row with all products
row = [deposit['date']]
for product in self.products:
row.append(deposit['quantities'].get(product, 0))
row.append(f"{deposit['total']:.2f}")
writer.writerow(row)
messagebox.showinfo("Erfolg", "Historie wurde erfolgreich exportiert!")
except Exception as e:
messagebox.showerror("Fehler", f"Fehler beim Exportieren: {str(e)}")
def clear_deposit_history(self):
if not self.deposit_history:
messagebox.showinfo("Info", "Keine Historie zum Löschen vorhanden.")
return
if messagebox.askyesno("Löschen bestätigen",
"Sind Sie sicher, dass Sie die gesamte Abgabe-Historie löschen möchten?\n"
"Dieser Vorgang kann nicht rückgängig gemacht werden!"):
try:
self.deposit_history = []
if os.path.exists('deposit_history.json'):
os.remove('deposit_history.json')
messagebox.showinfo("Erfolg", "Abgabe-Historie wurde erfolgreich gelöscht!")
except Exception as e:
messagebox.showerror("Fehler", f"Fehler beim Löschen der Historie: {str(e)}")
def handle_shift_f1(self, event):
if event.state & 0x1:
self.remove_save_file()
def handle_shift_f2(self, event):
if event.state & 0x1:
self.clear_deposit_history()
def delete_achievements(self):
if not any(achievement.unlocked for achievement in self.achievements.values()):
messagebox.showinfo("Info", "Keine Auszeichnungen zum Löschen vorhanden.")
return
if messagebox.askyesno("Löschen bestätigen",
"Sind Sie sicher, dass Sie alle Auszeichnungen löschen möchten?\n"
"Dieser Vorgang kann nicht rückgängig gemacht werden!"):
try:
for achievement in self.achievements.values():
achievement.unlocked = False
achievement.unlock_date = None
if os.path.exists('achievements.json'):
os.remove('achievements.json')
messagebox.showinfo("Erfolg", "Alle Auszeichnungen wurden erfolgreich gelöscht!")
except Exception as e:
messagebox.showerror("Fehler", f"Fehler beim Löschen der Auszeichnungen: {str(e)}")
def open_scanner_window(self):
if self.scanner_window is None or not self.scanner_window.winfo_exists():
self.scanner_window = tk.Toplevel(self.root)
self.scanner_window.title("Pfand Scanner")
self.scanner_window.protocol("WM_DELETE_WINDOW", self.close_scanner_window)
# Create frames for scanner layout
self.camera_frame = ttk.Frame(self.scanner_window)
self.camera_frame.pack(side="left", padx=10, pady=5)
self.scanner_control_frame = ttk.Frame(self.scanner_window)
self.scanner_control_frame.pack(side="left", padx=10, pady=5, fill="y")
# Create camera label
self.camera_label = ttk.Label(self.camera_frame)
self.camera_label.pack()
# Create focus control
focus_frame = ttk.LabelFrame(self.scanner_control_frame, text="Kamera Einstellungen")
focus_frame.pack(pady=5, padx=5, fill="x")
ttk.Label(focus_frame, text="Fokus:").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="Autofokus",
variable=self.autofocus_var,
command=self.toggle_autofocus
)
self.autofocus_check.pack(pady=2)
# Create image processing controls
process_frame = ttk.LabelFrame(self.scanner_control_frame, text="Bildverarbeitung")
process_frame.pack(pady=5, padx=5, fill="x")
ttk.Label(process_frame, text="Helligkeit:").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="Kontrast:").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")
# Start/Stop button
self.scan_button = ttk.Button(
self.scanner_control_frame,
text="Scannen Starten",
command=self.toggle_scanning
)
self.scan_button.pack(pady=10)
# Initialize scan counter for achievements
self.daily_scans = 0
self.total_scans = 0
self.last_scan_date = None
# Queue for thread-safe communication
self.queue = queue.Queue()
# Set window size to match camera resolution
self.scanner_window.geometry("1600x800")
def close_scanner_window(self):
if self.scanning:
self.toggle_scanning()
if self.cap:
self.cap.release()
self.cap = None
if self.scanner_window:
self.scanner_window.destroy()
self.scanner_window = None
def toggle_scanning(self):
if not self.scanning:
self.cap = cv2.VideoCapture(0)
# Set optimal camera properties for performance
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) # Request 30 FPS
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # Minimize buffer delay
self.cap.set(cv2.CAP_PROP_AUTOFOCUS, 0)
self.cap.set(cv2.CAP_PROP_FOCUS, 0)
self.scanning = True
self.scan_button.configure(text="Scannen Stoppen")
self.process_video()
else:
self.scanning = False
if self.cap:
self.cap.release()
self.cap = None
self.scan_button.configure(text="Scannen Starten")
self.camera_label.configure(image='')
def toggle_autofocus(self):
if self.cap:
if self.autofocus_var.get():
self.cap.set(cv2.CAP_PROP_AUTOFOCUS, 1)
self.focus_slider.state(['disabled'])
else:
self.cap.set(cv2.CAP_PROP_AUTOFOCUS, 0)
self.focus_slider.state(['!disabled'])
self.cap.set(cv2.CAP_PROP_FOCUS, self.focus_slider.get())
def adjust_image(self, frame):
# This method is now only used for preview adjustments
brightness = self.brightness_slider.get() / 50.0 - 1.0
contrast = self.contrast_slider.get() / 50.0
adjusted = cv2.convertScaleAbs(frame, alpha=contrast, beta=brightness * 127)
return adjusted
def process_video(self):
if not self.scanning:
return
try:
ret, frame = self.cap.read()
if ret:
if not self.autofocus_var.get():
self.cap.set(cv2.CAP_PROP_FOCUS, self.focus_slider.get())
# Resize frame for faster processing (720p is plenty for barcode detection)
frame = cv2.resize(frame, (1280, 720))
# Only process every 3rd frame for barcode detection
if hasattr(self, 'frame_count'):
self.frame_count += 1
else:
self.frame_count = 0
if self.frame_count % 3 == 0: # Process every 3rd frame
# Process in grayscale for better performance
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
barcodes = decode(gray)
for barcode in barcodes:
points = barcode.polygon
if len(points) == 4:
pts = np.array([(p.x, p.y) for p in points])
cv2.polylines(frame, [pts], True, (0, 255, 0), 2)
barcode_data = barcode.data.decode('utf-8')
if barcode_data not in self.scanned_barcodes:
self.scanned_barcodes.add(barcode_data)
self.scanner_window.after(0, lambda d=barcode_data: self.handle_barcode(d))
# Convert and display frame
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
img = Image.fromarray(cv2image)
imgtk = ImageTk.PhotoImage(image=img)
self.camera_label.imgtk = imgtk
self.camera_label.configure(image=imgtk)
if self.scanning:
# Use a shorter delay for higher frame rate
self.scanner_window.after(5, self.process_video)
except Exception as e:
print(f"Error in process_video: {e}")
if self.scanning:
self.scanner_window.after(5, self.process_video)
def handle_barcode(self, barcode_data):
# First dialog for Pfand symbol verification
verify_dialog = tk.Toplevel(self.scanner_window)
verify_dialog.title("Pfand Symbol Überprüfung")
verify_dialog.transient(self.scanner_window)
verify_dialog.grab_set()
ttk.Label(verify_dialog, text="Ist ein Pfand Symbol auf dem Produkt?").pack(pady=10)
def handle_verification(has_pfand):
verify_dialog.destroy()
if has_pfand:
# Add barcode to history with timestamp and Pfand status
self.barcode_history.append({
'timestamp': datetime.now().strftime("%d.%m.%Y %H:%M:%S"),
'barcode': barcode_data,
'has_pfand': True
})
self.show_product_selection_dialog(barcode_data)
else:
# Add barcode to history with timestamp and Pfand status
self.barcode_history.append({
'timestamp': datetime.now().strftime("%d.%m.%Y %H:%M:%S"),
'barcode': barcode_data,
'has_pfand': False
})
self.scanned_barcodes.remove(barcode_data)
messagebox.showinfo("Kein Pfand", "Dieses Produkt hat kein Pfand Symbol.")
button_frame = ttk.Frame(verify_dialog)
button_frame.pack(pady=10)
ttk.Button(button_frame, text="Ja", command=lambda: handle_verification(True)).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="Nein", command=lambda: handle_verification(False)).pack(side=tk.LEFT, padx=5)
def show_product_selection_dialog(self, barcode_data):
dialog = tk.Toplevel(self.scanner_window)
dialog.title("Barcode Erkannt")
dialog.transient(self.scanner_window)
dialog.grab_set()
ttk.Label(dialog, text=f"Barcode erkannt: {barcode_data}").pack(pady=10)
ttk.Label(dialog, text="Produkttyp auswählen:").pack(pady=5)
product_var = tk.StringVar()
for product in self.products:
ttk.Radiobutton(dialog, text=product, variable=product_var, value=product).pack()
def confirm():
selected_product = product_var.get()
if selected_product:
print(f"Erhöhe {selected_product}") # Debug print
# Update quantity
current_qty = self.quantities.get(selected_product, 0)
self.quantities[selected_product] = current_qty + 1
print(f"Neue Menge für {selected_product}: {self.quantities[selected_product]}") # Debug print
# Update scan counters and check achievements
self.update_scan_achievements()
# Force immediate UI update
def do_update():
try:
# Directly update the spinbox
spinbox = self.spinboxes[selected_product]
spinbox.set(str(self.quantities[selected_product]))
spinbox.update() # Force the spinbox to update
# Update the total
self.update_total()
self.root.update_idletasks() # Force the entire UI to update
# Save the quantities
self.save_quantities()
print("UI Update und Speicherung abgeschlossen") # Debug print
except Exception as e:
print(f"Fehler beim UI Update: {e}")
# Schedule the update for the next event loop iteration
self.root.after(1, do_update)
dialog.destroy()
def skip():
self.scanned_barcodes.remove(barcode_data)
dialog.destroy()
button_frame = ttk.Frame(dialog)
button_frame.pack(pady=10)
ttk.Button(button_frame, text="Hinzufügen", command=confirm).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="Überspringen", command=skip).pack(side=tk.LEFT, padx=5)
def update_scan_achievements(self):
current_date = datetime.now().strftime("%Y-%m-%d")
# Update daily scan counter
if self.last_scan_date != current_date:
self.daily_scans = 1
self.last_scan_date = current_date
else:
self.daily_scans += 1
# Update total scan counter
self.total_scans += 1
# Check scan-related achievements
achievements_to_check = {
1: "first_scan",
50: "scans_50",
100: "scans_100",
500: "scans_500"
}
# Check total scans achievements
for count, achievement_key in achievements_to_check.items():
if self.total_scans == count and not self.achievements[achievement_key].unlocked:
self.unlock_achievement(achievement_key)
self.save_achievements()
# Check daily scan achievements
daily_achievements = {
10: "daily_10",
25: "daily_25"
}
for count, achievement_key in daily_achievements.items():
if self.daily_scans == count and not self.achievements[achievement_key].unlocked:
self.unlock_achievement(achievement_key)
self.save_achievements()
# Save the updated scan counts
self.save_quantities() # This ensures we don't lose progress
def update_ui(self):
def update_spinboxes():
try:
# Update all spinboxes to match quantities
main_frame = self.root.winfo_children()[0] # Get the main frame
for frame in main_frame.winfo_children():
if isinstance(frame, ttk.Frame):
# Find the product this frame represents
for widget in frame.winfo_children():
if isinstance(widget, ttk.Label) and widget.cget('text') in self.products:
product = widget.cget('text')
current_qty = self.quantities.get(product, 0)
# Find and update the spinbox
for w in frame.winfo_children():
if isinstance(w, ttk.Spinbox):
w.set(str(current_qty))
w.update()
break
# Update the total
self.update_total()
except Exception as e:
print(f"Error updating UI: {e}")
# Ensure updates happen in the main thread
if threading.current_thread() is threading.main_thread():
update_spinboxes()
else:
self.root.after(0, update_spinboxes)
def on_closing(self):
if self.scanner_window and self.scanner_window.winfo_exists():
self.close_scanner_window()
self.root.destroy()
def export_barcodes_csv(self):
if not self.barcode_history:
messagebox.showinfo("Info", "Keine Barcodes zum Exportieren vorhanden.")
return
try:
file_path = filedialog.asksaveasfilename(
defaultextension=".csv",
filetypes=[("CSV Dateien", "*.csv")],
initialfile="barcode_historie.csv"
)
if not file_path:
return
with open(file_path, 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile, delimiter=';')
# Write header
writer.writerow(['Datum', 'Barcode', 'Hat Pfand'])
# Write barcode history
for entry in self.barcode_history:
writer.writerow([
entry['timestamp'],
entry['barcode'],
'Ja' if entry['has_pfand'] else 'Nein'
])
messagebox.showinfo("Erfolg", "Barcode Historie wurde erfolgreich exportiert!")
except Exception as e:
messagebox.showerror("Fehler", f"Fehler beim Exportieren: {str(e)}")
def load_products(self):
try:
with open('products.json', 'r') as f:
data = json.load(f)
self.products = data.get('products', [])
self.PRICES = data.get('prices', {})
except FileNotFoundError:
# Default products if no JSON exists
self.products = ["Flaschen", "Bierflasche", "Kasten", "Dose", "Plastikflasche", "Monster", "Joghurt Glas"]
self.PRICES = {
"Flaschen": 0.25,
"Bierflasche": 0.20,
"Kasten": 3.00,
"Dose": 0.25,
"Plastikflasche": 0.25,
"Monster": 0.25,
"Joghurt Glas": 0.17,
}
self.save_products()
def save_products(self):
try:
data = {
'products': self.products,
'prices': self.PRICES
}
with open('products.json', 'w') as f:
json.dump(data, f)
except Exception as e:
messagebox.showerror("Fehler", f"Fehler beim Speichern der Produkte: {str(e)}")
def show_add_product_window(self):
dialog = tk.Toplevel(self.root)
dialog.title("Neues Produkt hinzufügen")
dialog.geometry("400x300")
dialog.transient(self.root)
dialog.grab_set()
# Product name
name_frame = ttk.Frame(dialog)
name_frame.pack(fill='x', padx=10, pady=5)
ttk.Label(name_frame, text="Produktname:").pack(side='left')
name_var = tk.StringVar()
name_entry = ttk.Entry(name_frame, textvariable=name_var)
name_entry.pack(side='left', fill='x', expand=True, padx=5)
# Deposit amount
deposit_frame = ttk.Frame(dialog)
deposit_frame.pack(fill='x', padx=10, pady=5)
ttk.Label(deposit_frame, text="Pfandbetrag (€):").pack(side='left')
deposit_var = tk.StringVar()
deposit_entry = ttk.Entry(deposit_frame, textvariable=deposit_var)
deposit_entry.pack(side='left', fill='x', expand=True, padx=5)
# Image selection
image_frame = ttk.Frame(dialog)
image_frame.pack(fill='x', padx=10, pady=5)
ttk.Label(image_frame, text="Bild:").pack(side='left')
image_path_var = tk.StringVar()
image_entry = ttk.Entry(image_frame, textvariable=image_path_var)
image_entry.pack(side='left', fill='x', expand=True, padx=5)
ttk.Button(image_frame, text="Durchsuchen", command=lambda: self.select_image(image_path_var)).pack(side='left')
def add_product():
name = name_var.get().strip()
try:
deposit = float(deposit_var.get().replace(',', '.'))
except ValueError:
messagebox.showerror("Fehler", "Bitte geben Sie einen gültigen Pfandbetrag ein.")
return
if not name:
messagebox.showerror("Fehler", "Bitte geben Sie einen Produktnamen ein.")
return
if name in self.products:
messagebox.showerror("Fehler", "Ein Produkt mit diesem Namen existiert bereits.")
return
image_path = image_path_var.get()
if image_path:
try:
# Create images directory if it doesn't exist
if not os.path.exists('PfandApplication/images'):
os.makedirs('images')
# Copy and rename the image
new_image_path = f"PfandApplication/images/{name.lower()}.png"
shutil.copy2(image_path, new_image_path)
except Exception as e:
messagebox.showerror("Fehler", f"Fehler beim Kopieren des Bildes: {str(e)}")
return
# Add the new product
self.products.append(name)
self.PRICES[name] = deposit
self.save_products()
# Update the UI
self.recreate_widgets()
dialog.destroy()
ttk.Button(dialog, text="Hinzufügen", command=add_product).pack(pady=10)
def select_image(self, image_path_var, preview_label=None):
file_path = filedialog.askopenfilename(
filetypes=[("PNG files", "*.png"), ("All files", "*.*")]
)
if file_path:
image_path_var.set(file_path)
if preview_label:
try:
image = Image.open(file_path)
# Resize image to fit preview (100x100)
image.thumbnail((100, 100), Image.Resampling.LANCZOS)
photo = ImageTk.PhotoImage(image)
preview_label.configure(image=photo)
preview_label.image = photo # Keep a reference
except Exception as e:
print(f"Error loading preview: {e}")
def show_manage_products_window(self):
dialog = tk.Toplevel(self.root)
dialog.title("Produkte verwalten")
dialog.geometry("1200x600")
dialog.transient(self.root)
dialog.grab_set()
# Create main container with two columns
main_container = ttk.Frame(dialog)
main_container.pack(fill='both', expand=True, padx=10, pady=5)
# Left column for product list
left_frame = ttk.Frame(main_container)
left_frame.pack(side='left', fill='both', expand=True, padx=(0, 5))
# Right column for add/edit product
right_frame = ttk.LabelFrame(main_container, text="Produkt hinzufügen")
right_frame.pack(side='right', fill='both', expand=True, padx=(5, 0))
# Create treeview in left frame
tree = ttk.Treeview(left_frame, columns=('Name', 'Pfand'), show='headings')
tree.heading('Name', text='Name')
tree.heading('Pfand', text='Pfand (€)')
tree.pack(fill='both', expand=True)
# Add scrollbar for treeview
scrollbar = ttk.Scrollbar(left_frame, orient='vertical', command=tree.yview)
scrollbar.pack(side='right', fill='y')
tree.configure(yscrollcommand=scrollbar.set)
# Populate treeview
for product in self.products:
tree.insert('', 'end', values=(product, f"{self.PRICES[product]:.2f}"))
# Add product form in right frame
# Product name
name_frame = ttk.Frame(right_frame)
name_frame.pack(fill='x', padx=10, pady=5)
ttk.Label(name_frame, text="Produktname:").pack(side='left')
name_var = tk.StringVar()
name_entry = ttk.Entry(name_frame, textvariable=name_var)
name_entry.pack(side='left', fill='x', expand=True, padx=5)
# Deposit amount
deposit_frame = ttk.Frame(right_frame)
deposit_frame.pack(fill='x', padx=10, pady=5)
ttk.Label(deposit_frame, text="Pfandbetrag (€):").pack(side='left')
deposit_var = tk.StringVar()
deposit_entry = ttk.Entry(deposit_frame, textvariable=deposit_var)
deposit_entry.pack(side='left', fill='x', expand=True, padx=5)
# Image selection with preview
image_frame = ttk.Frame(right_frame)
image_frame.pack(fill='x', padx=10, pady=5)
ttk.Label(image_frame, text="Bild:").pack(side='left')
image_path_var = tk.StringVar()
image_entry = ttk.Entry(image_frame, textvariable=image_path_var)
image_entry.pack(side='left', fill='x', expand=True, padx=5)
ttk.Button(image_frame, text="Durchsuchen", command=lambda: self.select_image(image_path_var, preview_label)).pack(side='left')
# Image preview
preview_frame = ttk.Frame(right_frame)
preview_frame.pack(fill='x', padx=10, pady=5)
preview_label = ttk.Label(preview_frame)
preview_label.pack()
def update_preview(image_path):
if image_path and os.path.exists(image_path):
try:
image = Image.open(image_path)
# Resize image to fit preview (100x100)
image.thumbnail((100, 100), Image.Resampling.LANCZOS)
photo = ImageTk.PhotoImage(image)
preview_label.configure(image=photo)
preview_label.image = photo # Keep a reference
except Exception as e:
print(f"Error loading preview: {e}")
def select_image_with_preview(image_path_var, preview_label):
file_path = filedialog.askopenfilename(
filetypes=[("PNG files", "*.png"), ("All files", "*.*")]
)
if file_path:
image_path_var.set(file_path)
update_preview(file_path)
def add_product():
name = name_var.get().strip()
try:
deposit = float(deposit_var.get().replace(',', '.'))
except ValueError:
messagebox.showerror("Fehler", "Bitte geben Sie einen gültigen Pfandbetrag ein.")
return
if not name:
messagebox.showerror("Fehler", "Bitte geben Sie einen Produktnamen ein.")
return
if name in self.products:
messagebox.showerror("Fehler", "Ein Produkt mit diesem Namen existiert bereits.")
return
image_path = image_path_var.get()
if image_path:
try:
# Create images directory if it doesn't exist
if not os.path.exists('PfandApplication/images'):
os.makedirs('images')
# Copy and rename the image
new_image_path = f"PfandApplication/images/{name.lower()}.png"
shutil.copy2(image_path, new_image_path)
except Exception as e:
messagebox.showerror("Fehler", f"Fehler beim Kopieren des Bildes: {str(e)}")
return
# Add the new product
self.products.append(name)
self.PRICES[name] = deposit
self.save_products()
# Update treeview
tree.insert('', 'end', values=(name, f"{deposit:.2f}"))
tree.yview_moveto(1) # Scroll to the bottom to show the new item
# Clear form
name_var.set("")
deposit_var.set("")
image_path_var.set("")
preview_label.configure(image='')
# Update the main window UI
self.recreate_widgets()
def delete_product():
selected = tree.selection()
if not selected:
messagebox.showwarning("Warnung", "Bitte wählen Sie ein Produkt aus.")
return
if messagebox.askyesno("Bestätigen", "Möchten Sie das ausgewählte Produkt wirklich löschen?"):
item = tree.item(selected[0])
product_name = item['values'][0]
# Remove from lists
self.products.remove(product_name)
del self.PRICES[product_name]
# Delete image if exists
image_path = f"PfandApplication/images/{product_name.lower()}.png"
if os.path.exists(image_path):
try:
os.remove(image_path)
except Exception as e:
print(f"Fehler beim Löschen des Bildes: {e}")
# Save changes and update UI
self.save_products()
self.recreate_widgets()
dialog.destroy()
# Add buttons
button_frame = ttk.Frame(right_frame)
button_frame.pack(fill='x', padx=10, pady=10)
ttk.Button(button_frame, text="Hinzufügen", command=add_product).pack(side='left', padx=5)
ttk.Button(button_frame, text="Löschen", command=delete_product).pack(side='left', padx=5)
# Bind image selection to preview update
image_path_var.trace_add('write', lambda *args: update_preview(image_path_var.get()))
def recreate_widgets(self):
# Clear existing widgets
for widget in self.root.winfo_children():
widget.destroy()
# Recreate menu
self.create_menu()
# Reload quantities
self.load_quantities()
# Recreate main widgets
self.create_widgets()
@staticmethod
def launch(check_for_update):
root = tk.Tk()
app = PfandCalculator(root)
if check_for_update == True:
root.after(1, run_silent_update) # Run uc on start (1s delay) => updater.py module || UNCOMMENT IN PROD
else:
pass
root.mainloop()
if __name__ == "__main__":
PfandCalculator.launch(True)