FS#12756 by Marek Salaba - update Czech translation
[maemo-rb.git] / apps / gui / folder_select.c
blobf2830fb8fde461c4ff6c608a4e9acef1a651e4ca
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
9 * Copyright (C) 2012 Jonathan Gordon
10 * Copyright (C) 2012 Thomas Martitz
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.
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
20 ****************************************************************************/
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"
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
43 enum child_state {
44 EXPANDED,
45 SELECTED,
46 COLLAPSED,
47 EACCESS,
50 struct child {
51 char* name;
52 struct folder *folder;
53 enum child_state state;
56 struct folder {
57 char *name;
58 struct child *children;
59 int children_count;
60 int depth;
62 struct folder* previous;
65 static char *buffer_front, *buffer_end;
66 static char* folder_alloc(size_t size)
68 char* retval;
69 /* 32-bit aligned */
70 size = (size + 3) & ~3;
71 if (buffer_front + size > buffer_end)
73 return NULL;
75 retval = buffer_front;
76 buffer_front += size;
77 return retval;
80 static char* folder_alloc_from_end(size_t size)
82 if (buffer_end - size < buffer_front)
84 return NULL;
86 buffer_end -= size;
87 return buffer_end;
90 static void get_full_path_r(struct folder *start, char* dst)
92 if (start->previous)
93 get_full_path_r(start->previous, dst);
95 if (start->name && start->name[0] && strcmp(start->name, "/"))
97 strlcat(dst, "/", MAX_PATH);
98 strlcat(dst, start->name, MAX_PATH);
102 static char* get_full_path(struct folder *start)
104 static char buffer[MAX_PATH];
106 if (strcmp(start->name, "/"))
108 buffer[0] = 0;
109 get_full_path_r(start, buffer);
111 else /* get_full_path_r() does the wrong thing for / */
112 return "/";
114 return buffer;
117 /* support function for qsort() */
118 static int compare(const void* p1, const void* p2)
120 struct child *left = (struct child*)p1;
121 struct child *right = (struct child*)p2;
122 return strcasecmp(left->name, right->name);
125 static struct folder* load_folder(struct folder* parent, char *folder)
127 DIR *dir;
128 char* path = get_full_path(parent);
129 char fullpath[MAX_PATH];
130 struct dirent *entry;
131 struct folder* this = (struct folder*)folder_alloc(sizeof(struct folder));
132 int child_count = 0;
133 char *first_child = NULL;
135 if (!strcmp(folder,"/"))
136 strlcpy(fullpath, folder, 2);
137 else
138 snprintf(fullpath, MAX_PATH, "%s/%s", parent ? path : "", folder);
140 if (!this)
141 return NULL;
142 dir = opendir(fullpath);
143 if (!dir)
144 return NULL;
145 this->previous = parent;
146 this->name = folder;
147 this->children = NULL;
148 this->children_count = 0;
149 this->depth = parent ? parent->depth + 1 : 0;
151 while ((entry = readdir(dir))) {
152 int len = strlen((char *)entry->d_name);
153 struct dirinfo info;
155 info = dir_get_info(dir, entry);
157 /* skip anything not a directory */
158 if ((info.attribute & ATTR_DIRECTORY) == 0) {
159 continue;
161 /* skip directories . and .. */
162 if ((!strcmp((char *)entry->d_name, ".")) ||
163 (!strcmp((char *)entry->d_name, ".."))) {
164 continue;
166 char *name = folder_alloc_from_end(len+1);
167 if (!name)
168 return NULL;
169 memcpy(name, (char *)entry->d_name, len+1);
170 child_count++;
171 first_child = name;
173 closedir(dir);
174 /* now put the names in the array */
175 this->children = (struct child*)folder_alloc(sizeof(struct child) * child_count);
177 if (!this->children)
178 return NULL;
179 while (child_count)
181 this->children[this->children_count].name = first_child;
182 this->children[this->children_count].folder = NULL;
183 this->children[this->children_count].state = COLLAPSED;
184 this->children_count++;
185 first_child += strlen(first_child) + 1;
186 child_count--;
188 qsort(this->children, this->children_count, sizeof(struct child), compare);
190 return this;
193 struct folder* load_root(void)
195 static struct child root_child;
197 root_child.name = "/";
198 root_child.folder = NULL;
199 root_child.state = COLLAPSED;
201 static struct folder root = {
202 .name = "",
203 .children = &root_child,
204 .children_count = 1,
205 .depth = -1,
206 .previous = NULL,
209 return &root;
212 static int count_items(struct folder *start)
214 int count = 0;
215 int i;
217 for (i=0; i<start->children_count; i++)
219 struct child *foo = &start->children[i];
220 if (foo->state == EXPANDED)
221 count += count_items(foo->folder);
222 count++;
224 return count;
227 static struct child* find_index(struct folder *start, int index, struct folder **parent)
229 int i = 0;
231 *parent = NULL;
233 while (i < start->children_count)
235 struct child *foo = &start->children[i];
236 if (i == index)
238 *parent = start;
239 return foo;
241 i++;
242 if (foo->state == EXPANDED)
244 struct child *bar = find_index(foo->folder, index - i, parent);
245 if (bar)
247 return bar;
249 index -= count_items(foo->folder);
252 return NULL;
255 static const char * folder_get_name(int selected_item, void * data,
256 char * buffer, size_t buffer_len)
258 struct folder *root = (struct folder*)data;
259 struct folder *parent;
260 struct child *this = find_index(root, selected_item , &parent);
262 buffer[0] = '\0';
264 if (parent->depth >= 0)
265 for(int i = 0; i <= parent->depth; i++)
266 strcat(buffer, "\t");
268 strlcat(buffer, this->name, buffer_len);
270 if (this->state == EACCESS)
271 { /* append error message to the entry if unaccessible */
272 size_t len = strlcat(buffer, " (", buffer_len);
273 if (buffer_len > len)
275 snprintf(&buffer[len], buffer_len - len, str(LANG_READ_FAILED),
276 this->name);
277 strlcat(buffer, ")", buffer_len);
281 return buffer;
284 static enum themable_icons folder_get_icon(int selected_item, void * data)
286 struct folder *root = (struct folder*)data;
287 struct folder *parent;
288 struct child *this = find_index(root, selected_item, &parent);
290 switch (this->state)
292 case SELECTED:
293 return Icon_Cursor;
294 case COLLAPSED:
295 return Icon_Folder;
296 case EXPANDED:
297 return Icon_Submenu;
298 case EACCESS:
299 return Icon_Questionmark;
301 return Icon_NOICON;
304 static int folder_action_callback(int action, struct gui_synclist *list)
306 struct folder *root = (struct folder*)list->data;
307 struct folder *parent;
308 struct child *this = find_index(root, list->selected_item, &parent), *child;
309 int i;
311 if (action == ACTION_STD_OK)
313 switch (this->state)
315 case EXPANDED:
316 this->state = SELECTED;
317 break;
318 case SELECTED:
319 this->state = COLLAPSED;
320 break;
321 case COLLAPSED:
322 if (this->folder == NULL)
323 this->folder = load_folder(parent, this->name);
324 this->state = this->folder ? (this->folder->children_count == 0 ?
325 SELECTED : EXPANDED) : EACCESS;
326 break;
327 case EACCESS:
328 /* cannot open, do nothing */
329 return action;
331 list->nb_items = count_items(root);
332 return ACTION_REDRAW;
334 else if (action == ACTION_STD_CONTEXT)
336 switch (this->state)
338 case EXPANDED:
339 for (i = 0; i < this->folder->children_count; i++)
341 child = &this->folder->children[i];
342 if (child->state == SELECTED ||
343 child->state == EXPANDED)
344 child->state = COLLAPSED;
345 else if (child->state == COLLAPSED)
346 child->state = SELECTED;
348 break;
349 case SELECTED:
350 case COLLAPSED:
351 if (this->folder == NULL)
352 this->folder = load_folder(parent, this->name);
353 this->state = this->folder ? (this->folder->children_count == 0 ?
354 SELECTED : EXPANDED) : EACCESS;
355 if (this->state == EACCESS)
356 break;
357 for (i = 0; i < this->folder->children_count; i++)
359 child = &this->folder->children[i];
360 child->state = SELECTED;
362 break;
363 case EACCESS:
364 /* cannot open, do nothing */
365 return action;
367 list->nb_items = count_items(root);
368 return ACTION_REDRAW;
372 return action;
375 static struct child* find_from_filename(char* filename, struct folder *root)
377 char *slash = strchr(filename, '/');
378 int i = 0;
379 if (slash)
380 *slash = '\0';
381 if (!root)
382 return NULL;
384 struct child *this;
386 /* filenames beginning with a / are specially treated as the
387 * loop below can't handle them. they can only occur on the first,
388 * and not recursive, calls to this function.*/
389 if (slash == filename)
391 /* filename begins with /. in this case root must be the
392 * top level folder */
393 this = &root->children[0];
394 if (!slash[1])
395 { /* filename == "/" */
396 return this;
398 else
400 /* filename == "/XXX/YYY". cascade down */
401 if (!this->folder)
402 this->folder = load_folder(root, this->name);
403 this->state = EXPANDED;
404 /* recurse with XXX/YYY */
405 return find_from_filename(slash+1, this->folder);
409 while (i < root->children_count)
411 this = &root->children[i];
412 if (!strcasecmp(this->name, filename))
414 if (!slash)
415 { /* filename == XXX */
416 return this;
418 else
420 /* filename == XXX/YYY. cascade down */
421 if (!this->folder)
422 this->folder = load_folder(root, this->name);
423 this->state = EXPANDED;
424 return find_from_filename(slash+1, this->folder);
427 i++;
429 return NULL;
432 /* _modifies_ buf */
433 int select_paths(struct folder* root, char* buf)
435 struct child *item = find_from_filename(buf, root);
436 if (item)
437 item->state = SELECTED;
438 return 0;
441 static void save_folders_r(struct folder *root, char* dst, size_t maxlen)
443 int i = 0;
445 while (i < root->children_count)
447 struct child *this = &root->children[i];
448 if (this->state == SELECTED)
450 if (this->folder)
451 snprintf(buffer_front, buffer_end - buffer_front,
452 "%s:", get_full_path(this->folder));
453 else
455 char *p = get_full_path(root);
456 snprintf(buffer_front, buffer_end - buffer_front,
457 "%s/%s:", strcmp(p, "/") ? p : "",
458 strcmp(this->name, "/") ? this->name : "");
460 strlcat(dst, buffer_front, maxlen);
462 else if (this->state == EXPANDED)
463 save_folders_r(this->folder, dst, maxlen);
464 i++;
468 static void save_folders(struct folder *root, char* dst, size_t maxlen)
470 int len;
471 dst[0] = '\0';
472 save_folders_r(root, dst, maxlen);
473 len = strlen(dst);
474 /* fix trailing ':' */
475 if (len > 1) dst[len-1] = '\0';
478 bool folder_select(char* setting, int setting_len)
480 struct folder *root;
481 struct simplelist_info info;
482 size_t buf_size;
483 /* 32 separate folders should be Enough For Everybody(TM) */
484 char *vect[32];
485 char copy[setting_len];
486 int nb_items;
488 /* copy onto stack as split_string() modifies it */
489 strlcpy(copy, setting, setting_len);
490 nb_items = split_string(copy, ':', vect, ARRAYLEN(vect));
492 buffer_front = plugin_get_buffer(&buf_size);
493 buffer_end = buffer_front + buf_size;
494 root = load_root();
496 if (nb_items > 0)
498 for(int i = 0; i < nb_items; i++)
499 select_paths(root, vect[i]);
502 simplelist_info_init(&info, str(LANG_SELECT_FOLDER),
503 count_items(root), root);
504 info.get_name = folder_get_name;
505 info.action_callback = folder_action_callback;
506 info.get_icon = folder_get_icon;
507 simplelist_show_list(&info);
509 /* done editing. check for changes */
510 save_folders(root, copy, setting_len);
511 if (strcmp(copy, setting))
512 { /* prompt for saving changes and commit if yes */
513 if (yesno_pop(ID2P(LANG_SAVE_CHANGES)))
515 strcpy(setting, copy);
516 settings_save();
517 return true;
520 return false;