Issue with selectively undoing functions in Python - python

I have created a program in the turtle canvas in which the user can press any letter key on the keyboard and the turtle draws the corresponding letter in the canvas. I have also implemented an undo function that undoes the last function called by clearing the canvas then redrawing everything up until the point before the undone action. This undo function works by the user pressing a tkinter button at the bottom of the canvas labeled "Undo" or pressing the "left" key on the keyboard.
However, I have also decided to create a dynamic (a.k.a. selective, nonlinear, etc.) undo method in which there is a tkinter drop down menu and each drawing function, as it is called, is written to that menu. Then, from the menu, the user can select the function she previously called that he/she wants to undo and the program will undo that specific function instance. The whole process is described below:
The tkinter drop down menu is created through the following code block:
# global variables because next code block located ABOVE this one
global fav
fav = Menubutton(text = "Selective redo", state = DISABLED)
fav.pack(side = "left")
fav.menu = Menu(fav, tearoff = 0)
fav["menu"] = fav.menu
global redo1
redo1 = fav.menu
fav.pack()
When letter drawn, that letter's point object written to menu using code block below:
po = Point(v,y,c,w,isdown(),x,ph,pw)
undo1.add_command(label = Point.__str__(po), command = lambda: selectundo(undo1.index(po)))
# 'po' is a Point object for each function, and the string of that is the label of the menu item
Point.__str__() is this Point object method:
def __str__(self):
return "({})".format(self.function)
Finally, user supposed to be able to select –from menu– which instance of letter to undo, and program undoes that letter instance using the user-defined function below:
def selectundo(x):
for ty in range(x, len(function)):
undoHandler()
update()
listen()
The issue here is that when the user chooses to draw two or more of the same letters, they both get written to the menu with the same exact index values and thus, if the user wants to undo say, 1 of those, it will instead undo ALL instances of the letter from the canvas, not just the one selected by the user! However, I want the program to ONLY undo the instance of the letter that the user selects from the menu. Any ideas on how I would fix this issue? Any help is much appreciated! :)

Related

Tkinter: How to disable a button after it is pressed?

So I am trying to make hangman in python using Tkinter and I have mostly finished the game but, I want to add a feature that disables the button once it is pressed so that the user doesn't guess the same letter twice. Any idea how I can implement it.
the code:https://drive.google.com/file/d/1v0tjlSZC_xHCQ0WopNPRJC1pLNaQ4wRR/view?usp=sharing
Not only do you overwrite the button object when you create all the buttons, the button object will also be None because place() doesn't return anything.
Split that line in two:
button = Button(...) and
button.place(...)
But even then the button object is overwritten for each new button, so the button object takes the value of the last button 'z'.
To disable individual buttons, you need to be able to access each of them, e.g. by putting all objects in a list: button[n] = Button(...) Then in the guess() function you must disable exactly that button that was pressed.

Controlling flow of application using GUI interface

(Thanks in advance if you decide to answer. Sorry If I am not able to describe the content of the Application clearly.)
I am currently making Desktop Application using Tkinter Python for my gui interface. I am having a problem related to the flow of the Application.
My Application
Main Page of my application has form designed with some textboxes and checkboxes. Check button work as True or False Condition, where each check button refers to whether or not a specific function has to be executed or not.
To store the values of the check button, the dictionary is maintained with keys as LabelName and values as True/False value.
Checkbox code
f1=tk.BoolVar() #People tend to use IntVar() but i prefer BoolVar()
ttk.Label(text="func1")
ttk.Checkbutton(parent, variable=f1)
f2=tk.BoolVar()
ttk.Label(text="func2")
ttk.Checkbutton(parent, variable=f2)
-------other such CheckButtons------------------
There's a submit button in the form on pressing which all the data entered into textbox along with these check buttons. Based on true-false values, functions are called which is handled by if-else conditions.
#submit button
ttk.Button(parent,text="Submit",command=onsubmit)
###########
def onsubmit():
----------statements to read data--------------
dict['func1']=f1
dict['func2']=f2
#other statements
-----------------------------------------------
if dict['func1']:
func1()
if dict['func2']:
func2()
---other if-else conditions---
Each function is individual module which consists of either form, or frame with data or matplotlib graphs for visualization of data and to do other operations on data and graphs which are placed on root window frame.
My problem
I want the user to control the flow by giving them the next button after every function is executed and then move onto the execution of the next function based on his input of the check button. The program should wait until the User presses the next button and after they press the next button, it should execute the next function and then wait again for the next button.
One of the solution:
Using fig.waitforbuttonpress() was the solution. But I didn't find it reliable. Because even mouse click could skip the function execution. But I need to specifically assign a button through which the user can select when to proceed to the next function.
I am not sure if I understood what your code does, but you could do it something like that I guess:
next_button = ttk.Button(parent,text="Next",command=func1)
...
def func1():
#do your stuff here
next_button.configure(command=func2)
Then you would have to add the last line of code to all the functions to always reassign the button.
Another way could be:
process = 0
next_button = ttk.Button(parent,text="Next",command=next)
def next():
global process
process += 1
if process == 1:
func1()
elif process == 2:
func2()
...
elif *last_function_reached*:
process = 0

Issue with differentiating between two of the same items in a tkinter menu

The program I created allows for any letter on the keyboard the user types to be written on the turtle graphics canvas. In my program, I have also created a Python menu to which a Point object (for each letter function) is written to every time a user executes a function/draws a letter. However, because of the nature of my program, the user can also attach two of the same functions to the menu. If two of the same things get attached to the menu, for example two functions, I need a way to differentiate between them somehow. To do this, I have created a counter in another function and called that counter function in the function where the menu is written to, like so:
Counter function:
def increase():
if not hasattr(increase, "counter"):
increase.counter = 0
increase.counter += 1
Code block when menu is written to:
global loki
kli.config(state = NORMAL)
loki = ("{}".format(po.getfunction()))
increase() #<-- Counter function
undo1.add_command(label = str(increase.counter) + Point.__str__(po), command = lambda: selectundo(undo1.index(po)))
Point.__str__ is this method in the Point class:
def __str__(self):
return "({})".format(self.function)
However, I keep getting this error whenever I select something from the menu:
undo1.add_command(label = str(increase.counter) + Point.__str__(po), command = lambda: selectundo(undo1.index(po)))
File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/tkinter/__init__.py", line 2782, in index
i = self.tk.call(self._w, 'index', index)
tkinter.TclError: bad menu entry index "(<function draw_O at 0x105834d90>)"
I am thinking it has something to do with the following function, which undoes the function that is selected from the menu, but I am not sure:
def selectundo(x):
# This function undoes the function selected from the menu
for ty in range(x, len(function)):
undoHandler()
update()
listen()
Although, before I concatenated str(increase.counter) to Point.__str__(po), it worked perfectly.
So, what am I doing wrong here? Any help at all is much appreciated! :)
EDIT: Just to clear up what I am trying to do and why, I am trying to differentiate between two (or more) of the same functions if they are written to the menu and I am doing this because of the selectundo function (shown above) since, for example, if the user draws two (or more) of the same letter, I need to be able to differentiate between them, because right now, when I can't, the selectundo function undoes ALL instances of that letter, NOT just the first instance of what is pressed on the menu, which is what I actually want the program to do. If what I am trying to do to accomplish the task is not possible or if there is a better way to accomplish the task, please tell be about any/the other way that I can use to accomplish the task. I hope this helps alleviate any confusion! :)

Most pythonic way to implement redoing in my Python turtle drawing program?

I have created a turtle drawing program that draws any letter on the turtle canvas that the user presses on the keyboard. I have already implemented an undo function to undo the last drawing the user calls (shown below), but now I am looking at how to implement a redo function. Can anybody give me any tips or tricks on the most pythonic way to do this, possibly based on my current undo function? I have googled a lot about this to no avail, so any help regarding this issue is much appreciated.
My undo function:
def Clear():
clear()
speed(0)
tracer(0,0)
def undoHandler():
if len(function) > 0:
undoHandler.handling = True
if not hasattr(undoHandler, "counter"):
undoHandler.counter = 0
undoHandler.counter += 1
Clear()
function.pop()
penup()
try:
goto(o,p)
print("Gone to")
except:
goto(-200, 100)
pendown()
# "function" is a deque I created with the letter functions appended to it
# Items created based on a Points class that also stores all the attributes including the width, height, color, etc. of each letter.
# Items from a queue I created for the letter functions. The following executes each item from the deque.
try:
for i in function:
k = i.getXY()
penup()
goto(k)
pendown()
hk = i.getletterheight()
global letter_height
letter_height = hk
rk = i.getletterwidth()
global letter_width
letter_width = rk
hw = i.getwidth()
width(hw)
op = i.getcolor()
try:
color(op)
except:
for g in colors:
cp = g.getcolor2()
colormode(255)
color(cp)
j = i.getfunction()
j()
except:
pass
update()
EDIT: Just to avoid confusion, what I want the "redo" to do is to clear the canvas, then redraw everything with one function past the undone point each time a button calling "redo" is pressed. For example, if the user draws "HELLO" on the canvas, and the user undoes up until the letter "H", when redo is pressed once, the turtle should redraw "H(new letter user chose)L", if redo is called a second time, the turtle should draw "H(new letter user chose)LL", so on so forth. It should also be able to change the undone letter to a letter the user replaced it with (hence the "redo"). For example, if the user undoes up to, for example, "H" in "HELLO", and the user replaces "E" with "A", then when redo is called, it should draw "HAL".
A simple method to handle undo/redo is to use two stacks.
If you think of it like a web browser, One stack is for going backwards and the other stack is for going forwards.
New Action with Overwrite: Each new user action is done, then pushed onto the backwards stack. Finally, the next redo action is overwritten by having an action popped and discarded from the forwards stack (if it is not empty).
Undo: When the user wants to go backwards (undo), an action is popped from the backwards stack, the action is undone, then the action is pushed to the forwards stack.
Redo All: When the user wants to go forwards (redo), an action is popped from the forwards stack, the action is redone, then the action is pushed to the backwards stack. In this case, redo is actually redo all, so redo should be repeated until the forwards stack is empty.
Warning: Ensure that each action is defined such that it is self-contained, otherwise you may run into issues when actions are overwritten.

Python Tkinter use to emulate blinking with interaction of multiple buttons

I am looking for a solution to emulate the behavior of the UI of an electronic component and the user interaction (which should be pushing buttons) with LEDs reporting an internal state of the electronic component.
I am using python and the tKinter module to do so.
My code runs and my GUI window displays correctly. However, when I push several times on buttons the behavior is not as expected.
I have 4 possible state for each LED (OFF, ON, (Blinking) SLOW, (Blinking) FAST).
I have 4 buttons which can have an impact on the state. Each button has an interaction function defined in the widget class I have defined, and each of this function, once called, redefines the internal state of the widget.
In order to control the blinking of the LED, I use a single loop and the self.after( ..) function. This function is the following:
def toggleLeds(self):
for led in [self.ledTxIP, self.ledRxIP, self.ledTxRS, self.ledRxRS, self.ledPower, self.ledRun, self.ledStatus, self.ledConfig]:
if (((led[1] == "SLOW") and (self._FastBlinking == 0)) or (led[1] =="FAST")):
bg = led[0].cget("background")
bg = "green" if bg == "black" else "black"
led[0].configure(background=bg)
elif((led[1] == "OFF") and (self._update == 1)):
led[0].configure(background="black")
self._update = 0
elif (self._update == 1):
led[0].configure(background="green")
self._update = 0
self._FastBlinking = (self._FastBlinking + 1)%2
self.update_idletasks()
self.after(self._FastBlinkTime, self.toggleLeds)
This one is called recursively through the self.after function, and at the end of the interaction function I have defined for each button.
Here is how I have defined a single LED:
self.ledTxIP = [tk.Label(self, width=1, borderwidth=2, relief="groove"),"OFF"]
And here is an example of the button interaction function:
def pushMode(self):
if (re.search("Reset",self.state) == None):
if (self.clickModCnt == 0):
self.state = "Status"
self._stateTimer = int(time.gmtime()[5])
elif (self.clickModCnt == 1):
if(int(time.gmtime()[5]) - self._stateTimer < 3):
self.state = "Config"
else:
self.state = "RunMode"
else:
self.state = "RunMode"
self.clickModCnt = (self.clickModCnt + 1)%3
self._update = 1
self.updateLedState()
If anybody has an advice on this, it would be more than welcome.
I don't know why this didn't jump out at me sooner, but I think the problem is listed in your own question text, referring to the toggleLeds method:
This one is called recursively through the self.after function, and at the end of the interaction function I have defined for each button.
When the program initially runs, I'm assuming that you call toggleLeds somewhere to kick off the initial pattern for the LEDs. That sets up a single recursive loop via the self.after call at the end of the method. However, if you also call that same method every time you click a button to change state, you're setting up a new loop with every button click, and each new loop may or may not be in sync with your initial loop.
There are a couple ways that I can think of to handle this possible conflict. One is to avoid making new calls to toggleLeds, but that way there could be a delay between the button click and the new LED pattern. If you don't mind that delay, that's probably the best solution.
If you want the light/blink pattern to change immediately, you need to interrupt the current loop and start a new one with the new light/blink states. According to the Tkinter reference produced by New Mexico Tech, the after method:
...returns an integer “after identifier” that can be passed to the .after_cancel() method if you want to cancel the callback.
Here's how you could take advantage of that. First make sure that you're storing that identifier when calling the after method:
self.after_id = self.after(self._FastBlinkTime, self.toggleLeds)
Then change your toggleLeds method definition to accept an optional "interrupt" argument, and to cancel the existing after loop if that argument is True:
def toggleLeds(self, interrupt=False):
if interrupt:
self.after_cancel(self.after_id)
# Existing code follows
Finally, pass True to that argument when calling the method after a button has been clicked:
# Existing button processing code here
self.toggleLeds(interrupt=True)
With these changes in place, each button click would cancel the current after cycle and start a new one, preventing more than one cycle from running at once, which should keep the LEDs in sync.

Categories