summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/SOURCES1
-rw-r--r--apps/gui/folder_select.c474
-rw-r--r--apps/gui/folder_select.h35
-rw-r--r--apps/lang/english.lang14
-rw-r--r--apps/misc.c31
-rw-r--r--apps/misc.h1
6 files changed, 556 insertions, 0 deletions
diff --git a/apps/SOURCES b/apps/SOURCES
index 8749c36c87..6005460a5a 100644
--- a/apps/SOURCES
+++ b/apps/SOURCES
@@ -86,6 +86,7 @@ gui/pitchscreen.c
86#ifdef HAVE_QUICKSCREEN 86#ifdef HAVE_QUICKSCREEN
87gui/quickscreen.c 87gui/quickscreen.c
88#endif 88#endif
89gui/folder_select.c
89 90
90gui/wps.c 91gui/wps.c
91gui/scrollbar.c 92gui/scrollbar.c
diff --git a/apps/gui/folder_select.c b/apps/gui/folder_select.c
new file mode 100644
index 0000000000..512b73f588
--- /dev/null
+++ b/apps/gui/folder_select.c
@@ -0,0 +1,474 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 *
9 * Copyright (C) 2012 Jonathan Gordon
10 * Copyright (C) 2012 Thomas Martitz
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include "inttypes.h"
26#include "config.h"
27#include "core_alloc.h"
28#include "filetypes.h"
29#include "lang.h"
30#include "language.h"
31#include "list.h"
32#include "plugin.h"
33
34
35/*
36 * Order for changing child states:
37 * 1) expand folder (skip to 3 if empty, skip to 4 if cannot be opened)
38 * 2) collapse and select
39 * 3) unselect (skip to 1)
40 * 4) do nothing
41 */
42
43enum child_state {
44 EXPANDED,
45 SELECTED,
46 COLLAPSED,
47 EACCESS,
48};
49
50struct child {
51 char* name;
52 struct folder *folder;
53 enum child_state state;
54};
55
56struct folder {
57 char *name;
58 struct child *children;
59 int children_count;
60 int depth;
61
62 struct folder* previous;
63};
64
65static char *buffer_front, *buffer_end;
66static char* folder_alloc(size_t size)
67{
68 char* retval;
69 /* 32-bit aligned */
70 size = (size + 3) & ~3;
71 if (buffer_front + size > buffer_end)
72 {
73 return NULL;
74 }
75 retval = buffer_front;
76 buffer_front += size;
77 return retval;
78}
79
80static char* folder_alloc_from_end(size_t size)
81{
82 if (buffer_end - size < buffer_front)
83 {
84 return NULL;
85 }
86 buffer_end -= size;
87 return buffer_end;
88}
89
90static void get_full_path_r(struct folder *start, char* dst)
91{
92 if (start->previous)
93 get_full_path_r(start->previous, dst);
94
95 if (start->name && start->name[0] && strcmp(start->name, "/"))
96 {
97 strlcat(dst, "/", MAX_PATH);
98 strlcat(dst, start->name, MAX_PATH);
99 }
100}
101
102static char* get_full_path(struct folder *start)
103{
104 static char buffer[MAX_PATH];
105
106 buffer[0] = '\0';
107
108 get_full_path_r(start, buffer);
109
110 return buffer;
111}
112
113/* support function for qsort() */
114static int compare(const void* p1, const void* p2)
115{
116 struct child *left = (struct child*)p1;
117 struct child *right = (struct child*)p2;
118 return strcasecmp(left->name, right->name);
119}
120
121static struct folder* load_folder(struct folder* parent, char *folder)
122{
123 DIR *dir;
124 char* path = get_full_path(parent);
125 char fullpath[MAX_PATH];
126 struct dirent *entry;
127 struct folder* this = (struct folder*)folder_alloc(sizeof(struct folder));
128 int child_count = 0;
129 char *first_child = NULL;
130
131 if (!strcmp(folder,"/"))
132 strlcpy(fullpath, folder, 2);
133 else
134 snprintf(fullpath, MAX_PATH, "%s/%s", parent ? path : "", folder);
135
136 if (!this)
137 return NULL;
138 dir = opendir(fullpath);
139 if (!dir)
140 return NULL;
141 this->previous = parent;
142 this->name = folder;
143 this->children = NULL;
144 this->children_count = 0;
145 this->depth = parent ? parent->depth + 1 : 0;
146
147 while ((entry = readdir(dir))) {
148 int len = strlen((char *)entry->d_name);
149 struct dirinfo info;
150
151 info = dir_get_info(dir, entry);
152
153 /* skip anything not a directory */
154 if ((info.attribute & ATTR_DIRECTORY) == 0) {
155 continue;
156 }
157 /* skip directories . and .. */
158 if ((!strcmp((char *)entry->d_name, ".")) ||
159 (!strcmp((char *)entry->d_name, ".."))) {
160 continue;
161 }
162 char *name = folder_alloc_from_end(len+1);
163 if (!name)
164 return NULL;
165 memcpy(name, (char *)entry->d_name, len+1);
166 child_count++;
167 first_child = name;
168 }
169 closedir(dir);
170 /* now put the names in the array */
171 this->children = (struct child*)folder_alloc(sizeof(struct child) * child_count);
172
173 if (!this->children)
174 return NULL;
175 while (child_count)
176 {
177 this->children[this->children_count].name = first_child;
178 this->children[this->children_count].folder = NULL;
179 this->children[this->children_count].state = COLLAPSED;
180 this->children_count++;
181 first_child += strlen(first_child) + 1;
182 child_count--;
183 }
184 qsort(this->children, this->children_count, sizeof(struct child), compare);
185
186 return this;
187}
188
189struct folder* load_root(void)
190{
191 static struct child root_child;
192
193 root_child.name = "/";
194 root_child.folder = NULL;
195 root_child.state = COLLAPSED;
196
197 static struct folder root = {
198 .name = "",
199 .children = &root_child,
200 .children_count = 1,
201 .depth = -1,
202 .previous = NULL,
203 };
204
205 return &root;
206}
207
208static int count_items(struct folder *start)
209{
210 int count = 0;
211 int i;
212
213 for (i=0; i<start->children_count; i++)
214 {
215 struct child *foo = &start->children[i];
216 if (foo->state == EXPANDED)
217 count += count_items(foo->folder);
218 count++;
219 }
220 return count;
221}
222
223static struct child* find_index(struct folder *start, int index, struct folder **parent)
224{
225 int i = 0;
226
227 *parent = NULL;
228
229 while (i < start->children_count)
230 {
231 struct child *foo = &start->children[i];
232 if (i == index)
233 {
234 *parent = start;
235 return foo;
236 }
237 i++;
238 if (foo->state == EXPANDED)
239 {
240 struct child *bar = find_index(foo->folder, index - i, parent);
241 if (bar)
242 {
243 return bar;
244 }
245 index -= count_items(foo->folder);
246 }
247 }
248 return NULL;
249}
250
251static const char * folder_get_name(int selected_item, void * data,
252 char * buffer, size_t buffer_len)
253{
254 struct folder *root = (struct folder*)data;
255 struct folder *parent;
256 struct child *this = find_index(root, selected_item , &parent);
257
258 buffer[0] = '\0';
259
260 if (parent->depth >= 0)
261 for(int i = 0; i <= parent->depth; i++)
262 strcat(buffer, "\t");
263
264 strlcat(buffer, this->name, buffer_len);
265
266 if (this->state == EACCESS)
267 { /* append error message to the entry if unaccessible */
268 size_t len = strlcat(buffer, " (", buffer_len);
269 if (buffer_len > len)
270 {
271 snprintf(&buffer[len], buffer_len - len, str(LANG_READ_FAILED),
272 this->name);
273 strlcat(buffer, ")", buffer_len);
274 }
275 }
276
277 return buffer;
278}
279
280static enum themable_icons folder_get_icon(int selected_item, void * data)
281{
282 struct folder *root = (struct folder*)data;
283 struct folder *parent;
284 struct child *this = find_index(root, selected_item, &parent);
285
286 switch (this->state)
287 {
288 case SELECTED:
289 return Icon_Cursor;
290 case COLLAPSED:
291 return Icon_Folder;
292 case EXPANDED:
293 return Icon_Submenu;
294 case EACCESS:
295 return Icon_Questionmark;
296 }
297 return Icon_NOICON;
298}
299
300static int folder_action_callback(int action, struct gui_synclist *list)
301{
302 struct folder *root = (struct folder*)list->data;
303
304 if (action == ACTION_STD_OK)
305 {
306 struct folder *parent;
307 struct child *this = find_index(root, list->selected_item, &parent);
308 switch (this->state)
309 {
310 case EXPANDED:
311 this->state = SELECTED;
312 break;
313 case SELECTED:
314 this->state = COLLAPSED;
315 break;
316 case COLLAPSED:
317 if (this->folder == NULL)
318 this->folder = load_folder(parent, this->name);
319 this->state = this->folder ? (this->folder->children_count == 0 ?
320 SELECTED : EXPANDED) : EACCESS;
321 break;
322 case EACCESS:
323 /* cannot open, do nothing */
324 break;
325 }
326 list->nb_items = count_items(root);
327 return ACTION_REDRAW;
328 }
329 return action;
330}
331
332static struct child* find_from_filename(char* filename, struct folder *root)
333{
334 char *slash = strchr(filename, '/');
335 int i = 0;
336 if (slash)
337 *slash = '\0';
338 if (!root)
339 return NULL;
340
341 struct child *this;
342
343 /* filenames beginning with a / are specially treated as the
344 * loop below can't handle them. they can only occur on the first,
345 * and not recursive, calls to this function.*/
346 if (slash == filename)
347 {
348 /* filename begins with /. in this case root must be the
349 * top level folder */
350 this = &root->children[0];
351 if (!slash[1])
352 { /* filename == "/" */
353 return this;
354 }
355 else
356 {
357 /* filename == "/XXX/YYY". cascade down */
358 if (!this->folder)
359 this->folder = load_folder(root, this->name);
360 this->state = EXPANDED;
361 /* recurse with XXX/YYY */
362 return find_from_filename(slash+1, this->folder);
363 }
364 }
365
366 while (i < root->children_count)
367 {
368 this = &root->children[i];
369 if (!strcasecmp(this->name, filename))
370 {
371 if (!slash)
372 { /* filename == XXX */
373 return this;
374 }
375 else
376 {
377 /* filename == XXX/YYY. cascade down */
378 if (!this->folder)
379 this->folder = load_folder(root, this->name);
380 this->state = EXPANDED;
381 return find_from_filename(slash+1, this->folder);
382 }
383 }
384 i++;
385 }
386 return NULL;
387}
388
389/* _modifies_ buf */
390int select_paths(struct folder* root, char* buf)
391{
392 struct child *item = find_from_filename(buf, root);
393 if (item)
394 item->state = SELECTED;
395 return 0;
396}
397
398static void save_folders_r(struct folder *root, char* dst, size_t maxlen)
399{
400 int i = 0;
401
402 while (i < root->children_count)
403 {
404 struct child *this = &root->children[i];
405 if (this->state == SELECTED)
406 {
407 if (this->folder)
408 snprintf(buffer_front, buffer_end - buffer_front,
409 "%s:", get_full_path(this->folder));
410 else
411 snprintf(buffer_front, buffer_end - buffer_front,
412 "%s/%s:", get_full_path(root), this->name);
413 strlcat(dst, buffer_front, maxlen);
414 }
415 else if (this->state == EXPANDED)
416 save_folders_r(this->folder, dst, maxlen);
417 i++;
418 }
419}
420
421static void save_folders(struct folder *root, char* dst, size_t maxlen)
422{
423 int len;
424 dst[0] = '\0';
425 save_folders_r(root, dst, maxlen);
426 len = strlen(dst);
427 /* fix trailing ':' */
428 if (len > 1) dst[len-1] = '\0';
429}
430
431bool folder_select(char* setting, int setting_len)
432{
433 struct folder *root;
434 struct simplelist_info info;
435 size_t buf_size;
436 /* 32 separate folders should be Enough For Everybody(TM) */
437 char *vect[32];
438 char copy[setting_len];
439 int nb_items;
440
441 /* copy onto stack as split_string() modifies it */
442 strlcpy(copy, setting, setting_len);
443 nb_items = split_string(copy, ':', vect, ARRAYLEN(vect));
444
445 buffer_front = plugin_get_buffer(&buf_size);
446 buffer_end = buffer_front + buf_size;
447 root = load_root();
448
449 if (nb_items > 0)
450 {
451 for(int i = 0; i < nb_items; i++)
452 select_paths(root, vect[i]);
453 }
454
455 simplelist_info_init(&info, str(LANG_SELECT_FOLDER),
456 count_items(root), root);
457 info.get_name = folder_get_name;
458 info.action_callback = folder_action_callback;
459 info.get_icon = folder_get_icon;
460 simplelist_show_list(&info);
461
462 /* done editing. check for changes */
463 save_folders(root, copy, setting_len);
464 if (strcmp(copy, setting))
465 { /* prompt for saving changes and commit if yes */
466 if (yesno_pop(ID2P(LANG_SAVE_CHANGES)))
467 {
468 strcpy(setting, copy);
469 settings_save();
470 return true;
471 }
472 }
473 return false;
474}
diff --git a/apps/gui/folder_select.h b/apps/gui/folder_select.h
new file mode 100644
index 0000000000..1c7e559532
--- /dev/null
+++ b/apps/gui/folder_select.h
@@ -0,0 +1,35 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 *
9 * Copyright (C) 2011 Jonathan Gordon
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 *
16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17 * KIND, either express or implied.
18 *
19 ****************************************************************************/
20
21#ifndef __FOLDER_SELECT_H__
22#define __FOLDER_SELECT_H__
23
24/**
25 * A GUI browser to select folders from the file system
26 *
27 * It reads a list of folders, separated by colons (:) from setting
28 * and pre-selects them in the UI. If the user is done it writes the new
29 * list back to setting (again separated by colons), assuming the
30 * user confirms the yesno dialog.
31 *
32 * Returns true if the the folder list has changed, otherwise false */
33bool folder_select(char* setting, int setting_len);
34
35#endif /* __FOLDER_SELECT_H__ */
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index 7366a80030..42acad2609 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -13086,3 +13086,17 @@
13086 swcodec: "Custom" 13086 swcodec: "Custom"
13087 </voice> 13087 </voice>
13088</phrase> 13088</phrase>
13089<phrase>
13090 id: LANG_SELECT_FOLDER
13091 desc: in settings_menu
13092 user: core
13093 <source>
13094 *: "Select one or more directories"
13095 </source>
13096 <dest>
13097 *: "Select one or more directories"
13098 </dest>
13099 <voice>
13100 *: "Select one or more directories"
13101 </voice>
13102</phrase>
diff --git a/apps/misc.c b/apps/misc.c
index 5b20b35c17..d7d4bdd2f9 100644
--- a/apps/misc.c
+++ b/apps/misc.c
@@ -1075,6 +1075,37 @@ void format_time(char* buf, int buf_size, long t)
1075 } 1075 }
1076} 1076}
1077 1077
1078/**
1079 * Splits str at each occurence of split_char and puts the substrings into vector,
1080 * but at most vector_lenght items. Empty substrings are ignored.
1081 *
1082 * Modifies str by replacing each split_char following a substring with nul
1083 *
1084 * Returns the number of substrings found, i.e. the number of valid strings
1085 * in vector
1086 */
1087int split_string(char *str, const char split_char, char *vector[], const int vector_length)
1088{
1089 int i;
1090 char *p = str;
1091
1092 /* skip leading splitters */
1093 while(*p == split_char) p++;
1094
1095 /* *p in the condition takes care of trailing splitters */
1096 for(i = 0; p && *p && i < vector_length; i++)
1097 {
1098 vector[i] = p;
1099 if ((p = strchr(p, split_char)))
1100 {
1101 *p++ = '\0';
1102 while(*p == split_char) p++; /* skip successive splitters */
1103 }
1104 }
1105
1106 return i;
1107}
1108
1078 1109
1079/** Open a UTF-8 file and set file descriptor to first byte after BOM. 1110/** Open a UTF-8 file and set file descriptor to first byte after BOM.
1080 * If no BOM is present this behaves like open(). 1111 * If no BOM is present this behaves like open().
diff --git a/apps/misc.h b/apps/misc.h
index 7ca8d75930..994e7775d2 100644
--- a/apps/misc.h
+++ b/apps/misc.h
@@ -73,6 +73,7 @@ extern int show_logo(void);
73#define BOM_UTF_16_BE "\xfe\xff" 73#define BOM_UTF_16_BE "\xfe\xff"
74#define BOM_UTF_16_SIZE 2 74#define BOM_UTF_16_SIZE 2
75 75
76int split_string(char *str, const char needle, char *vector[], int vector_length);
76int open_utf8(const char* pathname, int flags); 77int open_utf8(const char* pathname, int flags);
77 78
78#ifdef BOOTFILE 79#ifdef BOOTFILE