/* cursesmenudisplay.c Copyright (C) 2010-2019 Amf This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #ifdef CHROMA_CURSES_HEADER #include CHROMA_CURSES_HEADER #else #ifdef __WIN32__ #include #else #include #endif #endif #include "chroma.h" #include "menu.h" #include "actions.h" #include "level.h" #include "display.h" #include "util.h" #define MAX_WIDTH 65 extern int display_size_x, display_size_y; extern int actions[KEY_MAX]; extern short colourpair_menu; extern short colourpair_menugrey; extern short colourpair_red; extern short colourpair_green; extern short colourpair_yellow; extern short colourpair_blue; extern short colourpair_cyan; extern short colourpair_magenta; extern short colourpair_cyan; extern short colourpair_white; int menu_offset; int menu_width; int menu_height_notes; int menu_height_entries; int menu_y_min; int menu_y_max; int menu_y_logo_top; int menu_y_logo_bottom; int menu_scroll_y_min; int menu_scroll_y_max; int menu_scroll_top; int menu_scroll_bottom; int menu_entryheight(struct menuentry *pentry) { if(pentry->flags & MENU_INVISIBLE) return 0; if(pentry->flags & MENU_DOUBLE) return 2; return 1; } void menu_displayentry(struct menu *pmenu, struct menuentry *pentry, int y, int selected) { char buffer[MAX_WIDTH + 1]; int x, i; if(menu_width < 1) return; if(pentry->flags & MENU_INVISIBLE) return; if(pentry->flags & (MENU_GREY | MENU_NOTE | MENU_TEXT)) selected = 0; /* Plot first line, if visible */ if((y >= menu_y_min && y < menu_y_max) || (pentry->flags & MENU_NOTE)) { /* Plot key press */ attron(COLOR_PAIR(colourpair_white)); if(pentry->key != 0 && pentry->key != MENU_KEY_ANY) { mvprintw(y, menu_offset - 4, "[ ] "); attron(A_BOLD); mvprintw(y, menu_offset - 3, "%c", pentry->key); attroff(A_BOLD); } else mvprintw(y, menu_offset - 4, " "); attroff(COLOR_PAIR(colourpair_white)); /* Determine colour */ if(pentry->flags & (MENU_GREY | MENU_SPACE)) attron(COLOR_PAIR(colourpair_menugrey)); else if(pentry->flags & MENU_NOTE) { if(pmenu->logo == 0) attron(COLOR_PAIR(colourpair_menugrey)); } else if(pentry->flags & MENU_TEXT) attron(COLOR_PAIR(colourpair_white)); else attron(COLOR_PAIR(colourpair_menu)); if(selected) attron(A_REVERSE); /* Blank line */ move(y, menu_offset); for(i = 0; i < menu_width; i ++) addch(' '); /* Plot right hand side text */ if(pentry->text2 != NULL) { utf8strncpy(buffer, pentry->text2, menu_width - 2); x = menu_offset + menu_width - 1 - utf8strlen(buffer); mvprintw(y, x, buffer); } /* Plot main text */ if(pentry->text != NULL) { utf8strncpy(buffer, pentry->text, menu_width - 2); x = menu_offset + 1; if(pentry->flags & MENU_RIGHT) x = menu_offset + menu_width - utf8strlen(buffer); if(pentry->flags & MENU_CENTRE) x = menu_offset + ((menu_width - utf8strlen(buffer))/2); if(pentry->flags & MENU_BOLD) attron(A_BOLD); mvprintw(y, x, buffer); if(pentry->flags & MENU_BOLD) attroff(A_BOLD); } if(selected) attroff(A_REVERSE); if(pentry->flags & (MENU_GREY | MENU_NOTE | MENU_SPACE)) attroff(COLOR_PAIR(colourpair_menugrey)); else if(pentry->flags & MENU_TEXT) attroff(COLOR_PAIR(colourpair_white)); else attroff(COLOR_PAIR(colourpair_menu)); } if(!(pentry->flags & MENU_DOUBLE)) return; y ++; /* Plot second line, if visible */ if((y >= menu_y_min && y < menu_y_max) || (pentry->flags & MENU_NOTE)) { /* Blank key press area */ attron(COLOR_PAIR(colourpair_white)); mvprintw(y, menu_offset - 4, " "); attroff(COLOR_PAIR(colourpair_white)); /* Determine colour */ if(pentry->flags & (MENU_GREY | MENU_NOTE | MENU_SPACE)) attron(COLOR_PAIR(colourpair_menugrey)); else attron(COLOR_PAIR(colourpair_menu)); if(selected) attron(A_REVERSE); /* Blank line */ move(y, menu_offset); for(i = 0; i < menu_width; i ++) addch(' '); /* Plot right hand side text */ if(pentry->text4 != NULL) { utf8strncpy(buffer, pentry->text4, menu_width - 2); x = menu_offset + menu_width - 1 - utf8strlen(buffer); if(pentry->flags & MENU_EDITING) attron(COLOR_PAIR(colourpair_white)); mvprintw(y, x, buffer); if(pentry->flags & MENU_EDITING) attroff(COLOR_PAIR(colourpair_white)); } /* Plot left hand side text */ if(pentry->text3 != NULL) { utf8strncpy(buffer, pentry->text3, menu_width - 2); x = menu_offset + 1; mvprintw(y, x, buffer); } if(selected) attroff(A_REVERSE); if(pentry->flags & (MENU_GREY | MENU_SPACE)) attroff(COLOR_PAIR(colourpair_menugrey)); else if(pentry->flags & MENU_NOTE) { if(pmenu->logo == 0) attroff(COLOR_PAIR(colourpair_menugrey)); } else attroff(COLOR_PAIR(colourpair_menu)); } } void menu_display(struct menu *pmenu, int redraw) { struct menuentry *pentry; int x, y, selected; char title[] = "chroma"; char buffer[256]; int i; int state; int y_editing; short cp; int j; /* 012345678901234567890123456789012345678901 */ char logo[] = " 8 " " 8 " ".oPYo. 8oPYo. oPYo. .oPYo. ooYoYo. .oPYo. " "8 ' 8 8 8 `' 8 8 8' 8 8 .oooo8 " "8 . 8 8 8 8 8 8 8 8 8 8 " "`YooP' 8 8 8 `YooP' 8 8 8 `YooP8 "; #define LOGO_HEIGHT 7 /* Calculate various widths */ menu_width = display_size_x - 2; if(menu_width > MAX_WIDTH) menu_width = MAX_WIDTH; menu_offset = (display_size_x - menu_width) / 2; if(menu_offset < 0) menu_offset = 0; menu_width -= 5; menu_offset += 4; /* Calculate various heights */ menu_height_notes = 0; menu_height_entries = 0; pentry = pmenu->entry_first; while(pentry != NULL) { if(pentry->flags & MENU_NOTE) menu_height_notes += menu_entryheight(pentry); else menu_height_entries += menu_entryheight(pentry); pentry = pentry->next; } /* Add an extra line to pad the notes out, if there are any */ if(menu_height_notes != 0) menu_height_notes += 1; if(pmenu->logo) { menu_y_logo_top = (display_size_y - menu_height_entries - LOGO_HEIGHT) / 2; if(menu_y_logo_top < 0) menu_y_logo_top = 0; menu_y_logo_bottom = menu_y_logo_top + LOGO_HEIGHT; menu_y_min = display_size_y - menu_height_entries - 1; if(menu_y_min < menu_y_logo_bottom) menu_y_min = menu_y_logo_bottom; menu_y_max = display_size_y - 1; } else { menu_y_min = 4; menu_y_max = display_size_y - menu_height_notes - 1; if(pmenu->title != NULL) menu_y_min += 2; } /* If a full redraw, clear the screen and plot the title */ if(redraw == MENUREDRAW_ALL) { clear(); curs_set(0); if(pmenu->logo) { x = (display_size_x - 42) / 2; y = menu_y_logo_top; cp = colourpair_red; move(y, x); for(i = 0; i < strlen(logo); i ++) { addch(logo[i] | A_BOLD | COLOR_PAIR(cp)); switch(i % 42) { case 6: cp = colourpair_yellow; break; case 13: cp = colourpair_green; break; case 18: cp = colourpair_cyan; break; case 25: cp = colourpair_blue; break; case 33: cp = colourpair_magenta; break; case 41: y ++; move(y, x); cp = colourpair_red; break; } } y ++; } else { /* Display game title */ x = (display_size_x / 2) - strlen(title); attron(A_BOLD); for(i = 0; i < strlen(title); i ++) { if(i == 0) attron(COLOR_PAIR(colourpair_red)); if(i == 1) attron(COLOR_PAIR(colourpair_yellow)); if(i == 2) attron(COLOR_PAIR(colourpair_green)); if(i == 3) attron(COLOR_PAIR(colourpair_cyan)); if(i == 4) attron(COLOR_PAIR(colourpair_blue)); if(i == 5) attron(COLOR_PAIR(colourpair_magenta)); sprintf(buffer, "%c", title[i]); mvprintw(2, x, buffer); x +=2; } attroff(COLOR_PAIR(colourpair_red)); attroff(A_BOLD); /* Display menu title */ y = 4; x = (display_size_x / 2) - utf8strlen(pmenu->title); attron(A_BOLD); attroff(COLOR_PAIR(colourpair_white)); /* If the title is too long, plot normally */ if(x < 0) { x = (display_size_x - utf8strlen(pmenu->title)) / 2; mvprintw(y, x, pmenu->title); } else { /* Otherwise, spread it out */ for(i = 0; i < strlen(pmenu->title); i ++) { j = 0; buffer[j] = pmenu->title[i]; j ++; while((pmenu->title[i + j] & 0xc0) == 0x80) { buffer[j] = pmenu->title[i + j]; j ++; } buffer[j] = 0; mvprintw(y, x, buffer); x += 2; i += j - 1; } } attroff(A_BOLD); } } /* Keep scroll bar within reasonable limits */ if(pmenu->offset > (menu_height_entries - (menu_y_max - menu_y_min))) pmenu->offset = menu_height_entries - (menu_y_max - menu_y_min); if(pmenu->offset < 0) pmenu->offset = 0; /* Display scrollbar, if needed */ if(menu_height_entries > (menu_y_max - menu_y_min) || pmenu->offset != 0) { if(redraw >= MENUREDRAW_ENTRIES) { menu_scroll_y_min = menu_y_min + 1; menu_scroll_y_max = menu_y_max - 1; menu_scroll_top = menu_scroll_y_min + ((menu_scroll_y_max - menu_scroll_y_min) * pmenu->offset / menu_height_entries); menu_scroll_bottom = menu_scroll_top + ((menu_scroll_y_max - menu_scroll_y_min) * (1 + menu_y_max - menu_y_min) / menu_height_entries); attron(COLOR_PAIR(colourpair_menugrey)); x = menu_offset + menu_width + 1; mvaddch(menu_y_min, x, '^'); mvaddch(menu_y_max - 1, x, 'v'); attron(COLOR_PAIR(colourpair_white)); for(y = menu_y_min + 1; y < menu_y_max - 1; y ++) { /* >= and <= to guarantee at least one character scrollbar */ if(y >= menu_scroll_top && y <= menu_scroll_bottom) { attron(A_REVERSE); mvaddch(y, x, '|'); attroff(A_REVERSE); } else mvaddch(y, x, '.'); } } } /* Display notes */ if((menu_height_notes != 0) && redraw >= MENUREDRAW_ENTRIES) { if(pmenu->logo) y = menu_y_logo_bottom; else y = menu_y_max + 1; pentry = pmenu->entry_first; while(pentry != NULL) { if(pentry->flags & MENU_NOTE) { menu_displayentry(pmenu, pentry, y, 0); y += menu_entryheight(pentry); } pentry = pentry->next; } } /* Select an entry if the selected one is not on screen */ if(pmenu->entry_selected == NULL) pmenu->entry_selected = pmenu->entry_first; y = menu_y_min - pmenu->offset; pentry = pmenu->entry_first; pmenu->display_first = NULL; pmenu->display_last = NULL; state = 1; while(pentry != NULL) { /* Don't count notes */ if(pentry->flags & MENU_NOTE) { pentry = pentry->next; continue; } /* Is the entry off the top of the screen? */ if(y < (menu_y_min - menu_entryheight(pentry))) { if(pentry == pmenu->entry_selected) state = -1; y += menu_entryheight(pentry); pentry = pentry->next; continue; } /* Stop processing once we hit the bottom of the screen */ if(y > menu_y_max) { pentry = NULL; continue; } if(pentry == pmenu->entry_selected) state = 0; if(pmenu->display_first == NULL) pmenu->display_first = pentry->next; pmenu->display_last = pentry->previous; y += menu_entryheight(pentry); pentry = pentry->next; } if(state == -1) pmenu->entry_selected = pmenu->display_first; if(state == 1) pmenu->entry_selected = pmenu->display_last; if(state != 0 && redraw < MENUREDRAW_ENTRIES) redraw = MENUREDRAW_ENTRIES; /* Display entries */ pentry = pmenu->entry_first; y = menu_y_min - pmenu->offset; y_editing = 0; while(pentry != NULL) { /* Don't display notes */ if(pentry->flags & MENU_NOTE) { pentry = pentry->next; continue; } /* Don't display entries off the top of the screen */ if(y < (menu_y_min - menu_entryheight(pentry))) { y += menu_entryheight(pentry); pentry = pentry->next; continue; } /* Stop processing once we hit the bottom of the screen */ if(y > menu_y_max) { pentry = NULL; continue; } selected = (pmenu->entry_selected == pentry) ? 1 : 0; if(redraw >= MENUREDRAW_ENTRIES || (redraw >= MENUREDRAW_CHANGED && pentry->redraw)) menu_displayentry(pmenu, pentry, y, selected); if(pentry->flags & MENU_EDITING) y_editing = y + 1; pentry->redraw = 0; y += menu_entryheight(pentry); pentry = pentry->next; } /* Display cursor if we're editing a text field */ if(y_editing) { curs_set(1); move(y_editing, menu_offset + menu_width - 2); } else curs_set(0); /* Redraw the screen */ refresh(); } int menu_process(struct menu* pmenu) { int ok; int redraw; int c; struct menuentry* pentry; char *buffer; redraw = pmenu->redraw; ok = 0; while(!ok) { if(redraw != MENUREDRAW_NONE) { menu_display(pmenu, redraw); redraw = MENUREDRAW_NONE; } c = getch(); /* Are we editing a text field? */ if(pmenu->entry_selected != NULL && pmenu->entry_selected->flags & MENU_EDITING) { switch(c) { case KEY_RESIZE: getmaxyx(stdscr, display_size_y, display_size_x); redraw = MENUREDRAW_ALL; break; case '\n': case '\r': case 27: case KEY_UP: case KEY_DOWN: case KEY_PPAGE: case KEY_NPAGE: pmenu->entry_selected->flags -= MENU_EDITING; pmenu->entry_selected->redraw = 1; redraw = MENUREDRAW_CHANGED; break; case KEY_BACKSPACE: case KEY_DC: case 8: if(strlen(pmenu->entry_selected->text4) == 0) break; buffer = malloc(strlen(pmenu->entry_selected->text4) + 1); if(buffer != NULL) { strcpy(buffer, pmenu->entry_selected->text4); /* not particularly efficient, but does the job of deleting one UTF8 character */ while(strlen(buffer) > 0 && ((buffer[strlen(buffer) - 1] & 0xc0) == 0x80)) buffer[strlen(buffer) - 1] = 0; buffer[strlen(buffer) - 1] = 0; } menuentry_extratext(pmenu->entry_selected, NULL, NULL, buffer); free(buffer); pmenu->entry_selected->redraw = 1; redraw = MENUREDRAW_CHANGED; break; default: if(c > 31 && c != 127) { buffer = malloc(strlen(pmenu->entry_selected->text4) + 2); if(buffer != NULL) { strcpy(buffer, pmenu->entry_selected->text4); buffer[strlen(buffer) + 1] = 0; buffer[strlen(buffer)] = c; } menuentry_extratext(pmenu->entry_selected, NULL, NULL, buffer); free(buffer); pmenu->entry_selected->redraw = 1; redraw = MENUREDRAW_CHANGED; break; } break; } continue; } /* Not a text field, but a regular entry */ /* First, check if the key corresponds to an entry */ if(c >= 0 && c < 127) c = toupper(c); pentry = pmenu->entry_first; while(pentry != NULL) { if(pentry->key == c) { if(!(pentry->flags & (MENU_GREY | MENU_INVISIBLE | MENU_SPACE))) { if(pmenu->entry_selected != NULL) pmenu->entry_selected->redraw = 1; pmenu->entry_selected = pentry; pentry->redraw = 1; if(pmenu->entry_selected->flags & MENU_EDITABLE) { pmenu->entry_selected->flags |= MENU_EDITING; pmenu->entry_selected->redraw = 1; redraw = MENUREDRAW_CHANGED; } else ok = MENU_SELECT; break; } } pentry = pentry->next; } if(ok == MENU_SELECT) continue; if(c < 0) continue; /* Otherwise, see if it corresponds to an action */ switch(actions[c]) { case ACTION_UP: pentry = pmenu->entry_selected; while(pentry != NULL) { if(pentry == pmenu->display_first || pentry->flags & MENU_TEXT) redraw = MENUREDRAW_ENTRIES; pentry = pentry->previous; if(pentry == NULL) break; if(redraw != MENUREDRAW_NONE) pmenu->offset -= menu_entryheight(pentry); if(!(pentry->flags & (MENU_GREY | MENU_NOTE | MENU_SPACE))) break; } if(pentry != NULL) { pentry->redraw = 1; pmenu->entry_selected->redraw = 1; pmenu->entry_selected = pentry; if(redraw == MENUREDRAW_NONE) redraw = MENUREDRAW_CHANGED; } break; case ACTION_DOWN: pentry = pmenu->entry_selected; while(pentry != NULL) { if(pentry == pmenu->display_last || pentry->flags & MENU_TEXT) redraw = MENUREDRAW_ENTRIES; pentry = pentry->next; if(pentry == NULL) break; if(redraw != MENUREDRAW_NONE) pmenu->offset += menu_entryheight(pentry); if(!(pentry->flags & (MENU_GREY | MENU_NOTE | MENU_SPACE))) break; } if(pentry != NULL) { pentry->redraw = 1; pmenu->entry_selected->redraw = 1; pmenu->entry_selected = pentry; if(redraw == MENUREDRAW_NONE) redraw = MENUREDRAW_CHANGED; } break; case ACTION_LEFT: if(pmenu->entry_selected != NULL && pmenu->entry_selected->flags & MENU_SCROLLABLE) ok = MENU_SCROLLLEFT; break; case ACTION_RIGHT: if(pmenu->entry_selected != NULL && pmenu->entry_selected->flags & MENU_SCROLLABLE) ok = MENU_SCROLLRIGHT; break; case ACTION_PAGE_UP: pmenu->offset -= (menu_y_max - menu_y_min); redraw = MENUREDRAW_ENTRIES; break; case ACTION_PAGE_DOWN: pmenu->offset += (menu_y_max - menu_y_min); redraw = MENUREDRAW_ENTRIES; break; case ACTION_ENTER: if(pmenu->entry_selected != NULL) { if(pmenu->entry_selected->flags & MENU_EDITABLE) { pmenu->entry_selected->flags |= MENU_EDITING; pmenu->entry_selected->redraw = 1; redraw = MENUREDRAW_CHANGED; } else ok = MENU_SELECT; } break; case ACTION_DELETE: if(pmenu->entry_selected != NULL && pmenu->entry_selected->flags & MENU_DELETABLE) ok = MENU_DELETE; break; case ACTION_QUIT: ok = MENU_QUIT; break; case ACTION_REDRAW: getmaxyx(stdscr, display_size_y, display_size_x); redraw = MENUREDRAW_ALL; break; case ACTION_HIDE: display_hide(); redraw = MENUREDRAW_ALL; break; default: /* See if there is an "any key" entry */ pentry = pmenu->entry_first; while(pentry != NULL) { if(pentry->key == MENU_KEY_ANY) { if(!(pentry->flags & (MENU_GREY | MENU_INVISIBLE | MENU_SPACE))) { if(pmenu->entry_selected != NULL) pmenu->entry_selected->redraw = 1; pmenu->entry_selected = pentry; pentry->redraw = 1; ok = MENU_SELECT; break; } } pentry = pentry->next; } break; } } /* Redraw the whole menu when we next process it */ pmenu->redraw = MENUREDRAW_ALL; if(ok == MENU_SELECT) { if(pmenu->entry_selected->flags & MENU_SCROLLABLE) ok = MENU_SCROLLRIGHT; } /* unless this is a scrollable entry */ if(ok == MENU_SCROLLLEFT || ok == MENU_SCROLLRIGHT) { pmenu->redraw = MENUREDRAW_CHANGED; pmenu->entry_selected->redraw = 1; } return ok; } int menu_addfile(struct menu *pmenu, char *filename) { FILE *file; char word[256], buffer[4096]; char c; int i; int ok, wok; file = fopen(filename, "r"); if(file == NULL) return 0; ok = 0; strcpy(buffer, ""); while(!ok) { wok = 0; i = 0; while(!wok) { c = fgetc(file); if(c == 0 || c == 13 || c == 10 || c == 32 || c == 9 || feof(file) || i == 254) wok = 1; else word[i++] = c; } word[i] = 0; /* We assume menu_width is defined by the time we get here - a safe assumption provided this isn't the first menu we see. */ if(utf8strlen(buffer) + utf8strlen(word) < menu_width - 2 && !(strcmp(word, "") == 0 && (c == 10 || c == 13)) && !(strncmp(word, "===", 3) == 0)) { if(buffer[0] != 0 && word[0] != 0) strcat(buffer, " "); strcat(buffer, word); } else { menuentry_new(pmenu, buffer, 0, MENU_TEXT); if(strcmp(word, "") == 0 && (c == 10 || c ==13)) menuentry_new(pmenu, "", 0, MENU_TEXT); if(strncmp(word, "===", 3) == 0) { strcpy(buffer, ""); pmenu->entry_last->flags |= MENU_GREY; } else strcpy(buffer, word); } if(feof(file)) ok = 1; } menuentry_new(pmenu, buffer, 0, MENU_TEXT); fclose(file); return 1; }