Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Supported by

Custom form widgets

Hi,
I'm new to OpenSesame but being a software developer, I appreciate the possibilities offered by the Python support.
I'm currently working on an experiment designed for tablets with heavy use of touch responses. We're using a thermometer graphic with several buttons that the user can press. To eliminate false responses (buttons are close together) we want the user to press a button twice. After the first press, the button should be somehow highlighted. The user can now change his mind and select and highlight another button or click on the same button a second time to log in the response.
So far I used a form with image_buttons, execute the form multiple times to get the multiple clicks and highlight the buttons with the draw_frame() method.
This doesn't seem very elegant, so I'm asking if there's a simpler way to develop this. Would you suggest developing my own custom widget for that and if so, is there any documentation that could help me? Or am I completely off and you guys have a better idea on how to tackle this problem?

Comments

  • edited August 2016

    Hi,

    That's an interesting question. The best solution is probably to subclass libopensesame.widgets.button, and implement the desired behavior this way. The script below illustrates the basic idea.

    There is not any documentation for this--it's outside of what normal users are expected to do. However, if you have some experience with coding, viewing the code on GitHub, which is well documented, should be fairly helpful.

    Cheers!
    Sebastiaan

    from libopensesame import widgets
    
    
    class touch_button(widgets.button):
    
        """A form button that needs to be tapped twice to be triggered."""
    
        def __init__(self, *arglist, **kwdict):
    
            """Start inactive."""
    
            widgets.button.__init__(self, *arglist, **kwdict)
            self._active = False
    
        def on_mouse_click(self, pos):
    
            """On a mouseclick, activate the current touch_button and deactivate all
            other touch_buttons when the current touch_button not yet active. If the
            current touch_button is active, accept the click as a regular button."""
    
            if self._active:
                return widgets.button.on_mouse_click(self, pos)
            for w in self.form.widgets:
                if isinstance(w, touch_button):
                    w._active = False
            self._active = True
    
        def draw_frame(self, rect=None, style='normal'):
    
            """Draw a normal frame when active, and a plain frame otherwise."""
    
            if self._active:
                widgets.button.draw_frame(self, rect, style='normal')           
            else:
                widgets.button.draw_frame(self, rect, style='plain')
    
    
    # Create a simple demo form     
    form = widgets.form(exp)
    b1 = touch_button(form, text='Button1')
    b2 = touch_button(form, text='Button2')
    form.set_widget(b1, (0,0))
    form.set_widget(b2, (0,1))
    form._exec()
    
  • Wow, thanks sebastian!
    The example works really well and shows me how to implement my own class. Would you suggest putting the code in an inline object at the beginning of the experiment or is there a better approach through import?
    I had some problems getting the thermometer to look good, as it right now consists of image_buttons with images of different sizes for which the column/row approach is not well suited.
    Another point is that we're restricted to python coded forms with this. I'm trying to make the thermometer response usable also for OpenSesame beginners. I therefore thought about providing it as a plugin, that the users could easily use in an experiment. They could then create their sketchpads graphically and ideally put the thermometer response object after the sketchpad to collect the response.
    Would this be possible? I would need to show the thermometer during stimulus presentation already, though... Maybe it's not worth the effort?
    Anyhow, I'm not sure yet which way would be the best to tackle this. Maybe you could give me some hints on this?

  • Sure, it would be possible to create your own plugin that simply renders a specific form--because that's essentially what you want to do, right? Whether it's worth the effort depends on how widely you think the thermometer response might be used. But once you've written the code as an inline script, turning it into a plugin may not be that much extra effort:

    I actually thought of a slight optimization of the code above, which I posted below. The functionality is the same, but instead of creating a new touch_button widget, it replaces the original button by the touch_button. In other words, if you add this code to the prepare phase of an inline_script at the start of your experiment (or do something analogous through a custom plugin), all buttons will be touch buttons.

    from libopensesame import widgets
    # We're going to monkeypatch the button, and we need to keep a copy of the
    # original unpatched button
    from libopensesame.widgets import original_button
    
    
    class touch_button(original_button):
    
        """A form button that needs to be tapped twice to be triggered."""
    
        def __init__(self, *arglist, **kwdict):
    
            """Start inactive."""
    
            original_button.__init__(self, *arglist, **kwdict)
            self._active = False
    
        def on_mouse_click(self, pos):
    
            """On a mouseclick, activate the current touch_button and deactivate all
            other touch_buttons when the current touch_button not yet active. If the
            current touch_button is active, accept the click as a regular button."""
    
            if self._active:
                return original_button.on_mouse_click(self, pos)
            for w in self.form.widgets:
                if isinstance(w, touch_button):
                    w._active = False
            self._active = True
    
        def draw_frame(self, rect=None, style='normal'):
    
            """Draw a normal frame when active, and a plain frame otherwise."""
    
            if self._active:
                original_button.draw_frame(self, rect, style='normal')           
            else:
                original_button.draw_frame(self, rect, style='plain')
    
    
    # Replace the standard button by our own touch button!
    widgets.button = touch_button
    
  • Nice! Thank you very much Sebastian!
    I'm first going to customize the image_button so that I'm able to provide an image to show in the "active" state of the button.
    I'll let you know about the progress then.

  • Hi,
    I really liked this functionality of touching twice in order to log the response. I wanted to know if the same is possible in checkbox? If so, what changes do I need to make in the above script?

Sign In or Register to comment.