Add Kivy GUI interface for label printer
- Created label_printer_gui.py: Complete Kivy-based GUI application - Two-column layout (input form + live preview) - SAP-Nr, Quantity, Cable ID input fields - Real-time barcode preview (11.5cm x 8cm) - Printer selection dropdown - Print button with CUPS integration - Added setup automation: - setup_and_run.py: Python setup launcher - start_gui.sh: Bash launcher script - validate_project.py: Project validation - Added comprehensive documentation: - INDEX.md: Project overview and quick start - GETTING_STARTED.md: 15-minute quick start guide - README_GUI.md: Complete feature documentation - TECHNICAL_DOCS.md: Architecture and customization - FILE_GUIDE.md: File reference guide - IMPLEMENTATION_SUMMARY.md: Implementation overview - Updated dependencies: - requirements_gui.txt: New Kivy dependencies - Preserved: - print_label.py: Original printing engine (modified to remove main code) - Original documentation and dependencies Features: - Live preview of labels as you type - Automatic CUPS printer detection - Non-blocking background printing - User-friendly error handling - Responsive two-column layout - Production-ready quality
This commit is contained in:
526
label_printer_gui.py
Normal file
526
label_printer_gui.py
Normal file
@@ -0,0 +1,526 @@
|
||||
"""
|
||||
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
|
||||
from print_label import create_label_image, print_label_standalone
|
||||
|
||||
# Set window size
|
||||
Window.size = (1600, 900)
|
||||
|
||||
|
||||
class LabelPreviewWidget(ScatterLayout):
|
||||
"""Widget for displaying the label preview"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.label_image = None
|
||||
self.temp_preview_path = None
|
||||
|
||||
def update_preview(self, text):
|
||||
"""Update the preview with new label image"""
|
||||
if text:
|
||||
try:
|
||||
self.label_image = create_label_image(text)
|
||||
self.display_preview()
|
||||
except Exception as e:
|
||||
print(f"Error creating preview: {e}")
|
||||
|
||||
def display_preview(self):
|
||||
"""Display the preview image"""
|
||||
if self.label_image:
|
||||
# Save to temporary file
|
||||
import tempfile
|
||||
if self.temp_preview_path and os.path.exists(self.temp_preview_path):
|
||||
try:
|
||||
os.remove(self.temp_preview_path)
|
||||
except:
|
||||
pass
|
||||
|
||||
with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp:
|
||||
self.label_image.save(tmp.name)
|
||||
self.temp_preview_path = tmp.name
|
||||
|
||||
# Clear and recreate children
|
||||
self.clear_widgets()
|
||||
|
||||
# Add image
|
||||
img = KivyImage(source=self.temp_preview_path, size_hint=(1, 1))
|
||||
self.add_widget(img)
|
||||
|
||||
|
||||
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',
|
||||
input_filter='int',
|
||||
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 (11.5cm x 8cm)[/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
|
||||
quantity = self.qty_input.text
|
||||
cable_id = self.cable_id_input.text
|
||||
|
||||
# Create label text combining all fields
|
||||
if sap_nr or quantity or cable_id:
|
||||
label_text = f"{sap_nr}|{quantity}|{cable_id}"
|
||||
self.preview_widget.update_preview(label_text)
|
||||
|
||||
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()
|
||||
|
||||
|
||||
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',
|
||||
input_filter='int',
|
||||
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 (11.5cm x 8cm)[/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
|
||||
quantity = self.qty_input.text
|
||||
cable_id = self.cable_id_input.text
|
||||
|
||||
# Create label text combining all fields
|
||||
if sap_nr or quantity or cable_id:
|
||||
label_text = f"{sap_nr}|{quantity}|{cable_id}"
|
||||
self.preview_widget.update_preview(label_text)
|
||||
|
||||
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()
|
||||
Reference in New Issue
Block a user