From eefa21a19721ebc8b250799675d9be8ff092997c Mon Sep 17 00:00:00 2001 From: "B. Stack" Date: Thu, 4 Apr 2024 15:52:27 -0400 Subject: tk: add initial images, and levelset balloon count --- srb_tk.py | 130 +++++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 90 insertions(+), 40 deletions(-) (limited to 'srb_tk.py') diff --git a/srb_tk.py b/srb_tk.py index 0f74e8d..fc29a28 100755 --- a/srb_tk.py +++ b/srb_tk.py @@ -21,8 +21,11 @@ # possible way to have a widget store its variable: https://stackoverflow.com/questions/63871376/tkinter-widget-cgetvariable # count number of 1s in a binary value https://stackoverflow.com/questions/8871204/count-number-of-1s-in-binary-representation/71307775#71307775 # Improve: -# Add levelset, level info. # Enable the checkbox for profile-in-use +# Add reset-level-breakables +# disable/gray out levels labels not reachable because of levelset_available_levels +# setting that sets the background colors of the regions +# add images to option menus if possible: https://stackoverflow.com/questions/67334913/can-i-possibly-put-an-image-label-into-an-option-box-in-tkinter # Dependencies: # dep-devuan: python3-tkinter, python3-pil.imagetk # rec-devuan: python3-cairosvg @@ -30,8 +33,16 @@ # spinbox appears to way too wide by default, even without a .grid(sticky="EW"). Try defining on the spinbox object at instantiation width=3 import tkinter as tk from pathlib import Path -import tkinter.filedialog, srb_lib, os, time +import tkinter.filedialog, srb_lib, os, time, sys import stackrpms_tk_lib as stk +from PIL import Image, ImageTk + +srb_tk_dir = os.path.split(os.path.abspath(__file__))[0] +image_dir = os.path.join(srb_tk_dir,"images") + +# make this configurable? +IMAGES = True +IMAGE_FAILURES = 0 ABOUT_TEXT = """ srb_tk \"Savegame Editor for Snoopy vs. the Red Baron\" @@ -40,6 +51,20 @@ SPDX-License-Identifier: GPL-3.0-only Icon courtesy of https://icons8.com/icon/9336/aircraft """ +def get_srb_image(filename, sizex, sizey, resample=Image.Resampling.LANCZOS): + """ helper function that returns an ImageTk from the named file from the configured images directory, resized for you. """ + global IMAGE_FAILURES + if IMAGES and IMAGE_FAILURES < 5: + try: + img = Image.open(os.path.join(image_dir,filename)).resize((sizex,sizey),resample=resample) + return ImageTk.PhotoImage(img) + except: + IMAGE_FAILURES += 1 + print(f"Failed to load image {filename}, only {5-IMAGE_FAILURES} failures left before giving up on images entirely.",file=sys.stderr) + return stk.empty_photoimage(sizex) + else: + return stk.empty_photoimage(sizex) + class App(tk.Frame): def __init__(self, master): super().__init__(master) @@ -62,7 +87,7 @@ class App(tk.Frame): # app icon #imgicon = stk.get_scaled_icon("hexedit",24,"Numix-Circle", "","apps") #imgicon = stk.get_scaled_icon(os.path.join(os.getenv("HOME"),"Downloads/snoopy.png"),24,"Numix-Circle", "","apps") - imgicon = stk.get_scaled_icon("./srb.png",24,"default", "","apps") + imgicon = stk.get_scaled_icon(os.path.join(image_dir,"srb.png"),24,"default", "","apps") self.master.tk.call("wm","iconphoto",self.master._w,imgicon) # app variables @@ -112,11 +137,11 @@ class App(tk.Frame): self.pur_plane_str_baron = tk.StringVar(value="baron") self.pur_plane_bool_baron = tk.BooleanVar() # levelsets and levels - #self.levelset_name_strs = [] self.levelset_status_ints = [] + self.levelset_balloons_ints = [] for i in range(0,len(srb_lib.LEVELSETS)): - #self.levelset_name_strs.append(tk.StringVar(value=srb_lib.get_levelset_info(i)[0]["name"])) self.levelset_status_ints.append(tk.StringVar()) # will hold the status, as in completed mission mask + self.levelset_balloons_ints.append(tk.IntVar()) # will hold the number of balloons for the levelset self.level_strs = [] self.level_statuses_hex = [] self.level_statuses = [] @@ -146,7 +171,7 @@ class App(tk.Frame): self.ent_checksum.grid(row=2,column=0) self.chk_silent = tk.Checkbutton(self.master,text="silent",variable=self.silent) self.chk_silent.grid(row=2,column=1) - self.frm_profiles = tk.Frame(self.master,background="orange",borderwidth=1) + self.frm_profiles = tk.Frame(self.master) #,background="orange",borderwidth=1) self.frm_profiles.grid(row=3,column=0,columnspan=2) # investigate radio button key-bind, up-down, to increment/decrement choice? self.rad_prof1 = stk.Radiobutton(self.frm_profiles,value=1,text="Profile 1",variable=self.selected_profile,underline=8) @@ -185,10 +210,11 @@ class App(tk.Frame): #self.selected_profile.set(1) # purposefully do not set this value. # one profile, so we will have to use the value of self.selected_profile - self.frm_curprof = tk.Frame(self.master,background="red") + self.frm_curprof = tk.Frame(self.master) #,background="red") self.frm_curprof.grid(row=4,column=0,rowspan=22,columnspan=2) # most form fields are inside the current profile - self.lbl_money = tk.Label(self.frm_curprof,text="Money") + self.img_money = get_srb_image("money.png",20,16) + self.lbl_money = tk.Label(self.frm_curprof,text="Money",image=self.img_money,compound="left") self.lbl_money.grid(row=0,column=0) self.ent_money = stk.Entry(self.frm_curprof,textvariable=self.money,func=self.load_form_into_data) #self.ent_money.bind("",self.load_form_into_data) @@ -201,15 +227,18 @@ class App(tk.Frame): # the indicatoron,padx,pady and grid sticky are incredibly useful and hard to find in the docs self.opt_equ_weapon.config(indicatoron=False,padx=1,pady=1,takefocus=1) self.opt_equ_weapon.grid(row=1,column=1,sticky="ew") - self.lbl_health = tk.Label(self.frm_curprof,text="Health") + self.img_health = get_srb_image("health.png",16,16) + self.lbl_health = tk.Label(self.frm_curprof,text="Health",image=self.img_health,compound="left") self.lbl_health.grid(row=2,column=0) self.spn_health = stk.Spinbox(self.frm_curprof,from_=1,to=4,textvariable=self.health,width=2) self.spn_health.grid(row=2,column=1) - self.lbl_stunt = tk.Label(self.frm_curprof,text="Stunt") + self.img_stunt = get_srb_image("stunt.png",16,16) + self.lbl_stunt = tk.Label(self.frm_curprof,text="Stunt",image=self.img_stunt,compound="left") self.lbl_stunt.grid(row=3,column=0) self.spn_stunt = stk.Spinbox(self.frm_curprof,from_=1,to=4,textvariable=self.stunt,width=2) self.spn_stunt.grid(row=3,column=1) - self.lbl_gun = tk.Label(self.frm_curprof,text="Gun") + self.img_gun = get_srb_image("gun.png",19,16) + self.lbl_gun = tk.Label(self.frm_curprof,text="Gun",image=self.img_gun,compound="left") self.lbl_gun.grid(row=4,column=0) self.spn_gun = stk.Spinbox(self.frm_curprof,from_=1,to=5,textvariable=self.gun,width=2) self.spn_gun.grid(row=4,column=1) @@ -219,21 +248,29 @@ class App(tk.Frame): #self.lbl_pur_weap.grid(row=5,column=0) self.lbl_pur_weap = tk.Label(self.frm_curprof,text="Planes") self.lbl_pur_weap.grid(row=5,column=1) - self.chk_pur_weap_potato = stk.Checkbutton(self.frm_curprof,text="Potato Gun",variable=self.pur_weap_potato,func=self.load_form_into_data) + self.img_potato = get_srb_image("Potato Gun.png",16,16) + self.chk_pur_weap_potato = stk.Checkbutton(self.frm_curprof,text="Potato Gun",variable=self.pur_weap_potato,func=self.load_form_into_data,image=self.img_potato,compound="left") self.chk_pur_weap_potato.grid(row=6,column=0,sticky="W") - self.chk_pur_weap_stinger = stk.Checkbutton(self.frm_curprof,text="Stinger",variable=self.pur_weap_stinger,func=self.load_form_into_data) + self.img_stinger = get_srb_image("Stinger.png",16,16) + self.chk_pur_weap_stinger = stk.Checkbutton(self.frm_curprof,text="Stinger",variable=self.pur_weap_stinger,func=self.load_form_into_data,image=self.img_stinger,compound="left") self.chk_pur_weap_stinger.grid(row=7,column=0,sticky="W") - self.chk_pur_weap_watball = stk.Checkbutton(self.frm_curprof,text="Water Balloon Cannon",variable=self.pur_weap_watball,func=self.load_form_into_data) + self.img_watball = get_srb_image("Water Balloon Cannon.png",12,16) + self.chk_pur_weap_watball = stk.Checkbutton(self.frm_curprof,text="Water Balloon Cannon",variable=self.pur_weap_watball,func=self.load_form_into_data,image=self.img_watball,compound="left") self.chk_pur_weap_watball.grid(row=8,column=0,sticky="W") - self.chk_pur_weap_snow = stk.Checkbutton(self.frm_curprof,text="Snow Blower",variable=self.pur_weap_snow,func=self.load_form_into_data) + self.img_snow = get_srb_image("Snow Blower.png",16,16) + self.chk_pur_weap_snow = stk.Checkbutton(self.frm_curprof,text="Snow Blower",variable=self.pur_weap_snow,func=self.load_form_into_data,image=self.img_snow,compound="left") self.chk_pur_weap_snow.grid(row=9,column=0,sticky="W") - self.chk_pur_weap_fire = stk.Checkbutton(self.frm_curprof,text="Fire Boomerang",variable=self.pur_weap_fire,func=self.load_form_into_data) + self.img_fire = get_srb_image("Fire Boomerang.png",12,16) + self.chk_pur_weap_fire = stk.Checkbutton(self.frm_curprof,text="Fire Boomerang",variable=self.pur_weap_fire,func=self.load_form_into_data,image=self.img_fire,compound="left") self.chk_pur_weap_fire.grid(row=10,column=0,sticky="W") - self.chk_pur_weap_lightn = stk.Checkbutton(self.frm_curprof,text="Lightning Rod",variable=self.pur_weap_lightn,func=self.load_form_into_data) + self.img_lightn = get_srb_image("Lightning Rod.png",16,16) + self.chk_pur_weap_lightn = stk.Checkbutton(self.frm_curprof,text="Lightning Rod",variable=self.pur_weap_lightn,func=self.load_form_into_data,image=self.img_lightn,compound="left") self.chk_pur_weap_lightn.grid(row=11,column=0,sticky="W") - self.chk_pur_weap_romanc = stk.Checkbutton(self.frm_curprof,text="Roman Candles",variable=self.pur_weap_romanc,func=self.load_form_into_data) + self.img_romanc = get_srb_image("Roman Candles.png",16,16) + self.chk_pur_weap_romanc = stk.Checkbutton(self.frm_curprof,text="Roman Candles",variable=self.pur_weap_romanc,func=self.load_form_into_data,image=self.img_romanc,compound="left") self.chk_pur_weap_romanc.grid(row=12,column=0,sticky="W") - self.chk_pur_weap_pumpkin = stk.Checkbutton(self.frm_curprof,text="10 Gauge Pumpkin",variable=self.pur_weap_pumpkin,func=self.load_form_into_data) + self.img_pumpkin = get_srb_image("10 Gauge Pumpkin.png",16,16) + self.chk_pur_weap_pumpkin = stk.Checkbutton(self.frm_curprof,text="10 Gauge Pumpkin",variable=self.pur_weap_pumpkin,func=self.load_form_into_data,image=self.img_pumpkin,compound="left") self.chk_pur_weap_pumpkin.grid(row=13,column=0,sticky="W") self.chk_pur_plane_marcie = stk.Checkbutton(self.frm_curprof,textvariable=self.pur_plane_str_marcie,variable=self.pur_plane_bool_marcie,func=self.load_form_into_data) self.chk_pur_plane_marcie.grid(row=6,column=1,sticky="W") @@ -249,22 +286,23 @@ class App(tk.Frame): self.chk_pur_plane_baron.grid(row=11,column=1,sticky="W") # level and levelsets - self.frm_levels = tk.Frame(self.master,borderwidth=1,background="green") + self.frm_levels = tk.Frame(self.master) #,borderwidth=1,background="green") self.frm_levels.grid(row=0,column=3,rowspan=len(srb_lib.LEVELS)+4) self.lbl_levelsets = [] self.spn_levelsets = [] + self.ent_levelsets_balloons = [] self.lbl_levelsets_name = tk.Label(self.frm_levels,text="Billboard/Available") - self.lbl_levelsets_name.grid(row=0,column=0,sticky="EW",columnspan=2) + self.lbl_levelsets_name.grid(row=0,column=0,sticky="EW",columnspan=4) #self.lbl_levelsets_available_levels = tk.Label(self.frm_levels,text="Available") #self.lbl_levelsets_available_levels.grid(row=0,column=1,columnspan=2) self.lbl_levels_name = tk.Label(self.frm_levels,text="Level") - self.lbl_levels_name.grid(row=0,column=3,sticky="EW") + self.lbl_levels_name.grid(row=0,column=5,sticky="EW") self.lbl_levels_status = tk.Label(self.frm_levels,text="Rank") - self.lbl_levels_status.grid(row=0,column=4,sticky="EW",columnspan=2) + self.lbl_levels_status.grid(row=0,column=6,sticky="EW",columnspan=2) self.lbl_levels_balloons = tk.Label(self.frm_levels,text="Balloons") - self.lbl_levels_balloons.grid(row=0,column=6,columnspan=4,sticky="EW") + self.lbl_levels_balloons.grid(row=0,column=8,columnspan=4,sticky="EW") self.lbl_levels_letters = tk.Label(self.frm_levels,text="Letters") - self.lbl_levels_letters.grid(row=0,column=10,columnspan=4,sticky="EW") + self.lbl_levels_letters.grid(row=0,column=12,columnspan=4,sticky="EW") self.lbl_levels = [] self.opt_levels_hex = [] self.opt_levels = [] @@ -275,13 +313,27 @@ class App(tk.Frame): self.btn_letters_none = [] self.btn_letters_all = [] x = 1 + self.img_balloons = [] for i in range(0,len(srb_lib.LEVELSETS)): tl = srb_lib.get_levelset_info(i)[0] + if "c" not in tl or tl["c"] == "none": + # this one will fail, but that is ok. there is no balloon color for the last levelset. + self.img_balloons.append(get_srb_image("balloon-none.png",16,20)) + elif tl["c"] and tl["c"] != "none": + self.img_balloons.append(get_srb_image("balloon-" + tl["c"] + ".png",16,20)) + #self.lbl_levelsets.append(tk.Label(self.frm_levels,text=tl["name"],image=self.img_balloons[-1],compound="right")) self.lbl_levelsets.append(tk.Label(self.frm_levels,text=tl["name"])) self.lbl_levelsets[i].grid(row=x,column=0,sticky="EW",columnspan=2) + if "c" in tl and tl["c"] != "none": + tk.Label(self.frm_levels,image=self.img_balloons[-1]).grid(row=x,column=2,columnspan=2) self.spn_levelsets.append(stk.Spinbox(self.frm_levels,textvariable=self.levelset_status_ints[i],from_=0,to=tl["l"],width=2)) self.spn_levelsets[i].grid(row=x+1,column=0) tk.Label(self.frm_levels,text="/ " + str(tl["l"])).grid(row=x+1,column=1,sticky="EW") + # balloons per levelset + if "c" in tl and tl["c"] != "none": + self.ent_levelsets_balloons.append(tk.Entry(self.frm_levels,textvariable=self.levelset_balloons_ints[i],state="readonly",width=3)) + self.ent_levelsets_balloons[-1].grid(row=x+1,column=2) + tk.Label(self.frm_levels,text="/" + str(tl["b"])).grid(row=x+1,column=3,sticky="E") #print(f"For levelset {tl},") these_levels = [k for k in srb_lib.LEVELS if k["setid"] == tl["id"]] sorted(these_levels,key=lambda i: i["id"]) @@ -289,29 +341,29 @@ class App(tk.Frame): for j in these_levels: #print(f"Process level {j}") self.lbl_levels.append(tk.Label(self.frm_levels,text=j["name"])) - self.lbl_levels[-1].grid(row=x,column=3) + self.lbl_levels[-1].grid(row=x,column=5) self.opt_levels_hex.append(tk.Entry(self.frm_levels,state="readonly",textvariable=self.level_statuses_hex[j["id"]],width=2)) - self.opt_levels_hex[-1].grid(row=x,column=4) + self.opt_levels_hex[-1].grid(row=x,column=6) self.opt_levels.append(tk.OptionMenu(self.frm_levels,self.level_statuses[j["id"]],*statuses)) self.opt_levels[-1].config(indicatoron=False,padx=0,pady=0,takefocus=1,width=8) - self.opt_levels[-1].grid(row=x,column=5,sticky="EW") + self.opt_levels[-1].grid(row=x,column=7,sticky="EW") # only some levels have balloons if j["b"]: self.ent_levels_balloons.append(tk.Entry(self.frm_levels,state="readonly",textvariable=self.level_balloons[j["id"]],width=2)) - self.ent_levels_balloons[-1].grid(row=x,column=6) - tk.Label(self.frm_levels,text="/ " + str(j["b"])).grid(row=x,column=7) + self.ent_levels_balloons[-1].grid(row=x,column=8) + tk.Label(self.frm_levels,text="/ " + str(j["b"])).grid(row=x,column=9) self.btn_balloons_none.append(tk.Button(self.frm_levels,text="none",padx=0,pady=0,command=lambda tl=j,action="none",ty="balloons": self.button_action_level(tl,action,ty))) - self.btn_balloons_none[-1].grid(row=x,column=8) + self.btn_balloons_none[-1].grid(row=x,column=10) self.btn_balloons_all.append(tk.Button(self.frm_levels,text="all",padx=0,pady=0,command=lambda tl=j,action="all",ty="balloons": self.button_action_level(tl,action,ty))) - self.btn_balloons_all[-1].grid(row=x,column=9) + self.btn_balloons_all[-1].grid(row=x,column=11) # every level has letters though self.ent_levels_letters.append(tk.Entry(self.frm_levels,state="readonly",textvariable=self.level_letters[j["id"]],width=2)) - self.ent_levels_letters[-1].grid(row=x,column=10) - tk.Label(self.frm_levels,text="/ " + str(len(str(j["l"])))).grid(row=x,column=11) + self.ent_levels_letters[-1].grid(row=x,column=12) + tk.Label(self.frm_levels,text="/ " + str(len(str(j["l"])))).grid(row=x,column=13) self.btn_letters_none.append(tk.Button(self.frm_levels,text="none",padx=0,pady=0,command=lambda tl=j,action="none",ty="letters": self.button_action_level(tl,action,ty))) - self.btn_letters_none[-1].grid(row=x,column=12) + self.btn_letters_none[-1].grid(row=x,column=14) self.btn_letters_all.append(tk.Button(self.frm_levels,text="all",padx=0,pady=0,command=lambda tl=j,action="all",ty="letters": self.button_action_level(tl,action,ty))) - self.btn_letters_all[-1].grid(row=x,column=13) + self.btn_letters_all[-1].grid(row=x,column=15) x += 1 x += 1 # end for-i-in-levelsets @@ -603,7 +655,7 @@ class App(tk.Frame): self.pur_plane_bool_baron.set(True if "baron" in planes_list else False) x = 0 for i in srb_lib.LEVELSETS: - mission_mask, letters = srb_lib.get_levelset_status(self.bdata,profile_id,x,silent=self.silent.get()) + mission_mask, letters, balloon_count = srb_lib.get_levelset_status(self.bdata,profile_id,x,silent=self.silent.get()) if 0 == x: self.pur_plane_str_marcie.set(letters) elif 1 == x: @@ -620,10 +672,8 @@ class App(tk.Frame): mission_mask = bin(int(mission_mask[1:],2)).count('1') #print(f"for levelset {i}, need to set max {i['l']}, current {mission_mask}") self.levelset_status_ints[x].set(mission_mask) + self.levelset_balloons_ints[x].set(balloon_count) x += 1 - # levelset available levels - for i in range(0,len(srb_lib.LEVELSETS)): - this_mission_mask, collected_letters = srb_lib.get_levelset_status(self.bdata,profile_id,i,silent=True) # levels for i in range(0,len(srb_lib.LEVELS)): tl_status, tl_balloons, tl_letters = srb_lib.get_level_status(self.bdata,profile_id,i,silent=True) -- cgit