Logo Search packages:      
Sourcecode: dahdi-tools version File versions  Download package

menuselect_curses.c

/*
 * Asterisk -- An open source telephony toolkit.
 *
 * Copyright (C) 2005 - 2006, Russell Bryant
 *
 * Russell Bryant <russell@digium.com>
 *
 * 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 <russell@digium.com>
 * 
 * \brief curses frontend for selection maintenance
 */

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <curses.h>

#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 depend *dep;
      struct conflict *con;
      struct use *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);

      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);
      }

}

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->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 - 6;
      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);
}

00457 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
};

00555 struct blip {
      enum blip_type type;
      int x;
      int y;
      int ox;
      int oy;
      int goingleft;
      AST_LIST_ENTRY(blip) entry;
};

static AST_LIST_HEAD_NOLOCK(, blip) blips;

static int score = 0;
static int num_aliens = 0;
struct blip *tank = NULL;

/*! Probability of a bomb, out of 100 */
#define BOMB_PROB   1

static int init_blips(void)
{
      int i, j;
      struct blip *cur;

      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) + 1;
                  AST_LIST_INSERT_HEAD(&blips, cur, entry);
                  num_aliens++;
            }
      }

      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';
      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 move_aliens(void)
{
      struct blip *cur;

      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;
            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;

      AST_LIST_TRAVERSE(&blips, cur, entry) {
            if (cur->type != BLIP_BOMB)
                  continue;
            cur->y++;
            if (cur->x == tank->x && cur->y == tank->y)
                  return 1;
      }

      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 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 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)
                  continue;
            if (cur->x == shot->x && cur->y == shot->y) {
                  score += 20;
                  remove_blip(shot);
                  remove_blip(cur);
                  if (!num_aliens) {
                        game_over(1);
                        return 1;
                  }
            }
      }

      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;

      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)
                  break;
            if (!(jiffies % 25)) {
                  if (move_aliens() || move_bombs()) {
                        game_over(0);
                        break;
                  }
                  if (check_placement())
                        break;
            }
            if (!(jiffies % 10)) {
                  move_shots();
                  if (check_placement())
                        break;
            }
            repaint_screen();
            jiffies++;
            usleep(1000);
      }

      while ((blip = AST_LIST_REMOVE_HEAD(&blips, entry)))
            free(blip);

      nodelay(stdscr, FALSE);
}

Generated by  Doxygen 1.6.0   Back to index