/* * Asterisk -- An open source telephony toolkit. * * Copyright (C) 2005 - 2006, Russell Bryant * * Russell Bryant * * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2. See the LICENSE file * at the top of the source tree. */ /* * \file * * \author Russell Bryant * * \brief curses frontend for selection maintenance */ #include #include #include #include #include #include #include #include #include "menuselect.h" #define MENU_HELP "Press 'h' for help." #define TITLE_HEIGHT 7 #define MIN_X 80 #define MIN_Y 27 #define PAGE_OFFSET 10 #define SCROLL_NONE 0 #define SCROLL_DOWN 1 #define SCROLL_DOWN_INDICATOR "... More ..." #define MIN(a, b) ({ typeof(a) __a = (a); typeof(b) __b = (b); ((__a > __b) ? __b : __a);}) #define MAX(a, b) ({ typeof(a) __a = (a); typeof(b) __b = (b); ((__a < __b) ? __b : __a);}) extern int changes_made; /*! Maximum number of characters horizontally */ static int max_x = 0; /*! Maximum number of characters vertically */ static int max_y = 0; static const char * const help_info[] = { "scroll => up/down arrows", "toggle selection => Enter", "select => y", "deselect => n", "select all => F8", "deselect all => F7", "back => left arrow", "quit => q", "save and quit => x", "", "XXX means dependencies have not been met", " or a conflict exists", "", "< > means a dependency has been deselected", " and will be automatically re-selected", " if this item is selected", "", "( ) means a conflicting item has been", " selected", }; /*! \brief Handle a window resize in xterm */ static void _winch_handler(int sig) { getmaxyx(stdscr, max_y, max_x); if (max_x < MIN_X || max_y < MIN_Y) { fprintf(stderr, "Terminal must be at least %d x %d.\n", MIN_X, MIN_Y); max_x = MIN_X - 1; max_y = MIN_Y - 1; } } static struct sigaction winch_handler = { .sa_handler = _winch_handler, }; /*! \brief Handle a SIGQUIT */ static void _sigint_handler(int sig) { } static struct sigaction sigint_handler = { .sa_handler = _sigint_handler, }; /*! \brief Display help information */ static void show_help(WINDOW *win) { int i; wclear(win); for (i = 0; i < (sizeof(help_info) / sizeof(help_info[0])); i++) { wmove(win, i, max_x / 2 - 15); waddstr(win, (char *) help_info[i]); } wrefresh(win); getch(); /* display the help until the user hits a key */ } static int really_quit(WINDOW *win) { int c; wclear(win); wmove(win, 2, max_x / 2 - 15); waddstr(win, "ARE YOU SURE?"); wmove(win, 3, max_x / 2 - 12); waddstr(win, "--- It appears you have made some changes, and"); wmove(win, 4, max_x / 2 - 12); waddstr(win, "you have opted to Quit without saving these changes!"); wmove(win, 6, max_x / 2 - 12); waddstr(win, " Please Enter Y to exit without saving;"); wmove(win, 7, max_x / 2 - 12); waddstr(win, " Enter N to cancel your decision to quit,"); wmove(win, 8, max_x / 2 - 12); waddstr(win, " and keep working in menuselect, or"); wmove(win, 9, max_x / 2 - 12); waddstr(win, " Enter S to save your changes, and exit"); wmove(win, 10, max_x / 2 - 12); wrefresh(win); while ((c=getch())) { if (c == 'Y' || c == 'y') { c = 'q'; break; } if (c == 'S' || c == 's') { c = 'S'; break; } if (c == 'N' || c == 'n') { c = '%'; break; } } return c; } static void draw_main_menu(WINDOW *menu, int curopt) { struct category *cat; char buf[64]; int i = 0; wclear(menu); AST_LIST_TRAVERSE(&categories, cat, list) { wmove(menu, i++, max_x / 2 - 10); snprintf(buf, sizeof(buf), " %s", strlen_zero(cat->displayname) ? cat->name : cat->displayname); waddstr(menu, buf); } wmove(menu, curopt, (max_x / 2) - 15); waddstr(menu, "--->"); wmove(menu, 0, 0); wrefresh(menu); } static void display_mem_info(WINDOW *menu, struct member *mem, int start, int end) { char buf[64]; struct reference *dep; struct reference *con; struct reference *use; wmove(menu, end - start + 2, max_x / 2 - 16); wclrtoeol(menu); wmove(menu, end - start + 3, max_x / 2 - 16); wclrtoeol(menu); wmove(menu, end - start + 4, max_x / 2 - 16); wclrtoeol(menu); wmove(menu, end - start + 5, max_x / 2 - 16); wclrtoeol(menu); wmove(menu, end - start + 6, max_x / 2 - 16); wclrtoeol(menu); if (mem->displayname) { wmove(menu, end - start + 2, max_x / 2 - 16); waddstr(menu, (char *) mem->displayname); } if (!AST_LIST_EMPTY(&mem->deps)) { wmove(menu, end - start + 3, max_x / 2 - 16); strcpy(buf, "Depends on: "); AST_LIST_TRAVERSE(&mem->deps, dep, list) { strncat(buf, dep->displayname, sizeof(buf) - strlen(buf) - 1); strncat(buf, dep->member ? "(M)" : "(E)", sizeof(buf) - strlen(buf) - 1); if (AST_LIST_NEXT(dep, list)) strncat(buf, ", ", sizeof(buf) - strlen(buf) - 1); } waddstr(menu, buf); } if (!AST_LIST_EMPTY(&mem->uses)) { wmove(menu, end - start + 4, max_x / 2 - 16); strcpy(buf, "Can use: "); AST_LIST_TRAVERSE(&mem->uses, use, list) { strncat(buf, use->displayname, sizeof(buf) - strlen(buf) - 1); strncat(buf, use->member ? "(M)" : "(E)", sizeof(buf) - strlen(buf) - 1); if (AST_LIST_NEXT(use, list)) strncat(buf, ", ", sizeof(buf) - strlen(buf) - 1); } waddstr(menu, buf); } if (!AST_LIST_EMPTY(&mem->conflicts)) { wmove(menu, end - start + 5, max_x / 2 - 16); strcpy(buf, "Conflicts with: "); AST_LIST_TRAVERSE(&mem->conflicts, con, list) { strncat(buf, con->displayname, sizeof(buf) - strlen(buf) - 1); strncat(buf, con->member ? "(M)" : "(E)", sizeof(buf) - strlen(buf) - 1); if (AST_LIST_NEXT(con, list)) strncat(buf, ", ", sizeof(buf) - strlen(buf) - 1); } waddstr(menu, buf); } if (!mem->is_separator) { /* Separators lack support levels */ { /* support level */ wmove(menu, end - start + 6, max_x / 2 - 16); snprintf(buf, sizeof(buf), "Support Level: %s", mem->support_level); if (mem->replacement && *mem->replacement) { char buf2[64]; snprintf(buf2, sizeof(buf2), ", Replaced by: %s", mem->replacement); strncat(buf, buf2, sizeof(buf) - strlen(buf) - 1); } waddstr(menu, buf); } } } static void draw_category_menu(WINDOW *menu, struct category *cat, int start, int end, int curopt, int changed, int flags) { int i = 0; int j = 0; struct member *mem; char buf[64]; if (!changed) { /* If all we have to do is move the cursor, * then don't clear the screen and start over */ AST_LIST_TRAVERSE(&cat->members, mem, list) { i++; if (curopt + 1 == i) { display_mem_info(menu, mem, start, end); break; } } wmove(menu, curopt - start, max_x / 2 - 9); wrefresh(menu); return; } wclear(menu); i = 0; AST_LIST_TRAVERSE(&cat->members, mem, list) { if (i < start) { i++; continue; } wmove(menu, j++, max_x / 2 - 10); i++; if ((mem->depsfailed == HARD_FAILURE) || (mem->conflictsfailed == HARD_FAILURE)) { snprintf(buf, sizeof(buf), "XXX %s", mem->name); } else if (mem->is_separator) { snprintf(buf, sizeof(buf), " --- %s ---", mem->name); } else if (mem->depsfailed == SOFT_FAILURE) { snprintf(buf, sizeof(buf), "<%s> %s", mem->enabled ? "*" : " ", mem->name); } else if (mem->conflictsfailed == SOFT_FAILURE) { snprintf(buf, sizeof(buf), "(%s) %s", mem->enabled ? "*" : " ", mem->name); } else { snprintf(buf, sizeof(buf), "[%s] %s", mem->enabled ? "*" : " ", mem->name); } waddstr(menu, buf); if (curopt + 1 == i) display_mem_info(menu, mem, start, end); if (i == end - (flags & SCROLL_DOWN ? 1 : 0)) break; } if (flags & SCROLL_DOWN) { wmove(menu, j, max_x / 2 - sizeof(SCROLL_DOWN_INDICATOR) / 2); waddstr(menu, SCROLL_DOWN_INDICATOR); } wmove(menu, curopt - start, max_x / 2 - 9); wrefresh(menu); } static void play_space(void); static int move_up(int *current, int itemcount, int delta, int *start, int *end, int scroll) { if (*current > 0) { *current = MAX(*current - delta, 0); if (*current < *start) { int diff = *start - MAX(*start - delta, 0); *start -= diff; *end -= diff; return 1; } } return 0; } static int move_down(int *current, int itemcount, int delta, int *start, int *end, int scroll) { if (*current < itemcount) { *current = MIN(*current + delta, itemcount); if (*current > *end - 1 - (scroll & SCROLL_DOWN ? 1 : 0)) { int diff = MIN(*end + delta - 1, itemcount) - *end + 1; *start += diff; *end += diff; return 1; } } return 0; } static int run_category_menu(WINDOW *menu, int cat_num) { struct category *cat; int i = 0; int start = 0; int end = max_y - TITLE_HEIGHT - 8; int c; int curopt = 0; int maxopt; int changed = 1; int scroll = SCROLL_NONE; AST_LIST_TRAVERSE(&categories, cat, list) { if (i++ == cat_num) break; } if (!cat) return -1; maxopt = count_members(cat) - 1; if (maxopt > end) { scroll = SCROLL_DOWN; } draw_category_menu(menu, cat, start, end, curopt, changed, scroll); while ((c = getch())) { changed = 0; switch (c) { case KEY_UP: changed = move_up(&curopt, maxopt, 1, &start, &end, scroll); break; case KEY_DOWN: changed = move_down(&curopt, maxopt, 1, &start, &end, scroll); break; case KEY_PPAGE: changed = move_up( &curopt, maxopt, MIN(PAGE_OFFSET, max_y - TITLE_HEIGHT - 6 - (scroll & SCROLL_DOWN ? 1 : 0)), &start, &end, scroll); break; case KEY_NPAGE: changed = move_down( &curopt, maxopt, MIN(PAGE_OFFSET, max_y - TITLE_HEIGHT - 6 - (scroll & SCROLL_DOWN ? 1 : 0)), &start, &end, scroll); break; case KEY_HOME: changed = move_up(&curopt, maxopt, curopt, &start, &end, scroll); break; case KEY_END: changed = move_down(&curopt, maxopt, maxopt - curopt, &start, &end, scroll); break; case KEY_LEFT: case 27: /* Esc key */ return 0; case KEY_RIGHT: case KEY_ENTER: case '\n': case ' ': toggle_enabled_index(cat, curopt); changed = 1; break; case 'y': case 'Y': set_enabled(cat, curopt); changed = 1; break; case 'n': case 'N': clear_enabled(cat, curopt); changed = 1; break; case 'h': case 'H': show_help(menu); changed = 1; break; case KEY_F(7): set_all(cat, 0); changed = 1; break; case KEY_F(8): set_all(cat, 1); changed = 1; default: break; } if (c == 'x' || c == 'X' || c == 'Q' || c == 'q') break; if (end <= maxopt) { scroll |= SCROLL_DOWN; } else { scroll &= ~SCROLL_DOWN; } draw_category_menu(menu, cat, start, end, curopt, changed, scroll); } wrefresh(menu); return c; } static void draw_title_window(WINDOW *title) { char titlebar[strlen(menu_name) + 9]; memset(titlebar, '*', sizeof(titlebar) - 1); titlebar[sizeof(titlebar) - 1] = '\0'; wclear(title); wmove(title, 1, (max_x / 2) - (strlen(titlebar) / 2)); waddstr(title, titlebar); wmove(title, 2, (max_x / 2) - (strlen(menu_name) / 2)); waddstr(title, (char *) menu_name); wmove(title, 3, (max_x / 2) - (strlen(titlebar) / 2)); waddstr(title, titlebar); wmove(title, 5, (max_x / 2) - (strlen(MENU_HELP) / 2)); waddstr(title, MENU_HELP); wrefresh(title); } int run_menu(void) { WINDOW *title; WINDOW *menu; int maxopt; int curopt = 0; int c; int res = 0; setenv("ESCDELAY", "0", 1); /* So that ESC is processed immediately */ initscr(); getmaxyx(stdscr, max_y, max_x); sigaction(SIGWINCH, &winch_handler, NULL); /* handle window resizing in xterm */ sigaction(SIGINT, &sigint_handler, NULL); /* handle window resizing in xterm */ if (max_x < MIN_X || max_y < MIN_Y) { fprintf(stderr, "Terminal must be at least %d x %d.\n", MIN_X, MIN_Y); endwin(); return -1; } cbreak(); /* don't buffer input until the enter key is pressed */ noecho(); /* don't echo user input to the screen */ keypad(stdscr, TRUE); /* allow the use of arrow keys */ clear(); refresh(); maxopt = count_categories() - 1; /* We have two windows - the title window at the top, and the menu window gets the rest */ title = newwin(TITLE_HEIGHT, max_x, 0, 0); menu = newwin(max_y - TITLE_HEIGHT, max_x, TITLE_HEIGHT, 0); draw_title_window(title); draw_main_menu(menu, curopt); while ((c = getch())) { switch (c) { case KEY_UP: if (curopt > 0) curopt--; break; case KEY_DOWN: if (curopt < maxopt) curopt++; break; case KEY_HOME: curopt = 0; break; case KEY_END: curopt = maxopt; break; case KEY_RIGHT: case KEY_ENTER: case '\n': case ' ': c = run_category_menu(menu, curopt); break; case 'h': case 'H': show_help(menu); break; case 'i': case 'I': play_space(); draw_title_window(title); default: break; } if (c == 'q' || c == 'Q' || c == 27 || c == 3) { if (changes_made) { c = really_quit(menu); if (c == 'q') { res = -1; break; } } else { res = -1; break; } } if (c == 'x' || c == 'X' || c == 's' || c == 'S') break; draw_main_menu(menu, curopt); } endwin(); return res; } enum blip_type { BLIP_TANK = 0, BLIP_SHOT, BLIP_BOMB, BLIP_ALIEN, BLIP_BARRIER, BLIP_UFO }; struct blip { enum blip_type type; int x; int y; int ox; int oy; int goingleft; int health; AST_LIST_ENTRY(blip) entry; }; static AST_LIST_HEAD_NOLOCK(, blip) blips; static int respawn = 0; static int score = 0; static int num_aliens = 0; static int alien_sleeptime = 0; struct blip *ufo = NULL; struct blip *tank = NULL; /*! Probability of a bomb, out of 100 */ #define BOMB_PROB 1 static int add_barrier(int x, int y) { struct blip *cur = NULL; cur = calloc(1,sizeof(struct blip)); if(!cur) { return -1; } cur->type=BLIP_BARRIER; cur->x = x; cur->y=max_y - y; cur->health = 1; AST_LIST_INSERT_HEAD(&blips, cur,entry); return 0; } static int init_blips(void) { int i, j; struct blip *cur; int offset = 4; srandom(time(NULL) + getpid()); /* make tank */ cur = calloc(1, sizeof(struct blip)); if (!cur) return -1; cur->type = BLIP_TANK; cur->x = max_x / 2; cur->y = max_y - 1; AST_LIST_INSERT_HEAD(&blips, cur, entry); tank = cur; /* 3 rows of 10 aliens */ num_aliens = 0; for (i = 0; i < 3; i++) { for (j = 0; j < 10; j++) { cur = calloc(1, sizeof(struct blip)); if (!cur) return -1; cur->type = BLIP_ALIEN; cur->x = (j * 2) + 1; cur->y = (i * 2) + 2; AST_LIST_INSERT_HEAD(&blips, cur, entry); num_aliens++; } } for(i=0; i < 4; i++) { if (i > 0) offset += 5 + ((max_x) -28) / 3; add_barrier(offset + 1, 6); add_barrier(offset + 2, 6); add_barrier(offset + 3, 6); add_barrier(offset, 5); add_barrier(offset + 1, 5); add_barrier(offset + 2, 5); add_barrier(offset + 3, 5); add_barrier(offset + 4, 5); add_barrier(offset, 4); add_barrier(offset + 1, 4); add_barrier(offset + 3, 4); add_barrier(offset + 4, 4); } return 0; } static inline chtype type2chtype(enum blip_type type) { switch (type) { case BLIP_TANK: return 'A'; case BLIP_ALIEN: return 'X'; case BLIP_SHOT: return '|'; case BLIP_BOMB: return 'o'; case BLIP_BARRIER: return '*'; case BLIP_UFO: return '@'; default: break; } return '?'; } static int repaint_screen(void) { struct blip *cur; wmove(stdscr, 0, 0); wprintw(stdscr, "Score: %d", score); AST_LIST_TRAVERSE(&blips, cur, entry) { if (cur->x != cur->ox || cur->y != cur->oy) { wmove(stdscr, cur->oy, cur->ox); waddch(stdscr, ' '); wmove(stdscr, cur->y, cur->x); waddch(stdscr, type2chtype(cur->type)); cur->ox = cur->x; cur->oy = cur->y; } } wmove(stdscr, 0, max_x - 1); wrefresh(stdscr); return 0; } static int tank_move_left(void) { if (tank->x > 0) tank->x--; return 0; } static int tank_move_right(void) { if (tank->x < (max_x - 1)) tank->x++; return 0; } static int count_shots(void) { struct blip *cur; int count = 0; AST_LIST_TRAVERSE(&blips, cur, entry) { if (cur->type == BLIP_SHOT) count++; } return count; } static int tank_shoot(void) { struct blip *shot; if (count_shots() == 3) return 0; score--; shot = calloc(1, sizeof(struct blip)); if (!shot) return -1; shot->type = BLIP_SHOT; shot->x = tank->x; shot->y = max_y - 2; AST_LIST_INSERT_HEAD(&blips, shot, entry); return 0; } static int remove_blip(struct blip *blip) { if (!blip) { return -1; } AST_LIST_REMOVE(&blips, blip, entry); if (blip->type == BLIP_ALIEN) { num_aliens--; } wmove(stdscr, blip->oy, blip->ox); waddch(stdscr, ' '); free(blip); return 0; } static int move_aliens(void) { struct blip *cur; struct blip *current_barrier; AST_LIST_TRAVERSE(&blips, cur, entry) { if (cur->type != BLIP_ALIEN) { /* do nothing if it's not an alien */ continue; } if (cur->goingleft && (cur->x == 0)) { cur->y++; cur->goingleft = 0; } else if (!cur->goingleft && cur->x == (max_x - 1)) { cur->y++; cur->goingleft = 1; } else if (cur->goingleft) { cur->x--; } else { cur->x++; } /* Alien into the tank == game over */ if (cur->x == tank->x && cur->y == tank->y) return 1; AST_LIST_TRAVERSE(&blips, current_barrier, entry){ if(current_barrier->type!=BLIP_BARRIER) continue; if(cur->y == current_barrier->y && cur->x == current_barrier -> x) remove_blip(current_barrier); } if (random() % 100 < BOMB_PROB && cur->y != max_y) { struct blip *bomb = calloc(1, sizeof(struct blip)); if (!bomb) continue; bomb->type = BLIP_BOMB; bomb->x = cur->x; bomb->y = cur->y + 1; AST_LIST_INSERT_HEAD(&blips, bomb, entry); } } return 0; } static int move_bombs(void) { struct blip *cur; struct blip *current_barrier; AST_LIST_TRAVERSE(&blips, cur, entry) { int mark = 0; if (cur->type != BLIP_BOMB) continue; cur->y++; if (cur->x == tank->x && cur->y == tank->y) { return 1; } AST_LIST_TRAVERSE(&blips, current_barrier, entry) { if (current_barrier->type != BLIP_BARRIER) continue; if (cur->x == current_barrier->x && cur->y == current_barrier->y) { mark = 1; current_barrier->health--; if (current_barrier->health == 0) remove_blip(current_barrier); } } if (mark){ remove_blip(cur);} } return 0; } static void move_shots(void) { struct blip *cur; AST_LIST_TRAVERSE(&blips, cur, entry) { if (cur->type != BLIP_SHOT) continue; cur->y--; } } static int ufo_action() { struct blip *cur; AST_LIST_TRAVERSE(&blips, cur, entry) { if (cur->type != BLIP_UFO) { continue; } cur->x--; if (cur->x < 0) { remove_blip(cur); respawn += 1; } } if (respawn == 7) { respawn = 0; /* make new mothership*/ cur = calloc(1, sizeof(struct blip)); if(!cur) return -1; cur->type = BLIP_UFO; cur->x = max_x - 1; cur->y = 1; AST_LIST_INSERT_HEAD(&blips, cur, entry); } return 0; } static void game_over(int win) { clear(); wmove(stdscr, max_y / 2, max_x / 2 - 10); wprintw(stdscr, "Game over! You %s!", win ? "win" : "lose"); wmove(stdscr, 0, max_x - 1); wrefresh(stdscr); sleep(1); while (getch() != ' '); return; } static int check_shot(struct blip *shot) { struct blip *cur; AST_LIST_TRAVERSE(&blips, cur, entry) { if ((cur->type == BLIP_ALIEN || cur->type == BLIP_UFO) && cur->x == shot->x && cur->y == shot->y){ if (cur->type == BLIP_UFO) { score += 80; } score += 20; remove_blip(cur); remove_blip(shot); respawn += 1; if (!num_aliens) { if(alien_sleeptime < 101) { game_over(1); return 1; } else { alien_sleeptime = alien_sleeptime - 100; return 1; } } break; } if (cur->type == BLIP_BARRIER) { if (shot->x == cur->x && shot->y == cur->y) { remove_blip(cur); remove_blip(shot); break; } } } return 0; } static int check_placement(void) { struct blip *cur; AST_LIST_TRAVERSE_SAFE_BEGIN(&blips, cur, entry) { if (cur->y <= 0 || cur->y >= max_y) { AST_LIST_REMOVE_CURRENT(&blips, entry); remove_blip(cur); } else if (cur->type == BLIP_SHOT && check_shot(cur)) return 1; } AST_LIST_TRAVERSE_SAFE_END return 0; } static void play_space(void) { int c; unsigned int jiffies = 1; int quit = 0; struct blip *blip; alien_sleeptime = 1000; score = 0; while(alien_sleeptime > 100) { jiffies = 1; clear(); nodelay(stdscr, TRUE); init_blips(); repaint_screen(); for (;;) { c = getch(); switch (c) { case ' ': tank_shoot(); break; case KEY_LEFT: tank_move_left(); break; case KEY_RIGHT: tank_move_right(); break; case 'x': case 'X': case 'q': case 'Q': quit = 1; default: /* ignore unknown input */ break; } if (quit) { alien_sleeptime = 1; break; } if (!(jiffies % 25)) { if (move_aliens() || move_bombs() || ufo_action()) { alien_sleeptime = 1; game_over(0); break; } if (check_placement()) break; } if (!(jiffies % 10)) { move_shots(); if (check_placement()) break; } repaint_screen(); jiffies++; usleep(alien_sleeptime); } while ((blip = AST_LIST_REMOVE_HEAD(&blips, entry))) free(blip); } nodelay(stdscr, FALSE); }