-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #353 from CAMBI-tech/matrix-inquiry-preview
Matrix Inquiry Preview
- Loading branch information
Showing
20 changed files
with
929 additions
and
414 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
"""Handles button press interactions""" | ||
from abc import ABC, abstractmethod | ||
from typing import List, Optional, Type | ||
|
||
from psychopy import event | ||
from psychopy.core import CountdownTimer | ||
|
||
from bcipy.helpers.clock import Clock | ||
from bcipy.helpers.task import get_key_press | ||
|
||
|
||
class ButtonPressHandler(ABC): | ||
"""Handles button press events.""" | ||
|
||
def __init__(self, | ||
max_wait: float, | ||
key_input: str, | ||
clock: Optional[Clock] = None, | ||
timer: Type[CountdownTimer] = CountdownTimer): | ||
""" | ||
Parameters | ||
---------- | ||
wait_length - maximum number of seconds to wait for a key press | ||
key_input - key that we are listening for. | ||
clock - clock used to associate the key event with a timestamp | ||
""" | ||
self.max_wait = max_wait | ||
self.key_input = key_input | ||
self.clock = clock or Clock() | ||
self.response: Optional[List] = None | ||
|
||
self._timer: Optional[CountdownTimer] = None | ||
self.make_timer = timer | ||
|
||
@property | ||
def response_label(self) -> Optional[str]: | ||
"""Label for the latest button press""" | ||
return self.response[0] if self.response else None | ||
|
||
@property | ||
def response_timestamp(self) -> Optional[float]: | ||
"""Timestamp for the latest button response""" | ||
return self.response[1] if self.response else None | ||
|
||
def _reset(self) -> None: | ||
"""Reset any existing events and timers.""" | ||
self._timer = self.make_timer(self.max_wait) | ||
self.response = None | ||
event.clearEvents(eventType='keyboard') | ||
self._timer.reset() | ||
|
||
def await_response(self) -> None: | ||
"""Wait for a button response for a maximum number of seconds. Wait | ||
period could end early if the class determines that some other | ||
criteria have been met (such as an acceptable response).""" | ||
|
||
self._reset() | ||
while self._should_keep_waiting() and self._within_wait_period(): | ||
self._check_key_press() | ||
|
||
def has_response(self) -> bool: | ||
"""Whether a response has been provided""" | ||
return self.response is not None | ||
|
||
def _check_key_press(self) -> None: | ||
"""Check for any key press events and set the latest as the response.""" | ||
self.response = get_key_press( | ||
key_list=[self.key_input], | ||
clock=self.clock, | ||
) | ||
|
||
def _within_wait_period(self) -> bool: | ||
"""Check that we are within the allotted time for a response.""" | ||
return (self._timer is not None) and (self._timer.getTime() > 0) | ||
|
||
def _should_keep_waiting(self) -> bool: | ||
"""Check that we should keep waiting for responses.""" | ||
return not self.has_response() | ||
|
||
@abstractmethod | ||
def accept_result(self) -> bool: | ||
"""Should the result of a button press be affirmative""" | ||
|
||
|
||
class AcceptButtonPressHandler(ButtonPressHandler): | ||
"""ButtonPressHandler where a matching button press indicates an affirmative result.""" | ||
|
||
def accept_result(self) -> bool: | ||
"""Should the result of a button press be affirmative""" | ||
return self.has_response() | ||
|
||
|
||
class RejectButtonPressHandler(ButtonPressHandler): | ||
"""ButtonPressHandler where a matching button press indicates a rejection.""" | ||
|
||
def accept_result(self) -> bool: | ||
"""Should the result of a button press be affirmative""" | ||
return not self.has_response() | ||
|
||
|
||
class PreviewOnlyButtonPressHandler(ButtonPressHandler): | ||
"""ButtonPressHandler that waits for the entire span of the configured max_wait.""" | ||
|
||
def _should_keep_waiting(self) -> bool: | ||
return True | ||
|
||
def accept_result(self) -> bool: | ||
"""Should the result of a button press be affirmative""" | ||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.