diff --git a/.gitignore b/.gitignore index bc7f1b2..f8cf2ae 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Build artifacts build/ +<<<<<<< HEAD # Python cache __pycache__/ @@ -17,6 +18,17 @@ ENV/ label/ logs/ 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 conf/app.conf diff --git a/dist/LabelPrinter.exe b/dist/LabelPrinter.exe deleted file mode 100644 index 6ed2f09..0000000 Binary files a/dist/LabelPrinter.exe and /dev/null differ diff --git a/label_printer_gui.py b/label_printer_gui.py index 8fa91e1..f0087b5 100644 --- a/label_printer_gui.py +++ b/label_printer_gui.py @@ -24,6 +24,7 @@ import time import datetime import glob import configparser +import subprocess from print_label import print_label_standalone, get_available_printers from kivy.clock import Clock from watchdog.observers import Observer @@ -645,6 +646,14 @@ printer = PDF def browse_file(self, instance): """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) # File chooser @@ -680,6 +689,79 @@ printer = PDF content.add_widget(buttons) 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): """Start or stop file monitoring""" @@ -767,12 +849,12 @@ printer = PDF def read_file_variables(self): """Read variables from monitored file - Expected format: article;nr_art;serial;template_type;count - template_type: 0=OK, 1=NOK + Expected format: article;nr_art;serial;status;count + status: 1=OK, 0=NOK count: number of labels to print (default=1) """ 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: 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) 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 # or key=value pairs on separate lines @@ -788,7 +870,7 @@ printer = PDF article = "" nr_art = "" serial = "" - template_type = 0 # Default to OK template + status_flag = "1" count = 1 # Default to 1 label # Try semicolon-separated format first @@ -801,10 +883,7 @@ printer = PDF if len(parts) >= 3: serial = parts[2].strip() if len(parts) >= 4: - try: - template_type = int(parts[3].strip()) - except ValueError: - template_type = 0 + status_flag = parts[3].strip() if len(parts) >= 5: try: count = int(parts[4].strip()) @@ -828,11 +907,8 @@ printer = PDF nr_art = value elif key in ['serial', 'serial_no', 'serial-no', 'serialno']: serial = value - elif key in ['template', 'template_type', 'type']: - try: - template_type = int(value) - except ValueError: - template_type = 0 + elif key in ['status', 'result', 'quality', 'ok', 'is_ok', 'nok']: + status_flag = value elif key in ['count', 'quantity', 'copies']: try: count = int(value) @@ -842,16 +918,23 @@ printer = PDF count = 100 except ValueError: 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: print(f"Error reading file: {e}") - return None, None, None + return None, None, None, '1', 1 def print_label(self, instance): """Handle print button press - read from file and print""" - # Read variables from file including template type and count - article, nr_art, serial, template_type, count = self.read_file_variables() + # Read variables from file including status and count + article, nr_art, serial, status_flag, count = self.read_file_variables() # Resolve display name to full printer name 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) return - # Select template based on template_type - if template_type == 1: + # Select template based on status_flag (1=OK, 0=NOK) + if status_flag == '0': template_path = os.path.join('conf', 'label_template_nok.svg') template_name = "NOK" else: @@ -875,7 +958,8 @@ printer = PDF template_name = "DEFAULT" # 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 popup = Popup( diff --git a/print_label.py b/print_label.py index 7e8d57f..2aacbb9 100644 --- a/print_label.py +++ b/print_label.py @@ -208,7 +208,8 @@ def create_label_pdf(text, svg_template=None): PDFs are saved to the pdf_backup folder. 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) Returns: @@ -219,6 +220,7 @@ def create_label_pdf(text, svg_template=None): article = parts[0].strip() if len(parts) > 0 else '' nr_art = parts[1].strip() if len(parts) > 1 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 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") pdf_filename = os.path.join(pdf_backup_dir, f"final_label_{timestamp}.pdf") - # Use SVG template for customizable layout - if svg_template is None or not os.path.exists(svg_template): - # Try default templates - default_svg = os.path.join('conf', 'label_template.svg') - if os.path.exists(default_svg): - svg_template = default_svg - print(f"Using SVG template: {default_svg}") - else: - print("SVG template not found, using fallback PDF generation") + # Select template/image based on status flag + # 1 = OK label, 0 = NOK label + selected_template = svg_template if svg_template and os.path.exists(svg_template) else None + default_svg = os.path.join('conf', 'label_template.svg') + ok_svg = os.path.join('conf', 'label_template_ok.svg') + nok_svg = os.path.join('conf', 'label_template_nok.svg') + + if selected_template: + 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: - 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 - image_path = os.path.join('conf', 'accepted.png') - - return generator.create_label_pdf(article, nr_art, serial, pdf_filename, image_path, svg_template) + return generator.create_label_pdf(article, nr_art, serial, pdf_filename, image_path, selected_template) 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: 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: - adobe_path = None - for key_path in [ - 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.") - + print("SumatraPDF not found or failed. PDF saved as backup only (no viewer launched).") + return False + return True else: # Non-PDF files diff --git a/sample_data.txt b/sample_data.txt index 8cf5a60..f8a2622 100644 --- a/sample_data.txt +++ b/sample_data.txt @@ -1 +1 @@ -COM-2024-002;ART-67890;SN-20260212;0;1 \ No newline at end of file +COM-2024-002;ART-67890;SN-20260212;1;1