Compare commits

7 Commits

9 changed files with 1501 additions and 231 deletions

647
.gitignore vendored
View File

@@ -1 +1,648 @@
*.pyc
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
# Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
# poetry.lock
# poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
# pdm.lock
# pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
# pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# Redis
*.rdb
*.aof
*.pid
# RabbitMQ
mnesia/
rabbitmq/
rabbitmq-data/
# ActiveMQ
activemq-data/
# SageMath parsed files
*.sage.py
# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
# .idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/
# Streamlit
.streamlit/secrets.toml
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
*.env
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
[Dd]ebug/x64/
[Dd]ebugPublic/x64/
[Rr]elease/x64/
[Rr]eleases/x64/
bin/x64/
obj/x64/
[Dd]ebug/x86/
[Dd]ebugPublic/x86/
[Rr]elease/x86/
[Rr]eleases/x86/
bin/x86/
obj/x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
[Aa][Rr][Mm]64[Ee][Cc]/
bld/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Build results on 'Bin' directories
**/[Bb]in/*
# Uncomment if you have tasks that rely on *.refresh files to move binaries
# (https://github.com/github/gitignore/pull/3736)
#!**/[Bb]in/*.refresh
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
*.trx
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Approval Tests result files
*.received.*
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.idb
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
# but not Directory.Build.rsp, as it configures directory-level build defaults
!Directory.Build.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
**/.paket/paket.exe
paket-files/
# FAKE - F# Make
**/.fake/
# CodeRush personal settings
**/.cr/personal
# Python Tools for Visual Studio (PTVS)
**/__pycache__/
*.pyc
# Cake - Uncomment if you are using it
#tools/**
#!tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
MSBuild_Logs/
# AWS SAM Build and Temporary Artifacts folder
.aws-sam
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
**/.mfractor/
# Local History for Visual Studio
**/.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
**/.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -1,5 +1,6 @@
# @ImportStructure ; ref@todo/main.py , Element 3
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
from tkinter import ttk, messagebox, filedialog, font
import webbrowser
import json
from PIL import Image, ImageTk
@@ -15,16 +16,27 @@ import queue
import numpy as np
import shutil
# Local Dependencies
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 import main as todo
from PfandApplication.todo.main import todo_instance
class Achievement:
def __init__(self, title, description, condition_type, condition_value):
self.title = title
@@ -519,32 +531,156 @@ class PfandCalculator:
close_button.grid(row=2, column=1, padx=10, pady=10, sticky="ew")
todo_button = (
tk.Button( # Looks like fucking shit , fix this horsecrap in the future
tk.Button( # fixed this in commit 5b51c670694ba813a56880eabbdb8a8f6446fa7c
about_window, text="Load Todo", command=self.create_todo_list
)
)
todo_button.grid(row=2, column=3, padx=10, pady=10, sticky="ew")
# Todo
todo_button.grid(row=3, column=0, columnspan=2, padx=10, pady=10, sticky="ew")
# ref@todo/main.py
def create_todo_list(self):
todo = tk.Toplevel(self.root)
todo.title("Todo Liste")
todo.resizable(True, True)
segoeui = font.Font(family="Segoe UI",size=11,weight="normal")
monaco = font.Font(family="Monaco", size=9, slant="italic")
seperator = ttk.Separator(todo, orient="horizontal")
seperator2 = ttk.Separator(todo, orient="horizontal")
for col in range(2):
todo.grid_columnconfigure(col, weight=1)
todo.grid_rowconfigure(0, weight=0)
def close_todo():
todo.destroy()
# Function to get done Todo Elements
# tuple[0] total, [1] done. [2] precent for pbar
def return_elements(todo_string : str) -> tuple[int, int, int]:
total = todo_string.count("[ ]") + todo_string.count("[x]")
done = todo_string.count("[x]")
percent_done = (done / total) * 100 if total > 0 else 0
return total, done, percent_done
progress_bar = ttk.Progressbar(todo, value=return_elements(todo_instance.load_todo())[2], style='Striped.Horizontal.TProgressbar')
# Shit will display here that is going to be loaded from external
label_todo = tk.Label(
todo,
label_todo = tk.Label( todo,
text=todo_instance.load_todo(),
font=segoeui,
padx=10,
pady=10,
justify="center",
justify="left",
anchor="center",
)
label_todo.grid(row=1, column=0, columnspan=2, pady=0, sticky="nsew")
label_todo_changelog = tk.Label( todo,
text=todo_instance.load_changelog(),
font=segoeui,
padx=10,
pady=10,
justify="left",
anchor="center"
)
label_todo_tldr = tk.Label( todo,
text=todo_instance.load_tldr(),
font=segoeui,
padx=10,
pady=10,
anchor="center",
justify="left"
)
percent = return_elements(todo_instance.load_todo())[2]
percent = round(percent, 2)
label_todo_percentage_bar_text = tk.Label(
todo,
text=f"{percent}% Done (For Todo)",
font=monaco,
justify="right",
anchor="w",
padx=10,
pady=0
)
label_todo_close = tk.Button( todo,
text="Schließen",
command=close_todo,
font=segoeui,
padx=10,
pady=10,
)
# setup grid
label_todo_tldr.grid(
row=1,
column=0,
columnspan=2,
padx=0,
pady=5,
sticky="nsew"
)
seperator.grid(
row=2,
column=0,
columnspan=2,
pady=5,
sticky="ew"
)
label_todo.grid(
row=3,
column=0,
columnspan=2,
pady=5,
sticky="nsew"
)
seperator2.grid(
row=4,
column=0,
columnspan=2,
pady=5,
sticky="ew"
)
label_todo_changelog.grid(
row=5,
column=0,
columnspan=2,
pady=5,
sticky="nsew"
)
label_todo_percentage_bar_text.grid(
row=6,
column=0,
columnspan=2,
sticky="ew"
)
progress_bar.grid(
row=7,
column=0,
columnspan=2,
pady=5,
sticky="ew"
)
label_todo_close.grid(
row=8,
column=0,
columnspan=2,
pady=5,
sticky="nsew"
)
# TGTG Credits
def TGTG_credits(self):
@@ -714,7 +850,7 @@ class PfandCalculator:
about_uscan,
text=(
"µScan - Der bessere Barcode Scanner\n"
"Version 2.2.2\n"
"Version 2.3.5\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"
"Beachte das µScan eine Kamera benötigt die mindestens 30FPS aufnehmen kann (Process-FPS können eingestellt werden!)"
@@ -1736,7 +1872,6 @@ class PfandCalculator:
"Kasten",
"Dose",
"Plastikflasche",
"Monster",
"Joghurt Glas",
]
self.PRICES = {
@@ -1745,7 +1880,6 @@ class PfandCalculator:
"Kasten": 3.00,
"Dose": 0.25,
"Plastikflasche": 0.25,
"Monster": 0.25,
"Joghurt Glas": 0.17,
}
self.save_products()
@@ -2093,4 +2227,4 @@ class PfandCalculator:
if __name__ == "__main__":
PfandCalculator.launch(True)
PfandCalculator.launch(False)

View File

@@ -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,81 +10,328 @@ 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)
# Threading control
self.running = True
self.camera_thread = None
self.process_thread = None
self.init_gui()
self.init_camera()
self.queue = queue.Queue()
self.frame_queue = queue.Queue(maxsize=2) # Limit queue size to prevent memory issues
self.pfand_values = {
"EINWEG": 0.25,
"MEHRWEG": 0.15,
"DOSE": 0.25,
}
self.update_preview()
# Start threads
self.start_threads()
self.window.protocol("WM_DELETE_WINDOW", self.on_closing)
self.process_queue()
def setup_styles(self):
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
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", 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="v2.3.5", 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 +346,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 +381,67 @@ class PfandScanner:
return cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2)
def update_preview(self):
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 start_threads(self):
"""Start camera and processing threads"""
self.camera_thread = threading.Thread(target=self.camera_worker, daemon=True)
self.camera_thread.start()
self.process_thread = threading.Thread(target=self.process_worker, daemon=True)
self.process_thread.start()
# Start UI update loop
self.update_preview()
self.process_queue()
def camera_worker(self):
"""Worker thread for camera capture and processing"""
while self.running:
try:
if not hasattr(self, 'cap') or not self.cap.isOpened():
time.sleep(0.1)
continue
ret, frame = self.cap.read()
if ret:
if not self.autofocus_var.get():
@@ -189,63 +451,51 @@ 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)
img = Image.fromarray(cv2image)
imgtk = ImageTk.PhotoImage(image=img)
self.camera_label.imgtk = imgtk
self.camera_label.configure(image=imgtk)
# Draw barcode outlines
frame_with_outlines = self.draw_barcode_outline(frame.copy(), self.current_barcodes)
# Put frame in queue for UI thread
try:
if self.frame_queue.full():
self.frame_queue.get_nowait() # Discard old frame
self.frame_queue.put(frame_with_outlines, timeout=0.1)
except queue.Full:
pass # Skip this frame if queue is full
except Exception as e:
print(f"Error in video preview: {e}")
print(f"Fehler in der Kamera-Verarbeitung: {e}")
time.sleep(0.1)
self.window.after(10, self.update_preview) # ~100 FPS preview
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")
ttk.Label(self.product_win, text=f"Welches Produkt soll dem Barcode '{barcode_data}' zugeordnet werden?").pack(pady=5)
selected_product = tk.StringVar()
for prod in self.quantities:
ttk.Radiobutton(self.product_win, text=prod, variable=selected_product, value=prod).pack(anchor='w')
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.")
ttk.Button(self.product_win, text="Bestätigen", command=confirm).pack(pady=5)
def process_queue(self):
def process_worker(self):
"""Worker thread for barcode processing"""
while self.running:
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:
return
continue
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}"))
# Update UI in main thread
self.window.after(0, self.add_to_treeview, current_time, barcode_data, pfand_type, deposit)
# 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 +503,258 @@ class PfandScanner:
except queue.Empty:
pass
except Exception as e:
print(f"Error in queue processing: {e}")
finally:
print(f"Fehler in der Warteschlangenverarbeitung: {e}")
def add_to_treeview(self, current_time, barcode_data, pfand_type, deposit):
"""Add item to treeview (called from main thread)"""
# 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
self.update_statistics()
# Sound notification
if self.beep_var.get():
self.window.bell()
def update_preview(self):
"""Update camera preview (called from main thread)"""
try:
# Get frame from queue
frame = self.frame_queue.get_nowait()
# Convert and display
cv2image = cv2.cvtColor(frame, 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 queue.Empty:
pass # No new frame available
except Exception as e:
print(f"Fehler in der Video-Vorschau: {e}")
# Schedule next update
if self.running:
self.window.after(30, 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("Produkt auswählen")
self.product_win.geometry("500x400")
self.product_win.minsize(450, 350)
self.product_win.resizable(True, True)
# Center the window
self.product_win.transient(self.window)
self.product_win.grab_set()
# Configure grid weights for proper expansion
self.product_win.columnconfigure(0, weight=1)
self.product_win.rowconfigure(1, weight=1)
# Header frame
header_frame = ttk.Frame(self.product_win, padding="10")
header_frame.grid(row=0, column=0, sticky="ew", padx=10, pady=(10, 5))
header_frame.columnconfigure(0, weight=1)
ttk.Label(header_frame, text="Produkt für Barcode auswählen:",
style='Heading.TLabel').grid(row=0, column=0, sticky="w")
ttk.Label(header_frame, text=f"'{barcode_data}'",
style='Info.TLabel', font=('Courier', 10)).grid(row=1, column=0, sticky="w", pady=(0, 5))
# Main content frame with scrollable area
content_frame = ttk.Frame(self.product_win)
content_frame.grid(row=1, column=0, sticky="nsew", padx=10, pady=5)
content_frame.columnconfigure(0, weight=1)
content_frame.rowconfigure(0, weight=1)
selected_product = tk.StringVar()
# Create a canvas with scrollbar for the product list
# Create a canvas with scrollbar for the product list
canvas = tk.Canvas(content_frame, bg='white', highlightthickness=0)
scrollbar = ttk.Scrollbar(content_frame, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=scrollbar.set)
# Frame inside canvas
scrollable_frame = ttk.Frame(canvas)
window_id = canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
# Update scrollregion when frame contents change
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
# Resize inner frame width when canvas resizes
def on_canvas_configure(event):
canvas.itemconfig(window_id, width=event.width)
canvas.bind("<Configure>", on_canvas_configure)
# Place canvas + scrollbar
canvas.grid(row=0, column=0, sticky="nsew")
scrollbar.grid(row=0, column=1, sticky="ns")
# Add products to scrollable frame
if self.products:
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)
product_frame.columnconfigure(1, weight=1)
ttk.Radiobutton(
product_frame,
text=product,
variable=selected_product,
value=product
).grid(row=0, column=0, sticky="w")
info_text = f"Aktuell: {current_quantity}, Preis: €{price:.2f}"
ttk.Label(product_frame, text=info_text, style='Info.TLabel').grid(row=0, column=1, sticky="e", padx=(10, 0))
else:
no_products_label = ttk.Label(scrollable_frame, text="Noch keine Produkte definiert.",
style='Info.TLabel', justify="center")
no_products_label.pack(pady=20)
# Button frame at the bottom
button_frame = ttk.Frame(self.product_win, padding="10")
button_frame.grid(row=2, column=0, sticky="ew", padx=10, pady=(5, 10))
# Configure button frame columns for proper spacing
button_frame.columnconfigure(0, weight=1)
button_frame.columnconfigure(1, weight=0)
button_frame.columnconfigure(2, weight=0)
button_frame.columnconfigure(3, weight=0)
# Helper functions for buttons
def confirm():
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 wählen Sie ein Produkt aus.")
def cancel():
self.product_win.destroy()
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 proper 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=2, padx=5)
ttk.Button(button_frame, text="Bestätigen", command=confirm).grid(row=0, column=3, padx=5)
# Bind Enter key to confirm
self.product_win.bind('<Return>', lambda e: confirm())
self.product_win.bind('<Escape>', lambda e: cancel())
# Set focus to the window
self.product_win.focus_set()
def process_queue(self):
try:
# This method is now handled by the process_worker thread
pass
except Exception as e:
print(f"Fehler in der Warteschlangenverarbeitung: {e}")
# Schedule next check
if self.running:
self.window.after(100, self.process_queue)
def on_closing(self):
if self.cap and self.cap.isOpened():
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)
if self.process_thread and self.process_thread.is_alive():
self.process_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.5")
else:
# For standalone testing
root = tk.Tk()
app = PfandScanner(root, "µScan V2.3.5")
root.mainloop()

View File

@@ -2,6 +2,10 @@
# #
# AUTOGENERATED BY SETUPKEYTOOL V1.0.03 #
# THIS IS SENSITIVE INFORMATION #
# #
# THIS IS FILLED IN BY THE #
# GUI ASSISTANT #
# #
# @ZockerKatze/@rattatwinko #
# #
#########################################
@@ -11,4 +15,4 @@ from tgtg import TgtgClient
# Don't ever give this Info to someone you dont trust!
# This is the Private Key to your TGTG Account
client = TgtgClient(access_token="asdf", refresh_token="asdf", cookie="asdf")
client = TgtgClient(access_token="", refresh_token="", cookie="")

View File

@@ -61,10 +61,10 @@ def ask_for_tokens():
# Create Tkinter window
root = tk.Tk()
root.title("Enter API Credentials")
root.title("API Kredenzen")
title_label = tk.Label(root, text="Enter your API Credentials", font=("Arial", 14, "bold"))
title_label = tk.Label(root, text="Gebe deine API Kredenzen ein!", font=("Arial", 14, "bold"))
title_label.grid(row=0, columnspan=2, pady=10)
# Add labels and entry fields for the tokens and cookie
@@ -81,7 +81,7 @@ def ask_for_tokens():
cookie_entry.grid(row=3, column=1)
# Submit button to process the tokens
submit_button = tk.Button(root, text="Submit", command=submit_tokens)
submit_button = tk.Button(root, text="Speichern", command=submit_tokens)
submit_button.grid(row=4, columnspan=2)
# Keep the window on top

View File

@@ -1,72 +1,65 @@
class todo:
def __init__(self):
# ------------------------------------------------------------------------------------------------------------------#
self.todo: str = """
TODO:
[X] main.py@433 - fix this to actually load the Todo List cause currently its dumb as fuck.
- Do this tmrw 11.JUN.25! easy task. at least it should be.
[ ] main.py@GENERAL - refactor to make the ChatGPT Code actually readable.
[x] main.py@TODO - Refactor and make it look nicer! PRIO: HIGH
[ ] @Project_Structure - Fix this shit
- Isnt too bad , we can work with the structure.
"""
# ------------------------------------------------------------------------------------------------------------------#
self.archived_todo: str = """
archived_todo list:
[X] main.py@433 - fix this to actually load the Todo List cause currently its dumb as fuck.
[X] main.py@433 - {FINISHED: 12.JUN.25@21:20} - fix this to actually load the Todo List cause currently its dumb as fuck.
- Do this tmrw 11.JUN.25! easy task. at least it should be.
[X] @ImportStructure - {FINISHED: 13.SEP.25@18:47} - Fix the Import structure, it currently looks like ass. At least the local ImportStructure.
- @LocalImportStructure => Fix by end of next week. (KW25)
"""
# ------------------------------------------------------------------------------------------------------------------#
self.pineapple = """
⠀⠀⠀⠀⠀⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⡿⠛⣷⡄⠀⠀⠀⠀⠀⢀⣀⣤⣤⣶⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠘⠻⣿⣿⢶⣤⣀⠀⠀⠀⢀⣾⠟⠀⠀⠘⢿⡆⠀⣠⣴⠿⣻⣿⠟⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠻⣷⡈⠻⣷⣄⠀⣼⡟⠀⠀⠀⠀⠸⣷⣼⠟⠁⣴⠿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢰⣤⣄⣀⠀⠘⣿⡀⠈⠻⣷⡿⠀⠀⠀⠀⠀⠀⢹⡇⠀⣸⡟⠀⠀⠀⣀⣠⣤⣶⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠉⠻⣿⣟⠿⣿⣿⡄⠀⢸⡇⠀⠀⣤⣿⣦⠀⠸⣿⢰⣿⣤⣴⠿⠛⣻⡿⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠙⢷⣄⠉⠛⢷⣼⡇⠀⣴⠟⠁⢻⣧⡀⣿⡾⠟⠁⠀⣠⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣧⠀⠀⠙⢷⣾⡟⠀⠀⠀⢻⣿⠟⠁⠀⢀⣾⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⢀⣤⣀⠀⠀⢻⣧⠀⠀⢸⣿⠀⠀⠀⠀⠀⢿⡆⠀⢀⣾⠋⠀⢐⣠⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠙⠻⣿⣦⣅⣿⡆⠀⢸⡿⠀⠀⣀⠀⠀⢸⣇⠀⣼⣯⣴⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣝⠻⣷⣄⢸⡇⢀⣾⠿⣷⡀⠘⣿⣴⠟⠋⣴⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢈⣿⣏⠉⠹⣿⣇⣾⠏⠀⠹⣧⣰⡿⠃⢀⣾⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠘⢿⣶⣤⣿⣆⠀⠘⢿⣿⠀⠀⠀⢿⡏⠀⢀⣾⣧⣶⣾⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣿⡝⠻⢷⣤⣸⡧⠀⠀⠀⢸⣧⣴⠟⢋⣴⡟⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⡄⠀⠙⠿⠷⠀⠀⠀⣿⠟⠁⢠⣾⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⡶⢿⡿⣿⠿⠿⠿⠿⠿⠿⣿⣷⠿⠾⢿⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣠⣴⠟⠋⠀⠀⣽⣏⠀⠀⠀⠀⠀⠀⣸⢷⣄⠀⠀⠉⠛⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⢀⣶⠻⡅⠀⠀⣠⡞⠁⠙⢧⠀⠀⠀⠀⡼⠃⠀⠙⢷⣄⠀⣼⠁⠙⢷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⣰⡿⠁⠀⢿⣄⡾⠋⠀⠀⠀⠈⢳⣄⢀⡾⠁⠀⠀⠀⠀⠙⣷⣏⠀⠀⠈⠻⣧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⣼⡟⠀⠀⣀⡶⢻⡀⠀⠀⠀⠀⠀⠀⣹⣿⡀⠀⠀⠀⠀⠀⢠⡟⠈⠛⢦⣀⠀⢹⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⣼⣟⠀⣠⡴⠋⠀⠀⠙⣦⠀⠀⠀⢀⡴⠋⠀⠙⢦⡀⠀⠀⣰⠟⠀⠀⠀⠀⠉⠛⣾⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⣰⣿⡽⣟⠁⠀⠀⠀⠀⠀⠈⢷⣄⡴⠋⠀⠀⠀⠀⠀⠙⢦⣴⠋⠀⠀⠀⠀⠀⠀⢰⡏⠀⢹⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⢀⣿⠃⠀⠸⣦⠀⠀⠀⠀⠀⢀⡴⠿⢧⡀⠀⠀⠀⠀⠀⢀⡼⠙⠳⢤⡀⠀⠀⠀⢠⡏⠀⠀⠈⢿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣸⡟⠀⠀⠀⠘⢧⡀⠀⢀⡴⠋⠀⠀⠈⠹⣦⡀⠀⢀⣴⠏⠁⠀⠀⠀⠙⠲⢤⣠⡏⠀⠀⠀⠀⠸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣿⠃⠀⠀⠀⠀⢈⣿⣾⠋⠀⠀⠀⠀⠀⠀⠀⠛⣦⡞⠁⠀⠀⠀⠀⠀⠀⠀⣤⠟⠓⠦⣤⣀⠀⠀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣿⠀⠀⣀⡤⠖⠋⠀⠙⢷⡀⠀⠀⠀⠀⠀⣠⠞⠁⠙⠳⣤⡀⠀⠀⠀⢀⡼⠋⠀⠀⠀⠀⠈⠙⣳⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣿⠷⣏⠉⠀⠀⠀⠀⠀⠀⠙⢦⡀⢀⣰⠞⠁⠀⠀⠀⠀⠈⠙⠶⢤⣠⠟⠀⠀⠀⠀⠀⠀⠀⣰⠏⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣿⠀⠹⣆⠀⠀⠀⠀⠀⠀⢀⣠⠟⢯⣄⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⠉⠓⢶⣄⡀⠀⠀⠀⡼⠃⠀⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⣿⡆⠀⠈⢳⡄⠀⣀⣤⠞⠋⠁⠀⠀⠉⠳⣄⡀⠀⠀⠀⣠⡞⠃⠀⠀⠀⠀⠀⠉⠛⢳⣾⣥⣀⡀⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⢸⣷⣀⣀⡤⠾⢿⣁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢳⣤⣞⠃⠀⠀⠀⠀⠀⠀⠀⢀⡴⠋⠀⠀⠀⢹⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⣿⡏⠁⠀⠀⠀⠙⢶⣀⠀⠀⠀⠀⠀⢀⣠⠶⠋⠁⠉⠳⠦⣄⣀⠀⠀⠀⣠⠟⠀⠀⠀⠀⢀⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠹⣷⡄⠀⠀⠀⠀⠀⠉⠳⣦⣀⣤⠖⠋⠁⠀⠀⠀⠀⠀⠀⠀⠉⢙⣶⢾⣥⣀⠀⠀⠀⠀⣼⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠹⣧⡀⠀⠀⠀⣠⣤⠖⠛⠙⠳⣤⡀⠀⠀⠀⠀⠀⠀⠀⢀⡴⠋⠀⠀⠀⠈⠙⠛⢳⣾⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠹⣷⣶⡛⠉⠁⠀⠀⠀⠀⠀⠀⠉⠳⠦⣄⡀⣀⡴⠞⠉⠀⠀⠀⠀⠀⠀⢀⡴⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠙⢿⣙⢦⣄⡀⠀⠀⠀⠀⠀⠀⢀⣀⡼⠛⠛⠶⢤⣀⣀⠀⠀⠀⣀⡴⢋⣼⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠈⠻⣦⡈⠙⢦⣄⣀⣠⡴⠚⠋⠁⠀⠀⠀⠀⠀⠈⠉⢛⡶⠾⣧⣴⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠈⠻⣷⣾⡛⠙⠳⢦⣄⡀⠀⠀⠀⠀⢀⣠⡴⠚⢉⣠⣴⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠻⠷⣦⣄⣀⣉⣻⣶⢴⣞⣋⣠⣴⡶⠟⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠛⠛⠛⠉⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
self.changelog: str = """
Changelog:
13.SEP.25@18:49 => Fixed µScan.
- Fixed the UI, made it look better and made the Scanner run better.
- Mainly OpenCV as a C extern
- Tkinter pulls it together.
- Its Threaded, so it runs better.
Fixed Todo Widget to look better. You can see @main:PfandCalculator/create_todo_list()
- Now has a progressbar which counts the elements in the Todo variable, if you check one off [x]. then it counts as finished and the
progressbar advances! main.py@PfandCalculator/create_todo_list/return_elements(str) -> tuple[int, int, int]
Third returned Element being The percent out of 100, which is done.
12.JUN.25@21:23 => Changed the Todo List to be much more readable.
12.JUN.25@21:20 => Fixed Todo List Load Bug. Now loads properly. It was a import issue.
"""
# ------------------------------------------------------------------------------------------------------------------#
self.tldr : str = """
Quick TLDR:
If you see any "ref" in the main.py file you are probably looking at a reference to a certain file.
example: ref@todo/main.py , element int(x)
This is refering to the todo list with element of X
# This Project is certainly older than mucapy , But still i declare types now. in todo code xD
# Anyways. This function maybe needs fixing. due too toodoo bug (bug1)
This can be for other things too, such as other Methods in Classes.
example: ref@tgtg_orderchecker/main.py@method/class/variable
This is refering to some object.
if you see any "@" , you are probably seeing a reference to the todo list.
for example element 3 in todo (@LocalImportStructure)
"""
# ---------------------------------------------------------------------------------------------------------------------#
# Methods to return the things we want.
def load_todo(self) -> str:
return self.todo
def load_changelog(self) -> str:
return self.changelog
def load_tldr(self) -> str:
return self.tldr
# toodoo bug may also reside here. idk
todo_instance = todo()

View File

@@ -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()

View File

@@ -1 +1 @@
{"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", "Joghurt Glas"], "prices": {"Flaschen": 0.25, "Bierflasche": 0.2, "Kasten": 3.0, "Dose": 0.25, "Plastikflasche": 0.25, "Joghurt Glas": 0.17}}