A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * Copyright (C) 2016 William Wilgus
9 * Derivative of folder_select.c by:
10 * Copyright (C) 2012 Jonathan Gordon
11 * Copyright (C) 2012 Thomas Martitz
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ****************************************************************************/
22/* TODO add language defines */
23
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include "lang.h"
28#include "language.h"
29#include "list.h"
30#include "plugin.h"
31#include "mask_select.h"
32#include "talk.h"
33
34/*
35 * Order for changing child states:
36 * 1) expand folder
37 * 2) collapse and select
38 * 3) deselect (skip to 1)
39 */
40
41enum child_state {
42 EXPANDED,
43 SELECTED,
44 COLLAPSED,
45 DESELECTED,
46};
47
48/* Children of main categories */
49struct child {
50 const char* name;
51 struct category *category;
52 enum child_state state;
53 int key_value;
54};
55
56/* Main Categories in root */
57struct category {
58 const char *name;
59 struct child *children;
60 int children_count;
61 int depth;
62 struct category* previous;
63 int key_value; /*values of all children OR|D*/
64};
65
66/* empty category for children of root only one level needed */
67static struct category empty = {
68 .name = "",
69 .children = NULL,
70 .children_count = 0,
71 .depth = 1,
72 .previous = NULL,
73};
74
75/* Or | all keyvalues that user selected */
76static int calculate_mask_r(struct category *root, int mask)
77{
78 int i = 0;
79 while (i < root->children_count)
80 {
81 struct child *this = &root->children[i];
82 if (this)
83 {
84 if (this->state == SELECTED)
85 mask |= this->key_value;
86
87 else if (this->state == EXPANDED)
88 mask = calculate_mask_r(this->category, mask);
89 }
90 i++;
91 }
92return mask;
93}
94
95static int count_items(struct category *start)
96{
97 int count = 0;
98 int i;
99
100 for (i=0; i<start->children_count; i++)
101 {
102 struct child *foo = &start->children[i];
103 if (foo && foo->state == EXPANDED)
104 count += count_items(foo->category);
105 count++;
106 }
107 return count;
108}
109
110static struct child* find_index(struct category *start,
111 int index,struct category **parent)
112{
113 int i = 0;
114
115 *parent = NULL;
116
117 while (i < start->children_count)
118 {
119 struct child *foo = &start->children[i];
120 if (i == index)
121 {
122 *parent = start;
123 return foo;
124 }
125 i++;
126 if (foo && foo->state == EXPANDED)
127 {
128 struct child *bar = find_index(foo->category, index - i, parent);
129 if (bar)
130 {
131 return bar;
132 }
133 index -= count_items(foo->category);
134 }
135 }
136 return NULL;
137}
138
139/* simplelist uses this callback to change
140 the states of the categories/children */
141static int item_action_callback(int action, struct gui_synclist *list)
142{
143 struct category *root = (struct category*)list->data;
144 struct category *parent;
145 struct child *this = find_index(root, list->selected_item, &parent);
146
147 if (action == ACTION_STD_OK && this)
148 {
149 switch (this->state)
150 {
151 case EXPANDED:
152 this->state = SELECTED;
153 if (global_settings.talk_menu)
154 talk_id(LANG_ON, false);
155 break;
156 case SELECTED:
157 this->state = this->category->children_count == 0 ?
158 DESELECTED : COLLAPSED;
159 if (global_settings.talk_menu && this->category->children_count == 0)
160 talk_id(LANG_OFF, false);
161 break;
162 case COLLAPSED:
163 if (this->category == NULL)
164 this->category = root;
165 this->state = this->category->children_count == 0 ?
166 SELECTED : EXPANDED;
167 if (global_settings.talk_menu && this->category->children_count == 0)
168 talk_id(LANG_ON, false);
169 break;
170 case DESELECTED:
171 this->state = SELECTED;
172 if (global_settings.talk_menu)
173 talk_id(LANG_ON, false);
174 break;
175
176 default:
177 /* do nothing */
178 return action;
179 }
180 list->nb_items = count_items(root);
181 return ACTION_REDRAW;
182 }
183
184 return action;
185}
186
187static const char * item_get_name(int selected_item, void * data,
188 char * buffer, size_t buffer_len)
189{
190 struct category *root = (struct category*)data;
191 struct category *parent;
192 struct child *this = find_index(root, selected_item , &parent);
193
194 buffer[0] = '\0';
195
196 if (parent->depth >= 0)
197 for(int i = 0; i <= parent->depth; i++)
198 strcat(buffer, "\t\0");
199
200 if (this)
201 {
202 /* state of selection needs icons so if icons are disabled use text*/
203 if (!global_settings.show_icons)
204 {
205 if (this->state == SELECTED)
206 strcat(buffer, "+\0");
207 else
208 strcat(buffer," \0");
209 }
210 strlcat(buffer, P2STR((const unsigned char *)this->name), buffer_len);
211 }
212 return buffer;
213}
214
215static int item_get_talk(int selected_item, void *data)
216{
217 struct category *root = (struct category*)data;
218 struct category *parent;
219 struct child *this = find_index(root, selected_item , &parent);
220 if (global_settings.talk_menu && this)
221 {
222 long id = P2ID((const unsigned char *)(this->name));
223 if(id>=0)
224 talk_id(id, true);
225 else
226 talk_spell(this->name, true);
227 talk_id(VOICE_PAUSE,true);
228 if (this->state == SELECTED)
229 talk_id(LANG_ON, true);
230 else if (this->state == DESELECTED)
231 talk_id(LANG_OFF, true);
232 }
233 return 0;
234}
235
236static enum themable_icons item_get_icon(int selected_item, void * data)
237{
238 struct category *root = (struct category*)data;
239 struct category *parent;
240 struct child *this = find_index(root, selected_item, &parent);
241
242 if (this)
243 {
244 switch (this->state)
245 {
246 case SELECTED:
247 return Icon_Submenu;
248 case COLLAPSED:
249 return Icon_NOICON;
250 case EXPANDED:
251 return Icon_Submenu_Entered;
252 default:
253 return Icon_NOICON;
254 }
255 }
256 return Icon_NOICON;
257}
258
259/* supply your original mask,the page header (ie. User do this..), mask items
260 and count, they will be selected automatically if the mask includes
261 them. If user selects new items and chooses to save settings
262 new mask returned otherwise, original is returned
263*/
264int mask_select(int mask, const unsigned char* headermsg,
265 struct s_mask_items *mask_items, size_t items)
266{
267 struct simplelist_info info;
268
269 struct child action_child[items];
270 for (unsigned i = 0; i < items; i++)
271 {
272 action_child[i].name = mask_items[i].name;
273 action_child[i].category = ∅
274 action_child[i].key_value = mask_items[i].bit_value;
275 action_child[i].state = mask_items[i].bit_value & mask ?
276 SELECTED : DESELECTED;
277 }
278
279 struct category root = {
280 .name = "",
281 .children = (struct child*) &action_child,
282 .children_count = items,
283 .depth = -1,
284 .previous = NULL,
285 };
286
287 simplelist_info_init(&info, P2STR(headermsg), count_items(&root), &root);
288 info.get_name = item_get_name;
289 info.action_callback = item_action_callback;
290 info.get_icon = item_get_icon;
291 info.get_talk = item_get_talk;
292 simplelist_show_list(&info);
293
294 return calculate_mask_r(&root,0);
295}