Files
label_printer/label_printer_gui.py

391 lines
14 KiB
Python

"""
Label Printer GUI Application using Kivy
This application provides a user-friendly interface for printing labels with barcodes.
"""
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.spinner import Spinner
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 kivy.clock import Clock
# Set window size
Window.size = (1600, 900)
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"""
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:
conn = cups.Connection()
printers = conn.getPrinters()
return list(printers.keys()) if printers else ["PDF"]
except Exception as e:
print(f"Error getting printers: {e}")
return ["PDF"]
def build(self):
"""Build the main UI"""
self.title = "Label Printer Interface"
# 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)
# Title
title = Label(text='[b]Label Information[/b]', markup=True, size_hint_y=0.08,
font_size='18sp')
container.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)
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')
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)
)
self.sap_input.bind(text=self.on_input_change)
form_layout.add_widget(self.sap_input)
# Cantitate
qty_label = Label(text='Cantitate:', size_hint_y=None, height=40,
font_size='14sp')
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)
)
self.qty_input.bind(text=self.on_input_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')
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)
)
self.cable_id_input.bind(text=self.on_input_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')
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'
)
self.printer_spinner = printer_spinner
form_layout.add_widget(printer_spinner)
scroll.add_widget(form_layout)
container.add_widget(scroll)
# Print button
print_button = Button(
text='PRINT LABEL',
size_hint_y=0.15,
font_size='16sp',
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)
return container
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_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 print_label(self, instance):
"""Handle print button press"""
sap_nr = self.sap_input.text.strip()
quantity = self.qty_input.text.strip()
cable_id = self.cable_id_input.text.strip()
printer = self.printer_spinner.text
# Validate input
if not sap_nr and not quantity and not cable_id:
self.show_popup("Error", "Please enter at least one field")
return
# Create combined label text
label_text = f"{sap_nr}|{quantity}|{cable_id}"
# Show loading popup
popup = Popup(
title='Printing',
content=BoxLayout(
orientation='vertical',
padding=10,
spacing=10
),
size_hint=(0.6, 0.3)
)
popup.content.add_widget(Label(text='Printing label...\nPlease wait'))
popup.open()
# Print in background thread
def print_thread():
try:
success = print_label_standalone(label_text, printer, preview=0)
if success:
popup.dismiss()
self.show_popup("Success", "Label printed successfully!")
else:
popup.dismiss()
self.show_popup("Error", "Failed to print label")
except Exception as e:
popup.dismiss()
self.show_popup("Error", f"Print error: {str(e)}")
thread = threading.Thread(target=print_thread)
thread.daemon = True
thread.start()
def show_popup(self, title, message):
"""Show a popup message"""
popup = Popup(
title=title,
content=BoxLayout(
orientation='vertical',
padding=10,
spacing=10
),
size_hint=(0.6, 0.3)
)
popup.content.add_widget(Label(text=message))
close_button = Button(text='OK', size_hint_y=0.3)
close_button.bind(on_press=popup.dismiss)
popup.content.add_widget(close_button)
popup.open()
if __name__ == '__main__':
app = LabelPrinterApp()
app.run()