Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

 << zurück
Linux-UNIX-Programmierung von Jürgen Wolf
Das umfassende Handbuch – 2., aktualisierte und erweiterte Auflage 2006
Buch: Linux-UNIX-Programmierung

Linux-UNIX-Programmierung
1216 S., mit CD, 49,90 Euro
Rheinwerk Computing
ISBN 3-89842-749-8
gp Kapitel 13 Terminal E/A und Benutzerschnittstellen für die Konsole
  gp 13.1 termios
    gp 13.1.1 Terminalattribute bearbeiten
    gp 13.1.2 Flags setzen und löschen
    gp 13.1.3 Terminalidentifizierung
    gp 13.1.4 Geschwindigkeitskontrolle – Baudrate von Terminals einstellen
  gp 13.2 terminfo
    gp 13.2.1 terminfo verwenden
    gp 13.2.2 terminfo initialisieren – setupterm()
    gp 13.2.3 Eigenschaften eines Terminals (Finden von capnames) – tigetflag(), tigetnum() und tigetstr()
    gp 13.2.4 Mit terminfo-Eigenschaften arbeiten – putp(), tputs(), tparm()
  gp 13.3 ncurses – Halbgrafik
    gp 13.3.1 ncurses initialisieren
    gp 13.3.2 Tastaturmodus und Ein- und Ausgabe
    gp 13.3.3 Eigenschaft der Fenster
    gp 13.3.4 Scrolling
    gp 13.3.5 Attribute und Farben setzen
    gp 13.3.6 Fensterroutinen
    gp 13.3.7 Mausprogrammierung mit ncurses


Rheinwerk Computing

13.3 ncurses – Halbgrafik  downtop

In diesem Kapitel erhalten Sie eine Einführung zu ncurses – einer freien Implementierung der klassischen Bildschirmsteuerung der UNIX-Bibliothek curses. ncurses ist eine Bibliothek der höheren Ebene und eine Schnittstelle zur Kontrolle und Manipulation der Bildschirmausgabe. Weiterhin beinhaltet ncurses Funktionen zur Behandlung der Tastatur- und Mauseingabe, zur Behandlung und Erzeugung mehrerer Fenster – ja es gibt sogar Funktionen für Panels, Menüsteuerung und Forms. Eine vollständige Beschreibung würde hier allerdings den Rahmen des Buchs sprengen – und ich war der Auffassung, mehr Platz für die Programmierung von grafischen Oberflächen zu lassen. Benötigen Sie eine umfangreichere Lektüre zu ncurses, dann ist die Manual Page wieder das Nonplusultra (oder ein Blick auf die Buch-CD).

ncurses ist sozusagen die Antwort von den Bekehrten des »Bösen«, die zum »Guten« gewechselt sind und sich die Frage stellen: Wo ist conio? – und auch als gute Alternative zur niedrigeren Fraktion termcap und terminfo.

Ihnen wird außerdem auffallen, dass mit ncurses ein gewisser Overhead entfällt, den Sie mit termios und terminfo zuvor betrieben haben. Meistens werden ungemütliche Aufgaben bei ncurses mit einer Funktion erledigt – wozu Sie zuvor eine ganze Routine haben schreiben müssen.

Da Sie ncurses nicht standardmäßig auf jedem System vorfinden, muss diese Bibliothek dem Linker mit dem Flag -lncurses oder (unter UNIX-artigem -curses) hinzugelinkt werden (eventuell muss die entsprechende Bibliothek nachinstalliert werden).


Rheinwerk Computing

13.3.1 ncurses initialisieren  downtop

Bei ncurses fällt relativ oft der Begriff Window und dann wieder der Begriff Screen, was beides allerdings nicht ganz dasselbe bedeutet. Der Screen ist der physische Terminalbildschirm, der im Zeichen- oder Konsolenmodus ausgeführt wird, und als Window bezeichnet man den rechteckigen Bereich auf dem Bildschirm, der sich innerhalb des Screens befindet. Das erste Window, das ncurses auf dem Bildschirm ausgibt, wird als stdscr bezeichnet. Alle anderen Windows, die darauf folgen, können Sie unter Angabe des Typs WINDOW selbst benennen. Schließlich können Sie mit ncurses mit mehr als einem Window arbeiten.

Um überhaupt mit ncurses arbeiten zu können, müssen Sie erst eine Initialisierung machen. Dies wird mit der folgenden Funktion erledigt:

#include <curses.h>
WINDOW *initscr(void);

Konnte ncurses erfolgreich initialisiert werden, gibt initscr() einen Zeiger auf stdscr zurück, ansonsten im Fehlerfall wird NULL zurückgegeben. Nach der Initialisierung von ncurses können Sie weitere Funktionen aufrufen.

Wenn Sie das Programm beenden wollen, sollten Sie ncurses ebenfalls davon in Kenntnis setzen.

#include <curses.h>
int endwin(void);

Damit wird das aktuelle Terminal wieder freigegeben. Bei Erfolg liefert endwin() die symbolische Konstante OK, ansonsten bei einem Fehler ERR zurück.

Somit sieht das Grundgerüst zur ncurses-Programmierung folgendermaßen aus:

/* cur1.c */
#include <curses.h>
int main (void) {
   /* curses initialisieren */
   initscr ();
   /* curses-Funktionen usw. */
   /* curses beenden         */
   endwin ();
   return 0;
}

Logischerweise passiert nach dem Übersetzen und Starten des Programms noch gar nichts.


Rheinwerk Computing

13.3.2 Tastaturmodus und Ein- und Ausgabe  downtop


Hinweis   Bei den meisten Funktionen, die einen Integer zurückgeben, wird bei einem Fehler immer ERR, ansonsten bei Erfolg ein anderer Wert als ERR (!ERR) zurückgegeben. Funktionen, die einen Zeiger verwenden, geben bei einem Fehler immer NULL zurück.


Im nächsten Abschnitt lernen Sie einige (der vielen) Möglichkeiten kennen, wie Sie Zeichen von der Tastatur einlesen und wieder ausgeben können.

Der Tastaturmodus

Um den Puffer (zeilenweise) abzustellen bzw. wieder einzuschalten, stehen Ihnen bei ncurses folgende Funktionen zur Verfügung:

#include <curses.h> 
int cbreak(void);
int nobreak(void);

Mit der Funktion cbreak() stellen Sie den Puffer ab, und mit der Funktion nobreak() können Sie dies wieder rückgängig machen. Wurde der Puffer abgeschaltet, werden die einzelnen Zeichen sofort ohne Betätigung von (ENTER) eingelesen. Mit der Funktion cbreak() bearbeitet der Tastaturtreiber außerdem nur noch die Tastenkombinationen (CTRL)+(S), (CTRL)+(Q) und (CTRL)+(C). Standardmäßig ist unter Linux logischerweise (auch unter ncurses) die (zeilenweise) Pufferung eingeschaltet.

Ebenso können Sie mit ncurses das Terminal auch in den raw()-Modus setzen bzw. wieder zurücksetzen. Dies erledigen Sie mit den entsprechenden Funktionen:

#include <curses.h>
// raw-Modus an
int raw(void); 
// raw-Modus aus
int noraw(void);

Um bei einer Eingabe von der Tastatur die anschließenden Zeichen oder Steuercodes nicht am Bildschirm anzuzeigen, kann die Funktion noecho() verwendet werden. Rückgängig gemacht wird dies wieder mit der Funktion echo().

#include <curses.h>
int noecho(void);
int echo(void);

Wollen Sie testen, ob bestimmte Tasten wie Cursor up, Page down, Home, Cursor down, Ende, F1 usw. betätigt wurden, dann verwenden Sie am besten die folgende Funktion:

#include <curses.h>
int keypad(WINDOW *win, bool bf);

Der Parameter win wird (wurde) bereits zu Beginn mit initscr() initialisiert (stdscr). Mit bf können Sie das Verhalten des Bildschirms festlegen. Geben Sie hierfür TRUE an und der Anwender drückt eine Funktionstaste, dann gibt die Funktion wgetch() z. B. einen Wert zurück, den die Funktionstaste repräsentiert. Falls Sie hierfür FALSE angeben, werden die Funktionstasten nicht gesondert behandelt, und die Anwendung interpretiert die Escape-Sequenzen selbst.

Ein- und Ausgabe


Hinweis   Eine Besonderheit bei der Ausgabe gleich zu Beginn. Damit die anschließenden Ausgabefunktionen von ncurses auch wirklich etwas auf dem Bildschirm wiedergeben, muss nach jeder Veränderung des Bildschirms die Funktion refresh() aufgerufen werden. Dazu in Kürze mehr.


Bei ncurses haben fast alle Routinen zur Ein- und Ausgabe zwei Varianten, zum einen die »normale« Variante, zum anderen eine, die mit einem mv-Präfix beginnt. Beide Schreibweisen bedeuten dasselbe, nur dass Sie mit dem mv-Präfix (mv=move) den Cursor erst noch auf die Position y, x stellen können. Achtung, falls Sie die Koordinaten von conio im Kopf haben (x, y), bei ncurses geben Sie zuerst die y- und dann die x-Koordinate an.

Um einzelne Zeichen von der Tastatur einzulesen, stehen Ihnen folgende Funktionen zur Verfügung:

#include <curses.h>
int getch(void);
int mvgetch(int y, int x);

Mit der Funktion getch() lesen Sie ein Zeichen von der Tastatur ein. mvgetch(y,x) macht dasselbe, nur mit den Koordinaten y und x (Zeile und Spalte). Die Funktion getch() ist natürlich auch abhängig von den Funktionen cbreak(), raw(), nocbreak() und noraw(). Außerdem werden mit getch() auch Werte jenseits von 0 bis 255 zurückgegeben. Damit können Sie sich, wenn Sie die Funktionstastenerkennung keypad(stdscr,TRUE) aktiviert haben, den Integerwert der Funktionstasten ausgeben lassen. Hier ein Beispiel dazu:

/* cur2.c */
#include <curses.h>
int main (void) {
  int c;
  initscr ();      
  keypad (stdscr, TRUE);
  noecho();
  while ((c = getch ()) != 'q') {
    switch (c) {
    case KEY_DOWN:
      printw ("KEY_DOWN : %d\n", c);
      break;
    case KEY_UP:
      printw ("KEY_UP   : %d\n", c);
      break;
    case KEY_LEFT:
      printw ("KEY_LEFT : %d\n", c);
      break;
    case KEY_RIGHT:
      printw ("KEY_RIGHT: %d\n", c);
      break;
    default:
      printw ("Tastaturcode %d\n", c);
    }
  }
  endwin ();
  return 0;
}

Das Programm bei der Ausführung:

$ gcc -o cur2 cur2.c -lncurses
$ ./cur2
---[Bildschirm wird geleert]---
KEY_UP   : 259
KEY_LEFT : 260
KEY_DOWN : 258
KEY_RIGHT: 261
Tastaturcode 265
Tastaturcode 266
Tastaturcode 267 
Tastaturcode 339
Tastaturcode 97 
Tastaturcode 99
...
q

Wenn Sie das Programm ausführen, aktivieren Sie mit keypad(stdscr,TRUE) die Cursor- und Funktionstasten – sprich, diese werden berücksichtigt. Im Beispiel können so lange Tasten gedrückt werden, bis Sie q betätigen. Sie sehen außerdem, wie Sie einzelne Funktionstasten explizit abfragen können (KEY_DOWN, KEY_UP, KEY_RIGHT, KEY_LEFT). Weitere symbolische Konstanten, die in der Headerdatei curses.h definiert sind und abgefragt werden können, sind:

#define KEY_BREAK 0401        /* Break (unzuverlässig )        */
#define KEY_DOWN 0402         /* Pfeil-nach-unten              */
#define KEY_UP 0403           /* Pfeil-nach-oben               */
#define KEY_LEFT 0404         /* Pfeil-nach-links              */
#define KEY_RIGHT 0405        /* Pfeil-nach-rechts             */
#define KEY_HOME 0406         /* Home                          */
#define KEY_BACKSPACE 0407    /* Backspace (unzuverlässig)     */
#define KEY_F0 0410           /* Funktionstasten (Platz für 64)*/
#define KEY_F(n) (KEY_F0+(n)) /* Wert für Funktionstaste n     */
#define KEY_DL 0510           /* Delete Zeile                  */
#define KEY_IL 0511           /* Insert Zeile                  */
#define KEY_DC 0512           /* Delete Zeichen                */
#define KEY_CLEAR 0515        /* Screen löschen                */
#define KEY_EOS 0516          /* Löschen bis Ende des Screens  */
#define KEY_EOL 0517          /* Löschen zum Ende der Zeile    */
#define KEY_SF 0520           /* 1 Zeile herunterscrollen      */
#define KEY_SR 0521           /* 1 Zeile heraufscrollen        */
#define KEY_NPAGE 0522        /* Nächste Seite                 */
#define KEY_PPAGE 0523        /* Vorherige Seite               */
#define KEY_STAB 0524         /* TAB gesetzt                   */
#define KEY_CTAB 0525         /* TAB losgelöst                 */
#define KEY_ENTER 0527        /* Enter                         */
#define KEY_PRINT 0532        /* Print                         */

Natürlich sind das noch lange nicht alle Konstanten. Weitere Funktionstasten und deren symbolische Konstanten können Sie unter /usr/include/curses.h finden.

Zum Einlesen von Strings werden folgende Funktionen verwendet:

#include <curses.h>
int getstr(char *string);
int getnstr(char *string, int n);

Beide Funktionen können Sie als Gegenstück zu gets() -> getstr() und fgets() -> getnstr() sehen. Auch hier kann nur von getstr() abgeraten werden (wie im Falle von gets()), da keine Längenprüfung vorgenommen wird. Beide Funktionen lesen von der Standardeingabe einen String in die Adresse von string. Bei getnstr() wird mit n noch eine zusätzliche Längenangabe gemacht. Beide Funktionen gibt es natürlich auch als mv-Alternative zur Positionierung des Cursors:

#include <curses.h>
int mvgetstr(int y, int x, char *string);
int mvgetnstr(int y, int x, char *string, int n);

Hierzu ein einfaches Beispiel zum Einlesen von Strings:

/* cur3.c */
#include <curses.h>
#define MAX 100
int main (void) {
   char string[MAX];
   char string2[MAX];
   initscr ();
   printw("Bitte Eingabe machen : ");
   getnstr (string, MAX);
   mvprintw( 5, 10 ,"Eingabe machen : ");
   mvgetnstr (5, 30, string2, MAX);
   endwin ();  
   printf ("Die 1. Eingabe lautet : %s \n", string);
   printf ("Die 2. Eingabe lautet : %s \n", string2);
   return 0;
}

Das Programm bei der Ausführung:

$ gcc -o cur3 cur3.c -lncurses
$ ./cur3
---[Bildschirm leeren]---
Bitte Eingabe machen : Hallo Welt
          Eingabe machen :    Hallo Welt
...

Um ein einzelnes Zeichen wieder in den Eingabepuffer zurückzuschieben, kann folgende Funktion verwendet werden:

#include <curses.h>
int ungetch(char zeichen);

Jetzt werden die wichtigsten Ausgabefunktionen von ncurses behandelt. Wie schon erwähnt, muss nach einem Aufruf von ncurses-Ausgabefunktionen wie printw(), addstr() etc. die Funktion refresh() aufgerufen werden, damit alle Veränderungen auch wirklich am Bildschirm angezeigt werden.

#include <curses.h>
int refresh(void);

Um die Ausgabe an einer bestimmten Position des Terminals vorzunehmen, kann der Cursor mit der Funktion move() platziert werden.

#include <curses.h>
int move(int y, int x);

Damit platzieren Sie den Cursor auf dem Bildschirm in der y-ten Zeile und der x-ten Spalte.

Wenn Sie eine Zeile löschen oder eine Zeile einfügen wollen, stehen folgende Funktionen zur Verfügung:

#include <curses.h>
int deleteln(void);
int insertln(void);
int insdelln(int anzahl);

Mit deleteln() löschen Sie die Zeile, in der sich der Cursor gerade befindet. Die unteren Zeilen werden dabei nach oben gezogen. Mit insertln() fügen Sie eine Leerzeile an der Position ein, wo sich der Cursor im Augenblick befindet. Die letzte Zeile geht dabei verloren! Mit insdelln() fügen Sie anzahl Zeilen über die aktuelle Cursorposition ein und schieben die unteren Zeilen nach unten. Dadurch gehen die letzten anzahl Zeilen verloren.

Weitere ebenbürtige Funktionen, die Sie in ähnlicher Form aus der Standard-C-Bibliothek her kennen, sind:

#include <curses.h>
int printw(char *format, ...);
int mvprintw(int y, int x, char *format, ...);

Zu printw() gibt es nichts zu sagen, da diese wie printf() zu verwenden ist. Mit mvprintw() können Sie noch zusätzlich die Ausgabe an der y-ten Zeile und der x-ten Spalte positionieren. Mit beiden Funktionen lassen sich außerdem keine ACS_ Sonderzeichen darstellen, da ein char meistens 8 Bit breit ist. Was ACS_ Sonderzeichen sind, erfahren Sie in Kürze. Ein Listing demonstriert Ihnen die hier vorgestellten Funktionen:

/* cur4.c */
#include <curses.h>
#define MAX 100
int main (void) {
  char string[MAX];
  char string2[MAX];
  const char string3[] = "Dies ist der neue String3\n";
  int c;
  initscr ();
  printw ("Bitte Eingabe für String machen: ");
  getnstr (string, MAX);
  mvprintw (3, 0, "Bitte Eingabe für 2. String machen: ");
  getnstr (string2, MAX);
  mvprintw (7, 20, "String ist gleich: %s\n", string);
  mvprintw (8, 20, "Diese Zeile wird mit deleteln() gelöscht\n");
  mvprintw (9, 20, "String 2 ist gleich: %s\n", string2);
  mvprintw (14, 20, "Weiter mit TASTE");
  noecho ();
  c = getch ();
  move (8, 20);
  // Zeile löschen
  deleteln ();
  noecho ();
  c = getch ();
  move (8, 20);
  // (Leer-)Zeile einfügen
  insertln (); 
  mvprintw (8, 20, string3);
  noecho ();
  c = getch ();
  // Bildschirm löschen
  clear (); 
  refresh ();
  mvprintw (12, 38, "ENDE");
  // Wartet 20 Zehntelsek. auf getch()
  halfdelay (20);  
  // ... ansonsten beendet sich das Progr. selbst
  c = getch ();
  endwin ();
  return 0;
}

Das Beispiel sollte eigentlich für sich selbst sprechen. Zuerst werden Sie aufgefordert, zwei Strings einzugeben. Anschließend wird eine Zeile gelöscht und dann wieder eine hinzugefügt. Auf die Funktion halfdelay() wird noch eingegangen.

Mit der Funktion addch() können Sie ein einzelnes Zeichen an der aktuellen Cursorposition ausgeben.

#include <curses.h>
int addch(chtype zeichen);

Wie Sie an dem Datentyp chtype erkennen können, handelt es sich hierbei nicht um ein »normales« char-Zeichen. Der Datentyp chtype ist ein 32-Bit-Wert. Damit lassen sich auch ganz andere Zeichen als nur ASCII-Zeichen darstellen. Folgende Sonderzeichen können Sie u. a. damit darstellen:

#define ACS_ULCORNER (acs_map['l']) /* Ecke links oben         */
#define ACS_LLCORNER (acs_map['m']) /* Ecke links unten        */
#define ACS_URCORNER (acs_map['k']) /* Ecke rechts oben        */
#define ACS_LRCORNER (acs_map['j']) /* Ecke rechts unten       */
#define ACS_LTEE (acs_map['t'])     /* T-Stück nach rechts     */
#define ACS_RTEE (acs_map['u'])     /* T-Stück nach links      */
#define ACS_BTEE (acs_map['v'])     /* T-Stück nach oben       */
#define ACS_TTEE (acs_map['w'])     /* T-Stück nach unten      */
#define ACS_HLINE (acs_map['q'])    /* horizontale Linie       */
#define ACS_VLINE (acs_map['x'])    /* vertikale Linie         */
#define ACS_PLUS (acs_map['n'])     /* großes Pluszeichen      */
#define ACS_S1 (acs_map['o'])       /* Scan-Linie 1            */
#define ACS_S9 (acs_map['s'])       /* Scan-Linie 2            */
#define ACS_DIAMOND (acs_map['`'])  /* Diamant                 */
#define ACS_CKBOARD (acs_map['a'])  /* checker board (stipple) */
#define ACS_DEGREE (acs_map['f'])   /* Symbol für Grad         */
#define ACS_PLMINUS (acs_map['g'])  /* Plus/Minus              */
#define ACS_BULLET (acs_map['~'])   /* Bullet                  */

Bei den folgenden Typen kann es passieren, dass diese nicht richtig dargestellt werden. Als Alternative werden dann stattdessen einfache ASCII-Zeichen dargestellt. Z. B. für Pfeil-nach-links wird hierbei einfach < ausgegeben oder für den Pfeil-nach-rechts das Zeichen >.

#define ACS_LARROW (acs_map[','])    /* Pfeil-nach-links     */
#define ACS_RARROW (acs_map['+'])    /* Pfeil-nach-rechts    */
#define ACS_DARROW (acs_map['.'])    /* Pfeil-nach-unten     */
#define ACS_UARROW (acs_map['-'])    /* Pfeil-nach-oben      */
#define ACS_BOARD (acs_map['h'])     /* Tafel mit Quadraten  */
#define ACS_LANTERN (acs_map['i'])   /* Laternen-Symbol      */
#define ACS_BLOCK (acs_map['0'])     /* Würfel-Block         */

Zur Demonstration ein einfaches Beispiel. Es wird ein einfacher Rahmen »gezeichnet«, bei dem sich im Zentrum ein String befindet.

/* cur5.c */
#include <curses.h>
int main (void) {
  int i, j;
  initscr ();
  // Ecke links oben
  addch (ACS_ULCORNER);
  // Eine horizontale Linie
  for (i = 0; i < 20; i++)
     addch (ACS_HLINE);
  // Ecke rechts oben
  addch (ACS_URCORNER);
  addch ('\n');
  // vertikale Linie rechts und links (21 Zeilen)
  for (i = 0; i < 10; i++)
    for (j = 0; j <= 21; j++)
      if (j == 0)
        addch (ACS_VLINE);
      else if (j == 21) {
        addch (ACS_VLINE);
        addch ('\n');
      } 
      else
        addch (' ');
  // Ecke links unten
  addch (ACS_LLCORNER);
  // Eine horizontale Linie
  for (i = 0; i < 20; i++)
    addch (ACS_HLINE);
  // Ecke rechts unten
  addch (ACS_LRCORNER);
  addch ('\n');
  mvprintw (5, 7, "<TASTE>");
  getch ();
  endwin ();
  return 0;
}

So sieht‘s aus:


Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 13.1    Ein einfacher Rahmen mithilfe von addch()


Zur zeichenweisen Ausgabe mittels addch() gibt es auch eine Version für Strings:

#include <curses.h>
int addstr(char *string);
int addnstr(char *string, int anzahlzeichen);

Angewendet werden diese Funktionen so:

addstr("Hallo Welt");
addnstr("Hallo Welt",5);    /* Wird nur Hallo ausgegeben */

Natürlich gibt es auch bei diesen Funktionen weitere Versionen mit dem vorangestellten mv-Präfix zum Positionieren. Es sind außerdem noch eine Menge mehr Funktionen dieser Art vorhanden. Hierfür sind, bei Bedarf, die Manual Pages von ncurses heranzuziehen.

Wollen Sie den Bildschirm von der aktuellen Position des Cursors aus löschen, können Sie folgende Funktion verwenden:

#include <curses.h>
int clrtobot(void);

Sie löschen damit alles, was sich hinter dem Cursor befindet – alles, was davor steht, bleibt erhalten. Selbige Funktion gibt es auch für eine Zeile:

#include <curses.h>
int clrtoeol(void);

Mit der Funktion clrtoeol() löschen Sie den Rest (nicht die ganze Zeile) der Zeile ab der Position, an der sich der Cursor gerade befindet. Es geht auch noch kleiner:

#include <curses.h>
int delch(void);
int mvdelch(int y, int x);

Mit diesen beiden Funktionen können Sie das Zeichen an der Stelle löschen, an der sich der Cursor im Augenblick befindet. Mit mvdelch() können Sie dazu den Cursor exakt positionieren. Alle weiteren Zeichen in dieser Zeile werden dabei um eine Position nach links versetzt. Dazu ein Beispiel:

/* cur6.c */
#include <curses.h>
int main (void) {
   initscr ();
   mvprintw (5, 5, "Diese Zeile enthält einen Fehhler");
   mvprintw (6, 5, "Bitte Taste drücken für Fehlerkorrektur");
   getch ();
   mvdelch (5, 33);
   mvprintw (7, 5, "Fehler wurde korrigiert! "
                   " Bitte Taste drücken!");
   getch ();
   endwin ();
   return 0;
}

In diesem Programm wurde das Wort 'Fehhler' mit zwei 'h' geschrieben, und mittels mvdelch(y,x) wird der Fehler behoben.

Zum Löschen des Bildschirms stehen Ihnen folgende Funktionen zur Verfügung:

#include <curses.h>
int erase(void);
int clear(void);

Beide Funktionen löschen den Bildschirm und setzen den Cursor auf die linke obere Ecke (0,0). Mit clear() wird bei dem nächsten refresh()-Aufruf der Bildschirm komplett neu ausgegeben.


Rheinwerk Computing

13.3.3 Eigenschaft der Fenster  downtop

Um den kompletten Bildschirm beim nächsten Aufruf der Funktion refresh() auch wirklich neu zu zeichnen, steht Ihnen die Funktion clearok() zur Verfügung.

#include <curses.h>
int clearok(WINDOW *win, bool bf);

Wird bf auf TRUE gesetzt, dann wird der Bildschirm win beim nächsten refresh() komplett neu gezeichnet. Wird hingegen FALSE verwendet, dann wird nur die Änderung seit dem letzten refresh() neu ausgegeben.

Damit am Ende der letzten Zeile beim Einfügen einer neuen Zeile weiter nach unten gescrollt wird, sollten Sie folgende Funktion verwenden:

#include <curses.h>
int scrollok(WINDOW *win,bool bf);

Falls der Parameter bf=TRUE ist, können Sie noch eine weitere Zeile nach unten scrollen, auch wenn der Cursor schon in der letzten Zeile steht und durch ein Newline oder ein weiteres Zeichen in die nächste Zeile springt. Wenn Sie den Parameter bf auf FALSE setzten, bleibt der Cursor in der letzten Zeile stehen und lässt sich nicht mehr weiter nach unten scrollen. Folgendes Beispiel demonstriert Ihnen den Effekt einmal ohne und einmal mit scrollok().

/* cur7.c */
#include <curses.h>
int main (void) {
  int i;
  initscr ();
  for (i = 0; i < 30; i++) {
    printw ("%d: Ohne Funktion scrollok()\n", i);
    halfdelay (2);
    getch ();
  }
  clear ();
  refresh ();
  scrollok (stdscr, TRUE);
  for (i = 0; i < 30; i++) {
    printw ("%d: Mit Funktion scrollok()\n", i);
    halfdelay (2);
    getch ();
  }
  getch ();
  endwin ();
  return 0;
}

Im ersten Durchlauf ohne scrollok() wird die Ausgabe keinen Schritt tiefer gehen als bis in Zeile 25. Die restlichen fünf Zeilen werden einfach auf der gleichen Zeile ausgegeben. Beim zweiten Durchlauf wurde mit scrollok(stdsrc,TRUE) das Scrollen eingeschaltet – was bei der Ausgabe auch dazu führt, dass ab der Zeile 25 weiter in die Zeilen 26, 27, 28 und 29 »gesprungen« wird.

Was als erste und letzte Zeile betrachtet werden soll, können Sie mit der Funktion setscrreg() angeben.

#include <curses.h>
int setscrreg(int oben, int unten);

Das Argument oben ist die Zeile, ab der gescrollt wird, und unten stellt die letzte Zeile dar, wie weit gescrollt wird. Die oberste Zeile hat im Programm dann den Wert 0. Hierzu wird das Listing von eben um setscrreg() erweitert:

/* cur8.c */
#include <curses.h>
int main (void) {
  int i;
  initscr ();
  setscrreg (5, 10);
  for (i = 0; i < 30; i++) {
    printw ("%d: Ohne Funktion scrollok()\n", i);
    halfdelay (2);
    getch ();
  }
  clear ();
  refresh ();
  scrollok (stdscr, TRUE);
  for (i = 0; i < 30; i++) {
    printw ("%d: Mit Funktion scrollok()\n", i);
    halfdelay (2);
    getch ();
  }
  getch ();
  endwin ();
  return 0;
}

Mit setscrreg(5,10) legen Sie die Bildschirmgröße auf fünf Zeilen fest. Die Bildschirmgröße im Beispiel beginnt in Zeile fünf und endet in Zeile zehn.

Wollen Sie die Position des Cursors auf dem Bildschirm erfragen, dann steht folgende Funktion dafür zur Verfügung:

#include <curses.h>
void getyx(WINDOW *win, int y, int x);

Die Koordinaten des Cursors in win werden in den Parametern y und x abgelegt. Es muss hier nicht der Adressoperator & an den beiden Koordinatenstellen übergeben werden. Das Beispiel von zuvor wird (noch) ein weiteres Mal um die Funktion getyx() erweitert:

/* cur9.c */
#include <curses.h>
int main (void) {
  int i, y, x;
  initscr ();
  setscrreg (0, 10);
  for (i = 0; i < 30; i++) {
    getyx (stdscr, y, x);
    printw ("%d. (%d/%d) Ohne Funktion scrollok()\n", i, y, x);
    halfdelay (2);
    getch ();
  }
  clear ();
  refresh ();
  scrollok (stdscr, TRUE);
  for (i = 0; i < 30; i++) {
    getyx (stdscr, y, x);
    printw ("%d. (%d/%d) Mit Funktion scrollok()\n", i, y, x);
    halfdelay (2);
    getch ();
  }
  getch ();
  endwin ();
  return 0;
}

Rheinwerk Computing

13.3.4 Scrolling  downtop

Damit Sie das Scrolling einsetzen können, müssen Sie es mit der Funktion scrollok(), die Sie bereits kennen gelernt haben, aktivieren. Um jetzt dafür zu sorgen, dass der Bildschirm automatisch von oben nach unten gescrollt wird, müssen Sie folgende Funktion verwenden:

#include <curses.h>
int scroll(WINDOW *win);

Mit dieser Funktion schieben Sie den durch win angegebenen Bildschirm um eine Zeile nach oben. Im Falle von stdscr wird der gesamte Bildschirm gescrollt.

In welche Richtung (nach oben oder unten) und um wie viele Zeilen auf einmal gescrollt werden soll, geben Sie mit folgender Funktion an:

#include <curses.h>
int scrl(int anzahl);

Mit scrl() schieben Sie den Bildschirm mittels anzahl>0, anzahl Zeilen nach oben und mit anzahl<0, anzahl Zeilen nach unten. Mit der Funktion setscrreg() (siehe den Abschnitt zuvor) können Sie den Scrollbereich definieren.

Auch hierzu ein kurzes Beispiel, das die Funktionen scroll(), scrl() und weitere bisher kennen gelernte ncurses-Funktionen demonstriert. Ein kleines Raumschiff soll durch die unendlichen Weiten des Universums gleiten. Mit den Tasten Pfeil-nach-links und Pfeil-nach-rechts können Sie das Raumschiff steuern. Mit q wird das Programm beendet. Wenn Sie fleißig sind, können Sie gerne eine Kollisionskontrolle einbauen, um das Raumschiff bei Kontakt mit einem Stern explodieren zu lassen. Mit Arrays lässt sich so etwas recht einfach realisieren.

/* cur10.c */
#include <curses.h>
#include <stdlib.h>
#define QUIT 113   /*'q'*/
#define LEFT 260   /* '<-'*/
#define RIGHT 261  /* '->'*/
static void print_raumschiff (int x) {
  mvdelch (9, x + 2);
  mvdelch (9, x + 1);
  mvdelch (9, x);
  mvdelch (9, x - 1);
  mvdelch (9, x - 2);
  mvaddch (10, x - 1, ACS_LLCORNER);
  mvaddch (10, x + 1, ACS_LRCORNER);
  mvaddch (10, x, ACS_TTEE);
}
int main (void) {
  int x = 40, zufall, c, i;
  srand (79);
  initscr ();
  // Cursor und Funktionstasten ein
  keypad (stdscr, TRUE); 
  // keine Ausgabe
  noecho ();   
  // Scrolling ein
  scrollok (stdscr, TRUE);
  scroll (stdscr);
  while (c != QUIT) {
    // Eine Zeile nach unten scrollen
    scrl (1);
    for (i = 0; i < 5; i++) {
      zufall = rand () % 79;
      mvaddch (20, zufall, '*');
      mvprintw (0, 0,
                "'q' drücken für Quit | Taste für Start | "
                "<- nach links -> nach rechts");
      mvprintw(1,0, "Position Raumschiff %d",x);
    }
    c = getch ();
    halfdelay (3);
    switch (c) {
    case LEFT:
      if (x < 1)
        x = 79;
      else
        x--;
      break;
    case RIGHT:
      if (x > 79)
        x = 1;
      else
        x++;
      break;
    default:
      break;
    }
    print_raumschiff (x);
  }
  endwin ();
  return 0;
}

So sieht es aus:


Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 13.2    Raumschiff im Weltall mit ncurses



Hinweis   In diesem Listing wird davon ausgegangen, dass Sie eine gewöhnliche 80 x 24 cm große Konsole (z. B. VT100) verwenden. Andernfalls kann die Anwendung nicht richtig laufen (es sei denn, Sie passen die Breite der Konsole dem Listing an).



Rheinwerk Computing

13.3.5 Attribute und Farben setzen  downtop

Natürlich ist es auch möglich, die Attribute (Schrift fett, unterstrichen etc.) und Farben zu verändern. Um die Attribute des Textes zu aktivieren bzw. zu deaktivieren, stehen Ihnen folgende Funktionen zur Verfügung:

#include <curses.h>
int attroff(int attribut);
int attron(int attribut);
int attrset(int attribut);

Mit der Funktion attron() können Sie das Attribut attribut setzen. Mit attron(A_BLINK) wird zum Beispiel der Text blinkend ausgegeben (bei Erfolg). Mit attroff() wird das Attribut attribut deaktiviert. Mit der Funktion attrset() hingegen können Sie jedes beliebige Attribut setzen. Mithilfe des bitweisen ODER-Operators lassen sich dabei mehrere Attribute auf einmal setzen, z. B.:

attrset(A_BOLD | A_BLINK);
printw("Dieser Text wird FETT und BLINKEND angezeigt");

In der folgenden Tabelle finden Sie einen Überblick zu den Attributen, die Sie setzen bzw. deaktivieren können:


Tabelle 13.8    Attribute für die Textdarstellung

Attribut Bedeutung
A_NORMAL Normale Darstellung (Standardeinstellung)
A_STANDOUT Zeichen stärkste Helligkeit
A_UNDERLINE Zeichen unterstreichen
A_REVERSE Invers
A_BLINK Blinken
A_DIM Zeichen werden dunkler
A_BOLD Fette Zeichen
A_PROTECT Geschützter Modus
A_INVIS Unsichtbar
A_ALTCHARSET Umschalten auf Zeichengrafik
A_CHARTEXT Macht aus dem 32-Bit-Wert (chtype) durch eine &-Verknüpfung einen 8-Bit-Wert (ASCII)

Wenn Sie mit den Farben spielen wollen, müssen Sie überprüfen, ob Farben in dem aktuellen Terminaltyp verfügbar sind. Hierzu wird folgende Funktion verwendet:

#include <curses.h>
bool has_colors( void );

Verwenden lässt sich die Funktion recht einfach:

if(has_colors==TRUE) {
     // Wir haben Farbe*/
}
else {
    // Wir haben keine Farbe
}

Eine weitere Anwendung zu den Farben wäre die Funktion zur Abfrage, ob die Farben des aktuellen Terminaltyps überhaupt verändert werden dürfen:

#include <curses.h>
bool can_change_colors(void);

Sofern Ihnen also Farben für den Terminaltyp zur Verfügung stehen, müssen Sie als Nächstes die Farben initialisieren:

#include <curses.h>
int start_color(void);

Diese Funktion sollten Sie nach der Funktion initscr() aufrufen. Mit start_color() initialisieren Sie die Farben und gleichzeitig auch die globalen Variablen COLORS und COLOR_PAIRS. COLOR_PAIRS ist ein Farbenpaar, das sich aus dem Hintergrund und der Schriftfarbe zusammensetzt. Folgende Farben sind dabei in der Headerdatei curses.h definiert:

#define COLOR_BLACK    0
#define COLOR_RED      1
#define COLOR_GREEN    2
#define COLOR_YELLOW   3
#define COLOR_BLUE     4
#define COLOR_MAGENTA  5
#define COLOR_CYAN     6
#define COLOR_WHITE    7

Um ein Farbenpaar (Schrift und Hintergrundfarbe) festzulegen, wird die folgende Funktion verwendet:

#include <curses.h>
int init_pair(short paarnummer, int zeichen, int hintergrund);

Mit der Funktion init_pair() legen Sie mit der Angabe der Zeichenfarbe und der Hintergrundfarbe eine Paarnummer (paarnummer) fest, die Sie dann mit der Funktion attrset() verwenden können, z. B.:

init_pair(1,COLOR_RED,COLOR_GREEN);

Damit legen Sie ein Farbenpaar mit roter Schrift und grünem Hintergrund fest. Das Paar hat die Nummer 1. Dies setzen Sie jetzt wie folgt in die Praxis um:

attrset(COLOR_PAIR(1));
printfw("Dieser Text wird mit roten Zeichen auf grünem"
        " Hintergrund ausgegeben.");
/* Eine weitere Farbe wird definiert */
init_pair(2,COLOR_BLUE,COLOR_BLACK);
attrset(A_BOLD|COLOR_PAIR(2));
printw("Dieser Text wird mit fetter blauer Schrift "
       "und schwarzem Hintergrund ausgegeben.");

Die Funktion COLOR_PAIR(n) setzt die Farbenpaare zusammen (Hintergrund und Schrift). Mit init_pair() können Sie gewöhnlich insgesamt 64 (8 * 8) verschiedene Farbkombinationen festlegen. Nur das Farbpaar mit dem Index 0 (COLOR_PAIR(0)) kann nie geändert werden. Dieser Wert entspricht immer einem weißen Text auf schwarzem Hintergrund – und dient für den Fall der Fälle dazu, Ihren Terminal wieder zurückzusetzen.

Wollen Sie ermitteln, welche Farben in einem bestimmten Farbenpaar definiert wurden, dann können Sie folgende Funktion verwenden:

#include <curses.h>
int pair_content( short paarnummer,
                  short *zeichenfarbe,
                  short *hintergrundfarbe);

pair_content() eignet sich hervorragend, um mehrere Farbenpaare auf einmal zu definieren, wie der folgende Codeausschnitt demonstrieren soll:

int farbe1,farbe2,i=1;
if(has_color==TRUE) {
     start_color();
     // WHITE=7 => BLACK=0 
     for(farbe1=COLOR_WHITE; farbe1>=COLOR_BLACK; farbe1--)
       for(farbe2=COLOR_BLACK; farbe2<=COLOR_WHITE; farbe2++)
         init_pair(i++, farbe1, farbe2);
   }

Damit werden alle 64 Farbenpaare auf einmal definiert. Um jetzt zu ermitteln, welchen Farbwert das Paar 50 hat, müssen Sie die Funktion pair_content() aufrufen:

pair_content(50, &zeichenfarbe, &hintergrundfarbe);

Jetzt befindet sich in der Adresse von zeichenfarbe und in der Adresse von hintergrundfarbe ein entsprechender Farbeintrag des Farbenpaars mit der Nummer 50.

Die Attribute und Farben werden jetzt im Listing des fliegenden Raumschiffs im Weltall eingesetzt.

/* cur11.c */
#include <curses.h>
#include <stdlib.h>
#define QUIT 113  // 'q'
#define LEFT 260  // '<-'
#define RIGHT 261 // -> 
static void print_raumschiff (int x) {
  mvdelch (9, x + 2);
  mvdelch (9, x + 1);
  mvdelch (9, x);
  mvdelch (9, x - 1);
  mvdelch (9, x - 2);
  /*Raumschiff fette Schrift */
  attrset (A_BOLD | COLOR_PAIR (2));
  mvaddch (10, x - 1, ACS_LLCORNER);
  mvaddch (10, x + 1, ACS_LRCORNER);
  attrset (A_BOLD | COLOR_PAIR (3));
  mvaddch (10, x, ACS_TTEE);
}
int main (void) {
  int x = 40, zufall, c, i;
  srand (79);
  initscr ();
  if (has_colors () == TRUE)
    start_color ();
  else
    exit (EXIT_FAILURE);
  keypad (stdscr, TRUE);   
  noecho ();      
  scrollok (stdscr, TRUE);   
  scroll (stdscr);
  // Farbenpaar Nummer 1
  init_pair (1, COLOR_YELLOW, COLOR_BLACK);
  // Paar Nummer 2
  init_pair (2, COLOR_RED, COLOR_RED);
  // Farbenpaar Nummer 3
  init_pair (3, COLOR_GREEN, COLOR_BLACK);
  // Farbenpaar Nummer 4
  init_pair (4, COLOR_BLACK, COLOR_GREEN);
  while (c != QUIT) {
    scrl (1);   
    for (i = 0; i < 5; i++) {
      zufall = rand () % 79;
      // Sterne dunkel ausgegeben
      attrset (A_DIM | COLOR_PAIR (1));
      mvaddch (20, zufall, '*');
      attrset (A_UNDERLINE | A_BOLD | COLOR_PAIR (4));
      mvprintw (0, 0,
                "'q' drücken für Quit | Taste für Start | "
                "<- nach links -> nach rechts");
    }
    c = getch ();
    halfdelay (3);
    switch (c) {
    case LEFT:
      if (x < 1)
        x = 79;
      else
        x--;
      break;
    case RIGHT:
      if (x > 79)
        x = 1;
      else
        x++;
      break;
    default:
      break;
    }
    print_raumschiff (x);
  }    
  endwin ();
  return 0;
}

Ein Screenshot kann ich mir hierbei ersparen, da dieser ohnehin schwarz-weiß ist.


Hinweis   Auch in diesem Listing wird davon ausgegangen, dass Sie eine gewöhnliche 80 × 24 cm große Konsole (z. B. VT100) verwenden. Andernfalls kann die Anwendung nicht richtig laufen (es sei denn, Sie passen die Breite der Konsole dem Listing an).



Rheinwerk Computing

13.3.6 Fensterroutinen  downtop

Fensterroutinen funktionieren genauso wie schon die im Abschnitt zuvor besprochenen Funktionen, nur wird diesen immer ein Zeiger auf eine WINDOW-Struktur übergeben. Zusätzlich bekommen die Funktionen, die sich auf ein bestimmtes Fenster beziehen, noch das Präfix w. Aus printw() wird dann z. B.

wprintw(WINDOW *win, char *format, ...);

und aus den Funktionen mit den mv-Präfixen wird ein mvw-Präfix:

mvwprintw(WINDOW *win, int y, int x, char *format, ...);

Bei allen Funktionen, die Sie bereits kennen, müssen Sie also zusätzlich das w oder mvw und die WINDOW-Struktur verwenden. Falls Sie sich nicht sicher sind, sehen Sie sich einfach die entsprechende Manual Page dazu an. Geben Sie zum Beispiel man getch ein, so werden Sie auch die Routine wgetch(), die sich auf das Fenster bezieht, finden. Da alle bisher beschriebenen Funktionen somit genauso funktionieren, erspare ich mir die genauere Beschreibung dazu.

Um ein weiteres Fenster zu erzeugen, wird die Funktion newwin() verwendet:

#include <curses.h>
WINDOW *newwin( int anzahl_zeilen, int anzah_Spalten,
                int cursor_y, int cursor_X );

Mit newwin erstellen Sie ein neues Fenster mit anzahl_zeilen und anzahl_spalten. Zusätzlich können Sie den Cursor gleich an entsprechender y- und x-Position festlegen (0,0 bedeutet die linke obere Seite des neuen Fensters).

Um ein neues Fenster auch ein wenig hervorzuheben, sollten Sie einen Rahmen darum legen. Einen Rahmen um ein bestimmtes Fenster können Sie mit der Funktion box() ausgeben:

#include <curses.h>
int box( WINDOW *win,
         chtype zeichenvertikal,
         chtype zeichenhorizontal );

Damit legen Sie einen Rahmen um das Fenster, worauf win mit den Zeichen zeichenvertikal und zeichenhorizontal zeigt, die beide vom Typ chtype sind, womit Sie also auch Zeichen jenseits der char-Grenze ausgeben können. Hierzu ein kurzer Codeausschnitt, wie Sie einen Rahmen um ein Fenster legen können:

// Fenster mit dem Namen fenster 10 Zeilen und 79 Spalten groß
fenster=newwin(10,79,0,0);
// Linien um den Fensterbereich ziehen eine Box
box(fenster,ACS_VLINE,ACS_HLINE);

Die ACS_-Zeichen und den Datentyp chtype haben Sie ja bereits kennen gelernt.

Gleiches wie mit der Funktion box() können Sie auch mit den folgenden Funktionen machen:

#include <curses.h>
// n-Zeichen horizontal ab akt. Cursorposition
int hline(chtype zeichen, int n);
// n-Zeichen vertikal ab akt. Cursorposition
int vline(chtype zeichen, int n); 

Sie können ein Fenster auch duplizieren. Das erledigen Sie mit der Funktion:

#include <curses.h>
WINDOW *dupwin(WINDOW *win);
int delwin(WINDOW *win);

Der Rückgabewert von dupwin() ist eine exakte Kopie des Fensters win. Wenn Sie dieses Fenster nicht mehr benötigen, sollten Sie den nicht mehr benötigten Speicherplatz, den dieses Fenster belegt, mit der Funktion delwin() wieder freigeben, z. B.:

// Fenster fenster1 duplizieren
fenster2=dupwin(fenster1);
...
// Nach vielen Zeilen Code wird die Kopie nicht mehr benötigt
delwin(fenster2);

Verschieben können Sie ein Fenster mit der Funktion:

#include <curses.h>
int mvwin(WINDOW *win, int y, int x);

Als Ausgangspunkt zum Verschieben wird immer die linke obere Ecke genommen. Diese ermitteln Sie, falls Ihnen diese nicht bekannt ist, mit der Funktion:

#include <curses.h>
void getbegyx(WINDOW *win, int y, int x);

Achtung! Auch hier gilt es, nicht den Adressoperator & für die Variablen y und x einzusetzen. Wollen Sie beispielsweise den Bildschirm von der aktuellen Position drei Zeilen tiefer und zehn Spalten nach rechts setzen, dann funktioniert dies so:

int y,x;
...
getbegyx(fenster1,y,x);
mvwin(fenster1,y+3,x+10);

Benötigen Sie die gesamte Größe eines Fensters, dann verwenden Sie die Funktion

#include <curses.h>
int getmaxyx(WINDOW *win, int y, int x);

Auch hier benötigen Sie für die Variablen y und x keinen Adressoperator.

Manchmal, wenn Sie die Funktion refresh() benötigen, um den gesamten Fensterinhalt neu zu zeichnen, benötigen Sie eine Funktion, die ein Fenster als verändert markiert. Denn refresh() zeichnet nur dann neu, wenn sich auf dem Bildschirm auch etwas verändert hat. Ein Fenster als verändert markieren können Sie mit der Funktion

#include <curses.h>
int touchwin(WINDOW *win);

Der Standardbildschirm stdscr kann nun mittels refresh() neu gezeichnet werden. Bei anderen Fenstern benötigen Sie folgende Funktion:

#include <curses.h>
wrefresh(WINDOW *win);

Falls Sie überprüfen wollen, ob sich auf dem Fenster etwas verändert hat, können Sie dies mit der Funktion is_wintouched() abfragen.

#include <curses.h>
int is_wintouched(WINDOW *win);

Ein kurzer Codeausschnitt, der dies demonstrieren soll:

// wurde Fenster verändert?
if(is_wintouched(fenster1)) 
    // dann zeichne neu
    wrefresh(fenster1);   
else {
   // tu so, als ob das Fenster verändert wurde
   touchwin(fenster1); 
   // ... und zeichne dann neu
   wrefresh(fenster1);    
}

Wollen Sie einzelne Zeichen von einem Fenster in das andere Fenster kopieren, dann steht Ihnen folgende Funktionen zur Verfügung:

#include <curses.h>
int overwrite(const WINDOW *quelle, WINDOW *ziel);
int overlay(const WINDOW *quelle, WINDOW *ziel);

Diese beiden Funktionen kopieren (nur) die Zeichen vom Fenster quelle ins Fenster ziel. Der Unterschied der beiden Funktionen liegt daran, dass overlay() kein Leerzeichen mitkopiert.

Jetzt folgt wieder ein für sich selbst sprechendes Programm, das Ihnen die meisten Fensterroutinen in der Praxis demonstrieren soll. Die Anwendung hält nach jedem Beispiel an, die Sie aber mit einem Tastendruck weiter ausführen können.

/* cur12.c */
#include <curses.h>
#define schwarz  0
#define rot      1
#define gruen    2
#define gelb     3
#define blau     4
#define lila     5
#define hblau    6
#define weiss    7
// zeigt uns alle möglichen Farbpaare an
static void show_all_pairs(void) {
  int farbe1, farbe2, i=1, c;
  mvprintw(1,1,"Darstellung aller Farbpaare: \n");
  if(has_colors() == TRUE) {
    start_color();
    for(farbe1=weiss; farbe1 >= schwarz; farbe1--)
      for(farbe2=schwarz; farbe2 <= weiss; farbe2++) {
        init_pair(i, farbe1, farbe2);
        attrset(COLOR_PAIR(i));
        printw(" %d ",i++);
      }
  }
  noecho();
  c=getch();
}
// Erzeugen ein neues Fenster mit sämtlichen Attributen
static WINDOW *create_new_window( 
     WINDOW *neues_fenster, int zeilen,int spalten, int hinterg,
     int vorderg,int begin_y, int begin_x, char *text,
     int text_y, int text_x ) {
  neues_fenster = newwin(zeilen, spalten, begin_y, begin_x);
  init_pair(1, hinterg, vorderg);
  wattrset(neues_fenster, COLOR_PAIR(1));
  box(neues_fenster, ACS_VLINE, ACS_HLINE);
  mvwprintw(neues_fenster, text_y, text_x, text);
  return neues_fenster;
}
int main(void) {
  WINDOW *fenster1,*fenster2;
  int y,x;
  initscr();
  raw();
  keypad(stdscr,TRUE);
  if(has_colors() == TRUE)
    start_color();
  show_all_pairs();
  clear();
  refresh();
  fenster1=create_new_window( fenster1,11,50,blau,weiss,0,0,
                              "Hallo Welt",5,20 );
  keypad(fenster1,TRUE);
  wrefresh(fenster1);
  wgetch(fenster1);
  fenster2=create_new_window( fenster2,10,50,rot,gruen,11,30,
                              "Fenster 2",1,1);
  keypad(fenster2,TRUE);
  wrefresh(fenster2);
  wgetch(fenster2);
  mvwprintw(fenster1,5,5,"Wir verschieben Fenster 2"
            " mit mvwin()");
  wrefresh(fenster1);
  wgetch(fenster1);
  // Wo ist das Fenster 2 genau?
  getbegyx(fenster2,y,x); 
  // stdscr neuzeichnen
  touchwin(stdscr);
  refresh();
  // Fenster 3 Zeilen tiefer und 20 Spalten nach links
  mvwin(fenster2,y+3,x-20);
  // Fenster 1 wurde nicht verändert ...
  // ... aber wir wollen trotzdem neu zeichnen ...
  touchwin(fenster1);
  wrefresh(fenster1);
  wrefresh(fenster2);
  wgetch(fenster2);
  mvwprintw(fenster1,5,5," Wir löschen Fenster 2 mit delwin() ");
  wrefresh(fenster1);
  wgetch(fenster1);
  // Fenster 2 löschen
  delwin(fenster2);
  touchwin(stdscr);
  refresh();
  mvwin(fenster1,0,0);
  wrefresh(fenster1);
  wgetch(fenster1);
  mvwprintw(fenster1,5,5,"Wir kopieren dieses Fenster "
            "mit dupwin()");
  wrefresh(fenster1);
  wgetch(fenster1);
  fenster2=dupwin(fenster1);
  mvwin(fenster2,12,0);
  wrefresh(fenster2);
  wgetch(fenster2);
  mvwprintw(fenster1,5,1," Fenster 1 wird nun geloescht ");
  wrefresh(fenster1);
  wgetch(fenster1);
  delwin(fenster1);
  touchwin(stdscr);
  refresh();
  touchwin(fenster2);
  wrefresh(fenster2);
  wgetch(fenster2);
  attrset(COLOR_PAIR(0));
  mvprintw(1,1,"Nun wird das letzte Fenster auch noch beendet");
  refresh();
  wgetch(fenster2);
  delwin(fenster2);
  touchwin(stdscr);
  refresh();
  mvprintw(1,1,"Jetzt ist nur noch das Fenster stdscr"
           " in Betrieb!");
  getch();
  endwin();
  return 0;
}

Rheinwerk Computing

13.3.7 Mausprogrammierung mit ncurses  toptop

Mit ncurses ist es auch möglich, die Maus mit einzubeziehen. Gemeint sind damit allerdings jetzt hier nicht die Funktionen der Bibliothek gpm. Der Vorteil der Mausroutinen von ncurses ist, dass diese sowohl im Textmodus als auch im XTerm laufen.


Hinweis   Zur Mausprogrammierung mit der gpm-Library finden Sie etwas auf der Buch-CD.


Folgende Struktur wurde für die Mausprogrammierung mit ncurses definiert:

typedef struct {
  short ID;         // ID, falls mehrere Mäuse angeschlossen sind 
  int x,y,z;        // Koordinaten, z nicht benötigt
  mmask_t bstate;   // Bitmaske für Mausknöpfe
} MEVENT;

Der Datentyp mmask_t ist ein primitiver Datentyp, der meistens als unsigned long dargestellt wird.

Damit ncurses überhaupt eine Mauseingabe zur Kenntnis nimmt, muss die Funktion mousemask() aufgerufen werden.

#include <curses.h>
mmask_t mousemask(mmask_t neuemaske, mmask_t *altemaske);

Mit dem Parameter neuemaske geben Sie an, welche Mausereignisse bearbeitet werden sollen. Folgende Mausereignisse sind dabei in ncurses definiert:

#define BUTTON1_RELEASED        000000000001L
#define BUTTON1_PRESSED         000000000002L
#define BUTTON1_CLICKED         000000000004L
#define BUTTON1_DOUBLE_CLICKED  000000000010L
#define BUTTON1_TRIPLE_CLICKED  000000000020L
#define BUTTON1_RESERVED_EVENT  000000000040L
#define BUTTON2_RELEASED        000000000100L
#define BUTTON2_PRESSED         000000000200L
#define BUTTON2_CLICKED         000000000400L
#define BUTTON2_DOUBLE_CLICKED  000000001000L
#define BUTTON2_TRIPLE_CLICKED  000000002000L
#define BUTTON2_RESERVED_EVENT  000000004000L
#define BUTTON3_RELEASED        000000010000L
#define BUTTON3_PRESSED         000000020000L
#define BUTTON3_CLICKED         000000040000L
#define BUTTON3_DOUBLE_CLICKED  000000100000L
#define BUTTON3_TRIPLE_CLICKED  000000200000L
#define BUTTON3_RESERVED_EVENT  000000400000L
#define BUTTON4_RELEASED        000001000000L
#define BUTTON4_PRESSED         000002000000L
#define BUTTON4_CLICKED         000004000000L
#define BUTTON4_DOUBLE_CLICKED  000010000000L
#define BUTTON4_TRIPLE_CLICKED  000020000000L
#define BUTTON4_RESERVED_EVENT  000040000000L
#define BUTTON_CTRL             000100000000L
#define BUTTON_SHIFT            000200000000L
#define BUTTON_ALT              000400000000L
#define ALL_MOUSE_EVENTS
#define REPORT_MOUSE_POSITION

Wollen Sie jetzt z. B. überprüfen, ob die linke Maustaste gedrückt wurde, müssen Sie folgende Maske einrichten:

int maske = mousemask(BUTTON1_PRESSED, NULL);

Das Mausereignis können Sie mit wgetch() »einlesen«:

chtype button;
button = wgetch(stdscr);

Wurde eine Mausaktivität (z. B. eine Maustaste wurde betätigt) registriert, so gibt wgetch() die symbolische Konstante KEY_MOUSE zurück:

if(button == KEY_MOUSE) {
   //Mausaktivitäten
 }

Welche Aktivität das jetzt genau war, lässt sich mit der folgenden Funktion herausfinden:

#include <curses.h>
int getmouse(MEVENT *event);

Ein Codeausschnitt dazu sieht so aus:

MEVENT event;
...
if(getmouse(&event) == OK) {
   // Konnte Mausereignis lesen
 }

Der Rückgabewert der Funktion getmouse() lautet OK bei erfolgreichem Lesen, ansonsten bei einem Fehler ERR.

Jetzt, da mit der Funktion getmouse() die Strukturvariablen von MEVENT mit den entsprechenden Werten »belegt« wurden, können Sie diese wie folgt auswerten:

// y-Position des Mausereignisses
y-Koordinate = event.y;
// x-Position des Mausereignisses
x-Koordinate = event.x;    
// (eventuell) Mausknopf, wenn betätigt
bitmaske = event.bstate;
// Maus-ID, falls mehrere "Mäuse" vorhanden  
ID = event.id;

Wollen Sie z. B. auf Doppelklick testen, müssen Sie eigentlich nur die Strukturvariable bstate wie folgt überprüfen:

If(event.bstate == BUTTON1_DOUBLE_CLICK) {
   //der linke Mausbutton wurde doppelt geklickt 
 }

Um zu überprüfen, wo und ob im zulässigen Fenster die Mauskoordinate (y, x) liegt, benötigen Sie die Funktion:

#include <curses.h>
bool wenclose(WINDOW *win, int y, int x);

Der Rückgabewert dieser Funktion lautet, falls das Mausereignis an einer gültigen Position im Fenster win auftrat, TRUE, ansonsten FALSE. Für x und y werden gewöhnlich die ermittelten Werte der Strukturvariablen x und y von MEVENT verwendet.

Ein kleines Programmbeispiel soll Ihnen die Verwendung der Maus mit ncurses demonstrieren. Sie klicken in einen Bereich des Fensters stdscr, und es werden die Werte zurückgegeben, an welcher Position genau Sie im Fenster den linken Mausbutton betätigt haben.

/* cur13.c */
#include <curses.h>
#include <stdlib.h>
int main (void) {
  MEVENT pos;
  int l_maus;
  chtype button;
  initscr ();
  noecho ();
  keypad (stdscr, TRUE);
  l_maus = mousemask (BUTTON1_PRESSED, NULL);
  while (1) {
    button = wgetch (stdscr);
    if (button == KEY_MOUSE) {
      if (getmouse (&pos) == OK) {
        wenclose (stdscr, pos.y, pos.x);
        mvwprintw (stdscr, 1, 0, "y = %2d x = %2d",
                   pos.y, pos.x);
      }
    }
    wrefresh (stdscr);
  }
  endwin ();
  return 0;
}

Dieses Beispiel soll jetzt noch durch die zusätzliche Überprüfung erweitert werden, an welcher Stelle im Fenster stdscr die Maustaste gedrückt wurde (y,x) und an welcher Stelle die Maustaste im Fenster wieder losgelassen wurde:

/* cur14.c */
#include <curses.h>
#include <stdlib.h>
int main (void) {
  MEVENT pos;
  int l_maus;
  chtype button;
  initscr ();
  noecho ();
  keypad (stdscr, TRUE);
  l_maus = mousemask 
   ( BUTTON1_RELEASED | BUTTON1_PRESSED | BUTTON1_CLICKED, NULL);
  while (1) {
    button = wgetch (stdscr);
    clear ();
    if (button == KEY_MOUSE) {
      if (getmouse (&pos) == OK) {
        // linke Maustaste gedrückt ...
        if (pos.bstate == BUTTON1_PRESSED) {
          wenclose (stdscr, pos.y, pos.x);
          mvwprintw (stdscr, 1, 0,
                  "linke Maustaste gedrückt bei y = %2d x = %2d",
                  pos.y, pos.x);
        }
        // linke Maustaste losgelassen ...
        if (pos.bstate == BUTTON1_RELEASED) {
          // Reaktionszeit eine Tausendstelsekunde setzen
          mouseinterval (1);
          wenclose (stdscr, pos.y, pos.x);
          mvwprintw (stdscr, 2, 0,
                   "linke Maustaste losgelassen y = %2d x = %2d",
                   pos.y, pos.x);
        }
      }
    }
    wrefresh (stdscr);
  }
  endwin ();
  return 0;
}

Wenn die Maustaste zu schnell oder zu langsam reagiert, können Sie (wie im Beispiel eben gesehen) die folgende Funktion zur Änderung benutzen:

#include <curses.h>
int mouseinterval(int Tausendstelsekunde);

Zum Abschluss des Kapitels werden die Mausroutinen etwas praxisnäher eingesetzt. Sie erzeugen ein Fenster (WINDOW *fenster) und geben dieses auf dem Bildschirm aus. Sie können jetzt in jedem beliebigen Bereich im stdscr mit der linken Maus drücken, und das Fenster wird an diesem Punkt, falls möglich, versetzt. Als Ausgangspunkt dient die linke obere Ecke von stdscr. Weiterhin befindet sich an der linken oberen Ecke der Buchstabe 'S' und an der rechten oberen Ecke der Buchstabe 'M'. Mit 'S' für »Schließen« schließen Sie das Fenster und mit 'M' für »Maximieren« maximieren Sie es. Ein erneutes Klicken auf 'M' bewirkt wieder das Minimieren zur normalen Größe.

/* cur15.c */
#include <curses.h>
#include <stdlib.h>
#define MAXSIZE 1
#define MINSIZE 0
static int y_click, x_click;
static int y_click2, x_click2;
static int size = MINSIZE;
static void 
where_clicked (WINDOW * win, WINDOW * win2, MEVENT pos) {
  // linke obere Ecke von win2
  getbegyx (win2, y_click2, x_click2);
  mouseinterval (1);
  // wo in win wurde geklickt
  wenclose (win, pos.y, pos.x);
  y_click = pos.y;
  x_click = pos.x;
  // wurde auf die linke obere Ecke von win2
  if (y_click == y_click2 && x_click == x_click2) {
    endwin ();
    exit (EXIT_FAILURE);
  }
  // ... oder wurde auf die rechte obere Ecke geklickt 
  else if (y_click == y_click2 && x_click == x_click2 + 39 ||
           x_click == x_click2 + 78) {
    touchwin (stdscr);
    refresh ();
    if (size == MINSIZE) {
      delwin (win2);
      size = MAXSIZE;
    } 
    else if (size == MAXSIZE) {
      delwin (win2);
      size = MINSIZE;
    }
  }
}
/* Verschieben des Fensters an Position der globalen Variablen  */
/* x_click und y_click, die zuvor mit where_clicked() ermittelt */
/* wurden                                                       */
static void move_win_to (WINDOW * win) {
  touchwin (stdscr);
  wrefresh (stdscr);
  mvwin (win, y_click + 1, x_click + 1);
  touchwin (win);
  wrefresh (win);
}
static WINDOW *create_new_window (
      WINDOW * neues_fenster, int zeilen, int spalten,
      int hinterg, int vordergr, int begin_y, int begin_x ) {
  neues_fenster = newwin (zeilen, spalten, begin_y, begin_x);
  init_pair (1, hinterg, vordergr);
  wattrset (neues_fenster, COLOR_PAIR (1));
  box (neues_fenster, ACS_VLINE, ACS_HLINE);
  return neues_fenster;
}
int main (void) {
  MEVENT pos;
  int l_maus;
  chtype button;
  WINDOW *fenster;
  initscr ();
  if (has_colors () == TRUE)
    start_color ();
  noecho ();
  keypad (stdscr, TRUE);
  fenster = create_new_window (
    fenster, 10, 40, COLOR_RED, COLOR_BLUE, 5, 15);
  init_pair (2, COLOR_BLACK, COLOR_YELLOW);
  wattrset (fenster, COLOR_PAIR (2));
  mvwaddch (fenster, 0, 0, 'S');
  mvwaddch (fenster, 0, 39, 'M');
  wattrset (fenster, COLOR_PAIR (0));
  mvwprintw (fenster, 2, 10, "S=Fenster schliessen");
  mvwprintw (fenster, 6, 10, "M=Fenster maximieren");
  wrefresh (fenster);
  l_maus = mousemask (BUTTON1_RELEASED | BUTTON1_PRESSED, NULL);
  while (1) {
    button = wgetch (stdscr);
    if (button == KEY_MOUSE) {
      if (getmouse (&pos) == OK) {
        where_clicked (stdscr, fenster, pos);
        // move_win_to(fenster); 
        if (size == MAXSIZE) {
          fenster =
            create_new_window (fenster,
                               24, 79,
                               COLOR_RED,
                               COLOR_BLUE,
                               0, 0);
          wattrset (fenster, COLOR_PAIR (2));
          mvwaddch (fenster, 0, 0, 'S');
          mvwaddch (fenster, 0, 78, 'M');
          wattrset (fenster, COLOR_PAIR (0));
          mvwprintw (fenster, 10, 30,
                     "S=Fenster schliessen");
          mvwprintw (fenster, 14, 30,
                     "M=Fenster minimieren");
          wrefresh (fenster);
        } else if (size == MINSIZE) {
          fenster =
            create_new_window (fenster,
                               10, 40,
                               COLOR_RED,
                               COLOR_BLUE,
                               5, 15);
          init_pair (2, COLOR_BLACK,
                     COLOR_YELLOW);
          wattrset (fenster, COLOR_PAIR (2));
          mvwaddch (fenster, 0, 0, 'S');
          mvwaddch (fenster, 0, 39, 'M');
          wattrset (fenster, COLOR_PAIR (0));
          mvwprintw (fenster, 2, 10,
                     "S=Fenster schliessen");
          mvwprintw (fenster, 6, 10,
                     "M=Fenster maximieren");
          wrefresh (fenster);
          move_win_to (fenster);
        }
      }
    }
  }
  endwin ();
  return 0;
}

So sieht‘s aus:


Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 13.3    Eine einfache Dialogbox für die Konsole



Hinweis   In diesem Listing wird ebenfalls davon ausgegangen, dass Sie eine gewöhnliche 80 x 24 cm große Konsole (z. B. VT100) verwenden. Andernfalls kann die Anwendung nicht richtig laufen (es sei denn, Sie passen die Breite der Konsole dem Listing an).


Mit diesem Grundwissen können Sie jetzt eine passable Benutzeroberfläche für die Konsole bauen. Zwei weitere Funktionen zur Mausprogrammierung mit ncurses, die es noch gibt, hier aber nicht durchgenommen wurden, wären:

int ungetmouse(MEVENT *event);
bool wmouse_trafo(WINDOW *win, int *Py, int *Px);

Für nähere Informationen lesen Sie bitte die Manual Page (man mouse).


Hinweis   Ursprünglich hätte ich hier gerne noch ein etwas umfangreicheres Beispiel demonstriert. Es handelt sich dabei um ein Videoverwaltungsprogramm mit ncurses und den verketteten Listen. Da das Listing allein mehrere Seiten im Buch verschlungen hätte und ich Ihnen das nicht zumuten will, finden Sie das Beispiel auf der Buch-CD wieder.


 << zurück
  
  Zum Rheinwerk-Shop
Neuauflage: Linux-UNIX-Programmierung
Neuauflage:
Linux-UNIX-
Programmierung

bestellen
 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchtipps
Zum Rheinwerk-Shop: Linux-Server






 Linux-Server


Zum Rheinwerk-Shop: Das Komplettpaket LPIC-1 & LPIC-2






 Das Komplettpaket
 LPIC-1 & LPIC-2


Zum Rheinwerk-Shop: Linux-Hochverfügbarkeit






 Linux-
 Hochverfügbarkeit


Zum Rheinwerk-Shop: Shell-Programmierung






 Shell-
 Programmierung


Zum Rheinwerk-Shop: Linux Handbuch






 Linux Handbuch


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
Info





Copyright © Rheinwerk Verlag GmbH 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern