Improve GUI and barcode generation

- Update GUI title to 'Label Printing' with white text
- Add character limit (25 chars) to all input fields
- Add number-only filter to quantity field
- Fix barcode generation in PDF module
- Create pdf_backup folder for storing generated PDFs
- Add pdf backup logging and confirmation
- Move demo files and tests to documentation folder
- Reorganize project structure for better clarity
This commit is contained in:
Quality App Developer
2026-02-05 01:02:53 +02:00
parent a79fdf3759
commit 33c9c3d099
1516 changed files with 2532 additions and 275024 deletions

View File

@@ -1,6 +1,6 @@
"""
Label Printer GUI Application using Kivy
This application provides a user-friendly interface for printing labels with barcodes.
Simplified mobile-friendly interface for printing labels.
"""
from kivy.app import App
@@ -14,168 +14,32 @@ from kivy.uix.scrollview import ScrollView
from kivy.uix.popup import Popup
from kivy.core.window import Window
from kivy.uix.image import Image as KivyImage
from kivy.uix.widget import Widget
from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg
from kivy.graphics import Color, Rectangle
from kivy.uix.scatterlayout import ScatterLayout
from PIL import Image as PILImage
import cups
import io
import os
import threading
import tempfile
import time
from print_label import create_label_image, print_label_standalone
from print_label import print_label_standalone
from kivy.clock import Clock
# Set window size
Window.size = (1600, 900)
# Set window size - portrait/phone dimensions (375x667 like iPhone)
# Adjusted to be slightly wider for touch-friendly UI
Window.size = (420, 700)
class LabelPreviewWidget(BoxLayout):
"""Widget for displaying the warehouse identification label preview"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation = 'vertical'
self.temp_file_path = None
def update_preview(self, sap_nr, cantitate, lot_number):
"""Update preview with label and barcode information"""
try:
# Create label with article info
label_text = f"{sap_nr}|{cantitate}|{lot_number}"
self.generate_and_display_preview(sap_nr, cantitate, lot_number, label_text)
except Exception as e:
print(f"Error updating preview: {e}")
def generate_and_display_preview(self, sap_nr, cantitate, lot_number, barcode_text):
"""Generate preview showing alternating text and barcode rows"""
from PIL import Image, ImageDraw, ImageFont
import barcode
from barcode.writer import ImageWriter
# Canvas dimensions (11.5cm x 8cm) * 1.5 = 17.25cm x 12cm
# At 96 DPI: 11.5cm ≈ 435px, 8cm ≈ 303px
# Increased by 50%: 652px x 454px
canvas_width = 650
canvas_height = 450
# Left margin: 0.5cm ≈ 19px * 1.5 = 28px
left_margin = 28
usable_width = canvas_width - left_margin
# Create canvas
canvas = Image.new('RGB', (canvas_width, canvas_height), 'white')
draw = ImageDraw.Draw(canvas)
# 6 rows total
row_height = canvas_height // 6
# Font for text (larger for bigger canvas)
try:
label_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 20)
value_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 16)
except:
label_font = ImageFont.load_default()
value_font = ImageFont.load_default()
# Row definitions: (label_text, barcode_value, row_index)
rows_data = [
("SAP-Nr. Article", sap_nr, 0),
(None, sap_nr, 1), # Barcode row
("Cantitate", cantitate, 2),
(None, cantitate, 3), # Barcode row
("ID rola cablu", lot_number, 4),
(None, lot_number, 5), # Barcode row
]
for label_text, value, row_idx in rows_data:
row_y = row_idx * row_height
# Draw row border
draw.rectangle(
[(0, row_y), (canvas_width, row_y + row_height)],
outline='black',
width=1
)
if label_text:
# Text row: show label and value
draw.text(
(left_margin + 8, row_y + 8),
f"{label_text}: {value if value else '(empty)'}",
fill='black',
font=label_font
)
else:
# Barcode row
if value and value.strip():
try:
# Limit barcode text to 25 characters
barcode_value = str(value)[:25]
CODE128 = barcode.get_barcode_class('code128')
writer_options = {
"write_text": False,
"module_width": 0.5,
"module_height": 12, # Smaller height for better fit
"quiet_zone": 2,
"font_size": 0
}
code = CODE128(barcode_value, writer=ImageWriter())
filename = code.save('label_barcode_tmp', options=writer_options)
barcode_img = Image.open(filename)
# Use standard barcode size, don't resize to fit width
canvas.paste(barcode_img, (left_margin + 4, row_y + 12))
except Exception as e:
print(f"Barcode error: {e}")
draw.text(
(left_margin + 8, row_y + row_height // 2 - 10),
"Barcode: " + str(value)[:25],
fill='black',
font=value_font
)
else:
draw.text(
(left_margin + 8, row_y + row_height // 2 - 10),
"(no data)",
fill='gray',
font=value_font
)
# Save and display
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp:
canvas.save(tmp.name)
# Clean up old file
if self.temp_file_path and os.path.exists(self.temp_file_path):
try:
os.remove(self.temp_file_path)
except:
pass
self.temp_file_path = tmp.name
# Update display
self.clear_widgets()
img_widget = KivyImage(source=tmp.name, size_hint=(1, 1))
self.add_widget(img_widget)
class LabelPrinterApp(App):
"""Main Kivy application for label printing"""
"""Simplified Kivy application for label printing"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.available_printers = self.get_available_printers()
self.preview_widget = None
def get_available_printers(self):
"""Get list of available printers from CUPS"""
try:
import cups
conn = cups.Connection()
printers = conn.getPrinters()
return list(printers.keys()) if printers else ["PDF"]
@@ -184,138 +48,138 @@ class LabelPrinterApp(App):
return ["PDF"]
def build(self):
"""Build the main UI"""
self.title = "Label Printer Interface"
"""Build the simplified single-column UI"""
self.title = "Label Printing"
# Main layout - horizontal split between input and preview
main_layout = BoxLayout(orientation='horizontal', spacing=10, padding=10)
# Left column - Input form
left_column = self.create_input_column()
# Right column - Preview
right_column = self.create_preview_column()
main_layout.add_widget(left_column)
main_layout.add_widget(right_column)
return main_layout
def create_input_column(self):
"""Create the left column with input fields"""
container = BoxLayout(orientation='vertical', size_hint_x=0.4, spacing=10)
# Main container - single column layout
main_layout = BoxLayout(orientation='vertical', spacing=8, padding=12)
# Title
title = Label(text='[b]Label Information[/b]', markup=True, size_hint_y=0.08,
font_size='18sp')
container.add_widget(title)
title = Label(
text='[b]Label Printing[/b]',
markup=True,
size_hint_y=0.08,
font_size='18sp',
color=(1, 1, 1, 1)
)
main_layout.add_widget(title)
# Scroll view for form
scroll = ScrollView(size_hint_y=0.85)
form_layout = GridLayout(cols=1, spacing=10, size_hint_y=None, padding=10)
# Scroll view for form fields
scroll = ScrollView(size_hint_y=0.75)
form_layout = GridLayout(cols=1, spacing=8, size_hint_y=None, padding=8)
form_layout.bind(minimum_height=form_layout.setter('height'))
# SAP-Nr. Articol
sap_label = Label(text='SAP-Nr. Articol:', size_hint_y=None, height=40,
font_size='14sp')
sap_label = Label(
text='SAP-Nr. Articol:',
size_hint_y=None,
height=30,
font_size='12sp'
)
form_layout.add_widget(sap_label)
self.sap_input = TextInput(
multiline=False,
size_hint_y=None,
height=50,
font_size='16sp',
background_color=(0.95, 0.95, 0.95, 1)
height=45,
font_size='14sp',
background_color=(0.95, 0.95, 0.95, 1),
padding=(10, 10)
)
self.sap_input.bind(text=self.on_input_change)
self.sap_input.bind(text=self.on_sap_text_change)
form_layout.add_widget(self.sap_input)
# Cantitate
qty_label = Label(text='Cantitate:', size_hint_y=None, height=40,
font_size='14sp')
qty_label = Label(
text='Cantitate:',
size_hint_y=None,
height=30,
font_size='12sp'
)
form_layout.add_widget(qty_label)
self.qty_input = TextInput(
multiline=False,
size_hint_y=None,
height=50,
font_size='16sp',
background_color=(0.95, 0.95, 0.95, 1)
height=45,
font_size='14sp',
background_color=(0.95, 0.95, 0.95, 1),
padding=(10, 10),
input_filter='int' # Only allow numbers
)
self.qty_input.bind(text=self.on_input_change)
self.qty_input.bind(text=self.on_qty_text_change)
form_layout.add_widget(self.qty_input)
# ID rola cablu
cable_id_label = Label(text='ID rola cablu:', size_hint_y=None, height=40,
font_size='14sp')
cable_id_label = Label(
text='ID rola cablu:',
size_hint_y=None,
height=30,
font_size='12sp'
)
form_layout.add_widget(cable_id_label)
self.cable_id_input = TextInput(
multiline=False,
size_hint_y=None,
height=50,
font_size='16sp',
background_color=(0.95, 0.95, 0.95, 1)
height=45,
font_size='14sp',
background_color=(0.95, 0.95, 0.95, 1),
padding=(10, 10)
)
self.cable_id_input.bind(text=self.on_input_change)
self.cable_id_input.bind(text=self.on_cable_id_text_change)
form_layout.add_widget(self.cable_id_input)
# Printer selection
printer_label = Label(text='Select Printer:', size_hint_y=None, height=40,
font_size='14sp')
printer_label = Label(
text='Select Printer:',
size_hint_y=None,
height=30,
font_size='12sp'
)
form_layout.add_widget(printer_label)
printer_spinner = Spinner(
text=self.available_printers[0] if self.available_printers else "No Printers",
values=self.available_printers,
size_hint_y=None,
height=50,
font_size='14sp'
height=45,
font_size='12sp'
)
self.printer_spinner = printer_spinner
form_layout.add_widget(printer_spinner)
scroll.add_widget(form_layout)
container.add_widget(scroll)
main_layout.add_widget(scroll)
# Print button
print_button = Button(
text='PRINT LABEL',
size_hint_y=0.15,
font_size='16sp',
font_size='14sp',
background_color=(0.2, 0.6, 0.2, 1),
background_normal='',
bold=True
)
print_button.bind(on_press=self.print_label)
container.add_widget(print_button)
main_layout.add_widget(print_button)
return container
return main_layout
def create_preview_column(self):
"""Create the right column with preview"""
container = BoxLayout(orientation='vertical', size_hint_x=0.6, spacing=10)
# Title
title = Label(text='[b]Label Preview[/b]', markup=True,
size_hint_y=0.08, font_size='18sp')
container.add_widget(title)
# Preview canvas
self.preview_widget = LabelPreviewWidget(size_hint_y=0.92)
container.add_widget(self.preview_widget)
return container
def on_sap_text_change(self, instance, value):
"""Limit SAP input to 25 characters"""
if len(value) > 25:
self.sap_input.text = value[:25]
def on_input_change(self, instance, value):
"""Update preview when input changes"""
# Get all input values
sap_nr = self.sap_input.text
cantitate = self.qty_input.text # This is actually the barcode/cantitate field
cable_id = self.cable_id_input.text
# Update preview
self.preview_widget.update_preview(sap_nr, cantitate, cable_id)
def on_qty_text_change(self, instance, value):
"""Limit Quantity input to 25 characters"""
if len(value) > 25:
self.qty_input.text = value[:25]
def on_cable_id_text_change(self, instance, value):
"""Limit Cable ID input to 25 characters"""
if len(value) > 25:
self.cable_id_input.text = value[:25]
def print_label(self, instance):
"""Handle print button press"""
@@ -340,30 +204,39 @@ class LabelPrinterApp(App):
padding=10,
spacing=10
),
size_hint=(0.6, 0.3)
size_hint=(0.8, 0.3)
)
popup.content.add_widget(Label(text='Printing label...\nPlease wait'))
popup.content.add_widget(Label(text='Processing label...\nPlease wait'))
popup.open()
# Print in background thread
# Print in background thread (using PDF by default)
def print_thread():
try:
success = print_label_standalone(label_text, printer, preview=0)
success = print_label_standalone(label_text, printer, preview=0, use_pdf=True)
if success:
popup.dismiss()
self.show_popup("Success", "Label printed successfully!")
# Use Clock.schedule_once to update UI from main thread
Clock.schedule_once(lambda dt: popup.dismiss(), 0)
Clock.schedule_once(lambda dt: self.show_popup("Success", "Label printed successfully!"), 0.1)
# Clear inputs after successful print
Clock.schedule_once(lambda dt: self.clear_inputs(), 0.2)
else:
popup.dismiss()
self.show_popup("Error", "Failed to print label")
Clock.schedule_once(lambda dt: popup.dismiss(), 0)
Clock.schedule_once(lambda dt: self.show_popup("Error", "Failed to print label"), 0.1)
except Exception as e:
popup.dismiss()
self.show_popup("Error", f"Print error: {str(e)}")
Clock.schedule_once(lambda dt: popup.dismiss(), 0)
Clock.schedule_once(lambda dt: self.show_popup("Error", f"Print error: {str(e)}"), 0.1)
thread = threading.Thread(target=print_thread)
thread.daemon = True
thread.start()
def clear_inputs(self):
"""Clear all input fields"""
self.sap_input.text = ''
self.qty_input.text = ''
self.cable_id_input.text = ''
def show_popup(self, title, message):
"""Show a popup message"""
popup = Popup(
@@ -373,7 +246,7 @@ class LabelPrinterApp(App):
padding=10,
spacing=10
),
size_hint=(0.6, 0.3)
size_hint=(0.8, 0.4)
)
popup.content.add_widget(Label(text=message))