Fix Windows browse, status label mapping, and repo cleanup
This commit is contained in:
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
# Build artifacts
|
# Build artifacts
|
||||||
build/
|
build/
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
# Python cache
|
# Python cache
|
||||||
__pycache__/
|
__pycache__/
|
||||||
@@ -17,6 +18,17 @@ ENV/
|
|||||||
label/
|
label/
|
||||||
logs/
|
logs/
|
||||||
pdf_backup/
|
pdf_backup/
|
||||||
|
=======
|
||||||
|
dist/
|
||||||
|
logs/
|
||||||
|
pdf_backup/
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
>>>>>>> 747656e (Fix Windows browse, status label mapping, and repo cleanup)
|
||||||
|
|
||||||
# User-specific configuration
|
# User-specific configuration
|
||||||
conf/app.conf
|
conf/app.conf
|
||||||
|
|||||||
BIN
dist/LabelPrinter.exe
vendored
BIN
dist/LabelPrinter.exe
vendored
Binary file not shown.
@@ -24,6 +24,7 @@ import time
|
|||||||
import datetime
|
import datetime
|
||||||
import glob
|
import glob
|
||||||
import configparser
|
import configparser
|
||||||
|
import subprocess
|
||||||
from print_label import print_label_standalone, get_available_printers
|
from print_label import print_label_standalone, get_available_printers
|
||||||
from kivy.clock import Clock
|
from kivy.clock import Clock
|
||||||
from watchdog.observers import Observer
|
from watchdog.observers import Observer
|
||||||
@@ -645,6 +646,14 @@ printer = PDF
|
|||||||
|
|
||||||
def browse_file(self, instance):
|
def browse_file(self, instance):
|
||||||
"""Open file browser to select file to monitor"""
|
"""Open file browser to select file to monitor"""
|
||||||
|
# On Windows, use native dialog to avoid missing-library issues with Kivy FileChooser
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
selected_file = self._browse_file_windows_native()
|
||||||
|
if selected_file:
|
||||||
|
self.file_input.text = selected_file
|
||||||
|
self.save_config()
|
||||||
|
return
|
||||||
|
|
||||||
content = BoxLayout(orientation='vertical', spacing=10, padding=10)
|
content = BoxLayout(orientation='vertical', spacing=10, padding=10)
|
||||||
|
|
||||||
# File chooser
|
# File chooser
|
||||||
@@ -680,6 +689,79 @@ printer = PDF
|
|||||||
|
|
||||||
content.add_widget(buttons)
|
content.add_widget(buttons)
|
||||||
popup.open()
|
popup.open()
|
||||||
|
|
||||||
|
def _browse_file_windows_native(self):
|
||||||
|
"""Open a native Windows file dialog via PowerShell/.NET and return selected file path."""
|
||||||
|
ps_script = (
|
||||||
|
"Add-Type -AssemblyName System.Windows.Forms; "
|
||||||
|
"$dialog = New-Object System.Windows.Forms.OpenFileDialog; "
|
||||||
|
"$dialog.Title = 'Select File to Monitor'; "
|
||||||
|
"$dialog.Filter = 'Text files (*.txt)|*.txt|All files (*.*)|*.*'; "
|
||||||
|
"$dialog.Multiselect = $false; "
|
||||||
|
"if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { "
|
||||||
|
"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; "
|
||||||
|
"Write-Output $dialog.FileName "
|
||||||
|
"}"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
creationflags = getattr(subprocess, 'CREATE_NO_WINDOW', 0)
|
||||||
|
result = subprocess.run(
|
||||||
|
['powershell', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', ps_script],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
creationflags=creationflags
|
||||||
|
)
|
||||||
|
|
||||||
|
selected = result.stdout.strip()
|
||||||
|
if selected and os.path.isfile(selected):
|
||||||
|
return selected
|
||||||
|
|
||||||
|
if result.stderr.strip():
|
||||||
|
print(f"Windows file dialog error: {result.stderr.strip()}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Native Windows browse failed: {e}")
|
||||||
|
|
||||||
|
# Fallback: manual path entry popup (no external dependencies)
|
||||||
|
self.show_manual_path_popup()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def show_manual_path_popup(self):
|
||||||
|
"""Fallback popup for manually entering monitored file path."""
|
||||||
|
content = BoxLayout(orientation='vertical', spacing=8, padding=10)
|
||||||
|
|
||||||
|
input_box = TextInput(
|
||||||
|
text=self.file_input.text.strip(),
|
||||||
|
multiline=False,
|
||||||
|
hint_text='Enter full file path, e.g. C:\\Users\\Public\\Documents\\check.txt'
|
||||||
|
)
|
||||||
|
content.add_widget(Label(text='Browse unavailable. Enter file path manually:'))
|
||||||
|
content.add_widget(input_box)
|
||||||
|
|
||||||
|
buttons = BoxLayout(size_hint_y=0.35, spacing=8)
|
||||||
|
popup = Popup(title='Set Monitor File', content=content, size_hint=(0.9, 0.4))
|
||||||
|
|
||||||
|
def apply_path(_instance):
|
||||||
|
path = input_box.text.strip()
|
||||||
|
if path and os.path.isfile(path):
|
||||||
|
self.file_input.text = path
|
||||||
|
self.save_config()
|
||||||
|
popup.dismiss()
|
||||||
|
self.show_popup('Success', f'File set:\n{path}', auto_dismiss_after=2)
|
||||||
|
else:
|
||||||
|
self.show_popup('Error', 'Invalid file path', auto_dismiss_after=3)
|
||||||
|
|
||||||
|
ok_btn = Button(text='Apply')
|
||||||
|
ok_btn.bind(on_press=apply_path)
|
||||||
|
buttons.add_widget(ok_btn)
|
||||||
|
|
||||||
|
cancel_btn = Button(text='Cancel')
|
||||||
|
cancel_btn.bind(on_press=popup.dismiss)
|
||||||
|
buttons.add_widget(cancel_btn)
|
||||||
|
|
||||||
|
content.add_widget(buttons)
|
||||||
|
popup.open()
|
||||||
|
|
||||||
def toggle_monitoring(self, instance):
|
def toggle_monitoring(self, instance):
|
||||||
"""Start or stop file monitoring"""
|
"""Start or stop file monitoring"""
|
||||||
@@ -767,12 +849,12 @@ printer = PDF
|
|||||||
|
|
||||||
def read_file_variables(self):
|
def read_file_variables(self):
|
||||||
"""Read variables from monitored file
|
"""Read variables from monitored file
|
||||||
Expected format: article;nr_art;serial;template_type;count
|
Expected format: article;nr_art;serial;status;count
|
||||||
template_type: 0=OK, 1=NOK
|
status: 1=OK, 0=NOK
|
||||||
count: number of labels to print (default=1)
|
count: number of labels to print (default=1)
|
||||||
"""
|
"""
|
||||||
if not self.monitored_file or not os.path.exists(self.monitored_file):
|
if not self.monitored_file or not os.path.exists(self.monitored_file):
|
||||||
return None, None, None, 0, 1
|
return None, None, None, '1', 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(self.monitored_file, 'r', encoding='utf-8') as f:
|
with open(self.monitored_file, 'r', encoding='utf-8') as f:
|
||||||
@@ -780,7 +862,7 @@ printer = PDF
|
|||||||
|
|
||||||
# Skip if file is empty or only contains "-" (cleared marker)
|
# Skip if file is empty or only contains "-" (cleared marker)
|
||||||
if not content or content == '-':
|
if not content or content == '-':
|
||||||
return None, None, None, 0, 1
|
return None, None, None, '1', 1
|
||||||
|
|
||||||
# Parse file content - expecting format: article;nr_art;serial;template_type;count
|
# Parse file content - expecting format: article;nr_art;serial;template_type;count
|
||||||
# or key=value pairs on separate lines
|
# or key=value pairs on separate lines
|
||||||
@@ -788,7 +870,7 @@ printer = PDF
|
|||||||
article = ""
|
article = ""
|
||||||
nr_art = ""
|
nr_art = ""
|
||||||
serial = ""
|
serial = ""
|
||||||
template_type = 0 # Default to OK template
|
status_flag = "1"
|
||||||
count = 1 # Default to 1 label
|
count = 1 # Default to 1 label
|
||||||
|
|
||||||
# Try semicolon-separated format first
|
# Try semicolon-separated format first
|
||||||
@@ -801,10 +883,7 @@ printer = PDF
|
|||||||
if len(parts) >= 3:
|
if len(parts) >= 3:
|
||||||
serial = parts[2].strip()
|
serial = parts[2].strip()
|
||||||
if len(parts) >= 4:
|
if len(parts) >= 4:
|
||||||
try:
|
status_flag = parts[3].strip()
|
||||||
template_type = int(parts[3].strip())
|
|
||||||
except ValueError:
|
|
||||||
template_type = 0
|
|
||||||
if len(parts) >= 5:
|
if len(parts) >= 5:
|
||||||
try:
|
try:
|
||||||
count = int(parts[4].strip())
|
count = int(parts[4].strip())
|
||||||
@@ -828,11 +907,8 @@ printer = PDF
|
|||||||
nr_art = value
|
nr_art = value
|
||||||
elif key in ['serial', 'serial_no', 'serial-no', 'serialno']:
|
elif key in ['serial', 'serial_no', 'serial-no', 'serialno']:
|
||||||
serial = value
|
serial = value
|
||||||
elif key in ['template', 'template_type', 'type']:
|
elif key in ['status', 'result', 'quality', 'ok', 'is_ok', 'nok']:
|
||||||
try:
|
status_flag = value
|
||||||
template_type = int(value)
|
|
||||||
except ValueError:
|
|
||||||
template_type = 0
|
|
||||||
elif key in ['count', 'quantity', 'copies']:
|
elif key in ['count', 'quantity', 'copies']:
|
||||||
try:
|
try:
|
||||||
count = int(value)
|
count = int(value)
|
||||||
@@ -842,16 +918,23 @@ printer = PDF
|
|||||||
count = 100
|
count = 100
|
||||||
except ValueError:
|
except ValueError:
|
||||||
count = 1
|
count = 1
|
||||||
|
|
||||||
|
# Normalize status flag: 1 = OK label, 0 = NOK label
|
||||||
|
normalized_status = str(status_flag).strip().lower()
|
||||||
|
if normalized_status in ['0', 'nok', 'no', 'false', 'rejected', 'refused', 'fail']:
|
||||||
|
status_flag = '0'
|
||||||
|
else:
|
||||||
|
status_flag = '1'
|
||||||
|
|
||||||
return article, nr_art, serial, template_type, count
|
return article, nr_art, serial, status_flag, count
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error reading file: {e}")
|
print(f"Error reading file: {e}")
|
||||||
return None, None, None
|
return None, None, None, '1', 1
|
||||||
|
|
||||||
def print_label(self, instance):
|
def print_label(self, instance):
|
||||||
"""Handle print button press - read from file and print"""
|
"""Handle print button press - read from file and print"""
|
||||||
# Read variables from file including template type and count
|
# Read variables from file including status and count
|
||||||
article, nr_art, serial, template_type, count = self.read_file_variables()
|
article, nr_art, serial, status_flag, count = self.read_file_variables()
|
||||||
|
|
||||||
# Resolve display name to full printer name
|
# Resolve display name to full printer name
|
||||||
printer = self._get_full_printer_name(self.printer_spinner.text)
|
printer = self._get_full_printer_name(self.printer_spinner.text)
|
||||||
@@ -861,8 +944,8 @@ printer = PDF
|
|||||||
self.show_popup("Error", "No data in file or file not set", auto_dismiss_after=3)
|
self.show_popup("Error", "No data in file or file not set", auto_dismiss_after=3)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Select template based on template_type
|
# Select template based on status_flag (1=OK, 0=NOK)
|
||||||
if template_type == 1:
|
if status_flag == '0':
|
||||||
template_path = os.path.join('conf', 'label_template_nok.svg')
|
template_path = os.path.join('conf', 'label_template_nok.svg')
|
||||||
template_name = "NOK"
|
template_name = "NOK"
|
||||||
else:
|
else:
|
||||||
@@ -875,7 +958,8 @@ printer = PDF
|
|||||||
template_name = "DEFAULT"
|
template_name = "DEFAULT"
|
||||||
|
|
||||||
# Create combined label text using semicolon separator
|
# Create combined label text using semicolon separator
|
||||||
label_text = f"{article};{nr_art};{serial}"
|
# status_flag: 1 = OK label, 0 = NOK label
|
||||||
|
label_text = f"{article};{nr_art};{serial};{status_flag}"
|
||||||
|
|
||||||
# Show loading popup with count info
|
# Show loading popup with count info
|
||||||
popup = Popup(
|
popup = Popup(
|
||||||
|
|||||||
113
print_label.py
113
print_label.py
@@ -208,7 +208,8 @@ def create_label_pdf(text, svg_template=None):
|
|||||||
PDFs are saved to the pdf_backup folder.
|
PDFs are saved to the pdf_backup folder.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
text (str): Combined text in format "article;nr_art;serial" or single value
|
text (str): Combined text in format "article;nr_art;serial;status" or single value
|
||||||
|
status: 1 = OK label, 0 = NOK label
|
||||||
svg_template (str): Path to specific SVG template to use (optional)
|
svg_template (str): Path to specific SVG template to use (optional)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -219,6 +220,7 @@ def create_label_pdf(text, svg_template=None):
|
|||||||
article = parts[0].strip() if len(parts) > 0 else ''
|
article = parts[0].strip() if len(parts) > 0 else ''
|
||||||
nr_art = parts[1].strip() if len(parts) > 1 else ''
|
nr_art = parts[1].strip() if len(parts) > 1 else ''
|
||||||
serial = parts[2].strip() if len(parts) > 2 else ''
|
serial = parts[2].strip() if len(parts) > 2 else ''
|
||||||
|
status_flag = parts[3].strip() if len(parts) > 3 else '1'
|
||||||
|
|
||||||
# Create PDF using high-quality generator
|
# Create PDF using high-quality generator
|
||||||
generator = PDFLabelGenerator()
|
generator = PDFLabelGenerator()
|
||||||
@@ -230,22 +232,31 @@ def create_label_pdf(text, svg_template=None):
|
|||||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
pdf_filename = os.path.join(pdf_backup_dir, f"final_label_{timestamp}.pdf")
|
pdf_filename = os.path.join(pdf_backup_dir, f"final_label_{timestamp}.pdf")
|
||||||
|
|
||||||
# Use SVG template for customizable layout
|
# Select template/image based on status flag
|
||||||
if svg_template is None or not os.path.exists(svg_template):
|
# 1 = OK label, 0 = NOK label
|
||||||
# Try default templates
|
selected_template = svg_template if svg_template and os.path.exists(svg_template) else None
|
||||||
default_svg = os.path.join('conf', 'label_template.svg')
|
default_svg = os.path.join('conf', 'label_template.svg')
|
||||||
if os.path.exists(default_svg):
|
ok_svg = os.path.join('conf', 'label_template_ok.svg')
|
||||||
svg_template = default_svg
|
nok_svg = os.path.join('conf', 'label_template_nok.svg')
|
||||||
print(f"Using SVG template: {default_svg}")
|
|
||||||
else:
|
if selected_template:
|
||||||
print("SVG template not found, using fallback PDF generation")
|
image_path = os.path.join('conf', 'accepted.png') if status_flag != '0' else os.path.join('conf', 'refused.png')
|
||||||
|
elif status_flag == '0':
|
||||||
|
# NOK label: prefer dedicated NOK SVG template, otherwise use refused image in standard layout
|
||||||
|
if os.path.exists(nok_svg):
|
||||||
|
selected_template = nok_svg
|
||||||
|
elif os.path.exists(default_svg):
|
||||||
|
selected_template = default_svg
|
||||||
|
image_path = os.path.join('conf', 'refused.png')
|
||||||
else:
|
else:
|
||||||
print(f"Using SVG template: {svg_template}")
|
# OK label (default): prefer dedicated OK SVG template, fallback to default SVG template
|
||||||
|
if os.path.exists(ok_svg):
|
||||||
|
selected_template = ok_svg
|
||||||
|
elif os.path.exists(default_svg):
|
||||||
|
selected_template = default_svg
|
||||||
|
image_path = os.path.join('conf', 'accepted.png')
|
||||||
|
|
||||||
# Check for default image path
|
return generator.create_label_pdf(article, nr_art, serial, pdf_filename, image_path, selected_template)
|
||||||
image_path = os.path.join('conf', 'accepted.png')
|
|
||||||
|
|
||||||
return generator.create_label_pdf(article, nr_art, serial, pdf_filename, image_path, svg_template)
|
|
||||||
|
|
||||||
|
|
||||||
def configure_printer_quality(printer_name, width_mm=35, height_mm=25):
|
def configure_printer_quality(printer_name, width_mm=35, height_mm=25):
|
||||||
@@ -423,75 +434,11 @@ def print_to_printer(printer_name, file_path):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"SumatraPDF error: {e}")
|
print(f"SumatraPDF error: {e}")
|
||||||
|
|
||||||
# Method 2: Adobe Reader silent printing
|
# Do not launch default PDF viewers (Adobe/Edge/etc.) as fallback.
|
||||||
if not printed:
|
if not printed:
|
||||||
adobe_path = None
|
print("SumatraPDF not found or failed. PDF saved as backup only (no viewer launched).")
|
||||||
for key_path in [
|
return False
|
||||||
r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\AcroRd32.exe",
|
|
||||||
r"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\Acrobat.exe"
|
|
||||||
]:
|
|
||||||
try:
|
|
||||||
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key_path)
|
|
||||||
adobe_path, _ = winreg.QueryValueEx(key, "")
|
|
||||||
winreg.CloseKey(key)
|
|
||||||
break
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if adobe_path and os.path.exists(adobe_path):
|
|
||||||
try:
|
|
||||||
subprocess.run([
|
|
||||||
adobe_path,
|
|
||||||
"/t", # Print and close
|
|
||||||
file_path,
|
|
||||||
printer_name
|
|
||||||
], check=False, creationflags=subprocess.CREATE_NO_WINDOW)
|
|
||||||
print(f"Label sent to printer via Adobe Reader: {printer_name}")
|
|
||||||
printed = True
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Method 3: GhostScript (if installed)
|
|
||||||
if not printed:
|
|
||||||
gs_paths = [
|
|
||||||
r"C:\Program Files\gs\gs10.02.1\bin\gswin64c.exe",
|
|
||||||
r"C:\Program Files (x86)\gs\gs10.02.1\bin\gswin32c.exe",
|
|
||||||
]
|
|
||||||
# Try to find gswin in PATH
|
|
||||||
try:
|
|
||||||
gs_result = subprocess.run(['where', 'gswin64c'],
|
|
||||||
capture_output=True, text=True, check=False)
|
|
||||||
if gs_result.returncode == 0:
|
|
||||||
gs_paths.insert(0, gs_result.stdout.strip().split('\n')[0])
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
for gs_path in gs_paths:
|
|
||||||
if os.path.exists(gs_path):
|
|
||||||
try:
|
|
||||||
subprocess.run([
|
|
||||||
gs_path,
|
|
||||||
"-dNOPAUSE", "-dBATCH", "-dQUIET",
|
|
||||||
f"-sDEVICE=mswinpr2",
|
|
||||||
f"-sOutputFile=%printer%{printer_name}",
|
|
||||||
file_path
|
|
||||||
], check=False, creationflags=subprocess.CREATE_NO_WINDOW)
|
|
||||||
print(f"Label sent to printer via GhostScript: {printer_name}")
|
|
||||||
printed = True
|
|
||||||
break
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not printed:
|
|
||||||
# Fallback: Let user know and save PDF
|
|
||||||
print("=" * 60)
|
|
||||||
print("NOTICE: Silent PDF printing requires SumatraPDF")
|
|
||||||
print("SumatraPDF not found (should be bundled inside the app)")
|
|
||||||
print("If you built the app yourself, ensure SumatraPDF.exe is downloaded first.")
|
|
||||||
print("=" * 60)
|
|
||||||
print(f"PDF saved to: {file_path}")
|
|
||||||
print("The PDF can be printed manually.")
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
# Non-PDF files
|
# Non-PDF files
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
COM-2024-002;ART-67890;SN-20260212;0;1
|
COM-2024-002;ART-67890;SN-20260212;1;1
|
||||||
|
|||||||
Reference in New Issue
Block a user