diff --git a/apps/bookmark.c b/apps/bookmark.c
index f00ace7c41..f4bbd1c7df 100644
--- a/apps/bookmark.c
+++ b/apps/bookmark.c
@@ -988,7 +988,7 @@ static int select_bookmark(const char* bookmark_file_name,
= (strcmp(bookmark_file_name, RECENT_BOOKMARK_FILE) == 0);
gui_synclist_init(&list, &get_bookmark_info,
- (void*) bookmarks, false, 2, NULL);
+ (void*) bookmarks, false, 2, NULL, false);
if(global_settings.talk_menu)
gui_synclist_set_voice_callback(&list, bookmark_list_voice_cb);
diff --git a/apps/cuesheet.c b/apps/cuesheet.c
index c5a1aacad9..48c340739a 100644
--- a/apps/cuesheet.c
+++ b/apps/cuesheet.c
@@ -464,7 +464,7 @@ void browse_cuesheet(struct cuesheet *cue)
title[sizeof(title) - 2] = '~'; /* give indication of truncation */
- gui_synclist_init(&lists, list_get_name_cb, cue, false, 2, NULL);
+ gui_synclist_init(&lists, list_get_name_cb, cue, false, 2, NULL, false);
gui_synclist_set_nb_items(&lists, 2*cue->track_count);
gui_synclist_set_title(&lists, title, 0);
diff --git a/apps/filetypes.h b/apps/filetypes.h
index a20a2f82ee..fd477544c4 100644
--- a/apps/filetypes.h
+++ b/apps/filetypes.h
@@ -50,6 +50,7 @@
#define FILE_ATTR_OPX 0x1400 /* open plugins shortcut */
#define FILE_ATTR_LOG 0x1500 /* log file */
#define FILE_ATTR_MASK 0xFF00 /* which bits tree.c uses for file types */
+#define FILE_ATTR_MULTISELECTION 0x40000000 /* is file a part of a multiselection ? */
long tree_get_filetype_voiceclip(int attr);
diff --git a/apps/gui/bitmap/list-skinned.c b/apps/gui/bitmap/list-skinned.c
index 454010275d..9a13dd2c64 100644
--- a/apps/gui/bitmap/list-skinned.c
+++ b/apps/gui/bitmap/list-skinned.c
@@ -67,7 +67,7 @@ static bool skinlist_is_configured(enum screen_type screen,
struct gui_synclist *list)
{
return (listcfg[screen] != NULL) &&
- (!list || (list && list->selected_size == 1));
+ (!list || (list && (list->selected_size == 1 || list->multiple_selection_force_single_entry_scroll)));
}
static int current_drawing_line;
static int offset_to_item(int offset, bool wrap)
@@ -214,7 +214,8 @@ bool skinlist_draw(struct screen *display, struct gui_synclist *list)
display->set_background(parent->bg_pattern);
#endif
display->clear_viewport();
- current_item = list->selected_item;
+ int selected_item = list->selected_item;
+ current_item = selected_item;
current_nbitems = list->nb_items;
needs_scrollbar[screen] = list->nb_items > display_lines;
@@ -225,7 +226,7 @@ bool skinlist_draw(struct screen *display, struct gui_synclist *list)
if (list_start_item+cur_line+1 > list->nb_items)
break;
current_drawing_line = list_start_item+cur_line;
- is_selected = list_start_item+cur_line == list->selected_item;
+ is_selected = list_start_item+cur_line == selected_item;
for (viewport = SKINOFFSETTOPTR(get_skin_buffer(wps.data), listcfg[screen]->data->tree);
viewport;
@@ -315,6 +316,6 @@ bool skinlist_draw(struct screen *display, struct gui_synclist *list)
}
else
display->update_viewport();
- current_drawing_line = list->selected_item;
+ current_drawing_line = selected_item;
return true;
}
diff --git a/apps/gui/bitmap/list.c b/apps/gui/bitmap/list.c
index 17db15b8bd..4a47cf5f3d 100644
--- a/apps/gui/bitmap/list.c
+++ b/apps/gui/bitmap/list.c
@@ -248,7 +248,16 @@ void list_draw(struct screen *display, struct gui_synclist *list)
const int nb_lines = list_get_nb_lines(list, screen);
linedes.height = list->line_height[screen];
- linedes.nlines = list->selected_size;
+ int selected_item = list->selected_item;
+ int selected_size = list->selected_size;
+ if (selected_size <= 0)
+ {
+ selected_item += selected_size;
+ while (selected_item < 0)
+ selected_item += list->nb_items;
+ selected_size = ((- selected_size) + 1);
+ }
+ linedes.nlines = selected_size;
#if LCD_DEPTH > 1
/* XXX: Do we want to support the separator on remote displays? */
if (display->screen_type == SCREEN_MAIN)
@@ -388,8 +397,8 @@ void list_draw(struct screen *display, struct gui_synclist *list)
/* don't draw it during scrolling */
list->scroll_mode == SCROLL_NONE &&
#endif
- i >= list->selected_item
- && i < list->selected_item + list->selected_size)
+ ((i >= selected_item && i < selected_item + selected_size) ||
+ (i < (selected_item + selected_size - list->nb_items))))
{/* The selected item must be displayed scrolling */
#ifdef HAVE_LCD_COLOR
if (list->selection_color)
@@ -451,7 +460,7 @@ void list_draw(struct screen *display, struct gui_synclist *list)
#endif
linedes.style = style;
linedes.scroll = is_selected ? true : list->scroll_all;
- linedes.line = i % list->selected_size;
+ linedes.line = i % selected_size;
icon = list->callback_get_item_icon ?
list->callback_get_item_icon(i, list->data) : Icon_NOICON;
@@ -484,6 +493,8 @@ void list_draw(struct screen *display, struct gui_synclist *list)
#if defined(HAVE_TOUCHSCREEN)
/* This needs to be fixed if we ever get more than 1 touchscreen on a target. */
+/* FIXME: Multiple selection (with multiple_selection_force_single_entry_scroll)
+ is not coded (yet) for touchscreen devices */
static void do_touch_scroll(struct gui_synclist *gui_list, int new_y_pos)
{
diff --git a/apps/gui/list.c b/apps/gui/list.c
index 25ef70b76f..710639ddff 100644
--- a/apps/gui/list.c
+++ b/apps/gui/list.c
@@ -160,15 +160,19 @@ void gui_synclist_init(struct gui_synclist * gui_list,
list_get_name callback_get_item_name,
void * data,
bool scroll_all,
- int selected_size, struct viewport list_parent[NB_SCREENS]
+ int selected_size, struct viewport list_parent[NB_SCREENS],
+ bool multiple_selection_force_single_entry_scroll
)
{
gui_list->callback_get_item_icon = NULL;
gui_list->callback_get_item_name = callback_get_item_name;
gui_list->callback_speak_item = NULL;
gui_list->callback_draw_item = NULL;
+ gui_list->multiple_selection_force_single_entry_scroll = multiple_selection_force_single_entry_scroll;
gui_list->nb_items = 0;
gui_list->selected_item = 0;
+ gui_list->min_index_forbid_limit = 0;
+ gui_list->end_index_forbid_limit = 0;
gui_synclist_init_display_settings(gui_list);
#ifdef HAVE_TOUCHSCREEN
gui_list->y_pos = 0;
@@ -256,30 +260,34 @@ static void gui_list_put_selection_on_screen(struct gui_synclist * gui_list,
int nb_lines = list_get_nb_lines(gui_list, screen);
int bottom = MAX(0, gui_list->nb_items - nb_lines);
int new_start_item = gui_list->start_item[screen];
- int difference = gui_list->selected_item - gui_list->start_item[screen];
- const int scroll_limit_up = (nb_lines < gui_list->selected_size+2 ? 0:1);
- const int scroll_limit_down = (scroll_limit_up+gui_list->selected_size);
-
- if (gui_list->selected_size >= nb_lines)
+ int selected_item = gui_list->selected_item;
+ int selected_size = gui_list->selected_size;
+ if (gui_list->multiple_selection_force_single_entry_scroll)
+ selected_size = 1;
+ int difference = selected_item - gui_list->start_item[screen];
+ const int scroll_limit_up = (nb_lines < selected_size + 2 ? 0:1);
+ const int scroll_limit_down = (scroll_limit_up+selected_size);
+
+ if (selected_size >= nb_lines)
{
- new_start_item = gui_list->selected_item;
+ new_start_item = selected_item;
}
else if (gui_list->scroll_paginated)
{
- nb_lines -= nb_lines%gui_list->selected_size;
+ nb_lines -= nb_lines%selected_size;
if (difference < 0 || difference >= nb_lines)
{
- new_start_item = gui_list->selected_item -
- (gui_list->selected_item%nb_lines);
+ new_start_item = selected_item -
+ (selected_item%nb_lines);
}
}
else if (difference <= scroll_limit_up) /* list moved up */
{
- new_start_item = gui_list->selected_item - scroll_limit_up;
+ new_start_item = selected_item - scroll_limit_up;
}
else if (difference > nb_lines - scroll_limit_down) /* list moved down */
{
- new_start_item = gui_list->selected_item + scroll_limit_down - nb_lines;
+ new_start_item = selected_item + scroll_limit_down - nb_lines;
}
if (new_start_item < 0)
gui_list->start_item[screen] = 0;
@@ -389,22 +397,31 @@ void gui_synclist_select_item(struct gui_synclist * gui_list, int item_number)
static void gui_list_select_at_offset(struct gui_synclist * gui_list,
int offset, bool allow_wrap)
{
- if (gui_list->selected_size > 1)
+ int selected_item = gui_list->selected_item;
+ int selected_size = gui_list->selected_size;
+ int max_index = gui_list->nb_items - gui_list->end_index_forbid_limit;
+ if (max_index < gui_list->min_index_forbid_limit)
+ max_index = gui_list->min_index_forbid_limit;
+ if (gui_list->multiple_selection_force_single_entry_scroll)
+ selected_size = 1;
+ else if (selected_size > 1)
+ offset *= selected_size;
+
+ int new_selection = selected_item + offset;
+ int remain = (max_index - selected_size);
+ if (remain < gui_list->min_index_forbid_limit)
+ remain = gui_list->min_index_forbid_limit;
+ else if (remain > max_index)
+ remain = max_index;
+
+ if (new_selection >= max_index)
{
- offset *= gui_list->selected_size;
- }
-
- int new_selection = gui_list->selected_item + offset;
- int remain = (gui_list->nb_items - gui_list->selected_size);
-
- if (new_selection >= gui_list->nb_items)
- {
- new_selection = allow_wrap ? 0 : remain;
+ new_selection = (allow_wrap ? gui_list->min_index_forbid_limit : remain);
edge_beep(gui_list, allow_wrap);
}
- else if (new_selection < 0)
+ else if (new_selection < gui_list->min_index_forbid_limit)
{
- new_selection = allow_wrap ? remain : 0;
+ new_selection = (allow_wrap ? remain : gui_list->min_index_forbid_limit);
edge_beep(gui_list, allow_wrap);
}
@@ -517,7 +534,7 @@ static void gui_synclist_select_next_page(struct gui_synclist * lists,
bool allow_wrap)
{
int nb_lines = list_get_nb_lines(lists, screen);
- if (lists->selected_size > 1)
+ if (lists->selected_size > 1 && !lists->multiple_selection_force_single_entry_scroll)
nb_lines = MAX(1, nb_lines/lists->selected_size);
gui_list_select_at_offset(lists, nb_lines, allow_wrap);
@@ -528,7 +545,7 @@ static void gui_synclist_select_previous_page(struct gui_synclist * lists,
bool allow_wrap)
{
int nb_lines = list_get_nb_lines(lists, screen);
- if (lists->selected_size > 1)
+ if (lists->selected_size > 1 && !lists->multiple_selection_force_single_entry_scroll)
nb_lines = MAX(1, nb_lines/lists->selected_size);
gui_list_select_at_offset(lists, -nb_lines, allow_wrap);
@@ -578,8 +595,11 @@ bool gui_synclist_keyclick_callback(int action, void* data)
if (action == ACTION_STD_PREV && !lists->wraparound)
return false;
}
+ int selected_size = lists->selected_size;
+ if (lists->multiple_selection_force_single_entry_scroll)
+ selected_size = 1;
- if (lists->selected_item == lists->nb_items - lists->selected_size)
+ if (lists->selected_item == lists->nb_items - selected_size)
{
if (action == ACTION_STD_NEXTREPEAT)
return false;
@@ -709,6 +729,7 @@ bool gui_synclist_do_button(struct gui_synclist * lists, int *actionptr)
#ifndef HAVE_WHEEL_ACCELERATION
if (button_queue_count() < FRAMEDROP_TRIGGER)
#endif
+ if (!lists->multiple_selection_force_single_entry_scroll)
gui_synclist_draw(lists);
yield();
*actionptr = ACTION_STD_PREV;
@@ -722,6 +743,7 @@ bool gui_synclist_do_button(struct gui_synclist * lists, int *actionptr)
#ifndef HAVE_WHEEL_ACCELERATION
if (button_queue_count() < FRAMEDROP_TRIGGER)
#endif
+ if (!lists->multiple_selection_force_single_entry_scroll)
gui_synclist_draw(lists);
yield();
*actionptr = ACTION_STD_NEXT;
@@ -729,7 +751,8 @@ bool gui_synclist_do_button(struct gui_synclist * lists, int *actionptr)
case ACTION_TREE_PGRIGHT:
gui_synclist_scroll_right(lists);
- gui_synclist_draw(lists);
+ if (!lists->multiple_selection_force_single_entry_scroll)
+ gui_synclist_draw(lists);
yield();
return true;
case ACTION_TREE_ROOT_INIT:
@@ -755,7 +778,8 @@ bool gui_synclist_do_button(struct gui_synclist * lists, int *actionptr)
return false;
}
gui_synclist_scroll_left(lists);
- gui_synclist_draw(lists);
+ if (!lists->multiple_selection_force_single_entry_scroll)
+ gui_synclist_draw(lists);
pgleft_allow_cancel = false; /* stop ACTION_TREE_PAGE_LEFT
skipping to root */
yield();
@@ -772,7 +796,8 @@ bool gui_synclist_do_button(struct gui_synclist * lists, int *actionptr)
#endif
SCREEN_MAIN;
gui_synclist_select_previous_page(lists, screen, false);
- gui_synclist_draw(lists);
+ if (!lists->multiple_selection_force_single_entry_scroll)
+ gui_synclist_draw(lists);
yield();
*actionptr = ACTION_STD_NEXT;
}
@@ -787,7 +812,8 @@ bool gui_synclist_do_button(struct gui_synclist * lists, int *actionptr)
#endif
SCREEN_MAIN;
gui_synclist_select_next_page(lists, screen, false);
- gui_synclist_draw(lists);
+ if (!lists->multiple_selection_force_single_entry_scroll)
+ gui_synclist_draw(lists);
yield();
*actionptr = ACTION_STD_PREV;
}
@@ -938,7 +964,7 @@ bool simplelist_show_list(struct simplelist_info *info)
}
gui_synclist_init(&lists, getname, info->callback_data,
- info->scroll_all, info->selection_size, NULL);
+ info->scroll_all, info->selection_size, NULL, false);
if (info->title)
gui_synclist_set_title(&lists, info->title, info->title_icon);
diff --git a/apps/gui/list.h b/apps/gui/list.h
index 814a383e9a..fb0968cdad 100644
--- a/apps/gui/list.h
+++ b/apps/gui/list.h
@@ -155,10 +155,12 @@ struct gui_synclist
bool scroll_all;
int nb_items;
int selected_item;
-
+ int min_index_forbid_limit; /* don't let the user scroll under it */
+ int end_index_forbid_limit; /* don't let the user scroll past (number_items-this var) */
int start_item[NB_SCREENS]; /* the item that is displayed at the top of the screen */
/* the number of lines that are selected at the same time */
int selected_size;
+ bool multiple_selection_force_single_entry_scroll;
/* the number of pixels each line occupies (including optional padding on touchscreen */
int line_height[NB_SCREENS];
int offset_position[NB_SCREENS]; /* the list's screen scroll placement in pixels */
@@ -199,7 +201,8 @@ extern void gui_synclist_init(
void * data,
bool scroll_all,
int selected_size,
- struct viewport parent[NB_SCREENS] /* NOTE: new screens should NOT set this to NULL */
+ struct viewport parent[NB_SCREENS], /* NOTE: new screens should NOT set this to NULL */
+ bool multiple_selection_force_single_entry_scroll
);
extern void gui_synclist_set_nb_items(struct gui_synclist * lists, int nb_items);
extern void gui_synclist_set_icon_callback(struct gui_synclist * lists, list_get_icon icon_callback);
diff --git a/apps/gui/option_select.c b/apps/gui/option_select.c
index c7eab6aba1..236c0b8fc4 100644
--- a/apps/gui/option_select.c
+++ b/apps/gui/option_select.c
@@ -506,7 +506,7 @@ bool option_screen(const struct settings_list *setting,
else return false; /* only int/bools can go here */
push_current_activity(ACTIVITY_OPTIONSELECT);
gui_synclist_init(&lists, value_setting_get_name_cb,
- (void*)setting, false, 1, parent);
+ (void*)setting, false, 1, parent, false);
if (setting->lang_id == -1)
{
title = setting_get_cfgvals(setting);
diff --git a/apps/gui/wps.c b/apps/gui/wps.c
index 7bbeba0049..89d08a0a8a 100644
--- a/apps/gui/wps.c
+++ b/apps/gui/wps.c
@@ -803,7 +803,7 @@ long gui_wps_show(void)
theme_enabled = false;
gwps_leave_wps(theme_enabled);
onplay(state->id3->path,
- FILE_ATTR_AUDIO, CONTEXT_WPS, hotkey, ONPLAY_NO_CUSTOMACTION);
+ FILE_ATTR_AUDIO, CONTEXT_WPS, hotkey, ONPLAY_NO_CUSTOMACTION, false);
if (!audio_status())
{
/* re-enable theme since we're returning to SBS */
@@ -820,7 +820,7 @@ long gui_wps_show(void)
{
gwps_leave_wps(true);
int retval = onplay(state->id3->path,
- FILE_ATTR_AUDIO, CONTEXT_WPS, hotkey, ONPLAY_NO_CUSTOMACTION);
+ FILE_ATTR_AUDIO, CONTEXT_WPS, hotkey, ONPLAY_NO_CUSTOMACTION, false);
/* if music is stopped in the context menu we want to exit the wps */
if (retval == ONPLAY_MAINMENU
|| !audio_status())
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index 56044bfcbf..d5263480e3 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -5985,6 +5985,62 @@
*: "Disk size"
+
+ id: LANG_MULTISELECT_ENABLE
+ desc: enable multiselection (in lists)
+ user: core
+
+ *: "Enable Multiselection"
+
+
+ *: "Enable Multiselection"
+
+
+ *: "Enable Multiselection"
+
+
+
+ id: LANG_MULTISELECT_DISABLE
+ desc: disable multiselection (in lists)
+ user: core
+
+ *: "Disable Multiselection"
+
+
+ *: "Disable Multiselection"
+
+
+ *: "Disable Multiselection"
+
+
+
+ id: LANG_MULTISELECT_TOGGLE
+ desc: enable or disable multiselection in lists (toggle enable/disable)
+ user: core
+
+ *: "Toggle Multiselection"
+
+
+ *: "Toggle Multiselection"
+
+
+ *: "Toggle Multiselection"
+
+
+
+ id: LANG_ALL_FOLDERS_FROM_CURRENT_SELECTION
+ desc: ask yes/no popup
+ user: core
+
+ *: "All folders from current selection"
+
+
+ *: "All folders from current selection"
+
+
+ *: "All folders from current selection"
+
+
id: LANG_DISK_FREE_INFO
desc: disk size info
diff --git a/apps/lang/francais.lang b/apps/lang/francais.lang
index 316c3eef6c..60761784f2 100644
--- a/apps/lang/francais.lang
+++ b/apps/lang/francais.lang
@@ -5930,6 +5930,62 @@
*: "Disque"
+
+ id: LANG_MULTISELECT_ENABLE
+ desc: enable multiselection (in lists)
+ user: core
+
+ *: "Enable Multiselection"
+
+
+ *: "Activer la Multisélection"
+
+
+ *: "Activer la Multisélection"
+
+
+
+ id: LANG_MULTISELECT_DISABLE
+ desc: disable multiselection (in lists)
+ user: core
+
+ *: "Disable Multiselection"
+
+
+ *: "Désactiver la Multisélection"
+
+
+ *: "Désactiver la Multisélection"
+
+
+
+ id: LANG_MULTISELECT_TOGGLE
+ desc: enable or disable multiselection in lists (toggle enable/disable)
+ user: core
+
+ *: "Toggle Multiselection"
+
+
+ *: "Activer/Désactiver la Multisélection"
+
+
+ *: "Activer ou Désactiver la Multisélection"
+
+
+
+ id: LANG_ALL_FOLDERS_FROM_CURRENT_SELECTION
+ desc: ask yes/no popup
+ user: core
+
+ *: "All folders from current selection"
+
+
+ *: "Tous les dossiers de la sélection courante"
+
+
+ *: "Tous les dossiers de la sélection courante"
+
+
id: LANG_DISK_FREE_INFO
desc: disk size info
diff --git a/apps/menu.c b/apps/menu.c
index ce96c64d48..93c2e58a59 100644
--- a/apps/menu.c
+++ b/apps/menu.c
@@ -265,7 +265,7 @@ static int init_menu_lists(const struct menu_item_ex *menu,
current_submenus_menu = (struct menu_item_ex *)menu;
- gui_synclist_init(lists,get_menu_item_name,(void*)menu,false,1, parent);
+ gui_synclist_init(lists,get_menu_item_name,(void*)menu,false,1, parent, false);
title = init_title(menu, &icon, buf, buf_sz);
gui_synclist_set_title(lists, title, icon);
gui_synclist_set_icon_callback(lists, global_settings.show_icons?menu_get_icon:NULL);
diff --git a/apps/menus/recording_menu.c b/apps/menus/recording_menu.c
index 9ef0eb6bac..dfc5e1b177 100644
--- a/apps/menus/recording_menu.c
+++ b/apps/menus/recording_menu.c
@@ -489,7 +489,7 @@ int rectrigger(void)
settings[STOP_THRESHOLD] =
find_setting(&global_settings.rec_stop_thres_linear);
}
- gui_synclist_init(&lists, trigger_get_name, settings, false, 2, vp);
+ gui_synclist_init(&lists, trigger_get_name, settings, false, 2, vp, false);
gui_synclist_set_nb_items(&lists, TRIG_OPTION_COUNT*2);
gui_synclist_set_icon_callback(&lists, global_settings.show_icons?trigger_get_icon:NULL);
/* restart trigger with new values */
diff --git a/apps/onplay.c b/apps/onplay.c
index 0c176d9038..96794a49ec 100644
--- a/apps/onplay.c
+++ b/apps/onplay.c
@@ -237,16 +237,6 @@ MAKE_ONPLAYMENU( wps_playlist_menu, ID2P(LANG_CURRENT_PLAYLIST),
&search_playlist_item, &reshuffle_item, &playing_time_item
);
-/* argument for add_to_playlist (for use by menu callbacks) */
-#define PL_NONE 0x00
-#define PL_QUEUE 0x01
-#define PL_REPLACE 0x02
-struct add_to_pl_param
-{
- int8_t position;
- uint8_t flags;
-};
-
static struct add_to_pl_param addtopl_insert = {PLAYLIST_INSERT, PL_NONE};
static struct add_to_pl_param addtopl_insert_first = {PLAYLIST_INSERT_FIRST, PL_NONE};
static struct add_to_pl_param addtopl_insert_last = {PLAYLIST_INSERT_LAST, PL_NONE};
@@ -262,6 +252,13 @@ static struct add_to_pl_param addtopl_queue_last_shuf = {PLAYLIST_INSERT_LAST_S
static struct add_to_pl_param addtopl_replace = {PLAYLIST_INSERT, PL_REPLACE};
static struct add_to_pl_param addtopl_replace_shuffled = {PLAYLIST_INSERT_LAST_SHUFFLED, PL_REPLACE};
+static bool addto_pl_return_only_choice = false;
+static struct add_to_pl_param *addto_pl_current_choice = NULL;
+
+static bool show_playlist_cat_menu_return_only_choice = false;
+static char *show_playlist_cat_menu_current_choice = NULL;
+static bool show_playlist_cat_menu_current_choice_is_new_playlist = false;
+
static void op_playlist_insert_selected(int position, bool queue)
{
#ifdef HAVE_TAGCACHE
@@ -311,6 +308,12 @@ static int add_to_playlist(void* arg)
/* warn if replacing the playlist */
if (new_playlist && !warn_on_pl_erase())
return 1;
+
+ if (addto_pl_return_only_choice)
+ {
+ addto_pl_current_choice = param;
+ return 1;
+ }
splash(0, ID2P(LANG_WAIT));
@@ -519,16 +522,50 @@ void onplay_show_playlist_menu(const char* path, int attr, void (*playlist_inser
do_menu(&tree_playlist_menu, NULL, NULL, false);
}
+struct add_to_pl_param* onplay_show_playlist_menu_get_choice(void)
+{
+ addto_pl_current_choice = NULL;
+ addto_pl_return_only_choice = true;
+ in_queue_submenu = false;
+ do_menu(&tree_playlist_menu, NULL, NULL, false);
+ addto_pl_return_only_choice = false;
+ return addto_pl_current_choice;
+}
+
+struct add_to_pl_param* onplay_show_playlist_menu_get_current_choice(void)
+{
+ return addto_pl_current_choice;
+}
+
/* playlist catalog options */
static bool cat_add_to_a_playlist(void)
{
- return catalog_add_to_a_playlist(selected_file.path, selected_file.attr,
+ if (show_playlist_cat_menu_return_only_choice)
+ {
+ show_playlist_cat_menu_current_choice = catalog_add_to_a_playlist_get_choice(selected_file.path, selected_file.attr, false, NULL);
+ return true;
+ }
+ else
+ return catalog_add_to_a_playlist(selected_file.path, selected_file.attr,
false, NULL, ctx_add_to_playlist);
}
+char* onplay_show_playlist_cat_menu_get_current_choice(void)
+{
+ return show_playlist_cat_menu_current_choice;
+}
+
static bool cat_add_to_a_new_playlist(void)
{
- return catalog_add_to_a_playlist(selected_file.path, selected_file.attr,
+ if (show_playlist_cat_menu_return_only_choice)
+ {
+ show_playlist_cat_menu_current_choice = catalog_add_to_a_playlist_get_choice(selected_file.path, selected_file.attr,
+ true, NULL);
+ show_playlist_cat_menu_current_choice_is_new_playlist = true;
+ return true;
+ }
+ else
+ return catalog_add_to_a_playlist(selected_file.path, selected_file.attr,
true, NULL, ctx_add_to_playlist);
}
@@ -544,6 +581,21 @@ MAKE_ONPLAYMENU(cat_playlist_menu, ID2P(LANG_ADD_TO_PL),
cat_playlist_callback, Icon_Playlist,
&cat_add_to_list, &cat_add_to_new);
+bool onplay_show_playlist_cat_menu_current_choice_is_new_playlist(void)
+{
+ return show_playlist_cat_menu_current_choice_is_new_playlist;
+}
+
+char* onplay_show_playlist_cat_menu_get_choice(void)
+{
+ show_playlist_cat_menu_current_choice = NULL;
+ show_playlist_cat_menu_current_choice_is_new_playlist = false;
+ show_playlist_cat_menu_return_only_choice = true;
+ do_menu(&cat_playlist_menu, NULL, NULL, false);
+ show_playlist_cat_menu_return_only_choice = false;
+ return show_playlist_cat_menu_current_choice;
+}
+
void onplay_show_playlist_cat_menu(const char* track_name, int attr, void (*add_to_pl_cb))
{
ctx_add_to_playlist = add_to_pl_cb;
@@ -837,6 +889,12 @@ static bool list_viewers(void)
return false;
}
+static bool set_multiselect(void)
+{
+ onplay_result = ONPLAY_MULTISELECT;
+ return true;
+}
+
#ifdef HAVE_TAGCACHE
static bool prepare_database_sel(void *param)
{
@@ -905,6 +963,10 @@ MENUITEM_FUNCTION_W_PARAM(properties_item, 0, ID2P(LANG_PROPERTIES),
MENUITEM_FUNCTION_W_PARAM(track_info_item, 0, ID2P(LANG_MENU_SHOW_ID3_INFO),
onplay_load_plugin, (void *)"properties",
clipboard_callback, Icon_NOICON);
+MENUITEM_FUNCTION(multiselect_item_enable, 0, ID2P(LANG_MULTISELECT_ENABLE),
+ set_multiselect, clipboard_callback, Icon_NOICON);
+MENUITEM_FUNCTION(multiselect_item_disable, 0, ID2P(LANG_MULTISELECT_DISABLE),
+ set_multiselect, clipboard_callback, Icon_NOICON);
#ifdef HAVE_TAGCACHE
MENUITEM_FUNCTION_W_PARAM(pictureflow_item, 0, ID2P(LANG_ONPLAY_PICTUREFLOW),
onplay_load_plugin, (void *)"pictureflow",
@@ -1017,9 +1079,17 @@ static int clipboard_callback(int action,
struct gui_synclist *this_list)
{
(void)this_list;
+ bool multiselection_enabled = tree_get_context()->multiselection_enabled;
switch (action)
{
case ACTION_REQUEST_MENUITEM:
+ if (this_item == &multiselect_item_disable)
+ return (multiselection_enabled ? action : ACTION_EXIT_MENUITEM);
+ if (this_item == &multiselect_item_enable)
+ return (!multiselection_enabled ? action : ACTION_EXIT_MENUITEM);
+ if (multiselection_enabled &&
+ this_item != &tree_playlist_menu && this_item != &cat_playlist_menu)
+ return ACTION_EXIT_MENUITEM;
#ifdef HAVE_MULTIVOLUME
/* no rename+delete for volumes */
if ((selected_file.attr & ATTR_VOLUME) &&
@@ -1162,6 +1232,8 @@ MAKE_ONPLAYMENU( tree_onplay_menu, ID2P(LANG_ONPLAY_MENU_TITLE),
&rename_file_item, &clipboard_cut_item, &clipboard_copy_item,
&clipboard_paste_item, &delete_file_item, &delete_dir_item,
&list_viewers_item, &create_dir_item, &properties_item, &track_info_item,
+ &multiselect_item_enable,
+ &multiselect_item_disable,
#ifdef HAVE_TAGCACHE
&pictureflow_item,
#endif
@@ -1189,7 +1261,7 @@ static int onplaymenu_callback(int action,
}
break;
case ACTION_REQUEST_MENUITEM:
- if (this_item == &view_playlist_item)
+ if (this_item == &view_playlist_item && !tree_get_context()->multiselection_enabled)
{
if ((selected_file.attr & FILE_ATTR_MASK) == FILE_ATTR_M3U &&
selected_file.context == CONTEXT_TREE)
@@ -1335,6 +1407,11 @@ static const struct hotkey_assignment hotkey_items[] = {
.func = HOTKEY_FUNC(bookmark_load_menu, NULL),
.return_code = ONPLAY_START_PLAY,
.flags = HOTKEY_FLAG_WPS },
+ { .action = HOTKEY_MULTISELECTION,
+ .lang_id = LANG_MULTISELECT_TOGGLE,
+ .func = HOTKEY_FUNC(NULL, NULL),
+ .return_code = ONPLAY_MULTISELECT,
+ .flags = HOTKEY_FLAG_TREE },
{ .action = HOTKEY_PROPERTIES,
.lang_id = LANG_PROPERTIES,
.func = HOTKEY_FUNC(hotkey_tree_run_plugin, (void *)"properties"),
@@ -1387,11 +1464,17 @@ static int execute_hotkey(bool is_wps)
}
#endif /* HOTKEY */
-int onplay(char* file, int attr, int from_context, bool hotkey, int customaction)
+int onplay(char* file, int attr, int from_context, bool hotkey, int customaction, bool getchoice)
{
+ addto_pl_return_only_choice = getchoice;
+ show_playlist_cat_menu_return_only_choice = getchoice;
+ show_playlist_cat_menu_current_choice_is_new_playlist = false;
+ addto_pl_current_choice = NULL;
+ show_playlist_cat_menu_current_choice = NULL;
const struct menu_item_ex *menu;
onplay_result = ONPLAY_OK;
ctx_current_playlist_insert = NULL;
+
selected_file_set(from_context, NULL, attr);
#ifdef HAVE_TAGCACHE
@@ -1401,13 +1484,13 @@ int onplay(char* file, int attr, int from_context, bool hotkey, int customaction
if (file != NULL)
{
/* add a leading slash so that catalog_add_to_a_playlist
- later prefills the name when creating a new playlist */
+ later prefills the name when creating a new playlist */
snprintf(selected_file.buf, MAX_PATH, "/%s", file);
selected_file.path = selected_file.buf;
}
}
- else
-#endif
+ else
+ #endif
{
ctx_add_to_playlist = NULL;
if (file != NULL)
@@ -1421,13 +1504,20 @@ int onplay(char* file, int attr, int from_context, bool hotkey, int customaction
#ifdef HAVE_HOTKEY
if (hotkey)
- return execute_hotkey(from_context == CONTEXT_WPS);
+ {
+ int result = execute_hotkey(from_context == CONTEXT_WPS);
+ addto_pl_return_only_choice = false;
+ show_playlist_cat_menu_return_only_choice = false;
+ return result;
+ }
#else
(void)hotkey;
#endif
if (customaction == ONPLAY_CUSTOMACTION_SHUFFLE_SONGS)
{
int returnCode = add_to_playlist(&addtopl_replace_shuffled);
+ addto_pl_return_only_choice = false;
+ show_playlist_cat_menu_return_only_choice = false;
if (returnCode == 1)
// User did not want to erase his current playlist, so let's show again the database main menu
return ONPLAY_RELOAD_DIR;
@@ -1444,7 +1534,8 @@ int onplay(char* file, int attr, int from_context, bool hotkey, int customaction
if (get_current_activity() == ACTIVITY_CONTEXTMENU) /* Activity may have been */
pop_current_activity(); /* popped already by menu item */
-
+ addto_pl_return_only_choice = false;
+ show_playlist_cat_menu_return_only_choice = false;
if (menu_selection == GO_TO_WPS)
return ONPLAY_START_PLAY;
if (menu_selection == GO_TO_ROOT)
diff --git a/apps/onplay.h b/apps/onplay.h
index 26b1247f7b..d63bf1fe13 100644
--- a/apps/onplay.h
+++ b/apps/onplay.h
@@ -25,13 +25,24 @@
#include "menu.h"
#endif
+/* argument for add_to_playlist (for use by menu callbacks) */
+#define PL_NONE 0x00
+#define PL_QUEUE 0x01
+#define PL_REPLACE 0x02
+
+struct add_to_pl_param
+{
+ int8_t position;
+ uint8_t flags;
+};
+
enum {
ONPLAY_NO_CUSTOMACTION,
ONPLAY_CUSTOMACTION_SHUFFLE_SONGS,
ONPLAY_CUSTOMACTION_FIRSTLETTER,
};
-int onplay(char* file, int attr, int from_context, bool hotkey, int customaction);
+int onplay(char* file, int attr, int from_context, bool hotkey, int customaction, bool getchoice);
int get_onplay_context(void);
enum {
@@ -41,6 +52,7 @@ enum {
ONPLAY_START_PLAY,
ONPLAY_PLAYLIST,
ONPLAY_PLUGIN,
+ ONPLAY_MULTISELECT,
#ifdef HAVE_HOTKEY
ONPLAY_FUNC_RETURN, /* for use in hotkey_assignment only */
#endif
@@ -62,6 +74,7 @@ enum hotkey_action {
HOTKEY_INSERT,
HOTKEY_INSERT_SHUFFLED,
HOTKEY_BOOKMARK_LIST,
+ HOTKEY_MULTISELECTION,
};
enum hotkey_flags {
HOTKEY_FLAG_NONE = 0x0,
@@ -84,6 +97,10 @@ const struct hotkey_assignment *get_hotkey(int action);
/* needed for the playlist viewer.. eventually clean this up */
void onplay_show_playlist_cat_menu(const char* track_name, int attr,
void (*add_to_pl_cb));
+bool onplay_show_playlist_cat_menu_current_choice_is_new_playlist(void);
+char* onplay_show_playlist_cat_menu_get_choice(void);
void onplay_show_playlist_menu(const char* path, int attr, void (*playlist_insert_cb));
-
+struct add_to_pl_param* onplay_show_playlist_menu_get_choice(void);
+struct add_to_pl_param* onplay_show_playlist_menu_get_current_choice(void);
+char* onplay_show_playlist_cat_menu_get_current_choice(void);
#endif
diff --git a/apps/playlist.c b/apps/playlist.c
index 1543fad262..4a27036000 100644
--- a/apps/playlist.c
+++ b/apps/playlist.c
@@ -2507,29 +2507,25 @@ void playlist_insert_context_release(struct playlist_insert_context *context)
*/
int playlist_insert_directory(struct playlist_info* playlist,
const char *dirname, int position, bool queue,
- bool recurse, struct playlist_insert_context* context)
+ bool recurse, struct playlist_insert_context* context_to_use)
{
- bool context_provided = context;
- int result = 0;
- struct playlist_insert_context c;
-
- if (!context_provided)
- {
- context = &c;
- result = playlist_insert_context_create(playlist, &c,
- position, queue, true);
- }
+ int result = -1;
+ struct playlist_insert_context context;
+ if (context_to_use == NULL)
+ result = playlist_insert_context_create(playlist, &context, position, queue, true);
+ else
+ result = 0;
if (result >= 0)
{
cpu_boost(true);
result = playlist_directory_tracksearch(dirname, recurse,
- directory_search_callback, context);
+ directory_search_callback, context_to_use == NULL ? &context : context_to_use);
cpu_boost(false);
}
- if (!context_provided)
- playlist_insert_context_release(&c);
+ if (context_to_use == NULL)
+ playlist_insert_context_release(&context);
return result;
}
diff --git a/apps/playlist.h b/apps/playlist.h
index 02e1262cd3..f145669573 100644
--- a/apps/playlist.h
+++ b/apps/playlist.h
@@ -157,7 +157,7 @@ int playlist_insert_context_add(struct playlist_insert_context *context,
void playlist_insert_context_release(struct playlist_insert_context *context);
int playlist_insert_directory(struct playlist_info* playlist,
const char *dirname, int position, bool queue,
- bool recurse, struct playlist_insert_context *context);
+ bool recurse, struct playlist_insert_context* context_to_use);
int playlist_insert_playlist(struct playlist_info* playlist, const char *filename,
int position, bool queue);
bool playlist_entries_iterate(const char *filename,
diff --git a/apps/playlist_catalog.c b/apps/playlist_catalog.c
index 736e5595dc..64b799f02b 100644
--- a/apps/playlist_catalog.c
+++ b/apps/playlist_catalog.c
@@ -288,7 +288,7 @@ static int add_track_to_playlist(char* filename, void* context)
/* Add "sel" file into specified "playlist". How to insert depends on type
of file */
int catalog_insert_into(const char* playlist, bool new_playlist,
- const char* sel, int sel_attr)
+ const char* sel, int sel_attr, bool* forced_recurse_dir)
{
int fd;
int result = -1;
@@ -349,21 +349,25 @@ int catalog_insert_into(const char* playlist, bool new_playlist,
else if (sel_attr & ATTR_DIRECTORY)
{
/* search directory for tracks and append to playlist */
- bool recurse;
- const char *lines[] = {
- ID2P(LANG_RECURSE_DIRECTORY_QUESTION), sel};
- const struct text_message message={lines, 2};
struct add_track_context context;
-
-
- if (sel[1] == '\0' && sel[0] == PATH_ROOTCHR)
- recurse = true;
- else if (global_settings.recursive_dir_insert != RECURSE_ASK)
- recurse = (bool)global_settings.recursive_dir_insert;
+ bool recurse;
+ if (forced_recurse_dir != NULL)
+ recurse = *forced_recurse_dir;
else
{
- /* Ask if user wants to recurse directory */
- recurse = (gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES);
+ const char *lines[] = {
+ ID2P(LANG_RECURSE_DIRECTORY_QUESTION), sel};
+ const struct text_message message={lines, 2};
+
+ if (sel[1] == '\0' && sel[0] == PATH_ROOTCHR)
+ recurse = true;
+ else if (global_settings.recursive_dir_insert != RECURSE_ASK)
+ recurse = (bool)global_settings.recursive_dir_insert;
+ else
+ {
+ /* Ask if user wants to recurse directory */
+ recurse = (gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES);
+ }
}
context.fd = fd;
@@ -457,13 +461,14 @@ bool catalog_pick_new_playlist_name(char *pl_name, size_t buf_size,
return do_save;
}
+static char playlist[MAX_PATH + 7]; /* room for /.m3u8\0*/
+
static int (*ctx_add_to_playlist)(const char* playlist, bool new_playlist);
bool catalog_add_to_a_playlist(const char* sel, int sel_attr,
bool new_playlist, char *m3u8name,
void (*add_to_pl_cb))
{
int result;
- char playlist[MAX_PATH + 7]; /* room for /.m3u8\0*/
size_t basename_start;
if ((browser_status & CATBROWSE_PLAYLIST) == CATBROWSE_PLAYLIST)
return false;
@@ -515,7 +520,59 @@ bool catalog_add_to_a_playlist(const char* sel, int sel_attr,
result = ctx_add_to_playlist(playlist, new_playlist);
}
else
- result = catalog_insert_into(playlist, new_playlist, sel, sel_attr);
+ result = catalog_insert_into(playlist, new_playlist, sel, sel_attr, NULL);
return (result == 0);
}
+
+char* catalog_add_to_a_playlist_get_choice(const char* sel, int sel_attr,
+ bool new_playlist, char *m3u8name)
+{
+ size_t basename_start;
+ if ((browser_status & CATBROWSE_PLAYLIST) == CATBROWSE_PLAYLIST)
+ return NULL;
+
+ if (initialize_catalog_buf(playlist, sizeof(playlist)) < 0)
+ return NULL;
+
+ if (new_playlist)
+ {
+ if (m3u8name == NULL)
+ {
+ const char *name;
+ /* If sel is empty, root, or playlist directory we use 'all' */
+ if (!sel || !strcmp(sel, "/") || !strcmp(sel, playlist))
+ {
+ sel = "/";
+ name = "/all";
+ }
+ else /*If sel is a folder, we prefill the text field with its name*/
+ name = strrchr(sel, '/');
+
+ if (name == NULL || ((sel_attr & ATTR_DIRECTORY) != ATTR_DIRECTORY) ||
+ ((sel_attr & FILE_ATTR_MULTISELECTION) == FILE_ATTR_MULTISELECTION))
+ create_numbered_filename(playlist, playlist, PLAYLIST_UNTITLED_PREFIX,
+ ".m3u8", 1 IF_CNFN_NUM_(, NULL));
+ else
+ {
+ basename_start = strlen(playlist) + 1;
+ strlcat(playlist, name, sizeof(playlist));
+ fix_path_part(playlist, basename_start,
+ sizeof(playlist) - 1 - basename_start) ;
+ apply_playlist_extension(playlist, sizeof(playlist));
+ }
+ }
+ else
+ strmemccpy(playlist, m3u8name, sizeof(playlist));
+
+ if (!catalog_pick_new_playlist_name(playlist, sizeof(playlist), NULL))
+ return NULL;
+ }
+ else
+ {
+ if (display_playlists(playlist, CATBROWSE_PLAYLIST) < 0)
+ return NULL;
+ }
+
+ return playlist;
+}
\ No newline at end of file
diff --git a/apps/playlist_catalog.h b/apps/playlist_catalog.h
index add8d23293..49ead1f28b 100644
--- a/apps/playlist_catalog.h
+++ b/apps/playlist_catalog.h
@@ -36,9 +36,6 @@ bool catalog_view_playlists(void);
bool catalog_pick_new_playlist_name(char *pl_name, size_t buf_size,
const char* curr_pl_name);
-int catalog_insert_into(const char* playlist, bool new_playlist,
- const char* sel, int sel_attr);
-
/*
* Add something to a playlist (new or select from list of playlists in
* catalog).
@@ -56,4 +53,10 @@ bool catalog_add_to_a_playlist(const char* sel, int sel_attr,
bool new_playlist, char* m3u8name,
void (*add_to_pl_cb));
+char* catalog_add_to_a_playlist_get_choice(const char* sel, int sel_attr,
+ bool new_playlist, char* m3u8name);
+
+int catalog_insert_into(const char* playlist, bool new_playlist,
+ const char* sel, int sel_attr, bool* forced_recurse_dir);
+
#endif
diff --git a/apps/playlist_viewer.c b/apps/playlist_viewer.c
index d2971981e3..0f10055fb7 100644
--- a/apps/playlist_viewer.c
+++ b/apps/playlist_viewer.c
@@ -120,10 +120,13 @@ struct playlist_viewer {
int *initial_selection; /* The initially selected track */
int current_playing_track; /* Index of current playing track */
int selected_track; /* The selected track, relative (first is 0) */
+ int selected_size; /* Multiselection. Offset is the selected track. */
+ int min_index_forbid_limit; /* don't let the user scroll under it */
+ int end_index_forbid_limit; /* don't let the user scroll past (number_items-this var) */
+ bool multiselection_enabled;
+ int multiselection_offset_track;
int moving_track; /* The track to move, relative (first is 0)
or -1 if nothing is currently being moved */
- int moving_playlist_index; /* Playlist-relative index (as opposed to
- viewer-relative index) of moving track */
struct playlist_buffer buffer;
struct mp3entry *id3;
bool allow_view_text_plugin;
@@ -477,8 +480,11 @@ static bool playlist_viewer_init(struct playlist_viewer * viewer,
}
playlist_buffer_init(&viewer->buffer, buffer, buffer_size);
+ viewer->min_index_forbid_limit = 0;
+ viewer->end_index_forbid_limit = 0;
+ viewer->selected_size = 1;
viewer->moving_track = -1;
- viewer->moving_playlist_index = -1;
+ viewer->multiselection_enabled = false;
viewer->initial_selection = most_recent_selection;
if (!reload)
@@ -673,21 +679,98 @@ static enum pv_context_result open_pictureflow(const struct playlist_entry *curr
#endif
#endif /*defined(HAVE_HOTKEY) || defined(HAVE_TAGCACHE)*/
-static enum pv_context_result delete_track(int current_track_index,
- int index, bool current_was_playing)
+static enum pv_context_result add_tracks_to_current_playlist(int index, struct add_to_pl_param *choice)
{
- playlist_delete(viewer.playlist, current_track_index);
+ bool modified = false;
+ struct playlist_entry *current_track;
+ if (choice != NULL)
+ {
+ int selected_size = viewer.selected_size;
+ if (selected_size < 0)
+ {
+ index -= (- selected_size);
+ selected_size = ((- selected_size) + 1);
+ }
+ struct playlist_insert_context pl_context;
+ bool queue = (choice->flags & PL_QUEUE) == PL_QUEUE;
+ bool new_playlist = (choice->flags & PL_REPLACE) == PL_REPLACE;
+ if (new_playlist && global_settings.keep_current_track_on_replace_playlist)
+ {
+ if (audio_status() & AUDIO_STATUS_PLAY)
+ {
+ modified = true;
+ playlist_remove_all_tracks(NULL);
+ new_playlist = false;
+ }
+ }
+ if (new_playlist)
+ {
+ modified = true;
+ playlist_create(NULL, NULL);
+ }
+
+ if (playlist_insert_context_create(NULL, &pl_context, choice->position, queue, true) >= 0)
+ {
+ for (int i = 0; i < selected_size; i++)
+ {
+ current_track = playlist_buffer_get_track(&viewer.buffer, index + i);
+ if (playlist_insert_context_add(&pl_context, current_track->name) < 0)
+ {
+ break;
+ }
+ modified = true;
+ }
+ }
+ playlist_insert_context_release(&pl_context);
+ if (new_playlist && (playlist_amount() > 0))
+ {
+ if (global_settings.playlist_shuffle)
+ playlist_shuffle(current_tick, -1);
+ playlist_start(0, 0, 0);
+ }
+ }
+ return modified ? PV_CONTEXT_MODIFIED : PV_CONTEXT_UNCHANGED;
+}
+
+static enum pv_context_result delete_tracks(int index)
+{
+ if (viewer.selected_size < 0)
+ {
+ index -= (- viewer.selected_size);
+ viewer.selected_size = ((- viewer.selected_size) + 1);
+ }
+ bool current_was_playing = false;
+ int pl_track_info_index;
+
+ for (int i = 0; i < viewer.selected_size; i++)
+ {
+ /* Playlist viewer orders songs based on display index. We need to
+ convert to real playlist index to access track */
+ pl_track_info_index = (index + playlist_get_first_index(viewer.playlist))
+ % viewer.num_tracks;
+ if (!current_was_playing)
+ current_was_playing = (audio_status() & AUDIO_STATUS_PLAY) && /* or paused */
+ (pl_track_info_index == viewer.current_playing_track);
+ playlist_delete(viewer.playlist, pl_track_info_index);
+ if (i + 1 < viewer.selected_size)
+ {
+ if (!viewer.playlist)
+ playlist_get_resume_info(&viewer.current_playing_track);
+ else
+ viewer.current_playing_track = -1;
+
+ viewer.num_tracks = playlist_amount_ex(viewer.playlist);
+ }
+ }
if (current_was_playing)
{
if (playlist_amount_ex(viewer.playlist) <= 0)
audio_stop();
else
{
- /* Start playing new track except if it's the lasttrack
- track in the playlist and repeat mode is disabled */
- struct playlist_entry *current_track =
- playlist_buffer_get_track(&viewer.buffer, index);
- if (current_track->display_index != viewer.num_tracks ||
+ /* Start playing new track except if it's the lasttrack
+ track in the playlist and repeat mode is disabled */
+ if ((index + 1) != playlist_amount_ex(viewer.playlist) ||
global_settings.repeat_mode == REPEAT_ALL)
{
audio_play(0, 0);
@@ -700,14 +783,11 @@ static enum pv_context_result delete_track(int current_track_index,
static enum pv_context_result context_menu(int index)
{
- struct playlist_entry *current_track = playlist_buffer_get_track(&viewer.buffer,
- index);
- bool current_was_playing = (audio_status() & AUDIO_STATUS_PLAY) && /* or paused */
- (current_track->index == viewer.current_playing_track);
+ struct playlist_entry *current_track;
MENUITEM_STRINGLIST(menu_items, ID2P(LANG_PLAYLIST), NULL,
ID2P(LANG_PLAYING_NEXT), ID2P(LANG_ADD_TO_PL),
- ID2P(LANG_REMOVE), ID2P(LANG_MOVE),
+ ID2P(LANG_MULTISELECT_ENABLE), ID2P(LANG_REMOVE), ID2P(LANG_MOVE),
ID2P(LANG_MENU_SHOW_ID3_INFO),
ID2P(LANG_SHUFFLE), ID2P(LANG_SAVE),
ID2P(LANG_PLAYLISTVIEWER_SETTINGS)
@@ -715,35 +795,95 @@ static enum pv_context_result context_menu(int index)
,ID2P(LANG_ONPLAY_PICTUREFLOW)
#endif
);
- int sel = do_menu(&menu_items, NULL, NULL, false);
+
+ MENUITEM_STRINGLIST(menu_items_multiselection_enabled, ID2P(LANG_PLAYLIST), NULL,
+ ID2P(LANG_PLAYING_NEXT), ID2P(LANG_ADD_TO_PL),
+ ID2P(LANG_MULTISELECT_DISABLE), ID2P(LANG_REMOVE), ID2P(LANG_MOVE)
+ );
+ int sel;
+ if (viewer.multiselection_enabled)
+ sel = do_menu(&menu_items_multiselection_enabled, NULL, NULL, false);
+ else
+ sel = do_menu(&menu_items, NULL, NULL, false);
if (sel == MENU_ATTACHED_USB)
return PV_CONTEXT_USB;
else if (sel >= 0)
{
/* Abort current move */
viewer.moving_track = -1;
- viewer.moving_playlist_index = -1;
switch (sel)
{
case 0:
+ {
/* Playing Next... menu */
- onplay_show_playlist_menu(current_track->name, FILE_ATTR_AUDIO, NULL);
- return PV_CONTEXT_UNCHANGED;
+ struct add_to_pl_param *choice = onplay_show_playlist_menu_get_choice();
+ return add_tracks_to_current_playlist(index, choice);
+ }
case 1:
+ {
/* Add to Playlist... menu */
- onplay_show_playlist_cat_menu(current_track->name, FILE_ATTR_AUDIO, NULL);
+ char* choice = onplay_show_playlist_cat_menu_get_choice();
+ if (viewer.selected_size < 0)
+ {
+ index -= (- viewer.selected_size);
+ viewer.selected_size = ((- viewer.selected_size) + 1);
+ }
+ if (choice != NULL)
+ {
+ bool new_playlist = onplay_show_playlist_cat_menu_current_choice_is_new_playlist();
+ for (int i = 0; i < viewer.selected_size; i++)
+ {
+ current_track = playlist_buffer_get_track(&viewer.buffer, index + i);
+ if (catalog_insert_into(choice, new_playlist, current_track->name, FILE_ATTR_AUDIO, NULL) != 0)
+ {
+ splash(HZ*2, ID2P(LANG_PLAYLIST_ACCESS_ERROR));
+ break;
+ }
+ new_playlist = false;
+ }
+ viewer.selected_size = 1;
+ viewer.multiselection_enabled = false;
+ }
return PV_CONTEXT_UNCHANGED;
+ }
case 2:
- return delete_track(current_track->index, index, current_was_playing);
+ /* Make a multiselection */
+ if (!viewer.multiselection_enabled)
+ {
+ viewer.multiselection_enabled = true;
+ viewer.multiselection_offset_track = index;
+ return PV_CONTEXT_UNCHANGED;
+ }
+ else
+ {
+ /* Will be disabled later bacause of PV_CONTEXT_MODIFIED */
+ return PV_CONTEXT_MODIFIED;
+ }
case 3:
+ return delete_tracks(index);
+ case 4:
/* move track */
viewer.moving_track = index;
- viewer.moving_playlist_index = current_track->index;
+ if (viewer.multiselection_enabled)
+ {
+ int selected_size = viewer.selected_size;
+ if (selected_size < 0)
+ {
+ viewer.min_index_forbid_limit = (- selected_size);
+ viewer.end_index_forbid_limit = 0;
+ }
+ else
+ {
+ viewer.min_index_forbid_limit = 0;
+ viewer.end_index_forbid_limit = (selected_size - 1);
+ }
+ }
return PV_CONTEXT_UNCHANGED;
- case 4:
- return show_track_info(current_track);
case 5:
+ current_track = playlist_buffer_get_track(&viewer.buffer, index);
+ return show_track_info(current_track);
+ case 6:
/* shuffle */
if (!yesno_pop_confirm(ID2P(LANG_SHUFFLE)))
return PV_CONTEXT_UNCHANGED;
@@ -751,11 +891,11 @@ static enum pv_context_result context_menu(int index)
playlist_randomise(viewer.playlist, current_tick, !viewer.playlist);
viewer.selected_track = 0;
return PV_CONTEXT_MODIFIED;
- case 6:
+ case 7:
save_playlist_screen(viewer.playlist);
/* playlist indices of current playlist may have changed */
return viewer.playlist ? PV_CONTEXT_UNCHANGED : PV_CONTEXT_PL_UPDATE;
- case 7:
+ case 8:
{
/* playlist viewer settings */
sel = global_settings.playlist_viewer_track_display;
@@ -766,7 +906,8 @@ static enum pv_context_result context_menu(int index)
PV_CONTEXT_UNCHANGED : PV_CONTEXT_PL_UPDATE;
}
#ifdef HAVE_TAGCACHE
- case 8:
+ case 9:
+ current_track = playlist_buffer_get_track(&viewer.buffer, index);
return open_pictureflow(current_track);
#endif
}
@@ -777,24 +918,40 @@ static enum pv_context_result context_menu(int index)
static int get_track_num(struct playlist_viewer *local_viewer,
int selected_item)
{
+ int result = selected_item;
if (local_viewer->moving_track >= 0)
{
- if (local_viewer->selected_track == selected_item)
- {
- return local_viewer->moving_track;
- }
- else if (local_viewer->selected_track > selected_item
- && selected_item >= local_viewer->moving_track)
+ int selected_size = viewer.selected_size;
+ int moving_track = local_viewer->moving_track;
+ int cur_selected_track = local_viewer->selected_track;
+ if (selected_size < 0)
{
- return selected_item+1; /* move down */
+ moving_track -= (- selected_size);
+ cur_selected_track -= (- selected_size);
+ selected_size = ((- selected_size) + 1);
}
- else if (local_viewer->selected_track < selected_item
- && selected_item <= local_viewer->moving_track)
+ for (int i = 0; i < selected_size; i++)
{
- return selected_item-1; /* move up */
+ if ((cur_selected_track + i) == selected_item)
+ {
+ result = moving_track + i;
+ break;
+ }
+ else if ((cur_selected_track + (selected_size - 1 - i)) > selected_item
+ && selected_item >= (moving_track + (selected_size - 1 - i)))
+ {
+ /* move down */
+ result = selected_item + i + 1;
+ }
+ else if ((cur_selected_track + i) < selected_item
+ && selected_item <= (moving_track + i))
+ {
+ /* move up */
+ result = selected_item - i - 1;
+ }
}
}
- return selected_item;
+ return result;
}
static struct playlist_entry* pv_get_track(struct playlist_viewer *local_viewer, int selected_item)
@@ -821,15 +978,24 @@ static enum themable_icons playlist_callback_icons(int selected_item,
{
struct playlist_viewer *local_viewer = (struct playlist_viewer *)data;
struct playlist_entry *track = pv_get_track(local_viewer, selected_item);
+ int moving_track = local_viewer->moving_track;
+ int selected_size = local_viewer->selected_size;
+ if (moving_track >= 0 && selected_size < 0)
+ {
+ moving_track -= (- selected_size);
+ selected_size = ((- selected_size) + 1);
+ }
if (track->index == local_viewer->current_playing_track)
{
/* Current playing track */
return Icon_Audio;
}
- else if (track->index == local_viewer->moving_playlist_index)
+ else if (moving_track >= 0
+ && track->index >= moving_track
+ && track->index < (moving_track + selected_size))
{
- /* Track we are moving */
+ /* Tracks we are moving */
return Icon_Moving;
}
else if (track->attr & PLAYLIST_ATTR_QUEUED)
@@ -885,7 +1051,10 @@ static void update_gui(struct gui_synclist * playlist_lists, bool init)
{
if (init)
gui_synclist_init(playlist_lists, playlist_callback_name,
- &viewer, false, 1, NULL);
+ &viewer, false, 1, NULL, true);
+ playlist_lists->selected_size = viewer.selected_size;
+ playlist_lists->min_index_forbid_limit = viewer.min_index_forbid_limit;
+ playlist_lists->end_index_forbid_limit = viewer.end_index_forbid_limit;
gui_synclist_set_nb_items(playlist_lists, viewer.num_tracks);
gui_synclist_set_voice_callback(playlist_lists,
global_settings.talk_file?
@@ -903,7 +1072,11 @@ static bool update_viewer(struct gui_synclist *playlist_lists, enum pv_context_r
{
bool exit = false;
if (res == PV_CONTEXT_MODIFIED)
+ {
+ viewer.selected_size = 1;
+ viewer.multiselection_enabled = false;
playlist_set_modified(viewer.playlist, true);
+ }
if (res == PV_CONTEXT_MODIFIED || res == PV_CONTEXT_PL_UPDATE)
{
@@ -977,17 +1150,73 @@ enum playlist_viewer_result playlist_viewer_ex(const char* filename,
/* during moving, another redraw is going to be needed,
* since viewer.selected_track is updated too late (after the first draw)
* drawing the moving item needs it */
- viewer.selected_track=gui_synclist_get_sel_pos(&playlist_lists);
+ bool list_redraw_needed = false;
+ if (viewer.selected_track != gui_synclist_get_sel_pos(&playlist_lists))
+ {
+ viewer.selected_track = gui_synclist_get_sel_pos(&playlist_lists);
+ list_redraw_needed = true;
+ }
+ if (viewer.multiselection_enabled)
+ {
+ if (viewer.moving_track < 0)
+ {
+ int offsetdiff = viewer.selected_track - viewer.multiselection_offset_track;
+ if (offsetdiff < 0)
+ {
+ viewer.selected_size = (- offsetdiff) + 1;
+ }
+ else if (offsetdiff == 0)
+ {
+ viewer.selected_size = 1;
+ }
+ else
+ {
+ viewer.selected_size = - (offsetdiff);
+ }
+ viewer.min_index_forbid_limit = 0;
+ viewer.end_index_forbid_limit = 0;
+ }
+ }
+ else
+ {
+ viewer.selected_size = 1;
+ viewer.min_index_forbid_limit = 0;
+ viewer.end_index_forbid_limit = 0;
+ }
+ if (playlist_lists.selected_size != viewer.selected_size)
+ {
+ playlist_lists.selected_size = viewer.selected_size;
+ list_redraw_needed = true;
+ }
+ if (playlist_lists.min_index_forbid_limit != viewer.min_index_forbid_limit)
+ {
+ playlist_lists.min_index_forbid_limit = viewer.min_index_forbid_limit;
+ list_redraw_needed = true;
+ }
+ if (playlist_lists.end_index_forbid_limit != viewer.end_index_forbid_limit)
+ {
+ playlist_lists.end_index_forbid_limit = viewer.end_index_forbid_limit;
+ list_redraw_needed = true;
+ }
if (res)
{
bool reload = playlist_buffer_needs_reload(&viewer.buffer,
viewer.selected_track);
if (reload)
+ {
playlist_buffer_load_entries_screen(&viewer.buffer,
button == ACTION_STD_NEXT ? FORWARD : BACKWARD,
viewer.selected_track);
- if (reload || viewer.moving_track >= 0)
- gui_synclist_draw(&playlist_lists);
+ list_redraw_needed = true;
+ }
+ }
+ if (list_redraw_needed)
+ /* it's important to refresh conditionally only when something changed,
+ or all texts scrolling will be broken */
+ gui_synclist_draw(&playlist_lists);
+ if (viewer.moving_track < 0 && button == ACTION_STD_OK && viewer.selected_size != 1)
+ {
+ button = ACTION_STD_CONTEXT;
}
switch (button)
{
@@ -999,7 +1228,15 @@ enum playlist_viewer_result playlist_viewer_ex(const char* filename,
viewer.selected_track = viewer.moving_track;
gui_synclist_select_item(&playlist_lists, viewer.moving_track);
viewer.moving_track = -1;
- viewer.moving_playlist_index = -1;
+ viewer.selected_size = 1;
+ viewer.multiselection_enabled = false;
+ gui_synclist_draw(&playlist_lists);
+ gui_synclist_speak_item(&playlist_lists);
+ }
+ else if (viewer.multiselection_enabled)
+ {
+ viewer.selected_size = 1;
+ viewer.multiselection_enabled = false;
gui_synclist_draw(&playlist_lists);
gui_synclist_speak_item(&playlist_lists);
}
@@ -1019,22 +1256,40 @@ enum playlist_viewer_result playlist_viewer_ex(const char* filename,
int ret_val, start_index = current_track->index;
if (viewer.moving_track >= 0)
{
- /* Move track */
- ret_val = playlist_move(viewer.playlist,
- viewer.moving_playlist_index,
- current_track->index);
- if (ret_val < 0)
+ /* Move tracks */
+ int moving_track = viewer.moving_track;
+ int distance = (viewer.selected_track - moving_track);
+ if (viewer.selected_size < 0)
{
- cond_talk_ids_fq(LANG_MOVE, LANG_FAILED);
- splashf(HZ, (unsigned char *)"%s %s", str(LANG_MOVE),
- str(LANG_FAILED));
+ moving_track -= (- viewer.selected_size);
+ viewer.selected_size = ((- viewer.selected_size) + 1);
+ }
+ for (int i = 0; i < viewer.selected_size; i++)
+ {
+ int start_index = moving_track;
+ int dest_index = moving_track + distance;
+ if (start_index > dest_index)
+ {
+ start_index = start_index + i;
+ dest_index = dest_index + i;
+ }
+ else if (viewer.selected_size > 1 && distance > 0)
+ dest_index = (dest_index + (viewer.selected_size - 1));
+ ret_val = playlist_move(viewer.playlist,
+ (playlist_buffer_get_track(&viewer.buffer, start_index)->index),
+ (playlist_buffer_get_track(&viewer.buffer, dest_index)->index));
+ if (ret_val < 0)
+ {
+ cond_talk_ids_fq(LANG_MOVE, LANG_FAILED);
+ splashf(HZ, (unsigned char *)"%s %s", str(LANG_MOVE),
+ str(LANG_FAILED));
+ break;
+ }
}
-
playlist_set_modified(viewer.playlist, true);
-
update_playlist(true);
+ viewer.multiselection_enabled = false;
viewer.moving_track = -1;
- viewer.moving_playlist_index = -1;
}
else if (global_settings.party_mode)
{
@@ -1150,6 +1405,22 @@ enum playlist_viewer_result playlist_viewer_ex(const char* filename,
goto exit;
}
}
+ else if (global_settings.hotkey_tree == HOTKEY_MULTISELECTION)
+ {
+ /* Make a multiselection */
+ if (!viewer.multiselection_enabled)
+ {
+ viewer.multiselection_enabled = true;
+ viewer.multiselection_offset_track = current_track->index;
+ /* Reset current text scrollings to avoid graphical glitches */
+ gui_synclist_draw(&playlist_lists);
+ }
+ else
+ {
+ /* Will be disabled later bacause of PV_CONTEXT_MODIFIED */
+ update_viewer(&playlist_lists, PV_CONTEXT_MODIFIED);
+ }
+ }
else if (global_settings.hotkey_tree == HOTKEY_PROPERTIES)
{
if (show_track_info(current_track) == PV_CONTEXT_USB)
@@ -1161,17 +1432,18 @@ enum playlist_viewer_result playlist_viewer_ex(const char* filename,
}
else if (global_settings.hotkey_tree == HOTKEY_DELETE)
{
- if (update_viewer(&playlist_lists,
- delete_track(current_track->index,
- viewer.selected_track,
- (current_track->index == viewer.current_playing_track))))
+ if (update_viewer(&playlist_lists, delete_tracks(viewer.selected_track)))
{
ret = PLAYLIST_VIEWER_CANCEL;
exit = true;
}
}
else
- onplay(current_track->name, FILE_ATTR_AUDIO, CONTEXT_STD, true, ONPLAY_NO_CUSTOMACTION);
+ {
+ onplay(current_track->name, FILE_ATTR_AUDIO, CONTEXT_STD, true, ONPLAY_NO_CUSTOMACTION, true);
+ struct add_to_pl_param *choice = onplay_show_playlist_menu_get_current_choice();
+ update_viewer(&playlist_lists, add_tracks_to_current_playlist(viewer.selected_track, choice));
+ }
break;
}
#endif /* HAVE_HOTKEY */
@@ -1284,7 +1556,7 @@ bool search_playlist(void)
backlight_on();
struct playlist_search_data s_data = {.track = &track, .found_indicies = found_indicies};
gui_synclist_init(&playlist_lists, playlist_search_callback_name,
- &s_data, false, 1, NULL);
+ &s_data, false, 1, NULL, true);
gui_synclist_set_title(&playlist_lists, str(LANG_SEARCH_RESULTS), NOICON);
if(global_settings.talk_file)
gui_synclist_set_voice_callback(&playlist_lists,
diff --git a/apps/plugin.c b/apps/plugin.c
index 9b6a446cec..7832fac39b 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -468,6 +468,7 @@ static const struct plugin_api rockbox_api = {
set_current_file,
set_dirfilter,
onplay_show_playlist_menu,
+ onplay_show_playlist_menu_get_choice,
onplay_show_playlist_cat_menu,
browse_id3,
diff --git a/apps/plugin.h b/apps/plugin.h
index 46b13b07fc..c8b1311798 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -395,7 +395,8 @@ struct plugin_api {
void (*gui_synclist_init)(struct gui_synclist * lists,
list_get_name callback_get_item_name, void * data,
bool scroll_all,int selected_size,
- struct viewport parent[NB_SCREENS]);
+ struct viewport parent[NB_SCREENS],
+ bool multiple_selection_force_single_entry_scroll);
void (*gui_synclist_set_nb_items)(struct gui_synclist * lists, int nb_items);
void (*gui_synclist_set_voice_callback)(struct gui_synclist * lists, list_speak_item voice_callback);
void (*gui_synclist_set_icon_callback)(struct gui_synclist * lists,
@@ -539,6 +540,7 @@ struct plugin_api {
void (*set_dirfilter)(int l_dirfilter);
void (*onplay_show_playlist_menu)(const char* path, int attr, void (*playlist_insert_cb));
+ struct add_to_pl_param* (*onplay_show_playlist_menu_get_choice)(void);
void (*onplay_show_playlist_cat_menu)(const char* track_name, int attr,
void (*add_to_pl_cb));
bool (*browse_id3)(struct mp3entry *id3,
@@ -868,7 +870,7 @@ struct plugin_api {
const char *filename, int position, bool queue, bool sync);
int (*playlist_insert_directory)(struct playlist_info* playlist,
const char *dirname, int position, bool queue,
- bool recurse, struct playlist_insert_context *context);
+ bool recurse, struct playlist_insert_context* context_to_use);
int (*playlist_insert_playlist)(struct playlist_info* playlist,
const char *filename, int position, bool queue);
int (*playlist_insert_context_create)(struct playlist_info* playlist,
diff --git a/apps/plugins/calendar.c b/apps/plugins/calendar.c
index d1213367ae..819bddca43 100644
--- a/apps/plugins/calendar.c
+++ b/apps/plugins/calendar.c
@@ -974,7 +974,7 @@ static bool view_events(int selected, struct shown *shown)
bool exit=false;
int button;
- rb->gui_synclist_init(&gui_memos, &get_event_text, shown, false, 1, NULL);
+ rb->gui_synclist_init(&gui_memos, &get_event_text, shown, false, 1, NULL, false);
rb->gui_synclist_set_title(&gui_memos, "Events (" CALENDAR_EVENT_MENU_NAME " : menu)", NOICON);
rb->gui_synclist_set_nb_items(&gui_memos, memos_in_shown_memory);
rb->gui_synclist_select_item(&gui_memos, selected);
diff --git a/apps/plugins/chessbox/chessbox_pgn.c b/apps/plugins/chessbox/chessbox_pgn.c
index d6da369b9e..80fa6c63e7 100644
--- a/apps/plugins/chessbox/chessbox_pgn.c
+++ b/apps/plugins/chessbox/chessbox_pgn.c
@@ -672,7 +672,7 @@ struct pgn_game_node* pgn_show_game_list(struct pgn_game_node* first_game){
}
- rb->gui_synclist_init(&games_list, &get_game_text, first_game, false, 1, NULL);
+ rb->gui_synclist_init(&games_list, &get_game_text, first_game, false, 1, NULL, false);
rb->gui_synclist_set_title(&games_list, rb->str(LANG_CHESSBOX_GAMES), NOICON);
if (rb->global_settings->talk_menu)
rb->gui_synclist_set_voice_callback(&games_list, speak_game_selection);
diff --git a/apps/plugins/keybox.c b/apps/plugins/keybox.c
index 749d76af74..a2e6b44e1a 100644
--- a/apps/plugins/keybox.c
+++ b/apps/plugins/keybox.c
@@ -654,7 +654,7 @@ enum plugin_status plugin_start(const void *parameter)
(void)parameter;
int ret;
- rb->gui_synclist_init(&kb_list, &kb_list_cb, NULL, false, 1, NULL);
+ rb->gui_synclist_init(&kb_list, &kb_list_cb, NULL, false, 1, NULL, false);
rb->gui_synclist_set_title(&kb_list, "Keybox", NOICON);
rb->gui_synclist_set_nb_items(&kb_list, 0);
diff --git a/apps/plugins/keyremap.c b/apps/plugins/keyremap.c
index bb7e965963..0dc5f5f46e 100644
--- a/apps/plugins/keyremap.c
+++ b/apps/plugins/keyremap.c
@@ -2043,7 +2043,7 @@ static void synclist_set(int id, int selected_item, int items, int sel_size)
list_voice_cb(0, menu_id);
rb->gui_synclist_init(&lists,list_get_name_cb,
- menu_id, false, sel_size, NULL);
+ menu_id, false, sel_size, NULL, false);
rb->gui_synclist_set_icon_callback(&lists,NULL);
rb->gui_synclist_set_voice_callback(&lists, list_voice_cb);
diff --git a/apps/plugins/lastfm_scrobbler.c b/apps/plugins/lastfm_scrobbler.c
index 906888c957..2c364973da 100644
--- a/apps/plugins/lastfm_scrobbler.c
+++ b/apps/plugins/lastfm_scrobbler.c
@@ -398,7 +398,7 @@ static void clear_display(void)
if (!lists.title) /* initialize the list, only used to display the title..*/
{
- rb->gui_synclist_init(&lists, NULL, NULL, false,1, NULL);
+ rb->gui_synclist_init(&lists, NULL, NULL, false, 1, NULL, false);
rb->gui_synclist_set_title(&lists, rb->str(LANG_AUDIOSCROBBLER), Icon_Moving);
}
diff --git a/apps/plugins/lastfm_scrobbler_viewer.c b/apps/plugins/lastfm_scrobbler_viewer.c
index c7bf2bab8b..fb488830b7 100644
--- a/apps/plugins/lastfm_scrobbler_viewer.c
+++ b/apps/plugins/lastfm_scrobbler_viewer.c
@@ -936,7 +936,7 @@ static void synclist_set(int selected_item, int items, int sel_size, struct prin
selected_item = 0;
rb->gui_synclist_init(&lists,list_get_name_cb,
- pc_data, false, sel_size, NULL);
+ pc_data, false, sel_size, NULL, false);
rb->gui_synclist_set_icon_callback(&lists, list_icon_cb);
rb->gui_synclist_set_voice_callback(&lists, list_voice_cb);
diff --git a/apps/plugins/lrcplayer.c b/apps/plugins/lrcplayer.c
index 9102f03264..67a422917d 100644
--- a/apps/plugins/lrcplayer.c
+++ b/apps/plugins/lrcplayer.c
@@ -2051,7 +2051,7 @@ static int timetag_editor(void)
selected = idx;
}
- rb->gui_synclist_init(&gui_editor, &get_lrc_timeline, NULL, false, 1, NULL);
+ rb->gui_synclist_init(&gui_editor, &get_lrc_timeline, NULL, false, 1, NULL, false);
rb->gui_synclist_set_nb_items(&gui_editor, current.nlrcline);
rb->gui_synclist_set_icon_callback(&gui_editor, get_icon);
rb->gui_synclist_set_title(&gui_editor, "Timetag Editor",
diff --git a/apps/plugins/main_menu_config.c b/apps/plugins/main_menu_config.c
index 94e7740fbe..0bc461ff06 100644
--- a/apps/plugins/main_menu_config.c
+++ b/apps/plugins/main_menu_config.c
@@ -183,7 +183,7 @@ enum plugin_status plugin_start(const void* parameter)
menu_table = rb->root_menu_get_options(&menu_item_count);
load_from_cfg();
- rb->gui_synclist_init(&list, menu_get_name, NULL, false, 1, NULL);
+ rb->gui_synclist_init(&list, menu_get_name, NULL, false, 1, NULL, false);
if (rb->global_settings->talk_menu)
rb->gui_synclist_set_voice_callback(&list, menu_speak_item);
rb->gui_synclist_set_icon_callback(&list, menu_get_icon);
diff --git a/apps/plugins/open_plugins.c b/apps/plugins/open_plugins.c
index e90d2d95de..6373331572 100644
--- a/apps/plugins/open_plugins.c
+++ b/apps/plugins/open_plugins.c
@@ -650,7 +650,7 @@ static void synclist_set(char* menu_id, int selection, int items, int sel_size)
selection = 0;
rb->gui_synclist_init(&lists,list_get_name_cb,
- menu_id, false, sel_size, NULL);
+ menu_id, false, sel_size, NULL, false);
rb->gui_synclist_set_voice_callback(&lists, list_voice_cb);
rb->gui_synclist_set_nb_items(&lists,items);
diff --git a/apps/plugins/playing_time.c b/apps/plugins/playing_time.c
index 6504988026..f2a8bb7904 100644
--- a/apps/plugins/playing_time.c
+++ b/apps/plugins/playing_time.c
@@ -317,7 +317,7 @@ static int pt_speak_info(int selected_item, void * data)
static bool pt_display_stats(struct playing_time_info *pti)
{
struct gui_synclist pt_lists;
- rb->gui_synclist_init(&pt_lists, &pt_get_info, pti, true, 2, NULL);
+ rb->gui_synclist_init(&pt_lists, &pt_get_info, pti, true, 2, NULL, false);
if (rb->global_settings->talk_menu)
rb->gui_synclist_set_voice_callback(&pt_lists, pt_speak_info);
rb->gui_synclist_set_nb_items(&pt_lists, pti->remaining_only ? 2 : 8*2);
@@ -367,7 +367,7 @@ static int pt_options_speak(int selected_item, void * data)
static int pt_options(struct playing_time_info *pti)
{
struct gui_synclist pt_options;
- rb->gui_synclist_init(&pt_options, &pt_options_name, NULL, true, 1, NULL);
+ rb->gui_synclist_init(&pt_options, &pt_options_name, NULL, true, 1, NULL, false);
if (rb->global_settings->talk_menu)
rb->gui_synclist_set_voice_callback(&pt_options, pt_options_speak);
rb->gui_synclist_set_nb_items(&pt_options, *pti->single_mode_tag ? 3 : 2);
diff --git a/apps/plugins/properties.c b/apps/plugins/properties.c
index 316b33292a..fc1dc070fc 100644
--- a/apps/plugins/properties.c
+++ b/apps/plugins/properties.c
@@ -162,7 +162,7 @@ static void setup_properties_list(struct dir_stats *stats)
else
nb_props = NUM_DIR_PROPERTIES;
- rb->gui_synclist_init(&properties_lists, &get_props, stats, false, 2, NULL);
+ rb->gui_synclist_init(&properties_lists, &get_props, stats, false, 2, NULL, false);
rb->gui_synclist_set_title(&properties_lists,
rb->str(props_type == PROPS_DIR ?
LANG_PROPERTIES_DIRECTORY_PROPERTIES :
diff --git a/apps/plugins/puzzles/rockbox.c b/apps/plugins/puzzles/rockbox.c
index 172207b63b..5f22344253 100644
--- a/apps/plugins/puzzles/rockbox.c
+++ b/apps/plugins/puzzles/rockbox.c
@@ -2138,7 +2138,7 @@ static int list_choose(const char *list_str, const char *title, int sel)
struct gui_synclist list;
- rb->gui_synclist_init(&list, &config_choices_formatter, (void*)list_str, false, 1, NULL);
+ rb->gui_synclist_init(&list, &config_choices_formatter, (void*)list_str, false, 1, NULL, false);
rb->gui_synclist_set_nb_items(&list, n);
rb->gui_synclist_select_item(&list, sel);
@@ -2350,7 +2350,7 @@ static bool config_menu_core(int which)
/* display a list */
struct gui_synclist list;
- rb->gui_synclist_init(&list, &config_formatter, config, false, 1, NULL);
+ rb->gui_synclist_init(&list, &config_formatter, config, false, 1, NULL, false);
rb->gui_synclist_set_nb_items(&list, n);
rb->gui_synclist_select_item(&list, 0);
@@ -2444,7 +2444,7 @@ static int do_preset_menu(struct preset_menu *menu, char *title, int selected)
/* display a list */
struct gui_synclist list;
- rb->gui_synclist_init(&list, &preset_formatter, menu, false, 1, NULL);
+ rb->gui_synclist_init(&list, &preset_formatter, menu, false, 1, NULL, false);
rb->gui_synclist_set_nb_items(&list, menu->n_entries);
rb->gui_synclist_select_item(&list, selected);
diff --git a/apps/plugins/random_folder_advance_config.c b/apps/plugins/random_folder_advance_config.c
index 7f932aabd8..31fb147670 100644
--- a/apps/plugins/random_folder_advance_config.c
+++ b/apps/plugins/random_folder_advance_config.c
@@ -370,7 +370,7 @@ static int edit(struct rfa_scan *scan)
}
scan->dirty = false;
rb->gui_synclist_init(&lists, list_get_name_cb, (void *) scan,
- false, 1, NULL);
+ false, 1, NULL, false);
rb->gui_synclist_set_nb_items(&lists, scan->dirs->count);
rb->gui_synclist_select_item(&lists, 0);
set_title(&lists, false, scan->dirs->count);
diff --git a/apps/plugins/rb_info.c b/apps/plugins/rb_info.c
index 31b06d6f75..39d158d5f6 100644
--- a/apps/plugins/rb_info.c
+++ b/apps/plugins/rb_info.c
@@ -508,7 +508,7 @@ static void synclist_set(char* menu_id, int selected_item, int items, int sel_si
list_voice_cb(0, menu_id);
rb->gui_synclist_init(&lists,list_get_name_cb,
- menu_id, false, sel_size, NULL);
+ menu_id, false, sel_size, NULL, false);
if (menu_id == MENU_ID(M_TESTPUT))
{
testput_cols = printcell_set_columns(&lists, NULL,
diff --git a/apps/plugins/shopper.c b/apps/plugins/shopper.c
index 25a484a31e..f584aa7807 100644
--- a/apps/plugins/shopper.c
+++ b/apps/plugins/shopper.c
@@ -302,7 +302,7 @@ enum plugin_status plugin_start(const void* parameter)
rb->cpu_boost(0);
#endif
/* now dump it in the list */
- rb->gui_synclist_init(&lists,list_get_name_cb,0, false, 1, NULL);
+ rb->gui_synclist_init(&lists,list_get_name_cb,0, false, 1, NULL, false);
rb->gui_synclist_set_icon_callback(&lists, list_get_icon_cb);
create_view(&lists);
rb->gui_synclist_set_nb_items(&lists,view_item_count);
diff --git a/apps/plugins/shortcuts/shortcuts_view.c b/apps/plugins/shortcuts/shortcuts_view.c
index fadf111c6d..08fd5b569e 100644
--- a/apps/plugins/shortcuts/shortcuts_view.c
+++ b/apps/plugins/shortcuts/shortcuts_view.c
@@ -105,7 +105,7 @@ int list_sc(int *selected_item)
/* Setup the GUI list object, draw it to the screen,
* and then handle the user input to it */
- rb->gui_synclist_init(&gui_sc, &build_sc_list, &sc_file, false, 1, NULL);
+ rb->gui_synclist_init(&gui_sc, &build_sc_list, &sc_file, false, 1, NULL, false);
rb->gui_synclist_set_title(&gui_sc,
(user_file?"Shortcuts (sealed)":"Shortcuts (editable)"), NOICON);
rb->gui_synclist_set_nb_items(&gui_sc, sc_file.entry_cnt);
diff --git a/apps/plugins/tagcache/tagcache.c b/apps/plugins/tagcache/tagcache.c
index c5ee21e870..26925c0274 100644
--- a/apps/plugins/tagcache/tagcache.c
+++ b/apps/plugins/tagcache/tagcache.c
@@ -760,7 +760,7 @@ static int commit_menu(void)
int button,i;
int selection, ret = 0;
- rb->gui_synclist_init(&lists,list_get_name_cb,0, false, 1, NULL);
+ rb->gui_synclist_init(&lists,list_get_name_cb,0, false, 1, NULL, false);
rb->gui_synclist_set_nb_items(&lists, 9);
rb->gui_synclist_select_item(&lists, 0);
diff --git a/apps/plugins/text_editor.c b/apps/plugins/text_editor.c
index b4f99447ae..73f4d4daa6 100644
--- a/apps/plugins/text_editor.c
+++ b/apps/plugins/text_editor.c
@@ -211,7 +211,7 @@ static bool save_changes(int overwrite)
static void setup_lists(struct gui_synclist *lists, int sel)
{
- rb->gui_synclist_init(lists,list_get_name_cb,0, false, 1, NULL);
+ rb->gui_synclist_init(lists,list_get_name_cb,0, false, 1, NULL, false);
rb->gui_synclist_set_nb_items(lists,line_count);
rb->gui_synclist_select_item(lists, sel);
rb->gui_synclist_draw(lists);
diff --git a/apps/plugins/zxbox/tapefile.c b/apps/plugins/zxbox/tapefile.c
index 054cae1aba..1830c77d5a 100644
--- a/apps/plugins/zxbox/tapefile.c
+++ b/apps/plugins/zxbox/tapefile.c
@@ -66,18 +66,18 @@ static dbyte callctr, callbeg;
#define ST_DIRE 2
#define ST_MISC 3
-#define PL_NONE 0
-#define PL_PAUSE 1
-#define PL_LEADER 2
-#define PL_DATA 3
-#define PL_END 4
-#define PL_PSEQ 5
-#define PL_DIRE 6
+#define TFPL_NONE 0
+#define TFPL_PAUSE 1
+#define TFPL_LEADER 2
+#define TFPL_DATA 3
+#define TFPL_END 4
+#define TFPL_PSEQ 5
+#define TFPL_DIRE 6
#define IMP_1MS 3500
static dbyte lead_pause;
-static int playstate = PL_NONE;
+static int playstate = TFPL_NONE;
static int currlev;
#define DEF_LEAD_PAUSE 2000
@@ -339,7 +339,7 @@ static int end_seg(struct seginfo *csp)
segi++;
isbeg();
}
- playstate = PL_NONE;
+ playstate = TFPL_NONE;
return 1;
}
@@ -717,7 +717,7 @@ byte *tf_get_block(int i)
int next_byte(void)
{
- playstate = PL_NONE;
+ playstate = TFPL_NONE;
return next_data();
}
@@ -737,7 +737,7 @@ int next_imps(unsigned short *impbuf, int buflen, long timelen)
while(impbuf < impbufend - 1 && timelen > 0) {
switch(playstate) {
- case PL_PAUSE:
+ case TFPL_PAUSE:
if(currlev && lead_pause) {
PULSE(IMP_1MS);
lead_pause --;
@@ -758,15 +758,15 @@ int next_imps(unsigned short *impbuf, int buflen, long timelen)
tf_cseg.ptr != tf_cseg.len) finished = 0;
switch (tf_cseg.type) {
- case ST_NORM: playstate = PL_LEADER; break;
- case ST_DIRE: playstate = PL_DIRE; dirpulse = 0; break;
- case ST_PSEQ: playstate = PL_PSEQ; break;
- default: playstate = PL_NONE;
+ case ST_NORM: playstate = TFPL_LEADER; break;
+ case ST_DIRE: playstate = TFPL_DIRE; dirpulse = 0; break;
+ case ST_PSEQ: playstate = TFPL_PSEQ; break;
+ default: playstate = TFPL_NONE;
}
}
break;
- case PL_LEADER:
+ case TFPL_LEADER:
if(tf_cseg.num >= 2) {
DPULSE(tf_cseg.pulse, tf_cseg.pulse);
tf_cseg.num -= 2;
@@ -780,15 +780,15 @@ int next_imps(unsigned short *impbuf, int buflen, long timelen)
if(tf_cseg.sync1p || tf_cseg.sync2p)
DPULSE(tf_cseg.sync1p, tf_cseg.sync2p);
bitrem = 0;
- playstate = PL_DATA;
+ playstate = TFPL_DATA;
}
break;
- case PL_DATA:
+ case TFPL_DATA:
if(!bitrem) {
toput = next_data();
if(toput < 0) {
- playstate = PL_END;
+ playstate = TFPL_END;
break;
}
if(tf_cseg.ptr != tf_cseg.len) {
@@ -822,14 +822,14 @@ int next_imps(unsigned short *impbuf, int buflen, long timelen)
bitrem--, toput <<= 1;
break;
- case PL_PSEQ:
+ case TFPL_PSEQ:
{
int b1, b2;
dbyte pulse1, pulse2;
b1 = next_data();
b2 = next_data();
if(b1 < 0 || b2 < 0) {
- playstate = PL_END;
+ playstate = TFPL_END;
break;
}
pulse1 = b1 + (b2 << 8);
@@ -838,7 +838,7 @@ int next_imps(unsigned short *impbuf, int buflen, long timelen)
b2 = next_data();
if(b1 < 0 || b2 < 0) {
PULSE(pulse1);
- playstate = PL_END;
+ playstate = TFPL_END;
break;
}
pulse2 = b1 + (b2 << 8);
@@ -846,12 +846,12 @@ int next_imps(unsigned short *impbuf, int buflen, long timelen)
}
break;
- case PL_DIRE:
+ case TFPL_DIRE:
for(;;) {
if(!bitrem) {
toput = next_data();
if(toput < 0) {
- playstate = PL_END;
+ playstate = TFPL_END;
DPULSE(dirpulse, 0);
break;
}
@@ -877,17 +877,17 @@ int next_imps(unsigned short *impbuf, int buflen, long timelen)
}
break;
- case PL_END:
+ case TFPL_END:
if(tf_cseg.pause) {
PULSE(IMP_1MS);
tf_cseg.pause--;
if(currlev) PULSE(0);
finished = 1;
}
- playstate = PL_NONE;
+ playstate = TFPL_NONE;
break;
- case PL_NONE:
+ case TFPL_NONE:
default:
return PTRDIFF(impbuf, impbufstart);
}
@@ -916,10 +916,10 @@ int next_segment(void)
}
if(tf_cseg.segtype >= SEG_DATA) {
- playstate = PL_PAUSE;
+ playstate = TFPL_PAUSE;
if(lead_pause) finished = 1;
}
- else playstate = PL_NONE;
+ else playstate = TFPL_NONE;
if(tf_cseg.segtype <= SEG_STOP && !finished) {
endnext = 1;
@@ -930,7 +930,7 @@ int next_segment(void)
tf_cseg.pause = 1;
tf_cseg.segtype = SEG_VIRTUAL;
- playstate = PL_END;
+ playstate = TFPL_END;
}
return tf_cseg.segtype;
@@ -954,7 +954,7 @@ unsigned segment_pos(void)
void close_tapefile(void)
{
if(tapefd != -1) {
- playstate = PL_NONE;
+ playstate = TFPL_NONE;
rb->close(tapefd);
tapefd = -1;
}
diff --git a/apps/radio/presets.c b/apps/radio/presets.c
index 9ac4b75ee3..2d97f7ba0b 100644
--- a/apps/radio/presets.c
+++ b/apps/radio/presets.c
@@ -503,7 +503,7 @@ int handle_radio_presets(void)
if(presets_loaded == false)
return result;
- gui_synclist_init(&lists, presets_get_name, NULL, false, 1, NULL);
+ gui_synclist_init(&lists, presets_get_name, NULL, false, 1, NULL, false);
gui_synclist_set_title(&lists, str(LANG_PRESET), NOICON);
if(global_settings.talk_file)
gui_synclist_set_voice_callback(&lists, presets_speak_name);
diff --git a/apps/recorder/recording.c b/apps/recorder/recording.c
index 253142bd97..0a339e70f3 100644
--- a/apps/recorder/recording.c
+++ b/apps/recorder/recording.c
@@ -1087,7 +1087,7 @@ bool recording_screen(bool no_source)
}
/* init the bottom list */
- gui_synclist_init(&lists, reclist_get_name, NULL, false, 1, vp_list);
+ gui_synclist_init(&lists, reclist_get_name, NULL, false, 1, vp_list, false);
gui_synclist_set_title(&lists, NULL, Icon_NOICON);
send_event(GUI_EVENT_ACTIONUPDATE, (void*)1); /* force a redraw */
diff --git a/apps/screens.c b/apps/screens.c
index 5e19db7edd..2487bc0da5 100644
--- a/apps/screens.c
+++ b/apps/screens.c
@@ -807,7 +807,7 @@ bool browse_id3_ex(struct mp3entry *id3, struct playlist_info *playlist,
info.info_id[info.count++] = i;
}
- gui_synclist_init(&id3_lists, &id3_get_name_cb, &info, true, 2, NULL);
+ gui_synclist_init(&id3_lists, &id3_get_name_cb, &info, true, 2, NULL, false);
if(global_settings.talk_menu)
gui_synclist_set_voice_callback(&id3_lists, id3_speak_item);
gui_synclist_set_nb_items(&id3_lists, info.count*2);
diff --git a/apps/settings_list.c b/apps/settings_list.c
index 4ac206b5be..f7e5494ec2 100644
--- a/apps/settings_list.c
+++ b/apps/settings_list.c
@@ -2367,21 +2367,21 @@ const struct settings_list settings[] = {
TABLE_SETTING(0, hotkey_tree,
LANG_HOTKEY_FILE_BROWSER, HOTKEY_OFF, "hotkey tree",
#ifdef HAVE_TAGCACHE
- "off,properties,pictureflow,open with,delete,insert,insert shuffled",
+ "off,properties,pictureflow,open with,delete,insert,insert shuffled,multiselection",
#else
- "off,properties,open with,delete,insert,insert shuffled",
+ "off,properties,open with,delete,insert,insert shuffled,multiselection",
#endif
UNIT_INT, hotkey_formatter, hotkey_getlang, NULL,
#ifdef HAVE_TAGCACHE
- 7,
+ 8,
#else
- 6,
+ 7,
#endif
HOTKEY_OFF,HOTKEY_PROPERTIES,
#ifdef HAVE_TAGCACHE
HOTKEY_PICTUREFLOW,
#endif
- HOTKEY_OPEN_WITH, HOTKEY_DELETE, HOTKEY_INSERT, HOTKEY_INSERT_SHUFFLED),
+ HOTKEY_OPEN_WITH, HOTKEY_DELETE, HOTKEY_INSERT, HOTKEY_INSERT_SHUFFLED, HOTKEY_MULTISELECTION),
#endif /* HAVE_HOTKEY */
INT_SETTING(F_TIME_SETTING, resume_rewind, LANG_RESUME_REWIND, 0,
diff --git a/apps/tagtree.c b/apps/tagtree.c
index 67ff646205..b10b9398f8 100644
--- a/apps/tagtree.c
+++ b/apps/tagtree.c
@@ -2415,7 +2415,7 @@ static bool* fill_random_playlist_indexes(bool *bool_array, size_t arr_sz,
static bool insert_all_playlist(struct tree_context *c,
const char* playlist, bool new_playlist,
- int position, bool queue)
+ int position, bool queue, struct playlist_insert_context* context_to_use)
{
struct tagcache_search tcs;
int n;
@@ -2438,7 +2438,7 @@ static bool insert_all_playlist(struct tree_context *c,
if (playlist == NULL)
{
- if (playlist_insert_context_create(NULL, &context, position, queue, false) < 0)
+ if (context_to_use == NULL && playlist_insert_context_create(NULL, &context, position, queue, false) < 0)
{
tagcache_search_finish(&tcs);
cpu_boost(false);
@@ -2554,7 +2554,7 @@ static bool insert_all_playlist(struct tree_context *c,
}
}
- if (playlist_insert_context_add(&context, buf) < 0) {
+ if (playlist_insert_context_add(context_to_use == NULL ? &context : context_to_use, buf) < 0) {
logf("playlist_insert_track failed");
exit_loop_now = true;
break;
@@ -2572,7 +2572,10 @@ static bool insert_all_playlist(struct tree_context *c,
}
if (playlist == NULL)
- playlist_insert_context_release(&context);
+ {
+ if (context_to_use == NULL)
+ playlist_insert_context_release(&context);
+ }
else
close(fd);
@@ -2604,11 +2607,15 @@ static void reset_tc_to_prev(int dirlevel, int selected_item)
tagtree_load(tc);
}
-static bool tagtree_insert_selection(int position, bool queue,
- const char* playlist, bool new_playlist)
+bool tagtree_insert_selection(int position, bool queue,
+ const char* playlist, bool new_playlist,
+ struct playlist_insert_context* context_to_use,
+ int selected_size)
{
char buf[MAX_PATH];
int dirlevel = tc->dirlevel;
+ /* goto_allsubentries will modify tc->selected_item so we need to store a copy here to restore
+ the position at the end of the function with reset_tc_to_prev */
int selected_item = tc->selected_item;
int newtable;
int ret;
@@ -2621,19 +2628,47 @@ static bool tagtree_insert_selection(int position, bool queue,
#endif
, 0, 0, 0);
- newtable = tagtree_get_entry(tc, tc->selected_item)->newtable;
+ newtable = tagtree_get_entry(tc, selected_item)->newtable;
if (newtable == TABLE_PLAYTRACK) /* Insert a single track? */
{
- if (tagtree_get_filename(tc, buf, sizeof buf) < 0)
+ struct tagcache_search tcs;
+ if (!tagcache_search(&tcs, tag_filename))
return false;
- if (!playlist)
- playlist_insert_track(NULL, buf, position, queue, true);
- else
- catalog_insert_into(playlist, new_playlist, buf, FILE_ATTR_AUDIO);
-
- return true;
+ for (int i = 0; i < selected_size; i++)
+ {
+ if (i > 0 && action_userabort(TIMEOUT_NOBLOCK))
+ {
+ tagcache_search_finish(&tcs);
+ return false;
+ }
+ int extraseek = tagtree_get_entry(tc, selected_item + i)->extraseek;
+
+ if (!tagcache_retrieve(&tcs, extraseek, tcs.type, buf, sizeof buf))
+ {
+ tagcache_search_finish(&tcs);
+ return false;
+ }
+
+ if (context_to_use != NULL)
+ {
+ if (playlist_insert_context_add(context_to_use, buf) < 0)
+ {
+ tagcache_search_finish(&tcs);
+ return false;
+ }
+ }
+ else if (playlist != NULL)
+ {
+ catalog_insert_into(playlist, new_playlist, buf, FILE_ATTR_AUDIO, NULL);
+ new_playlist = false;
+ }
+ else
+ playlist_insert_track(NULL, buf, position, queue, true);
+ }
+ tagcache_search_finish(&tcs);
+ return (selected_size == 1);
}
ret = goto_allsubentries(newtable);
@@ -2641,7 +2676,7 @@ static bool tagtree_insert_selection(int position, bool queue,
{
if (tc->filesindir <= 0)
splash(HZ, ID2P(LANG_END_PLAYLIST));
- else if (!insert_all_playlist(tc, playlist, new_playlist, position, queue))
+ else if (!insert_all_playlist(tc, playlist, new_playlist, position, queue, context_to_use))
splash(HZ*2, ID2P(LANG_FAILED));
}
@@ -2723,7 +2758,7 @@ bool tagtree_get_subentry_filename(char *buf, size_t bufsize)
bool tagtree_current_playlist_insert(int position, bool queue)
{
- return tagtree_insert_selection(position, queue, NULL, false);
+ return tagtree_insert_selection(position, queue, NULL, false, NULL, 1);
}
@@ -2731,7 +2766,7 @@ int tagtree_add_to_playlist(const char* playlist, bool new_playlist)
{
if (!new_playlist)
tagtree_load(tc); /* because display_playlists was called */
- return tagtree_insert_selection(0, false, playlist, new_playlist) ? 0 : -1;
+ return tagtree_insert_selection(0, false, playlist, new_playlist, NULL, 1) ? 0 : -1;
}
static int tagtree_play_folder(struct tree_context* c)
@@ -2745,7 +2780,7 @@ static int tagtree_play_folder(struct tree_context* c)
return -1;
}
- if (!insert_all_playlist(c, NULL, false, PLAYLIST_INSERT_LAST, false))
+ if (!insert_all_playlist(c, NULL, false, PLAYLIST_INSERT_LAST, false, NULL))
return -2;
int n = c->filesindir;
diff --git a/apps/tagtree.h b/apps/tagtree.h
index a57a5c2f80..80dd71d58d 100644
--- a/apps/tagtree.h
+++ b/apps/tagtree.h
@@ -25,6 +25,7 @@
#include "config.h"
#include "tagcache.h"
#include "tree.h"
+#include "playlist.h"
#define TAGNAVI_VERSION "#! rockbox/tagbrowser/2.0"
#define TAGMENU_MAX_ITEMS 64
@@ -48,6 +49,7 @@ int tagtree_get_filename(struct tree_context* c, char *buf, int buflen);
int tagtree_get_custom_action(struct tree_context* c);
bool tagtree_get_subentry_filename(char *buf, size_t bufsize);
bool tagtree_subentries_do_action(bool (*action_cb)(const char *file_name));
+bool tagtree_insert_selection(int position, bool queue, const char* playlist, bool new_playlist, struct playlist_insert_context* context_to_use, int selected_size);
#endif
#endif
diff --git a/apps/tree.c b/apps/tree.c
index 5df204bbf2..f63c34f047 100644
--- a/apps/tree.c
+++ b/apps/tree.c
@@ -394,6 +394,28 @@ static int tree_get_file_position(char * filename)
return(ret);
}
+static void set_multiselection_selected_size(void)
+{
+ if (tc.multiselection_enabled)
+ {
+ int offsetdiff = tc.selected_item - tc.multiselection_offset_item;
+ if (offsetdiff < 0)
+ {
+ tc.multiselection_selected_size = (- offsetdiff) + 1;
+ }
+ else if (offsetdiff == 0)
+ {
+ tc.multiselection_selected_size = 1;
+ }
+ else
+ {
+ tc.multiselection_selected_size = - (offsetdiff);
+ }
+ }
+ else
+ tc.multiselection_selected_size = 1;
+}
+
/*
* Called when a new dir is loaded (for example when returning from other apps ...)
* also completely redraws the tree
@@ -461,7 +483,7 @@ static int update_dir(void)
}
}
- gui_synclist_init(list, &tree_get_filename, &tc, false, 1, NULL);
+ gui_synclist_init(list, &tree_get_filename, &tc, false, 1, NULL, true);
#ifdef HAVE_TAGCACHE
if (id3db)
@@ -526,8 +548,10 @@ static int update_dir(void)
gui_synclist_set_color_callback(list, &tree_get_filecolor);
#endif
if( tc.selected_item >= tc.filesindir)
- tc.selected_item=tc.filesindir-1;
+ tc.selected_item = tc.filesindir-1;
+ set_multiselection_selected_size();
+ list->selected_size = tc.multiselection_selected_size;
gui_synclist_select_item(list, tc.selected_item);
gui_synclist_draw(list);
gui_synclist_speak_item(list);
@@ -702,6 +726,79 @@ static void set_current_file_ex(const char *path, const char *filename)
}
}
+static bool tree_insert_selection_is_dir_recurse(void)
+{
+ bool recurse = (global_settings.recursive_dir_insert == RECURSE_ON);
+ if (global_settings.recursive_dir_insert == RECURSE_ASK)
+ {
+
+ const char *lines[] = {
+ ID2P(LANG_RECURSE_DIRECTORY_QUESTION),
+ ID2P(LANG_ALL_FOLDERS_FROM_CURRENT_SELECTION)
+ };
+ const struct text_message message={lines, 2};
+ /* Ask if user wants to recurse directory */
+ recurse = (gui_syncyesno_run(&message, NULL, NULL)==YESNO_YES);
+ }
+ return recurse == RECURSE_ON;
+}
+
+static bool tree_insert_selection(int position, bool queue,
+ const char* playlist, bool new_playlist,
+ struct playlist_insert_context* context_to_use, char* buf, size_t bufsize, char* currdir,
+ bool* dir_recurse_asked, bool* dir_recurse, int selected_size)
+{
+ bool result = false;
+ #ifdef HAVE_TAGCACHE
+ bool id3db = *tc.dirfilter == SHOW_ID3DB;
+ if (id3db)
+ {
+ result = tagtree_insert_selection(position, queue, playlist,
+ new_playlist, context_to_use, selected_size);
+ }
+ else
+ #else
+ (void) selected_size;
+ #endif
+ {
+ struct entry *entry = get_valid_entry(__func__, &tc, tc.selected_item);
+ ft_assemble_path(buf, bufsize, currdir, entry->name);
+ if ((entry->attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO)
+ {
+ if (context_to_use == NULL)
+ result = catalog_insert_into(playlist, new_playlist, buf, FILE_ATTR_AUDIO, NULL) >= 0;
+ else
+ result = playlist_insert_context_add(context_to_use, buf) >= 0;
+ }
+ else if ((entry->attr & FILE_ATTR_MASK) == FILE_ATTR_M3U)
+ {
+ if (context_to_use == NULL)
+ result = catalog_insert_into(playlist, new_playlist, buf, FILE_ATTR_M3U, NULL) >= 0;
+ else
+ result = playlist_entries_iterate(buf, context_to_use, NULL);
+ }
+ else if (entry->attr & ATTR_DIRECTORY)
+ {
+ if (!(*dir_recurse_asked))
+ {
+ /* We ask the user only once for all the selection */
+ *dir_recurse = tree_insert_selection_is_dir_recurse();
+ *dir_recurse_asked = true;
+ }
+ if (context_to_use == NULL)
+ result = catalog_insert_into(playlist, new_playlist, buf, ATTR_DIRECTORY, dir_recurse) >= 0;
+ else
+ result = playlist_insert_directory(NULL, buf, position, queue, *dir_recurse, context_to_use) >= 0;
+ if (ft_load(&tc, currdir) < 0)
+ {
+ /* Reload current dir */
+ result = -1;
+ }
+ }
+ }
+ return result;
+}
+
/* Selects a file and update tree context properly */
void set_current_file(const char *path)
{
@@ -711,6 +808,7 @@ void set_current_file(const char *path)
static int exit_to_new_screen(int screen)
{
+ tc.multiselection_enabled = false;
gui_synclist_scroll_stop(&tree_lists);
return screen;
}
@@ -766,7 +864,23 @@ static int dirbrowse(void)
list_do_action_timeout(&tree_lists, HZ/2));
oldbutton = button;
gui_synclist_do_button(&tree_lists, &button);
- tc.selected_item = gui_synclist_get_sel_pos(&tree_lists);
+ struct gui_synclist * const list = &tree_lists;
+ bool list_redraw_needed = false;
+ if (tc.selected_item != gui_synclist_get_sel_pos(list))
+ {
+ tc.selected_item = gui_synclist_get_sel_pos(list);
+ list_redraw_needed = true;
+ }
+ set_multiselection_selected_size();
+ if (list->selected_size != tc.multiselection_selected_size)
+ {
+ list->selected_size = tc.multiselection_selected_size;
+ list_redraw_needed = true;
+ }
+ if (list_redraw_needed)
+ /* it's important to refresh conditionally only when something changed,
+ or all texts scrolling will be broken */
+ gui_synclist_draw(list);
int customaction = ONPLAY_NO_CUSTOMACTION;
bool do_restore_display = true;
#ifdef HAVE_TAGCACHE
@@ -781,11 +895,18 @@ static int dirbrowse(void)
}
}
#endif
+ if (button == ACTION_STD_OK && tc.multiselection_selected_size != 1)
+ {
+ button = ACTION_STD_CONTEXT;
+ }
switch ( button ) {
case ACTION_STD_OK:
/* nothing to do if no files to display */
if ( numentries == 0 )
break;
+ if (tc.multiselection_enabled && tc.multiselection_selected_size != 1)
+ break;
+ tc.multiselection_enabled = false;
if (tc.browse->flags & BROWSE_SELECTONLY)
{
struct entry *entry =
@@ -821,6 +942,12 @@ static int dirbrowse(void)
break;
case ACTION_STD_CANCEL:
+ if (tc.multiselection_enabled)
+ {
+ tc.multiselection_enabled = false;
+ restore = do_restore_display;
+ break;
+ }
exit_to_new_screen(0);
if (*tc.dirfilter > NUM_FILTER_MODES && tc.dirlevel < 1) {
exit_func = true;
@@ -912,12 +1039,13 @@ static int dirbrowse(void)
bool hotkey = button == ACTION_TREE_HOTKEY;
int onplay_result;
int attr = 0;
+ size_t bufsize;
if (tc.browse->flags & BROWSE_NO_CONTEXT_MENU)
break;
if(!numentries)
- onplay_result = onplay(NULL, 0, curr_context, hotkey, customaction);
+ onplay_result = onplay(NULL, 0, curr_context, hotkey, customaction, false);
else {
#ifdef HAVE_TAGCACHE
if (id3db)
@@ -959,15 +1087,101 @@ static int dirbrowse(void)
else
#endif
{
- struct entry *entry =
- get_valid_entry(__func__, &tc, tc.selected_item);
-
+ struct entry *entry = get_valid_entry(__func__, &tc, tc.selected_item);
attr = entry->attr;
-
- ft_assemble_path(buf, sizeof(buf), currdir, entry->name);
-
+ bufsize = sizeof(buf);
+ ft_assemble_path(buf, bufsize, currdir, entry->name);
+ }
+ if (tc.multiselection_selected_size != 1)
+ attr |= FILE_ATTR_MULTISELECTION;
+ onplay_result = onplay(buf, attr, curr_context, hotkey, customaction, true);
+ struct add_to_pl_param *choice = onplay_show_playlist_menu_get_current_choice();
+ char* cat_menu_choice = onplay_show_playlist_cat_menu_get_current_choice();
+ int initial_selected_item = tc.selected_item;
+ int selected_item = tc.selected_item;
+ int selected_size = tc.multiselection_selected_size;
+ if (selected_size < 0)
+ {
+ selected_item -= (- selected_size);
+ selected_size = ((- selected_size) + 1);
+ }
+ struct playlist_insert_context pl_context;
+ bool new_playlist;
+ bool dir_recurse_asked = false;
+ bool dir_recurse = false;
+ if (choice != NULL)
+ {
+ bool queue = (choice->flags & PL_QUEUE) == PL_QUEUE;
+ new_playlist = (choice->flags & PL_REPLACE) == PL_REPLACE;
+ if (new_playlist && global_settings.keep_current_track_on_replace_playlist)
+ {
+ if (audio_status() & AUDIO_STATUS_PLAY)
+ {
+ playlist_remove_all_tracks(NULL);
+ new_playlist = false;
+ }
+ }
+ if (new_playlist)
+ {
+ playlist_create(NULL, NULL);
+ }
+ if (playlist_insert_context_create(NULL, &pl_context, choice->position, queue, true) >= 0)
+ {
+ for (int i = 0; i < selected_size; i++)
+ {
+ if (i > 0 && action_userabort(TIMEOUT_NOBLOCK))
+ break;
+ tc.selected_item = selected_item + i;
+ if (!tree_insert_selection(choice->position, queue, NULL,
+ new_playlist, &pl_context, buf, bufsize, currdir,
+ &dir_recurse_asked, &dir_recurse, selected_size))
+ {
+ break;
+ }
+ }
+ tc.selected_item = initial_selected_item;
+ }
+ playlist_insert_context_release(&pl_context);
+ if (new_playlist && (playlist_amount() > 0))
+ {
+ if (global_settings.playlist_shuffle)
+ playlist_shuffle(current_tick, -1);
+ playlist_start(0, 0, 0);
+ onplay_result = ONPLAY_START_PLAY;
+ }
+ tc.multiselection_enabled = false;
+ }
+ else if (cat_menu_choice != NULL)
+ {
+ new_playlist = onplay_show_playlist_cat_menu_current_choice_is_new_playlist();
+ #ifdef HAVE_TAGCACHE
+ if (id3db)
+ {
+ if (!new_playlist)
+ tagtree_load(&tc); /* because display_playlists was called */
+ }
+ else
+ #endif
+ {
+ if (!new_playlist)
+ ft_load(&tc, currdir); /* because display_playlists was called */
+ }
+ for (int i = 0; i < selected_size; i++)
+ {
+ if (i > 0 && action_userabort(TIMEOUT_NOBLOCK))
+ break;
+ tc.selected_item = selected_item + i;
+ if (!tree_insert_selection(PLAYLIST_INSERT_LAST, false, cat_menu_choice,
+ new_playlist, NULL, buf, bufsize, currdir,
+ &dir_recurse_asked, &dir_recurse, selected_size))
+ {
+ break;
+ }
+ new_playlist = false;
+ }
+ tc.selected_item = initial_selected_item;
+ tc.multiselection_enabled = false;
}
- onplay_result = onplay(buf, attr, curr_context, hotkey, customaction);
}
switch (onplay_result)
{
@@ -983,6 +1197,19 @@ static int dirbrowse(void)
reload_dir = true;
break;
+ case ONPLAY_MULTISELECT:
+ restore = do_restore_display;
+ if (!tc.multiselection_enabled)
+ {
+ tc.multiselection_enabled = true;
+ tc.multiselection_offset_item = tc.selected_item;
+ }
+ else
+ {
+ tc.multiselection_enabled = false;
+ }
+ break;
+
case ONPLAY_START_PLAY:
return exit_to_new_screen(GO_TO_WPS);
break;
@@ -1045,9 +1272,12 @@ static int dirbrowse(void)
if (!reload_dir)
{
+ tc.multiselection_enabled = false;
+ tc.selected_item = 0;
+ set_multiselection_selected_size();
+ list->selected_size = tc.multiselection_selected_size;
gui_synclist_select_item(&tree_lists, 0);
gui_synclist_draw(&tree_lists);
- tc.selected_item = 0;
lastdir[0] = 0;
}
@@ -1107,6 +1337,8 @@ int rockbox_browse(struct browse_context *browse)
backups[backup_count] = tc;
backup_count++;
int *prev_dirfilter = tc.dirfilter;
+ tc.multiselection_enabled = false;
+ tc.multiselection_selected_size = 1;
tc.dirfilter = &dirfilter;
tc.sort_dir = global_settings.sort_dir;
diff --git a/apps/tree.h b/apps/tree.h
index 4c7c2e8fe7..63b19eb4c9 100644
--- a/apps/tree.h
+++ b/apps/tree.h
@@ -102,6 +102,10 @@ struct tree_context {
bool is_browsing; /* valid browse context? */
struct browse_context *browse;
+
+ bool multiselection_enabled;
+ int multiselection_offset_item;
+ int multiselection_selected_size;
};
/*
diff --git a/manual/working_with_playlists/main.tex b/manual/working_with_playlists/main.tex
index bdb595b19c..d992921dd5 100644
--- a/manual/working_with_playlists/main.tex
+++ b/manual/working_with_playlists/main.tex
@@ -144,3 +144,17 @@ \subsubsection{Through \setting{Playlists}}
It can be used like the \setting{File Browser} but will display
the content of a playlist when one is selected.
+\subsection{Multiselection}
+This option is available through the \setting{File Browser}, the \setting{Database Browser},
+and also through the \setting{Playlist catalogue} to make playlists management way more flexible,
+should you ever need to move/add or delete complete CDs/albums (or part of them).
+
+You can enable \setting{Multiselection} from the \setting{Context Menu} to perform certain
+actions to multiple files in a row.
+To cancel multiselection, select the setting again or press the \ActionWpsSkipPrev{} button.
+
+\note{Once you are in the multiselection state, navigating on the list
+ will automatically select all items relative to the initially selected element.
+ To perform your desired action in mass, just open a \setting{Context Menu}
+ when all your desired entries are selected according to your needs.}
+