Ncurses: Menüs

Aus Wikibooks


<<< ncurses-Startseite ncurses << Inhaltsverzeichnis
< Panels Formulare >


Ein ncurses-Menü ist ein Menü in der ursprünglichen Bedeutung, also eine einfache Auswahlbox. Allerdings bietet auch die menu-Bibliothek genügend Möglichkeiten um diese Auswahlboxen sehr schön und sinnvoll auszugestalten.

Ein Menü erzeugen und wieder löschen[Bearbeiten]

Die Erzeugung eines Menüs untergliedert sich in folgende Schritte:

  • Speicherplatz für die Items (Menüeinträge) reservieren: calloc-Funktion
  • Items erzeugen: ITEM *new_item(const char *name, const char *description);
  • Menü erzeugen: MENU *new_menu(ITEM **items);
  • Menü "posten" (post = anheften, ankleben -> "PostIt") : int post_menu(MENU *menu);

Das "Abbauen" eines Menüs geschieht in umgekehrter Reihenfolge:

  • Menü "unposten": int unpost_menu(MENU *menu);
  • Menü freigeben: int free_menu(MENU *menu);
  • Items freigeben: int free_item(ITEM *item);
  • Reservierten Items-Speicherplatz freigeben: free-Funktion

Der Menü-Treiber[Bearbeiten]

Eingabeereignisse für das Menü können mit der Funktion

int menu_driver(MENU *menu, int c);

abgehandelt werden.

Der Parameter c bstimmt, welche Menüaktion durchgeführt werden soll:

REQ_LEFT_ITEM bewegt den Menücursor um einen Eintrag nach links
REQ_RIGHT_ITEM bewegt den Menücursor um einen Eintrag nach rechts
REQ_UP_ITEM bewegt den Menücursor um einen Eintrag nach oben
REQ_DOWN_ITEM bewegt den Menücursor um einen Eintrag nach unten
REQ_SCR_ULINE eine Zeile aufwärts scrollen
REQ_SCR_DLINE eine Zeile abwärts scrollen
REQ_SCR_UPAGE eine Seite aufwärts scrollen
REQ_SCR_DPAGE eine Seite abwärts scrollen
REQ_FIRST_ITEM bewegt den Menücursor zum ersten Eintrag
REQ_LAST_ITEM bewegt den Menücursor zum letzten Eintrag
REQ_NEXT_ITEM bewegt den Menücursor zum nächsten Eintrag
REQ_PREV_ITEM bewegt den Menücursor zum vorherigen Eintrag
REQ_TOGGLE_ITEM An- oder abwählen eines Eintrags
REQ_CLEAR_PATTERN Suchmusterpuffer löschen
REQ_BACK_PATTERN Das vorherige Zeichen aus dem Suchmusterpuffer löschen
REQ_NEXT_MATCH Menücursor zum nächsten Eintrag, der zum Suchmuster passt, bewegen
REQ_PREV_MATCH Menücursor zum vorigen Eintrag, der zum Suchmuster passt, bewegen

Den aktuell angewählten Menüeintrag ermitteln[Bearbeiten]

ITEM *current_item(const MENU *menu);
int item_index(const ITEM *item);

Beispiel[Bearbeiten]

#include <menu.h>
#include <stdlib.h>

ITEM **it;
MENU *me;

void quit(void)
{
  int i;

  unpost_menu(me);
  free_menu(me);

  for(i=0; i<=4; i++)
  {
    free_item(it[i]);
  }

  free(it);
  endwin();
}

int main(void)
{
  int ch;

  initscr();
  atexit(quit);
  clear();
  noecho();
  curs_set(0);
  cbreak();
  nl();
  keypad(stdscr, TRUE);

  it = (ITEM **)calloc(5, sizeof(ITEM *));
  it[0] = new_item("M1", "");
  it[1] = new_item("M2", "");
  it[2] = new_item("M3", "");
  it[3] = new_item("Ende", "");
  it[4] = 0;
  me = new_menu(it);
  post_menu(me);	

  mvaddstr(7, 3, "Programm mittels Menü oder F1-Funktionstaste beenden");
  refresh();

  while((ch=getch()) != KEY_F(1))
  {
    switch(ch)
    {
      case KEY_DOWN:
        menu_driver(me, REQ_DOWN_ITEM);
        break;
      case KEY_UP:
        menu_driver(me, REQ_UP_ITEM);
        break;
      case 0xA: /* Return- bzw. Enter-Taste -> ASCII-Code */
        if(item_index(current_item(me)) == 3)
          exit(0);	
    }
  } 

  return (0);  
}

Compilieren, Linken:

gcc -o bsp bsp.c -lmenu -lncurses

Das Menü formatieren[Bearbeiten]

Ein Menü ist standardmäßig bis zu 16 Zeilen hoch und 1 Spalte breit. Zur Einstellung einer anderen Größe, z.B. zur Generierung eines mehrspaltigen Menüs, existiert die Funktion

int set_menu_format(MENU *menu, int rows, int cols);

Diese Funktion muss im Bedarfsfall aufgerufen werden, bevor das Menu gepostet wird.

Beispiel[Bearbeiten]

#include <menu.h>
#include <stdlib.h>

ITEM **it;
MENU *me;

void quit(void)
{
  int i;

  unpost_menu(me);
  free_menu(me);

  for(i=0; i<=4; i++)
  {
    free_item(it[i]);
  }

  free(it);
  endwin();
}

int main(void)
{
  int ch;

  initscr();
  atexit(quit);
  clear();
  noecho();
  curs_set(0);
  cbreak();
  nl();
  keypad(stdscr, TRUE);

  it = (ITEM **)calloc(5, sizeof(ITEM *));
  it[0] = new_item("M1", "");
  it[1] = new_item("M2", "");
  it[2] = new_item("M3", "");
  it[3] = new_item("Ende", "");
  it[4] = 0;
  me = new_menu(it);
  set_menu_format(me, 2, 2);
  post_menu(me);	

  mvaddstr(7, 3, "Programm mittels Menü oder F1-Funktionstaste beenden");
  refresh();

  while((ch=getch()) != KEY_F(1))
  {
    switch(ch)
    {
      case KEY_DOWN:
        menu_driver(me, REQ_DOWN_ITEM);
        break;
      case KEY_UP:
        menu_driver(me, REQ_UP_ITEM);
        break;
      case KEY_RIGHT:
        menu_driver(me, REQ_RIGHT_ITEM);
        break;
      case KEY_LEFT:
        menu_driver(me, REQ_LEFT_ITEM);
        break;				
      case 0xA: /* Return- bzw. Enter-Taste -> ASCII-Code */
        if(item_index(current_item(me)) == 3)
          exit(0);	
    }
  }   

  return (0);  
}

Das Markierungssymbol[Bearbeiten]

Zwecks besserer Sichtbarkeit wird vor dem ausgewählten Menüeintrag zusätzlich ein Zeichen oder eine Zeichenkette als Markierungssymbol gesetzt. Normalerweise ist dies ein Bindestrich. Dieses Verhalten kann aber mit der Funktion

int set_menu_mark(MENU *menu, const char *mark);

geändert werden. Diese Funktion wird nur dann das gewünschte Resultat liefern, wenn sie vor dem Posten des Menüs aufgerufen wird.

Beispiel (Programmausschnitt)[Bearbeiten]

  // ...
  set_menu_mark(me, "-->");
  post_menu(me);
  // ... 

Beispiel (Programmausschnitt)[Bearbeiten]

  // Ausschalten des Markierungssysmbols
  //...
  set_menu_mark(me, "");
  post_menu(me);
  // ... 

Das Markierungssymbol sollte aber nur dann ausgeschaltet werden, wenn absolut sichergestellt ist, dass das Programm nur auf Terminals mit "Highlighting"- oder Farbunterstützung eingesetzt wird. Das "Erraten" der jeweiligen Menücursorposition kann sich sonst sehr nervenaufreibend gestalten.

Menüfenster[Bearbeiten]

Jedes Menü kann mit einem Haupt- und Unterfenster verknüpft werden. Das Hauptfenster kann z.B. zur Aufnahme eines Menütitels und zur Umrahmung des Unterfensters dienen. Im Unterfenster werden die Menüeinträge dargestellt.

int set_menu_win(MENU *menu, WINDOW *win);
int set_menu_sub(MENU *menu, WINDOW *sub);

Werden diese Funktionen in einem Programm nicht eingesetzt, so sind die Menüs mit dem Standardscreen verbunden.

Beispiel[Bearbeiten]

#include <menu.h>
#include <stdlib.h>

ITEM   **it;
MENU   *me;
WINDOW *win;

void quit(void)
{
  int i;

  unpost_menu(me);
  free_menu(me);

  for(i=0; i<=4; i++)
  {
    free_item(it[i]);
  }

  free(it);
  delwin(win);
  endwin();
}

int main(void)
{
  int ch;

  initscr();
  atexit(quit);
  clear();
  noecho();
  curs_set(0);
  cbreak();
  nl();
  keypad(stdscr, TRUE);

  it = (ITEM **)calloc(5, sizeof(ITEM *));
  it[0] = new_item("M1", "Menueeintrag 1");
  it[1] = new_item("M2", "Menueeintrag 2");
  it[2] = new_item("M3", "Menueeintrag 3");
  it[3] = new_item("Ende", "Programm beenden");
  it[4] = 0;
  me = new_menu(it);

  win = newwin(8, 30, 5, 5);
  set_menu_win (me, win);
  set_menu_sub (me, derwin(win, 4, 28, 3, 2));
  box(win, 0, 0);  
  mvwaddstr(win, 1, 2, "***** Testmenü *****");
  post_menu(me);        

  mvaddstr(14, 3, "Programm mittels Menü oder F1-Funktionstaste beenden");

  refresh();
  wrefresh(win);

  while((ch=getch()) != KEY_F(1))
  {
    switch(ch)
    {
      case KEY_DOWN:
        menu_driver(me, REQ_DOWN_ITEM);
        break;
      case KEY_UP:
        menu_driver(me, REQ_UP_ITEM);
        break;
      case 0xA: /* Return- bzw. Enter-Taste -> ASCII-Code */
        if(item_index(current_item(me)) == 3)
          exit(0);      
    }

    wrefresh(win);
  }   

  return (0);  
}

Die minimal notwendige Größe eines Unterfensters kann über die Funktion

int scale_menu(const MENU *menu, int *rows, int *columns);

ermittelt werden.

Menüs bunt gestalten[Bearbeiten]

Die Farbgebung und Darstellungsattribute des selektierten Items festlegen:

int set_menu_fore(MENU *menu, chtype attr);

Die Farbgebung und Darstellungsattribute der unselektierten Items festlegen:

int set_menu_back(MENU *menu, chtype attr);

Das Erscheinungsbild des Menühauptfensters kann natürlich über die konventionellen ncurses-Befehle (wbkgd, wattrset, etc.) gestaltet werden.

Beispiel[Bearbeiten]

#include <menu.h>
#include <stdlib.h>

ITEM   **it;
MENU   *me;
WINDOW *win;

void quit(void)
{
  int i;

  unpost_menu(me);
  free_menu(me);

  for(i=0; i<=4; i++)
  {
    free_item(it[i]);
  }

  free(it);
  delwin(win);
 
  endwin();
}

int main(void)
{
  int ch;

  initscr();
  atexit(quit);
  clear();
  noecho();
  curs_set(0);
  cbreak();
  nl();
  keypad(stdscr, TRUE);
  start_color();

  init_pair(1, COLOR_WHITE, COLOR_BLUE);
  init_pair(2, COLOR_BLUE, COLOR_YELLOW);

  bkgd(COLOR_PAIR(1));

  it = (ITEM **)calloc(5, sizeof(ITEM *));
  it[0] = new_item("M1", "Menueeintrag 1");
  it[1] = new_item("M2", "Menueeintrag 2");
  it[2] = new_item("M3", "Menueeintrag 3");
  it[3] = new_item("Ende", "Programm beenden");
  it[4] = 0;
  me = new_menu(it);

  win = newwin(8, 30, 5, 5);
  set_menu_win (me, win);
  set_menu_sub (me, derwin(win, 4, 28, 3, 2));
  box(win, 0, 0);  
  mvwaddstr(win, 1, 2, "***** Testmenü *****");
  set_menu_fore(me, COLOR_PAIR(1)|A_REVERSE);
  set_menu_back(me, COLOR_PAIR(1));
  wbkgd(win, COLOR_PAIR(2));

  post_menu(me);        

  mvaddstr(14, 3, "Programm mittels Menü oder F1-Funktionstaste beenden");

  refresh();
  wrefresh(win);
 
  while((ch=getch()) != KEY_F(1))
  {
    switch(ch)
    {
      case KEY_DOWN:
        menu_driver(me, REQ_DOWN_ITEM);
        break;
      case KEY_UP:
        menu_driver(me, REQ_UP_ITEM);
        break;
      case 0xA: /* Return- bzw. Enter-Taste -> ASCII-Code */
        if(item_index(current_item(me)) == 3)
          exit(0);      
    }

    wrefresh(win);
  }   

  return (0);  
}

Optionen für Menüeinträge[Bearbeiten]

int set_item_opts(ITEM *item, OPTIONS opts);
int item_opts_on(ITEM *item, OPTIONS opts);
int item_opts_off(ITEM *item, OPTIONS opts);

Die Funktionsbezeichnungen sind selbsterklärend. Es gibt hier nur eine Option, nämlich

O_SELECTABLE

Wird die Selektierbarkeit für einen Menüeintrag hiermit ausgeschaltet, so ist dieser Menüeintrag als nicht selektierbar dargestellt. Dies bezieht sich aber nur auf die Menüdarstellung, das Item ist trotzdem immer noch auswählbar. Die "Nichtselektierbarkeit" eines Items muss vom Programmierer im weiteren Code berücksichtigt werden. Möglich ist dies durch die Abfrage der Itemoptionen

OPTIONS item_opts(const ITEM *item);

Die Farbgebung für derartige nicht selektierbare Items kann über die Funktion

int set_menu_grey(MENU *menu, chtype attr);

gesteuert werden.

Beispiel[Bearbeiten]

#include <menu.h>
#include <stdlib.h>

ITEM   **it;
MENU   *me;
WINDOW *win;

void quit(void)
{
  int i;

  unpost_menu(me);
  free_menu(me);

  for(i=0; i<=4; i++)
  {
    free_item(it[i]);
  }

  free(it);
  delwin(win);
 
  endwin();
} 

int main(void)
{
  int ch; 

  initscr();
  atexit(quit);
  clear();
  noecho();
  curs_set(0);
  cbreak();
  nl();
  keypad(stdscr, TRUE);
  start_color();

  init_pair(1, COLOR_WHITE, COLOR_BLUE);
  init_pair(2, COLOR_BLUE, COLOR_YELLOW);
  init_pair(3, COLOR_BLACK, COLOR_BLUE); 

  bkgd(COLOR_PAIR(1));

  it = (ITEM **)calloc(5, sizeof(ITEM *));
  it[0] = new_item("M1", "Menueeintrag 1");
  it[1] = new_item("M2", "Menueeintrag 2");
  it[2] = new_item("M3", "Menueeintrag 3");
  it[3] = new_item("Ende", "Programm beenden");
  it[4] = 0;
  item_opts_off(it[3], O_SELECTABLE);
  me = new_menu(it);

  win = newwin(8, 30, 5, 5);
  set_menu_win (me, win);
  set_menu_sub (me, derwin(win, 4, 28, 3, 2));
  box(win, 0, 0);  
  mvwaddstr(win, 1, 2, "***** Testmenü *****");
  set_menu_fore(me, COLOR_PAIR(1)|A_REVERSE);
  set_menu_back(me, COLOR_PAIR(1));
  set_menu_grey(me, COLOR_PAIR(3));
  wbkgd(win, COLOR_PAIR(2));
  post_menu(me);        

  mvaddstr(14, 3, "Programm mittels F1-Funktionstaste beenden");

  refresh();
  wrefresh(win);
 
  while((ch=getch()) != KEY_F(1))
  {
    switch(ch)
    {
      case KEY_DOWN:
        menu_driver(me, REQ_DOWN_ITEM);
        break;
      case KEY_UP:
        menu_driver(me, REQ_UP_ITEM);
        break;
      case 0xA: /* Return- bzw. Enter-Taste -> ASCII-Code */
        if(item_index(current_item(me))==3 &&
           item_opts(current_item(me))==O_SELECTABLE)
        exit(0);      
    }

    wrefresh(win);
  }   

  return (0);  
}

Menüoptionen[Bearbeiten]

Zum Setzen von Menüoptionen werden diese Funktionen verwendet:

int set_menu_opts(MENU *menu, OPTIONS opts);
int menu_opts_on(MENU *menu, OPTIONS opts);
int menu_opts_off(MENU *menu, OPTIONS opts);

Einige der verfügbaren Optionswerte sind

O_SHOWDESC Beschreibungen zu den Einträgen anzeigen
O_NONCYCLIC Nicht-zyklisch, am Menüende nicht automatisch zum Menübeginn springen und umgekehrt
O_ONEVALUE Nur ein Menüeintrag ist auswählbar

Alle Optionen sind standardmäßig eingeschaltet.

Beispiel: O_SHOWDESC[Bearbeiten]

// ...
menu_opts_off(me, O_SHOWDESC); 
// ...

Beispiel: Mehrfachauswahl[Bearbeiten]

Bei einer möglichen Mehrfachauswahl kann nicht mehr einfach mittels current_item der ausgewählte Menüeintrag bestimmt werden. Stattdessen kann die Funktion

bool item_value(const ITEM *item);

Verwendung finden. Diese Funktion liefert für selektierte Menüeinträge TRUE, für unselektierte Menüeinträge FALSE.

// ...
menu_opts_off(me, O_ONEVALUE);
// ...
  
while((ch=getch()) != KEY_F(1))
{
  switch(ch)
  {
    // ...
    case 0x20: /* Leertaste */
      menu_driver(me, REQ_TOGGLE_ITEM);

      int i;

      for(i=0; i<4; i++)
      {
        mvprintw(16+i, 3, "Item %i: %i", i+1, item_value(it[i]));
      }
      break;
    // ...
  }
}
//...

Damit sind die ncurses-Menüs zwar bei weitem noch nicht umfassend abgehandelt. Jedoch wären weitere Themen teilweise schon sehr komplex und nur für spezielle Einsatzfälle wirklich interessant. Mit dieser Bemerkung soll dieses Kapitel hier vorerst abgeschlossen werden.


<<< ncurses-Startseite ncurses << Inhaltsverzeichnis
< Panels Formulare >