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.
Related
I'm trying my hand at Pynput, and I'm starting off with creating a simple program to record the movements of a mouse, and then replay those movements once a button is clicked.
However, every time I click the mouse, it just starts to freak out and endlessly loop. I think it's going through the movements at a super high speed, but I eventually have to Alt-F4 the shell to stop it.
Any help would be appreciated.
import pynput
arr = []
from pynput import mouse
mou = pynput.mouse.Controller()
def on_move(x,y):
Pos = mou.position
arr.append(Pos)
def on_click(x, y, button, pressed):
listener.stop()
for i in arr:
mou.position = i
print("Done")
listener = mouse.Listener(on_move = on_move, on_click=on_click)
listener.start()
You have to be careful when using multiple threads (which is the case here, since mouse.Listener runs in its own thread). Apparently, as long as you are in the callback function, all events are still processed, even after you have called listener.stop(). So when replaying, for each mouse position you set, the on_move callback function is called, so that mouse position is added to your list again, which causes the endless loop.
In general, it's bad practice to implement too much functionality (in this case the "replaying") in a callback function. A better solution would be to use an event to signal another thread that the mouse button has been clicked. See the following example code. A few remarks:
I've added a few print statements to see what's happening.
I've added a small delay between the mouse positions to really see the playback. (NB: This also might make breaking out of the application a bit easier in case it hangs!)
I've changed a few variable names to make more sense. Calling an array "arr" is not a good idea. Try to use names that really describe the variable. In this case it is a list of positions, so I choose to call it positions.
I'm using return False to stop the mouse controller. The documentation states "Call pynput.mouse.Listener.stop from anywhere, raise StopException or return False from a callback to stop the listener.", but personally, I think returning False is the cleanest and safest solution.
import threading
import time
import pynput
positions = []
clicked = threading.Event()
controller = pynput.mouse.Controller()
def on_move(x, y):
print(f'on_move({x}, {y})')
positions.append((x, y))
def on_click(x, y, button, pressed):
print(f'on_move({x}, {y}, {button}, {pressed})')
# Tell the main thread that the mouse is clicked
clicked.set()
return False
listener = pynput.mouse.Listener(on_move=on_move, on_click=on_click)
listener.start()
try:
listener.wait()
# Wait for the signal from the listener thread
clicked.wait()
finally:
listener.stop()
print('*REPLAYING*')
for position in positions:
controller.position = position
time.sleep(0.01)
Note that when you run this in a Windows command prompt, the application might hang because you have pressed the mouse button and are then starting to send mouse positions. This causes a "drag" movement, which pauses the terminal. If this happens, you can just press Escape and the program will continue to run.
You got yourself an infinite loop. I think the listener you referred to in the on_click method might be null or undefined. Also according to some documentation I found you need to return false for the on_click method to stop listening
This is what I was looking at:
https://pythonhosted.org/pynput/mouse.html
I am writing a script to read my keystrokes and draw simple shapes in turtle.
To record keystrokes, I am using the keyboard module and I am using turtle for drawing.
I am getting struck due to the use of threading in the keyboard module.
What I am currently doing is-
I added hotkeys using keyboard.add_hotkey method.
If I am pressing a certain key, that letter is added to a list (named data) for later use.
When I press the combination of Ctrl+Shift+S, the save function is called. In the save function, a turtle window is instantiated, and the list data is popped one letter at a time. The shape is drawn according to the letter popped.
When the list gets empty, I save the drawing and close the turtle window.
The problem that I am facing is that once the save function is called, the program stops listening to other calls. It is perhaps due to the use of threads in keyboard module.
The code is attached here-
def start():
#turtle.mainloop()
s=turtle.Screen().setup( width = WIDTH, height = HEIGHT, startx = 0, starty = 0)
global t
t=turtle.Turtle()
turtle.ht()
t.ht()
def save():
start()
global t
global data
t.speed(0)
while data:
fun = data.pop()
if fun=='c':
draw_circle()
elif fun=='r':
draw_rectangle()
elif fun=='p':
draw_polygon()
elif fun=='h':
draw_hexagon()
elif fun=='t':
draw_triangle()
elif fun=='m':
draw_pentagon()
ts = turtle.getscreen()
ts.getcanvas().postscript(file="drawing.eps")
img = Image.open('drawing.eps')
img.save('drawing.png')
turtle.bye()
def push_fun(fun):
data.append(fun)
if __name__=='__main__':
keyboard.add_hotkey('ctrl+shift+s', save)
keyboard.add_hotkey('ctrl+shift+e', exit)
keyboard.add_hotkey('ctrl+shift+p', send_to_server)
# keyboard.add_hotkey('ctrl+shift+s', save, args=(data))
keyboard.add_hotkey('c', push_fun, args=('c',))
keyboard.add_hotkey('s', push_fun, args=('t',))
keyboard.add_hotkey('h', push_fun, args=('h',))
keyboard.add_hotkey('p', push_fun, args=('p',))
keyboard.add_hotkey('r', push_fun, args=('r',))
keyboard.wait()
After the save function is called, the program remains in the keyboard.wait() part but does not listen to any other key press.
you can end the wait by setting a key to press like:
keyboard.wait('space')
but keyboard.wait() without any keys given blocks all keystrokes forever
I'm not sure why you even put it there, I'm not even sure why it picks anything up at all, but I think you can do this:
if __name__=='__main__':
'''your keystrokes'''
while True: #or set a timer or something
pass
you didn't give me the whole code so I can't make sure it works
you also need to global the list data in the push_fun function
I hope it helps :)
I'm trying to make a game where the turtle needs to be able register and respond to user inputs (Simplified, a letter will appear and the user needs to click it on the keyboard. So if it shows "b" the user types "b"). I only added the letters a to f to make it simpler during the test. Each of them has a function that that'll execute when the letter has been pressed and the program is listening to it.
Everything worked out fine until I added a while function. Currently there is nothing in the while function (Asides from pass) but after I made it the code will no longer respond to user inputs. Could someone tell me how to fix this? My end goal is to have the program always listening for user input while a while loop runs and does its code. Below is my current code
import signal, turtle
def timeout_handler(signal, frame): # End of timer function
raise Exception('Time is up!')
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(10) # Number inside is how long the game will last.
def hit_a():
print("a registered")
def hit_b():
print("b registered")
def hit_c():
print("c registered")
def hit_d():
print("d registered")
def hit_e():
print("e registered")
def hit_f():
print("f registered")
turtle.onkey(hit_a, "a")
turtle.onkey(hit_b, "b")
turtle.onkey(hit_c, "c")
turtle.onkey(hit_d, "d")
turtle.onkey(hit_e, "e")
turtle.onkey(hit_f, "f")
turtle.listen()
while True:
pass
# Add program here
turtle.mainloop()
EDIT: I need a while code in there or at least a loop to keep a section repeating. In pseudocode it looks like this:
Several turtles each write one letter somewhere on the screen
Program waits for user to input a letter
Award/deduct points based on if they got it right or wrong
go back to line 2 if they got it wrong.
repeat
I plan on adding more stuff but I first need to get the base game going. The only way I know on how to code this is by using a while loop. But using one appears to be impossible since it prevents the program from listening to user inputs. Note that I need this all on turtle, not on the terminal (Command prompt if on windows) As I will colour code the letters to show which one to avoid and which ones to enter. How should I write this?
I also want to quickly mention that I have "avoided" this problem before. In the code below the program responds to user inputs when it's in the while loop. (Ignore the problems and the functions onkey() is assigned to. The idea is that the program responds while in the loop). However, I can't find out why in this code the program responds while in the loop but in the code above it doesn't register any user input
turtle.onkey(lower_game1_number, "s")
turtle.onkey(increase_game1_number, "w")
turtle.listen(xdummy=None, ydummy=None)
while True: # game1 is a turtle
game1.write(first_game_num_display,False,"center",("Arial",30))
game1_timer = random.randint(2,4)
time.sleep(game1_timer)
increase_game1_number()
game1.undo()
print(game1_timer)
mainloop()
Along with the "never use while True: in an event-based environment" advice that #martineau provided, you don't need to drag the 'signal' library into the code as you can handle that with an ontimer() event.
I need a while code in there or at least a loop to keep a section
repeating.
To address this, I've replaced my previous example code with a simple game that shows a letter and will keep moving it around the screen and changing it's color every two seconds, until you type that letter. Upon which, it will change to a different letter and continue on:
from turtle import Screen, Turtle
from random import shuffle, randrange
from itertools import cycle
WIDTH, HEIGHT = 600, 600
FONT_SIZE = 36
FONT = ('Arial', FONT_SIZE, 'bold')
LETTERS = list("abcdefghijklmnopqrstuvwxyz")
COLORS = ['red', 'blue', 'green', 'magenta', 'cyan', 'black', 'orange', 'gray']
def play():
global letter
if hit == letter:
letter = next(letters)
turtle.clear()
turtle.color(next(colors))
turtle.goto(randrange(FONT_SIZE - WIDTH/2, WIDTH/2 - FONT_SIZE), randrange(FONT_SIZE - HEIGHT/2, HEIGHT/2 - FONT_SIZE))
turtle.write(letter, font=FONT)
screen.ontimer(play, 2000)
letters = LETTERS
shuffle(letters)
letters = cycle(letters)
letter = next(letters)
colors = COLORS
shuffle(colors)
colors = cycle(colors)
hit = None
screen = Screen()
screen.setup(WIDTH, HEIGHT)
turtle = Turtle(visible=False)
turtle.penup()
for character in LETTERS:
def hit_character(character=character):
global hit
hit = character
screen.onkey(hit_character, character)
screen.listen()
play()
screen.mainloop()
Make sure to click on the window before typing to get it to listen for input.
Hopefully this will give you some ideas how to go about your more complex problem without using a while True: loop. Or at least show you how you might set up all your onkey() event assignments and handlers without rewriting the same code for every letter in the alphabet...
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! :)
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.