From 2d627a9a78571eed2809fc343b06ed95a569beca Mon Sep 17 00:00:00 2001 From: Chris McGowan Date: Fri, 19 Jul 2024 20:25:03 +1000 Subject: [PATCH] Images in Widgets can be cloned, saved and generated in python --- createWidget.py | 4 +-- editWidget.py | 29 ++++++++++++---- pytkguivars.py | 90 +++++++++++++++++++++++++------------------------ pytkquickgui.py | 36 +++++++++++++++++--- 4 files changed, 101 insertions(+), 58 deletions(-) diff --git a/createWidget.py b/createWidget.py index 82beebe..e1ac17f 100644 --- a/createWidget.py +++ b/createWidget.py @@ -285,11 +285,11 @@ def addPlace(self, placeDict): self.widget.place(x=self.x, y=self.y, width=self.width, height=self.height) def editPlacePopup(self): - popup = ew.widgetEditPopup(self.root, self.widget) + popup = ew.widgetEditPopup(self.root, self.widget, self.pythonName) popup.createLayoutPopup() def editTtkPopup(self): - popup = ew.widgetEditPopup(self.root, self.widget) + popup = ew.widgetEditPopup(self.root, self.widget, self.pythonName) popup.createEditPopup() def findParentObject(self,parent): diff --git a/editWidget.py b/editWidget.py index a8c7f07..0476c47 100644 --- a/editWidget.py +++ b/editWidget.py @@ -80,10 +80,11 @@ class widgetEditPopup: This class handles the popups for editing the widget """ - def __init__(self, root, widget): + def __init__(self, root, widget, widgetName): self.keys = None self.specialKeys = None self.widget = widget + self.widgetName = widgetName self.root = root self.stringDict = {} # clickCanvas.bind('', leftMouseDrag) # clickCanvas2.bind('', leftMouseDragResize) @@ -222,13 +223,13 @@ def leftMouseRelease(self, event): :param event: """ log.debug("leftMouseRelease event %s %s", str(event), self.widget) - + def selectImage(self, key): """ Select an image to load into the widget :param key: Name of key or attribute """ - # Need to convert this to skimage (or other package. PIL has issues with github pylint) + # Need to convert this file to image (or other package. PIL has issues with github pylint) log.warning("selectImage is Expermental") # return f_types = [("Png Files", "*.png"), ("Jpg kFiles", "*.jpg")] @@ -240,7 +241,21 @@ def selectImage(self, key): self.addToStringDict(key, imageTk) self.addToStringDict(filePath, filename) self.widget.configure(image=imageTk) - myVars.imagesUsed.add(imageTk) + # [ 0 WIDGET 1 KEY 2 FILENAME 3 PHOTOIMAGE] + # Check to see if it is new + found = False + for f in myVars.widgetImageFilenames: + if f[myVars.WIDGET] == self.widgetName: + if [myVars.KEY] == key: + f[myVars.FILENAME] = filename + f[myVars.PHOTOIMAGE] = myVars.imageTest + found = True + if found is False: + f = [self.widgetName,key,filename,myVars.imageTest] + log.debug("Adding f %s",str(f)) + myVars.widgetImageFilenames.append(f) + + def applyLayoutSettings(self) -> None: """ Apply changed layout for the Widget @@ -546,7 +561,7 @@ def createEditPopup(self) -> None: labelCol = 0 controlCol = 3 val: str = "" - # Some widgets will need extra 'keys' + # Some widgets will need extra 'keys' if wName == "notebook": # self.specialKeys("Tabs") log.warning("TBD -- Adding Tabs for notebook") @@ -828,13 +843,13 @@ def createEditPopup(self) -> None: gridRow += 1 if widgetName == "ttk::notebook": log.info(widgetName) - # Need a Tabs button + # Need a Tabs button keys.append(tuple(("Tabs", "Tabs"))) elif widgetName == "ttk::labelframe": keys1 = self.widget.label.keys() for k1 in keys1: keys.append(tuple(("label", k1))) - keys2 = self.widget.scale.keys() + keys2 = self.widget.scale.keys() for k2 in keys2: keys.append(tuple(("scale", k2))) log.debug(keys) diff --git a/pytkguivars.py b/pytkguivars.py index ae4dfc8..aae4fe6 100644 --- a/pytkguivars.py +++ b/pytkguivars.py @@ -8,9 +8,17 @@ projectDict: dict childNameVars: list[tk.StringVar] imageFileNames: list[tk.StringVar] -stringUsed: list[bool] imageTest: any imagesUsed: list[tk.PhotoImage] +# [ 0 WIDGET 1 KEY 2 FILENAME 3 PHOTOIMAGE] +# The PHOTOIMAGE is not saved as it is unique to an instance. +# It will get generated on load project +WIDGET: int = 0 +KEY: int = 1 +FILENAME: int = 2 +PHOTOIMAGE: int = 3 + +widgetImageFilenames: [] snapTo: int imageIndex: int backgroundColor: str @@ -59,7 +67,6 @@ def sprintf(buf: str, fmt, *args) -> str: def initVars(): - global stringUsed global childNameVars global imageIndex global imagesUsed @@ -70,11 +77,11 @@ def initVars(): global theme global rootWidgetName global createdWidgetOrder + global widgetImageFilenames projectDict = {} childNameVars = [tk.StringVar()] * 64 imageFileNames = [tk.StringVar()] * 64 imagesUsed = [tk.PhotoImage] - # stringUsed = [bool] backgroundColor = "skyBlue3" # snapTo = int imageIndex = 0 @@ -82,6 +89,7 @@ def initVars(): theme = "default" rootWidgetName = "rootWidget" createdWidgetOrder = [] + widgetImageFilenames = [] # Common Procs @@ -166,6 +174,10 @@ def saveWidgetAsDict(widgetName) -> dict: log.debug("Key->%s<-", key) if key != "in": value = w[key] + if key == "image": + if value: + # The value is w.widgetName + key + value = widgetName + key log.debug("Value->%s<-", str(value)) attrId = "Attribute" + str(keyCount) # Ignore empty values @@ -223,7 +235,23 @@ def buildAWidget(widgetId: object, wDictOrig: dict) -> str: key = aDict["Key"] val = aDict["Value"] useValQuotes = True - # Looks like a bug in tkinter scale objects .. + if key == "image": + if val: + # The problem here is the ID is for the original widget. + # Create widget keeps the count. Use the next one that will get created + # As this is a clone, find the original amd make a new entry + newWidgetName = "Widget" + str(cw.createWidget.widgetId) + for f in widgetImageFilenames: + if f[WIDGET] == widgetName: + if f[KEY] == key: + filename = f[FILENAME] + newImage = tk.PhotoImage(file=filename) + n = [newWidgetName,key,filename,newImage] + widgetImageFilenames.append(n) + log.info("New image for newWidgetName %s %s",newWidgetName,n) + break + val = "myVars.getPhotoImage('" + newWidgetName + "','" + key + "')" + # like 'to' 'from' needs to have an underscore if key == "from": key = "from_" # j(' is in lists for combo boxes @@ -233,12 +261,14 @@ def buildAWidget(widgetId: object, wDictOrig: dict) -> str: key, val, aDict,) # Typically, this a TK object that is in < xxx > format continue - if val.find("(") > -1: + if key != 'image' and val.find("(") > -1: # The 'values' key has this saved format. This might be a tk thing. # It needs to be converted to a list newVal = fixComboValues(key, val) val = newVal useValQuotes = False + if key == 'image': + useValQuotes = False if len(val) > 0: tmpWidgetDef: str = "" if useValQuotes: @@ -292,42 +322,14 @@ def fixWidgetTypeName(wType) -> str: wType = t return wType -# sigh .. chat gpt wrote this bit ... it kinda sucks -# import tkinter as tk -# from tkinter import scrolledtext -# -# class AutoResizePopup(tk.Toplevel): -# def __init__(self, master=None, text=""): -# super().__init__(master) -# self.title("Help") -# -# # Create a ScrolledText widget -# self.text_widget = scrolledtext.ScrolledText(self, wrap=tk.WORD) -# # Insert the provided text -# self.text_widget.insert(tk.END, text) -# -# # self.text_widget.pack(fill=tk.BOTH, expand=False) -# self.text_widget.pack() -# -# # Make the Text widget read-only -# self.text_widget.configure(state='disabled') -# -# # Create a close button -# self.close_button = tk.Button(self, text="Close", command=self.close) -# self.close_button.pack(pady=5) -# -# # Automatically resize the window to fit the text -# self.update_idletasks() -# # self.geometry(f"{self.text_widget.winfo_width()}x{self.text_widget.winfo_height() + 40}") -# -# def close(self): -# self.destroy() -# -# def samplePopup(rootw): -# sample_text = ( -# "This is a sample text for the popup window. " -# "The window should automatically resize to fit the content. " -# "You can add more text here to see how it adjusts." ) -# popup = AutoResizePopup(rootw, text=sample_text) -# -# +def getPhotoImage(widgetName,key) -> tk.PhotoImage: + # the 'image=' part of tkinter widget parameters is tricky to save and restore. + # The imageName and path to file is in myVars.widgetImageFilenames + for w in widgetImageFilenames: + if widgetName == w[WIDGET]: + if key == w[KEY]: + log.info("getPhotoImage %s",str(w)) + fileName = w[FILENAME] + w[PHOTOIMAGE] = tk.PhotoImage(file=fileName) + return w[PHOTOIMAGE] + diff --git a/pytkquickgui.py b/pytkquickgui.py index e92b4ad..5acd447 100644 --- a/pytkquickgui.py +++ b/pytkquickgui.py @@ -81,7 +81,7 @@ def workOutWidgetCreationOrder() -> list: while not finished: finished = True sanityCheckCount += 1 - if sanityCheckCount > 1000: + if sanityCheckCount > 10000: log.critical("Loop is not exiting Ahhhhh %d", sanityCheckCount) break for nameEntry in cw.createWidget.widgetNameList: @@ -100,6 +100,15 @@ def workOutWidgetCreationOrder() -> list: finished = False return createdWidgetOrder +def createCleanImageList() -> []: + cleanFilenames: [] = [] + for f in myVars.widgetImageFilenames: + c = [f[myVars.WIDGET],f[myVars.KEY] ,f[myVars.FILENAME],None] + cleanFilenames.append(c) + log.debug("widgetImageFilenames %s",str(myVars.widgetImageFilenames)) + log.debug("cleanFilenames %s",str(cleanFilenames)) + return cleanFilenames + def saveProject(): widgetCount = 0 @@ -115,6 +124,7 @@ def saveProject(): "theme": myVars.theme, "widgetNameList": cleanList, "backgroundColor": myVars.backgroundColor, + "imageFileNames" : createCleanImageList(), } # Work out the order to create the Widgets so the parenting is correct createdWidgetOrder = workOutWidgetCreationOrder() @@ -136,7 +146,7 @@ def saveProject(): log.debug("projectData ->%s<-", str(projectData)) myVars.projectDict = projectData fileName = myVars.projectFileName - log.info("projectFileName ->%s<-", fileName) + log.debug("projectFileName ->%s<-", fileName) f = open(fileName, "wb") try: pickle.dump(projectData, f) @@ -144,6 +154,7 @@ def saveProject(): log.error("Exception TypeError %s", str(e)) log.warning("Error in Project Data \n%s", str(projectData)) f.close() + log.debug("projectData %s",projectData) myVars.lastProjectSaved = myVars.projectFileName myVars.projectSaved = True # Store the last project saved @@ -207,6 +218,10 @@ def buildPython() -> str: aDict = wDict.get(attribute) key = aDict.get("Key") val = aDict.get("Value") + if key == "image": + if val > "": + val = str(widgetName) + str(key) + if key == "command": if val > "": log.info("command ->%s<-",val) @@ -220,10 +235,14 @@ def buildPython() -> str: log.info("variable ->%s<-",val) tkvars.append(val) + print("") print("####### TK variables #######") - # print(tkvars) + for f in myVars.widgetImageFilenames: + name = str(f[myVars.WIDGET]) + str(f[myVars.KEY]) + print(name + " = tk.PhotoImage(file='" + f[myVars.FILENAME] + "')" ) for v in tkvars: print(v + " = tk.StringVar(rootWin,'0.0')") + print("") print("####### Functions #######") for f in functions: print("") @@ -249,7 +268,7 @@ def buildPython() -> str: keyCount = widgetName + "-KeyCount" widgetDef = widgetName + " = " + wType + "(" + parentName nKeys = wDict.get(keyCount) - specialKeys = ["command","textvariable","variable"] + specialKeys = ["command","textvariable","variable","image"] for a in range(nKeys): useValQuotes = True attribute = "Attribute" + str(a) @@ -280,6 +299,8 @@ def buildPython() -> str: if useValQuotes: tmpWidgetDef = widgetDef + ", " + key + "='" + val + "'" else: + if key == "image": + val = str(widgetName) + key tmpWidgetDef = widgetDef + ", " + key + "=" + val widgetDef = tmpWidgetDef print(widgetDef + ")") @@ -570,6 +591,7 @@ def loadProject(project): myVars.backgroundColor = runDict.get("backgroundColor") widgetNameList = runDict.get("widgetNameList") nWidgets = runDict.get("widgetCount") + myVars.widgetImageFilenames = runDict.get("imageFileNames") widgetsFound = 0 n = 0 while widgetsFound < nWidgets: @@ -581,8 +603,9 @@ def loadProject(project): widgetDef = myVars.buildAWidget(n, wDict) try: # widget = ast.literal_eval(widgetDef) + log.info("widgetDef ->%s<-", widgetDef) + # imageName = str(widgetId) + "image" widget = eval(widgetDef) - log.debug("widgetDef ->%s<-", widgetDef) except NameError as e: log.error("%d dict %s eval() NameError %s", n, str(wDict), str(e)) @@ -671,6 +694,9 @@ def widgetTree(): for nl in cw.createWidget.widgetNameList: print("WidgetNameList", nl) + for il in myVars.widgetImageFilenames: + print("widgetImageFilename %s",il) + def chooseBackground(): # global mainCanvas