Add scan-tag.png between drawing and QR code on ribbon prints

Inserts the scan-tag image centered horizontally between the user drawing
and QR code in ribbon print output. Total height increases by scan-tag height.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
trav
2026-01-18 23:12:45 -08:00
parent 360c0358c6
commit d78f84d8ff

480
kiosk.py
View File

@ -24,6 +24,25 @@ import glob
import traceback
# Load configuration
def load_config():
config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.json')
try:
with open(config_path) as f:
return json.load(f)
except FileNotFoundError:
print("Warning: config.json not found. Copy config.example.json to config.json and customize it.")
return {
"printers": {"sticker": "sticker_printer", "ribbon": "tag-printer"},
"camera": {"preferred_name": "NexiGo"},
"ribbon": {"width": 450, "margin": 50},
"background_color": "#bcfef9"
}
CONFIG = load_config()
BG_COLOR = CONFIG.get("background_color", "#bcfef9")
def get_preferred_camera_index(preferred_name_fragment=None):
"""
Try to find a stable camera index for an external webcam.
@ -95,15 +114,12 @@ def get_preferred_camera_index(preferred_name_fragment=None):
# Configuration
MIGRATION_ITEMS_DIR = "/home/trav/Documents/migration_items"
# Global variables
class GlobalVars:
qr_code_value = None
print_type = "neither"
migration_ticket = ""
selected_user = None
ribbon_size = None # 'small', 'medium', or 'large'
BUTTON_FONT = None
TEXT_FONT = None
@ -147,28 +163,11 @@ class Kiosk(tk.Tk):
self.start_over()
def start_over(self):
global migration_ticket
global MIGRATION_ITEMS_DIR
# Check if migration_ticket is defined
#if 'migration_ticket' in globals():
# Delete any existing migration files
# if migration_ticket:
# text_file = os.path.join(MIGRATION_ITEMS_DIR, f"{migration_ticket}.txt")
# image_file = os.path.join(MIGRATION_ITEMS_DIR, f"{migration_ticket}.jpg")
# if os.path.exists(text_file):
# os.remove(text_file)
# if os.path.exists(image_file):
# os.remove(image_file)
# Reset migration_ticket
# migration_ticket = ""
# Reset other global variables
# Reset global variables
GlobalVars.qr_code_value = None
GlobalVars.print_type = "neither"
GlobalVars.selected_user = None
GlobalVars.ribbon_size = None
# Switch to the home screen
self.switch_frame(Screen0)
@ -176,12 +175,12 @@ class Kiosk(tk.Tk):
# home screen
class Screen0(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, bg='#bcfef9')
tk.Frame.__init__(self, master, bg=BG_COLOR)
title_font = tkfont.Font(size=42, family='Helvetica') # 30% bigger
# Split the screen into two frames
left_frame = tk.Frame(self, bg='#bcfef9')
right_frame = tk.Frame(self, bg='#bcfef9', padx=40) # 40px padding on the right side
left_frame = tk.Frame(self, bg=BG_COLOR)
right_frame = tk.Frame(self, bg=BG_COLOR, padx=40) # 40px padding on the right side
left_frame.grid(row=0, column=0, sticky='nsew')
right_frame.grid(row=0, column=1, sticky='nsew')
@ -189,21 +188,18 @@ class Screen0(tk.Frame):
self.grid_columnconfigure(1, weight=1) # For right frame
# Title and buttons on the left side
title_label = tk.Label(left_frame, text="Custodisco", bg='#bcfef9', fg='#800080', font=('Helvetica', 64)) # dark purple color
title_label = tk.Label(left_frame, text="Custodisco", bg=BG_COLOR, fg='#800080', font=('Helvetica', 64)) # dark purple color
title_label.pack(side='top', pady=200) # adjust to your needs
# Welcome message on the left side
# welcome_text = """"""
# welcome_label = tk.Label(left_frame, text=welcome_text, bg='#bcfef9', font=GlobalVars.TEXT_FONT, justify='left', wraplength=650)
# welcome_label = tk.Label(left_frame, text=welcome_text, bg=BG_COLOR, font=GlobalVars.TEXT_FONT, justify='left', wraplength=650)
# welcome_label.pack(side='top', padx=20, pady=20)
tk.Button(right_frame, text="Create Item", command=lambda: master.switch_frame(Screen1), height=4, width=75, bg='peach puff', font=GlobalVars.BUTTON_FONT).pack(side='top', pady=30)
# Button for Migration mode
#tk.Button(right_frame, text="Create Item", command=lambda: master.switch_frame(Screen15), height=4, width=75, bg='peach puff', font=GlobalVars.BUTTON_FONT).pack(side='top', pady=30)
tk.Button(right_frame, text="Lookup Item", command=lambda: master.switch_frame(Screen14), height=4, width=75, bg='peach puff', font=GlobalVars.BUTTON_FONT).pack(side='top', pady=30)
# Create the quit button
@ -217,7 +213,7 @@ class Screen0(tk.Frame):
class Screen1(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, bg='#bcfef9')
tk.Frame.__init__(self, master, bg=BG_COLOR)
master.add_home_button(self)
# Create the label widget with the text
@ -238,7 +234,7 @@ class VirtualScrolledFrame(ttk.Frame):
# Create a canvas object and a vertical scrollbar for scrolling it
self.vscrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL)
self.canvas = tk.Canvas(self, yscrollcommand=self.vscrollbar.set, bg='#bcfef9')
self.canvas = tk.Canvas(self, yscrollcommand=self.vscrollbar.set, bg=BG_COLOR)
self.vscrollbar.config(command=self.canvas.yview)
# Reset the view
@ -246,7 +242,7 @@ class VirtualScrolledFrame(ttk.Frame):
self.canvas.yview_moveto(0)
# Create a frame inside the canvas which will be scrolled with it
self.interior = tk.Frame(self.canvas, bg='#bcfef9')
self.interior = tk.Frame(self.canvas, bg=BG_COLOR)
self.interior_id = self.canvas.create_window(0, 0, window=self.interior, anchor=tk.NW)
# Pack the widgets
@ -275,12 +271,12 @@ class VirtualScrolledFrame(ttk.Frame):
# find yourself in list of ssb users
class Screen2(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, bg='#bcfef9')
tk.Frame.__init__(self, master, bg=BG_COLOR)
self.selected_label = None
self.filtered_users = []
# Create a new frame at the top for the label and text box
self.top_frame = tk.Frame(self, bg='#bcfef9')
self.top_frame = tk.Frame(self, bg=BG_COLOR)
self.top_frame.pack(side="top", fill="x", pady=(60, 10))
# Add a label with text wrapping
@ -288,7 +284,7 @@ class Screen2(tk.Frame):
text="Start typing your public key or alias to find yourself in the list then click on your key to select it.",
font=GlobalVars.TEXT_FONT,
wraplength=800,
bg='#bcfef9')
bg=BG_COLOR)
self.label.pack(side="top", pady=(0, 10))
# Add text box to the top frame
@ -300,13 +296,13 @@ class Screen2(tk.Frame):
self.entry.focus_set()
# Create container for user list
self.container = tk.Frame(self, bg='#bcfef9')
self.container = tk.Frame(self, bg=BG_COLOR)
self.container.pack(fill='both', expand=True, padx=20, pady=10)
# Create a canvas for the user list
self.canvas = tk.Canvas(self.container, bg='#bcfef9')
self.canvas = tk.Canvas(self.container, bg=BG_COLOR)
self.scrollbar = ttk.Scrollbar(self.container, orient="vertical", command=self.canvas.yview)
self.scrollable_frame = tk.Frame(self.canvas, bg='#bcfef9')
self.scrollable_frame = tk.Frame(self.canvas, bg=BG_COLOR)
self.scrollable_frame.bind(
"<Configure>",
@ -324,7 +320,7 @@ class Screen2(tk.Frame):
style.configure("Vertical.TScrollbar", arrowsize=48, width=48)
# Create a frame for action buttons at the bottom
self.button_frame = tk.Frame(self, bg='#bcfef9')
self.button_frame = tk.Frame(self, bg=BG_COLOR)
self.button_frame.pack(side="bottom", fill="x", pady=10)
# The 'Refresh List' button
@ -354,16 +350,16 @@ class Screen2(tk.Frame):
for index, user in enumerate(self.filtered_users):
try:
frame = tk.Frame(self.scrollable_frame, bg='#bcfef9')
frame = tk.Frame(self.scrollable_frame, bg=BG_COLOR)
frame.pack(fill='x', expand=True, pady=2)
alias = self.unescape_unicode(user.get('alias', ''))
id = user.get('id', '')
alias_label = tk.Label(frame, text=alias, font=('Helvetica', 14, 'bold'), width=20, anchor='w', bg='#bcfef9')
alias_label = tk.Label(frame, text=alias, font=('Helvetica', 14, 'bold'), width=20, anchor='w', bg=BG_COLOR)
alias_label.pack(side='left', padx=(0, 10))
id_label = tk.Label(frame, text=id, font=('Helvetica', 14), anchor='w', bg='#bcfef9')
id_label = tk.Label(frame, text=id, font=('Helvetica', 14), anchor='w', bg=BG_COLOR)
id_label.pack(side='left', expand=True, fill='x')
frame.bind('<Button-1>', lambda e, u=user, f=frame: self.on_user_clicked(f, u))
@ -407,9 +403,9 @@ class Screen2(tk.Frame):
print(f"Highlighted frame: {frame}")
def unhighlight_frame(self, frame):
frame.configure(bg="#bcfef9")
frame.configure(bg=BG_COLOR)
for child in frame.winfo_children():
child.configure(bg="#bcfef9")
child.configure(bg=BG_COLOR)
print(f"Unhighlighted frame: {frame}")
def update_users_list(self):
@ -493,14 +489,14 @@ class Screen2(tk.Frame):
#take photo of item
class Screen3(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master, bg='#bcfef9')
tk.Frame.__init__(self, master, bg=BG_COLOR)
# Create the "Start Over" button
home_button = tk.Button(text="Start Over", command=self.show_warning_dialog, bg='peach puff', font=GlobalVars.BUTTON_FONT)
home_button.place(x=0, y=0) # top-left corner
# Prefer the external NexiGo USB webcam when available
self.vid = cv2.VideoCapture(get_preferred_camera_index("NexiGo"))
self.vid = cv2.VideoCapture(get_preferred_camera_index(CONFIG["camera"]["preferred_name"]))
self.is_capturing = True
self.freeze_frame = None
@ -509,9 +505,9 @@ class Screen3(tk.Frame):
self.canvas.pack(side="left")
# Info and button on the right
self.text_frame = tk.Frame(self, bg='#bcfef9')
self.text_frame = tk.Frame(self, bg=BG_COLOR)
self.text_frame.pack(side="right", fill="both", expand=True)
tk.Label(self.text_frame, text="Now we will take a picture of your item to show up on Scuttlebutt.\n\nIf you tap Take Photo a second time it will re-take the photo\nbut wont show you a preview during the countdown (this is a bug)", font=("Helvetica", 16), bg='#bcfef9').pack(pady=10)
tk.Label(self.text_frame, text="Now we will take a picture of your item to show up on Scuttlebutt.\n\nIf you tap Take Photo a second time it will re-take the photo\nbut wont show you a preview during the countdown (this is a bug)", font=("Helvetica", 16), bg=BG_COLOR).pack(pady=10)
self.button = tk.Button(self.text_frame, text="Take Photo", command=self.take_photo, height=3, width=37, bg='peach puff', font=GlobalVars.BUTTON_FONT)
self.button.pack(pady=10)
@ -530,7 +526,7 @@ class Screen3(tk.Frame):
self.master.switch_frame(Screen0)
# Prefer the external NexiGo USB webcam when available
self.vid = cv2.VideoCapture(get_preferred_camera_index("NexiGo"))
self.vid = cv2.VideoCapture(get_preferred_camera_index(CONFIG["camera"]["preferred_name"]))
self.is_capturing = True
self.freeze_frame = None
@ -539,9 +535,9 @@ class Screen3(tk.Frame):
self.canvas.pack(side="left")
# Info and button on the right
self.text_frame = tk.Frame(self, bg='#bcfef9')
self.text_frame = tk.Frame(self, bg=BG_COLOR)
self.text_frame.pack(side="right", fill="both", expand=True)
tk.Label(self.text_frame, text="Now we will take a picture of your item to show up on Scuttlebutt.\n\nIf you tap Take Photo a second time it will re-take the photo\nbut wont show you a preview during the countdown (this is a bug)", font=("Helvetica", 16), bg='#bcfef9').pack(pady=10)
tk.Label(self.text_frame, text="Now we will take a picture of your item to show up on Scuttlebutt.\n\nIf you tap Take Photo a second time it will re-take the photo\nbut wont show you a preview during the countdown (this is a bug)", font=("Helvetica", 16), bg=BG_COLOR).pack(pady=10)
self.button = tk.Button(self.text_frame, text="Take Photo", command=self.take_photo, height=3, width=37, bg='peach puff', font=GlobalVars.BUTTON_FONT)
self.button.pack(pady=10)
@ -557,14 +553,14 @@ class Screen3(tk.Frame):
#take photo of item
class Screen3(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master, bg='#bcfef9')
tk.Frame.__init__(self, master, bg=BG_COLOR)
# Create the "Start Over" button
home_button = tk.Button(text="Start Over", command=self.show_warning_dialog, bg='peach puff', font=GlobalVars.BUTTON_FONT)
home_button.place(x=0, y=0) # top-left corner
# Prefer the external NexiGo USB webcam when available
self.vid = cv2.VideoCapture(get_preferred_camera_index("NexiGo"))
self.vid = cv2.VideoCapture(get_preferred_camera_index(CONFIG["camera"]["preferred_name"]))
self.is_capturing = True
self.freeze_frame = None
self.countdown_text = None
@ -575,9 +571,9 @@ class Screen3(tk.Frame):
self.canvas.pack(side="left")
# Info and button on the right
self.text_frame = tk.Frame(self, bg='#bcfef9')
self.text_frame = tk.Frame(self, bg=BG_COLOR)
self.text_frame.pack(side="right", fill="both", expand=True)
# tk.Label(self.text_frame, text="Take a photo of your item", font=("Helvetica", 16), bg='#bcfef9').pack(pady=10)
# tk.Label(self.text_frame, text="Take a photo of your item", font=("Helvetica", 16), bg=BG_COLOR).pack(pady=10)
self.button = tk.Button(self.text_frame, text="Take Photo", command=self.take_photo, height=3, width=37, bg='peach puff', font=GlobalVars.BUTTON_FONT)
self.button.pack(pady=10)
@ -647,19 +643,6 @@ class Screen3(tk.Frame):
self.canvas.create_image(0, 0, image=self.last_photo, anchor='nw')
def done(self):
global migration_ticket
global MIGRATION_ITEMS_DIR
# If migration_ticket is set, copy the photo to MIGRATION_ITEMS_DIR
#if migration_ticket:
# source_path = 'freeze_frame.jpg'
# destination_path = os.path.join(MIGRATION_ITEMS_DIR, f"{migration_ticket}.jpg")
# try:
# shutil.copy2(source_path, destination_path)
# print(f"Photo saved as {destination_path}")
#except Exception as e:
# print(f"Error saving photo: {e}")
self.release_resources()
self.master.switch_frame(Screen5)
@ -679,7 +662,7 @@ class Screen3(tk.Frame):
# draw a sticker
class Screen4(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, bg='#bcfef9')
tk.Frame.__init__(self, master, bg=BG_COLOR)
# Configure column minsizes
self.grid_columnconfigure(0, minsize=675)
@ -687,11 +670,11 @@ class Screen4(tk.Frame):
self.grid_columnconfigure(2, minsize=100)
# Creating a frame for the left side of the screen for drawing and instructions
self.left_frame = tk.Frame(self, bg='#bcfef9')
self.left_frame = tk.Frame(self, bg=BG_COLOR)
self.left_frame.grid(row=0, column=0, padx=2)
# Frame for the tools
self.right_frame = tk.Frame(self, bg='#bcfef9')
self.right_frame = tk.Frame(self, bg=BG_COLOR)
self.right_frame.grid(row=0, column=2, padx=70)
# Add Import Image Button
@ -719,13 +702,13 @@ class Screen4(tk.Frame):
self.add_qr_box()
# Create frames for pen size and color tools
self.pen_size_frame = tk.Frame(self.right_frame, bg='#bcfef9')
self.pen_size_frame = tk.Frame(self.right_frame, bg=BG_COLOR)
self.pen_size_frame.pack(pady=(0, 20))
self.pen_color_frame = tk.Frame(self.right_frame, bg='#bcfef9')
self.pen_color_frame = tk.Frame(self.right_frame, bg=BG_COLOR)
self.pen_color_frame.pack(pady=(0, 20))
# Pen size label
tk.Label(self.pen_size_frame, text="Pen Size", font=GlobalVars.TEXT_FONT, bg='#bcfef9').pack()
tk.Label(self.pen_size_frame, text="Pen Size", font=GlobalVars.TEXT_FONT, bg=BG_COLOR).pack()
# Add Draw Size buttons
pen_sizes = [(".", 1), ("*", 2), ("", 3), ("", 4), ("", 5)]
@ -734,7 +717,7 @@ class Screen4(tk.Frame):
height=2, width=5, bg='peach puff').pack(pady=2)
# Pen color label
tk.Label(self.pen_color_frame, text="Pen Color", font=GlobalVars.TEXT_FONT, bg='#bcfef9').pack()
tk.Label(self.pen_color_frame, text="Pen Color", font=GlobalVars.TEXT_FONT, bg=BG_COLOR).pack()
# Creating color buttons
colors = ['black', 'gray', 'white']
@ -754,7 +737,7 @@ class Screen4(tk.Frame):
master.add_home_button(self)
# Define the info_label to display QRX, QRY, and QRscale values
self.info_label = tk.Label(self.right_frame, text="", bg='#bcfef9', font=GlobalVars.TEXT_FONT)
self.info_label = tk.Label(self.right_frame, text="", bg=BG_COLOR, font=GlobalVars.TEXT_FONT)
self.info_label.pack(pady=5)
def draw_line(self, event):
@ -879,7 +862,7 @@ class Screen4(tk.Frame):
# typed description
class Screen5(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, bg='#bcfef9')
tk.Frame.__init__(self, master, bg=BG_COLOR)
master.add_home_button(self)
@ -900,38 +883,16 @@ class Screen5(tk.Frame):
def save_info_and_switch(self):
global info_text
global migration_ticket
global MIGRATION_ITEMS_DIR
info_text = self.info_entry.get("1.0", "end-1c")
info_text = info_text.replace('\n', '\\n')
# If migration_ticket is set (not empty), save the text and image to MIGRATION_ITEMS_DIR
#if migration_ticket:
# Save text
# text_file_path = os.path.join(MIGRATION_ITEMS_DIR, f"{migration_ticket}.txt")
# try:
# with open(text_file_path, 'w') as f:
# f.write(info_text)
# print(f"Text saved as {text_file_path}")
# except Exception as e:
# print(f"Error saving text: {e}")
# Save image
# source_image_path = 'freeze_frame.jpg'
# destination_image_path = os.path.join(MIGRATION_ITEMS_DIR, f"{migration_ticket}.jpg")
# try:
# shutil.copy2(source_image_path, destination_image_path)
# print(f"Photo saved as {destination_image_path}")
# except Exception as e:
# print(f"Error saving photo: {e}")
self.master.switch_frame(Screen11)
# I understand
class Screen6(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, bg='#bcfef9')
tk.Frame.__init__(self, master, bg=BG_COLOR)
master.add_home_button(self)
tk.Button(self, text="I Understand", command=lambda: master.switch_frame(Screen3), height=3, width=30, bg='peach puff', font=GlobalVars.BUTTON_FONT).pack(pady=10)
@ -941,7 +902,7 @@ class Screen6(tk.Frame):
# create user not implemented lol
class Screen7(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, bg='#bcfef9')
tk.Frame.__init__(self, master, bg=BG_COLOR)
master.add_home_button(self)
# Assume there's a method to manage the text entry
@ -958,39 +919,46 @@ class Screen7(tk.Frame):
# draw a ribbon tag
class Screen8(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, bg='#bcfef9')
tk.Frame.__init__(self, master, bg=BG_COLOR)
# Original dimensions
self.original_width = 325
self.original_height = 179
# Get ribbon width from config and calculate dimensions based on selected size
self.ribbon_width = CONFIG.get("ribbon", {}).get("width", 450)
self.printable_width = self.ribbon_width - 50 # 25px margin each side
multiplier = {'small': 0.5, 'medium': 1.0, 'large': 1.5}.get(GlobalVars.ribbon_size, 1.0)
self.original_width = self.printable_width
self.original_height = int(self.printable_width * multiplier)
# Doubled dimensions for display
self.display_width = self.original_width * 2
self.display_height = self.original_height * 2
# Adaptive display scaling based on size
# Target max display height ~700px to fit comfortably on screen
max_display_height = 700
ideal_scale = max_display_height / self.original_height
self.scale_factor = min(2, max(1, int(ideal_scale))) # Clamp between 1x and 2x
self.grid_size = self.scale_factor
# Grid size (2x2 pixels)
self.grid_size = 2
# Display dimensions
self.display_width = self.original_width * self.scale_factor
self.display_height = self.original_height * self.scale_factor
# Add the home button
master.add_home_button(self)
# Main container to hold all elements
main_container = tk.Frame(self, bg='#bcfef9')
main_container = tk.Frame(self, bg=BG_COLOR)
main_container.place(relx=0.5, rely=0.5, anchor='center')
# Left frame for drawing area and import button
left_frame = tk.Frame(main_container, bg='#bcfef9')
left_frame = tk.Frame(main_container, bg=BG_COLOR)
left_frame.pack(side='left', padx=(0, 20))
# Right frame for tools and buttons
right_frame = tk.Frame(main_container, bg='#bcfef9')
right_frame = tk.Frame(main_container, bg=BG_COLOR)
right_frame.pack(side='right')
# Import Image Button
tk.Button(left_frame, text="Import Image", command=self.import_image, height=2, width=15, bg='peach puff', font=GlobalVars.BUTTON_FONT).pack(pady=10)
# Simplified instructions
self.label = tk.Label(left_frame, text="You may now draw your ribbon :)", wraplength=300, font=GlobalVars.TEXT_FONT, bg='#bcfef9')
self.label = tk.Label(left_frame, text="You may now draw your ribbon :)", wraplength=300, font=GlobalVars.TEXT_FONT, bg=BG_COLOR)
self.label.pack(pady=10)
# Drawing area (doubled size for display)
@ -1009,10 +977,10 @@ class Screen8(tk.Frame):
self.canvas.pack(pady=10)
# Pen size frame
pen_size_frame = tk.Frame(right_frame, bg='#bcfef9')
pen_size_frame = tk.Frame(right_frame, bg=BG_COLOR)
pen_size_frame.pack(pady=10)
tk.Label(pen_size_frame, text="Pen Size", font=GlobalVars.TEXT_FONT, bg='#bcfef9').pack()
tk.Label(pen_size_frame, text="Pen Size", font=GlobalVars.TEXT_FONT, bg=BG_COLOR).pack()
# Pen size buttons
pen_sizes = [(".", 1), ("*", 2), ("", 3), ("", 4), ("", 5)]
@ -1021,10 +989,10 @@ class Screen8(tk.Frame):
height=2, width=5, bg='peach puff').pack(pady=2)
# Pen color frame
pen_color_frame = tk.Frame(right_frame, bg='#bcfef9')
pen_color_frame = tk.Frame(right_frame, bg=BG_COLOR)
pen_color_frame.pack(pady=10)
tk.Label(pen_color_frame, text="Pen Color", font=GlobalVars.TEXT_FONT, bg='#bcfef9').pack()
tk.Label(pen_color_frame, text="Pen Color", font=GlobalVars.TEXT_FONT, bg=BG_COLOR).pack()
# Color buttons
colors = ['black', 'gray', 'white']
@ -1152,7 +1120,7 @@ class Screen8(tk.Frame):
# txt update
class Screen9(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, bg='#bcfef9')
tk.Frame.__init__(self, master, bg=BG_COLOR)
master.add_home_button(self)
# Assume there's a method to manage the text entry
tk.Button(self, text="Done", command=lambda: master.switch_frame(Screen10), height=3, width=30, bg='peach puff', font=GlobalVars.BUTTON_FONT).pack(pady=10)
@ -1162,15 +1130,15 @@ class Screen10(tk.Frame):
def __init__(self, master):
GlobalVars.selected_user = None # Reset the selected user
tk.Frame.__init__(self, master, bg='#bcfef9')
tk.Label(self, text="Thank you!", bg='#bcfef9', font=('Helvetica', 48)).pack()
tk.Frame.__init__(self, master, bg=BG_COLOR)
tk.Label(self, text="Thank you!", bg=BG_COLOR, font=('Helvetica', 48)).pack()
tk.Button(self, text="Done", command=lambda: master.switch_frame(Screen0), height=3, width=30, bg='peach puff', font=GlobalVars.BUTTON_FONT).pack(pady=10)
# Sticker or tag?
class Screen11(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, bg='#bcfef9')
tk.Frame.__init__(self, master, bg=BG_COLOR)
master.add_home_button(self)
# Instructions
self.label = tk.Label(self, text="Which type of tag would you like to design?",
@ -1180,9 +1148,9 @@ class Screen11(tk.Frame):
# Button functions
def select_ribbon():
global print_type
global print_type
print_type = 'ribbon'
master.switch_frame(Screen8)
master.switch_frame(ScreenRibbonSize)
def select_sticker():
global print_type
@ -1195,10 +1163,106 @@ class Screen11(tk.Frame):
tk.Button(self, text="Ribbon tag", command=select_ribbon, height=4, width=39, bg='peach puff', font=GlobalVars.BUTTON_FONT).pack(side='top', pady=30)
# Select ribbon size
class ScreenRibbonSize(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, bg=BG_COLOR)
master.add_home_button(self)
# Get ribbon config
self.ribbon_width = CONFIG.get("ribbon", {}).get("width", 450)
self.margin = CONFIG.get("ribbon", {}).get("margin", 50)
self.printable_width = self.ribbon_width - 50 # 25px margin each side
# Title
title_label = tk.Label(self, text="Select your ribbon size:",
font=GlobalVars.TEXT_FONT, bg=BG_COLOR)
title_label.pack(pady=30)
# Container for the three size options
options_frame = tk.Frame(self, bg=BG_COLOR)
options_frame.pack(pady=20)
# Size options with multipliers
sizes = [
('Small', 0.5, 'Half-height drawing'),
('Medium', 1.0, 'Square drawing'),
('Large', 1.5, 'Tall drawing')
]
for size_name, multiplier, description in sizes:
self.create_size_option(options_frame, size_name, multiplier, description, master)
def create_size_option(self, parent, size_name, multiplier, description, master):
# Frame for each option
option_frame = tk.Frame(parent, bg=BG_COLOR, padx=20)
option_frame.pack(side='left', padx=20)
# Calculate dimensions for this size (based on printable width)
drawing_height = int(self.printable_width * multiplier)
qr_scale = self.printable_width // 21
qr_size = 21 * qr_scale
# Total height includes top/bottom margins
total_height = self.margin + drawing_height + qr_size + self.margin
# Create preview canvas (scaled down for display)
preview_scale = 0.3
preview_width = int(self.ribbon_width * preview_scale)
preview_height = int(total_height * preview_scale)
margin_preview = int(self.margin * preview_scale)
drawing_preview_height = int(drawing_height * preview_scale)
qr_preview_size = int(qr_size * preview_scale)
side_margin_preview = int(25 * preview_scale) # 25px side margins
canvas = tk.Canvas(option_frame, width=preview_width, height=preview_height,
bg='white', highlightthickness=2, highlightbackground='gray')
canvas.pack(pady=10)
# Draw top margin area
canvas.create_rectangle(0, 0, preview_width, margin_preview,
fill='#f5f5f5', outline='')
# Draw drawing area (with side margins indicated)
canvas.create_rectangle(side_margin_preview, margin_preview,
preview_width - side_margin_preview, margin_preview + drawing_preview_height,
fill='#e0e0e0', outline='black')
canvas.create_text(preview_width // 2, margin_preview + drawing_preview_height // 2,
text="Drawing", font=('Helvetica', 10))
# Draw QR indicator (centered)
qr_x = (preview_width - qr_preview_size) // 2
qr_y = margin_preview + drawing_preview_height
canvas.create_rectangle(qr_x, qr_y,
qr_x + qr_preview_size, qr_y + qr_preview_size,
fill='#808080', outline='black')
canvas.create_text(preview_width // 2, qr_y + qr_preview_size // 2,
text="QR", font=('Helvetica', 8), fill='white')
# Draw bottom margin area
canvas.create_rectangle(0, qr_y + qr_preview_size, preview_width, preview_height,
fill='#f5f5f5', outline='')
# Size name and dimensions button (show printable dimensions)
button_text = f"{size_name}\n{self.printable_width}×{drawing_height}px"
btn = tk.Button(option_frame, text=button_text,
command=lambda s=size_name.lower(): self.select_size(s, master),
height=3, width=15, bg='peach puff', font=GlobalVars.BUTTON_FONT)
btn.pack(pady=10)
# Description label
desc_label = tk.Label(option_frame, text=description,
font=('Helvetica', 12), bg=BG_COLOR)
desc_label.pack()
def select_size(self, size, master):
GlobalVars.ribbon_size = size
master.switch_frame(Screen8)
# after QR scanned for lookup
class Screen12(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, bg='#bcfef9')
tk.Frame.__init__(self, master, bg=BG_COLOR)
print(f"Initializing Screen12 with QR code value: {GlobalVars.qr_code_value}")
@ -1223,10 +1287,10 @@ class Screen12(tk.Frame):
def display_error(self, message):
print(f"Displaying error: {message}")
error_frame = tk.Frame(self, bg='#bcfef9')
error_frame = tk.Frame(self, bg=BG_COLOR)
error_frame.pack(expand=True, fill='both', padx=20, pady=20)
tk.Label(error_frame, text=message, bg='#bcfef9', font=GlobalVars.TEXT_FONT, wraplength=500).pack(pady=50)
tk.Label(error_frame, text=message, bg=BG_COLOR, font=GlobalVars.TEXT_FONT, wraplength=500).pack(pady=50)
# Add "Go Back" button for error cases
tk.Button(error_frame, text="Go Back", command=lambda: self.master.switch_frame(Screen0),
@ -1245,11 +1309,11 @@ class Screen12(tk.Frame):
def display_content(self, message_content, image_path, replies):
# Main content frame
content_frame = tk.Frame(self, bg='#bcfef9')
content_frame = tk.Frame(self, bg=BG_COLOR)
content_frame.pack(expand=True, fill='both', padx=20, pady=10)
# Left column: Image
left_column = tk.Frame(content_frame, bg='#bcfef9')
left_column = tk.Frame(content_frame, bg=BG_COLOR)
left_column.pack(side='left', fill='both', expand=False)
if image_path and os.path.exists(image_path):
@ -1257,7 +1321,7 @@ class Screen12(tk.Frame):
img = Image.open(image_path)
img.thumbnail((550, 550)) # Slightly smaller than before
photo = ImageTk.PhotoImage(img)
img_label = tk.Label(left_column, image=photo, bg='#bcfef9')
img_label = tk.Label(left_column, image=photo, bg=BG_COLOR)
img_label.image = photo # Keep a reference
img_label.pack(expand=True, fill='both')
print(f"Displayed image: {image_path}")
@ -1269,12 +1333,12 @@ class Screen12(tk.Frame):
print("No image path provided")
# Right column: Scrollable text
right_column = tk.Frame(content_frame, bg='#bcfef9')
right_column = tk.Frame(content_frame, bg=BG_COLOR)
right_column.pack(side='right', fill='both', padx=10, expand=True)
canvas = tk.Canvas(right_column, bg='#bcfef9')
canvas = tk.Canvas(right_column, bg=BG_COLOR)
scrollbar = ttk.Scrollbar(right_column, orient="vertical", command=canvas.yview)
scrollable_frame = tk.Frame(canvas, bg='#bcfef9')
scrollable_frame = tk.Frame(canvas, bg=BG_COLOR)
scrollable_frame.bind(
"<Configure>",
@ -1288,18 +1352,18 @@ class Screen12(tk.Frame):
text_content = message_content.get('content', {}).get('text', '')
# Remove markdown image syntax
text_content = re.sub(r'!\[.*?\]\(.*?\)', '', text_content).strip()
tk.Label(scrollable_frame, text=text_content, wraplength=650, justify='left', bg='#bcfef9', font=("Helvetica", 28)).pack(pady=5, padx=(5, 0))
tk.Label(scrollable_frame, text=text_content, wraplength=650, justify='left', bg=BG_COLOR, font=("Helvetica", 28)).pack(pady=5, padx=(5, 0))
# Display replies
if replies:
tk.Label(scrollable_frame, text="Replies:", wraplength=650, justify='left', bg='#bcfef9', font=("Helvetica", 24, "bold")).pack(pady=(20, 5), padx=(5, 0))
tk.Label(scrollable_frame, text="Replies:", wraplength=650, justify='left', bg=BG_COLOR, font=("Helvetica", 24, "bold")).pack(pady=(20, 5), padx=(5, 0))
for reply in replies:
author_id = reply.get('value', {}).get('author', 'Unknown')
author_alias = self.get_alias(author_id)
author_display = f"{author_alias} ({author_id})" if author_alias else author_id
reply_text = reply.get('value', {}).get('content', {}).get('text', '')
tk.Label(scrollable_frame, text=f"{author_display}:", wraplength=650, justify='left', bg='#bcfef9', font=("Helvetica", 20, "bold")).pack(pady=(10, 0), padx=(5, 0))
tk.Label(scrollable_frame, text=reply_text, wraplength=650, justify='left', bg='#bcfef9', font=("Helvetica", 18)).pack(pady=(0, 10), padx=(5, 0))
tk.Label(scrollable_frame, text=f"{author_display}:", wraplength=650, justify='left', bg=BG_COLOR, font=("Helvetica", 20, "bold")).pack(pady=(10, 0), padx=(5, 0))
tk.Label(scrollable_frame, text=reply_text, wraplength=650, justify='left', bg=BG_COLOR, font=("Helvetica", 18)).pack(pady=(0, 10), padx=(5, 0))
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
@ -1316,7 +1380,7 @@ class Screen12(tk.Frame):
class Screen13(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, bg='#bcfef9')
tk.Frame.__init__(self, master, bg=BG_COLOR)
master.add_home_button(self)
# Create a container to hold the widgets
@ -1333,7 +1397,7 @@ class Screen13(tk.Frame):
# go ahead and print the thing
def printy(self):
global print_type, migration_ticket, MIGRATION_ITEMS_DIR
global print_type
# Specify the path to your image file
path_to_image = "/home/trav/Documents/custodiosk/freeze_frame.jpg"
@ -1346,23 +1410,17 @@ class Screen13(tk.Frame):
# make ssb post
key = addtoDB.addToSSB(path_to_image, info_text, 1)
# If we have a migration ticket, append the message ID to the description file
#if migration_ticket:
# description_file_path = os.path.join(MIGRATION_ITEMS_DIR, f"{migration_ticket}.txt")
# try:
# with open(description_file_path, 'a') as f:
# f.write(f"\n\n\n{key}")
# print(f"Appended message ID to description file: {description_file_path}")
# except Exception as e:
# print(f"Error appending message ID to description file: {e}")
# ssb give! (make sure we have a UID to give to first)
if GlobalVars.selected_user and GlobalVars.selected_user.strip() != "":
nothing = addtoDB.addToSSB(GlobalVars.selected_user, key, 2)
# gonna need to revise this later but for now the textile tags are always full-size QR:
# Calculate QR scale based on print type
if print_type == "ribbon":
QRscale = 7
# Pixel-perfect QR: ribbon_width // 21 for version 1 QR
ribbon_width = CONFIG.get("ribbon", {}).get("width", 450)
printable_width = ribbon_width - 50 # 25px margin each side
QRscale = printable_width // 21
# else QRscale stays as set from imported image or master.QRscale
# Create qr code
#from https://ourcodeworld.com/articles/read/554/how-to-create-a-qr-code-image-or-svg-in-python
@ -1378,15 +1436,21 @@ class Screen13(tk.Frame):
# Create an image from the QR Code instance
img = qr.make_image()
# Resize QR to fit layout (handles version auto-upgrade from fit=True)
if print_type == "ribbon":
target_qr_size = 21 * QRscale # Integer math: 21 * 19 = 399
img = img.resize((target_qr_size, target_qr_size), Image.NEAREST)
whereToSaveQR = 'qr.jpg'
img.save(whereToSaveQR)
# compose image for tag
drawing = Image.open("drawing.png") # drawing
qr = Image.open("qr.jpg") # qr
qr_img = Image.open("qr.jpg") # qr
#### merge em
#### merge em
## if sticker
if print_type == "sticker":
## if we didn't custom set X/Y, set to defaults
@ -1395,21 +1459,49 @@ class Screen13(tk.Frame):
QRY=217
merged_image = Image.new('L', (675, 375), "white")
merged_image.paste(drawing, (0, 8))
merged_image.paste(qr, (QRX, QRY+8)) # we add 8 because this is slightly off from when we drew it
merged_image.paste(qr_img, (QRX, QRY+8)) # we add 8 because this is slightly off from when we drew it
merged_image.save("merged_image.png")
# if ribbon
if print_type == "ribbon":
merged_image = Image.new('L', (375, 675), "white")
merged_image.paste(drawing, (25, 100)) # set it 25 in because that's the border
merged_image.paste(qr, (42, 279)) # paste without mask
# Get config values
ribbon_width = CONFIG.get("ribbon", {}).get("width", 450)
margin = CONFIG.get("ribbon", {}).get("margin", 50)
printable_width = ribbon_width - 50 # 25px side margins
multiplier = {'small': 0.5, 'medium': 1.0, 'large': 1.5}.get(GlobalVars.ribbon_size, 1.0)
drawing_height = int(printable_width * multiplier)
# Load scan-tag image
scan_tag = Image.open("scan-tag.png").convert("L")
scan_tag_height = scan_tag.height
# Pixel-perfect QR (sized to printable width)
qr_scale = printable_width // 21
qr_size = 21 * qr_scale
# Total label height includes top/bottom margins and scan-tag
total_height = margin + drawing_height + scan_tag_height + qr_size + margin
# Create full-width image with margins
merged_image = Image.new('L', (ribbon_width, total_height), "white")
# Paste drawing 25px in from left, margin down from top
merged_image.paste(drawing, (25, margin))
# Paste scan-tag centered below drawing
scan_tag_x = (ribbon_width - scan_tag.width) // 2
merged_image.paste(scan_tag, (scan_tag_x, margin + drawing_height))
# Center QR horizontally within the full ribbon width, below scan-tag
qr_x = (ribbon_width - qr_size) // 2
merged_image.paste(qr_img, (qr_x, margin + drawing_height + scan_tag_height))
merged_image.save("merged_image.png")
image = Image.open("merged_image.png")
# rotated_image = image.transpose(Image.ROTATE_270) # Transpose and rotate 90 degrees, old version when we weren't doing ribbon vertical
# rotated_image.save("merged_image.png")
# Get the ZPL code for the image
zpl_code = tozpl.print_to_zpl("merged_image.png")
if print_type == "ribbon":
zpl_code = tozpl.print_to_zpl("merged_image.png",
print_width=ribbon_width,
label_length=total_height)
else:
zpl_code = tozpl.print_to_zpl("merged_image.png")
#save the zpl
# Open the file in write mode
@ -1424,15 +1516,14 @@ class Screen13(tk.Frame):
# print to sticker printer
if print_type == "sticker":
try:
result = subprocess.Popen('lpr -P sticker_printer -o raw to_print.zpl', shell=True, stdout=subprocess.PIPE, )
# print('no print')
result = subprocess.Popen(f'lp -d {CONFIG["printers"]["sticker"]} -o raw to_print.zpl', shell=True, stdout=subprocess.PIPE, )
except:
print('traceback.format_exc():\n%s' % traceback.format_exc())
exit()
# or print to tag printer:
if print_type == "ribbon":
try:
result = subprocess.Popen('lpr -P tag-printer -o raw to_print.zpl', shell=True, stdout=subprocess.PIPE, )
result = subprocess.Popen(f'lp -d {CONFIG["printers"]["ribbon"]} -o raw to_print.zpl', shell=True, stdout=subprocess.PIPE, )
except:
print('traceback.format_exc():\n%s' % traceback.format_exc())
exit()
@ -1481,7 +1572,7 @@ class Screen14(tk.Frame):
self.qreader = QReader()
# Prefer the external NexiGo USB webcam when available
self.cap = cv2.VideoCapture(get_preferred_camera_index("NexiGo"))
self.cap = cv2.VideoCapture(get_preferred_camera_index(CONFIG["camera"]["preferred_name"]))
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
@ -1524,59 +1615,6 @@ class Screen14(tk.Frame):
super().destroy()
# New screen for Create Migration Item
class Screen15(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, bg='#bcfef9')
# Create a main container frame
main_frame = tk.Frame(self, bg='#bcfef9')
main_frame.place(relx=0.5, rely=0.5, anchor='center')
# Add the home button to the main frame (this will be at the top)
master.add_home_button(self)
# Instructions (now in the centered main frame)
tk.Label(main_frame, text="If you're archiving an Item of Migration, please enter the last 3 digits of your ticket number. Otherwise, you can leave this blank.", font=GlobalVars.TEXT_FONT, bg='#bcfef9', wraplength=500).pack(pady=20)
# Text box for entering ticket number (now in the centered main frame)
self.ticket_entry = tk.Entry(main_frame, font=GlobalVars.TEXT_FONT)
self.ticket_entry.pack(pady=20)
# Done button (now in the centered main frame)
tk.Button(main_frame, text="Done", command=self.process_ticket, height=3, width=30, bg='peach puff', font=GlobalVars.BUTTON_FONT).pack(pady=20)
# Set focus to the ticket entry field
self.ticket_entry.focus_set()
def process_ticket(self):
global migration_ticket
ticket = self.ticket_entry.get().strip()
if ticket == "":
# If the field is blank, proceed without setting migration_ticket
migration_ticket = ""
self.master.switch_frame(Screen1) # Go to the Scuttlebutt username selection screen
elif re.match(r'^\d{1,4}$', ticket):
if self.is_ticket_available(ticket):
migration_ticket = ticket
self.master.switch_frame(Screen1) # Go to the Scuttlebutt username selection screen
else:
messagebox.showerror("Invalid Input", "This migration number is already in use. Please try a different number.")
else:
messagebox.showerror("Invalid Input", "Please check your number and try again. It should be a 1-4 digit number or left blank.")
def is_ticket_available(self, ticket):
# Get all files in the migration items directory
files = os.listdir(MIGRATION_ITEMS_DIR)
# Strip file extensions and check if the ticket number exists
existing_numbers = [os.path.splitext(f)[0] for f in files]
return ticket not in existing_numbers
if __name__ == "__main__":
app = Kiosk()
app.mainloop()