import socket import threading import tkinter as tk from tkinter import ttk, scrolledtext, messagebox from datetime import datetime import csv import os from pathlib import Path import requests import json # ─── Defaults ─────────────────────────────────────────────────────────────── DEFAULT_HOST = "198.162.1.180" DEFAULT_PORT = 9100 # Standard raw ZPL port for Zebra printers CONNECT_TIMEOUT = 2 # seconds SEND_TIMEOUT = 3 # seconds DEFAULT_PREFIX = "172.16.12." # Default ZPL template shown on launch DEFAULT_ZPL = """\ ^XA ^CF0,40 ^FO50,50^FDHello, World!^FS ^FO50,110^FDLine two text^FS ^XZ""" prefixlist = ["172.16.12.", "192.168.1."] suffixlist = ["158","180","80","153","154"] IPlist = ["172.16.12.80", "192.168.1.180"] CONFIG_FILE = "settings.json" URL_LOCATION = "http://projectcherenkova.com/" LOGO_FILE = "logodata2.txt" app_entries = {} entrylst = [] # ─── Main App ──────────────────────────────────────────────────────────────── class ZPLSenderApp(tk.Tk): def __init__(quickp): super().__init__() quickp.title("ZPL Label Sender") quickp.resizable(True, True) quickp.minsize(600, 720) quickp._build_ui() quickp._apply_theme() # ── UI Construction ────────────────────────────────────────────────────── def _build_ui(quickp): quickp.columnconfigure(2, weight=1) quickp.rowconfigure(2, weight=1) # ZPL editor expands # ── Connection bar ────────────────────────────────────────────────── conn_frame = ttk.LabelFrame(quickp, text="Printer Connection", padding=10) conn_frame.grid(row=0, column=0, sticky="w", padx=14, pady=(14, 6)) conn_frame.columnconfigure(1, weight=1) ttk.Label(conn_frame, text="IP / Host:").grid(row=0, column=0, sticky="w") quickp.host_var = tk.StringVar(value=DEFAULT_HOST) ttk.Combobox(conn_frame, values=IPlist, state="normal", textvariable=quickp.host_var, width=22).grid( row=0, column=0, sticky="w", padx=(56, 16)) ttk.Label(conn_frame, text="Port:").grid(row=0, column=1, sticky="w") quickp.port_var = tk.StringVar(value=str(DEFAULT_PORT)) ttk.Entry(conn_frame, textvariable=quickp.port_var, width=7).grid( row=0, column=1, sticky="w", padx=(30, 16)) quickp.test_btn = ttk.Button(conn_frame, text="Test Connection", command=quickp._test_connection) quickp.test_btn.grid(row=0, column=2, columnspan=2, sticky="ew", padx=(6,45)) ttk.Label(conn_frame, text="Prefix:").grid(row=2, column=0, sticky="w", padx=(0,2)) quickp.prefix_var = tk.StringVar(value=str(DEFAULT_PREFIX)) ttk.Combobox(conn_frame, values=prefixlist, state="normal",width=12).grid(row=2, column=0, sticky="w", padx=(38, 6)) ttk.Label(conn_frame, text="Suffix:").grid(row=2, column=1, sticky="w", padx=(0,2)) ttk.Combobox(conn_frame, values=suffixlist, state="normal", width=3).grid(row=2,column=1, sticky="w", padx=(75,6)) # Connection status indicator quickp.conn_status = ttk.Label(conn_frame, text="● Not tested", foreground="#888888") quickp.conn_status.grid(row=1, column=0, columnspan=5, sticky="e", pady=(6, 6), padx=(6,6)) # ── Options ────────────────────────────────────────────────────── options_frame = ttk.LabelFrame(quickp, text="Options", padding=10) options_frame.grid(row=2, column=0, sticky="nsew", padx=14, pady=6) options_frame.columnconfigure(0, weight=1) options_frame.rowconfigure(0, weight=1) options_frame.datecheck_var = tk.BooleanVar(value=True) datecheck_var = tk.BooleanVar(value=True) options_date = ttk.Checkbutton(quickp, text="Options", variable=datecheck_var, padding=10) datecheck_var.set(True) #Notebook______________________________________ tabControl = ttk.Notebook(quickp, padding = 6) tabControl.grid(row=3, column=0, sticky="nsew") tabControl.columnconfigure(0, weight=1) tab1 = ttk.Frame(tabControl) tab2 = ttk.Frame(tabControl) tabControl.add(tab1, text ='Quicklabel') tabControl.add(tab2, text ='Tab 2') # tabControl.pack(expand = 1) ttk.Label(tab1, text ="Title").grid(column = 0, row = 0, sticky="w", padx = 6, pady = 3) ttk.Label(tab2, text ="McMasterizer").grid(column = 0,row = 1, padx = 6,pady = 3) #Tab1__________________________________________ NAME="Hank" TITLE="Backup Parts (Contaminated)" SUBHEADER="SUBHEADER" NOTES = "NOTES" QRCODE = """http://google.com""" Logolist = ["Trio", "Hank", "HankTrio", "Meatboy"] ttk.Label(tab1, text="Name:").grid(row=0, column=0, sticky="w", padx=6) quickp.name_var = tk.StringVar(value=NAME) name_var = tk.StringVar(value="NAME") ttk.Entry(tab1, textvariable=quickp.name_var, width=17).grid( row=0, column=0, sticky="w", padx=(75, 16)) print(name_var) print("namevar!") ttk.Label(tab1, text="Title:").grid(row=1, column=0, sticky="w", padx=6) quickp.title_var = tk.StringVar(value=TITLE) TITLE = "TITLE" title_var = tk.StringVar(value=str(TITLE)) ttk.Entry(tab1, textvariable=quickp.title_var, width=17).grid( row=1, column=0, sticky="w", padx=(75, 16)) ttk.Label(tab1, text="Subheader:").grid(row=2, column=0, sticky="w", padx=6) quickp.subheader_var = tk.StringVar(value=SUBHEADER) SUBHEADER = "name" subheader_var = tk.StringVar(value=str(SUBHEADER)) ttk.Entry(tab1, textvariable=quickp.subheader_var, width=17).grid(row=2, column=0, sticky="w", padx=(75, 16)) print(quickp.subheader_var) ttk.Label(tab1, text="notes:").grid(row=3, column=0, sticky="w", padx=6) quickp.notes_var = tk.StringVar(value=NOTES) NOTES = "NOTES" notes_var = tk.StringVar(value=NOTES) ttk.Entry(tab1, textvariable=quickp.notes_var, width=17).grid( row=3, column=0, sticky="w", padx=(75, 16)) ttk.Label(tab1, text="QR_URL:").grid(row=0, column=1, sticky="w", padx=6) quickp.QR_VAR = tk.StringVar(value=QRCODE) QR_VAR = tk.StringVar(value=str(QRCODE)) ttk.Entry(tab1, textvariable=quickp.QR_VAR, width=17).grid( row=0, column=1, sticky="w", padx=(150, 16)) ttk.Label(tab1, text="Logo").grid(row=1, column=1, sticky="w", padx=(0,2)) quickp.logo_var = tk.StringVar(value=str("Hank")) logo_var = tk.StringVar(value=Logolist[1]) ttk.Combobox(tab1, values=Logolist, textvariable = quickp.logo_var, state="normal",width=12).grid(row=1, column=1, sticky="w", padx=(50, 6)) ttk.Label(conn_frame, text="Suffix:").grid(row=2, column=1, sticky="w", padx=(12,2)) # ── Send controls ─────────────────────────────────────────────────── send_frame = ttk.Frame(quickp, padding=(14, 4, 14, 4)) send_frame.grid(row=4, column=0, sticky="ew") send_frame.columnconfigure(0, weight=1) quickp.copies_var = tk.IntVar(value=1) ttk.Label(send_frame, text="Copies:").grid(row=0, column=1, padx=(0, 4)) ttk.Spinbox(send_frame, from_=1, to=99, textvariable=quickp.copies_var, width=4).grid(row=0, column=2, padx=(0, 10)) quickp.send_btn = ttk.Button(send_frame, text="▶ Send to Printer", command=quickp._send, style="Accent.TButton") quickp.send_btn.grid(row=0, column=3, sticky="e") quickp.test_btn = ttk.Button(send_frame, text="test", command=quickp._getform, style="Accent.TButton") quickp.test_btn.grid(row=0, column=0, sticky="w") # ── Log ───────────────────────────────────────────────────────────── log_frame = ttk.LabelFrame(quickp, text="Log", padding=6) log_frame.grid(row=5, column=0, sticky="ew", padx=14, pady=(4, 14)) log_frame.columnconfigure(0, weight=1) quickp.log = tk.Text(log_frame, height=6, state="disabled", font=("Courier New", 9), relief="flat", background="#1e1e1e", foreground="#cccccc", insertbackground="white") quickp.log.grid(row=0, column=0, sticky="ew") sb = ttk.Scrollbar(log_frame, command=quickp.log.yview) sb.grid(row=0, column=1, sticky="ns") quickp.log["yscrollcommand"] = sb.set ttk.Button(log_frame, text="Clear Log", command=quickp._clear_log).grid(row=1, column=0, sticky="w", pady=(4, 0)) #Quit-------------------------------------------------------------- quit_frame = ttk.LabelFrame(quickp, text="Quit", padding=6) quit_frame.grid(row=6, column=0, sticky="ew", padx=14, pady=(4, 14)) quit_frame.columnconfigure(0, weight=1) ttk.Button(quit_frame, text="QUIT", command=quickp.quit_script).grid(row=6, column=0, sticky="w", pady=(4, 0)) # ── Theme ──────────────────────────────────────────────────────────────── def _apply_theme(quickp): style = ttk.Style(quickp) try: style.theme_use("clam") except tk.TclError: pass style.configure("Accent.TButton", font=("Segoe UI", 10, "bold")) #Hank>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> def _getform(quickp): formslist = ["title\n", "name\n", "time\n", "date\n", "subheader\n", "notes\n", "QR URL\n", "logo_var\n", "logozpl\n"] print("YAY RESET:" + "".join(formslist)) formslist[0] = quickp.title_var.get() formslist[1] = quickp.name_var.get() formslist[2] = datetime.now().strftime("%x") formslist[3] = datetime.now().strftime("%H:%M:%S") formslist[4] = quickp.subheader_var.get() formslist[5] = quickp.notes_var.get() formslist[6] = quickp.QR_VAR.get() formslist[7] = quickp.logo_var.get() logo_entry = quickp.logo_var.get() if logo_entry == "HankTrio": logozpl = quickp.readstringfromURL(URL_LOCATION,"logodata2.txt",10) elif logo_entry == "Trio": logozpl = quickp.readstringfromURL(URL_LOCATION,"logodata2.txt",6) elif logo_entry == "Hank": logozpl = quickp.readstringfromURL(URL_LOCATION,"logodata2.txt",2) elif logo_entry == "None": logozpl = "" else: quickp._log(quickp, "No Logo Data") logozpl = """ ^FO420,250^GFA,5000,5000,25,,::gJ03C,gJ07F,gI01FFC,gI07FFE,gH01JF8,gH07JFE,gH0LF8,gG03LFC,gG07MF,g01NFC,g07OF,Y01PF8,Y03PFE,Y0RF8,X03RFC,X0TF,W01TFC,W07UF,V01VF8,V07VFE,V0XF8,U03XFC,U0gF,T03gFC,T0gHF,S01gHF8,S07gHFE,S0gJF8,R03gJFC,R0gLF8,Q03gLFC,Q07gMF,P01gNF8,P07gNFE,O01gPF8,O03gPFE,O0gRF,N03gRFC,N0gTF,M01gTFC,M07gTFE,L01gVF8,L07gVFE,L0gXF8,K03gXFC,K0hF,J03hFC,J0hHF,J0NFEgJ07NF8,I01OFgJ07NF8,::::::::::::::::::::::::::I01NFEgJ07NF8,I01OFU018M0OF8,I01WFCL01WF8,I01WF8L01WF8,:::::::::::::I01LFK01KF8L01WF8,I01JF8N01IF8L01WF8,I01IFQ01FF8L01WF8,I01FF8R01FCL01WF8,I01FF8S038L01WF8,I01FF8T08L01WF8,I01FF8T02L01WF8,I01FF8T03CK01WF8,I01FF8T03F8J01WF8,I01FF8T03FEJ01WF8,I01FF8T03FFCI01WF8,I01FF8T03IFI01WF8,I01FF8T03IFE001WF8,I01FF8T03JF801WF8,I01FF8T03JFE01WF8,I01FF8T03KFC1WF8,I01FF8T03LF9WF8,I01FF8T03LFCWF8,I01FF8T03LFC3VF8,I01FF8T03LFC03UF8,I01FF8T03LFC007TF8,I01FF8T03LFCI0QF0FF8,I01FF8T03LFCJ07NF00FF8,I01FF8T03LFCK0MFI0FF8,I01FF8T03LFCM0IFK0FF8,I01FF8T03LFCU0FF8,::::::::::::::::::::::I01FFCT03LFCT03FF8,I01IFT03LFCT07FF8,I01IF8S03LFCS01IF8,I01IFES03LFCS07IF8,J0JF8R03LFER01JF8,J07IFER03LFCR03IFE,J03JFR03LF8R0JFC,K0JFCQ03KFER03JF,K03JFQ03KF8R0JFC,L0JFCP03KFR01JF,L03IFEP03JFCR07IFE,L01JF8O03JFR01JF8,M07IFEO03IFCR07JF,M01JF8N03IFS0JF8,N0JFCN03FFER03JF,N03JFN03FF8R0JFC,O0JFCM03FFR01JF8,O03JFM03F8R07IFE,O01JF8L03FR01JF8,P07IFEL03CR07IFE,P01JFL03S0JFC,Q0JFEK02R03JF,Q03JFX0JFC,R0JFCV03JF,R03JFV07IFE,R01JF8T01JF8,S07IFET07IFE,S01JF8S0JF8,T07IFER03JF,T03JFR0JFC,U0JFCP03JF,U03JFP0JFE,V0JFCN01JF,V07IFEN07IFE,V01JF8M0JF8,W07IFEL03JF,W03JF8K0JFC,X07IFCJ03JF,X03JFJ07IFC,Y0JF8001JF8,Y07IFE007IFE,Y01JF81JF8,g07IFE3IFE,g01NFC,gG0NF,gG03LFC,gH0LF,gH03JFE,gH01JF8,gI07FFE,gI01FFC,gJ07F,gJ03C,,::^FS """ formslist[8] = logozpl formslabellist = ["\nTITLE:" + formslist[0],"\nNAME:"+ formslist[1],"\nTIME:"+ formslist[2],"\nDATE:"+ formslist[3],"\nSUBHEADER:"+ formslist[4],"\nNOTES:"+ formslist[5],"\nQR URL:"+ formslist[6],"\nLOGO_VAR:"+ formslist[7],"\nLOGOZPL:\n"+ formslist[8]] print("Formslist:" + "".join(formslist)) print("Forms LABEL list:\n" + "".join(formslabellist)) return(formslist) def _get_entries(quickp): geometry = quickp.geometry() app_entries = {} app_entries['name_var'] = quickp.name_var.get() app_entries['title_var'] = quickp.title_var.get() app_entries['subheader_var'] = quickp.subheader_var.get() app_entries['notes_var'] = quickp.notes_var.get() app_entries['logo_var'] = quickp.logo_var.get() app_entries['QR_var'] = quickp.QR_VAR.get() app_entries['host_var'] = quickp.host_var.get() print(app_entries) def save_state(quickp): """Saves the current window geometry and entry contents to a JSON file.""" # Get current window geometry (e.g., "600x400+100+50") wndCfg = { 'Size': [400, 400], 'Pos': [100, 100]} s = wndCfg['Size'] p = wndCfg['Pos'] geometry = quickp.geometry() print("GEO_______________" +str(geometry)) app_entries = {} app_entries['name_var'] = quickp.name_var.get() app_entries['title_var'] = quickp.title_var.get() app_entries['subheader_var'] = quickp.subheader_var.get() app_entries['notes_var'] = quickp.notes_var.get() app_entries['logo_var'] = quickp.logo_var.get() app_entries['QR_var'] = quickp.QR_VAR.get() app_entries['host_var'] = quickp.host_var.get() # Get data from entry widgets entry_data = {name: app_entries.get("0") for name, entry in app_entries.items()} app_state = { 'geometry': geometry, 'entries': entry_data } with open(CONFIG_FILE, 'w') as f: json.dump(app_state, f) quickp.destroy() # Manually destroy the window after saving def load_state(quickp, entries): """Loads the window geometry and entry contents from a JSON file.""" if os.path.exists(CONFIG_FILE): with open(CONFIG_FILE, 'r') as f: try: app_state = json.load(f) # Restore window geometry if 'geometry' in app_state: quickp.geometry(app_state['geometry']) # Restore entry data if 'entries' in app_state: for name, data in app_state['entries'].items(): if name in entries: entries[name].delete(0, tk.END) entries[name].insert(0, data) except json.JSONDecodeError: # Handle case where file is empty or corrupted pass def readstringfromURL(quickp, URL, filename, line): URL_prefix = str(URL) filename = str(filename) namer = URL_prefix + filename print (namer) response = requests.get(namer) stringreturn = response.text.splitlines()[line] print("THIS IS THE LOGO RETURN") print(stringreturn) return(stringreturn) def readzpltemplate(quickp, txt_name,): URL_prefix = URL_LOCATION txtname = str(txt_name) namer = URL_prefix + txtname print("readfromzpltemplate() initializing:\n" + URL_LOCATION+ txt_name + "\n" ) print (namer) response = requests.get(namer) response.raw zpl_URL_list = response.text.splitlines() # ["line1", "line2", "line3"] for item in zpl_URL_list: print(item) return(zpl_URL_list) def make_quicklabel_zpl(quickp): #Form Get namezpl = quickp.name_var.get() #print(name_var.get()) print(namezpl) titlezpl = quickp.title_var.get() namezpl = quickp.name_var.get() datezpl = datetime.now().strftime("%x") timezpl = datetime.now().strftime("%H:%M:%S") subheaderzpl = quickp.subheader_var.get() noteszpl = quickp.notes_var.get() QRzpl = quickp.QR_VAR.get() logochoice = quickp.logo_var.get() if logochoice == "HankTrio": logozpl = quickp.readstringfromURL(URL_LOCATION,"logodata2.txt",10) elif logochoice == "Trio": logozpl = quickp.readstringfromURL(URL_LOCATION,"logodata2.txt",6) elif logochoice == "Hank": logozpl = quickp.readstringfromURL(URL_LOCATION,"logodata2.txt",2) else: quickp._log(quickp, "No Data") logozpl = """^PQ1,0,1,Y^FO420,250^GFA,6216,6216,28,hTFE,:::::::::::::XFBgUFE,XFDgUFE,XFEgUFE,YF7gTFE,YFBgTFE,:YFDgTFE,YFEgTFE,YFE7gSFE,gF7gSFE,gFBgSFE,gF9gSFE,gFDgSFE,gFCgSFE,gFE7gRFE,:YF7F3gRFE,YFBFBgRFE,YFBF9gRFE,YFDFCgRFE,YFCFCgRFE,YFEFE7gQFE,YFE7E3gQFE,gF3E3gQFE,gF3F1gQFE,gF9F1gQFE,gF8F8gQFE,WFEFFCF8gQFE,WFE7FC7C7gPFE,XF3FE7C3gPFE,XF9FE3C3gPFE,XF8FF1E1gPFE,XFC7F1E1gPFE,XFC3F1E0KF80gIFE,XFE3F8F0JFC00gIFE,XFE1F9F07FFEI0gIFE,XFE1JF7FF8I0DgHFE,gMFJ0CgHFE,gLFEJ0C7gGFE,gLF8J0C3gGFE,gGF0F8FFK0C1gGFE,gFE0F03FK0C0gGFE,XFC1E1E03EFEI0C07gFE,XF01E1E01IF801C07gFE,XF01E1E007BFC03C03gFE,VFE301E1E0071BC06201gFE,PF7KFC301E0600B11E0E101gFE,PF9JFE8301E0201003FFC080gFE,PFCJFC8301E0383801FFC0C0gFE,PFE3IF88301E03838007FC0407YFE,QF1FFD88181E0382I07FE0603YFE,QF8FF188181E07840F0E020E03YFE,QFC7E3081C1IFC70F0E03FC01YFE,QFE3218C0C3IFEF060201EI0YFE,QFE6318C1LFEI07L0YFE,RFE1987MFE801F8K0YFE,RFE0DDOFC01FCK0YFE,SF0LF1JF800FEK0YFE,QF9MFE3JF981FEJ01YFE,PFC1MFC7KF83FEJ01YFE,OFE01ELF8LF83FEJ01YFE,OF00F83KF1LF83FE1C001YFE,NFE3FF81JFC7LF83FE3F003YFE,RF83JF9MF83FC7F063YFE,RF87IFE7MF83FDFF0E3YFE,RF0JF8NF83JF0E7YFE,RF0JF3NF83IFE1CgFE,RF1SF83IFC38gFE,QFE3SF83IF8FBgFE,QFE3SF83IF9gHFE,QFE7SF83gLFE,QFCKFBNF81gLFE,:QFDKFBNF81gLFE,QF9FFBFDB7MF81gLFE,QFBFFBFDOFC1gLFE,QFBFFBFFDNFC1gLFE,QF7FFBFD6NFC1gLFE,QF7FFBFCOFC1gLFE,PFEIFB79D7MFC1gLFE,PFEFFDBF7DNFC1gLFE,SFDE95DBMFC0gLFE,SFDE35DDMFC0OFEVFE,SFDAD9DNFC0OFEVFE,SFC6IFEMFC0OFEVFE,SF3KF7LFE07NFEVFE,TF7FFDNFE07NFCVFE,TF7FFDNFE07NFC7UFE,TF7QFE03NFC7UFE,SFD7FC020191C63E01NFC7UFE,TF7FC022110C63E007KFEF83UFE,TF7IFE3F18C63F001KFEF81UFE,WF1E0F1EC43FJ0JFEF80UFE,TF7FF1E071FC03FI01JFCF007TFE,TF7FF1E3F1FC63F003KFDE007TFE,TF7FF1E3118C63F003KF9E007TFE,TF7FF1E0110C63F801KF9C007TFE,WF1E2191C63F800IFBF18007TFE,gMF8007FE7E3I07TFE,gMF8I0F0FE2I07TFE,gMFCJ01FCJ07TFE,gMFCJ07F8J07TFE,gMFCQ07TFE,gMFEQ07TFE,::gNFQ07TFE,:gNF8P07TFE,gNF8K02J07TFE,gNFCK07J07TFE,:gNFEK04J07TFE,gNFEK0CCI07TFE,gOFK08E001UFE,gOF8I018C007UFE,gOFCI011801VFE,gOFEI0F1007VFE,gPF001E303WFE,gPF803020XFE,gPFE02047XFE,gQF860gFE,hTFE,:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::^FS""" subheaderzpl = quickp.subheader_var.get() FZL = quickp.readzpltemplate("QL2x3.txt") quickp._log(FZL) #OptionsGet print(r"Pre Values:") print(FZL[1]) print(str(titlezpl)) print(FZL[5]) print(str(namezpl)) print(FZL[9]) print(str(datezpl)) print(FZL[13]) print(str(subheaderzpl)) print(FZL[17]) print(str(timezpl)) print(FZL[21]) print(str(noteszpl)) print(FZL[25]) print(str(QRzpl)) print(FZL[29]) print(logozpl) zplconcat = FZL[0] + "\n" + titlezpl + "\n" + FZL[4] + "\n" + namezpl + "\n" + FZL[8] + "\n" + datezpl + "\n" + FZL[12] + "\n" + subheaderzpl+ "\n" +FZL[17-1] + "\n" + timezpl + "\n" + FZL[21-1] + "\n" + noteszpl + "\n" + FZL[25-1] + "\n" + QRzpl + "\n" + FZL[29-1] + "\n" + logozpl + "\n" + FZL[33-1] quickp._log(zplconcat) print("this is the ZPL concat:___________") print(zplconcat) #quickp.quit_script() return (zplconcat) def quit_script(quickp): quickp._get_entries() quickp.save_state() #root.destroy() # This closes the window and stops the main loop #>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> # ── Helpers ────────────────────────────────────────────────────────────── def _log(quickp, message: str, level: str = "INFO"): colours = {"INFO": "#cccccc", "OK": "#7ec87e", "WARN": "#f0c060", "ERROR": "#e06060"} ts = datetime.now().strftime("%H:%M:%S") line = f"[{ts}] [{level}] {message}\n" quickp.log.configure(state="normal") quickp.log.insert("end", line, level) quickp.log.tag_configure(level, foreground=colours.get(level, "#cccccc")) quickp.log.see("end") quickp.log.configure(state="disabled") def _clear_log(quickp): quickp.log.configure(state="normal") quickp.log.delete("1.0", "end") quickp.log.configure(state="disabled") def _clear_editor(quickp): quickp.zpl_editor.delete("1.0", "end") quickp._update_char_count() def _update_char_count(quickp, _event=None): txt = quickp.zpl_editor.get("1.0", "end-1c") quickp.char_label.config(text=f"{len(txt)} chars | " f"{len(txt.splitlines())} lines") def _wrap_xaz(quickp): txt = quickp.zpl_editor.get("1.0", "end-1c").strip() if not txt.startswith("^XA"): txt = "^XA\n" + txt if not txt.endswith("^XZ"): txt = txt + "\n^XZ" quickp.zpl_editor.delete("1.0", "end") quickp.zpl_editor.insert("1.0", txt) def _template_menu(quickp): templates = { "Simple Text Label": ( "^XA\n^CF0,40\n^FO50,50^FDSample Label^FS\n^XZ" ), "Text + Barcode (Code128)": ( "^XA\n^CF0,30\n^FO50,30^FDProduct Name^FS\n" "^FO50,80^BY2^BCN,80,Y,N,N^FD123456789^FS\n^XZ" ), "QR Code": ( "^XA\n^FO50,50\n^BQN,2,5\n^FDMAhttps://example.com^FS\n^XZ" ), "Two-column layout": ( "^XA\n^CF0,25\n" "^FO30,30^FDSKU:^FS ^FO130,30^FD00123^FS\n" "^FO30,70^FDQty:^FS ^FO130,70^FD500^FS\n" "^FO30,110^FDDate:^FS ^FO130,110^FD2025-01-01^FS\n^XZ" ), } win = tk.Toplevel(quickp) win.title("Load Template") win.resizable(False, False) ttk.Label(win, text="Choose a template:", padding=10).pack() for name, zpl in templates.items(): def load(z=zpl, w=win): quickp.zpl_editor.delete("1.0", "end") quickp.zpl_editor.insert("1.0", z) quickp._update_char_count() w.destroy() ttk.Button(win, text=name, command=load, width=34).pack( padx=20, pady=3) ttk.Button(win, text="Cancel", command=win.destroy).pack(pady=(6, 14)) # ── Networking ─────────────────────────────────────────────────────────── def _parse_connection(quickp): host = quickp.host_var.get().strip() if not host: raise ValueError("Host/IP cannot be empty.") try: port = int(quickp.port_var.get().strip()) if not (1 <= port <= 65535): raise ValueError except ValueError: raise ValueError("Port must be an integer between 1 and 65535.") return host, port def _test_connection(quickp): try: host, port = quickp._parse_connection() except ValueError as e: messagebox.showerror("Invalid Input", str(e)) return quickp.conn_status.config(text="● Testing…", foreground="#f0c060") quickp.test_btn.state(["disabled"]) quickp._log(f"Testing connection to {host}:{port}…") def worker(): try: with socket.create_connection((host, port), timeout=CONNECT_TIMEOUT): pass quickp.after(0, quickp._on_test_ok, host, port) except Exception as exc: quickp.after(0, quickp._on_test_fail, exc) threading.Thread(target=worker, daemon=True).start() def _on_test_ok(quickp, host, port): quickp.conn_status.config(text=f"● Connected to {host}:{port}", foreground="#7ec87e") quickp.test_btn.state(["!disabled"]) quickp._log(f"Connection to {host}:{port} succeeded.", "OK") def _on_test_fail(quickp, exc): quickp.conn_status.config(text=f"● Failed: {exc}", foreground="#e06060") quickp.test_btn.state(["!disabled"]) quickp._log(f"Connection failed: {exc}", "ERROR") def _send(quickp): zpl = quickp.make_quicklabel_zpl() try: host, port = quickp._parse_connection() except ValueError as e: messagebox.showerror("Invalid Input", str(e)) return #zpl = quickp.zpl_editor.get("1.0", "end-1c").strip() if not zpl: messagebox.showwarning("Empty ZPL", "The ZPL editor is empty.") return copies = quickp.copies_var.get() payload = (zpl + "\n") * copies quickp.send_btn.state(["disabled"]) quickp._log(f"Sending {copies} copy/copies to {host}:{port} " f"({len(payload)} bytes)…") def worker(): try: with socket.create_connection((host, port), timeout=SEND_TIMEOUT) as sock: sock.sendall(payload.encode("utf-8")) quickp.after(0, quickp._on_send_ok, copies) except Exception as exc: quickp.after(0, quickp._on_send_fail, exc) threading.Thread(target=worker, daemon=True).start() def _on_send_ok(quickp, copies): quickp.send_btn.state(["!disabled"]) quickp._log(f"✓ Sent {copies} label(s) successfully.", "OK") def _on_send_fail(quickp, exc): quickp.send_btn.state(["!disabled"]) quickp._log(f"Send failed: {exc}", "ERROR") messagebox.showerror("Send Failed", str(exc)) # ─── Entry Point ───────────────────────────────────────────────────────────── if __name__ == "__main__": app = ZPLSenderApp() app.load_state(app_entries) app.protocol("WM_DELETE_WINDOW", lambda: save_state(quickp, app_entries)) app.mainloop()