Blocking button click signals in PyQt - python

I have a program that uses pyqt's .animateClick() feature to show the user a sequence of different button clicks that the user has to copy in that specific order. The problem is I don't want the animateClick() to send a signal, I only want the button click signals from the user. Here is some of my code to demonstrate what I mean, and how I tried to solve that problem (that doesn't work). I simplified my code quite a bit so its easier to read, let me know if you have any questions.
from PyQt4 import QtCore,QtGui
global flag
global ai_states
ai_states = []
user_states = []
class Program(object):
# Set up the push buttons
#Code Here.
# Connect push buttons to function get_state()
self.pushButton.clicked.connect(self.get_state)
self.pushButton_2.clicked.connect(self.get_state)
self.pushButton_3.clicked.connect(self.get_state)
self.pushButton_4.clicked.connect(self.get_state)
# Code that starts the start() function
def start(self):
flag = 0
ai_states[:] = []
i = -1
# Code here that generates ai_states, numbers 1-4, in any order, based on button numbers.
for k in ai_states:
i = i + 1
# Code here that animates button clicks determined by ai_states
# Changes the flag to 1 once the loop ends
if i == len(ai_states):
flag = 1
def get_state(self):
if flag == 1:
user_states.append(str(self.centralWidget.sender().text()))
else:
pass
if len(user_states) == len(ai_states):
# Checks to make sure the user inputted the same clicks as the ai_states
Even though the flag does come out to be 1 after the start() function, it is still appending the animatedClick() signals. What am I doing wrong? I'm new to GUI programming, so I'm probably going about this in a very bad way. Any help would be appreciated.

Never use global variables unless you really have to. If you need shared access to variables, use instance attributes:
from PyQt4 import QtCore,QtGui
class Program(object):
def __init__(self):
self.ai_states = []
self.user_states = []
self.flag = 1
# Set up the push buttons
# Code Here
# Connect push buttons to function get_state()
self.pushButton.clicked.connect(self.get_state)
self.pushButton_2.clicked.connect(self.get_state)
self.pushButton_3.clicked.connect(self.get_state)
self.pushButton_4.clicked.connect(self.get_state)
# Code that starts the start() function
def start(self):
self.flag = 0
del self.ai_states[:]
i = -1
# Code here that generates ai_states, numbers 1-4, in any order, based on button numbers.
for k in self.ai_states:
i = i + 1
# Code here that animates button clicks determined by ai_states
# Changes the flag to 1 once the loop ends
self.flag = 1
def get_state(self):
if self.flag == 1:
self.user_states.append(str(self.centralWidget.sender().text()))
if len(self.user_states) == len(self.ai_states):
# Checks to make sure the user inputted the same clicks as the ai_states

Related

For loop assign a variable name for each loop iteration

This is a part of a code I've been working on, during the for loop it iterates through product_buttons in order to define which button is selected, and after a button is selected I wanted to show which button was selected and I have managed to do it as follows:
product_buttons = [36,38,40] #raspberry pi pins
in_progress = False
ended = True
product = None
def show_msg(wid,msg):
wid.config(text = msg)
pass
def button_loop():
global in_progress,ended
waiting = False
product = None
while True:
for i in range(len(product_buttons)):
button = IO.input(product_buttons[i])
if button:
print(i,in_progress,ended,product)
if not in_progress:
product = i
while not ended:
time.sleep(0.1)
in_progress = True
show_msg(root.lab_quality,'Button {}
pressed'.format(i))
The code works fine and whenever a button is pressed it changes Button {} pressed with Button 0 pressed, Button 1 pressed or Button 2 pressed.
Now what I was trying to do was to assign a variable name to each iteration in the for loop in order to achieve something like:
i = 0 make i = to a variable for example let's call it a
i = 1 make i = to b
i = 2 make i = to c
So that (still for example) when button 0 is pressed it shows Button a is pressed.
I have tried to store the values like this quality={1:a, 2:b, 3:c} and then call it with show_msg(root.lab_quality,'Button {} pressed'.format(quality)), this was the idea I had in mind but didn't work out and I am a bit stuck and I need to be pointed in the right direction so that I can eventually work it out.
Any help will be greatly appreciated!
You need to define quality as a dictionary like this: quality={'1':'a', '2':'b', '3':'c'}. Then, you can call show_msg function:
show_msg(root.lab_quality,'Button {} pressed'.format(quality[str(i)]))
This way you use the value of i variable as the key to read its name from the quality dictionary.
Thanks guys I have worked it out I did:
btn = ['a','b','c']
show_msg(root.lab_quality,'QUALITY {}'.format(btn[j]))
and works like a charm, was so easy I don't know why I didn't see it before.

Can you call a function from tk.OptionMenu's command parameter without passing an argument

I want to check if the user has changed any values in tkinter widgets and then prompt to save those values or lose changes.
A lot of applications have 3 buttons at the bottom right of their settings frame OK, Cancel, and Apply where Apply is disabled until a change has been made. I am imitating this feature in my program.
When I use tk.OptionMenu(command=function), by default it sends the current user selection to the function, however I do not need to know the current selection because regardless of what it is, I want to prompt the user to save settings.
Running the program would result in the following error:
TypeError: function() takes 0 positional arguments but 1 was given
A simple workaround I thought of would be to give an arbitrary parameter to function() like so:
def function(param=None):
value_change.status = True
See Example Code for value_change.status usage.
However, PyCharm points out that param is not used and marks it as a weak warning. I don't have any use for the passed value so I can't do much with param except ignore it and the PyCharm warning as well. There's technically nothing wrong here but I like seeing that green checkmark at the top right of my screen so I came up with another workaround:
def function(param=None):
value_change.status = True if param else False
Both param=None and if param else False are redundant since they're only placeholders to make the code run smoothly and not throw any warnings.
Another problem arises when I want to use function() for other widget commands which do not pass arguments such as tk.Checkbutton(command=function) and I have to change my code to the following lines which makes the code appear even more redundant than before.
def function(param=True):
value_change.status = True if param else False
Is there a way to not pass an argument through tk.OptionMenu(command=function)?
Example Code
import time
import tkinter as tk
from threading import Thread
def value_change():
value_change.count = 0
value_change.status = False
print('Has the user changed settings in the past 3 seconds?')
while value_change.status != 'exit':
if value_change.status:
value_change.count += 1
# Reset count if no change after 3 seconds
if value_change.count > 3:
value_change.count = 0
value_change.status = False
print(value_change.status)
time.sleep(1)
def function(param=True):
value_change.status = True if param else False
value_change.count = 0
gui = tk.Tk()
gui.geometry('100x100')
"""Setup OptionMenu"""
menu_options = ['var1', 'var2', 'var3']
menu_string = tk.StringVar()
menu_string.set(menu_options[0])
option_menu = tk.OptionMenu(gui, menu_string, *menu_options, command=function)
option_menu.pack()
"""Setup CheckButton"""
check_int = tk.IntVar()
check = tk.Checkbutton(gui, variable=check_int, command=function)
check.pack()
"""Turn On Thread and Run Program"""
Thread(target=value_change).start()
gui.mainloop()
"""Signal Threaded Function to Exit"""
value_change.status = 'exit'
The above example is a minimal reproduction, I did not include the OK Cancel Apply buttons and their functions as they are not necessary and would only make the code lengthy.
You can use lambda:
option_menu = tk.OptionMenu(gui, menu_string, *menu_options, command=lambda v: function())

Python - successive random calls to the same method

On the following strip-down version of my program, I want to simulate an exchange where the user and the computer will act following a random sequence.
Here the variable row contains the sequence order. A value of 0 means the program is waiting for a user input (method val0). A value of 1 means there should be an automatic process (method val1).
It seems to work at the beginning when alternating 0 and 1 but as soon as we are waiting for two successive automatic calls, it goes out of whack.
I tried using the after method but I can't see how and where to insert it.
A while loop might do the trick on this example but the end program is more complex, with sequence interruption, reevaluation of the sequence and so on. So I don't know if it would still apply then.
from tkinter import *
class Application:
def __init__(self,master = None):
self.master = master
Label(master,text='press next').grid()
Button(master,text='Next',command=self.val0).grid()
self.index = IntVar()
self.index.set(0)
self.index.trace("w",self.nextTurn)
def val0(self):
print("User action")
self.index.set(self.index.get() +1)
def val1(self):
print("Automatic action")
self.index.set(self.index.get() +1)
def nextTurn(self, *args):
i = self.index.get()
if i >= len(row):
self.master.destroy()
return
if row[i] == 1:
self.val1()
if __name__ == "__main__":
row = [0,1,0,0,1,1,0,0,0,1,1,1]
root = Tk()
win = Application(root)
root.mainloop()
You can easily solve your problem by calling the nextTurn directly in the automatic action function:
def val1(self):
print("Automatic action")
self.index.set(self.index.get() +1)
self.nextTurn() # call nextTurn after this action
So if it was an automatic action, you step into the next row position, and call nextTurn again.
However this may become a problem if your row becomes too large because it uses recursion. In that case you will want another approach with the while you mentioned. For this second option you would only need to change nextTurn:
def nextTurn(self, *args):
i = self.index.get()
# while it is an automatic action and it has row values, keep calling val1
while i < len(row) and row[i] == 1:
self.val1() # call automatic action
i = self.index.get() #update the row position
else:
if i >= len(row):
self.master.destroy()
return

Pyglet, exit after all sounds played

AVbin is installed. Both .wav and .mp3 files work.
import pyglet
music = pyglet.media.load('A.mp3')
music.play()
player = pyglet.media.Player()
player.queue( pyglet.media.load('B.mp3'))
player.queue( pyglet.media.load('C.wav'))
player.play()
pyglet.app.run()
pyglet.app.exit()
I want to create a program that plays A, then plays the queue with B and then C, and finally quits after all three sounds play.
I tried the code above but according to this post, "this is [solely] because app.run() is a never-ending loop."
How can I modify my code minimally so that the program quits after the three sounds are played?
Bonus, but how can I modify my code minimally so that the program can play two (or more) sound files, E.mp3 and F.mp3, at once?
Thanks!
Because what you're asking is not as simple as you'd might think it is.
I've put together a code example with as much comments as I possibly could fit in without making the example to hard to read.
Below the code, I'll try to explain a few key functions as detailed as possible.
import pyglet
from pyglet.gl import *
from collections import OrderedDict
key = pyglet.window.key
class main(pyglet.window.Window):
def __init__ (self, width=800, height=600, fps=False, *args, **kwargs):
super(main, self).__init__(width, height, *args, **kwargs)
self.keys = OrderedDict() # This just keeps track of which keys we're holding down. In case we want to do repeated input.
self.alive = 1 # And as long as this is True, we'll keep on rendering.
## Add more songs to the list, either here, via input() from the console or on_key_ress() function below.
self.songs = ['A.wav', 'B.wav', 'C.wav']
self.song_pool = None
self.player = pyglet.media.Player()
for song in self.songs:
media = pyglet.media.load(song)
if self.song_pool is None:
## == if the Song Pool hasn't been setup,
## we'll set one up. Because we need to know the audio_format()
## we can't really set it up in advance (consists more information than just 'mp3' or 'wav')
self.song_pool = pyglet.media.SourceGroup(media.audio_format, None)
## == Queue the media into the song pool.
self.song_pool.queue(pyglet.media.load(song))
## == And then, queue the song_pool into the player.
## We do this because SourceGroup (song_pool) as a function called
## .has_next() which we'll require later on.
self.player.queue(self.song_pool)
## == Normally, you would do self.player.eos_action = self.function()
## But for whatever windows reasons, this doesn't work for me in testing.
## So below is a manual workaround that works about as good.
self.current_track = pyglet.text.Label('', x=width/2, y=height/2+50, anchor_x='center', anchor_y='center')
self.current_time = pyglet.text.Label('', x=width/2, y=height/2-50, anchor_x='center', anchor_y='center')
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_key_release(self, symbol, modifiers):
try:
del self.keys[symbol]
except:
pass
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
elif symbol == key.SPACE:
if self.player.playing:
self.player.pause()
else:
self.player.play()
elif symbol == key.RIGHT:
self.player.seek(self.player.time + 15)
## == You could check the user input here,
## and add more songs via the keyboard here.
## For as long as self.song_pool has tracks,
## this player will continue to play.
self.keys[symbol] = True
def end_of_tracks(self, *args, **kwargs):
self.alive=0
def render(self):
## Clear the screen
self.clear()
## == You could show some video, image or text here while the music plays.
## I'll drop in a example where the current Track Name and time are playing.
## == Grab the media_info (if any, otherwise this returns None)
media_info = self.player.source.info
if not media_info:
## == if there were no meta-data, we'll show the file-name instead:
media_info = self.player.source._file.name
else:
## == But if we got meta data, we'll show "Artist - Track Title"
media_info = media_info.author + ' - ' + media_info.title
self.current_track.text = media_info
self.current_track.draw()
## == This part exists of two things,
## 1. Grab the Current Time Stamp and the Song Duration.
## Check if the song_pool() is at it's end, and if the track Cur>=Max -> We'll quit.
## * (This is the manual workaround)
cur_t, end_t = int(self.player.time), int(self.player.source._get_duration())
if self.song_pool.has_next() is False and cur_t >= end_t:
self.alive=False
## 2. Show the current time and maximum time in seconds to the user.
self.current_time.text = str(cur_t)+'/'+str(end_t) + 'seconds'
self.current_time.draw()
## This "renders" the graphics:
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
x = main()
x.run()
Now, normally you'd decorate your way trough this with a bunch of functions.
But I like to subclass and OOP my way through any graphical libraries, because it gets messy quite fast otherwise.
So instead of pyglet.app.run(), I've got a custom made run() function.
All this does is mimic the pyglet.app.run(), for the most part. Enough to get going at least.
Because player.eos_* events appears to be broken.
I've added a manual example of how you could check if the songs are done playing or not.
This is a combination of self.song_pool pyglet.media.SourceGroup, self.player.time pyglet.media.player.time and self.player.source._get_duration() which returns the track duration.
The SourceGroup gives us a has_next() function which tells us if we're at the end of the queued songs. The other two variables tells us if we've reached the end of the current track. This is all we need to determinate if we want to exit or not.
Now, I haven't technically added a way to add more songs. Because again, that would also be harder than you think. Unless you opt in for if symbol == key.LCTRL: self.song_pool.queue(pyglet.media.load(input('Song: '))) for instance. But again, all you would need to do, is add more songs to the self.song_pool queue, and there you go.
I hope this answers your question. Even the bonus one.

How do I program a double key press to make a non-modifier key behave like a modifier key within my program?

I am writing a typing program that includes many more characters than are available on a standard keyboard. In order to achieve this I need to transform some of the alphabet keys into modifier keys CTRL+A. For example f+j would output a. Typing f then j is slow for the user, I need them to be able to press f and j at the same time and receive one output. It's fine (preferable even) if some of the keyboard's normal functionality is stopped while the program is running.
I have looked into pygame Keydown, but it only seems to have functions for increasing key repeat and not stopping key output. Pyglet is also a possibility, but it doesn't have exact documentation on how I could make additional modifier keys. The only way I can figure out is to be constantly scanning the whole keyboard to see if any keys are pressed, but that won't determine the order the keys are pressed in and will create errors for the user, as the user pressing f then j would be read the same as the user pressing j then f and I need only the f then j combo to be understood as a keystroke by the system.
Here's a Pyglet version of how you could do it.
I based it on common GUI class that I use often here on SO because it's modular and easier to build on without the code getting messy after 40 lines.
import pyglet
from pyglet.gl import *
key = pyglet.window.key
class main(pyglet.window.Window):
def __init__ (self):
super(main, self).__init__(800, 800, fullscreen = False)
self.x, self.y = 0, 0
#self.bg = Spr('background.jpg')
self.output = pyglet.text.Label('',
font_size=14,
x=self.width//2, y=self.height//2,
anchor_x='center', anchor_y='center')
self.alive = 1
self.pressed = []
self.key_table = {213 : 'a'}
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_key_release(self, symbol, modifiers):
if symbol == key.LCTRL:
pass # Again, here's how you modify based on Left CTRL for instance
## All key presses represents a integer, a=97, b=98 etc.
## What we do here is have a custom key_table, representing combinations.
## If the sum of two pressed matches to our table, we add that to our label.
## - If no match was found, we add the character representing each press instead.
## This way we support multiple presses but joined ones still takes priority.
key_values = sum(self.pressed)
if key_values in self.key_table:
self.output.text += self.key_table[key_values]
else:
for i in self.pressed:
self.output.text += chr(i)
self.pressed = []
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
elif symbol == key.LCTRL:
pass # Modify based on left control for instance
else:
self.pressed.append(symbol)
def render(self):
self.clear()
#self.bg.draw()
self.output.draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
x = main()
x.run()
It might look like a lot of code, especially to the Pygame answer. But you could condense this down to ~15 lines as well, but again, the code would get messy if you tried to build on it any further.
Hope this works. Now I haven't thought the math through on this one.. It might be possible that two duplicate key combinations will produce the same value as another key representation, simply replace the dictionary keys 213 for instance with a tuple key such as self.key_table = {(107, 106) : 'a'} which would represent k+j
Few benefits:
No need to keep track of delay's
Fast and responsive
Any key could be turned into a modifier or map against custom keyboard layouts, meaning you could turn QWERTY into DWORAK for this application alone.. Not sure why you would want that, but hey.. None of my business :D
Overrides default keyboard inputs, so you can intercept them and do whatever you want with them.
Edit: One cool feature would be to register each key down but replace the last character with the joined combination.. Again this is all manual works since a keyboard isn't meant to do double-key-representations, and it's more of a graphical idea.. But would be cool :)
Here is some simple code to print keys pressed in quick succession, written in Python 2. It should be able to easily be modified to suit your needs:
import pygame, sys
pygame.init()
screen = pygame.display.set_mode([500,500])
clock = pygame.time.Clock()
combokeys = []
timer = 0
ACCEPTABLE_DELAY = 30 #0.5 seconds
while 1:
clock.tick(60)
timer += 1
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if timer <= ACCEPTABLE_DELAY:
combokeys.append(event.unicode)
else:
combokeys = [event.unicode]
timer = 0
print combokeys
I have not been able to test this code (working at school computer), so please notify me in the comments if something did not work so I can fix it.
You can change the value given for ACCEPTABLE_DELAY to change the delay before something is considered a different key combination. The delay should be (ACCEPTABLE_DELAY/60) seconds.

Categories