/**
 * \file
 *
 * \brief Spin control widget
 * Copyright (c) 2011-2015 Atmel Corporation. All rights reserved.
 *
 * \asf_license_start
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 *    Atmel microcontroller product.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * \asf_license_stop
 *
 */
/*
 * Support and FAQ: visit <a href="http://www.atmel.com/design-support/">Atmel Support</a>
 */

#include "sysfont.h"
#include "stdio.h"
#include "gfx_mono.h"
#include "gfx_mono_spinctrl.h"

PROGMEM_DECLARE(gfx_mono_color_t, spin_indicator_data[]) = {
	GFX_MONO_SPINCTRL_SPIN_INDICATOR_BITMAP
};

PROGMEM_DECLARE(gfx_mono_color_t, indicator_data[]) = {
	GFX_MONO_SPINCTRL_INDICATOR_BITMAP
};

struct gfx_mono_bitmap gfx_mono_spinctrl_bitmap_spin_indicator = {
	.height = GFX_MONO_SPINCTRL_SPIN_INDICATOR_HEIGHT,
	.width = GFX_MONO_SPINCTRL_SPIN_INDICATOR_WIDTH,
	.type = GFX_MONO_BITMAP_PROGMEM,
	.data.progmem = spin_indicator_data
};

struct gfx_mono_bitmap gfx_mono_spinctrl_bitmap_indicator = {
	.height = GFX_MONO_SPINCTRL_INDICATOR_HEIGHT,
	.width = GFX_MONO_SPINCTRL_INDICATOR_WIDTH,
	.type = GFX_MONO_BITMAP_PROGMEM,
	.data.progmem = indicator_data
};

/**
 * \brief Draw or delete indicator arrow in front of spinner
 *
 * \param *spinner initialized gfx_mono_spinctrl struct
 * \param draw     true on draw, false on delete
 */
static void gfx_mono_spinctrl_draw_indicator(struct gfx_mono_spinctrl *spinner,
		bool draw)
{
	if (draw) {
		gfx_mono_put_bitmap(&gfx_mono_spinctrl_bitmap_indicator, 0,
				spinner->y);
	} else {
		gfx_mono_draw_filled_rect(0, spinner->y,
				GFX_MONO_SPINCTRL_INDICATOR_WIDTH,
				GFX_MONO_SPINCTRL_INDICATOR_HEIGHT,
				GFX_PIXEL_CLR);
	}
}

/**
 * \brief Draw or delete indicator in front of spinner data
 *
 * \param spinner  pointer to initialized gfx_mono_spinctrl struct
 * \param draw     true on draw, false on delete
 */
static void gfx_mono_spinctrl_draw_spin_indicator(struct gfx_mono_spinctrl
		*spinner, bool draw)
{
	if (draw) {
		gfx_mono_put_bitmap(&gfx_mono_spinctrl_bitmap_spin_indicator,
				GFX_MONO_LCD_WIDTH - GFX_MONO_SPINCTRL_SPIN_INDICATOR_WIDTH,
				spinner->y);
	} else {
		gfx_mono_draw_filled_rect(GFX_MONO_LCD_WIDTH -
				GFX_MONO_SPINCTRL_SPIN_INDICATOR_WIDTH,
				spinner->y,
				GFX_MONO_SPINCTRL_SPIN_INDICATOR_WIDTH,
				GFX_MONO_SPINCTRL_SPIN_INDICATOR_HEIGHT,
				GFX_PIXEL_CLR);
	}
}

/**
 * \brief Draw OK button at bottom of screen
 *
 * This function draws an OK button at the bottom of the screen. It will
 * also draw an indicator arrow in front of the button if the indicator bool
 * is true. If the draw bool is false, the OK button will be deleted, and if
 * the indicator bool is false, the indicator will be deleted.
 *
 * \param draw       true on draw, false on delete
 * \param indicator  true on draw indicator, false on delete
 */
static void gfx_mono_spinctrl_draw_button(bool draw, bool indicator)
{
	uint8_t width;
	uint8_t height;
	uint8_t offset;
	char string_buf[22];

	/* Clear bottom line */
	gfx_mono_draw_filled_rect(0,
			(SYSFONT_HEIGHT + 1) *
			GFX_MONO_SPINCTRL_MAX_ELEMENTS_IN_SPINCOLLECTION,
			GFX_MONO_LCD_WIDTH, SYSFONT_HEIGHT, GFX_PIXEL_CLR);

	snprintf(string_buf, sizeof(string_buf), "OK");
	gfx_mono_get_string_bounding_box(string_buf, &sysfont, &width, &height);
	offset = (GFX_MONO_LCD_WIDTH - width) / 2;

	if (draw) {
		/* Draw OK button in the middle of the last line */
		gfx_mono_draw_string(string_buf, offset,
				(SYSFONT_HEIGHT + 1) *
				GFX_MONO_SPINCTRL_MAX_ELEMENTS_IN_SPINCOLLECTION,
				&sysfont);
		if (indicator) {
			/* Draw indicator arrow in front of button */
			gfx_mono_put_bitmap(&gfx_mono_spinctrl_bitmap_indicator,
					offset - GFX_MONO_SPINCTRL_INDICATOR_WIDTH,
					(SYSFONT_HEIGHT + 1) *
					GFX_MONO_SPINCTRL_MAX_ELEMENTS_IN_SPINCOLLECTION);
		} else {
			/* Delete indicator */
			gfx_mono_draw_filled_rect(offset -
					GFX_MONO_SPINCTRL_INDICATOR_WIDTH,
					(SYSFONT_HEIGHT + 1) *
					GFX_MONO_SPINCTRL_MAX_ELEMENTS_IN_SPINCOLLECTION,
					GFX_MONO_SPINCTRL_INDICATOR_WIDTH,
					GFX_MONO_SPINCTRL_INDICATOR_HEIGHT,
					GFX_PIXEL_CLR);
		}
	} else {
		/* Delete OK button */
		gfx_mono_draw_filled_rect(
				offset - GFX_MONO_SPINCTRL_INDICATOR_WIDTH,
				(SYSFONT_HEIGHT + 1) *
				GFX_MONO_SPINCTRL_MAX_ELEMENTS_IN_SPINCOLLECTION, 20,
				SYSFONT_HEIGHT, GFX_PIXEL_CLR);
	}
}

/**
 * \brief Draw spinner at its position.
 *
 * This function draws a spinner at its position.
 * The title of the spinner is only drawn if the redraw option is set.
 * If the spinner is in focus, arrows will be drawn next to the spinner value
 * to indicate that it is spinnable.
 *
 * \param spinner  pointer to initialized gfx_mono_spinctrl struct
 * \param redraw   true if title of spinner should be drawn
 */
void gfx_mono_spinctrl_draw(struct gfx_mono_spinctrl *spinner, bool redraw)
{
	char string_buf[GFX_MONO_SPINCTRL_INT_SPINNER_WIDTH];
	uint8_t index;
	uint8_t offset;

	if (redraw) {
		/* Clear line */
		gfx_mono_draw_filled_rect(0, spinner->y, GFX_MONO_LCD_WIDTH,
				SYSFONT_HEIGHT, GFX_PIXEL_CLR);
		/* Draw title */
		gfx_mono_draw_progmem_string((char PROGMEM_PTR_T)spinner->title,
				GFX_MONO_SPINCTRL_INDICATOR_WIDTH + 1,
				spinner->y, &sysfont);
	}

	if (spinner->in_focus) {
		gfx_mono_spinctrl_draw_spin_indicator(spinner, true);
	} else {
		gfx_mono_spinctrl_draw_spin_indicator(spinner, false);
	}

	if (spinner->datatype == SPINTYPE_INTEGER) {
		offset = GFX_MONO_LCD_WIDTH -
				(SYSFONT_WIDTH *
				GFX_MONO_SPINCTRL_INT_SPINNER_WIDTH);
		snprintf(string_buf, sizeof(string_buf), "%d",
				spinner->integer_data);
		/* Delete previous spinner data */
		gfx_mono_draw_filled_rect(offset, spinner->y,
				GFX_MONO_LCD_WIDTH - offset -
				GFX_MONO_SPINCTRL_SPIN_INDICATOR_WIDTH,
				SYSFONT_HEIGHT,
				GFX_PIXEL_CLR);
		/* Draw integer data */
		gfx_mono_draw_string(string_buf, offset, spinner->y, &sysfont);
	} else if (spinner->datatype == SPINTYPE_STRING) {
		index = spinner->strings.index;
		offset = GFX_MONO_LCD_WIDTH -
				(SYSFONT_WIDTH *
				GFX_MONO_SPINCTRL_STRING_SPINNER_WIDTH);

		/* Delete previous spinner data */
		gfx_mono_draw_filled_rect(offset, spinner->y,
				GFX_MONO_LCD_WIDTH - offset -
				GFX_MONO_SPINCTRL_SPIN_INDICATOR_WIDTH,
				SYSFONT_HEIGHT,
				GFX_PIXEL_CLR);

		/* Draw string data */
		gfx_mono_draw_progmem_string(
				(char PROGMEM_PTR_T)spinner->strings.data[index], offset,
				spinner->y, &sysfont);
	}
}

/**
 * \brief Initialize a spinner.
 *
 * This function initializes a spinner to either
 * \ref gfx_mono_spinctrl_type_t "SPINTYPE_STRING" spinner that spins through
 * the strings in a \ref PROGMEM_STRING_T or a
 * \ref gfx_mono_spinctrl_type_t "SPINTYPE_INTEGER" spinner that spins through
 * integers.
 * If the spinner type is SPINTYPE_INTEGER, it will spin from lower_limit to
 * upper_limit.
 * If the spinner type is SPINTYPE_STRING, a pointer to a PROGMEM_STRING_T must
 * be provided, and the spinner will spin through the strings starting at
 * index lower_limit ending at index upper_limit.
 * The y parameter specifies where to place the spinner on the screen, but is
 * overwritten if the spinner is put in a spincollection.
 *
 * \param spinner      pointer to initialized gfx_mono_spinctrl struct
 * \param datatype     typer of spinner, integer or string
 * \param title        title of spinner
 * \param data         pointer to progmem string array if datatype is string.
 *                     NULL if datatype is integer.
 * \param lower_limit  lower limit and start value of spinner's data
 * \param upper_limit  upper limit of spinner's data
 * \param y            y position of spinner
 */
void gfx_mono_spinctrl_init(struct gfx_mono_spinctrl *spinner,
		gfx_mono_spinctrl_type_t datatype, PROGMEM_STRING_T title,
		PROGMEM_STRING_T *data, int16_t lower_limit,
		int16_t upper_limit,
		gfx_coord_t y)
{
	/* Initialization of spinner parameters */
	spinner->title = title;
	spinner->datatype = datatype;
	spinner->lower_limit = lower_limit;
	spinner->upper_limit = upper_limit;
	spinner->y = y;
	spinner->in_focus = false;
	spinner->last_saved_value = spinner->lower_limit;

	if (datatype == SPINTYPE_STRING) {
		spinner->strings.data = data;
		spinner->strings.index = lower_limit;
	} else {
		spinner->integer_data = lower_limit;
	}
}

/**
 * \brief Initialize a spincollection.
 *
 * This function initializes a spincollection to which spinners can be added.
 *
 * \param collection pointer to gfx_mono_spinctrl_spincollection to Initialize
 */
void gfx_mono_spinctrl_spincollection_init(struct
		gfx_mono_spinctrl_spincollection *collection)
{
	collection->active_spinner = false;
	collection->current_selection = 0;
	collection->number_of_spinners = 0;
	collection->init = true;
}

/**
 * \brief Add spinner to spincollection
 *
 * This function adds an initialized spinner to a spincollection and positions
 * it below any other spinners in the spincollection on the screen. The
 * spinners in the spincollection are linked together and number of spinners is
 * updated.
 * It is not possible to add more spinners than
 * \ref GFX_MONO_SPINCTRL_MAX_ELEMENTS_IN_SPINCOLLECTION "maximum number of
 * spinners in a spincollection".
 *
 * \param spinner       pointer to initialized gfx_mono_spinctrl struct to add
 *                      to collection
 * \param spinners      pointer to initialized gfx_mono_spinctrl_spincollection
 *                      struct
 *
 */
void gfx_mono_spinctrl_spincollection_add_spinner(struct
		gfx_mono_spinctrl *spinner,
		struct gfx_mono_spinctrl_spincollection *spinners)
{
	uint8_t i;
	struct gfx_mono_spinctrl *lastspinner;

	/* Do not add more spinner elements than maximum number of spinners */
	if (spinners->number_of_spinners >=
			GFX_MONO_SPINCTRL_MAX_ELEMENTS_IN_SPINCOLLECTION) {
		return;
	}

	/* Place new spinner below previous spinners on screen */
	spinner->y = (SYSFONT_HEIGHT + 1) * spinners->number_of_spinners;

	/* Add pointer to the spinner in spincollection if empty */
	if (spinners->number_of_spinners == 0) {
		spinners->collection = spinner;
	} else {
		lastspinner = spinners->collection;
		for (i = 1; i < spinners->number_of_spinners; i++) {
			lastspinner = lastspinner->next;
		}
		/* Link the new spinner to the current last spinner in the
		 * collection */
		lastspinner->next = spinner;
		/* Link the current last spinner as previous spinner for new
		 * spinner */
		spinner->prev = lastspinner;
	}

	/* Set added spinner as last spinner in collection */
	spinners->collection_last = spinner;
	/* Update number of spinners in collection */
	spinners->number_of_spinners++;
}

/**
 * \brief Show spincollection
 *
 * This function draws all the spinners in a spincollection to the screen,
 * together with an OK button at the bottom. It also draws an indicator arrow
 * in front of the top spinner.
 *
 * \param spinners pointer to initialized spincollection to display
 */
void gfx_mono_spinctrl_spincollection_show(struct
		gfx_mono_spinctrl_spincollection *spinners)
{
	uint8_t i;
	struct gfx_mono_spinctrl *iterator;

	/* Clear screen */
	gfx_mono_draw_filled_rect(0, 0, GFX_MONO_LCD_WIDTH, GFX_MONO_LCD_HEIGHT,
			GFX_PIXEL_CLR);

	/* Make sure there are spinners in the collection */
	if (spinners->number_of_spinners == 0) {
		return;
	}

	/* Draw spinners on screen */
	iterator = spinners->collection;
	for (i = 0; i < spinners->number_of_spinners; i++) {
		gfx_mono_spinctrl_draw(iterator, true);
		iterator = iterator->next;
	}
	/* Draw OK button at bottom of screen */
	gfx_mono_spinctrl_draw_button(true, false);
	/* Draw indicator arrow in front of first spinner */
	gfx_mono_spinctrl_draw_indicator(spinners->collection, true);
}

/**
 * \brief Step up spinner data
 *
 * This function steps up the data, making sure it does not go
 * beyond the upper limit. Wraps around if it does.
 *
 * \param spinner pointer to initialized spinner.
 */
static void gfx_mono_spinctrl_step_up(struct gfx_mono_spinctrl *spinner)
{
	/* Check if spinner type is integer or string, increment integer data or
	 * move to next string index.
	 */
	if (spinner->datatype == SPINTYPE_INTEGER) {
		if (spinner->integer_data < spinner->upper_limit) {
			spinner->integer_data++;
		} else {
			spinner->integer_data = spinner->lower_limit;
		}
	} else if (spinner->datatype == SPINTYPE_STRING) {
		if (spinner->strings.index < spinner->upper_limit) {
			spinner->strings.index++;
		} else {
			(spinner->strings.index) = spinner->lower_limit;
		}
	}
}

/**
 * \brief Step down spinner data
 *
 * * This function steps down the data, making sure it does not go
 * below the lower limit. Wraps around if it does.
 *
 * \param spinner pointer to initialized spinner.
 */
static void gfx_mono_spinctrl_step_down(struct gfx_mono_spinctrl *spinner)
{
	/* Check if spinner type is integer or string, increment integer data,
	 * move to next string index.
	 */
	if (spinner->datatype == SPINTYPE_INTEGER) {
		if (spinner->integer_data > spinner->lower_limit) {
			spinner->integer_data--;
		} else {
			spinner->integer_data = spinner->upper_limit;
		}
	} else if (spinner->datatype == SPINTYPE_STRING) {
		if (spinner->strings.index > (spinner->lower_limit)) {
			spinner->strings.index--;
		} else {
			spinner->strings.index = spinner->upper_limit;
		}
	}
}

/**
 * \brief Update single spinner depending on input.
 *
 * \param spinner pointer to initialized spinner.
 * \param keycode  keycode to process
 *
 * \retval selected            selected spinner value
 * \retval GFX_MONO_SPINCTRL_EVENT_IDLE spinner spinning
 * \retval GFX_MONO_SPINCTRL_EVENT_BACK spinner deselected
 */
int16_t gfx_mono_spinctrl_process_key(struct gfx_mono_spinctrl *spinner,
		uint8_t keycode)
{
	switch (keycode) {
	case GFX_MONO_SPINCTRL_KEYCODE_DOWN:
		if (spinner->in_focus) {
			gfx_mono_spinctrl_step_down(spinner);
			/* Update spinner on display */
			gfx_mono_spinctrl_draw(spinner, false);
		}

		/* Nothing selected yet */
		return GFX_MONO_SPINCTRL_EVENT_IDLE;

	case GFX_MONO_SPINCTRL_KEYCODE_UP:
		if (spinner->in_focus) {
			gfx_mono_spinctrl_step_up(spinner);
			/* Update spinner on display */
			gfx_mono_spinctrl_draw(spinner, false);
		}

		/* Nothing selected yet */
		return GFX_MONO_SPINCTRL_EVENT_IDLE;

	case GFX_MONO_SPINCTRL_KEYCODE_ENTER:
		if (spinner->in_focus) {
			if (spinner->datatype == SPINTYPE_INTEGER) {
				spinner->in_focus = false;
				gfx_mono_spinctrl_draw(spinner, false);
				/* Store saved value in case of aborting spinner
				 * later */
				spinner->last_saved_value
					= spinner->integer_data;
				/* Got what we want. Return selection. */
				return spinner->integer_data;
			} else if (spinner->datatype == SPINTYPE_STRING) {
				spinner->in_focus = false;
				gfx_mono_spinctrl_draw(spinner, false);
				/* Store saved value in case of aborting spinner
				 * later */
				spinner->last_saved_value
					= spinner->strings.index;
				/* Got what we want. Return selection. */
				return spinner->strings.index;
			}
		} else {
			/* Spinner selected */
			spinner->in_focus = true;
			gfx_mono_spinctrl_draw(spinner, false);
			return GFX_MONO_SPINCTRL_EVENT_IDLE;
		}

	case GFX_MONO_SPINCTRL_KEYCODE_BACK:
		/* User pressed "back" key, */
		spinner->in_focus = false;
		/* Spinner choice aborted, show last saved value instead */
		if (spinner->datatype == SPINTYPE_INTEGER) {
			spinner->integer_data = spinner->last_saved_value;
		} else if (spinner->datatype == SPINTYPE_STRING) {
			spinner->strings.index = spinner->last_saved_value;
		}

		gfx_mono_spinctrl_draw(spinner, false);
		return GFX_MONO_SPINCTRL_EVENT_BACK;

	default:
		/* Unknown key event */
		return GFX_MONO_SPINCTRL_EVENT_IDLE;
	}
}

/**
 * \brief Update spincollection on screen depending on input
 *
 * This function returns \ref GFX_MONO_SPINCTRL_EVENT_FINISH if user has
 * pressed the OK button. The spinner choices can then be extracted from the
 * results array. If a spinner is of type SPINTYPE_STRING, the index of the
 * progmem string will be stored in the results array, else the selected
 * integer value will be stored.
 * The choice from the first spinner added to the spincollection will be stored
 * at index 0 in the results array, the second at index 1 and so on.
 * If user has pressed the back button, \ref GFX_MONO_SPINCTRL_EVENT_BACK is
 * returned, signalling that the application should be cancelled.
 *
 * \param spinners       pointer to initialized
 *                       gfx_mono_spinctrl_spincollection
 * \param keycode        keycode to process
 * \param results        array to store results from the spinners, must be of
 *                       same length as number of spinners
 *
 * \retval GFX_MONO_SPINCTRL_EVENT_FINISH  user pressed ok button
 * \retval GFX_MONO_SPINCTRL_EVENT_BACK    user cancelled
 * \retval GFX_MONO_SPINCTRL_EVENT_IDLE    user is navigating in spincollection
 */
int16_t gfx_mono_spinctrl_spincollection_process_key(struct
		gfx_mono_spinctrl_spincollection *spinners, uint8_t keycode,
		int16_t results[])
{
	uint8_t i;
	struct gfx_mono_spinctrl *iterator;

	/* Make sure there are spinners in the collection, if not, cancel */
	if (spinners->number_of_spinners == 0) {
		return GFX_MONO_SPINCTRL_EVENT_BACK;
	}

	/* Store initial values in results array first time function is run */
	if (spinners->init) {
		iterator = spinners->collection;
		for (i = 0; i < spinners->number_of_spinners; i++) {
			if (iterator->datatype == SPINTYPE_INTEGER) {
				results[i] = iterator->integer_data;
			} else {
				results[i] = iterator->strings.index;
			}

			iterator = iterator->next;
		}
		spinners->init = false;
	}

	/* Find current spinner selection */
	iterator = spinners->collection;
	if (spinners->current_selection != GFX_MONO_SPINCTRL_BUTTON) {
		for (i = 0; i < spinners->current_selection; i++) {
			iterator = iterator->next;
		}
	}

	if (spinners->active_spinner) {
		/* Process chosen spinner */
		spinners->selection = gfx_mono_spinctrl_process_key(iterator,
				keycode);
		if (spinners->selection == GFX_MONO_SPINCTRL_EVENT_BACK) {
			/* User has exited spinner without saving the result */
			spinners->active_spinner = false;
		} else if (spinners->selection !=
				GFX_MONO_SPINCTRL_EVENT_IDLE) {
			/* Value selected, store in array */
			results[spinners->current_selection]
				= spinners->selection;
			/* Step out of spinner and into spincollection */
			spinners->active_spinner = false;
		}

		return GFX_MONO_SPINCTRL_EVENT_IDLE;
	} else {
		switch (keycode) {
		case GFX_MONO_SPINCTRL_KEYCODE_DOWN:
			if (spinners->current_selection ==
					GFX_MONO_SPINCTRL_BUTTON) {
				spinners->current_selection = 0;
				/* Delete indicator arrow in front of button */
				gfx_mono_spinctrl_draw_button(true, false);
				/* Draw indicator arrow in front of first
				 * spinner */
				gfx_mono_spinctrl_draw_indicator(iterator,
						true);
			} else if (spinners->current_selection <
					spinners->number_of_spinners - 1) {
				/* Delete indicator arrow */
				gfx_mono_spinctrl_draw_indicator(iterator,
						false);
				spinners->current_selection++;
				/* Draw indicator arrow in front of new spinner */
				gfx_mono_spinctrl_draw_indicator(iterator->next,
						true);
			} else {
				/* Delete indicator arrow */
				gfx_mono_spinctrl_draw_indicator(iterator,
						false);
				spinners->current_selection
					= GFX_MONO_SPINCTRL_BUTTON;
				/* Draw indicator arrow in front of button */
				gfx_mono_spinctrl_draw_button(true, true);
			}

			return GFX_MONO_SPINCTRL_EVENT_IDLE;

		case GFX_MONO_SPINCTRL_KEYCODE_UP:
			if (spinners->current_selection ==
					GFX_MONO_SPINCTRL_BUTTON) {
				/* Delete indicator arrow in front of button */
				gfx_mono_spinctrl_draw_button(true, false);
				spinners->current_selection
					= spinners->number_of_spinners - 1;
				/* Draw indicator arrow in front of new spinner */
				gfx_mono_spinctrl_draw_indicator(
						spinners->collection_last,
						true);
			} else if (spinners->current_selection > 0) {
				/* Delete indicator arrow */
				gfx_mono_spinctrl_draw_indicator(iterator,
						false);
				spinners->current_selection--;
				/* Draw indicator arrow in front of new spinner */
				gfx_mono_spinctrl_draw_indicator(iterator->prev,
						true);
			} else {
				/* Delete indicator arrow */
				gfx_mono_spinctrl_draw_indicator(iterator,
						false);
				spinners->current_selection
					= GFX_MONO_SPINCTRL_BUTTON;
				/* Draw indicator arrow in front of button */
				gfx_mono_spinctrl_draw_button(true, true);
			}

			return GFX_MONO_SPINCTRL_EVENT_IDLE;

		case GFX_MONO_SPINCTRL_KEYCODE_ENTER:
			if (spinners->current_selection ==
					GFX_MONO_SPINCTRL_BUTTON) {
				/* Finished with all selections, return */
				return GFX_MONO_SPINCTRL_EVENT_FINISH;
			} else {
				/* Spinner selected, send next keycode directly
				 * to spinner */
				gfx_mono_spinctrl_process_key(iterator,
						keycode);
				spinners->active_spinner = true;
				return GFX_MONO_SPINCTRL_EVENT_IDLE;
			}

		case GFX_MONO_SPINCTRL_KEYCODE_BACK:
			/* User pressed "back" key, */
			return GFX_MONO_SPINCTRL_EVENT_BACK;

		default:
			/* Unknown key event */
			return GFX_MONO_SPINCTRL_EVENT_IDLE;
		}
	}
}
