File: _textmenu.py

package info (click to toggle)
python-expyriment 0.7.0%2Bgit34-g55a4e7e-3.2
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 1,504 kB
  • ctags: 2,094
  • sloc: python: 12,766; makefile: 150
file content (358 lines) | stat: -rw-r--r-- 13,468 bytes parent folder | download | duplicates (2)
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
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
"""
A TextMenu.

This module contains a class implementing a TextMenu.

"""

__author__ = 'Florian Krause <florian@expyriment.org>, \
Oliver Lindemann <oliver@expyriment.org>'
__version__ = '0.7.0'
__revision__ = '55a4e7e'
__date__ = 'Wed Mar 26 14:33:37 2014 +0100'

import defaults
import expyriment
from _keyboard import Keyboard
from _input_output import Input


class TextMenu(Input):
    """A class implementing a text menu."""

    def __init__(self, heading, menu_items, width, position=None,
                 text_size=None, gap=None, heading_font=None,
                 text_font=None, background_colour=None,
                 text_colour=None, heading_text_colour=None,
                 select_background_colour=None, select_text_colour=None,
                 select_frame_colour=None, select_frame_line_width=None,
                 justification=None, scroll_menu=None,
                 background_stimulus=None, mouse=None):
        """Create a text menu.

        This creates a menu with items to be selected.

        Parameters
        ----------
        heading : str
            menu heading
        menu_items : str or list
            list with menu items
        width : int
            width of the menu
        position : (int, int), optional
        text_size : int, optional
        background_colour : (int, int, int), optional
            background colour of the menu
        gap : int, optional
            size of gap (pixel) between heading and item list
        heading_font : str, optional
            font to be used for the heading
        text_font : str, optional
            font to be used for the text
        text_colour : (int, int, int), optional
            text colour of the items
        heading_text_colour : (int, int, int), optional
            text colour of the heading
        select_background_colour : (int, int, int), optional
            background colour of the currently selected item
        select_text_colour : (int, int, int), optional
            text colour of the currently selected item
        select_frame_colour : (int, int, int), optional
            colour of the frame around the selected item
        select_frame_line_width : int, optional
            line width of the frame around the selected item
        justification : int, optional
            text justification: 0 (left), 1 (center), 2 (right)
        scroll_menu : int, optional
            maximum length of a item list before a scroll menu will
            be display. If the parameter is 0 of False scroll menu
            will not be displayed
        background_stimulus : visual expyriment stimulus, optional
            The background stimulus is a second stimulus that will be presented
            together with the TextMenu. For both stimuli overlap TextMenu
            will appear on top of the background_stimulus
        mouse : expyriment.io.Mouse object, optional
            If a mouse object is given, the menu can also be controlled by the
            mouse

        """

        if position is None:
            position = defaults.textmenu_position
        if gap is None:
            gap = defaults.textmenu_gap
        if heading_font is None:
            heading_font = defaults.textmenu_text_font
        if text_font is None:
            text_font = defaults.textmenu_text_font
        if text_size is None:
            text_size = defaults.textmenu_text_size
        if background_colour is None:
            background_colour = defaults.textmenu_background_colour
        if text_colour is None:
            text_colour = defaults.textmenu_text_colour
        if heading_text_colour is None:
            heading_text_colour = defaults.textmenu_heading_text_colour
        if select_background_colour is None:
            select_background_colour = \
                defaults.textmenu_select_background_colour
        if select_text_colour is None:
            select_text_colour = defaults.textmenu_select_text_colour
        if select_frame_colour is None:
            select_frame_colour = defaults.textmenu_select_frame_colour
        if select_frame_line_width is None:
            select_frame_line_width = defaults.textmenu_select_frame_line_width
        if justification is None:
            justification = defaults.textmenu_justification
        if scroll_menu is None:
            scroll_menu = defaults.textmenu_scroll_menu

        self._scroll_menu = abs(int(scroll_menu))
        if self._scroll_menu > 0 and self._scroll_menu < 5:
            self._scroll_menu = 5
        self._gap = gap
        self._position = position
        self._bkg_colours = [background_colour, select_background_colour]
        self._text_colours = [text_colour, select_text_colour]
        self._line_size = (width, expyriment.stimuli.TextLine(
            menu_items[0], text_size=text_size).surface_size[1] + 2)
        expyriment.stimuli._stimulus.Stimulus._id_counter -= 1
        self._frame = expyriment.stimuli.Frame(
            frame_line_width=select_frame_line_width,
            size=(self._line_size[0] + 2 * select_frame_line_width,
                  self._line_size[1] + 2 * select_frame_line_width),
            colour=select_frame_colour)
        expyriment.stimuli._stimulus.Stimulus._id_counter -= 1
        if background_stimulus is not None:
            if background_stimulus.__class__.__base__ in \
                     [expyriment.stimuli._visual.Visual, expyriment.stimuli.Shape]:
                self._background_stimulus = background_stimulus
            else:
                raise TypeError("{0} ".format(type(background_stimulus)) +
                                     "is not a valid background stimulus. " +
                                     "Use an expyriment visual stimulus.")
        else:
            self._background_stimulus = None

        if mouse is not None:
            self._mouse = mouse
        else:
            self._mouse = None

        self._canvas = expyriment.stimuli.BlankScreen()
        expyriment.stimuli._stimulus.Stimulus._id_counter -= 1
        self._original_items = menu_items
        self._menu_items = []
        for item in menu_items:
            self._menu_items.append(expyriment.stimuli.TextBox(
                "{0}".format(item),
                text_size=text_size, text_font=text_font,
                text_justification=justification,
                size=self._line_size))
            expyriment.stimuli._stimulus.Stimulus._id_counter -= 1
        self._heading = expyriment.stimuli.TextBox(
            heading,
            text_size=text_size,
            text_justification=justification,
            text_font=heading_font,
            text_colour=heading_text_colour,
            text_bold=True,
            background_colour=self._bkg_colours[0],
            size=self._line_size)
        expyriment.stimuli._stimulus.Stimulus._id_counter -= 1

    @property
    def heading(self):
        """Getter for heading"""
        return self._heading

    @property
    def menu_items(self):
        """Getter for menu_items"""
        return self._menu_items

    @property
    def position(self):
        """Getter for position"""
        return self._position

    @property
    def text_size(self):
        """Getter for text_size"""
        return self._heading.text_size

    @property
    def background_colour(self):
        """Getter for background_colour"""
        return self._bkg_colours[0]

    @property
    def select_background_colour(self):
        """Getter for select_background_colour"""
        return self._bkg_colours[1]

    @property
    def gap(self):
        """Getter for gap"""
        return self._gap

    @property
    def text_colour(self):
        """Getter for text_colour"""
        return self._text_colours[0]

    @property
    def select_text_colour(self):
        """Getter for select_text_colour"""
        return self._text_colours[1]

    @property
    def heading_text_colour(self):
        """Getter for heading_text_colour"""
        return self._heading.text_colour

    @property
    def select_frame_colour(self):
        """Getter for select_frame_colour"""
        return self._frame.colour

    @property
    def select_frame_line_width(self):
        """Getter for select_frame_line_width"""
        return self._frame.line_width

    @property
    def justification(self):
        """Getter for justification"""
        return self._heading.text_justification

    @property
    def scroll_menu(self):
        """Getter for scroll_menu"""
        return self._scroll_menu

    @property
    def background_stimulus(self):
        """Getter for background_stimulus"""
        return self._background_stimulus

    def _append_item(self, item, is_selected, y_position):
        """helper function"""
        item.clear_surface()
        item.position = (self._position[0], y_position + self._position[1])
        if is_selected:
            item.background_colour = self._bkg_colours[1]
            item.text_colour = self._text_colours[1]
        else:
            item.background_colour = self._bkg_colours[0]
            item.text_colour = self._text_colours[0]
        item.plot(self._canvas)

    def _redraw(self, selected_item):
        """helper function"""
        if self._scroll_menu > 0:
            n = self._scroll_menu
        else:
            n = len(self._menu_items)
        self._canvas.clear_surface()
        if self._background_stimulus is not None:
            self._background_stimulus.plot(self._canvas)
        y_pos = int(((1.5 + n) * self._line_size[1]) + (n * self._gap)) / 2
        self._heading.position = (self._position[0], y_pos + self._position[1])
        self._heading.plot(self._canvas)
        y_pos = y_pos - int(0.5 * self._line_size[1])

        if self._scroll_menu == 0:
            for cnt, item in enumerate(self._menu_items):
                y_pos -= (self._line_size[1] + self._gap)
                self._append_item(item, cnt == selected_item, y_pos)
                if cnt == selected_item:
                    self._frame.position = (0, y_pos)
        else:  # scroll menu
            for cnt in range(selected_item - self._scroll_menu / 2,
                             selected_item + 1 + self._scroll_menu / 2):
                y_pos -= (self._line_size[1] + self._gap)
                if cnt >= 0 and cnt < len(self._menu_items):
                    self._append_item(self._menu_items[cnt],
                                      cnt == selected_item,
                                      y_pos)
                    if cnt == selected_item:
                        self._frame.position = (0, y_pos)

        if self._frame.line_width > 0:
            self._frame.plot(self._canvas)
        self._canvas.present()

    def get(self, preselected_item=0):
        """Present the menu and return the selected item.

        Parameters
        ----------
        preselected_item : int, optional
            item that is preselected when showing menu

        Returns
        -------
        selected : int
            integer representing the selected item in the list

        """

        selected = preselected_item
        # Keyboard
        if self._mouse is None:
            while True:
                self._redraw(selected)
                key = Keyboard().wait()[0]
                if key == expyriment.misc.constants.K_UP:
                    selected -= 1
                elif key == expyriment.misc.constants.K_DOWN:
                    selected += 1
                elif key in expyriment.misc.constants.K_ALL_DIGITS and\
                        key > expyriment.misc.constants.K_0:
                    selected = key - expyriment.misc.constants.K_1
                elif key == expyriment.misc.constants.K_RETURN:
                    break
                if selected < 0:
                    selected = 0
                elif selected >= len(self._menu_items):
                    selected = len(self._menu_items) - 1
            return selected
        # Mouse
        else:
            pressed = None
            while True:
                pressed = None
                self._redraw(selected)
                event, pos, rt = self._mouse.wait_press()
                if self._scroll_menu == 0:
                    for cnt in range(len(self._menu_items)):
                        if 0 <= cnt < len(self._menu_items):
                            if self._menu_items[cnt].overlapping_with_position(pos):
                                pressed = cnt
                else:
                    for cnt in range(selected-self._scroll_menu/2,
                                     selected+1+self._scroll_menu/2):
                        if 0 <= cnt < len(self._menu_items):
                            if self._menu_items[cnt].overlapping_with_position(pos):
                                pressed = cnt
                if pressed is not None:
                    if pressed == selected:
                        break
                    else:
                        selected = pressed
            return self._original_items[pressed]


if __name__ == "__main__":
    from expyriment import control
    control.set_develop_mode(True)
    defaults.event_logging = 0
    exp = control.initialize()

    menu = TextMenu(heading="Expyriment TextMenu",
                    items=["Items 1", "Items 1", "Items 3", "Items 4",
                           "Items 5"],
                    width=250)
    print menu.get()