Пример #1
0
 def draw_cal_target(self, x, y=None, pump_events=True):
     fill()
     if pump_events: pump()
     if y is None:
         y = x[1]
         x = x[0]
     blit(self.dc_target, 5, (int(x), int(y)))
     flip()
    def record_saccades(self):
        # Following code a rehashing of code borrowed from John Christie's original code

        # Get & write time of target onset
        target_onset = self.el.now()
        self.el.write("TARGET_ON %d" % target_onset)

        # Until 2500ms post target onset, or until target fixated
        while self.el.now() - 2500 and not self.target_acquired:
            self.display_refresh(target=True)
            pump()
            # Get end point of saccades made
            queue = self.el.get_event_queue([EL_SACCADE_END])
            # Check to see if saccade was made to target
            for saccade in queue:
                # Get end point of saccade
                gaze = saccade.getEndGaze()
                # Check if gaze fell outside fixation boundary
                if lsl(gaze, P.screen_c) > self.gaze_boundary:
                    # Get distance between gaze and target
                    dist_from_target = lsl(gaze, self.target_loc)
                    # Log if saccade is inside or outside boundary around target
                    accuracy = SACC_OUTSIDE if dist_from_target > self.gaze_boundary else SACC_INSIDE

                    # If more than one saccade
                    if len(self.saccades):
                        # Grab duration of saccade, relative to the previous saccade
                        # Not entirely sure why 4 is added....
                        duration = saccade.getStartTime() + 4 - self.saccades[-1]['end_time']
                    # Otherwise, get duration of saccade relative to target onset
                    else:
                        duration = saccade.getStartTime() + 4 - target_onset

                    # Write saccade info to database
                    if len(self.saccades) < 3:
                        self.saccades.append({
                            "rt": saccade.getStartTime() - target_onset,
                            "accuracy": accuracy,
                            "dist_from_target": dist_from_target,
                            "start_x": saccade.getStartGaze()[0],
                            "start_y": saccade.getStartGaze()[1],
                            "end_x": saccade.getEndGaze()[0],
                            "end_y": saccade.getEndGaze()[1],
                            "end_time": saccade.getEndTime(),
                            "duration": duration
                        })

                    # Target found = True if gaze within boundary surrounding target
                    if dist_from_target <= self.gaze_boundary:
                        self.target_acquired = True
                        break
Пример #3
0
    def start_trial_button(self):

        fill()
        blit(self.next_trial_box,
             5,
             self.next_trial_button_loc,
             flip_x=P.flip_x)
        blit(self.next_trial_msg,
             5,
             self.next_trial_button_loc,
             flip_x=P.flip_x)
        flip()

        if P.demo_mode or P.dm_always_show_cursor:
            show_mouse_cursor()

        flush()
        clicked = False
        while not clicked:
            event_queue = pump(True)
            for e in event_queue:
                if e.type == SDL_MOUSEBUTTONDOWN:
                    clicked = self.within_boundary("next trial button",
                                                   [e.button.x, e.button.y])
                elif e.type == SDL_KEYDOWN:
                    ui_request(e.key.keysym)

        if not (P.demo_mode or P.dm_always_show_cursor):
            hide_mouse_cursor()
Пример #4
0
    def get_effort(self):

        slider_loc = (P.screen_c[0], int(P.screen_y * 0.55))
        slider_cols = {'line': WHITE, 'slider': TRANSLUCENT_WHITE}
        scale = Slider(int(P.screen_x * 0.75),
                       ticks=5,
                       location=slider_loc,
                       fills=slider_cols)
        label_pad = scale.tick.surface_height

        show_mouse_cursor()
        onset = time.time()
        while True:
            sq = pump(True)
            ui_request(queue=sq)
            fill()
            blit(self.effort_q, 5, (P.screen_c[0], int(P.screen_y * 0.3)))
            blit(self.mineffort_msg, 8,
                 (scale.xmin, slider_loc[1] + label_pad))
            blit(self.maxeffort_msg, 8,
                 (scale.xmax, slider_loc[1] + label_pad))
            scale.draw()
            if scale.pos != None:
                self.submit.draw()
            flip()
            scale.listen(sq)
            if scale.pos != None:
                if self.submit.listen(sq) or key_pressed('Return', queue=sq):
                    rt = time.time() - onset
                    hide_mouse_cursor()
                    return (scale.pos, rt)
Пример #5
0
    def __trial__(self, trial, practice):
        """
		Private method; manages a trial.
		"""
        from klibs.KLUtilities import pump, show_mouse_cursor, hide_mouse_cursor

        # At start of every trial, before setup_response_collector or trial_prep are run, retrieve
        # the values of the independent variables (factors) for that trial (as generated earlier by
        # TrialFactory) and set them as attributes of the experiment object.
        factors = list(self.trial_factory.exp_factors.keys())
        for iv in factors:
            iv_value = trial[factors.index(iv)]
            setattr(self, iv, iv_value)

        pump()
        self.setup_response_collector()
        self.trial_prep()
        tx = None
        try:
            if P.development_mode and (P.dm_trial_show_mouse or
                                       (P.eye_tracking
                                        and not P.eye_tracker_available)):
                show_mouse_cursor()
            self.evm.start_clock()
            if P.eye_tracking and not P.manual_eyelink_recording:
                self.el.start(P.trial_number)
            P.in_trial = True
            self.__log_trial__(self.trial())
            P.in_trial = False
            if P.eye_tracking and not P.manual_eyelink_recording:
                self.el.stop()
            if P.development_mode and (P.dm_trial_show_mouse or
                                       (P.eye_tracking
                                        and not P.eye_tracker_available)):
                hide_mouse_cursor()
            self.evm.stop_clock()
            self.trial_clean_up()
        except TrialException as e:
            P.trial_id = False
            self.trial_clean_up()
            self.evm.stop_clock()
            tx = e
        if P.eye_tracking and not P.manual_eyelink_recording:
            # todo: add a warning, here, if the recording hasn't been stopped when under manual control
            self.el.stop()
        if tx:
            raise tx
Пример #6
0
    def jc_saccade_data(self):
        # following code is tidied up but otherwise borrowed from John Christie's original code
        target_onset = self.el.now()
        self.el.write("TARGETON %d" % target_onset)
        while self.el.now() - target_onset < 2500 and not self.target_acquired:
            self.display_refresh(self.box_axis_during_target(),
                                 self.circle,
                                 target=self.target_location)
            pump()  # refreshes TryLink event queue if using
            queue = self.el.get_event_queue([EL_SACCADE_END])
            for saccade in queue:
                gaze = saccade.getEndGaze()
                if lsl(gaze, P.screen_c) > self.fixation_boundary:
                    dist_from_target = lsl(
                        gaze, self.target_locs[self.target_location])
                    accuracy = SACC_OUTSIDE if dist_from_target > self.fixation_boundary else SACC_INSIDE
                    if len(self.saccades):
                        duration = saccade.getStartTime(
                        ) + 4 - self.saccades[-1]['end_time']
                    else:
                        duration = saccade.getStartTime() + 4 - target_onset
                    if len(self.saccades) < 3:
                        self.saccades.append({
                            "rt":
                            saccade.getStartTime() - target_onset,
                            "accuracy":
                            accuracy,
                            "dist_from_target":
                            dist_from_target,
                            "start_x":
                            saccade.getStartGaze()[0],
                            "start_y":
                            saccade.getStartGaze()[1],
                            "end_x":
                            saccade.getEndGaze()[0],
                            "end_y":
                            saccade.getEndGaze()[1],
                            "end_time":
                            saccade.getEndTime(),
                            "duration":
                            duration
                        })

                    if dist_from_target <= self.fixation_boundary:
                        self.target_acquired = True
                        break
Пример #7
0
 def jc_wait_time(self):
     if self.before_target:
         if lsl(self.el.gaze(), P.screen_c) > self.fixation_boundary:
             self.log_and_recycle_trial('eye')
         q = pump(True)
         if key_pressed(queue=q):
             if key_pressed(SDLK_SPACE, queue=q):
                 self.log_and_recycle_trial('early')
             else:
                 self.log_and_recycle_trial('key')
Пример #8
0
 def key_pressed(self, keysym, queue=None):
     pressed = False
     if not queue:
         queue = pump(True)
     for e in queue:
         if e.type == SDL_KEYDOWN:
             ui_request(e.key.keysym)
             if e.key.keysym.sym == keysym:
                 pressed = True
                 break
     return pressed
Пример #9
0
    def _collect(self):

        q = pump(True)
        ui_request(queue=q)
        for e in q:
            if e.type == sdl2.SDL_MOUSEBUTTONDOWN:
                coords = (e.button.x, e.button.y)
                response = self.which_boundary(coords)
                if response != None:
                    return response
        return None
 def wait_time(self):
     # Appropriated verbatim from original code written by John Christie
     if self.before_target:
         gaze = self.el.gaze()
         if not self.bi.within_boundary(label='drift_correct', p=gaze):
             self.log_and_recycle_trial('eye')
         q = pump(True)
         if key_pressed(queue=q):
             if key_pressed(SDLK_SPACE, queue=q):
                 self.log_and_recycle_trial('early')
             else:
                 self.log_and_recycle_trial('key')
Пример #11
0
	def drift_correct(self, location=None, target=None, fill_color=None, draw_target=True):
		"""Checks the accuracy of the eye tracker's calibration by presenting a fixation
		stimulus and requiring the participant to press the space bar while looking directly at
		it. If there is a large difference between the gaze location at the time the key was
		pressed and the true location of the fixation, it indicates that there has been drift
		in the calibration.

		In TryLink mode, drift correct targets are still displayed the same as with a hardware
		eye tracker. Simulated drift corrects are performed by clicking the drift correct target
		with the mouse.

		Args:
			location (Tuple(int, int), optional): The (x,y) pixel coordinates where the drift
				correct target should be located. Defaults to the center of the screen.
			target: A :obj:`Drawbject` or other :func:`KLGraphics.blit`-able shape to use as
				the drift correct target. Defaults to a circular :func:`drift_correct_target`.
			fill_color: A :obj:`List` or :obj:`Tuple` containing an RGBA colour to use for the
				background for the drift correct screen. Defaults to the value of
				``P.default_fill_color``.
			draw_target (bool, optional): A flag indicating whether the function should draw
				the drift correct target itself (True), or whether it should leave it to the
				programmer to draw the target before :meth:`drift_correct` is called (False). 
				Defaults to True.

		"""
		show_mouse_cursor()

		target = drift_correct_target() if target is None else target
		draw_target = EL_TRUE if draw_target in [EL_TRUE, True] else EL_FALSE
		location = P.screen_c if location is None else location
		if not iterable(location):
			raise ValueError("'location' must be a pair of (x,y) pixel coordinates.")
		dc_boundary = CircleBoundary('drift_correct', location, P.screen_y // 30)
		
		while True:
			event_queue = pump(True)
			ui_request(queue=event_queue)
			if draw_target == EL_TRUE:
				fill(P.default_fill_color if not fill_color else fill_color)
				blit(target, 5, location)
				flip()
			else:
				SDL_Delay(2) # required for pump() to reliably return mousebuttondown events
			for e in event_queue:
				if e.type == SDL_MOUSEBUTTONDOWN and dc_boundary.within([e.button.x, e.button.y]):
					hide_mouse_cursor()
					if draw_target == EL_TRUE:
						fill(P.default_fill_color if not fill_color else fill_color)
						flip()
					return 0
Пример #12
0
    def slide(self):
        show_mouse_cursor()
        self.blit()

        dragging = False
        while True:
            if not dragging:
                m_pos = mouse_pos()
                for event in pump(True):
                    if event.type == sdl2.SDL_KEYDOWN:
                        ui_request(event.key.keysym)
                    elif event.type in (sdl2.SDL_MOUSEBUTTONDOWN,
                                        sdl2.SDL_MOUSEBUTTONUP):
                        within_button = self.within_boundary("button", m_pos)
                        if self.button_active and within_button:
                            return self.response
                dragging = self.within_boundary("handle", m_pos)
            if dragging:
                button_up = False
                off_handle = False
                for event in pump(True):
                    if event.type == sdl2.SDL_KEYDOWN:
                        ui_request(event.key.keysym)
                    elif event.type == sdl2.SDL_MOUSEBUTTONUP:
                        button_up = True

                off_handle = not self.within_boundary("handle", mouse_pos())

                if off_handle or button_up:
                    dragging = False
                    self.response = self.handle_value()
                    self.button_active = True
                    flush()
                    return -1
                self.handle_pos = mouse_pos()[0]
                self.blit()
        return False
Пример #13
0
def key_pressed(key=None, queue=None):
    """Checks an event queue to see if a given key has been pressed. If no key is specified,
	the function will return True if any key has been pressed. If an event queue is not
	manually specified, :func:`~klibs.KLUtilities.pump` will be called and the returned event
	queue will be used.
	
	For a comprehensive list of valid key names, see the 'Name' column of the following 
	table: https://wiki.libsdl.org/StuartPBentley/CombinedKeyTable

	For a comprehensive list of valid SDL keycodes, consult the following table:
	https://wiki.libsdl.org/SDL_Keycode

	Args:
		key (str or :obj:`sdl2.SDL_Keycode`, optional): The key name or SDL keycode
			corresponding to the key to check. If not specified, any keypress will return
			True.
		queue (:obj:`List` of :obj:`sdl2.SDL_Event`, optional): A list of SDL_Events to check
			for valid keypress events.

	Returns:
		bool: True if key has been pressed, otherwise False.

	Raises:
		ValueError: If the keycode is anything other than an SDL_Keycode integer or None.

	"""
    strtypes = [type(u" "), type(" ")]  # for Python 2/3 unicode compatibility
    if type(key) in strtypes:
        keycode = SDL_GetKeyFromName(key.encode('utf8'))
        if keycode == 0:
            raise ValueError("'{0}' is not a recognized key name.".format(key))
    else:
        keycode = key

    if type(keycode).__name__ not in ['int', 'NoneType']:
        raise ValueError(
            "'key' must be a string, an SDL Keycode (int), or None.")

    pressed = False
    if queue == None:
        queue = pump(True)
    for e in queue:
        if e.type == SDL_KEYDOWN:
            ui_request(e.key.keysym)
            if not keycode or e.key.keysym.sym == keycode:
                pressed = True

    return pressed
Пример #14
0
 def collect_response(self):
     self.start = time.time()
     finished = False
     selection = None
     last_selected = None
     flush()
     mt_start = None
     while not finished:
         show_mouse_cursor()
         events = pump(True)
         for e in events:
             if e.type == sdl2.SDL_KEYDOWN:
                 ui_request(e.key.keysym)
             elif e.type == sdl2.SDL_MOUSEBUTTONDOWN:
                 selection = None
                 for b in self.buttons:
                     if self.within_boundary(b.button_text,
                                             [e.button.x, e.button.y]):
                         self.toggle(b)
                         if not self.rt:
                             self.rt = time.time() - self.start
                             mt_start = time.time()
                         if b.active:
                             selection = b
                             last_selected = b
                             if callable(b.callback):
                                 if self.finish_b is None:
                                     return b.callback
                                 else:
                                     b.callback()
                     try:
                         if self.finish_b.active and self.within_boundary(
                                 "Done", [e.button.x, e.button.y]):
                             self.response = int(last_selected.button_text)
                             self.mt = time.time() - mt_start
                             finished = True
                     except AttributeError:
                         pass
         try:
             self.finish_b.active = selection is not None
         except AttributeError:
             pass
         self.render()
     fill()
     flip()
     hide_mouse_cursor()
Пример #15
0
def konami_code(callback=None, cb_args={}, queue=None):
    """An implementation of the classic Konami code. If called repeatedly within a loop, this
	function will collect keypress matching the sequence and save them between calls until the full
	sequence has been entered correctly.
	
	If a callback function has been specified, it will be called once the code has been entered. 
	If any incorrect keys are pressed during entry, the collected input so far will be reset and
	the code will need to be entered again from the start.
	
	Useful for adding hidden debug menus and other things you really don't want participants
	activating by mistake...?

	Args:
		callback (function, optional): The function to be run upon successful input of the Konami
			code.
		cbargs (:obj:`Dict`, optional): A dict of keyword arguments to pass to the callback
			function when it's called.
		queue (:obj:`List` of :obj:`sdl2.SDL_Event`, optional): A list of SDL Events to check
			for valid keys in the sequence.

	Returns:
		bool: True if sequence was correctly entered, otherwise False.

	"""
    sequence = [
        SDLK_UP, SDLK_DOWN, SDLK_UP, SDLK_DOWN, SDLK_LEFT, SDLK_RIGHT,
        SDLK_LEFT, SDLK_RIGHT, SDLK_b, SDLK_a
    ]
    if not hasattr(konami_code, "input"):
        konami_code.input = [
        ]  # static variable, stays with the function between calls

    if queue == None:
        queue = pump(True)
    for e in queue:
        if e.type == SDL_KEYDOWN:
            ui_request(e.key.keysym)
            konami_code.input.append(e.key.keysym.sym)
            if konami_code.input != sequence[:len(konami_code.input)]:
                konami_code.input = []  # reset input if mismatch encountered
            elif len(konami_code.input) == len(sequence):
                konami_code.input = []
                if callable(callback):
                    callback(**cb_args)
                return True
    return False
Пример #16
0
 def get_input_key(self):
     keys = []
     for event in pump(True):
         if event.type == sdl2.SDL_KEYDOWN:
             keysym = event.key.keysym
             if not self.el._quitting:
                 # don't process quit requests while already quitting
                 ui_request(keysym)
             try:
                 key = self.pylink_keycodes[keysym.sym]
             except KeyError:
                 key = keysym.sym
             # don't allow escape to control tracker unless calibrating
             if key == pylink.ESC_KEY and not self.el.in_setup:
                 key = pylink.JUNK_KEY
             keys.append(pylink.KeyInput(key, keysym.mod))
     return keys
Пример #17
0
    def collect(self):

        show_mouse_cursor()
        onset = time.time()

        while self.scale.response == None:
            q = pump(True)
            ui_request(queue=q)
            fill()
            blit(self.q, location=self.origin, registration=8)
            self.scale.response_listener(q)
            flip()

        response = self.scale.response
        rt = time.time() - onset
        hide_mouse_cursor()
        self.scale.response = None  # reset for next time
        return Response(response, rt)
Пример #18
0
def any_key(allow_mouse_click=True):
    """A function that waits until any keyboard (or mouse, if enabled) input is received
	before returning. Intended for use in situations when you want to require input before
	progressing through the experiment (e.g. "To start the next block, press any key..."). 
	Not to be used for response collection (see :mod:`~klibs.KLResponseCollectors`).

	Args:
		allow_mouse_click (bool, optional): Whether to return immediately on mouse clicks in
			addition to key presses.
	
	"""
    any_key_pressed = False
    while not any_key_pressed:
        for event in pump(True):
            if event.type == SDL_KEYDOWN:
                ui_request(event.key.keysym)
                any_key_pressed = True
            if event.type == SDL_MOUSEBUTTONUP and allow_mouse_click:
                any_key_pressed = True
Пример #19
0
    def __validate(self):
        instruction = (
            "Okay, threshold set! "
            "To ensure its validity, please provide one (and only one) more response."
        )
        fill()
        message(instruction, location=P.screen_c, registration=5)
        flip()
        self.stream.start()
        validate_counter = CountDown(5)
        while validate_counter.counting():
            ui_request()
            if self.stream.sample().peak >= self.threshold:
                validate_counter.finish()
                self.threshold_valid = True
        self.stream.stop()

        if self.threshold_valid:
            validation_msg = "Great, validation was successful! Press any key to continue."
        else:
            validation_msg = (
                "Validation wasn't successful. "
                "Type C to re-calibrate or V to try validation again.")
        fill()
        message(validation_msg, location=P.screen_c, registration=5)
        flip()
        selection_made = False
        while not selection_made:
            q = pump(True)
            if self.threshold_valid:
                if key_pressed(queue=q):
                    return
            else:
                if key_pressed(SDLK_c, queue=q):
                    self.calibrate()
                elif key_pressed(SDLK_v, queue=q):
                    self.__validate()
Пример #20
0
def ui_request(key_press=None, execute=True, queue=None):
    """Checks keyboard input for interface commands, which currently include:
	
	- Quit (Ctrl/Command-Q): Quit the experiment runtime

	- Calibrate Eye Tracker (Ctrl/Command-C): Enter setup mode for the connected eye tracker, 
	  if eye tracking is enabled for the experiment and not using TryLink simulation.
	
	If no event queue from :func:`~klibs.KLUtilities.pump` and no keypress event(s) are
	supplied to this function, the current contents of the SDL2 event queue will be fetched
	and processed using :func:`~klibs.KLUtilities.pump`. 
	
	This function is meant to be called during loops in your experiment where no other input
	checking occurs, to ensure that you can quit your experiment or recalibrate your eye
	tracker during those periods. This function is automatically called by other functions that
	process keyboard/mouse input, such as :func:`any_key` and :func:`key_pressed`, so you will
	not need to call it yourself in places where one of them is already being called. 
	In addition, the :obj:`~klibs.KLResponseCollectors.ResponseCollector` collect method also
	calls this function every loop, meaning that you do not need to include it when writing
	ResponseCollector callbacks.

	Args:
		key_press (:obj:`sdl2.SDL_Keysym`, optional): The key.keysym of an SDL_KEYDOWN event to
			check for a valid UI command.
		execute (bool, optional): If True, valid UI commands will be executed immediately. 
			Otherwise, valid UI commands will return a string indicating the type of command
			received. Defaults to True.
		queue (:obj:`List` of :obj:`sdl2.SDL_Event`, optional): A list of SDL Events to check
			for valid UI commands.
		
	Returns:
		str or bool: "quit" if a Quit request encountered, "el_calibrate" if a Calibrate 
			Eye Tracker request encountered, otherwise False.
	"""
    if key_press == None:
        if queue == None:
            queue = pump(True)
        for e in queue:
            if e.type == SDL_KEYDOWN:
                request = ui_request(e.key.keysym, execute)
                if request:
                    return request
        return False

    else:
        try:
            key_press.mod
        except AttributeError:
            wrong = type(key_press).__name__
            e = "'key_press' must be a valid SDL Keysym object (got '{0}')".format(
                wrong)
            raise TypeError(e)

        k = key_press
        if any(k.mod & mod
               for mod in [KMOD_GUI, KMOD_CTRL]):  # if ctrl or meta being held
            if k.sym == SDLK_q:
                if execute:
                    from klibs.KLEnvironment import exp
                    exp.quit()
                return "quit"
            elif k.sym == SDLK_c:
                if P.eye_tracking:
                    from klibs.KLEnvironment import el
                    if el.initialized:  # make sure el.setup() has been run already
                        if execute:
                            el.calibrate()
                        return "el_calibrate"
        return False
Пример #21
0
def query(query_ob, anonymous=False):
    '''Asks the user a question and collects the response via the keyboard. Intended for use with
	the queries contained within a project's user_queries.json file. This function is used
	internally for collecting demographics at the start of each run, but can also be used during
	experiment runtime to collect info from participants based on the queries contained in the
	"experimental" section of the user_queries.json file.

	Args:
		query_ob (:class:`~klibs.KLJSON_Object.AttributeDict`): The object containing the query
			to present. See :obj:`~klibs.KLCommunication.user_queries` for more information.
		anonymous (bool, optional): If True, will immediately return the query object's anonymous
			value without prompting the user (used interally for P.development_mode). Defaults to
			False.
	
	Returns:
		The response to the query, coerced to the type specified by query_ob.format.type (can be
		str, int, float, bool, or None).
	
	Raises:
		ValueError: If the query object's type is not one of "str", "int", "float", "bool", or None,
			or if a query_ob.format.range value is given and the type is not "int" or "float".
		TypeError: If query_ob.accepted is specified and it is not a list of values, or if a
			query_ob.format.range is specified and it is not a two-item list.
			
	'''
    from klibs.KLEnvironment import txtm

    if anonymous:
        try:
            # Check if anon value is an EVAL statement, and if so evaluate it
            eval_statement = re.match(re.compile(u"^EVAL:[ ]*(.*)$"),
                                      query_ob.anonymous_value)
            if eval_statement:
                query_ob.anonymous_value = eval(eval_statement.group(1))
        except TypeError:
            pass
        return query_ob.anonymous_value

    f = query_ob.format
    if f.type not in ("int", "float", "str", "bool", None):
        err = "Invalid data type for query '{0}': {1}".format(
            query_ob.title, f.type)
        raise ValueError(err)

    # Set defaults for styles and positioning if not specified
    if f.styles == 'default':
        f.styles = AttributeDict({
            'query': 'default',
            'input': 'default',
            'error': 'alert'
        })

    locations = AttributeDict({
        'query': AUTO_POS,
        'input': AUTO_POS,
        'error': AUTO_POS
    })
    registrations = AttributeDict({
        'query': AUTO_POS,
        'input': AUTO_POS,
        'error': AUTO_POS
    })
    if f.positions == 'default':
        f.positions = AttributeDict({
            'locations': locations,
            'registrations': registrations
        })
    else:
        if f.positions.locations == 'default':
            f.positions.locations = locations
        if f.positions.registrations == 'default':
            f.positions.registrations = registrations

    q_text = message(query_ob.query,
                     f.styles.query,
                     align='center',
                     blit_txt=False)

    # address automatic positioning
    p = f.positions
    if p.locations.query == AUTO_POS:
        p.locations.query = [P.screen_c[0], int(0.1 * P.screen_y)]
        p.registrations.query = BL_CENTER
    for k in ['input', 'error']:
        if p.locations[k] == AUTO_POS:
            v_pad = q_text.height + 2 * txtm.styles[f.styles.query].line_height
            p.locations[k] = [P.screen_c[0], p.locations.query[1] + v_pad]
            p.registrations[k] = BL_CENTER

    # Create an informative error message for invalid responses
    accepted_responses = query_ob.accepted  # for code readability
    try:
        if accepted_responses:
            try:
                iter(accepted_responses)
                accepted_str = pretty_list(accepted_responses)
                invalid_answer_str = default_strings['invalid_answer'].format(
                    accepted_str)
            except:
                raise TypeError(
                    "The 'accepted' key of a question must be a list of values."
                )
        elif f.range:
            if f.type not in ("int", "float"):
                raise ValueError(
                    "Only queries with numeric types can use the range parameter."
                )
            elif isinstance(f.range, list) == False or len(f.range) != 2:
                raise TypeError(
                    "Query ranges must be two-item lists, containing an upper bound "
                    "and a lower bound.")
            try:
                template = default_strings['out_of_range']
            except KeyError:
                template = "Your answer must be a number between {0} and {1}, inclusive."
            invalid_answer_str = template.format(f.range[0], f.range[1])
    except:
        cso("\n<red>Error encountered while parsing query '{0}':</red>".format(
            query_ob.title))
        raise

    # user input loop; exited by breaking
    input_string = u''  # populated in loop below
    error_string = None
    user_finished = False

    # Clear event queue and draw query text to screen before entering input loop
    flush()
    SDL_StartTextInput()
    fill()
    blit(q_text, p.registrations.query, p.locations.query)
    flip()

    while not user_finished:
        for event in pump(True):

            if event.type == SDL_KEYDOWN:

                error_string = None  # clear error string (if any) on new key event
                ui_request(event.key.keysym)
                sdl_keysym = event.key.keysym.sym

                if sdl_keysym == SDLK_ESCAPE:
                    # Esc clears any existing input
                    input_string = ""

                elif sdl_keysym == SDLK_BACKSPACE:
                    # Backspace removes last character from input
                    input_string = input_string[:-1]

                elif sdl_keysym in (SDLK_KP_ENTER, SDLK_RETURN):
                    # Enter or Return check if a valid response has been made and end loop if it has
                    if len(input_string) > 0:
                        response = input_string
                        # If type is 'int' or 'float', make sure input can be converted to that type
                        if f.type == "int":
                            try:
                                response = int(input_string)
                            except ValueError:
                                error_string = "Please respond with an integer."
                        elif f.type == "float":
                            try:
                                response = float(input_string)
                            except ValueError:
                                error_string = "Please respond with a number."
                        # If no errors yet, check input against list of accepted values (if q has one)
                        if not error_string:
                            if accepted_responses:
                                user_finished = response in accepted_responses
                                if not user_finished:
                                    error_string = invalid_answer_str
                            elif f.range:
                                user_finished = (f.range[0] <= response <=
                                                 f.range[1])
                                if not user_finished:
                                    error_string = invalid_answer_str
                            else:
                                user_finished = True
                    elif query_ob.allow_null is True:
                        user_finished = True
                    else:
                        # If no input and allow_null is false, display error
                        error_string = default_strings['answer_not_supplied']

            elif event.type == SDL_TEXTINPUT:

                input_string += event.text.text.decode('utf-8')
                if f.case_sensitive is False:
                    input_string = input_string.lower()
                input_string = input_string.strip(
                )  # remove any trailing whitespace

            else:
                continue

            # If any text entered or error message encountered, render text for drawing
            if error_string:
                rendered_input = message(error_string,
                                         f.styles.error,
                                         blit_txt=False)
                input_string = ""
            elif len(input_string):
                if f.password:
                    rendered_input = message(len(input_string) * '*',
                                             f.styles.input,
                                             blit_txt=False)
                else:
                    rendered_input = message(input_string,
                                             f.styles.input,
                                             blit_txt=False)
            else:
                rendered_input = None

            # Draw question and any entered response to screen
            fill()
            blit(q_text, p.registrations.query, p.locations.query)
            if rendered_input:
                loc = p.locations.error if error_string else p.locations.input
                reg = p.registrations.error if error_string else p.registrations.input
                blit(rendered_input, reg, loc)
            flip()

    # Once a valid response has been made, clear the screen
    fill()
    flip()
    SDL_StopTextInput()

    if query_ob.allow_null and len(input_string) == 0:
        return None
    elif f.type == "int":
        return int(input_string)
    elif f.type == "str":
        if f.action == QUERY_ACTION_HASH:
            return make_hash(input_string)
        elif f.action == QUERY_ACTION_UPPERCASE:
            return utf8(input_string).upper()
        else:
            return utf8(input_string)
    elif f.type == "float":
        return float(input_string)
    elif f.type == "bool":
        return input_string in f.accept_as_true
    else:
        return input_string