fix for wrapping artefact when display_mode=ALWAYS
[open-ps2-loader.git] / src / menusys.c
blob7e8c2cdcc41205eff11f197825a022300bf7dc80
1 /*
2 Copyright 2009, Ifcaro & volca
3 Licenced under Academic Free License version 3.0
4 Review OpenUsbLd README & LICENSE files for further details.
5 */
7 #include "include/menusys.h"
8 #include "include/iosupport.h"
9 #include "include/usbld.h"
10 #include "include/renderman.h"
11 #include "include/fntsys.h"
12 #include "include/lang.h"
13 #include "include/themes.h"
14 #include "include/pad.h"
15 #include "include/gui.h"
16 #include "include/system.h"
17 #include "include/ioman.h"
18 #include "assert.h"
20 #define MENU_SETTINGS 0
21 #define MENU_GFX_SETTINGS 1
22 #define MENU_IP_CONFIG 2
23 #define MENU_SAVE_CHANGES 3
24 #define MENU_START_HDL 4
25 #define MENU_ABOUT 5
26 #define MENU_EXIT 6
27 #define MENU_POWER_OFF 7
29 // global menu variables
30 static menu_list_t* menu;
31 static menu_list_t* selected_item;
33 static int actionStatus;
34 static int itemConfigId;
35 static config_set_t* itemConfig;
37 // "main menu submenu"
38 static submenu_list_t* mainMenu;
39 // active item in the main menu
40 static submenu_list_t* mainMenuCurrent;
42 static s32 menuSemaId;
43 static ee_sema_t menuSema;
45 static void _menuLoadConfig() {
46 WaitSema(menuSemaId);
47 if (!itemConfig) {
48 item_list_t* list = selected_item->item->userdata;
49 itemConfig = list->itemGetConfig(itemConfigId);
51 actionStatus = 0;
52 SignalSema(menuSemaId);
55 static void _menuSaveConfig() {
56 WaitSema(menuSemaId);
57 configWrite(itemConfig);
58 itemConfigId = -1; // to invalidate cache and force reload
59 actionStatus = 0;
60 SignalSema(menuSemaId);
63 static void _menuRequestConfig() {
64 WaitSema(menuSemaId);
65 if (itemConfigId != selected_item->item->current->item.id) {
66 if (itemConfig) {
67 configFree(itemConfig);
68 itemConfig = NULL;
70 item_list_t* list = selected_item->item->userdata;
71 if (itemConfigId == -1 || guiInactiveFrames >= list->delay) {
72 itemConfigId = selected_item->item->current->item.id;
73 ioPutRequest(IO_CUSTOM_SIMPLEACTION, &_menuLoadConfig);
75 } else if (itemConfig)
76 actionStatus = 0;
77 SignalSema(menuSemaId);
80 config_set_t* menuLoadConfig() {
81 actionStatus = 1;
82 itemConfigId = -1;
83 guiHandleDeferedIO(&actionStatus, _l(_STR_LOADING_SETTINGS), IO_CUSTOM_SIMPLEACTION, &_menuRequestConfig);
84 return itemConfig;
87 void menuSaveConfig() {
88 actionStatus = 1;
89 guiHandleDeferedIO(&actionStatus, _l(_STR_SAVING_SETTINGS), IO_CUSTOM_SIMPLEACTION, &_menuSaveConfig);
92 static void menuInitMainMenu() {
93 if (mainMenu)
94 submenuDestroy(&mainMenu);
96 // initialize the menu
97 #ifndef __CHILDPROOF
98 submenuAppendItem(&mainMenu, -1, NULL, MENU_SETTINGS, _STR_SETTINGS);
99 submenuAppendItem(&mainMenu, -1, NULL, MENU_GFX_SETTINGS, _STR_GFX_SETTINGS);
100 submenuAppendItem(&mainMenu, -1, NULL, MENU_IP_CONFIG, _STR_IPCONFIG);
101 submenuAppendItem(&mainMenu, -1, NULL, MENU_SAVE_CHANGES, _STR_SAVE_CHANGES);
102 if (gHDDStartMode) // enabled at all?
103 submenuAppendItem(&mainMenu, -1, NULL, MENU_START_HDL, _STR_STARTHDL);
104 #endif
105 submenuAppendItem(&mainMenu, -1, NULL, MENU_ABOUT, _STR_ABOUT);
106 submenuAppendItem(&mainMenu, -1, NULL, MENU_EXIT, _STR_EXIT);
107 submenuAppendItem(&mainMenu, -1, NULL, MENU_POWER_OFF, _STR_POWEROFF);
109 mainMenuCurrent = mainMenu;
112 // -------------------------------------------------------------------------------------------
113 // ---------------------------------------- Menu manipulation --------------------------------
114 // -------------------------------------------------------------------------------------------
115 void menuInit() {
116 menu = NULL;
117 selected_item = NULL;
118 itemConfigId = -1;
119 itemConfig = NULL;
120 mainMenu = NULL;
121 mainMenuCurrent = NULL;
122 menuInitMainMenu();
124 menuSema.init_count = 1;
125 menuSema.max_count = 1;
126 menuSema.option = 0;
127 menuSemaId = CreateSema(&menuSema);
130 void menuEnd() {
131 // destroy menu
132 menu_list_t *cur = menu;
134 while (cur) {
135 menu_list_t *td = cur;
136 cur = cur->next;
138 if (&td->item)
139 submenuDestroy(&td->item->submenu);
141 menuRemoveHints(td->item);
143 free(td);
146 submenuDestroy(&mainMenu);
148 if (itemConfig) {
149 configFree(itemConfig);
150 itemConfig = NULL;
153 DeleteSema(menuSemaId);
156 static menu_list_t* AllocMenuItem(menu_item_t* item) {
157 menu_list_t* it;
159 it = malloc(sizeof(menu_list_t));
161 it->prev = NULL;
162 it->next = NULL;
163 it->item = item;
165 return it;
168 void menuAppendItem(menu_item_t* item) {
169 assert(item);
171 if (menu == NULL) {
172 menu = AllocMenuItem(item);
173 selected_item = menu;
174 return;
177 menu_list_t *cur = menu;
179 // traverse till the end
180 while (cur->next)
181 cur = cur->next;
183 // create new item
184 menu_list_t *newitem = AllocMenuItem(item);
186 // link
187 cur->next = newitem;
188 newitem->prev = cur;
191 void submenuRebuildCache(submenu_list_t* submenu) {
192 while (submenu) {
193 if (submenu->item.cache_id)
194 free(submenu->item.cache_id);
195 if(submenu->item.cache_uid)
196 free(submenu->item.cache_uid);
198 int size = gTheme->gameCacheCount * sizeof(int);
199 submenu->item.cache_id = malloc(size);
200 memset(submenu->item.cache_id, -1, size);
201 submenu->item.cache_uid = malloc(size);
202 memset(submenu->item.cache_uid, -1, size);
204 submenu = submenu->next;
208 static submenu_list_t* submenuAllocItem(int icon_id, char *text, int id, int text_id) {
209 submenu_list_t* it = (submenu_list_t*) malloc(sizeof(submenu_list_t));
211 it->prev = NULL;
212 it->next = NULL;
213 it->item.icon_id = icon_id;
214 it->item.text = text;
215 it->item.text_id = text_id;
216 it->item.id = id;
217 it->item.cache_id = NULL;
218 it->item.cache_uid = NULL;
219 submenuRebuildCache(it);
221 return it;
224 submenu_list_t* submenuAppendItem(submenu_list_t** submenu, int icon_id, char *text, int id, int text_id) {
225 if (*submenu == NULL) {
226 *submenu = submenuAllocItem(icon_id, text, id, text_id);
227 return *submenu;
230 submenu_list_t *cur = *submenu;
232 // traverse till the end
233 while (cur->next)
234 cur = cur->next;
236 // create new item
237 submenu_list_t *newitem = submenuAllocItem(icon_id, text, id, text_id);
239 // link
240 cur->next = newitem;
241 newitem->prev = cur;
243 return newitem;
246 static void submenuDestroyItem(submenu_list_t* submenu) {
247 free(submenu->item.cache_id);
248 free(submenu->item.cache_uid);
250 free(submenu);
253 void submenuRemoveItem(submenu_list_t** submenu, int id) {
254 submenu_list_t* cur = *submenu;
255 submenu_list_t* prev = NULL;
257 while (cur) {
258 if (cur->item.id == id) {
259 submenu_list_t* next = cur->next;
261 if (prev)
262 prev->next = cur->next;
264 if (*submenu == cur)
265 *submenu = next;
267 submenuDestroyItem(cur);
269 cur = next;
270 } else {
271 prev = cur;
272 cur = cur->next;
277 void submenuDestroy(submenu_list_t** submenu) {
278 // destroy sub menu
279 submenu_list_t *cur = *submenu;
281 while (cur) {
282 submenu_list_t *td = cur;
283 cur = cur->next;
285 submenuDestroyItem(td);
288 *submenu = NULL;
291 void menuAddHint(menu_item_t *menu, int text_id, int icon_id) {
292 // allocate a new hint item
293 menu_hint_item_t* hint = malloc(sizeof(menu_hint_item_t));
295 hint->text_id = text_id;
296 hint->icon_id = icon_id;
297 hint->next = NULL;
299 if (menu->hints) {
300 menu_hint_item_t* top = menu->hints;
302 // rewind to end
303 for (; top->next; top = top->next);
305 top->next = hint;
306 } else {
307 menu->hints = hint;
311 void menuRemoveHints(menu_item_t *menu) {
312 while (menu->hints) {
313 menu_hint_item_t* hint = menu->hints;
314 menu->hints = hint->next;
315 free(hint);
319 char *menuItemGetText(menu_item_t* it) {
320 if (it->text_id >= 0)
321 return _l(it->text_id);
322 else
323 return it->text;
326 char *submenuItemGetText(submenu_item_t* it) {
327 if (it->text_id >= 0)
328 return _l(it->text_id);
329 else
330 return it->text;
333 static void swap(submenu_list_t* a, submenu_list_t* b) {
334 submenu_list_t *pa, *nb;
335 pa = a->prev;
336 nb = b->next;
338 a->next = nb;
339 b->prev = pa;
340 b->next = a;
341 a->prev = b;
343 if (pa)
344 pa->next = b;
346 if (nb)
347 nb->prev = a;
350 // Sorts the given submenu by comparing the on-screen titles
351 void submenuSort(submenu_list_t** submenu) {
352 // a simple bubblesort
353 // *submenu = mergeSort(*submenu);
354 submenu_list_t *head = *submenu;
355 int sorted = 0;
357 if ((submenu == NULL) || (*submenu == NULL) || ((*submenu)->next == NULL))
358 return;
360 while (!sorted) {
361 sorted = 1;
363 submenu_list_t *tip = head;
365 while (tip->next) {
366 submenu_list_t *nxt = tip->next;
368 char *txt1 = submenuItemGetText(&tip->item);
369 char *txt2 = submenuItemGetText(&nxt->item);
371 int cmp = stricmp(txt1, txt2);
373 if (cmp > 0) {
374 swap(tip, nxt);
376 if (tip == head)
377 head = nxt;
379 sorted = 0;
380 } else {
381 tip = tip->next;
386 *submenu = head;
389 static void menuNextH() {
390 if(selected_item->next) {
391 selected_item = selected_item->next;
392 itemConfigId = -1;
396 static void menuPrevH() {
397 if(selected_item->prev) {
398 selected_item = selected_item->prev;
399 itemConfigId = -1;
403 static void menuNextV() {
404 submenu_list_t *cur = selected_item->item->current;
406 if(cur && cur->next) {
407 selected_item->item->current = cur->next;
409 // if the current item is beyond the page start, move the page start one page down
410 cur = selected_item->item->pagestart;
411 int itms = ((items_list_t*) gTheme->itemsList->extended)->displayedItems + 1;
412 while (--itms && cur)
413 if (selected_item->item->current == cur)
414 return;
415 else
416 cur = cur->next;
418 selected_item->item->pagestart = selected_item->item->current;
422 static void menuPrevV() {
423 submenu_list_t *cur = selected_item->item->current;
425 if(cur && cur->prev) {
426 selected_item->item->current = cur->prev;
428 // if the current item is on the page start, move the page start one page up
429 if (selected_item->item->pagestart == cur) {
430 int itms = ((items_list_t*) gTheme->itemsList->extended)->displayedItems + 1; // +1 because the selection will move as well
431 while (--itms && selected_item->item->pagestart->prev)
432 selected_item->item->pagestart = selected_item->item->pagestart->prev;
437 static void menuNextPage() {
438 submenu_list_t *cur = selected_item->item->pagestart;
440 if (cur) {
441 int itms = ((items_list_t*) gTheme->itemsList->extended)->displayedItems + 1;
442 while (--itms && cur->next)
443 cur = cur->next;
445 selected_item->item->current = cur;
446 selected_item->item->pagestart = selected_item->item->current;
450 static void menuPrevPage() {
451 submenu_list_t *cur = selected_item->item->pagestart;
453 if (cur) {
454 int itms = ((items_list_t*) gTheme->itemsList->extended)->displayedItems + 1;
455 while (--itms && cur->prev)
456 cur = cur->prev;
458 selected_item->item->current = cur;
459 selected_item->item->pagestart = selected_item->item->current;
463 static void menuFirstPage() {
464 selected_item->item->current = selected_item->item->submenu;
465 selected_item->item->pagestart = selected_item->item->current;
468 static void menuLastPage() {
469 submenu_list_t *cur = selected_item->item->current;
470 if (cur) {
471 while (cur->next)
472 cur = cur->next; // go to end
474 selected_item->item->current = cur;
476 int itms = ((items_list_t*) gTheme->itemsList->extended)->displayedItems;
477 while (--itms && cur->prev) // and move back to have a full page
478 cur = cur->prev;
480 selected_item->item->pagestart = cur;
484 void menuSetSelectedItem(menu_item_t* item) {
485 menu_list_t* itm = menu;
487 while (itm) {
488 if (itm->item == item) {
489 selected_item = itm;
490 return;
493 itm = itm->next;
497 void menuRenderMenu() {
498 guiDrawBGPlasma();
500 if (!mainMenu)
501 return;
503 // draw the animated menu
504 if (!mainMenuCurrent)
505 mainMenuCurrent = mainMenu;
507 submenu_list_t* it = mainMenu;
509 // calculate the number of items
510 int count = 0; int sitem = 0;
511 for (; it; count++, it=it->next) {
512 if (it == mainMenuCurrent)
513 sitem = count;
516 int spacing = 25;
517 int y = (gTheme->usedHeight >> 1) - (spacing * (count >> 1));
518 int cp = 0; // current position
519 for (it = mainMenu; it; it = it->next, cp++) {
520 // render, advance
521 fntRenderString(gTheme->itemsList->font, 320, y, ALIGN_CENTER, 0, 0, submenuItemGetText(&it->item), (cp == sitem) ? gTheme->selTextColor : gTheme->textColor);
522 y += spacing;
526 void menuHandleInputMenu() {
527 if (!mainMenu)
528 return;
530 if (!mainMenuCurrent)
531 mainMenuCurrent = mainMenu;
533 if (getKey(KEY_UP)) {
534 if (mainMenuCurrent->prev)
535 mainMenuCurrent = mainMenuCurrent->prev;
536 else // rewind to the last item
537 while (mainMenuCurrent->next)
538 mainMenuCurrent = mainMenuCurrent->next;
541 if (getKey(KEY_DOWN)) {
542 if (mainMenuCurrent->next)
543 mainMenuCurrent = mainMenuCurrent->next;
544 else
545 mainMenuCurrent = mainMenu;
548 if (getKeyOn(KEY_CROSS)) {
549 // execute the item via looking at the id of it
550 int id = mainMenuCurrent->item.id;
552 if (id == MENU_SETTINGS) {
553 guiShowConfig();
554 } else if (id == MENU_GFX_SETTINGS) {
555 guiShowUIConfig();
556 } else if (id == MENU_IP_CONFIG) {
557 guiShowIPConfig();
558 } else if (id == MENU_SAVE_CHANGES) {
559 saveConfig(CONFIG_OPL, 1);
560 } else if (id == MENU_START_HDL) {
561 handleHdlSrv();
562 } else if (id == MENU_ABOUT) {
563 guiShowAbout();
564 } else if (id == MENU_EXIT) {
565 sysExecExit();
566 } else if (id == MENU_POWER_OFF) {
567 sysPowerOff();
570 // so the exit press wont propagate twice
571 readPads();
574 if(getKeyOn(KEY_START) || getKeyOn(KEY_CIRCLE)) {
575 if (gAPPStartMode || gETHStartMode || gUSBStartMode || gHDDStartMode)
576 guiSwitchScreen(GUI_SCREEN_MAIN, TRANSITION_LEFT);
580 void menuRenderMain() {
581 // selected_item can't be NULL here as we only allow to switch to "Main" rendering when there is at least one device activated
582 theme_element_t* elem = gTheme->mainElems.first;
583 while (elem) {
584 if (elem->drawElem)
585 elem->drawElem(selected_item, selected_item->item->current, NULL, elem);
587 elem = elem->next;
591 void menuHandleInputMain() {
592 if(getKey(KEY_LEFT)) {
593 menuPrevH();
594 } else if(getKey(KEY_RIGHT)) {
595 menuNextH();
596 } else if(getKey(KEY_UP)) {
597 menuPrevV();
598 } else if(getKey(KEY_DOWN)){
599 menuNextV();
600 } else if(getKeyOn(KEY_CROSS)) {
601 if (selected_item->item->current && gUseInfoScreen && gTheme->infoElems.first)
602 guiSwitchScreen(GUI_SCREEN_INFO, TRANSITION_DOWN);
603 else
604 selected_item->item->execCross(selected_item->item);
605 } else if(getKeyOn(KEY_TRIANGLE)) {
606 selected_item->item->execTriangle(selected_item->item);
607 } else if(getKeyOn(KEY_CIRCLE)) {
608 selected_item->item->execCircle(selected_item->item);
609 } else if(getKeyOn(KEY_SQUARE)) {
610 selected_item->item->execSquare(selected_item->item);
611 } else if(getKeyOn(KEY_START)) {
612 // reinit main menu - show/hide items valid in the active context
613 menuInitMainMenu();
614 guiSwitchScreen(GUI_SCREEN_MENU, TRANSITION_RIGHT);
615 } else if(getKeyOn(KEY_SELECT)) {
616 selected_item->item->refresh(selected_item->item);
617 } else if(getKey(KEY_L1)) {
618 menuPrevPage();
619 } else if(getKey(KEY_R1)) {
620 menuNextPage();
621 } else if (getKeyOn(KEY_L2)) { // home
622 menuFirstPage();
623 } else if (getKeyOn(KEY_R2)) { // end
624 menuLastPage();
628 void menuRenderInfo() {
629 // selected_item->item->current can't be NULL here as we only allow to switch to "Info" rendering when there is at least one item
630 _menuRequestConfig();
632 //WaitSema(menuSemaId); If I'm not mistaking (assignment of itemConfig pointer is atomic), not needed
633 theme_element_t* elem = gTheme->infoElems.first;
634 while (elem) {
635 if (elem->drawElem)
636 elem->drawElem(selected_item, selected_item->item->current, itemConfig, elem);
638 elem = elem->next;
640 //SignalSema(menuSemaId);
643 void menuHandleInputInfo() {
644 if(getKeyOn(KEY_CROSS)) {
645 selected_item->item->execCross(selected_item->item);
646 } else if(getKey(KEY_UP)) {
647 menuPrevV();
648 } else if(getKey(KEY_DOWN)){
649 menuNextV();
650 } else if(getKeyOn(KEY_CIRCLE)) {
651 guiSwitchScreen(GUI_SCREEN_MAIN, TRANSITION_UP);
652 } else if(getKey(KEY_L1)) {
653 menuPrevPage();
654 } else if(getKey(KEY_R1)) {
655 menuNextPage();
656 } else if (getKeyOn(KEY_L2)) {
657 menuFirstPage();
658 } else if (getKeyOn(KEY_R2)) {
659 menuLastPage();