Now you can exit the clock plugin again
[kugel-rb.git] / apps / menu.c
blobe47523d4e4d468b49a80a66080422ea7df3ed898
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2002 Robert E. Hak
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
19 #include <stdbool.h>
20 #include <stdlib.h>
22 #include "hwcompat.h"
23 #include "lcd.h"
24 #include "font.h"
25 #include "backlight.h"
26 #include "menu.h"
27 #include "button.h"
28 #include "kernel.h"
29 #include "debug.h"
30 #include "usb.h"
31 #include "panic.h"
32 #include "settings.h"
33 #include "status.h"
34 #include "screens.h"
35 #include "talk.h"
37 #ifdef HAVE_LCD_BITMAP
38 #include "icons.h"
39 #include "widgets.h"
40 #endif
42 struct menu {
43 int top;
44 int cursor;
45 struct menu_item* items;
46 int itemcount;
47 int (*callback)(int, int);
48 #ifdef HAVE_LCD_BITMAP
49 bool use_buttonbar; /* true if a buttonbar is defined */
50 char *buttonbar[3];
51 #endif
54 #define MAX_MENUS 5
56 #ifdef HAVE_LCD_BITMAP
58 /* pixel margins */
59 #define MARGIN_X (global_settings.scrollbar && \
60 menu_lines < menus[m].itemcount ? SCROLLBAR_WIDTH : 0) +\
61 CURSOR_WIDTH
62 #define MARGIN_Y (global_settings.statusbar ? STATUSBAR_HEIGHT : 0)
64 /* position the entry-list starts at */
65 #define LINE_X 0
66 #define LINE_Y (global_settings.statusbar ? 1 : 0)
68 #define CURSOR_X (global_settings.scrollbar && \
69 menu_lines < menus[m].itemcount ? 1 : 0)
70 #define CURSOR_Y 0 /* the cursor is not positioned in regard to
71 the margins, so this is the amount of lines
72 we add to the cursor Y position to position
73 it on a line */
74 #define CURSOR_WIDTH (global_settings.invert_cursor ? 0 : 4)
76 #define SCROLLBAR_X 0
77 #define SCROLLBAR_Y lcd_getymargin()
78 #define SCROLLBAR_WIDTH 6
80 #else /* HAVE_LCD_BITMAP */
82 #define LINE_X 1 /* X position the entry-list starts at */
84 #define MENU_LINES 2
86 #define CURSOR_X 0
87 #define CURSOR_Y 0 /* not really used for players */
89 #endif /* HAVE_LCD_BITMAP */
91 #define CURSOR_CHAR 0x92
93 static struct menu menus[MAX_MENUS];
94 static bool inuse[MAX_MENUS] = { false };
96 /* count in letter positions, NOT pixels */
97 void put_cursorxy(int x, int y, bool on)
99 #ifdef HAVE_LCD_BITMAP
100 int fh, fw;
101 int xpos, ypos;
103 /* check here instead of at every call (ugly, but cheap) */
104 if (global_settings.invert_cursor)
105 return;
107 lcd_getstringsize("A", &fw, &fh);
108 xpos = x*6;
109 ypos = y*fh + lcd_getymargin();
110 if ( fh > 8 )
111 ypos += (fh - 8) / 2;
112 #endif
114 /* place the cursor */
115 if(on) {
116 #ifdef HAVE_LCD_BITMAP
117 lcd_bitmap ( bitmap_icons_6x8[Cursor],
118 xpos, ypos, 4, 8, true);
119 #else
120 lcd_putc(x, y, CURSOR_CHAR);
121 #endif
123 else {
124 #if defined(HAVE_LCD_BITMAP)
125 /* I use xy here since it needs to disregard the margins */
126 lcd_clearrect (xpos, ypos, 4, 8);
127 #else
128 lcd_putc(x, y, ' ');
129 #endif
133 void menu_draw(int m)
135 int i = 0;
136 #ifdef HAVE_LCD_BITMAP
137 int fw, fh;
138 int menu_lines;
139 int height = LCD_HEIGHT;
141 lcd_setfont(FONT_UI);
142 lcd_getstringsize("A", &fw, &fh);
143 if (global_settings.statusbar)
144 height -= STATUSBAR_HEIGHT;
146 if(global_settings.buttonbar && menus[m].use_buttonbar) {
147 buttonbar_set(menus[m].buttonbar[0],
148 menus[m].buttonbar[1],
149 menus[m].buttonbar[2]);
150 height -= BUTTONBAR_HEIGHT;
153 menu_lines = height / fh;
155 #else
156 int menu_lines = MENU_LINES;
157 #endif
159 lcd_clear_display();
160 #ifdef HAVE_LCD_BITMAP
161 lcd_setmargins(MARGIN_X,MARGIN_Y); /* leave room for cursor and icon */
162 #endif
163 /* Adjust cursor pos if it's below the screen */
164 if (menus[m].cursor - menus[m].top >= menu_lines)
165 menus[m].top++;
167 /* Adjust cursor pos if it's above the screen */
168 if(menus[m].cursor < menus[m].top)
169 menus[m].top = menus[m].cursor;
171 for (i = menus[m].top;
172 (i < menus[m].itemcount) && (i<menus[m].top+menu_lines);
173 i++) {
175 /* We want to scroll the line where the cursor is */
176 if((menus[m].cursor - menus[m].top)==(i-menus[m].top))
177 #ifdef HAVE_LCD_BITMAP
178 if (global_settings.invert_cursor)
179 lcd_puts_scroll_style(LINE_X, i-menus[m].top,
180 menus[m].items[i].desc, STYLE_INVERT);
181 else
182 #endif
183 lcd_puts_scroll(LINE_X, i-menus[m].top, menus[m].items[i].desc);
184 else
185 lcd_puts(LINE_X, i-menus[m].top, menus[m].items[i].desc);
188 /* place the cursor */
189 put_cursorxy(CURSOR_X, menus[m].cursor - menus[m].top, true);
191 #ifdef HAVE_LCD_BITMAP
192 if (global_settings.scrollbar && menus[m].itemcount > menu_lines)
193 scrollbar(SCROLLBAR_X, SCROLLBAR_Y, SCROLLBAR_WIDTH - 1,
194 height, menus[m].itemcount, menus[m].top,
195 menus[m].top + menu_lines, VERTICAL);
197 if(global_settings.buttonbar && menus[m].use_buttonbar)
198 buttonbar_draw();
199 #endif
200 status_draw(true);
202 lcd_update();
206 * Move the cursor to a particular id,
207 * target: where you want it to be
209 static void put_cursor(int m, int target)
211 int voice_id;
213 menus[m].cursor = target;
214 menu_draw(m);
216 /* "say" the entry under the cursor */
217 if(global_settings.talk_menu)
219 voice_id = menus[m].items[menus[m].cursor].voice_id;
220 if (voice_id >= 0) /* valid ID given? */
221 talk_id(voice_id, false); /* say it */
225 int menu_init(struct menu_item* mitems, int count, int (*callback)(int, int),
226 char *button1, char *button2, char *button3)
228 int i;
230 for ( i=0; i<MAX_MENUS; i++ ) {
231 if ( !inuse[i] ) {
232 inuse[i] = true;
233 break;
236 if ( i == MAX_MENUS ) {
237 DEBUGF("Out of menus!\n");
238 return -1;
240 menus[i].items = mitems;
241 menus[i].itemcount = count;
242 menus[i].top = 0;
243 menus[i].cursor = 0;
244 menus[i].callback = callback;
245 #ifdef HAVE_LCD_BITMAP
246 menus[i].buttonbar[0] = button1;
247 menus[i].buttonbar[1] = button2;
248 menus[i].buttonbar[2] = button3;
250 if(button1 || button2 || button3)
251 menus[i].use_buttonbar = true;
252 else
253 menus[i].use_buttonbar = false;
254 #else
255 (void)button1;
256 (void)button2;
257 (void)button3;
258 #endif
259 return i;
262 void menu_exit(int m)
264 inuse[m] = false;
267 int menu_show(int m)
269 bool exit = false;
270 int key;
271 #ifdef HAVE_LCD_BITMAP
272 int fw, fh;
273 int menu_lines;
274 int height = LCD_HEIGHT;
276 lcd_setfont(FONT_UI);
277 lcd_getstringsize("A", &fw, &fh);
278 if (global_settings.statusbar)
279 height -= STATUSBAR_HEIGHT;
281 if(global_settings.buttonbar && menus[m].use_buttonbar) {
282 buttonbar_set(menus[m].buttonbar[0],
283 menus[m].buttonbar[1],
284 menus[m].buttonbar[2]);
285 height -= BUTTONBAR_HEIGHT;
288 menu_lines = height / fh;
289 #endif
291 /* Put the cursor on the first line and draw the menu */
292 put_cursor(m, menus[m].cursor);
294 while (!exit) {
295 key = button_get_w_tmo(HZ/2);
298 * "short-circuit" the default keypresses by running the
299 * callback function
300 * The callback may return a new key value, often this will be
301 * BUTTON_NONE or the same key value, but it's perfectly legal
302 * to "simulate" key presses by returning another value.
305 if( menus[m].callback != NULL )
306 key = menus[m].callback(key, m);
308 switch( key ) {
310 #ifdef HAVE_RECORDER_KEYPAD
311 case BUTTON_UP:
312 case BUTTON_UP | BUTTON_REPEAT:
313 #else
314 case BUTTON_LEFT:
315 case BUTTON_LEFT | BUTTON_REPEAT:
316 #endif
317 if (menus[m].cursor) {
318 /* move up */
319 put_cursor(m, menus[m].cursor-1);
321 else {
322 /* move to bottom */
323 #ifdef HAVE_RECORDER_KEYPAD
324 menus[m].top = menus[m].itemcount-(menu_lines+1);
325 #else
326 menus[m].top = menus[m].itemcount-3;
327 #endif
328 if (menus[m].top < 0)
329 menus[m].top = 0;
330 menus[m].cursor = menus[m].itemcount-1;
331 put_cursor(m, menus[m].itemcount-1);
333 break;
335 #ifdef HAVE_RECORDER_KEYPAD
336 case BUTTON_DOWN:
337 case BUTTON_DOWN | BUTTON_REPEAT:
338 #else
339 case BUTTON_RIGHT:
340 case BUTTON_RIGHT | BUTTON_REPEAT:
341 #endif
342 if (menus[m].cursor < menus[m].itemcount-1) {
343 /* move down */
344 put_cursor(m, menus[m].cursor+1);
346 else {
347 /* move to top */
348 menus[m].top = 0;
349 menus[m].cursor = 0;
350 put_cursor(m, 0);
352 break;
354 #ifdef HAVE_RECORDER_KEYPAD
355 case BUTTON_RIGHT:
356 #endif
357 case BUTTON_PLAY:
358 /* Erase current display state */
359 lcd_clear_display();
360 return menus[m].cursor;
362 #ifdef HAVE_RECORDER_KEYPAD
363 case BUTTON_LEFT:
364 case BUTTON_F1:
365 case BUTTON_OFF | BUTTON_REPEAT:
366 #else
367 case BUTTON_STOP:
368 case BUTTON_MENU:
369 case BUTTON_STOP | BUTTON_REPEAT:
370 #endif
371 lcd_stop_scroll();
372 exit = true;
373 break;
375 case SYS_USB_CONNECTED:
376 usb_screen();
377 #ifdef HAVE_LCD_CHARCELLS
378 status_set_param(false);
379 #endif
380 return MENU_ATTACHED_USB;
383 status_draw(false);
385 return MENU_SELECTED_EXIT;
389 bool menu_run(int m)
391 bool stop=false;
392 while (!stop) {
393 int result=menu_show(m);
394 if (result == MENU_SELECTED_EXIT)
395 return false;
396 else if (result == MENU_ATTACHED_USB)
397 return true;
398 if (menus[m].items[menus[m].cursor].function()) {
399 return true;
402 return false;
406 * Property function - return the current cursor for "menu"
409 int menu_cursor(int menu)
411 return menus[menu].cursor;
415 * Property function - return the "menu" description at "position"
418 char* menu_description(int menu, int position)
420 return menus[menu].items[position].desc;
424 * Delete the element "position" from the menu items in "menu"
427 void menu_delete(int menu, int position)
429 int i;
431 /* copy the menu item from the one below */
432 for( i = position; i < (menus[menu].itemcount - 1); i++)
433 menus[menu].items[i] = menus[menu].items[i + 1];
435 /* reduce the count */
436 menus[menu].itemcount--;
438 /* adjust if this was the last menu item and the cursor was on it */
439 if( menus[menu].itemcount <= menus[menu].cursor)
440 menus[menu].cursor = menus[menu].itemcount - 1;
443 void menu_insert(int menu, int position, char *desc, int voice_id,
444 bool (*function) (void))
446 int i;
448 if(position < 0)
449 position = menus[menu].itemcount;
451 /* Move the items below one position forward */
452 for( i = menus[menu].itemcount; i > position; i--)
453 menus[menu].items[i] = menus[menu].items[i - 1];
455 /* Increase the count */
456 menus[menu].itemcount++;
458 /* Update the current item */
459 menus[menu].items[position].desc = desc;
460 menus[menu].items[position].voice_id = voice_id;
461 menus[menu].items[position].function = function;
465 * Property function - return the "count" of menu items in "menu"
468 int menu_count(int menu)
470 return menus[menu].itemcount;
474 * Allows a menu item at the current cursor position in "menu" to be moved up the list
477 bool menu_moveup(int menu)
479 struct menu_item swap;
481 /* can't be the first item ! */
482 if( menus[menu].cursor == 0)
483 return false;
485 /* use a temporary variable to do the swap */
486 swap = menus[menu].items[menus[menu].cursor - 1];
487 menus[menu].items[menus[menu].cursor - 1] = menus[menu].items[menus[menu].cursor];
488 menus[menu].items[menus[menu].cursor] = swap;
489 menus[menu].cursor--;
491 return true;
495 * Allows a menu item at the current cursor position in "menu" to be moved down the list
498 bool menu_movedown(int menu)
500 struct menu_item swap;
502 /* can't be the last item ! */
503 if( menus[menu].cursor == menus[menu].itemcount - 1)
504 return false;
506 /* use a temporary variable to do the swap */
507 swap = menus[menu].items[menus[menu].cursor + 1];
508 menus[menu].items[menus[menu].cursor + 1] = menus[menu].items[menus[menu].cursor];
509 menus[menu].items[menus[menu].cursor] = swap;
510 menus[menu].cursor++;
512 return true;