Files
Label-design/app.py
2025-06-30 15:45:36 +03:00

620 lines
24 KiB
Python

import tkinter as tk
from tkinter import simpledialog, messagebox, ttk, font
from PIL import Image, ImageTk, ImageDraw, ImageFont
import barcode
from barcode.writer import ImageWriter
import cups
import os
import time
# Valid operator codes (in a real app, this would be in a database)
VALID_OPERATORS = {
"OP001": "Operator 1",
"OP002": "Operator 2",
"OP003": "Operator 3",
"123456": "Admin" # Simple code for testing
}
class LabelApp(tk.Tk):
def __init__(self):
super().__init__()
# Configure fullscreen toggling
self.allow_fullscreen_toggle = True # Variable to control ESC key behavior
# Configure the main window
self.title("Label Printing System")
self.configure(bg="#f0f0f0")
# Create a container for all frames
self.container = tk.Frame(self)
self.container.pack(fill="both", expand=True)
# Current logged in operator
self.current_operator = None
# Initialize frames dictionary
self.frames = {}
# Create frames
for F in (LoginFrame, DashboardFrame, SettingsFrame):
frame = F(self.container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
# Configure container grid
self.container.grid_rowconfigure(0, weight=1)
self.container.grid_columnconfigure(0, weight=1)
# Show login frame
self.show_frame(LoginFrame)
# Bind Escape key to toggle fullscreen, respecting the variable
self.bind('<Escape>', self.handle_escape)
# Use after() to ensure fullscreen works - solves issues on some systems
self.attributes('-fullscreen', True)
self.after(100, self.ensure_fullscreen)
def show_frame(self, frame_class):
"""Bring the specified frame to the front"""
frame = self.frames[frame_class]
frame.tkraise()
def login(self, operator_code):
"""Attempt to login with the provided operator code"""
if operator_code in VALID_OPERATORS:
self.current_operator = {
"code": operator_code,
"name": VALID_OPERATORS[operator_code],
"login_time": time.strftime("%Y-%m-%d %H:%M:%S")
}
# Disable fullscreen toggle for admin users
if operator_code == "123456": # Admin code
self.set_fullscreen_toggle(False)
else:
self.set_fullscreen_toggle(True)
self.show_frame(DashboardFrame)
self.frames[DashboardFrame].update_operator_info()
return True
return False
def logout(self):
"""Log out and return to login screen"""
self.current_operator = None
self.show_frame(LoginFrame)
def handle_escape(self, event):
"""Handle ESC key press based on toggle variable"""
if self.allow_fullscreen_toggle:
self.toggle_fullscreen()
def toggle_fullscreen(self):
"""Toggle fullscreen mode"""
if self.attributes('-fullscreen'):
self.attributes('-fullscreen', False)
else:
self.attributes('-fullscreen', True)
def set_fullscreen_toggle(self, enabled):
"""Enable or disable the ESC key fullscreen toggle"""
self.allow_fullscreen_toggle = enabled
def ensure_fullscreen(self):
"""Make sure we're in fullscreen mode"""
if not self.attributes('-fullscreen'):
self.attributes('-fullscreen', True)
def is_admin(self):
"""Check if the current user is an admin"""
if self.current_operator and self.current_operator["code"] == "123456":
return True
return False
class LoginFrame(tk.Frame):
def __init__(self, parent, controller):
super().__init__(parent, bg="#f0f0f0")
self.controller = controller
# Create login form
login_frame = tk.Frame(self, bg="#ffffff", padx=30, pady=30)
login_frame.place(relx=0.5, rely=0.5, anchor="center")
# Title
title_font = font.Font(family="Arial", size=24, weight="bold")
tk.Label(login_frame, text="Label Printing System", font=title_font, bg="#ffffff").pack(pady=(0, 20))
# Operator code entry
tk.Label(login_frame, text="Scan Operator Code:", font=("Arial", 14), bg="#ffffff").pack(anchor="w", pady=(10, 5))
self.code_var = tk.StringVar()
code_entry = tk.Entry(login_frame, textvariable=self.code_var, font=("Arial", 16), width=20, show="*")
code_entry.pack(pady=(0, 20), fill="x")
code_entry.focus_set()
# Login button
login_btn = tk.Button(login_frame, text="Login", font=("Arial", 14),
bg="#4CAF50", fg="white", padx=20, pady=5,
command=self.login)
login_btn.pack(pady=(10, 5))
# Exit button
exit_btn = tk.Button(login_frame, text="Exit", font=("Arial", 14),
bg="#f44336", fg="white", padx=20, pady=5,
command=self.controller.destroy)
exit_btn.pack(pady=(5, 0))
# Bind Enter key to login
code_entry.bind("<Return>", lambda event: self.login())
def login(self):
operator_code = self.code_var.get()
if not operator_code:
messagebox.showerror("Error", "Please enter an operator code")
return
if self.controller.login(operator_code):
self.code_var.set("") # Clear the entry
else:
messagebox.showerror("Error", "Invalid operator code")
self.code_var.set("") # Clear the entry
class DashboardFrame(tk.Frame):
def __init__(self, parent, controller):
super().__init__(parent, bg="#f0f0f0")
self.controller = controller
# Create header frame
header_frame = tk.Frame(self, bg="#333333", height=60)
header_frame.pack(fill="x")
# App title
tk.Label(header_frame, text="Label Printing Dashboard", font=("Arial", 16, "bold"),
fg="white", bg="#333333").pack(side="left", padx=20, pady=10)
# Operator info
self.operator_label = tk.Label(header_frame, text="", font=("Arial", 12),
fg="white", bg="#333333")
self.operator_label.pack(side="right", padx=20, pady=10)
# Settings button (only visible to admin)
self.settings_btn = tk.Button(header_frame, text="Settings", font=("Arial", 12),
bg="#2196F3", fg="white", padx=10, pady=2,
command=lambda: controller.show_frame(SettingsFrame))
self.settings_btn.pack(side="right", padx=10, pady=10)
self.settings_btn.pack_forget() # Hide initially
# Logout button
logout_btn = tk.Button(header_frame, text="Logout", font=("Arial", 12),
bg="#f44336", fg="white", padx=10, pady=2,
command=self.controller.logout)
logout_btn.pack(side="right", padx=10, pady=10)
# Create main container to hold all three frames
main_container = tk.Frame(self, bg="#f0f0f0", padx=10, pady=10)
main_container.pack(fill="both", expand=True)
# Configure grid for the main container (2 rows, 2 columns)
main_container.columnconfigure(0, weight=1)
main_container.columnconfigure(1, weight=1)
main_container.rowconfigure(0, weight=2) # Top row larger
main_container.rowconfigure(1, weight=1) # Bottom row smaller
# 1. Create Label frame (top left)
label_frame = tk.LabelFrame(main_container, text="Create Label", font=("Arial", 14),
bg="#f0f0f0", padx=20, pady=20)
label_frame.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
# 2. Available Articles frame (top right)
articles_frame = tk.LabelFrame(main_container, text="Articole disponibile in viitor",
font=("Arial", 14), bg="#f0f0f0", padx=20, pady=20)
articles_frame.grid(row=0, column=1, sticky="nsew", padx=5, pady=5)
# 3. Last 6 Labels frame (bottom, spans two columns)
history_frame = tk.LabelFrame(main_container, text="Ultimele 6 etichete printate",
font=("Arial", 14), bg="#f0f0f0", padx=20, pady=20)
history_frame.grid(row=1, column=0, columnspan=2, sticky="nsew", padx=5, pady=5)
# Fill the Create Label frame with existing content
# Text input
tk.Label(label_frame, text="Enter text for label:", font=("Arial", 12), bg="#f0f0f0").pack(anchor="w", pady=(0, 5))
self.text_var = tk.StringVar()
text_entry = tk.Entry(label_frame, textvariable=self.text_var, font=("Arial", 14), width=40)
text_entry.pack(fill="x", pady=(0, 20))
text_entry.focus_set()
# Preview checkbox
self.preview_var = tk.BooleanVar(value=True)
preview_chk = tk.Checkbutton(label_frame, text="Show print preview", variable=self.preview_var,
font=("Arial", 12), bg="#f0f0f0")
preview_chk.pack(anchor="w", pady=(0, 20))
# Buttons frame
buttons_frame = tk.Frame(label_frame, bg="#f0f0f0")
buttons_frame.pack(fill="x", pady=10)
# Print button
print_btn = tk.Button(buttons_frame, text="Print Label", font=("Arial", 14),
bg="#4CAF50", fg="white", padx=20, pady=10,
command=self.print_label)
print_btn.pack(side="left", padx=(0, 10))
# Clear button
clear_btn = tk.Button(buttons_frame, text="Clear", font=("Arial", 14),
bg="#ff9800", fg="white", padx=20, pady=10,
command=lambda: self.text_var.set(""))
clear_btn.pack(side="left")
# Placeholder message for the other frames (to be filled later)
tk.Label(articles_frame, text="Future articles will be displayed here",
font=("Arial", 12), bg="#f0f0f0").pack(expand=True)
tk.Label(history_frame, text="Last 6 printed labels will be displayed here",
font=("Arial", 12), bg="#f0f0f0").pack(expand=True)
# Status frame at the bottom
status_frame = tk.Frame(self, bg="#e0e0e0", height=30)
status_frame.pack(fill="x", side="bottom")
self.status_label = tk.Label(status_frame, text="Ready", font=("Arial", 10), bg="#e0e0e0")
self.status_label.pack(side="left", padx=10, pady=5)
def update_operator_info(self):
"""Update the operator information displayed in the header"""
if self.controller.current_operator:
operator = self.controller.current_operator
self.operator_label.config(text=f"Operator: {operator['name']} ({operator['code']})")
# Show settings button only for admin
if self.controller.is_admin():
self.settings_btn.pack(side="right", padx=10, pady=10)
else:
self.settings_btn.pack_forget()
def print_label(self):
"""Print a label with the entered text"""
text = self.text_var.get().strip()
if not text:
messagebox.showerror("Error", "Please enter text for the label")
return
# Get saved printer or available printers
saved_printer = get_saved_printer()
if saved_printer:
printer = saved_printer
else:
# Get available printers
try:
printers = get_printers()
if not printers:
messagebox.showerror("Error", "No printers found")
return
except Exception as e:
messagebox.showerror("Error", f"Could not get printers: {str(e)}")
return
# Select printer
printer = select_printer(printers)
if not printer:
return # User cancelled
# Create and print label
try:
self.status_label.config(text="Creating label...")
self.update_idletasks()
label_img = create_label_image(text)
label_img.save('final_label.png')
if self.preview_var.get():
self.status_label.config(text="Showing preview...")
self.update_idletasks()
show_preview('final_label.png', lambda: self.complete_print(printer, text))
else:
self.complete_print(printer, text)
except Exception as e:
messagebox.showerror("Error", f"Error creating label: {str(e)}")
self.status_label.config(text="Ready")
def complete_print(self, printer, text):
"""Complete the print job after preview (if shown)"""
try:
self.status_label.config(text="Printing...")
self.update_idletasks()
print_label(printer, text)
self.status_label.config(text="Label printed successfully")
show_auto_close_info("Success", "Label sent to printer", timeout=2000)
# Clear text field after successful print
self.text_var.set("")
except Exception as e:
messagebox.showerror("Error", f"Error printing label: {str(e)}")
finally:
self.status_label.config(text="Ready")
class SettingsFrame(tk.Frame):
def __init__(self, parent, controller):
super().__init__(parent, bg="#f0f0f0")
self.controller = controller
# Create header frame
header_frame = tk.Frame(self, bg="#333333", height=60)
header_frame.pack(fill="x")
# App title
tk.Label(header_frame, text="Settings", font=("Arial", 16, "bold"),
fg="white", bg="#333333").pack(side="left", padx=20, pady=10)
# Back button
back_btn = tk.Button(header_frame, text="Back to Dashboard", font=("Arial", 12),
bg="#4CAF50", fg="white", padx=10, pady=2,
command=lambda: controller.show_frame(DashboardFrame))
back_btn.pack(side="right", padx=10, pady=10)
# Create main content frame
content_frame = tk.Frame(self, bg="#f0f0f0", padx=20, pady=20)
content_frame.pack(fill="both", expand=True)
# Printer settings section
printer_frame = tk.LabelFrame(content_frame, text="Printer Settings", font=("Arial", 14), bg="#f0f0f0", padx=20, pady=20)
printer_frame.pack(fill="x", expand=False, padx=20, pady=20)
# Printer selection
tk.Label(printer_frame, text="Select Default Printer:", font=("Arial", 12), bg="#f0f0f0").pack(anchor="w", pady=(0, 5))
# Get available printers
try:
self.printers = get_printers()
self.printer_var = tk.StringVar()
# Load saved printer if available
saved_printer = load_printer_config()
if saved_printer and saved_printer in self.printers:
self.printer_var.set(saved_printer)
elif self.printers:
self.printer_var.set(self.printers[0])
combo = ttk.Combobox(printer_frame, textvariable=self.printer_var, values=self.printers,
state="readonly", font=("Arial", 12), width=40)
combo.pack(pady=(0, 20), fill="x")
# Save button
save_btn = tk.Button(printer_frame, text="Save Printer Setting", font=("Arial", 12),
bg="#4CAF50", fg="white", padx=10, pady=5,
command=self.save_printer_config)
save_btn.pack(pady=10)
except Exception as e:
tk.Label(printer_frame, text=f"Error loading printers: {str(e)}", font=("Arial", 12),
fg="red", bg="#f0f0f0").pack(pady=10)
def save_printer_config(self):
"""Save the selected printer to a configuration file"""
selected_printer = self.printer_var.get()
if selected_printer:
try:
with open('printer_config.txt', 'w') as f:
f.write(selected_printer)
messagebox.showinfo("Success", "Printer setting saved successfully")
except Exception as e:
messagebox.showerror("Error", f"Could not save printer setting: {str(e)}")
# Your existing functions remain largely the same
def get_printers():
conn = cups.Connection()
return list(conn.getPrinters().keys())
def create_label_image(text):
# Your existing create_label_image function (unchanged)
# Label dimensions for 9x5 cm at 300 DPI
label_width = 1063 # 9 cm
label_height = 591 # 5 cm
# Outer frame (95% of label, centered)
outer_frame_width = int(label_width * 0.95)
outer_frame_height = int(label_height * 0.95)
outer_frame_x = (label_width - outer_frame_width) // 2
outer_frame_y = (label_height - outer_frame_height) // 2
# Barcode frame (top, inside outer frame)
barcode_frame_width = int(outer_frame_width * 0.90)
barcode_frame_height = int(outer_frame_height * 0.60)
barcode_frame_x = outer_frame_x + (outer_frame_width - barcode_frame_width) // 2
barcode_frame_y = outer_frame_y
# Text frame (immediately below barcode frame)
text_frame_width = int(outer_frame_width * 0.90)
text_frame_height = int(outer_frame_height * 0.35)
text_frame_x = outer_frame_x + (outer_frame_width - text_frame_width) // 2
gap_between_frames = 5 # or 0 for no gap
text_frame_y = barcode_frame_y + barcode_frame_height + gap_between_frames
# Generate barcode image (no text), at higher resolution
CODE128 = barcode.get_barcode_class('code128')
writer_options = {
"write_text": False,
"module_width": 0.5, # default is 0.2, increase for higher res
"module_height": barcode_frame_height, # match frame height
"quiet_zone": 3.5, # default, can adjust if needed
"font_size": 0 # no text
}
code = CODE128(text, writer=ImageWriter())
filename = code.save('label_barcode', options=writer_options)
barcode_img = Image.open(filename)
# Now resize barcode to exactly fit barcode frame (stretch, do not keep aspect ratio)
barcode_resized = barcode_img.resize((barcode_frame_width, barcode_frame_height), Image.LANCZOS)
# Create label image
label_img = Image.new('RGB', (label_width, label_height), 'white')
# Paste barcode centered in barcode frame
label_img.paste(barcode_resized, (barcode_frame_x, barcode_frame_y))
# Draw text in text frame, maximize font size to fit frame (keep sharpness)
font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
max_font_size = text_frame_height
min_font_size = 10
best_font_size = min_font_size
for font_size in range(min_font_size, max_font_size + 1):
try:
font = ImageFont.truetype(font_path, font_size)
except IOError:
font = ImageFont.load_default()
break
dummy_img = Image.new('RGB', (1, 1))
dummy_draw = ImageDraw.Draw(dummy_img)
text_bbox = dummy_draw.textbbox((0, 0), text, font=font)
text_width = text_bbox[2] - text_bbox[0]
text_height = text_bbox[3] - text_bbox[1]
if text_width > text_frame_width or text_height > text_frame_height:
break
best_font_size = font_size
# Use the best font size found
try:
font = ImageFont.truetype(font_path, best_font_size)
except IOError:
font = ImageFont.load_default()
draw = ImageDraw.Draw(label_img)
text_bbox = draw.textbbox((0, 0), text, font=font)
text_width = text_bbox[2] - text_bbox[0]
text_height = text_bbox[3] - text_bbox[1]
text_x = text_frame_x + (text_frame_width - text_width) // 2
text_y = text_frame_y + (text_frame_height - text_height) // 2
draw.text((text_x, text_y), text, font=font, fill='black')
os.remove(filename)
return label_img
def print_label(printer_name, text):
label_img = create_label_image(text)
label_img.save('final_label.png')
conn = cups.Connection()
conn.printFile(printer_name, 'final_label.png', "Label Print", {})
os.remove('final_label.png')
def show_preview(image_path, on_print):
preview = tk.Toplevel()
preview.title("Label Preview")
preview.geometry("1063x591")
preview.resizable(False, False)
img = Image.open(image_path)
img_tk = ImageTk.PhotoImage(img)
label = tk.Label(preview, image=img_tk)
label.image = img_tk # Keep reference
label.pack()
def print_and_close():
on_print()
preview.destroy()
# Auto-close and print after 4 seconds (4000 ms)
preview.after(4000, print_and_close)
preview.grab_set()
preview.wait_window()
def select_printer(printers):
dialog = tk.Toplevel()
dialog.title("Select Printer")
dialog.geometry("400x250") # Slightly larger for better visibility
dialog.configure(bg="#f0f0f0") # Match app background
dialog.resizable(False, False)
# Create a distinctive border
frame = tk.Frame(dialog, bd=2, relief="ridge", bg="white", padx=20, pady=20)
frame.pack(fill="both", expand=True, padx=10, pady=10)
tk.Label(frame, text="Select a printer:", font=("Arial", 14, "bold"), bg="white").pack(padx=10, pady=10)
printer_var = tk.StringVar(value=printers[0] if printers else "") # Default to first printer
combo = ttk.Combobox(frame, textvariable=printer_var, values=printers, state="readonly", font=("Arial", 12))
combo.pack(padx=10, pady=20, fill="x")
button_frame = tk.Frame(frame, bg="white")
button_frame.pack(pady=10, fill="x")
selected = {'printer': None}
def on_ok():
selected['printer'] = printer_var.get()
dialog.destroy()
def on_cancel():
dialog.destroy()
# Auto-select timeout
def auto_select():
if not selected['printer'] and printers:
selected['printer'] = printers[0]
dialog.destroy()
ok_btn = tk.Button(button_frame, text="OK", command=on_ok, font=("Arial", 12),
bg="#4CAF50", fg="white", width=8)
ok_btn.pack(side="left", padx=(50, 10))
tk.Button(button_frame, text="Cancel", command=on_cancel, font=("Arial", 12),
bg="#f44336", fg="white", width=8).pack(side="left")
# Ensure dialog is on top and focused
dialog.attributes('-topmost', True)
dialog.update()
dialog.attributes('-topmost', False)
# Auto-select after 15 seconds if no choice made
dialog.after(15000, auto_select)
# Set focus to the OK button
dialog.after(100, lambda: ok_btn.focus_set())
dialog.grab_set()
dialog.wait_window()
# Return default printer if none selected
if not selected['printer'] and printers:
return printers[0]
return selected['printer']
def show_auto_close_info(title, message, timeout=3000):
info = tk.Toplevel()
info.title(title)
info.geometry("300x100")
info.resizable(False, False)
tk.Label(info, text=message, font=("Arial", 12)).pack(expand=True, padx=10, pady=10)
info.after(timeout, info.destroy)
info.grab_set()
info.wait_window()
def load_printer_config():
"""Load the saved printer from configuration file"""
try:
if os.path.exists('printer_config.txt'):
with open('printer_config.txt', 'r') as f:
return f.read().strip()
except Exception:
pass
return None
def get_saved_printer():
"""Get the saved printer from configuration"""
saved_printer = load_printer_config()
if saved_printer:
# Verify printer still exists
available_printers = get_printers()
if saved_printer in available_printers:
return saved_printer
return None
# Replace your main() function with this
if __name__ == "__main__":
app = LabelApp()
app.mainloop()