Index: apps/action.h =================================================================== --- apps/action.h (revision 15144) +++ apps/action.h (working copy) @@ -111,6 +111,9 @@ ACTION_WPS_ID3SCREEN,/* optional */ ACTION_WPS_CONTEXT, ACTION_WPS_QUICKSCREEN,/* optional */ +#ifdef ACCEPT_DOUBLE_CLICK + ACTION_WPS_SCRUBSEEK,/* optional */ +#endif ACTION_WPS_MENU, /*this should be the same as ACTION_STD_MENU */ ACTION_WPS_REC, #if 0 Index: apps/abrepeat.h =================================================================== --- apps/abrepeat.h (revision 15144) +++ apps/abrepeat.h (working copy) @@ -49,7 +49,7 @@ #ifdef HAVE_LCD_BITMAP #include "screen_access.h" void ab_draw_markers(struct screen * screen, int capacity, - int x0, int x1, int y, int h); + int x0, int x1, int y, int h); #endif /* These functions really need to be inlined for speed */ Index: apps/lang/english.lang =================================================================== --- apps/lang/english.lang (revision 15144) +++ apps/lang/english.lang (working copy) @@ -11028,6 +11028,20 @@ + id: LANG_DOUBLE_CLICK_DELAY + desc: set delay in ms between clicks to detect double click + user: + + *: "Set Double-Click Delay" + + + *: "Set Double-Click Delay" + + + *: "Set Double-Click Delay" + + + id: LANG_EXT_ONLY_VIEW_ALL desc: in settings_menu user: @@ -11342,6 +11356,20 @@ + id: LANG_SCRUB_MODE_TIMEOUT + desc: Menu item to select timeout for Scrub mode (scrollwheel devices) + user: + + *: "Scrub Mode Timeout" + + + *: "Scrub Mode Timeout" + + + *: "" + + + id: LANG_THEME_MENU desc: in the settings menu user: Index: apps/gui/gwps-common.c =================================================================== --- apps/gui/gwps-common.c (revision 15144) +++ apps/gui/gwps-common.c (working copy) @@ -142,6 +142,228 @@ return false; } +bool scrub_ffwd_rew(int button) +{ + static const uint16_t ff_rew_steps[] = { + 1000, 2000, 3000, 4000, + 5000, 6000, 8000, 10000, + 15000, 20000, 25000, 30000, + 45000, 60000 + }; + + unsigned int step = 0; /* current ff/rewind step */ + unsigned int max_step = 0; /* maximum ff/rewind step */ + int ff_rewind_count = 0; /* current ff/rewind count (in ticks) */ + int direction = -1; /* forward=1 or backward=-1 */ + long accel_tick = 0; /* next time at which to bump the step size */ + bool exit = false; + bool usb = false; + bool update=false; /* update audio */ + bool timeout=false; /* time out scrub mode after preset seconds */ + long cur_pos=0; /* used to get round unsigned/signed comparison */ + long old_pos=0; /* used to calculate timeout */ + int wait_time=0; /* wait time before updating audio - makes for smooth scrubbing */ + int i = 0; + + if (button == ACTION_WPS_BROWSE) /* exit - should never happen */ + { + status_set_ffmode(0); + return usb; + } + + wps_state.scrubbed=false; /* we have not scrubbed yet */ + +if ( (audio_status() & AUDIO_STATUS_PLAY) && + wps_state.id3 && wps_state.id3->length ) + { + if ((!wps_state.paused) && (global_settings.scrub_mode_timeout==0)) /* pause audio if timeout is 0 */ +#if (CONFIG_CODEC == SWCODEC) + audio_pre_ff_rewind(); +#else + audio_pause(); +#endif +#if CONFIG_KEYPAD == PLAYER_PAD +if (global_settings.scrub_mode_timeout==0) + FOR_NB_SCREENS(i) + gui_wps[i].display->stop_scroll(); +#endif + + wps_state.ff_rewind = true; /* set scrub mode for progress bar */ + + step = ff_rew_steps[global_settings.ff_rewind_min_step]; + + accel_tick = current_tick + + global_settings.ff_rewind_accel*HZ; + } + old_pos=wps_state.id3->elapsed; /* get old position for time out (should be redundant actually) */ + + while (!exit) /* main loop - read button, take action*/ + { + switch ( button ) + { + case ACTION_WPS_VOLUP: + direction = 1; + case ACTION_WPS_VOLDOWN: + if (wps_state.ff_rewind) /* enabled earlier */ + { + if (direction == 1) + { + /* fast forwarding, calc max step relative to end */ + max_step = (wps_state.id3->length - + (wps_state.id3->elapsed + + ff_rewind_count)) * + FF_REWIND_MAX_PERCENT / 100; + status_set_ffmode(STATUS_FASTFORWARD); + } + else + { + /* rewinding, calc max step relative to start */ + max_step = (wps_state.id3->elapsed + ff_rewind_count) * + FF_REWIND_MAX_PERCENT / 100; + status_set_ffmode(STATUS_FASTBACKWARD); + } + + max_step = MAX(max_step, MIN_FF_REWIND_STEP); + + if (step > max_step) + step = max_step; + + ff_rewind_count += step * direction; + + if (global_settings.ff_rewind_accel != 0 && + current_tick >= accel_tick) + { + step *= 2; + accel_tick = current_tick + + global_settings.ff_rewind_accel*HZ; + } + } + if (direction > 0) { + if ((wps_state.id3->elapsed + ff_rewind_count) > + wps_state.id3->length) + ff_rewind_count = wps_state.id3->length - + wps_state.id3->elapsed; + } + else { + if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0) + ff_rewind_count = -wps_state.id3->elapsed; + + } + wps_state.scrubbed=true; /* now we have scrubbed used later to clean up wps */ + FOR_NB_SCREENS(i) + gui_wps_refresh(&gui_wps[i], + (wps_state.wps_time_countup == false)? + ff_rewind_count:-ff_rewind_count, + WPS_REFRESH_PLAYER_PROGRESS | + WPS_REFRESH_DYNAMIC); + + direction = -1; + update = true; + wait_time=0; + if ((wps_state.id3->length - wps_state.id3->elapsed) < 2000) /* few seconds to go before we run out of current song */ +#if (CONFIG_CODEC == SWCODEC) + audio_pre_ff_rewind(); /* so pause playback while we are still scrubbing*/ +#else + audio_pause(); +#endif + break; + + case ACTION_WPS_BROWSE: /* Exit on Select */ + case ACTION_WPS_PLAY: /* Exit on Play */ + if ((global_settings.scrub_mode_timeout==0) || (update)) + /* update playback if we are scrubbing while paused, or update required */ + { + wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count; + audio_ff_rewind(wps_state.id3->elapsed); + } + ff_rewind_count = 0; + wps_state.ff_rewind = false; + status_set_ffmode(0); +#if (CONFIG_CODEC != SWCODEC) + if (!wps_state.paused) + audio_resume(); +#endif +#ifdef HAVE_LCD_CHARCELLS + //gui_wps_display(); +#endif + exit = true; + break; + + case ACTION_NONE: /* No Button Pressed */ + if (global_settings.scrub_mode_timeout==0) + { + status_set_ffmode(STATUS_PAUSE); /* display paused when not ff or rew */ + FOR_NB_SCREENS(i) + gui_wps_refresh(&gui_wps[i], (wps_state.wps_time_countup == false)? + ff_rewind_count:-ff_rewind_count, + WPS_REFRESH_PLAYER_PROGRESS | WPS_REFRESH_DYNAMIC); + } + else + { + if ((update) && (wait_time > 3)) /* wait time of 3 works OK, should this be a variable? */ + { + wait_time =0; +#if (CONFIG_CODEC == SWCODEC) + audio_pre_ff_rewind(); /* pause audio so that we can update cleanly */ +#else + audio_pause(); +#endif + if (wps_state.id3->elapsed+ff_rewind_count < wps_state.id3->length) + wps_state.id3->elapsed = wps_state.id3->elapsed+ff_rewind_count; /* update position if not beyond end of file */ + else + { + wps_state.id3->elapsed = wps_state.id3->length; + timeout=true; /* scrubbed to end of song */ + } + audio_ff_rewind(wps_state.id3->elapsed); /* update file with new position */ + old_pos=wps_state.id3->elapsed; /*update old_pos for time out calculation */ + ff_rewind_count = 0; /*reset scrub position now that we have updated */ +#if (CONFIG_CODEC != SWCODEC) + if (!wps_state.paused) + audio_resume(); +#endif + update=false; /* only update once */ + } + else + { + if (!update) /* we havent scrubbed yet, so just show progress as normal */ + { + FOR_NB_SCREENS(i) + gui_wps_refresh(&gui_wps[i], + 0, + WPS_REFRESH_PLAYER_PROGRESS | + WPS_REFRESH_DYNAMIC); + } + else /* we have scrubbed so increment wait_time */ + { + wait_time++; + DEBUGF("wait time is %d\n", wait_time); + old_pos=wps_state.id3->elapsed; /*update old_pos for time out calculation */ + } + if ((wps_state.id3->length - wps_state.id3->elapsed) < 2250) /* .25 seconds to go before we run out of current song */ + timeout=true; + cur_pos=wps_state.id3->elapsed; + if ((short)(cur_pos - old_pos)/1000 > global_settings.scrub_mode_timeout) /* time out calculation */ + timeout=true; + } + } + break; + + default: /* loop unless USB is connected */ + if(default_event_handler(button) == SYS_USB_CONNECTED) { + status_set_ffmode(0); + usb = true; + exit = true; + } + } + if ((!exit) && (timeout==false)) /* if we are not exiting and timeout has not elapsed, read button */ + button = get_action(CONTEXT_WPS|ALLOW_SOFTLOCK,HZ/5); + else + button = ACTION_WPS_PLAY; /* otherwise we are exiting, so go back to play as normal */ + } + return usb; +} + bool ffwd_rew(int button) { static const uint16_t ff_rew_steps[] = { @@ -245,7 +467,7 @@ if ((int)(wps_state.id3->elapsed + ff_rewind_count) < 0) ff_rewind_count = -wps_state.id3->elapsed; } - + wps_state.scrubbed=true; /* now we have scrubbed used later to clean up wps */ FOR_NB_SCREENS(i) gui_wps_refresh(&gui_wps[i], (wps_state.wps_time_countup == false)? @@ -448,13 +670,98 @@ } #ifdef HAVE_LCD_BITMAP +static inline void scrub_draw_marker(struct screen * screen, int mark, int capacity, + int offset, int size, int y, int h) +{ + int pos; + int pos2; + + int w = size - offset; + pos = offset + ( (w * mark) / capacity ); + pos2=pos; + + /* draw lines in decreasing size until a height of zero is reached */ + screen->set_drawmode(DRMODE_SOLID); + while( h > 0 ) + { + screen->set_drawmode(DRMODE_COMPLEMENT); + screen->vline(pos, y, y+h-1); /* draw diamond right */ + screen->vline(pos2, y, y+h-1);/* draw diamond left */ + h -= 2; + y++; + pos++; + if (pos >= size-1) + pos=size-1; + pos2--; + if (pos2 <= offset) + pos2=offset; + } +} +static inline void gui_bitmap_scrub_cursor_draw(struct screen * screen, struct bitmap bm, int capacity, + int offset, int size, int x, int y, int height) +{ + int pos; + int y2; + int pos2; + int w = size - offset; + pos = offset + ( (w * x) / capacity ); + + pos2=pos - bm.width/2; + y2=y - (bm.height/2) + (height/2); /* scrub point is the centre of the bitmap */ + + screen->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID); + screen->fillrect(offset, y2, size, y-y2); /* clear rectangle above bm position - length of progressbar */ + screen->fillrect(offset, y+height, size, bm.height-((y-y2)+height)); /* clear rectangle below bm position - length of progressbar */ + screen->set_drawmode(DRMODE_SOLID|DRMODE_COMPLEMENT); + +#if LCD_DEPTH > 1 + if(bm.format == FORMAT_MONO) { +#endif + screen->mono_bitmap(bm.data, pos2, + y2, bm.width, + bm.height); +#if LCD_DEPTH > 1 + } else { + screen->transparent_bitmap((fb_data *)bm.data, + pos2, + y2, bm.width, + bm.height); + } +#endif + screen->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID); + if (pos2<=offset) /* if cursor is being drawn at the front of the progressbar */ + screen->fillrect(pos2, y2, offset-pos2, bm.height); /* clear rectangle in front of progressbar */ + if (pos2+bm.width>=size) /* if cursor is being drawn at the end of the progressbar */ + screen->fillrect(size, y2,(pos2+bm.width) - (size), bm.height); /* clear rectangle at the end of progressbar */ +} + +static inline void gui_bitmap_scrub_cursor_clear(struct screen * screen, struct bitmap bm, int capacity, + int offset, int size, int x, int y, int height) +{ /* clear area above and below progress bar (gets rid of remnants of scrub cursor) */ + int pos; + int y2; + int pos2; + int w = size - offset; + pos = offset + ( (w * x) / capacity ); + + pos2=pos - bm.width/2; + y2=y - (bm.height/2) + (height/2); /* scrub point is the centre of the bitmap */ + + DEBUGF("clear rectangle\n"); + screen->set_drawmode(DRMODE_SOLID|DRMODE_INVERSEVID); + screen->fillrect(pos2-bm.width/2, y2, bm.width*2, y-y2); /* clear rectangle above bm position - width of bm times 2 */ + screen->fillrect(pos2-bm.width/2, y+height, bm.width*2, bm.height-((y-y2)+height)); /* clear rectangle below bm position - width of bm times 2 */ + +} + static void draw_progressbar(struct gui_wps *gwps, int line) { struct wps_data *data = gwps->data; struct screen *display = gwps->display; struct wps_state *state = gwps->state; int h = font_get(FONT_UI)->height; + int ff_rewind_count; int sb_y; if (data->progress_top < 0) @@ -466,17 +773,21 @@ if (!data->progress_end) data->progress_end=display->width; - - if (gwps->data->progressbar.have_bitmap_pb) + + ff_rewind_count=state->ff_rewind_count; + if ((wps_state.ff_rewind) && (global_settings.scrub_mode_timeout!=0)) /* for scrubbing */ + state->ff_rewind_count=0; + + if (gwps->data->progressbar.have_bitmap_pb) gui_bitmap_scrollbar_draw(display, data->progressbar.bm, - data->progress_start, sb_y, - data->progress_end-data->progress_start, - data->progressbar.bm.height, - state->id3->length ? state->id3->length : 1, 0, - state->id3->length ? state->id3->elapsed - + state->ff_rewind_count : 0, - HORIZONTAL); - else + data->progress_start, sb_y, + data->progress_end-data->progress_start, + data->progressbar.bm.height, + state->id3->length ? state->id3->length : 1, 0, + state->id3->length ? state->id3->elapsed + + state->ff_rewind_count : 0, + HORIZONTAL); + else gui_scrollbar_draw(display, data->progress_start, sb_y, data->progress_end-data->progress_start, data->progress_height, @@ -485,6 +796,66 @@ + state->ff_rewind_count : 0, HORIZONTAL); +if (data->scrub_cursor.have_bitmap_pb) + { + if ((wps_state.ff_rewind) && (global_settings.scrub_mode_timeout!=0)) /* if scrubbing draw marker */ + gui_bitmap_scrub_cursor_draw(display, data->scrub_cursor.bm, + state->id3->length, data->progress_start, + data->progress_end, + state->id3->elapsed+ff_rewind_count, sb_y, + data->progressbar.bm.height); + } +else + { + if ((wps_state.ff_rewind) && (global_settings.scrub_mode_timeout!=0)) /* if scrubbing draw marker */ + { + if (data->progressbar.have_bitmap_pb) + { + scrub_draw_marker(display, + state->id3->elapsed+ff_rewind_count, + state->id3->length, data->progress_start, + data->progress_end , sb_y, + data->progressbar.bm.height); + } + else + {DEBUGF("Draw marker\n"); + scrub_draw_marker(display, + state->id3->elapsed+ff_rewind_count, + state->id3->length, data->progress_start, + data->progress_end , sb_y, + data->progress_height); + } + } + } +if ((!wps_state.ff_rewind) && (wps_state.scrubbed) && (global_settings.scrub_mode_timeout!=0) && (data->scrub_cursor.have_bitmap_pb)) /* after scrubbing with user defined cursor bmp */ + { + if (data->progressbar.have_bitmap_pb) + { + if (data->scrub_cursor.bm.height > data->progressbar.bm.height) /* if the cursor is larger than the progressbar */ + { /* clear rectangle above and below progressbar */ + gui_bitmap_scrub_cursor_clear(display, data->scrub_cursor.bm, + state->id3->length, data->progress_start, + data->progress_end, + state->id3->elapsed+ff_rewind_count, sb_y, + data->progressbar.bm.height); + } + } + else + { + if (data->scrub_cursor.bm.height > data->progress_height) /* if the cursor is larger than the progressbar */ + { /* clear rectangle above and below progressbar */ + gui_bitmap_scrub_cursor_clear(display, data->scrub_cursor.bm, + state->id3->length, data->progress_start, + data->progress_end, + state->id3->elapsed+ff_rewind_count, sb_y, + data->progress_height); + + } + } + wps_state.scrubbed=false; + } + + #ifdef AB_REPEAT_ENABLE if ( ab_repeat_mode_enabled() && state->id3->length != 0 ) ab_draw_markers(display, state->id3->length, @@ -1073,6 +1444,8 @@ mode = 4; if (status_get_ffmode() == STATUS_FASTBACKWARD) mode = 5; + if (status_get_ffmode() == STATUS_PAUSE) + mode = 3; if (intval) { *intval = mode; Index: apps/gui/gwps-common.h =================================================================== --- apps/gui/gwps-common.h (revision 15144) +++ apps/gui/gwps-common.h (working copy) @@ -27,6 +27,7 @@ bool gui_wps_display(void); bool update_onvol_change(struct gui_wps * gwps); bool update(struct gui_wps *gwps); +bool scrub_ffwd_rew(int button); bool ffwd_rew(int button); void display_keylock_text(bool locked); Index: apps/gui/gwps.c =================================================================== --- apps/gui/gwps.c (revision 15144) +++ apps/gui/gwps.c (working copy) @@ -502,7 +502,15 @@ return GO_TO_ROOT; break; +#ifdef ACCEPT_DOUBLE_CLICK + case ACTION_WPS_SCRUBSEEK: + if (global_settings.party_mode) /* don't do anything in party mode */ + break; + scrub_ffwd_rew(ACTION_NONE); /* go into scrub mode, but don't seek unless vol up or down */ + break; +#endif + #ifdef HAVE_QUICKSCREEN case ACTION_WPS_QUICKSCREEN: #if LCD_DEPTH > 1 Index: apps/gui/gwps.h =================================================================== --- apps/gui/gwps.h (revision 15144) +++ apps/gui/gwps.h (working copy) @@ -182,6 +182,7 @@ /* Image */ WPS_TOKEN_IMAGE_BACKDROP, WPS_TOKEN_IMAGE_PROGRESS_BAR, + WPS_TOKEN_IMAGE_SCRUB_CURSOR, WPS_TOKEN_IMAGE_PRELOAD, WPS_TOKEN_IMAGE_PRELOAD_DISPLAY, WPS_TOKEN_IMAGE_DISPLAY, @@ -298,6 +299,7 @@ #ifdef HAVE_LCD_BITMAP struct gui_img img[MAX_IMAGES]; struct prog_img progressbar; + struct prog_img scrub_cursor; unsigned char img_buf[IMG_BUFSIZE]; unsigned char* img_buf_ptr; int img_buf_free; @@ -376,6 +378,7 @@ struct wps_state { bool ff_rewind; + bool scrubbed; bool paused; int ff_rewind_count; bool wps_time_countup; Index: apps/gui/wps_parser.c =================================================================== --- apps/gui/wps_parser.c (revision 15144) +++ apps/gui/wps_parser.c (working copy) @@ -66,13 +66,14 @@ #ifdef HAVE_LCD_BITMAP #if LCD_DEPTH > 1 -#define MAX_BITMAPS MAX_IMAGES+2 /* WPS images + pbar bitmap + backdrop */ +#define MAX_BITMAPS MAX_IMAGES+3 /* WPS images + pbar bitmap + backdrop */ #else -#define MAX_BITMAPS MAX_IMAGES+1 /* WPS images + pbar bitmap */ +#define MAX_BITMAPS MAX_IMAGES+2 /* WPS images + pbar bitmap */ #endif #define PROGRESSBAR_BMP MAX_IMAGES -#define BACKDROP_BMP MAX_IMAGES+1 +#define BACKDROP_BMP MAX_IMAGES+2 +#define SCRUB_CURSOR_BMP MAX_IMAGES+1 /* pointers to the bitmap filenames in the WPS source */ static const char *bmp_names[MAX_BITMAPS]; @@ -283,6 +284,7 @@ { WPS_TOKEN_IMAGE_DISPLAY, "x", 0, parse_image_load }, { WPS_TOKEN_IMAGE_PROGRESS_BAR, "P", 0, parse_image_special }, + { WPS_TOKEN_IMAGE_SCRUB_CURSOR, "S", 0, parse_image_special }, #if (LCD_DEPTH > 1) || (defined(HAVE_LCD_REMOTE) && (LCD_REMOTE_DEPTH > 1)) { WPS_TOKEN_IMAGE_BACKDROP, "X", 0, parse_image_special }, #endif @@ -496,6 +498,12 @@ bmp_names[BACKDROP_BMP] = wps_bufptr + 1; } #endif + else if (token->type == WPS_TOKEN_IMAGE_SCRUB_CURSOR) + { + /* format: %S|filename.bmp| */ + DEBUGF("Found scrub cursor token\n"); + bmp_names[SCRUB_CURSOR_BMP] = wps_bufptr + 1; + } (void)wps_data; /* to avoid a warning */ @@ -885,6 +893,7 @@ data->img[i].always_display = false; } data->progressbar.have_bitmap_pb = false; + data->scrub_cursor.have_bitmap_pb = false; } #endif @@ -946,15 +955,25 @@ { get_image_filename(bmp_names[n], bmpdir, img_path, sizeof(img_path)); - - if (n == PROGRESSBAR_BMP) { + switch (n) + { + case PROGRESSBAR_BMP: /* progressbar bitmap */ bitmap = &wps_data->progressbar.bm; - loaded = &wps_data->progressbar.have_bitmap_pb; - } else { + loaded = &wps_data->progressbar.have_bitmap_pb; + break; + + case SCRUB_CURSOR_BMP: + /* scrub cursor bitmap */ + bitmap = &wps_data->scrub_cursor.bm; + loaded = &wps_data->scrub_cursor.have_bitmap_pb; + break; + + default: /* regular bitmap */ bitmap = &wps_data->img[n].bm; loaded = &wps_data->img[n].loaded; + break; } /* load the image */ Index: apps/settings.h =================================================================== --- apps/settings.h (revision 15144) +++ apps/settings.h (working copy) @@ -749,6 +749,15 @@ int list_accel_start_delay; /* ms before we start increaseing step size */ int list_accel_wait; /* ms between increases */ #endif + + int scrub_mode_timeout; /* time to wait before exiting scrub mode + 0 = never + number = time to wait in seconds */ + +#ifdef ACCEPT_DOUBLE_CLICK + int btn_double_click_delay; +#endif + #ifdef HAVE_USBSTACK int usb_stack_mode; /* device or host */ unsigned char usb_stack_device_driver[32]; /* usb device driver to load */ Index: apps/menus/settings_menu.c =================================================================== --- apps/menus/settings_menu.c (revision 15144) +++ apps/menus/settings_menu.c (working copy) @@ -344,6 +344,10 @@ MENUITEM_SETTING(buttonlight_brightness, &global_settings.buttonlight_brightness, NULL); #endif +#ifdef ACCEPT_DOUBLE_CLICK +MENUITEM_SETTING(btn_double_click_delay, &global_settings.btn_double_click_delay, NULL); +#endif + MAKE_MENU(system_menu, ID2P(LANG_SYSTEM), 0, Icon_System_menu, &start_screen, @@ -374,8 +378,11 @@ &buttonlight_timeout, #endif #ifdef HAVE_BUTTONLIGHT_BRIGHTNESS - &buttonlight_brightness + &buttonlight_brightness, #endif +#ifdef ACCEPT_DOUBLE_CLICK + &btn_double_click_delay +#endif ); /* SYSTEM MENU */ Index: apps/menus/playback_menu.c =================================================================== --- apps/menus/playback_menu.c (revision 15144) +++ apps/menus/playback_menu.c (working copy) @@ -60,8 +60,9 @@ MENUITEM_SETTING(ff_rewind_accel, &global_settings.ff_rewind_accel, NULL); MENUITEM_SETTING(ff_rewind_min_step, &global_settings.ff_rewind_min_step, NULL); +MENUITEM_SETTING(scrub_mode_timeout, &global_settings.scrub_mode_timeout, NULL); MAKE_MENU(ff_rewind_settings_menu, ID2P(LANG_WIND_MENU), 0, Icon_NOICON, - &ff_rewind_min_step, &ff_rewind_accel); + &ff_rewind_min_step, &ff_rewind_accel, &scrub_mode_timeout); #ifndef HAVE_FLASH_STORAGE #if CONFIG_CODEC == SWCODEC int buffermargin_callback(int action,const struct menu_item_ex *this_item) Index: apps/settings_list.c =================================================================== --- apps/settings_list.c (revision 15144) +++ apps/settings_list.c (working copy) @@ -258,6 +258,24 @@ snprintf(buffer, buffer_size, "2x/%ds", val); } +static const int scrub_mode_timeout_vals[] = {0,4,5,6,7,8,9,10,15,20,30}; +static long scrub_mode_timeout_getlang(int value) +{ + if (value == 0) + return LANG_OFF; + return TALK_ID(scrub_mode_timeout_vals[value], UNIT_SEC); +} +static void scrub_mode_timeout_formatter(char *buffer, size_t buffer_size, + int val, const char *unit) +{ + (void)unit; + if (val == 0) + strcpy(buffer, str(LANG_OFF)); + else + snprintf(buffer, buffer_size, "%ds", scrub_mode_timeout_vals[val]); +} + + static const unsigned char poweroff_idle_timer_times[] = {0,1,2,3,4,5,6,7,8,9,10,15,30,45,60}; static long poweroff_idle_timer_getlang(int value) { @@ -642,6 +660,10 @@ ff_rewind_min_step_getlang, NULL), INT_SETTING(0, ff_rewind_accel, LANG_FFRW_ACCEL, 3, "scan accel", UNIT_SEC, 16, 0, -1, scanaccel_formatter, scanaccel_getlang, NULL), + INT_SETTING(0, scrub_mode_timeout, LANG_SCRUB_MODE_TIMEOUT, + 0, "scrub mode timeout", UNIT_SEC, 0, 10, 1, + scrub_mode_timeout_formatter, scrub_mode_timeout_getlang, NULL), + #if (CONFIG_CODEC == SWCODEC) && !defined(HAVE_FLASH_STORAGE) STRINGCHOICE_SETTING(0, buffer_margin, LANG_MP3BUFFER_MARGIN, 0,"antiskip", "5s,15s,30s,1min,2min,3min,5min,10min",NULL, 8, @@ -1265,6 +1287,13 @@ 3, "list_accel_wait", UNIT_SEC, 1, 10, 1, scanaccel_formatter, scanaccel_getlang, NULL), #endif /* HAVE_SCROLLWHEEL */ + +#ifdef ACCEPT_DOUBLE_CLICK + INT_SETTING(0, btn_double_click_delay, LANG_DOUBLE_CLICK_DELAY, DC_DELAY_DEF, + "dbl click delay", UNIT_MS, DC_DELAY_MIN, DC_DELAY_MAX, 20, + NULL, NULL, btn_set_double_click_delay), +#endif + #ifdef HAVE_USBSTACK CHOICE_SETTING(0, usb_stack_mode, LANG_USBSTACK_MODE, 0, "usb mode", "device,host", Index: apps/keymaps/keymap-ipod.c =================================================================== --- apps/keymaps/keymap-ipod.c (revision 15144) +++ apps/keymaps/keymap-ipod.c (working copy) @@ -75,6 +75,9 @@ }; static const struct button_mapping button_context_wps[] = { +#ifdef ACCEPT_DOUBLE_CLICK + { ACTION_WPS_SCRUBSEEK, BUTTON_SELECT|BUTTON_REL, BUTTON_SELECT|BUTTON_DBL }, +#endif { ACTION_WPS_PLAY, BUTTON_PLAY|BUTTON_REL, BUTTON_PLAY }, { ACTION_WPS_STOP, BUTTON_PLAY|BUTTON_REPEAT, BUTTON_PLAY }, { ACTION_WPS_SKIPPREV, BUTTON_LEFT|BUTTON_REL, BUTTON_LEFT }, Index: firmware/export/button.h =================================================================== --- firmware/export/button.h (revision 15144) +++ firmware/export/button.h (working copy) @@ -61,5 +61,17 @@ #define BUTTON_REL 0x02000000 #define BUTTON_REPEAT 0x04000000 #define BUTTON_TOUCHPAD 0x08000000 +#ifdef ACCEPT_DOUBLE_CLICK +#define BUTTON_DBL 0x08000000 +#endif +#ifdef ACCEPT_DOUBLE_CLICK +#define DC_DELAY_MIN 100 +#define DC_DELAY_MAX 500 +#define DC_DELAY_DEF 300 +#define DC_DELAY_START 30 + +void btn_set_double_click_delay(int delay); +#endif + #endif /* _BUTTON_H_ */ Index: firmware/export/config-ipodvideo.h =================================================================== --- firmware/export/config-ipodvideo.h (revision 15144) +++ firmware/export/config-ipodvideo.h (working copy) @@ -159,4 +159,7 @@ #define ICODE_ATTR_TREMOR_NOT_MDCT +/* define this to turn on double click captures */ +#define ACCEPT_DOUBLE_CLICK + #endif Index: firmware/export/config-ipod3g.h =================================================================== --- firmware/export/config-ipod3g.h (revision 15144) +++ firmware/export/config-ipod3g.h (working copy) @@ -133,4 +133,7 @@ #define ICODE_ATTR_TREMOR_NOT_MDCT +/* define this to turn on double click captures */ +#define ACCEPT_DOUBLE_CLICK + #endif /* SIMULATOR */ Index: firmware/export/config-ipodcolor.h =================================================================== --- firmware/export/config-ipodcolor.h (revision 15144) +++ firmware/export/config-ipodcolor.h (working copy) @@ -141,4 +141,7 @@ #define ICODE_ATTR_TREMOR_NOT_MDCT +/* define this to turn on double click captures */ +#define ACCEPT_DOUBLE_CLICK + #endif Index: firmware/export/config-ipodmini.h =================================================================== --- firmware/export/config-ipodmini.h (revision 15144) +++ firmware/export/config-ipodmini.h (working copy) @@ -146,4 +146,7 @@ #define ICODE_ATTR_TREMOR_NOT_MDCT +/* define this to turn on double click captures */ +#define ACCEPT_DOUBLE_CLICK + #endif Index: firmware/export/config-ipod1g2g.h =================================================================== --- firmware/export/config-ipod1g2g.h (revision 15144) +++ firmware/export/config-ipod1g2g.h (working copy) @@ -130,4 +130,7 @@ #define ICODE_ATTR_TREMOR_NOT_MDCT +/* define this to turn on double click captures */ +#define ACCEPT_DOUBLE_CLICK + #endif /* SIMULATOR */ Index: firmware/export/config-ipodmini2g.h =================================================================== --- firmware/export/config-ipodmini2g.h (revision 15144) +++ firmware/export/config-ipodmini2g.h (working copy) @@ -149,4 +149,7 @@ #define ICODE_ATTR_TREMOR_NOT_MDCT +/* define this to turn on double click captures */ +#define ACCEPT_DOUBLE_CLICK + #endif Index: firmware/export/config-ipodnano.h =================================================================== --- firmware/export/config-ipodnano.h (revision 15144) +++ firmware/export/config-ipodnano.h (working copy) @@ -147,4 +147,7 @@ #define ICODE_ATTR_TREMOR_NOT_MDCT +/* define this to turn on double click captures */ +#define ACCEPT_DOUBLE_CLICK + #endif Index: firmware/export/config-ipod4g.h =================================================================== --- firmware/export/config-ipod4g.h (revision 15144) +++ firmware/export/config-ipod4g.h (working copy) @@ -152,4 +152,7 @@ #define ICODE_ATTR_TREMOR_NOT_MDCT +/* define this to turn on double click captures */ +#define ACCEPT_DOUBLE_CLICK + #endif Index: firmware/drivers/button.c =================================================================== --- firmware/drivers/button.c (revision 15144) +++ firmware/drivers/button.c (working copy) @@ -64,6 +64,12 @@ bool phones_present = false; #endif +#ifdef ACCEPT_DOUBLE_CLICK +static int double_click_interval; +static int double_click_start; +static int double_click_count; +#endif + /* how long until repeat kicks in, in ticks */ #define REPEAT_START 30 @@ -82,6 +88,9 @@ static int repeat_count = 0; static bool repeat = false; static bool post = false; +#ifdef ACCEPT_DOUBLE_CLICK + static int lastbtn_pushed = 0; +#endif #ifdef HAVE_BACKLIGHT static bool skip_release = false; #ifdef HAVE_REMOTE_LCD @@ -90,7 +99,7 @@ #endif int diff; int btn; - + #ifdef HAS_SERIAL_REMOTE /* Post events for the remote control */ btn = remote_control_rx(); @@ -133,23 +142,66 @@ else #endif if(!skip_release) +#ifdef ACCEPT_DOUBLE_CLICK + { + /* if double_click is < start then a double click was + * detected and queued, now we must queue the release, and dbl + */ + if (lastbtn_pushed > 0 && double_click_count < double_click_start) + { + queue_post(&button_queue, BUTTON_DBL | lastbtn_pushed, 0); + queue_post(&button_queue, BUTTON_REL | lastbtn_pushed, 0); + double_click_count = double_click_interval; + lastbtn_pushed = 0; + } + } +#else /* !ACCEPT_DOUBLE_CLICK */ queue_post(&button_queue, BUTTON_REL | diff, 0); +#endif else skip_release = false; -#else +#else /* !HAVE_REMOTE_LCD */ +#ifdef ACCEPT_DOUBLE_CLICK + /* see above section comment */ + if (lastbtn_pushed > 0 && double_click_count < double_click_start) + { + queue_post(&button_queue, BUTTON_DBL | lastbtn_pushed, 0); + queue_post(&button_queue, BUTTON_REL | lastbtn_pushed, 0); + double_click_count = double_click_interval; + lastbtn_pushed = 0; + } +#else /* !ACCEPT_DOUBLE_CLICK */ queue_post(&button_queue, BUTTON_REL | diff, 0); #endif +#endif } else { if ( btn ) { +#ifdef ACCEPT_DOUBLE_CLICK + /* check for double click */ + /* double clicked if lastbtn == 0, and we've waited long enough */ + if (lastbtn == 0 && btn == lastbtn_pushed && double_click_count < double_click_start) + { + /* double clicked now wait for button to be released + * cannot post yet, as the second click could be a hold */ + post = false; + double_click_count = 0; + } + else +#endif /* normal keypress */ if ( btn != lastbtn ) { post = true; repeat = false; repeat_speed = REPEAT_INTERVAL_START; +#ifdef ACCEPT_DOUBLE_CLICK + /* diff btn - reset double click, no post */ + post = false; + double_click_count = double_click_interval; +#endif } else /* repeat? */ { @@ -201,6 +253,18 @@ repeat_count = 0; /* initial repeat */ count = REPEAT_INTERVAL_START; +#ifdef ACCEPT_DOUBLE_CLICK + /* post original button press and release */ + if (double_click_count == 0) + { + queue_post(&button_queue, btn, 0); + queue_post(&button_queue, BUTTON_REL | btn, 0); + } + /* post current button press */ + queue_post(&button_queue, btn, 0); + /* reset double click detection */ + double_click_count = double_click_interval; +#endif } } } @@ -243,7 +307,9 @@ || (btn&BUTTON_REMOTE) #endif ) + { queue_post(&button_queue, btn, 0); + } else skip_release = true; #else /* no backlight, nothing to skip */ @@ -268,11 +334,35 @@ } else { +#ifdef ACCEPT_DOUBLE_CLICK + /* if we've waited longer than double_click_interval + * then it isn't a double click, so queue the button + * we also know it isn't a double click if repeat is true + */ + if (repeat || (lastbtn_pushed && --double_click_count <= 0)) + { + /* queue both the original press if not repeating, + * and the release + */ + if (!repeat) + queue_post(&button_queue, lastbtn_pushed, 0); + queue_post(&button_queue, BUTTON_REL | lastbtn_pushed, 0); + double_click_count = double_click_interval; + lastbtn_pushed = 0; + } +#endif repeat = false; count = 0; } } + +#ifdef ACCEPT_DOUBLE_CLICK + lastbtn = btn & ~(BUTTON_REL | BUTTON_REPEAT | BUTTON_DBL); + if (lastbtn) + lastbtn_pushed = lastbtn; +#else lastbtn = btn & ~(BUTTON_REL | BUTTON_REPEAT); +#endif } #ifdef HAVE_ADJUSTABLE_CPU_FREQ @@ -375,6 +465,10 @@ remote_filter_first_keypress = false; #endif #endif + +#ifdef ACCEPT_DOUBLE_CLICK + btn_set_double_click_delay(DC_DELAY_DEF); +#endif } #ifdef HAVE_LCD_BITMAP /* only bitmap displays can be flipped */ @@ -520,3 +614,17 @@ return delta; } #endif /* HAVE_SCROLLWHEEL */ + +#ifdef ACCEPT_DOUBLE_CLICK +void btn_set_double_click_delay(int delay) +{ + if (delay < DC_DELAY_MIN || + delay > DC_DELAY_MAX) + delay = DC_DELAY_DEF; + + double_click_interval = delay / 10; // convert to ticks + double_click_start = (delay - DC_DELAY_START) / 10; // conver to ticks + double_click_count = double_click_interval; +} +#endif +