Compare commits

7 Commits

9 changed files with 1501 additions and 231 deletions

647
.gitignore vendored
View File

@@ -1 +1,648 @@
*.pyc *.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 import tkinter as tk
from tkinter import ttk, messagebox, filedialog from tkinter import ttk, messagebox, filedialog, font
import webbrowser import webbrowser
import json import json
from PIL import Image, ImageTk from PIL import Image, ImageTk
@@ -15,15 +16,26 @@ import queue
import numpy as np import numpy as np
import shutil 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
from PfandApplication.todo import main as todo
from PfandApplication.todo.main import todo_instance
if __name__ == "__main__":
try:
from wiki import main as wiki
from pfand_scanner import launch_pfand_scanner
from updater import open_updater as open_updater, run_silent_update
from tgtg_orderchecker import main as tgtg
from tgtg_orderchecker import setupkey as tgtg_kt
from todo.main import todo_instance
except ImportError:
print("tried to import without package name. failed, trying to import using package name now")
pass
else:
# @LocalImportStructure ; ref@todo/main.py , Element 3
from PfandApplication.wiki import main as wiki
from PfandApplication.pfand_scanner import launch_pfand_scanner
from PfandApplication.updater import open_updater as open_updater, run_silent_update
from PfandApplication.tgtg_orderchecker import main as tgtg
from PfandApplication.tgtg_orderchecker import setupkey as tgtg_kt
from PfandApplication.todo.main import todo_instance
class Achievement: class Achievement:
def __init__(self, title, description, condition_type, condition_value): def __init__(self, title, description, condition_type, condition_value):
@@ -519,32 +531,156 @@ class PfandCalculator:
close_button.grid(row=2, column=1, padx=10, pady=10, sticky="ew") close_button.grid(row=2, column=1, padx=10, pady=10, sticky="ew")
todo_button = ( 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 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): def create_todo_list(self):
todo = tk.Toplevel(self.root) todo = tk.Toplevel(self.root)
todo.title("Todo Liste") todo.title("Todo Liste")
todo.resizable(True, True) 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): for col in range(2):
todo.grid_columnconfigure(col, weight=1) todo.grid_columnconfigure(col, weight=1)
todo.grid_rowconfigure(0, weight=0) 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 # Shit will display here that is going to be loaded from external
label_todo = tk.Label( label_todo = tk.Label( todo,
todo,
text=todo_instance.load_todo(), text=todo_instance.load_todo(),
font=segoeui,
padx=10, padx=10,
pady=10, pady=10,
justify="center", justify="left",
anchor="center", 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 # TGTG Credits
def TGTG_credits(self): def TGTG_credits(self):
@@ -714,7 +850,7 @@ class PfandCalculator:
about_uscan, about_uscan,
text=( text=(
"µScan - Der bessere Barcode Scanner\n" "µ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 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" "µ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!)" "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", "Kasten",
"Dose", "Dose",
"Plastikflasche", "Plastikflasche",
"Monster",
"Joghurt Glas", "Joghurt Glas",
] ]
self.PRICES = { self.PRICES = {
@@ -1745,7 +1880,6 @@ class PfandCalculator:
"Kasten": 3.00, "Kasten": 3.00,
"Dose": 0.25, "Dose": 0.25,
"Plastikflasche": 0.25, "Plastikflasche": 0.25,
"Monster": 0.25,
"Joghurt Glas": 0.17, "Joghurt Glas": 0.17,
} }
self.save_products() self.save_products()
@@ -2093,4 +2227,4 @@ class PfandCalculator:
if __name__ == "__main__": 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 import tkinter as tk
from tkinter import ttk, simpledialog, messagebox from tkinter import ttk, simpledialog, messagebox
import cv2 import cv2
@@ -10,81 +10,328 @@ import queue
import json import json
import os import os
import time import time
import numpy as np
class PfandScanner: class PfandScanner:
def __init__(self, window, window_title): def __init__(self, window, window_title):
self.window = window self.window = window
self.window.title(window_title) self.window.title(window_title)
self.window.geometry("1920x1080") self.window.geometry("1600x900")
self.window.minsize(960, 540) self.window.minsize(1200, 700)
self.window.configure(bg='#f0f0f0')
# Configure main window grid
self.window.columnconfigure(0, weight=1) self.window.columnconfigure(0, weight=1)
self.window.rowconfigure(0, weight=1) self.window.rowconfigure(0, weight=1)
# Style configuration
self.setup_styles()
self.data_file = "quantities.json" self.data_file = "quantities.json"
self.load_json() self.products_file = "products.json"
self.load_data()
self.barcode_times = {} self.barcode_times = {}
self.prompted_barcodes = set() self.prompted_barcodes = set()
self.current_barcodes = [] # Store current frame's barcodes for outlining
self.selected_device_index = tk.IntVar(value=0) self.selected_device_index = tk.IntVar(value=0)
self.last_process_time = time.time() self.last_process_time = time.time()
# FPS Einstellung ist hier! # FPS Setting
# FPS Setting is here! self.process_interval = 0.15 # Improved processing speed
self.process_interval = 0.30 # 30 FPS
# 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_gui()
self.init_camera() self.init_camera()
self.queue = queue.Queue() self.queue = queue.Queue()
self.frame_queue = queue.Queue(maxsize=2) # Limit queue size to prevent memory issues
self.pfand_values = { self.pfand_values = {
"EINWEG": 0.25, "EINWEG": 0.25,
"MEHRWEG": 0.15, "MEHRWEG": 0.15,
"DOSE": 0.25, "DOSE": 0.25,
} }
self.update_preview() # Start threads
self.start_threads()
self.window.protocol("WM_DELETE_WINDOW", self.on_closing) 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): 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.grid(sticky="nsew")
self.main_frame.columnconfigure(0, weight=3) self.main_frame.columnconfigure(1, weight=2) # Camera gets more space
self.main_frame.columnconfigure(1, weight=1) self.main_frame.columnconfigure(0, weight=1) # Controls
self.main_frame.columnconfigure(2, weight=3) self.main_frame.columnconfigure(2, weight=1) # Info
self.main_frame.rowconfigure(0, weight=1) self.main_frame.rowconfigure(1, weight=1)
self.camera_frame = ttk.Frame(self.main_frame) # Header
self.camera_frame.grid(row=0, column=0, padx=5, pady=5, sticky="nsew") 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) title_label = ttk.Label(header_frame, text="µScan", style='Title.TLabel')
self.control_frame.grid(row=0, column=1, padx=5, pady=5, sticky="nsew") title_label.pack(side="left")
self.info_frame = ttk.Frame(self.main_frame) status_label = ttk.Label(header_frame, text="v2.3.5", style='Info.TLabel')
self.info_frame.grid(row=0, column=2, padx=5, pady=5, sticky="nsew") status_label.pack(side="right")
self.camera_label = ttk.Label(self.camera_frame) # Control Panel (Left)
self.camera_label.pack(expand=True, fill="both") 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_device_selector()
self.init_controls() self.init_controls()
self.init_treeview() self.init_treeview()
self.init_statistics()
def init_device_selector(self): def init_device_selector(self):
device_frame = ttk.LabelFrame(self.control_frame, text="Video Device") device_frame = ttk.LabelFrame(self.control_frame, text="Kamera-Einstellungen", padding="10")
device_frame.pack(pady=5, padx=5, fill="x") 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 = 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() 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.current(0)
self.device_combo.bind("<<ComboboxSelected>>", self.change_camera) 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): def list_video_devices(self, max_devices=10):
available = [] available = []
for i in range(max_devices): for i in range(max_devices):
@@ -99,65 +346,21 @@ class PfandScanner:
self.selected_device_index.set(index) self.selected_device_index.set(index)
self.init_camera() 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): def init_camera(self):
if hasattr(self, 'cap') and self.cap and self.cap.isOpened(): if hasattr(self, 'cap') and self.cap and self.cap.isOpened():
self.cap.release() self.cap.release()
device_index = self.selected_device_index.get() device_index = self.selected_device_index.get()
self.cap = cv2.VideoCapture(device_index, cv2.CAP_DSHOW if os.name == 'nt' else 0) 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_WIDTH, 1280)
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
self.cap.set(cv2.CAP_PROP_FPS, 30)
self.toggle_autofocus() self.toggle_autofocus()
self.camera_status.configure(text="Status: Verbunden ✓", foreground=self.colors['success'])
def load_json(self):
if os.path.exists(self.data_file):
with open(self.data_file, 'r') as f:
self.quantities = json.load(f)
else: else:
self.quantities = {} self.camera_status.configure(text="Status: Fehler ✗", foreground=self.colors['danger'])
def save_json(self):
with open(self.data_file, 'w') as f:
json.dump(self.quantities, f, indent=4)
def toggle_autofocus(self): def toggle_autofocus(self):
if self.cap: if self.cap:
@@ -178,8 +381,67 @@ class PfandScanner:
return cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, return cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2) 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: try:
if not hasattr(self, 'cap') or not self.cap.isOpened():
time.sleep(0.1)
continue
ret, frame = self.cap.read() ret, frame = self.cap.read()
if ret: if ret:
if not self.autofocus_var.get(): if not self.autofocus_var.get():
@@ -189,63 +451,51 @@ class PfandScanner:
if current_time - self.last_process_time >= self.process_interval: if current_time - self.last_process_time >= self.process_interval:
processed_frame = self.adjust_image(frame) processed_frame = self.adjust_image(frame)
barcodes = decode(processed_frame) or decode(frame) barcodes = decode(processed_frame) or decode(frame)
self.current_barcodes = barcodes
for barcode in barcodes: for barcode in barcodes:
barcode_data = barcode.data.decode('utf-8') barcode_data = barcode.data.decode('utf-8')
self.queue.put(barcode_data) self.queue.put(barcode_data)
self.last_process_time = current_time self.last_process_time = current_time
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA) # Draw barcode outlines
img = Image.fromarray(cv2image) frame_with_outlines = self.draw_barcode_outline(frame.copy(), self.current_barcodes)
imgtk = ImageTk.PhotoImage(image=img)
self.camera_label.imgtk = imgtk # Put frame in queue for UI thread
self.camera_label.configure(image=imgtk) 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: 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 process_worker(self):
"""Worker thread for barcode processing"""
def show_product_selection(self, barcode_data): while self.running:
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):
try: try:
barcode_data = self.queue.get(timeout=0.1) barcode_data = self.queue.get(timeout=0.1)
now = datetime.now() now = datetime.now()
# Rate limiting
timestamps = self.barcode_times.get(barcode_data, []) timestamps = self.barcode_times.get(barcode_data, [])
timestamps = [t for t in timestamps if now - t <= timedelta(seconds=5)] timestamps = [t for t in timestamps if now - t <= timedelta(seconds=5)]
if len(timestamps) >= 3: if len(timestamps) >= 3:
return continue
timestamps.append(now) timestamps.append(now)
self.barcode_times[barcode_data] = timestamps 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" 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) 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: if barcode_data not in self.prompted_barcodes:
self.prompted_barcodes.add(barcode_data) self.prompted_barcodes.add(barcode_data)
self.window.after(0, self.show_product_selection, barcode_data) self.window.after(0, self.show_product_selection, barcode_data)
@@ -253,16 +503,258 @@ class PfandScanner:
except queue.Empty: except queue.Empty:
pass pass
except Exception as e: except Exception as e:
print(f"Error in queue processing: {e}") print(f"Fehler in der Warteschlangenverarbeitung: {e}")
finally:
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) self.window.after(100, self.process_queue)
def on_closing(self): 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() self.cap.release()
cv2.destroyAllWindows()
except:
pass
self.window.destroy() self.window.destroy()
if __name__ != "__main__": if __name__ != "__main__":
def launch_pfand_scanner(): def launch_pfand_scanner():
scanner_window = tk.Toplevel() 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 # # AUTOGENERATED BY SETUPKEYTOOL V1.0.03 #
# THIS IS SENSITIVE INFORMATION # # THIS IS SENSITIVE INFORMATION #
# #
# THIS IS FILLED IN BY THE #
# GUI ASSISTANT #
# #
# @ZockerKatze/@rattatwinko # # @ZockerKatze/@rattatwinko #
# # # #
######################################### #########################################
@@ -11,4 +15,4 @@ from tgtg import TgtgClient
# Don't ever give this Info to someone you dont trust! # Don't ever give this Info to someone you dont trust!
# This is the Private Key to your TGTG Account # 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 # Create Tkinter window
root = tk.Tk() 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) title_label.grid(row=0, columnspan=2, pady=10)
# Add labels and entry fields for the tokens and cookie # 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) cookie_entry.grid(row=3, column=1)
# Submit button to process the tokens # 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) submit_button.grid(row=4, columnspan=2)
# Keep the window on top # Keep the window on top

View File

@@ -1,72 +1,65 @@
class todo: class todo:
def __init__(self): def __init__(self):
# ------------------------------------------------------------------------------------------------------------------# # ------------------------------------------------------------------------------------------------------------------#
self.todo: str = """ self.todo: str = """
TODO: TODO:
[X] main.py@433 - fix this to actually load the Todo List cause currently its dumb as fuck. [ ] main.py@GENERAL - refactor to make the ChatGPT Code actually readable.
- Do this tmrw 11.JUN.25! easy task. at least it should be. [x] main.py@TODO - Refactor and make it look nicer! PRIO: HIGH
[] main.py@GENERAL - refactor to make the ChatGPT Code actually readable. [ ] @Project_Structure - Fix this shit
[] @Project_Structure - Fix this shit - Isnt too bad , we can work with the structure.
""" """
# ------------------------------------------------------------------------------------------------------------------# # ------------------------------------------------------------------------------------------------------------------#
self.archived_todo: str = """ self.archived_todo: str = """
archived_todo list: 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. - 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 This can be for other things too, such as other Methods in Classes.
# Anyways. This function maybe needs fixing. due too toodoo bug (bug1) 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: def load_todo(self) -> str:
return self.todo 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 # toodoo bug may also reside here. idk
todo_instance = todo() todo_instance = todo()

View File

@@ -10,7 +10,7 @@ import tempfile
import traceback import traceback
import threading 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"} IGNORED_FILES = {"key.py"}
class GitHubUpdater(tk.Toplevel): class GitHubUpdater(tk.Toplevel):
@@ -49,7 +49,7 @@ class GitHubUpdater(tk.Toplevel):
header = ttk.Label(self, text="Pfand Updater", style="Header.TLabel") header = ttk.Label(self, text="Pfand Updater", style="Header.TLabel")
header.pack(pady=(20, 5)) 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.status_label.pack(pady=(0, 10))
self.frame = ttk.Frame(self) self.frame = ttk.Frame(self)
@@ -73,14 +73,14 @@ class GitHubUpdater(tk.Toplevel):
button_frame = ttk.Frame(self) button_frame = ttk.Frame(self)
button_frame.pack(pady=15) 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(side="left", padx=10)
self.back_button.pack_forget() 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.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()
self.toggle_debug_btn.pack_forget() self.toggle_debug_btn.pack_forget()
@@ -93,10 +93,10 @@ class GitHubUpdater(tk.Toplevel):
self.debug_visible = not self.debug_visible self.debug_visible = not self.debug_visible
if self.debug_visible: if self.debug_visible:
self.debug_output.pack() self.debug_output.pack()
self.toggle_debug_btn.config(text="🔽 Fehlerdetails verbergen") self.toggle_debug_btn.config(text="Fehlerdetails verbergen")
else: else:
self.debug_output.pack_forget() 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): def show_root_view(self):
self.current_view = "root" self.current_view = "root"
@@ -125,7 +125,7 @@ class GitHubUpdater(tk.Toplevel):
def check_for_updates(self): def check_for_updates(self):
try: try:
self.status_label.config(text="⬇️ Lade Update herunter...", foreground="#ffb300") self.status_label.config(text="Lade Update herunter...", foreground="#ffb300")
self.update_idletasks() self.update_idletasks()
response = requests.get(GITHUB_REPO_ZIP) response = requests.get(GITHUB_REPO_ZIP)
@@ -137,13 +137,13 @@ class GitHubUpdater(tk.Toplevel):
if self.file_differences: if self.file_differences:
self.structure = self.build_structure(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.display_structure(self.structure)
self.update_button.config(state='normal') self.update_button.config(state='normal')
else: else:
self.status_label.config(text="Alles ist aktuell", foreground="#43a047") self.status_label.config(text="Alles ist aktuell", foreground="#43a047")
except Exception: 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.toggle_debug_btn.pack()
self.debug_output.insert("1.0", traceback.format_exc()) self.debug_output.insert("1.0", traceback.format_exc())
@@ -183,7 +183,7 @@ class GitHubUpdater(tk.Toplevel):
def perform_update(self): def perform_update(self):
self.update_button.config(state='disabled') 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() self.update_idletasks()
try: try:
@@ -199,10 +199,10 @@ class GitHubUpdater(tk.Toplevel):
os.makedirs(os.path.dirname(dest_path), exist_ok=True) os.makedirs(os.path.dirname(dest_path), exist_ok=True)
shutil.copy2(src_path, dest_path) shutil.copy2(src_path, dest_path)
messagebox.showinfo("Aktualisiert", "Dateien wurden erfolgreich aktualisiert.") messagebox.showinfo("Aktualisiert", "Dateien wurden erfolgreich aktualisiert.")
self.destroy() self.destroy()
except Exception as e: except Exception as e:
messagebox.showerror("Fehler", str(e)) messagebox.showerror("Fehler", str(e))
self.toggle_debug_btn.pack() self.toggle_debug_btn.pack()
self.debug_output.insert("1.0", traceback.format_exc()) self.debug_output.insert("1.0", traceback.format_exc())
@@ -238,7 +238,7 @@ def run_silent_update(master=None):
file_differences.append(rel_path) file_differences.append(rel_path)
if file_differences: 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: if result:
updater = GitHubUpdater(master) updater = GitHubUpdater(master)
updater.grab_set() 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}}