File: globalKeys.rst

package info (click to toggle)
psychopy 2023.2.4%2Bdfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 124,456 kB
  • sloc: python: 126,213; javascript: 11,982; makefile: 152; sh: 120; xml: 9
file content (315 lines) | stat: -rw-r--r-- 10,217 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
Global Event Keys
=================

Global event keys are single keys (or combinations of a single key and one or
more "modifier" keys such as Ctrl, Alt, etc.) with an associated Python
callback function. This function will be executed if the key (or
key/modifiers combination) was pressed.

.. note::

   Global event keys only work with the `pyglet` backend, which is the default.

|PsychoPy| fully automatically monitors and processes key presses during most
portions of the experimental run, for example during
`core.wait()` periods, or when calling `win.flip()`. If a global
event key press is detected, the specified function will be run
immediately. You are not required to manually poll and check for key
presses. This can be particularly useful to implement a global
"shutdown" key, or to trigger laboratory equipment on a key press
when testing your experimental script -- without cluttering the code.
But of course the application is not limited to these two scenarios.
In fact, you can associate any Python function with a global event key.

All active global event keys are stored in `event.globalKeys`.

Adding a global event key (simple)
----------------------------------
First, let's ensure no global event keys are currently set by calling
func:`event.globalKeys.clear`.
::

    >>> from psychopy import event
    >>> event.globalKeys.clear()

To add a new global event key, you need to invoke
func:`event.globalKeys.add`. This function has two required arguments: the
key name, and the function to associate with that key.
::

    >>> key = 'a'
    >>> def myfunc():
    ...     pass
    ...
    >>> event.globalKeys.add(key=key, func=myfunc)

Look at `event.globalKeys`, we can see that the global event key has indeed
been created.
::

    >>> event.globalKeys
    <_GlobalEventKeys :
        [A] -> 'myfunc' <function myfunc at 0x10669ba28>
    >

Your output should look similar. You may happen to spot
We can take a closer look at the specific global key event we added.
::

    >>> event.globalKeys['a']
    _GlobalEvent(func=<function myfunc at 0x10669ba28>, func_args=(), func_kwargs={}, name='myfunc')

This output tells us that

- our key `a` is associated with our function `myfunc`

- `myfunc` will be called without passing any positional or keyword
  arguments (`func_args` and `func_kwargs`, respectively)

- the event name was automatically set to the name of the function.

.. note::

   Pressing the key won't do anything unless a :class:`psychopy.visual.Window`
   is created and and its :func:~`psychopy.visual.Window.flip` method or
   :func:`psychopy.core.wait` are called.

Adding a global event key (advanced)
------------------------------------
We are going to associate a function with a more complex calling signature
(with positional and keyword arguments) with a global event key. First, let's
create the dummy function:
::

    >>> def myfunc2(*args, **kwargs):
    ...     pass
    ...

Next, compile some positional and keyword arguments and a custom name for this
event. Positional arguments must be passed as tists or uples, and keyword
arguments as dictionaries.
::

    >>> args = (1, 2)
    >>> kwargs = dict(foo=3, bar=4)
    >>> name = 'my name'

.. note::

   Even when intending to pass only a single positional argument, `args` must be
   a list or tuple, e.g., `args = [1]` or `args = (1,)`.


Finally, specify the key and a combination of modifiers. While key names are
just strings, modifiers are lists or tuples of modifier names.
::

    >>> key = 'b'
    >>> modifiers = ['ctrl', 'alt']

.. note::

   Even when specifying only a single modifier key, `modifiers` must be a list
   or tuple, e.g., `modifiers = ['ctrl']` or `modifiers = ('ctrl',)`.

We are now ready to create the global event key.
::

    >>> event.globalKeys.add(key=key, modifiers=modifiers,
    ... func=myfunc2, func_args=args, func_kwargs=kwargs,
    ... name=name)

Check that the global event key was successfully added.
::

    >>> event.globalKeys
    <_GlobalEventKeys :
        [A] -> 'myfunc' <function myfunc at 0x10669ba28>
        [CTRL] + [ALT] + [B] -> 'my name' <function myfunc2 at 0x112eecb90>
    >

The key combination `[CTRL] + [ALT] + [B]` is now associated with the function
`myfunc2`, which will be called in the following way:
::

    myfunc2(1, 2, foo=2, bar=4)

.. _indexing:

Indexing
--------
`event.globalKeys` can be accessed like an ordinary dictionary. The index keys
are `(key, modifiers)` namedtuples.
::

    >>> event.globalKeys.keys()
    [_IndexKey(key='a', modifiers=()), _IndexKey(key='b', modifiers=('ctrl', 'alt'))]

To access the global event associated with the key combination
`[CTRL] + [ALT] + [B]`, we can do

    >>> event.globalKeys['b', ['ctrl', 'alt']]
    _GlobalEvent(func=<function myfunc2 at 0x112eecb90>, func_args=(1, 2), func_kwargs={'foo': 3, 'bar': 4}, name='my name')

To make access more convenient, specifying the modifiers is optional in case
none were passed to :func:`psychopy.event.globalKeys.add` when the global
event key was added, meaning the following commands are identical.
::

    >>> event.globalKeys['a', ()]
    _GlobalEvent(func=<function myfunc at 0x10669ba28>, func_args=(), func_kwargs={}, name='myfunc')
    >>> event.globalKeys['a']
    _GlobalEvent(func=<function myfunc at 0x10669ba28>, func_args=(), func_kwargs={}, name='myfunc')

All elements of a global event can be accessed directly.
::

    >>> index = ('b', ['ctrl', 'alt'])
    >>> event.globalKeys[index].func
    <function myfunc2 at 0x112eecb90>
    >>> event.globalKeys[index].func_args
    (1, 2)
    >>> event.globalKeys[index].func_kwargs
    {'foo': 3, 'bar': 4}
    >>> event.globalKeys[index].name
    'my name'

Number of active event keys
---------------------------
The number of currently active event keys can be retrieved by passing
`event.globalKeys` to the `len()` function.
::

    >>> len(event.globalKeys)
    2

Removing global event keys
--------------------------
There are three ways to remove global event keys:

- using :func:`psychopy.event.globalKeys.remove`,
- using `del`, and
- using :func:`psychopy.event.globalKeys.pop`.

:func:`psychopy.event.globalKeys.remove`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To remove a single key, pass the key name and modifiers (if any) to
:func:`psychopy.event.globalKeys.remove`.
::

    >>> event.globalKeys.remove(key='a')

A convenience method to quickly delete *all* global event keys is to pass
`key='all'`
::

    >>> event.globalKeys.remove(key='all')

`del`
~~~~~
Like with other dictionaries, items can be removed from `event.globalKeys`
by using the `del` statement. The provided index key must be specified as
described in :ref:`indexing`.
::

    >>> index = ('b', ['ctrl', 'alt'])
    >>> del event.globalKeys[index]

:func:`psychopy.event.globalKeys.pop`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Again, as other dictionaries, `event.globalKeys` provides a `pop` method to
retrieve an item and remove it from the dict. The first argument to `pop` is the
index key, specified as described in :ref:`indexing`. The second argument is
optional. Its value will be returned in case no item with the matching indexing
key could be found, for example if the item had already been removed previously.
::

    >>> r = event.globalKeys.pop('a', None)
    >>> print(r)
    _GlobalEvent(func=<function myfunc at 0x10669ba28>, func_args=(), func_kwargs={}, name='myfunc')
    >>> r = event.globalKeys.pop('a', None)
    >>> print(r)
    None

Global shutdown key
-------------------
The |PsychoPy| preferences for `shutdownKey` and `shutdownKeyModifiers`
(both unset by default) will be used to automatically create a global
shutdown key. To demonstrate this automated behavior, let us first change
the preferences programmatically (these changes will be lost when quitting the
current Python session).
::

    >>> from psychopy.preferences import prefs
    >>> prefs.general['shutdownKey'] = 'q'

We can now check if a global shutdown key has been automatically created.
::

    >>> from psychopy import event
    >>> event.globalKeys
    <_GlobalEventKeys :
        [Q] -> 'shutdown (auto-created from prefs)' <function quit at 0x10c171938>
    >

And indeed, it worked!

What happened behind the scenes? When importing the `psychopy.event`
module, the initialization of `event.globalKeys` checked for valid shutdown key
preferences and automatically initialized a shutdown key accordingly.
This key is associated with the :func:~`psychopy.core.quit` function, which will
shut down |PsychoPy|.
::

   >>> from psychopy.core import quit
   >>> event.globalKeys['q'].func == quit
   True

Of course you can very easily add a global shutdown key manually, too. You
simply have to associate a key with :func:~`psychopy.core.quit`.
::

    >>> from psychopy import core, event
    >>> event.globalKeys.add(key='q', func=core.quit, name='shutdown')

That's it!

A working example
-----------------
In the above code snippets, our global event keys were not actually functional,
as we didn't create a window, which is required to actually collect the key
presses. Our working example will thus first create a window and then add
global event keys to change the window color and quit the experiment,
respectively.
::

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

    from __future__ import print_function
    from psychopy import core, event, visual


    def change_color(win, log=False):
        win.color = 'blue' if win.color == 'gray' else 'gray'
        if log:
            print('Changed color to %s' % win.color)


    win = visual.Window(color='gray')
    text = visual.TextStim(win,
                           text='Press C to change color,\n CTRL + Q to quit.')

    # Global event key to change window background color.
    event.globalKeys.add(key='c',
                         func=change_color,
                         func_args=[win],
                         func_kwargs=dict(log=True),
                         name='change window color')

    # Global event key (with modifier) to quit the experiment ("shutdown key").
    event.globalKeys.add(key='q', modifiers=['ctrl'], func=core.quit)

    while True:
        text.draw()
        win.flip()