summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter D'Hoye <peter.dhoye@gmail.com>2007-09-03 22:24:26 +0000
committerPeter D'Hoye <peter.dhoye@gmail.com>2007-09-03 22:24:26 +0000
commit946a815cd4166f8761fac0c9cba0092b871cf900 (patch)
tree043b27178fdb6822abb18abeb1f9aa6a6c1bd265
parent9b3be37f84286e24a14ac29b2ab0105d6171e188 (diff)
downloadrockbox-946a815cd4166f8761fac0c9cba0092b871cf900.tar.gz
rockbox-946a815cd4166f8761fac0c9cba0092b871cf900.zip
Accept FS #7667 by Alexander Levin with minor fixes by me. Splits the shortcuts plugin into two, one for adding and one for viewing. Removes hard-coded file extension and allows to link from one shortcut file to another.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@14599 a1c6a512-1295-4272-9138-f99709370657
-rw-r--r--apps/onplay.c2
-rw-r--r--apps/plugins/CATEGORIES3
-rw-r--r--apps/plugins/SOURCES1
-rw-r--r--apps/plugins/SUBDIRS1
-rw-r--r--apps/plugins/shortcuts.c550
-rw-r--r--apps/plugins/shortcuts/Makefile90
-rw-r--r--apps/plugins/shortcuts/shortcuts.h90
-rw-r--r--apps/plugins/shortcuts/shortcuts_append.c109
-rw-r--r--apps/plugins/shortcuts/shortcuts_common.c394
-rw-r--r--apps/plugins/shortcuts/shortcuts_view.c238
-rw-r--r--apps/plugins/viewers.config3
11 files changed, 927 insertions, 554 deletions
diff --git a/apps/onplay.c b/apps/onplay.c
index ba21572c3d..0e5169a007 100644
--- a/apps/onplay.c
+++ b/apps/onplay.c
@@ -1082,7 +1082,7 @@ MENUITEM_FUNCTION(set_recdir_item, 0, ID2P(LANG_SET_AS_REC_DIR),
#endif
static bool add_to_faves(void)
{
- if(PLUGIN_USB_CONNECTED == filetype_load_plugin("shortcuts",
+ if(PLUGIN_USB_CONNECTED == filetype_load_plugin("shortcuts_append",
selected_file))
onplay_result = ONPLAY_RELOAD_DIR;
return false;
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES
index a9997e60d4..a821fa0d91 100644
--- a/apps/plugins/CATEGORIES
+++ b/apps/plugins/CATEGORIES
@@ -58,7 +58,8 @@ rocklife,games
rockpaint,apps
search,viewers
searchengine,viewers
-shortcuts,viewers
+shortcuts_view,viewers
+shortcuts_append,viewers
sliding_puzzle,games
snake2,games
snake,games
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index 23d3e6becc..31d94d5811 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -13,7 +13,6 @@ random_folder_advance_config.c
rockblox.c
rockbox_flash.c
search.c
-shortcuts.c
snow.c
sort.c
stats.c
diff --git a/apps/plugins/SUBDIRS b/apps/plugins/SUBDIRS
index 3f0fc9051b..11643a5b2f 100644
--- a/apps/plugins/SUBDIRS
+++ b/apps/plugins/SUBDIRS
@@ -1,6 +1,7 @@
#ifndef IRIVER_IFP7XX_SERIES
/* For all targets */
+shortcuts
/* For various targets... */
diff --git a/apps/plugins/shortcuts.c b/apps/plugins/shortcuts.c
deleted file mode 100644
index af92bf4634..0000000000
--- a/apps/plugins/shortcuts.c
+++ /dev/null
@@ -1,550 +0,0 @@
-/***************************************************************************
- * __________ __ ___.
- * Open \______ \ ____ ____ | | _\_ |__ _______ ___
- * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
- * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
- * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
- * \/ \/ \/ \/ \/
- * $Id$
- *
- * Copyright (C) 2007 Bryan Childs
- *
- * All files in this archive are subject to the GNU General Public License.
- * See the file COPYING in the source tree root for full license agreement.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- ****************************************************************************/
-
-#include "plugin.h"
-
-PLUGIN_HEADER
-
-static struct plugin_api* rb;
-
-#define SHORTCUTS_FILENAME "/shortcuts.link"
-#define MAX_SHORTCUTS 50
-
-MEM_FUNCTION_WRAPPERS(rb);
-
-typedef struct sc_file_s
-{
- int readsize;
- char* filebuf;
-} sc_file_t;
-
-typedef struct sc_entries_s
-{
- char shortcut[MAX_PATH+1];
- int sc_len;
- struct sc_entries_s* next;
-} sc_entries_t;
-
-enum shortcut_type {
- SCTYPE_NONE,
- SCTYPE_FILE,
- SCTYPE_DIR,
-};
-
-enum sc_list_action_type {
- SCLA_NONE,
- SCLA_SELECT,
- SCLA_DELETE,
-};
-
-void sc_alloc_init(void);
-void* sc_malloc(unsigned int size);
-bool sc_init(void);
-enum sc_list_action_type draw_sc_list(struct gui_synclist gui_sc);
-char* build_sc_list(int selected_item, void* data, char* buffer);
-void delete_sc(int sc_num);
-bool load_sc_file(void);
-bool load_user_sc_file(char* filename);
-bool exists(char* filename);
-enum plugin_status list_sc(void);
-enum plugin_status write_sc_file(char* directory_name,enum shortcut_type st);
-
-char str_dirname[MAX_PATH];
-ssize_t bufleft;
-long mem_ptr;
-long bufsize;
-unsigned char* mallocbuf;
-bool its_a_dir = false;
-bool user_file = false;
-sc_file_t the_file;
-sc_entries_t* shortcuts = 0;
-sc_entries_t* lastentry = 0;
-int total_entries = 0;
-int gselected_item = 0;
-
-void sc_alloc_init(void){
- mem_ptr=0;
-
- mallocbuf = rb->plugin_get_buffer(&bufleft);
- bufsize = (long)bufleft;
-
- rb->memset(mallocbuf,0,bufsize);
-
- return;
-}
-
-void* sc_malloc(unsigned int size) {
- void* x;
-
- if(mem_ptr + (long)size > bufsize) {
- rb->splash(HZ*2,"OUT OF MEMORY");
- return NULL;
- }
-
- x=&mallocbuf[mem_ptr];
- mem_ptr+=(size+3)&~3; /* Keep memory 32-bit aligned */
-
- return x;
-}
-
-bool exists(char* filename){
- int fd = 0;
- /*strip trailing slashes */
- char* ptr = rb->strrchr((char*)filename, '/') + 1;
- int dirlen = (ptr - (char*)filename);
- rb->strncpy(str_dirname, (char*)filename, dirlen);
- str_dirname[dirlen] = 0;
-
- fd = rb->open(str_dirname,O_RDONLY);
- if (!fd) {
- return false;
- }
- rb->close(fd);
- return true;
-}
-
-bool sc_init(void) {
- return load_sc_file();
-}
-
-enum sc_list_action_type draw_sc_list(struct gui_synclist gui_sc) {
- int button;
-
- rb->gui_synclist_draw(&gui_sc);
-
- while (true) {
- /* draw the statusbar, should be done often */
- rb->gui_syncstatusbar_draw(rb->statusbars, true);
- /* user input */
- button = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
- if (rb->gui_synclist_do_button(&gui_sc,button,
- LIST_WRAP_UNLESS_HELD)) {
- /* automatic handling of user input.
- * _UNLESS_HELD can be _ON or _OFF also
- * selection changed, so redraw */
- continue;
- }
- switch (button) { /* process the user input */
- case ACTION_STD_OK:
- gselected_item = rb->gui_synclist_get_sel_pos(&gui_sc);
- return SCLA_SELECT;
- break;
- case ACTION_STD_MENU:
- if(!user_file){
- gselected_item = rb->gui_synclist_get_sel_pos(&gui_sc);
- rb->splash(HZ,"Deleting item");
- return SCLA_DELETE;
- } else {
- return SCLA_NONE;
- }
- break;
- case ACTION_STD_CANCEL:
- return SCLA_NONE;
- break;
- }
- }
-}
-
-char* build_sc_list(int selected_item, void* data, char* buffer) {
- int i;
- sc_entries_t* temp_node = (sc_entries_t*)data;
- char text_buffer[MAX_PATH];
-
- for (i=0;i<selected_item && temp_node != NULL;i++){
- temp_node = temp_node->next;
- }
- if (temp_node == NULL){
- return NULL;
- }
- rb->snprintf(text_buffer, MAX_PATH, "%s", temp_node->shortcut);
-
- rb->strcpy(buffer, text_buffer);
- return buffer;
-}
-
-void delete_sc(int sc_num){
- /* Note: This function is a nasty hack and I should probably
- * be shot for doing it this way.*/
- int i;
- sc_entries_t* current = shortcuts;
- sc_entries_t* previous = shortcuts;
-
- if(total_entries==1){
- /* This is the only item in the file
- * so just set the whole shortcuts list
- * to zero */
- shortcuts=0;
- } else {
- if(sc_num!=0){
- for (i=0;i<sc_num && current != NULL;i++){
- /* keep previous pointing at the prior
- * item in the list */
- if(previous!=current){
- previous = current;
- }
- current=current->next;
- }
- /* current should now be pointing at the item
- * to be deleted, so update the previous item
- * to point to whatever current is pointing to
- * as next item */
- if(current){
- previous->next = current->next;
- }else{
- previous->next = 0;
- }
- }else{
- shortcuts = shortcuts->next;
- }
- }
- return;
-}
-
-enum plugin_status list_sc(void) {
- int selected_item = 0;
- char selected_dir[MAX_PATH];
- enum sc_list_action_type action = SCLA_NONE;
- struct gui_synclist gui_sc;
-
- rb->memset(selected_dir,0,MAX_PATH);
-
- /* 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,shortcuts,false,1);
- rb->gui_synclist_set_title(&gui_sc,"Shortcuts",NOICON);
- rb->gui_synclist_set_nb_items(&gui_sc,total_entries);
- rb->gui_synclist_limit_scroll(&gui_sc,false);
- rb->gui_synclist_select_item(&gui_sc,0);
-
- /* Draw the prepared widget to the LCD now */
- action = draw_sc_list(gui_sc);
-
- /* which item do we action? */
- selected_item = gselected_item;
-
- /* Find out which option the user selected.
- * Handily, the callback function which is used
- * to populate the list is equally useful here! */
- build_sc_list(selected_item,(void*)shortcuts,selected_dir);
-
- /* perform the following actions if the user "selected"
- * the item in the list (i.e. they want to go there
- * in the filebrowser tree */
- switch(action) {
- case SCLA_SELECT:
- /* Check to see if the directory referenced still exists */
- if(!exists(selected_dir)){
- rb->splash(HZ*2,"File / Directory no longer exists on disk");
- return PLUGIN_ERROR;
- }
-
- /* Set the browsers dirfilter to the global setting
- * This is required in case the plugin was launched
- * from the plugins browser, in which case the
- * dirfilter is set to only display .rock files */
- rb->set_dirfilter(rb->global_settings->dirfilter);
-
- /* Change directory to the entry selected by the user */
- rb->set_current_file(selected_dir);
- break;
- case SCLA_DELETE:
- delete_sc(selected_item);
- return write_sc_file(0,SCTYPE_NONE);
- break;
- case SCLA_NONE:
- return PLUGIN_OK;
- break;
- }
- return PLUGIN_OK;
-}
-
-bool load_sc_file(void){
- int fd = 0;
- int amountread = 0;
- char sc_content[MAX_PATH];
- sc_entries_t* entry = 0;
-
- fd = rb->open(SHORTCUTS_FILENAME,O_RDONLY);
- if(fd<0){
- /* The shortcuts.link file didn't exist on disk
- * so create an empty one.
- */
- fd = rb->creat(SHORTCUTS_FILENAME);
- if(fd<0){
- /* For some reason we couldn't create a new shortcuts.link
- * file, so return an error message and exit
- */
- rb->splash(HZ*2,"Couldn't create the shortcuts file");
- return false;
- }
- /* File created, but there's nothing in it
- * so just exit */
- rb->close(fd);
- return true;
- }
-
- /* if we get to here, the file already exists, and has been opened
- * successfully, so we can start reading it
- */
- while((amountread=rb->read_line(fd,sc_content,MAX_PATH))){
- if(!(entry = (sc_entries_t*)sc_malloc(sizeof(sc_entries_t)))){
- rb->splash(HZ*2,"Couldn't get memory for a new entry");
- rb->close(fd);
- return false;
- }
- if(shortcuts==NULL) {
- /* This is the first entry created, so set
- * shortcuts to point to it
- */
- shortcuts=entry;
- }
- if(lastentry!=NULL) {
- /* This isn't the first item in the list
- * so update the previous item in the list
- * to point to this new item.
- */
- lastentry->next = entry;
- }
-
- total_entries++;
- rb->snprintf(entry->shortcut,amountread,"%s",sc_content);
- entry->sc_len = amountread-1;
-
- /* Make sure the 'next' pointer is null */
- entry->next=0;
-
- /* Now we can make last look at this entry,
- * ready for the next one
- */
- lastentry = entry;
- }
- rb->close(fd);
- return true;
-}
-
-bool load_user_sc_file(char* filename){
- int fd = 0;
- int amountread = 0;
- char sc_content[MAX_PATH];
- sc_entries_t* entry = 0;
-
- /* user has chosen to open a non-default .link file
- * so overwrite current memory contents */
- shortcuts = 0;
- lastentry = 0;
- total_entries = 0;
-
- fd = rb->open(filename,O_RDONLY);
- if(fd<0){
- /* The shortcuts.link file didn't exist on disk
- * so create an empty one.
- */
- rb->splash(HZ,"Couldn't open %s",filename);
- return false;
- }
-
- while((amountread=rb->read_line(fd,sc_content,MAX_PATH))){
- if(!(entry = (sc_entries_t*)sc_malloc(sizeof(sc_entries_t)))){
- rb->splash(HZ*2,"Couldn't get memory for a new entry");
- rb->close(fd);
- return false;
- }
- if(shortcuts==NULL) {
- /* This is the first entry created, so set
- * shortcuts to point to it
- */
- shortcuts=entry;
- }
- if(lastentry!=NULL) {
- /* This isn't the first item in the list
- * so update the previous item in the list
- * to point to this new item.
- */
- lastentry->next = entry;
- }
-
- total_entries++;
- rb->snprintf(entry->shortcut,amountread,"%s",sc_content);
- entry->sc_len = amountread-1;
-
- /* Make sure the 'next' pointer is null */
- entry->next=0;
-
- /* Now we can make last look at this entry,
- * ready for the next one
- */
- lastentry = entry;
- }
- rb->close(fd);
- return true;
-}
-
-enum plugin_status write_sc_file(char* directory_name, enum shortcut_type st) {
- int fd;
- int i;
- sc_entries_t *temp_node = shortcuts;
- char text_buffer[MAX_PATH];
-
- if(total_entries>=MAX_SHORTCUTS) {
- /* too many entries in the file already
- * so don't add this one, and give the
- * user an error */
- rb->splash(HZ*2,"Shortcuts file is full");
- return PLUGIN_ERROR;
- }
-
- /* ideally, we should just write a new
- * entry to the file, but I'm going to
- * be lazy, and just re-write the whole
- * thing. */
- fd = rb->open(SHORTCUTS_FILENAME,O_RDWR);
- if(fd<0){
- rb->splash(HZ*2,"Error writing to shortcuts file");
- return PLUGIN_ERROR;
- }
-
- /* truncate the current file, since we're writing it
- * all over again */
- rb->ftruncate(fd,0);
-
- /* Check to see that the list is not empty */
- if(temp_node){
- for (i=0;i<MAX_SHORTCUTS && temp_node != NULL;i++){
- rb->snprintf(text_buffer,temp_node->sc_len+2,
- "%s\n",temp_node->shortcut);
- rb->write(fd,text_buffer,temp_node->sc_len+1);
- temp_node = temp_node->next;
- }
- }
- /* Reached the end of the existing entries, so check to
- * see if we need to add one more for the new entry
- */
- if(st!=SCTYPE_NONE){
- if(st==SCTYPE_FILE) {
- rb->snprintf(text_buffer,rb->strlen(directory_name)+2, /*+2 is \n and 0x00 */
- "%s\n",directory_name);
- rb->write(fd,text_buffer,rb->strlen(directory_name)+1);
- } else if(st==SCTYPE_DIR){
- rb->snprintf(text_buffer,rb->strlen(directory_name)+3, /*+3 is /, \n and 0x00 */
- "%s/\n",directory_name);
- rb->write(fd,text_buffer,rb->strlen(directory_name)+2);
- }
- }
- rb->close(fd);
-
- return PLUGIN_OK;
-}
-
-enum plugin_status plugin_start(struct plugin_api* api, void* parameter) {
- rb = api;
- bool found = false;
-
- DIR* dir;
- struct dirent* entry;
-
- /* Initialise the plugin buffer */
- sc_alloc_init();
-
- if(!sc_init())
- return PLUGIN_ERROR;
-
- /* Were we passed a parameter at startup? */
- if(parameter) {
- /* determine if it's a file or a directory */
- char* ptr = rb->strrchr((char*)parameter, '/') + 1;
- int dirlen = (ptr - (char*)parameter);
- rb->strncpy(str_dirname, (char*)parameter, dirlen);
- str_dirname[dirlen] = 0;
-
- dir = rb->opendir(str_dirname);
- if (dir) {
- while(0 != (entry = rb->readdir(dir))) {
- if(!rb->strcmp(entry->d_name, parameter+dirlen)) {
- its_a_dir = entry->attribute & ATTR_DIRECTORY ?
- true : false;
- found = true;
- break;
- }
- }
- rb->closedir(dir);
- }
- /* now we know if it's a file or a directory
- * (or something went wrong) */
-
- if(!found) {
- /* Something's gone properly pear shaped -
- * we couldn't even find the entry */
- rb->splash(HZ*2,"File / Directory not found : %s",
- (char*)parameter);
- return PLUGIN_ERROR;
- }
-
- if(!its_a_dir) {
- /* Don't add the shortcuts.link file to itself */
- if(rb->strcmp((char*)parameter,SHORTCUTS_FILENAME)==0){
- return list_sc();
- }
- /* this section handles user created .link files */
- if(rb->strcasestr((char*)parameter,".link")){
- if(!load_user_sc_file((char*)parameter)){
- return PLUGIN_ERROR;
- }
- user_file = true;
- if(total_entries==1){
- /* if there's only one entry in the user .link file,
- * go straight to it without displaying the menu */
- char selected_dir[MAX_PATH];
- /* go to entry immediately */
- build_sc_list(0,(void*)shortcuts,selected_dir);
-
- /* Check to see if the directory referenced still exists */
- if(!exists(selected_dir)){
- rb->splash(HZ*2,"File / Directory no longer exists on disk");
- return PLUGIN_ERROR;
- }
-
- /* Set the browsers dirfilter to the global setting */
- rb->set_dirfilter(rb->global_settings->dirfilter);
-
- /* Change directory to the entry selected by the user */
- rb->set_current_file(selected_dir);
- return PLUGIN_OK;
- }else{
- /* This user created link file has multiple entries in it
- * so display a menu to choose between them */
- return list_sc();
- }
- } else {
- /* This isn't a .link file so add it to the shortcuts.link
- * file as a new entry */
- return write_sc_file((char*)parameter,SCTYPE_FILE);
- }
- }else{
- /* This is a directory and should be added to
- * the shortcuts.link file */
- return write_sc_file((char*)parameter,SCTYPE_DIR);
- }
- }
- else { /* We weren't passed a parameter, so open the
- * shortcuts file directly */
- return list_sc();
- }
- return PLUGIN_OK;
-}
-
diff --git a/apps/plugins/shortcuts/Makefile b/apps/plugins/shortcuts/Makefile
new file mode 100644
index 0000000000..93089cc140
--- /dev/null
+++ b/apps/plugins/shortcuts/Makefile
@@ -0,0 +1,90 @@
+# __________ __ ___.
+# Open \______ \ ____ ____ | | _\_ |__ _______ ___
+# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+# \/ \/ \/ \/ \/
+# $$Id: $$
+#
+
+INCLUDES = -I$(APPSDIR) -I.. -I. $(TARGET_INC) -I$(FIRMDIR)/include -I$(FIRMDIR)/export \
+ -I$(FIRMDIR)/common -I$(FIRMDIR)/drivers -I$(OUTDIR) -I$(BUILDDIR) \
+ -I$(BUILDDIR)/pluginbitmaps
+CFLAGS = $(INCLUDES) $(GCCOPTS) $(TARGET) $(EXTRA_DEFINES) \
+ -DTARGET_ID=$(TARGET_ID) -DMEM=${MEMORYSIZE} -DPLUGIN
+
+ifdef APPEXTRA
+ INCLUDES += $(patsubst %,-I$(APPSDIR)/%,$(subst :, ,$(APPEXTRA)))
+endif
+
+LINKFILE := $(OBJDIR)/link.lds
+DEPFILE = $(OBJDIR)/dep-shortcuts
+
+SOURCES := shortcuts_common.c shortcuts_view.c shortcuts_append.c
+VIEW_OBJS := $(OBJDIR)/shortcuts_common.o $(OBJDIR)/shortcuts_view.o
+APPEND_OBJS := $(OBJDIR)/shortcuts_common.o $(OBJDIR)/shortcuts_append.o
+DIRS = .
+
+ifndef SIMVER
+ LDS := ../plugin.lds
+endif
+
+OUTPUT = $(OUTDIR)/shortcuts_view.rock $(OUTDIR)/shortcuts_append.rock
+
+all: $(OUTPUT)
+
+ifndef SIMVER
+$(OBJDIR)/shortcuts_view.elf: $(VIEW_OBJS) $(LINKFILE) $(BITMAPLIBS)
+ $(call PRINTS,LD $(@F))$(CC) $(GCCOPTS) -O -nostdlib -o $@ $(VIEW_OBJS) -L$(BUILDDIR) -lplugin -lgcc \
+ $(LINKBITMAPS) -T$(LINKFILE) -Wl,-Map,$(OBJDIR)/shortcuts_view.map
+
+$(OUTDIR)/shortcuts_view.rock: $(OBJDIR)/shortcuts_view.elf
+ $(call PRINTS,OBJCOPY $(@F))$(OC) -O binary $< $@
+
+$(OBJDIR)/shortcuts_append.elf: $(APPEND_OBJS) $(LINKFILE) $(BITMAPLIBS)
+ $(call PRINTS,LD $(@F))$(CC) $(GCCOPTS) -O -nostdlib -o $@ $(APPEND_OBJS) -L$(BUILDDIR) -lplugin -lgcc \
+ $(LINKBITMAPS) -T$(LINKFILE) -Wl,-Map,$(OBJDIR)/shortcuts_append.map
+
+$(OUTDIR)/shortcuts_append.rock: $(OBJDIR)/shortcuts_append.elf
+ $(call PRINTS,OBJCOPY $(@F))$(OC) -O binary $< $@
+else
+
+###################################################
+# This is the SDL simulator version
+
+$(OUTDIR)/shortcuts_view.rock: $(VIEW_OBJS)
+ $(call PRINTS,LD $(@F))$(CC) $(CFLAGS) $(SHARED_FLAG) $(VIEW_OBJS) -L$(BUILDDIR) -lplugin $(LINKBITMAPS) -o $@
+ifeq ($(findstring CYGWIN,$(UNAME)),CYGWIN)
+# 'x' must be kept or you'll have "Win32 error 5"
+# $ fgrep 5 /usr/include/w32api/winerror.h | head -1
+# #define ERROR_ACCESS_DENIED 5L
+else
+ @chmod -x $@
+endif
+
+$(OUTDIR)/shortcuts_append.rock: $(APPEND_OBJS)
+ $(call PRINTS,LD $(@F))$(CC) $(CFLAGS) $(SHARED_FLAG) $(APPEND_OBJS) -L$(BUILDDIR) -lplugin $(LINKBITMAPS) -o $@
+ifeq ($(findstring CYGWIN,$(UNAME)),CYGWIN)
+# 'x' must be kept or you'll have "Win32 error 5"
+# $ fgrep 5 /usr/include/w32api/winerror.h | head -1
+# #define ERROR_ACCESS_DENIED 5L
+else
+ @chmod -x $@
+endif
+
+endif # end of simulator section
+
+
+include $(TOOLSDIR)/make.inc
+
+# MEMORYSIZE should be passed on to this makefile with the chosen memory size
+# given in number of MB
+$(LINKFILE): $(LDS)
+ $(call PRINTS,build $(@F))cat $< | $(CC) -DMEMORYSIZE=$(MEMORYSIZE) $(INCLUDES) $(TARGET) \
+ $(DEFINES) -E -P - >$@
+
+clean:
+ $(call PRINTS,cleaning shortcuts)rm -rf $(OBJDIR)/shortcuts
+ $(SILENT)rm -f $(OBJDIR)/shortcuts* $(DEPFILE)
+
+-include $(DEPFILE)
diff --git a/apps/plugins/shortcuts/shortcuts.h b/apps/plugins/shortcuts/shortcuts.h
new file mode 100644
index 0000000000..6ccb320a36
--- /dev/null
+++ b/apps/plugins/shortcuts/shortcuts.h
@@ -0,0 +1,90 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2007 Bryan Childs
+ * Copyright (c) 2007 Alexander Levin
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#ifndef _SHORTCUTS_H
+#define _SHORTCUTS_H
+
+#include "plugin.h"
+
+#define PATH_SEPARATOR "/"
+#define PATH_SEPARATOR_LEN 1 /* strlen(PATH_SEPARATOR) */
+
+#if defined(DEBUG) || defined(SIMULATOR)
+#define SC_DEBUG
+#endif
+
+#define SHORTCUTS_FILENAME "/shortcuts.link"
+
+extern struct plugin_api* rb;
+
+typedef struct sc_entry_s
+{
+ char path[MAX_PATH+1];
+ char disp[MAX_PATH+1];
+ bool explicit_disp;
+} sc_entry_t;
+
+typedef struct sc_file_s
+{
+ sc_entry_t *entries;
+ int max_entries; /* Max allowed number of entries */
+ int entry_cnt; /* Current number of entries */
+ int show_last_segments;
+} sc_file_t;
+
+
+extern void *memory_buf;
+extern long memory_bufsize;
+
+
+extern sc_file_t sc_file;
+
+
+/* Allocates a chunk of memory (as much as possible) */
+void allocate_memory(void **buf, size_t *bufsize);
+
+/* Initializes the file */
+void init_sc_file(sc_file_t *file, void *buf, size_t bufsize);
+
+/* Loads shortcuts from the file. Returns true iff succeeded */
+bool load_sc_file(sc_file_t *file, char* filename, bool must_exist,
+ void *entry_buf, size_t entry_bufsize);
+
+/* Writes contents to the file. File is overwritten. */
+bool dump_sc_file(sc_file_t *file, char *filename);
+
+/* Appends the entry to the file. Entry is copied. Returns true iff succeded. */
+bool append_entry(sc_file_t *file, sc_entry_t *entry);
+
+/* Removes the specified entry (0-based index). Returns true iff succeded. */
+bool remove_entry(sc_file_t *file, int entry_idx);
+
+/* Checks whether the index is a valid one for the file. */
+bool is_valid_index(sc_file_t *file, int entry_idx);
+
+bool file_exists(char *filename); /* Does the specified file exist? */
+bool dir_exists(char *path); /* Does the specified dir exist? */
+
+
+#ifdef SC_DEBUG
+void print_file(sc_file_t *file);
+#endif
+
+#endif
diff --git a/apps/plugins/shortcuts/shortcuts_append.c b/apps/plugins/shortcuts/shortcuts_append.c
new file mode 100644
index 0000000000..bc287234dd
--- /dev/null
+++ b/apps/plugins/shortcuts/shortcuts_append.c
@@ -0,0 +1,109 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2007 Bryan Childs
+ * Copyright (c) 2007 Alexander Levin
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#include "shortcuts.h"
+
+PLUGIN_HEADER
+
+
+bool append_entry_to_file(sc_file_t *file, char *path, bool is_dir)
+{
+ sc_entry_t entry;
+
+ unsigned int required_len = rb->strlen(path);
+ if (is_dir) {
+ required_len += PATH_SEPARATOR_LEN; /* Add 1 for the trailing / */
+ }
+ if (required_len >= sizeof(entry.path)) {
+ /* no attempt to print it: it will also be so too long for the splash */
+ rb->splash(HZ*2, "Can't append shortcut, it's too long");
+ return false;
+ }
+ entry.explicit_disp = false;
+ rb->strcpy(entry.path, path);
+ if (is_dir) {
+ rb->strcat(entry.path, PATH_SEPARATOR);
+ }
+ if (!append_entry(file, &entry)) {
+ rb->splash(HZ*2, "Too many entries!");
+ return false;
+ }
+ return true;
+}
+
+
+enum plugin_status plugin_start(struct plugin_api* api, void* void_parameter)
+{
+ rb = api;
+ bool found;
+ bool its_a_dir;
+
+ /* This is a viewer, so a parameter must have been specified */
+ if (void_parameter == NULL) {
+ rb->splash(HZ*2, "No parameter specified!");
+ return PLUGIN_ERROR;
+ }
+ char *parameter = (char*)void_parameter;
+ DEBUGF("Trying to append '%s' to the default link file '%s'...\n",
+ parameter, SHORTCUTS_FILENAME);
+
+ allocate_memory(&memory_buf, &memory_bufsize);
+
+ /* Determine if it's a file or a directory. First check
+ * if it's a dir and then file (not vice versa) since
+ * open() can also open a dir */
+ found = true;
+ if (dir_exists(parameter)) {
+ its_a_dir = true;
+ } else if (file_exists(parameter)) {
+ its_a_dir = false;
+ } else {
+ found = false;
+ }
+ /* now we know if it's a file or a directory
+ * (or something went wrong) */
+
+ if (!found) {
+ /* Something's gone properly pear shaped -
+ * we couldn't even find the entry */
+ rb->splash(HZ*2, "File/Dir not found: %s", parameter);
+ return PLUGIN_ERROR;
+ }
+
+ DEBUGF("'%s' is a %s\n", parameter, (its_a_dir ? "dir" : "file"));
+
+ if (!load_sc_file(&sc_file, SHORTCUTS_FILENAME, false,
+ memory_buf, memory_bufsize)) {
+ DEBUGF("Couldn't load '%s'\n", SHORTCUTS_FILENAME);
+ return PLUGIN_ERROR;
+ }
+
+ if (!append_entry_to_file(&sc_file, parameter, its_a_dir)) {
+ DEBUGF("Couldn't append entry (too many entries?)\n");
+ return PLUGIN_ERROR;
+ }
+
+ if (!dump_sc_file(&sc_file, SHORTCUTS_FILENAME)) {
+ DEBUGF("Couldn't write shortcuts to '%s'\n", SHORTCUTS_FILENAME);
+ return PLUGIN_ERROR;
+ }
+
+ return PLUGIN_OK;
+}
diff --git a/apps/plugins/shortcuts/shortcuts_common.c b/apps/plugins/shortcuts/shortcuts_common.c
new file mode 100644
index 0000000000..4a097f0a0d
--- /dev/null
+++ b/apps/plugins/shortcuts/shortcuts_common.c
@@ -0,0 +1,394 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2007 Bryan Childs
+ * Copyright (c) 2007 Alexander Levin
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#include "shortcuts.h"
+MEM_FUNCTION_WRAPPERS(rb);
+
+#define SHORTCUTS_FILENAME "/shortcuts.link"
+
+#define PATH_DISP_SEPARATOR "\t"
+#define PATH_DISP_SEPARATOR_LEN 1 /* strlen(PATH_DISP_SEPARATOR) */
+#define CONTROL_PREFIX "#"
+#define CONTROL_PREFIX_LEN 1 /* strlen(CONTROL_PREFIX) */
+#define NAME_VALUE_SEPARATOR "="
+#define NAME_VALUE_SEPARATOR_LEN 1 /* strlen(NAME_VALUE_SEPARATOR) */
+
+#define INSTR_DISPLAY_LAST_SEGMENTS "Display last path segments"
+
+/* Memory (will be used for entries) */
+void *memory_buf;
+long memory_bufsize; /* Size of memory_buf in bytes */
+
+
+/* The file we're processing */
+sc_file_t sc_file;
+
+bool parse_entry_content(char *line, sc_entry_t *entry, int last_segm);
+char *last_segments(char *path, int nsegm);
+bool is_control(char *line, sc_file_t *file);
+bool starts_with(char *string, char *prefix);
+bool parse_name_value(char *line, char *name, int namesize,
+ char *value, int valuesize);
+void write_entry_to_file(int fd, sc_entry_t *entry);
+void write_int_instruction_to_file(int fd, char *instr, int value);
+
+
+void allocate_memory(void **buf, size_t *bufsize)
+{
+ *buf = rb->plugin_get_buffer(bufsize);
+ DEBUGF("Got %ld bytes of memory\n", *bufsize);
+}
+
+
+void init_sc_file(sc_file_t *file, void *buf, size_t bufsize)
+{
+ file->entries = (sc_entry_t*)buf;
+ file->max_entries = bufsize / sizeof(sc_entry_t);
+ DEBUGF("Buffer capacity: %d entries\n", file->max_entries);
+ file->entry_cnt = 0;
+ file->show_last_segments = -1;
+}
+
+
+bool load_sc_file(sc_file_t *file, char *filename, bool must_exist,
+ void *entry_buf, size_t entry_bufsize)
+{
+ int fd = -1;
+ bool ret_val = false; /* Assume bad case */
+ int amountread = 0;
+ char sc_content[2*MAX_PATH];
+ sc_entry_t entry;
+
+ /* We start to load a new file -> prepare it */
+ init_sc_file(&sc_file, entry_buf, entry_bufsize);
+
+ fd = rb->open(filename, O_RDONLY);
+ if (fd < 0) {
+ /* The file didn't exist on disk */
+ if (!must_exist) {
+ DEBUGF("Trying to create link file '%s'...\n", filename);
+ fd = rb->creat(filename);
+ if (fd < 0){
+ /* For some reason we couldn't create the file,
+ * so return an error message and exit */
+ rb->splash(HZ*2, "Couldn't create the shortcuts file %s",
+ filename);
+ goto end_of_proc;
+ }
+ /* File created, but there's nothing in it, so just exit */
+ ret_val = true;
+ goto end_of_proc;
+ } else {
+ rb->splash(HZ, "Couldn't open %s", filename);
+ goto end_of_proc;
+ }
+ }
+
+ while ((amountread=rb->read_line(fd,sc_content, sizeof(sc_content)))) {
+ if (is_control(sc_content, file)) {
+ continue;
+ }
+ if (file->entry_cnt >= file->max_entries) {
+ rb->splash(HZ*2, "Too many entries in the file, max allowed: %d",
+ file->max_entries);
+ goto end_of_proc;
+ }
+ if (!parse_entry_content(sc_content, &entry,file->show_last_segments)) {
+ /* Could not parse the entry (too long path?) -> ignore */
+ DEBUGF("Could not parse '%s' -> ignored\n", sc_content);
+ continue;
+ }
+ DEBUGF("Parsed entry: path=%s, disp=%s\n", entry.path, entry.disp);
+ append_entry(file, &entry);
+ }
+
+#ifdef SC_DEBUG
+ print_file(file);
+#endif
+
+ ret_val = true; /* Everything went ok */
+
+end_of_proc:
+ if (fd >= 0) {
+ rb->close(fd);
+ fd = -1;
+ }
+ return ret_val;
+}
+
+#ifdef SC_DEBUG
+void print_file(sc_file_t *file)
+{
+ DEBUGF("Number of entries : %d\n", file->entry_cnt);
+ DEBUGF("Show Last Segments: %d\n", file->show_last_segments);
+ int i;
+ sc_entry_t *entry;
+ for (i=0, entry=file->entries; i<file->entry_cnt; i++,entry++) {
+ if (entry->explicit_disp) {
+ DEBUGF("%2d. '%s', show as '%s'\n", i+1, entry->path, entry->disp);
+ } else {
+ DEBUGF("%2d. '%s' (%s)\n", i+1, entry->path, entry->disp);
+ }
+ }
+}
+#endif
+
+
+bool append_entry(sc_file_t *file, sc_entry_t *entry)
+{
+ if (file->entry_cnt >= file->max_entries) {
+ return false;
+ }
+ rb->memcpy(file->entries+file->entry_cnt, entry, sizeof(*entry));
+ file->entry_cnt++;
+ return true;
+}
+
+
+bool remove_entry(sc_file_t *file, int entry_idx)
+{
+ if ((entry_idx<0) || (entry_idx>=file->entry_cnt)) {
+ return false;
+ }
+ sc_entry_t *start = file->entries + entry_idx;
+ rb->memmove(start, start + 1,
+ (file->entry_cnt-entry_idx-1) * sizeof(sc_entry_t));
+ file->entry_cnt--;
+ return true;
+}
+
+
+bool is_valid_index(sc_file_t *file, int entry_idx)
+{
+ return (entry_idx >= 0) && (entry_idx < file->entry_cnt);
+}
+
+
+bool parse_entry_content(char *line, sc_entry_t *entry, int last_segm)
+{
+ char *sep;
+ char *path, *disp;
+ unsigned int path_len, disp_len;
+ bool expl;
+
+ sep = rb->strcasestr(line, PATH_DISP_SEPARATOR);
+ expl = (sep != NULL);
+ if (expl) {
+ /* Explicit name for the entry is specified -> use it */
+ path = line;
+ path_len = sep - line;
+ disp = sep + PATH_DISP_SEPARATOR_LEN;
+ disp_len = rb->strlen(disp);
+ } else {
+ /* No special name to display */
+ path = line;
+ path_len = rb->strlen(line);
+ if (last_segm <= 0) {
+ disp = path;
+ } else {
+ disp = last_segments(line, last_segm);
+ }
+ disp_len = rb->strlen(disp);
+ }
+
+ if (path_len >= sizeof(entry->path) || disp_len >= sizeof(entry->disp)) {
+ DEBUGF("Bad entry: pathlen=%d, displen=%d\n", path_len, disp_len);
+ return false;
+ }
+ rb->strncpy(entry->path, path, path_len);
+ entry->path[path_len] = '\0';
+ rb->strcpy(entry->disp, disp); /* Safe since we've checked the length */
+ entry->explicit_disp = expl;
+ return true;
+}
+
+
+char *last_segments(char *path, int nsegm)
+{
+ char *p = rb->strrchr(path, PATH_SEPARATOR[0]); /* Hack */
+ int seg_cnt;
+ if (p == NULL)
+ return path; /* No separator??? */
+ seg_cnt = 0;
+ while ((p > path) && (seg_cnt < nsegm)) {
+ p--;
+ if (!starts_with(p, PATH_SEPARATOR)) {
+ continue;
+ }
+ seg_cnt++;
+ if (seg_cnt == nsegm && p > path) {
+ p++; /* Eat the '/' to show that something has been truncated */
+ }
+ }
+ return p;
+}
+
+
+bool is_control(char *line, sc_file_t *file)
+{
+ char name[MAX_PATH], value[MAX_PATH];
+ if (!starts_with(line, CONTROL_PREFIX)) {
+ return false;
+ }
+ line += CONTROL_PREFIX_LEN;
+
+ if (!parse_name_value(line, name, sizeof(name),
+ value, sizeof(value))) {
+ DEBUGF("Bad processing instruction: '%s'\n", line);
+ return true;
+ }
+
+ /* Process control instruction */
+ if (rb->strcasestr(name, INSTR_DISPLAY_LAST_SEGMENTS)) {
+ file->show_last_segments = rb->atoi(value);
+ DEBUGF("Set show last segms to %d\n", file->show_last_segments);
+ } else {
+ /* Unknown instruction -> ignore */
+ DEBUGF("Unknown processing instruction: '%s'\n", name);
+ }
+
+ return true;
+}
+
+
+bool starts_with(char *string, char *prefix)
+{
+ unsigned int pfx_len = rb->strlen(prefix);
+ if (rb->strlen(string) < pfx_len)
+ return false;
+ return (rb->strncmp(string, prefix, pfx_len) == 0);
+}
+
+
+bool parse_name_value(char *line, char *name, int namesize,
+ char *value, int valuesize)
+{
+ char *sep;
+ int name_len, val_len;
+ name[0] = value[0] = '\0';
+
+ sep = rb->strcasestr(line, NAME_VALUE_SEPARATOR);
+ if (sep == NULL) {
+ /* No separator char -> weird instruction */
+ return false;
+ }
+ name_len = sep - line;
+ if (name_len >= namesize) {
+ /* Too long name */
+ return false;
+ }
+ rb->strncpy(name, line, name_len);
+ name[name_len] = '\0';
+
+ val_len = rb->strlen(line) - name_len - NAME_VALUE_SEPARATOR_LEN;
+ if (val_len >= valuesize) {
+ /* Too long value */
+ return false;
+ }
+ rb->strncpy(value, sep+NAME_VALUE_SEPARATOR_LEN, val_len+1);
+ return true;
+}
+
+
+bool dump_sc_file(sc_file_t *file, char *filename)
+{
+ DEBUGF("Dumping shortcuts to the file '%s'\n", filename);
+ int fd;
+
+ /* ideally, we should just write a new
+ * entry to the file, but I'm going to
+ * be lazy, and just re-write the whole
+ * thing. */
+ fd = rb->open(filename, O_WRONLY|O_TRUNC);
+ if (fd < 0) {
+ rb->splash(HZ*2, "Could not open shortcuts file %s for writing",
+ filename);
+ return false;
+ }
+
+ /*
+ * Write instructions
+ */
+ /* Always dump the 'display last segms' settings (even it it's
+ * not active) so that it can be easily changed without having
+ * to remember the setting name */
+ write_int_instruction_to_file(fd,
+ INSTR_DISPLAY_LAST_SEGMENTS, file->show_last_segments);
+
+ int i;
+ sc_entry_t *entry;
+ for (i=0, entry=file->entries; i<file->entry_cnt; i++,entry++) {
+ write_entry_to_file(fd, entry);
+ }
+
+ rb->close(fd);
+ DEBUGF("Dumped %d entries to the file '%s'\n", file->entry_cnt, filename);
+
+ return true;
+}
+
+
+void write_int_instruction_to_file(int fd, char *instr, int value)
+{
+ rb->fdprintf(fd, "%s%s%s%d\n", CONTROL_PREFIX, instr,
+ NAME_VALUE_SEPARATOR, value);
+}
+
+
+void write_entry_to_file(int fd, sc_entry_t *entry)
+{
+ rb->fdprintf(fd, "%s", entry->path);
+ if (entry->explicit_disp) {
+ rb->fdprintf(fd, "%s%s", PATH_DISP_SEPARATOR, entry->disp);
+ }
+ rb->fdprintf(fd, "\n");
+}
+
+
+bool file_exists(char *filename)
+{
+ int fd = rb->open(filename, O_RDONLY);
+ bool retval;
+ if (fd >= 0) {
+ rb->close(fd);
+ retval = true;
+ } else {
+ retval = false;
+ }
+ DEBUGF("Checked existence of the file '%s': %s\n",
+ filename, (retval ? "found" : "NOT FOUND"));
+ return retval;
+}
+
+
+bool dir_exists(char *path)
+{
+ DIR* d = rb->opendir(path);
+ bool retval;
+ if (d != NULL) {
+ rb->closedir(d);
+ retval = true;
+ } else {
+ retval = false;
+ }
+ DEBUGF("Checked existence of the dir '%s': %s\n",
+ path, (retval ? "found" : "NOT FOUND"));
+ return retval;
+}
diff --git a/apps/plugins/shortcuts/shortcuts_view.c b/apps/plugins/shortcuts/shortcuts_view.c
new file mode 100644
index 0000000000..3b28b4b34c
--- /dev/null
+++ b/apps/plugins/shortcuts/shortcuts_view.c
@@ -0,0 +1,238 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2007 Bryan Childs
+ * Copyright (c) 2007 Alexander Levin
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#include "shortcuts.h"
+
+PLUGIN_HEADER
+
+enum sc_list_action_type
+{
+ SCLA_NONE,
+ SCLA_SELECT,
+ SCLA_DELETE,
+ SCLA_USB,
+};
+
+
+static char *link_filename;
+static bool user_file;
+static int gselected_item;
+static bool usb_connected = false;
+
+enum sc_list_action_type draw_sc_list(struct gui_synclist gui_sc);
+
+/* Will be passed sc_file* as data */
+char* build_sc_list(int selected_item, void *data, char *buffer);
+
+/* Returns true iff we should leave the main loop */
+bool list_sc(bool is_editable);
+
+bool goto_entry(char *file_or_dir);
+bool ends_with(char *str, char *suffix);
+
+
+enum sc_list_action_type draw_sc_list(struct gui_synclist gui_sc)
+{
+ int button;
+
+ rb->gui_synclist_draw(&gui_sc);
+
+ while (true) {
+ /* draw the statusbar, should be done often */
+ rb->gui_syncstatusbar_draw(rb->statusbars, true);
+ /* user input */
+ button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK);
+ if (rb->gui_synclist_do_button(&gui_sc, button,
+ LIST_WRAP_UNLESS_HELD)) {
+ /* automatic handling of user input.
+ * _UNLESS_HELD can be _ON or _OFF also
+ * selection changed, so redraw */
+ continue;
+ }
+ switch (button) { /* process the user input */
+ case ACTION_STD_OK:
+ gselected_item = rb->gui_synclist_get_sel_pos(&gui_sc);
+ return SCLA_SELECT;
+ case ACTION_STD_MENU:
+ /* Only allow delete entries in the default file
+ * since entries can be appended (with a plugin)
+ * to the default file only. The behaviour is thus
+ * symmetric in this respect. */
+ if (!user_file) {
+ gselected_item = rb->gui_synclist_get_sel_pos(&gui_sc);
+ return SCLA_DELETE;
+ }
+ break;
+ case ACTION_STD_CANCEL:
+ return SCLA_NONE;
+ default:
+ if (rb->default_event_handler(button) == SYS_USB_CONNECTED) {
+ return SCLA_USB;
+ }
+ }
+ }
+}
+
+
+char* build_sc_list(int selected_item, void *data, char *buffer)
+{
+ char text_buffer[MAX_PATH];
+ sc_file_t *file = (sc_file_t*)data;
+
+ if (!is_valid_index(file, selected_item)) {
+ return NULL;
+ }
+ sc_entry_t *entry = file->entries + selected_item;
+ rb->snprintf(text_buffer, sizeof(text_buffer), "%s", entry->disp);
+ rb->strcpy(buffer, text_buffer);
+ return buffer;
+}
+
+
+bool list_sc(bool is_editable)
+{
+ int selected_item = 0;
+ char selected_dir[MAX_PATH];
+ enum sc_list_action_type action = SCLA_NONE;
+ struct gui_synclist gui_sc;
+
+ rb->memset(selected_dir, 0, sizeof(selected_dir));
+
+ /* 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);
+ rb->gui_synclist_set_title(&gui_sc,
+ (is_editable?"Shortcuts (editable)":"Shortcuts (sealed)"), NOICON);
+ rb->gui_synclist_set_nb_items(&gui_sc, sc_file.entry_cnt);
+ rb->gui_synclist_limit_scroll(&gui_sc, false);
+ rb->gui_synclist_select_item(&gui_sc, 0);
+
+ /* Draw the prepared widget to the LCD now */
+ action = draw_sc_list(gui_sc);
+ if (action == SCLA_USB) {
+ usb_connected = true;
+ return true;
+ }
+
+ /* which item do we action? */
+ selected_item = gselected_item;
+
+ if (!is_valid_index(&sc_file, selected_item)) {
+ /* This should never happen */
+ rb->splash(HZ*2, "Bad entry selected!");
+ return true;
+ }
+
+ /* perform the following actions if the user "selected"
+ * the item in the list (i.e. they want to go there
+ * in the filebrowser tree */
+ switch(action) {
+ case SCLA_SELECT:
+ return goto_entry(sc_file.entries[selected_item].path);
+ case SCLA_DELETE:
+ rb->splash(HZ, "Deleting %s", sc_file.entries[selected_item].disp);
+ remove_entry(&sc_file, selected_item);
+ dump_sc_file(&sc_file, link_filename);
+ return (sc_file.entry_cnt == 0);
+ default:
+ return true;
+ }
+}
+
+
+bool goto_entry(char *file_or_dir)
+{
+ DEBUGF("Trying to go to '%s'...\n", file_or_dir);
+
+ bool is_dir = ends_with(file_or_dir, PATH_SEPARATOR);
+ bool exists;
+ char *what;
+ if (is_dir) {
+ what = "Directory";
+ exists = dir_exists(file_or_dir);
+ } else {
+ what = "File";
+ exists = file_exists(file_or_dir);
+ }
+
+ if (!exists) {
+ rb->splash(HZ*2, "%s %s no longer exists on disk", what, file_or_dir);
+ return false;
+ }
+ /* Set the browsers dirfilter to the global setting
+ * This is required in case the plugin was launched
+ * from the plugins browser, in which case the
+ * dirfilter is set to only display .rock files */
+ rb->set_dirfilter(rb->global_settings->dirfilter);
+
+ /* Change directory to the entry selected by the user */
+ rb->set_current_file(file_or_dir);
+ return true;
+}
+
+
+bool ends_with(char *string, char *suffix)
+{
+ unsigned int str_len = rb->strlen(string);
+ unsigned int sfx_len = rb->strlen(suffix);
+ if (str_len < sfx_len)
+ return false;
+ return (rb->strncmp(string + str_len - sfx_len, suffix, sfx_len) == 0);
+}
+
+
+enum plugin_status plugin_start(struct plugin_api* api, void* void_parameter)
+{
+ rb = api;
+ bool leave_loop;
+
+ /* This is a viewer, so a parameter must have been specified */
+ if (void_parameter == NULL) {
+ rb->splash(HZ*2, "No parameter specified!");
+ return PLUGIN_ERROR;
+ }
+ link_filename = (char*)void_parameter;
+ user_file = (rb->strcmp(link_filename, SHORTCUTS_FILENAME) != 0);
+
+ allocate_memory(&memory_buf, &memory_bufsize);
+
+ if (!load_sc_file(&sc_file, link_filename, true,
+ memory_buf, memory_bufsize)) {
+ DEBUGF("Could not load %s\n", link_filename);
+ return PLUGIN_ERROR;
+ }
+ if (sc_file.entry_cnt==0) {
+ rb->splash(HZ*2, "No shortcuts in the file!");
+ return PLUGIN_OK;
+ } else if ((sc_file.entry_cnt==1) && user_file) {
+ /* if there's only one entry in the user .link file,
+ * go straight to it without displaying the menu
+ * thus allowing 'quick links' */
+ goto_entry(sc_file.entries[0].path);
+ return PLUGIN_OK;
+ }
+
+ do {
+ /* Display a menu to choose between the entries */
+ leave_loop = list_sc(!user_file);
+ } while (!leave_loop);
+
+ return usb_connected ? PLUGIN_USB_CONNECTED : PLUGIN_OK;
+}
diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config
index 342af3a5e0..27fae18b4c 100644
--- a/apps/plugins/viewers.config
+++ b/apps/plugins/viewers.config
@@ -36,4 +36,5 @@ z80,viewers/zxbox,12
*,viewers/properties,-
colours,apps/text_editor,11
ssg,games/superdom,-
-link,viewers/shortcuts,-
+link,viewers/shortcuts_view,-
+*,viewers/shortcuts_append,-