2024-07-27 07:53:03 -04:00
import re
import threading
import ipaddress
from datetime import datetime
2024-07-20 07:40:38 -04:00
import tkinter as tk
2024-07-25 16:08:05 -04:00
from tkinter import ttk , Menu
2024-07-20 07:40:38 -04:00
from tkinter . scrolledtext import ScrolledText
2024-07-25 16:08:05 -04:00
import tkinter . font as tkfont
2024-07-27 07:53:03 -04:00
from tkcalendar import DateEntry # Ensure you have: pip install tkcalendar
2024-07-27 10:36:43 -04:00
from tkinter import messagebox
2024-07-25 16:08:05 -04:00
2024-07-27 10:36:43 -04:00
import pyperclip
2024-07-27 07:53:03 -04:00
from epson_print_conf import EpsonPrinter
from find_printers import PrinterScanner
VERSION = " 2.0 "
2024-07-20 07:40:38 -04:00
2024-07-27 10:36:43 -04:00
class ToolTip :
def __init__ ( self , widget , text = ' widget info ' , wrap_length = 10 ) :
self . widget = widget
self . text = text
self . wrap_length = wrap_length
self . tooltip_window = None
widget . bind ( " <Enter> " , self . enter )
widget . bind ( " <Leave> " , self . leave )
def enter ( self , event = None ) :
if self . tooltip_window or not self . text :
return
x , y , width , height = self . widget . bbox ( " insert " )
x + = self . widget . winfo_rootx ( ) + 20
y + = self . widget . winfo_rooty ( ) + 20
self . tooltip_window = tw = tk . Toplevel ( self . widget )
tw . wm_overrideredirect ( True )
# Calculate the position for the tooltip
screen_width = self . widget . winfo_screenwidth ( )
screen_height = self . widget . winfo_screenheight ( )
tw . geometry ( f " + { x } + { y + height + 2 } " ) # Default position below the widget
label = tk . Label ( tw , text = self . wrap_text ( self . text ) , justify = ' left ' ,
background = ' yellow ' , relief = ' solid ' , borderwidth = 1 )
label . pack ( ipadx = 1 )
# Check if the tooltip goes off the screen
tw . update_idletasks ( ) # Ensures the tooltip size is calculated
tw_width = tw . winfo_width ( )
tw_height = tw . winfo_height ( )
if x + tw_width > screen_width : # If tooltip goes beyond screen width
x = screen_width - tw_width - 5
if y + height + tw_height > screen_height : # If tooltip goes below screen height
y = y - tw_height - height - 2 # Position above the widget
tw . geometry ( f " + { x } + { y } " )
def leave ( self , event = None ) :
tw = self . tooltip_window
self . tooltip_window = None
if tw :
tw . destroy ( )
def wrap_text ( self , text ) :
words = text . split ( )
lines = [ ]
current_line = [ ]
for word in words :
if len ( current_line ) + len ( word . split ( ) ) < = self . wrap_length :
current_line . append ( word )
else :
lines . append ( ' ' . join ( current_line ) )
current_line = [ word ]
if current_line :
lines . append ( ' ' . join ( current_line ) )
return ' \n ' . join ( lines )
2024-07-20 07:40:38 -04:00
class EpsonPrinterUI ( tk . Tk ) :
def __init__ ( self ) :
super ( ) . __init__ ( )
2024-07-27 07:53:03 -04:00
self . title ( " Epson Printer Configuration - v " + VERSION )
self . geometry ( " 450x500 " )
self . minsize ( 450 , 500 )
2024-07-26 22:01:38 -04:00
self . printer_scanner = PrinterScanner ( )
2024-07-27 10:36:43 -04:00
self . ip_list = [ ]
self . ip_list_cycle = None
2024-07-26 22:01:38 -04:00
2024-07-20 07:40:38 -04:00
# configure the main window to be resizable
self . columnconfigure ( 0 , weight = 1 )
self . rowconfigure ( 0 , weight = 1 )
2024-07-27 07:53:03 -04:00
FRAME_PAD = 10
PAD = 5
PADX = 5
PADY = 5
2024-07-20 07:40:38 -04:00
# main Frame
2024-07-27 07:53:03 -04:00
main_frame = ttk . Frame ( self , padding = FRAME_PAD )
2024-07-20 07:40:38 -04:00
main_frame . grid ( row = 0 , column = 0 , sticky = ( tk . W , tk . E , tk . N , tk . S ) )
main_frame . columnconfigure ( 0 , weight = 1 )
2024-07-27 10:36:43 -04:00
main_frame . rowconfigure ( 3 , weight = 1 ) # Number of rows
row_n = 0
# [row 0] Container frame for the two LabelFrames Power-off timer and TI Received Time
model_ip_frame = ttk . Frame ( main_frame , padding = PAD )
model_ip_frame . grid ( row = row_n , column = 0 , pady = PADY , sticky = ( tk . W , tk . E ) )
model_ip_frame . columnconfigure ( 0 , weight = 1 ) # Allow column to expand
model_ip_frame . columnconfigure ( 1 , weight = 1 ) # Allow column to expand
# printer model selection
model_frame = ttk . LabelFrame ( model_ip_frame , text = " Printer Model " , padding = PAD )
model_frame . grid ( row = 0 , column = 0 , pady = PADY , padx = ( 0 , PADX ) , sticky = ( tk . W , tk . E ) )
model_frame . columnconfigure ( 0 , weight = 0 )
2024-07-20 07:40:38 -04:00
model_frame . columnconfigure ( 1 , weight = 1 )
self . model_var = tk . StringVar ( )
2024-07-27 10:36:43 -04:00
ttk . Label ( model_frame , text = " Model: " ) . grid ( row = 0 , column = 0 , sticky = tk . W , padx = PADX )
2024-07-20 07:40:38 -04:00
self . model_dropdown = ttk . Combobox ( model_frame , textvariable = self . model_var )
2024-07-26 22:01:38 -04:00
self . model_dropdown [ ' values ' ] = sorted ( EpsonPrinter ( ) . valid_printers )
2024-07-27 07:53:03 -04:00
self . model_dropdown . grid ( row = 0 , column = 1 , pady = PADY , padx = PADX , sticky = ( tk . W , tk . E ) )
2024-07-27 10:36:43 -04:00
ToolTip ( self . model_dropdown , " Select the model of the printer, or press ' Detect printers ' . " )
2024-07-20 07:40:38 -04:00
2024-07-27 10:36:43 -04:00
# IP address entry
ip_frame = ttk . LabelFrame ( model_ip_frame , text = " Printer IP Address " , padding = PAD )
ip_frame . grid ( row = 0 , column = 1 , pady = PADY , padx = ( PADX , 0 ) , sticky = ( tk . W , tk . E ) )
ip_frame . columnconfigure ( 0 , weight = 0 )
2024-07-20 07:40:38 -04:00
ip_frame . columnconfigure ( 1 , weight = 1 )
self . ip_var = tk . StringVar ( )
2024-07-27 10:36:43 -04:00
ttk . Label ( ip_frame , text = " IP Address: " ) . grid ( row = 0 , column = 0 , sticky = tk . W , padx = PADX )
2024-07-20 07:40:38 -04:00
self . ip_entry = ttk . Entry ( ip_frame , textvariable = self . ip_var )
2024-07-27 07:53:03 -04:00
self . ip_entry . grid ( row = 0 , column = 1 , pady = PADY , padx = PADX , sticky = ( tk . W , tk . E ) )
2024-07-27 10:36:43 -04:00
self . ip_entry . bind ( " <F2> " , self . next_ip )
ToolTip ( self . ip_entry , " Enter the IP address, or press ' Detect printers ' (enter part of it to speed up the detection), or press F2 to get the next local IP address, which can then be edited. " )
2024-07-27 07:53:03 -04:00
2024-07-27 10:36:43 -04:00
# [row 1] Container frame for the two LabelFrames Power-off timer and TI Received Time
row_n + = 1
2024-07-27 07:53:03 -04:00
container_frame = ttk . Frame ( main_frame , padding = PAD )
2024-07-27 10:36:43 -04:00
container_frame . grid ( row = row_n , column = 0 , pady = PADY , sticky = ( tk . W , tk . E ) )
2024-07-27 07:53:03 -04:00
container_frame . columnconfigure ( 0 , weight = 1 ) # Allow column to expand
container_frame . columnconfigure ( 1 , weight = 1 ) # Allow column to expand
# Power-off timer
po_timer_frame = ttk . LabelFrame ( container_frame , text = " Power-off timer (minutes) " , padding = PAD )
po_timer_frame . grid ( row = 0 , column = 0 , pady = PADY , padx = ( 0 , PADX ) , sticky = ( tk . W , tk . E ) )
po_timer_frame . columnconfigure ( 0 , weight = 0 ) # Button column on the left
po_timer_frame . columnconfigure ( 1 , weight = 1 ) # Entry column
po_timer_frame . columnconfigure ( 2 , weight = 0 ) # Button column on the right
2024-07-20 07:40:38 -04:00
2024-07-27 07:53:03 -04:00
# Configure validation command for numeric entry
validate_cmd = self . register ( self . validate_number_input )
self . po_timer_var = tk . StringVar ( )
2024-07-27 10:36:43 -04:00
self . po_timer_entry = ttk . Entry ( po_timer_frame , textvariable = self . po_timer_var , validate = ' all ' , validatecommand = ( validate_cmd , " % P " ) , width = 6 , justify = ' center ' )
2024-07-27 07:53:03 -04:00
self . po_timer_entry . grid ( row = 0 , column = 1 , pady = PADY , padx = PADX , sticky = ( tk . W , tk . E ) )
2024-07-27 10:36:43 -04:00
ToolTip ( self . po_timer_entry , " Enter a number of minutes. " )
2024-07-27 07:53:03 -04:00
2024-07-27 10:36:43 -04:00
button_width = 7
get_po_minutes = ttk . Button ( po_timer_frame , text = " Get " , width = button_width , command = self . get_po_mins )
2024-07-27 07:53:03 -04:00
get_po_minutes . grid ( row = 0 , column = 0 , padx = PADX , pady = PADY , sticky = tk . W )
2024-07-27 10:36:43 -04:00
set_po_minutes = ttk . Button ( po_timer_frame , text = " Set " , width = button_width , command = self . set_po_mins )
2024-07-27 07:53:03 -04:00
set_po_minutes . grid ( row = 0 , column = 2 , padx = PADX , pady = PADY , sticky = tk . E )
# TI Received Time
ti_received_frame = ttk . LabelFrame ( container_frame , text = " TI Received Time (date) " , padding = PAD )
ti_received_frame . grid ( row = 0 , column = 1 , pady = PADY , padx = ( PADX , 0 ) , sticky = ( tk . W , tk . E ) )
ti_received_frame . columnconfigure ( 0 , weight = 0 ) # Button column on the left
2024-07-27 10:36:43 -04:00
ti_received_frame . columnconfigure ( 1 , weight = 1 ) # Calendar column
2024-07-27 07:53:03 -04:00
ti_received_frame . columnconfigure ( 2 , weight = 0 ) # Button column on the right
# TI Received Time Calendar Widget
self . date_entry = DateEntry ( ti_received_frame , date_pattern = " yy-mm-dd " , width = 10 , borderwidth = 2 )
self . date_entry . grid ( row = 0 , column = 1 , padx = PADX , pady = PADY , sticky = ( tk . W , tk . E ) )
self . date_entry . delete ( 0 , " end " )
2024-07-27 10:36:43 -04:00
ToolTip ( self . date_entry , " Enter a valid date with format YY-MM-DD. " )
2024-07-27 07:53:03 -04:00
# TI Received Time Buttons
2024-07-27 10:36:43 -04:00
get_ti_received = ttk . Button ( ti_received_frame , text = " Get " , width = button_width , command = self . get_ti_date )
2024-07-27 07:53:03 -04:00
get_ti_received . grid ( row = 0 , column = 0 , padx = PADX , pady = PADY , sticky = tk . W )
2024-07-27 10:36:43 -04:00
set_ti_received = ttk . Button ( ti_received_frame , text = " Set " , width = button_width , command = self . set_ti_date )
2024-07-27 07:53:03 -04:00
set_ti_received . grid ( row = 0 , column = 2 , padx = PADX , pady = PADY , sticky = tk . E )
2024-07-27 10:36:43 -04:00
# [row 2] Buttons
row_n + = 1
2024-07-27 07:53:03 -04:00
button_frame = ttk . Frame ( main_frame , padding = PAD )
2024-07-27 10:36:43 -04:00
button_frame . grid ( row = row_n , column = 0 , pady = PADY , sticky = ( tk . W , tk . E ) )
2024-07-20 07:40:38 -04:00
button_frame . columnconfigure ( ( 0 , 1 , 2 ) , weight = 1 )
self . detect_button = ttk . Button ( button_frame , text = " Detect Printers " , command = self . start_detect_printers )
2024-07-27 07:53:03 -04:00
self . detect_button . grid ( row = 0 , column = 0 , padx = PADX , pady = PADX , sticky = ( tk . W , tk . E ) )
2024-07-20 07:40:38 -04:00
self . status_button = ttk . Button ( button_frame , text = " Print Status " , command = self . print_status )
2024-07-27 07:53:03 -04:00
self . status_button . grid ( row = 0 , column = 1 , padx = PADX , pady = PADY , sticky = ( tk . W , tk . E ) )
2024-07-20 07:40:38 -04:00
self . reset_button = ttk . Button ( button_frame , text = " Reset Waste Ink Levels " , command = self . reset_waste_ink )
2024-07-27 07:53:03 -04:00
self . reset_button . grid ( row = 0 , column = 2 , padx = PADX , pady = PADX , sticky = ( tk . W , tk . E ) )
2024-07-27 10:36:43 -04:00
# [row 3] Status display
row_n + = 1
2024-07-27 07:53:03 -04:00
status_frame = ttk . LabelFrame ( main_frame , text = " Status " , padding = PAD )
2024-07-27 10:36:43 -04:00
status_frame . grid ( row = row_n , column = 0 , pady = PADY , sticky = ( tk . W , tk . E , tk . N , tk . S ) )
2024-07-20 07:40:38 -04:00
status_frame . columnconfigure ( 0 , weight = 1 )
status_frame . rowconfigure ( 0 , weight = 1 )
2024-07-25 16:08:05 -04:00
# ScrolledText widget
2024-07-27 07:53:03 -04:00
self . status_text = ScrolledText ( status_frame , wrap = tk . WORD , font = ( " TkDefaultFont " ) )
self . status_text . grid ( row = 0 , column = 0 , pady = PADY , padx = PADY , sticky = ( tk . W , tk . E , tk . N , tk . S ) )
self . status_text . bind ( " <Key> " , lambda e : " break " ) # disable editing text
2024-07-27 10:36:43 -04:00
self . status_text . bind ( " <Control-c> " , lambda event : self . copy_to_clipboard ( self . status_text ) )
2024-07-27 07:53:03 -04:00
# self.status_text.bind("<Button-1>", lambda e: "break") # also disable the mouse
2024-07-25 16:08:05 -04:00
2024-07-27 07:53:03 -04:00
# Create a frame to contain the Treeview and its scrollbar
self . tree_frame = tk . Frame ( status_frame )
self . tree_frame . grid ( column = 0 , row = 0 , sticky = ( tk . W , tk . E , tk . N , tk . S ) )
self . tree_frame . columnconfigure ( 0 , weight = 1 )
self . tree_frame . rowconfigure ( 0 , weight = 1 )
# Style configuration for the treeview
2024-07-25 16:08:05 -04:00
style = ttk . Style ( self )
2024-07-27 07:53:03 -04:00
treeview_font = style . lookup ( " Treeview.Heading " , " font " )
2024-07-25 16:08:05 -04:00
2024-07-27 07:53:03 -04:00
# For the treeview, if the treeview_font is a tuple, split into components
if isinstance ( treeview_font , tuple ) :
treeview_font_name , treeview_font_size = treeview_font [ 0 ] , treeview_font [ 1 ]
2024-07-25 16:08:05 -04:00
else :
# If font is not a tuple, it might be a font string or other format.
2024-07-27 07:53:03 -04:00
treeview_font_name , treeview_font_size = tkfont . Font ( ) . actual ( ' family ' ) , tkfont . Font ( ) . actual ( ' size ' )
2024-07-25 16:08:05 -04:00
style . configure ( " Treeview.Heading " ,
2024-07-27 07:53:03 -04:00
font = ( treeview_font_name , treeview_font_size - 2 , " bold " ) ,
2024-07-25 16:08:05 -04:00
background = " lightblue " ,
foreground = " darkblue " )
# Create and configure the Treeview widget
2024-07-27 07:53:03 -04:00
self . tree = ttk . Treeview ( self . tree_frame , style = " Treeview " )
2024-07-25 16:08:05 -04:00
self . tree . heading ( " #0 " , text = " Status Information " , anchor = ' w ' )
self . tree . grid ( column = 0 , row = 0 , sticky = ( tk . W , tk . E , tk . N , tk . S ) )
# Create a vertical scrollbar for the Treeview
2024-07-27 07:53:03 -04:00
tree_scrollbar = ttk . Scrollbar ( self . tree_frame , orient = " vertical " , command = self . tree . yview )
2024-07-25 16:08:05 -04:00
tree_scrollbar . grid ( column = 1 , row = 0 , sticky = ( tk . N , tk . S ) )
# Configure the Treeview to use the scrollbar
self . tree . configure ( yscrollcommand = tree_scrollbar . set )
# Create a context menu
self . context_menu = Menu ( self , tearoff = 0 )
self . context_menu . add_command ( label = " Copy " , command = self . copy_selected_item )
# Bind the right-click event to the Treeview
self . tree . bind ( " <Button-3> " , self . show_context_menu )
2024-07-27 07:53:03 -04:00
# Hide the Treeview initially
self . tree_frame . grid_remove ( )
2024-07-27 10:36:43 -04:00
def next_ip ( self , event ) :
ip = self . ip_var . get ( )
if self . ip_list_cycle == None :
self . ip_list = self . printer_scanner . get_all_printers ( local = True )
self . ip_list_cycle = 0
if not self . ip_list :
return
self . ip_var . set ( self . ip_list [ self . ip_list_cycle ] )
self . ip_list_cycle + = 1
if self . ip_list_cycle > = len ( self . ip_list ) :
self . ip_list_cycle = None
def copy_to_clipboard ( self , text_widget ) :
try :
text = text_widget . selection_get ( )
pyperclip . copy ( text )
except tk . TclError :
pass
return " break "
2024-07-27 07:53:03 -04:00
def get_po_mins ( self ) :
self . show_status_text_view ( )
model = self . model_var . get ( )
ip_address = self . ip_var . get ( )
if not model or not self . _is_valid_ip ( ip_address ) :
self . status_text . insert ( tk . END , " [ERROR] Please select a printer model and enter a valid IP address. \n " )
return
printer = EpsonPrinter ( model = model , hostname = ip_address )
try :
po_timer = printer . stats ( ) [ ' stats ' ] [ ' Power off timer ' ]
self . status_text . insert ( tk . END , f " [INFO] Power off timer: { po_timer } minutes. \n " )
self . po_timer_var . set ( po_timer )
except Exception as e :
self . status_text . insert ( tk . END , f " [ERROR] { e } : Missing ' Power off timer ' in configuration \n " )
def set_po_mins ( self ) :
self . show_status_text_view ( )
model = self . model_var . get ( )
ip_address = self . ip_var . get ( )
if not model or not self . _is_valid_ip ( ip_address ) :
self . status_text . insert ( tk . END , " [ERROR] Please select a printer model and enter a valid IP address. \n " )
return
printer = EpsonPrinter ( model = model , hostname = ip_address )
try :
po_timer = printer . stats ( ) [ ' stats ' ] [ ' Power off timer ' ]
po_timer = self . po_timer_var . get ( )
if not po_timer . isnumeric ( ) :
self . status_text . insert ( tk . END , " [ERROR] Please Use a valid value for minutes. \n " )
return
self . status_text . insert ( tk . END , f " [INFO] Set Power off timer: { po_timer } minutes. \n " )
2024-07-27 10:36:43 -04:00
response = messagebox . askyesno ( " Confirm Action " , " Are you sure you want to proceed? " )
if response :
printer . write_poweroff_timer ( int ( po_timer ) )
else :
self . status_text . insert ( tk . END , f " [WARNING] Set Power off timer aborted. \n " )
2024-07-27 07:53:03 -04:00
except Exception as e :
self . status_text . insert ( tk . END , f " [ERROR] { e } : Cannot set ' Power off timer ' ; missing configuration \n " )
def get_ti_date ( self ) :
self . show_status_text_view ( )
model = self . model_var . get ( )
ip_address = self . ip_var . get ( )
if not model or not self . _is_valid_ip ( ip_address ) :
self . status_text . insert ( tk . END , " [ERROR] Please select a printer model and enter a valid IP address. \n " )
return
printer = EpsonPrinter ( model = model , hostname = ip_address )
try :
date_string = datetime . strptime ( printer . stats ( ) [ ' stats ' ] [ ' First TI received time ' ] , ' %d % b % Y ' ) . strftime ( ' % y- % m- %d ' )
self . status_text . insert ( tk . END , f " [INFO] First TI received time (YY-MM-DD): { date_string } . \n " )
self . date_entry . set_date ( date_string )
except Exception as e :
self . status_text . insert ( tk . END , f " [ERROR] { e } : Missing ' First TI received time ' in configuration \n " )
def set_ti_date ( self ) :
self . show_status_text_view ( )
model = self . model_var . get ( )
ip_address = self . ip_var . get ( )
if not model or not self . _is_valid_ip ( ip_address ) :
self . status_text . insert ( tk . END , " [ERROR] Please select a printer model and enter a valid IP address. \n " )
return
printer = EpsonPrinter ( model = model , hostname = ip_address )
try :
date_string = datetime . strptime ( printer . stats ( ) [ ' stats ' ] [ ' First TI received time ' ] , ' %d % b % Y ' ) . strftime ( ' % y- % m- %d ' )
date_string = self . date_entry . get_date ( )
self . status_text . insert ( tk . END , f " [INFO] Set ' First TI received time ' (YY-MM-DD) to: { date_string . strftime ( ' % Y- % m- %d ' ) } . \n " )
2024-07-27 10:36:43 -04:00
response = messagebox . askyesno ( " Confirm Action " , " Are you sure you want to proceed? " )
if response :
printer . write_first_ti_received_time ( date_string . year , date_string . month , date_string . day )
else :
self . status_text . insert ( tk . END , f " [WARNING] Change of ' First TI received time ' aborted. \n " )
2024-07-27 07:53:03 -04:00
except Exception as e :
self . status_text . insert ( tk . END , f " [ERROR] { e } : Cannot set ' First TI received time ' ; missing configuration \n " )
def validate_number_input ( self , new_value ) :
# This function will be called with the new input value
if new_value == " " or new_value . isdigit ( ) :
return True
else :
return False
2024-07-25 16:08:05 -04:00
def show_status_text_view ( self ) :
""" Show the status frame and hide the Treeview. """
2024-07-27 07:53:03 -04:00
self . tree_frame . grid_remove ( )
2024-07-25 16:08:05 -04:00
self . status_text . grid ( )
def show_treeview ( self ) :
""" Show the Treeview and hide the status frame. """
self . status_text . grid_remove ( )
2024-07-27 07:53:03 -04:00
self . tree_frame . grid ( )
2024-07-25 16:08:05 -04:00
2024-07-20 07:40:38 -04:00
def print_status ( self ) :
2024-07-25 16:08:05 -04:00
self . show_status_text_view ( )
2024-07-20 07:40:38 -04:00
model = self . model_var . get ( )
ip_address = self . ip_var . get ( )
if not model or not self . _is_valid_ip ( ip_address ) :
self . status_text . insert ( tk . END , " [ERROR] Please select a printer model and enter a valid IP address. \n " )
return
printer = EpsonPrinter ( model = model , hostname = ip_address )
try :
2024-07-25 16:08:05 -04:00
self . show_treeview ( )
# Populate the Treeview
self . populate_treeview ( ' ' , self . tree , printer . stats ( ) )
# Expand all nodes
self . expand_all ( self . tree )
2024-07-20 07:40:38 -04:00
except Exception as e :
2024-07-25 16:08:05 -04:00
self . show_status_text_view ( )
2024-07-20 07:40:38 -04:00
self . status_text . insert ( tk . END , f " [ERROR] { e } \n " )
2024-07-25 16:08:05 -04:00
2024-07-20 07:40:38 -04:00
def reset_waste_ink ( self ) :
2024-07-25 16:08:05 -04:00
self . show_status_text_view ( )
2024-07-20 07:40:38 -04:00
model = self . model_var . get ( )
ip_address = self . ip_var . get ( )
if not model or not self . _is_valid_ip ( ip_address ) :
self . status_text . insert ( tk . END , " [ERROR] Please select a printer model and enter a valid IP address. \n " )
return
printer = EpsonPrinter ( model = model , hostname = ip_address )
try :
2024-07-27 10:36:43 -04:00
response = messagebox . askyesno ( " Confirm Action " , " Are you sure you want to proceed? " )
if response :
printer . reset_waste_ink_levels ( )
self . status_text . insert ( tk . END , " [INFO] Waste ink levels have been reset. \n " )
else :
self . status_text . insert ( tk . END , f " [WARNING] Waste ink levels reset aborted. \n " )
2024-07-20 07:40:38 -04:00
except Exception as e :
self . status_text . insert ( tk . END , f " [ERROR] { e } \n " )
def start_detect_printers ( self ) :
2024-07-25 16:08:05 -04:00
self . show_status_text_view ( )
2024-07-20 07:40:38 -04:00
self . status_text . insert ( tk . END , " [INFO] Detecting printers... (this might take a while) \n " )
self . detect_button . config ( state = tk . DISABLED ) # disable button while processing
# run printer detection in new thread, as it can take a while
threading . Thread ( target = self . detect_printers ) . start ( )
def detect_printers ( self ) :
2024-07-25 16:08:05 -04:00
self . show_status_text_view ( )
2024-07-20 07:40:38 -04:00
try :
2024-07-26 22:01:38 -04:00
printers = self . printer_scanner . get_all_printers ( self . ip_var . get ( ) . strip ( ) )
2024-07-20 07:40:38 -04:00
if len ( printers ) > 0 :
2024-07-26 22:01:38 -04:00
if len ( printers ) == 1 :
self . status_text . insert ( tk . END , f " [INFO] Found printer ' { printers [ 0 ] [ ' name ' ] } ' at { printers [ 0 ] [ ' ip ' ] } (hostname: { printers [ 0 ] [ ' hostname ' ] } ) \n " )
self . ip_var . set ( printers [ 0 ] [ ' ip ' ] )
for model in self . get_printer_models ( printers [ 0 ] [ ' name ' ] ) :
if model in EpsonPrinter ( ) . valid_printers :
self . model_var . set ( model )
break
else :
self . status_text . insert ( tk . END , f " [INFO] Found { len ( printers ) } printers: \n " )
for printer in printers :
self . status_text . insert ( tk . END , f " [INFO] { printer [ ' name ' ] } found at { printer [ ' ip ' ] } (hostname: { printer [ ' hostname ' ] } ) \n " )
2024-07-20 07:40:38 -04:00
else :
self . status_text . insert ( tk . END , " [WARN] No printers found. \n " )
except Exception as e :
self . status_text . insert ( tk . END , f " [ERROR] { e } \n " )
finally :
self . detect_button . config ( state = tk . NORMAL ) # enable button after processing
def _is_valid_ip ( self , ip ) :
try :
ip = ipaddress . ip_address ( ip )
return True
except ValueError :
return False
2024-07-25 16:08:05 -04:00
def is_simple_type ( self , data ) :
return isinstance ( data , ( str , int , float , bool ) )
def contains_parentheses ( self , data ) :
""" Check if a string representation contains parentheses. """
if isinstance ( data , ( list , tuple , set ) ) :
for item in data :
if isinstance ( item , ( tuple , list , set ) ) :
return True
if isinstance ( item , str ) and ( ' ( ' in item or ' ) ' in item ) :
return True
return False
def populate_treeview ( self , parent , treeview , data ) :
if isinstance ( data , dict ) :
for key , value in data . items ( ) :
if isinstance ( value , ( dict , list , set , tuple ) ) :
node = treeview . insert ( parent , ' end ' , text = key )
self . populate_treeview ( node , treeview , value )
else :
treeview . insert ( parent , ' end ' , text = f " { key } : { value } " )
elif isinstance ( data , list ) :
if all ( self . is_simple_type ( item ) for item in data ) and not self . contains_parentheses ( data ) :
treeview . insert ( parent , ' end ' , text = ' , ' . join ( map ( str , data ) ) )
else :
for item in data :
if isinstance ( item , ( dict , list , set , tuple ) ) :
self . populate_treeview ( parent , treeview , item )
else :
treeview . insert ( parent , ' end ' , text = str ( item ) )
elif isinstance ( data , set ) :
if not self . contains_parentheses ( data ) :
treeview . insert ( parent , ' end ' , text = ' , ' . join ( map ( str , data ) ) )
else :
for item in data :
treeview . insert ( parent , ' end ' , text = str ( item ) )
elif isinstance ( data , tuple ) :
treeview . insert ( parent , ' end ' , text = str ( data ) )
else :
treeview . insert ( parent , ' end ' , text = str ( data ) )
def expand_all ( self , treeview ) :
def recursive_expand ( item ) :
treeview . item ( item , open = True )
children = treeview . get_children ( item )
for child in children :
recursive_expand ( child )
root_children = treeview . get_children ( )
for child in root_children :
recursive_expand ( child )
def show_context_menu ( self , event ) :
""" Show the context menu. """
# Select the item under the cursor
item = self . tree . identify_row ( event . y )
if item :
self . tree . selection_set ( item )
self . context_menu . post ( event . x_root , event . y_root )
def copy_selected_item ( self ) :
""" Copy the selected Treeview item text to the clipboard. """
selected_item = self . tree . selection ( )
if selected_item :
item_text = self . tree . item ( selected_item [ 0 ] , " text " )
self . clipboard_clear ( )
self . clipboard_append ( item_text )
2024-07-26 22:01:38 -04:00
def get_printer_models ( self , input_string ) :
# Tokenize the string
tokens = re . split ( ' |/ ' , input_string )
if not len ( tokens ) :
return [ ]
# Define the words to remove (uppercase, then case insensitive)
remove_tokens = { " EPSON " , " SERIES " }
# Process tokens
processed_tokens = [ ]
non_numeric_part = " "
pre_model = " "
for token in tokens :
upper_token = token . upper ( )
# Remove tokens that match remove_tokens
if any ( word == upper_token for word in remove_tokens ) :
continue
if not any ( char . isdigit ( ) for char in token ) : # no alphanum inside
pre_model = pre_model + token + " "
continue
# Identify the non-numeric part of the first token
if not token . isnumeric ( ) and not non_numeric_part :
non_numeric_part = ' ' . join ( c for c in token if not c . isdigit ( ) )
# if token is numeric, prepend the non-numeric part
if token . isnumeric ( ) :
processed_tokens . append ( f " { pre_model } { non_numeric_part } { token } " )
else :
processed_tokens . append ( f " { pre_model } { token } " )
if not processed_tokens and pre_model :
processed_tokens . append ( pre_model . strip ( ) )
return processed_tokens
2024-07-20 07:40:38 -04:00
if __name__ == " __main__ " :
app = EpsonPrinterUI ( )
app . mainloop ( )