Unterabschnitte

Elementare Fenstertechnik

Ein wenig Theorie zur Fenstertechnik

Was ist ein Fenster?

Ein Fenster innerhalb der libCUI beschreibt einen rechteckigen Bildschirmbereich, der mit einer rudimentären Basisfunktionalität ausgestattet ist. Um ein Fenster an eine spezielle Aufgabe anzupassen wird es mit Eigenschaften und Hook-Funktionen versehen.

Eigenschaften sind u.a. Fensterstile und Farben. Sie steuern hauptsächlich das optische Erscheinungsbild des Fensters und dessen Verhalten innerhalb des Fensterstapels.

über Hook-Funktionen werden dem Fenster vom Fenstermanager Nachrichten zugesendet, auf die es durch selbstimplementierte Weise reagieren kann. Solche Nachrichten sind z.B. die Aufforderung zum Zeichnen des Fensterbereichs oder die übergabe von Tastatureingaben an das Fenster.

über einen nichttypisierten Datenzeiger kann ein Fenster Instanz-Daten verwalten, die es für seine Funktion benötigt.

Lebenszyklus

Ein Fenster macht üblicherweise die folgenden Stadien im Laufe seiner Existenz durch: Anlegen, Spezialisieren, Erzeugen und Zerstören.

Image window1

Anlegen:
Das Anlegen einer neuen Fensterinstanz erfolgt mit Hilfe der Funktion ''WindowNew''. Dabei werden dem Fenster eine Position, eine Vater-Beziehung und eine Reihe von Fensterstilen mitgegeben. Das Ergebnis ist eine Struktur vom Typ CUIWINDOW, über die das Fenster in Zukunft angesprochen und identifiziert werden kann.

Spezialisieren:
Durch das Spezialisieren bekommt das Fenster weitere Eigenschaften (z.B. Farben), seine Funktionalität (Hook-Funktionen) und seine Instanzdaten beigebracht. Zu diesem Zweck gibt es eine Reihe von API-Funktionen. Siehe dazu u.a. den Abschnitt ''Hook Funktionen''.

Erzeugen:
Bevor ein Fenster im Fensterstapel sichtbar werden kann, muss es erzeugt werden. Dies erfolgt mit der Funktion ''WindowCreate''. Dabei werden die erforderlichen Curses-Strukturen erzeugt und das Fenster in den Fensterstapel eingebunden.

Zerstören:
Zerstört wird ein Fenster über die Funktion ''WindowDestroy''. Damit wird das Fenster und alle seine Kindfenster aus dem Fensterstapel entfernt. Zudem werden die zugehörigen Datenstrukturen freigegeben. Das heißt auch, dass ein Fenster wieder neu angelegt werden muss, bevor es erneut verwendet werden kann.

Es bietet sich an, das Anlegen und Spezialisieren in einer Funktion zu vereinen. Eingebürgert hat sich dabei die Namenskonvention FensternameNew. Beispiele: ''EditNew'', ''LabelNew'', ''ListboxNew'' ...

Fensterhierarchie

Grob unterteilt gibt es in der libCUI zwei Fensterkategorien: Popup-Fenster und Kindfenster.

Kindfenster sind immer ihrem Vaterfenster untergeordnet. Die Positionierung erfolgt relativ zum Vaterfenster und der sichtbare Bereich wird durch den Client Bereich des Vaterfensters gegrenzt (Clipping).

Popup-Fenster dagegen sind immer dem Desktopfenster untergeordnet und besitzen lediglich einen Besitzer- (Owner-) Verweis auf dasjenige Popup-Fenster aus dessen Kontext (Kindfenster) heraus das Vaterfenster angegeben wurde. Der sichtbare Bereich wird lediglich von weiteren übergeordneten Popup-Fenstern maskiert, nicht jedoch vom Vaterfenster.

Fenster werden in einem Stapel hierarchisch angeordnet, wobei die Basis aller Fenster das Desktopfenster ist. Dieses existiert bereits nach dem Aufruf von ''WindowStart()'', besitzt jedoch keine besondere Funktion (außer der Darstellung einer einfarbigen Fläche). über dem Desktop ist der Fensterstapel angeordnet, wobei jedes Fenster vier Referenzen kennt: Das Vaterfenster (P = Parent), das Besitzerfenster (O = Owner), das nächste Fenster in der selben Hierarchieebene (N = Next) und das erste Fenster in der Liste der Kindfenster (C = Child). Es ergibt sich die in der Abbildung dargestellte Struktur.

Image window3

Wird ein Fenster aus dem Fensterstapel entfernt (''WindowDestroy''), dann verschwinden auch sämtliche Kindfenster von der Anzeige. Das selbe passiert auch dann, wenn die Sichtbarkeit eines Fensters beeinflusst wird.

Kindfenster des Desktops sind übrigens immer Popup-Fenster. Dieser Stil wird beim Erzeugen erzwugen, wenn als Vaterfenster das Desktopfenster angegeben wurde.

Bereiche eines Fensters

Ein Fenster in der libCUI besitzt zwei Darstellungsbereiche: Den Client Bereich und den Nicht-Client Bereich. Im Nicht-Client Bereich werden Fensterdekorationen wie z.B. ein Rahmen eine Titlezeile oder Bildlaufleisten dargestellt. Im Client Bereich kann ein Fenster seine Daten zur Ansicht bringen oder weitere Kindfenster anordnen. Jedes Fenster besitzt immer beide Bereiche. Ob der Nicht-Client Bereich tatsächlich sichtbar ist, wird über Fensterstile gesteuert.

Beispiel

Anhand eines Code-Beispiels werden nun einige der vorgestellten Konzepte erläutert. Das vorgestellte Programm soll ein einfaches Popup-Fenster (vergleichbar dem Hallo-World Programm aus dem vorigen Kapitel) zentriert darstellen, in dem die aktuelle Systemzeit ausgegeben wird. Hier das Programmlisting:

#include <stdlib.h>
#include <time.h>
#include <cui.h>

#define MY_TIMER 100

void quit(void)
{
   WindowEnd();
}

void MyPaintHook(void* w)
{
   CUIWINDOW* win = (CUIWINDOW*) w;
   CUIRECT    rc;
   char       tstr[64];
   time_t     ti;
   struct tm* loctime;

   WindowGetClientRect(win, &rc);

   time(&ti);

   loctime = localtime(&ti);

   sprintf(tstr, "%02i:%02i:%02i",
      loctime->tm_hour,
      loctime->tm_min,
      loctime->tm_sec);
   mvwaddstr(win->Win, rc.H / 2 - 1, 
            (rc.W - strlen(tstr)) / 2, tstr);

   strcpy(tstr, "F10 = Close");
   mvwaddstr(win->Win, rc.H / 2 + 1, 
            (rc.W - strlen(tstr)) / 2, tstr);
}

int MyKeyHook(void* w, int key)
{
   if (key == KEY_F(10))
   {
      if (WindowClose((CUIWINDOW*) w, EXIT_SUCCESS))
      {
         WindowKillTimer((CUIWINDOW*) w, MY_TIMER);
      }

      return TRUE;
   }
   return FALSE;
}

void MyTimerHook(void* w, int id)
{
   WindowInvalidate((CUIWINDOW*) w);
}

int main(void)
{
   CUIWINDOW* mywin;

   WindowStart(TRUE, TRUE);
   atexit(quit);

   mywin = WindowNew(WindowGetDesktop(),
                     0, 0, 25, 7,
                     CWS_POPUP | CWS_BORDER | CWS_CENTERED);

   WindowSetText      (mywin, "Clock Window");
   WindowSetPaintHook (mywin, MyPaintHook);
   WindowSetKeyHook   (mywin, MyKeyHook);
   WindowSetTimerHook (mywin, MyTimerHook);
   
   WindowCreate       (mywin);
   WindowSetTimer     (mywin, MY_TIMER, 500);

   return WindowRun();
}

Zunächst ist die ''main''-Routine zu beachten. Hier wird wie gehabt der curses-Modus aktiviert und anschließend über ''WindowNew'' eine neue Fensterinstanz angelegt. Diese erhält einen Verweis auf das Vaterfenster (hier das Desktop-Fenster), eine Position (0, 0) und eine Größe (25, 7). Zudem werden mehrere Fensterstile angegeben, die festlegen, dass das Fenster ein Popup-Fenster mit einem Rand sein soll und immer zentriert in der Bildschirmmitte anzuordnen ist.

Nun wird das Fenster spezialisiert, indem es einen Fenstertitel und drei Hook-Funktionen zugewiesen bekommt. Die Hook-Funktion ''MyPaintHook'' ist für das Zeichnen des Client-Bereichs des Fensters zuständig, die Hook-Funktion ''MyKeyHook'' bearbeitet die Tastatureingaben des Anwenders und die Funktion ''MyTimerHook'' empfängt Timer-Ereignisse, falls ein Window-Timer aktiviert wurde.

Abschließend wird das Fenster über ''WindowCreate'' erzeugt und erhält zudem durch ''WindowSetTimer'' einen Window-Timer zugewiesen, der alle 500 Millisekunden abläuft.

Die Funktion ''WindowRun'' aktiviert nun den Window-Manager und kehrt erst dann zum Hauptprogramm zurück, wenn das Fenster geschlossen wurde. Der Rückgabewert ist dabei der Exit-Code, der der WindowClose-Funktion mitgegeben wurde (siehe ''MyKeyHook'').

Die Hook-Funktion ''MyPaintHook'' ermittelt das Rechteck des Client-Bereichs des Fensters und zudem die aktuelle Systemzeit. Letztere wird in eine Zeichenkette umgewandelt und mit der curses-Funktion ''mvwaddstr'' zentriert im Fenster ausgegeben. Dabei ist unbedingt zu beachten, dass keine curses-Funktion aufgerufen wird, die für die Aktualisierung des Bildschirminhalts zuständig ist (''wupdate'' o.ä.). Die Aktualisierung wird vollständig vom Fenster-Manager übernommen.

Bei der Funktion ''MyKeyHook'' handelt es sich um eine Hook-Funktion, die einen bool-Wert als Rückgabewert kennt. Über diesen wird dem System signalisiert, ob das an die Funktion übergebene Zeichen behandelt wurde oder nicht. Falls die Funktion ''FALSE'' zurückgibt, wird die Standardbehandlung der libCUI für die eingegebene Taste aktiv.

Falls die Funktion ''MyKeyHook'' die F10-Taste erkennt, wird versucht das Fenster über die Funktion ''WindowClose'' zu schließen, was im vorliegenden Fall auch immer gelingt. Im Hintergrund wird dabei die hier nicht implementierte Hook-Funktion ''CanCloseHook'' für das aktuelle Fenster (und alle seine Kindfenster) aufgerufen, über die angefragt wird, ob das Schließen des Fensters erlaubt ist oder nicht. Falls das Fenster geschlossen wurde, wird auch der Window-Timer beendet.

Die Timer-Funktion ''MyTimerHook'' verzichtet auf die Auswertung des Parameters ''id'', da nur ein Timer aktiviert wurde. Anderenfalls muss über ''id'' der richtige Timer zugeordnet werden. Der Aufruf der Funktion ''WindowInvalidate'' bewirkt ein Neuzeichnen des Fensters und damit das Aktualisieren des Bildschirminhaltes. Alternativ hätten auch direkt hier die Textausgaben in das Fenster erfolgen können. Allerdings darf dann am Ende der Funktion der Funktionsaufruf ''WindowInvalidateScreen'' nicht fehlen, der die Bildschirmausgabe (ohne Neuzeichnen des Fensters) erzwingt.

Fensterklassen und Instanzen

Im Grunde ist die Programmierung unter der libCUI in Ansätzen mit der objektorientierten Programmierung vergleichbar. Der vorliegende Abschnitt soll diese Sichtweise verdeutlichen.

Jedes Fenster ist eine Instanz des Objekts CUIWINDOW, wobei die (virtuellen) Objektmethoden des Objekts die Hook-Funktionen sind und die Datenelemente (Objektvariablen) in CUIWINDOW->InstData gespeichert werden. Soll ein Fenster mit einem Verhalten implementiert werden, das vom Standardverhalten abweicht, was so gut wie immer der Fall sein sollte, dann erbt das Fenster zunächst das Standardverhalten, kann dieses aber durch überschreiben der Hook-Funktionen ändern.

Da jedoch unter 'C' die erforderlichen Sprachelemente fehlen, also keine Konstruktoren oder Destruktoren verfügbar sind und auch kein ''this'' Zeiger funktioniert, sind ein paar Klimmzüge bei der Programmierung erforderlich:

Image window5

Objekt anlegen

Instanz anlegen -> WindowNew()
Objektmethoden zuweisen -> WindowSetHookXXXX()
Datenbereich des Objekts -> win->InstData = malloc(sizeof(MYDATA));
Curses Fenster erzeugen -> WindowCreate()

Der Datenbereich des Objekts könnte auch im Hook ''WindowCreateHook'' angelegt werden (den man als Konstruktor verstehen könnte).

Objekt löschen

Wird WindowDestroy(win) aufgerufen, dann wird das Fenster und alle Kindfenster gelöscht. Dabei wird der Hook ''WindowDestroyHook'' aufgerufen. Der WindowDestroyHook ist quasi der Destruktor der Objektinstanz. Hier müssen alle Datenelemente in win->InstData gelöscht werden. Im einfachsten Fall ist das:

free(win->InstData);

Das Löschen der Fensterstruktur selbst (die mit WindowNew angelegt wurde) übernimmt der Window-Manager.

Objekt referenzieren

Da es in 'C' keinen ''this'' Zeiger gibt, ist nicht bekannt, mit welcher Fensterinstanz ein Hook- Aufruf verbunden ist. Beispielsweise verwenden alle Edit-Controls die selben Hooks.

Deshalb ist im Hook immer der Parameter ''w'' enthalten, der ein nicht typisierter Zeiger auf die Fenster-Instanz ist (also sozusagen ''this'').

über w->InstData kann dann auf die Objektvariablen der Objektinstanz zugegriffen werden. Solange nicht sicher ist, dass es nur eine Instanz eines Fensters geben wird, sollte also nicht mit globalen Veriablen sondern mit Instanzdaten geabreitet werden.

Typsicherheit

Da immer über Zeiger vom Typ ''CUIWINDOW'' auf Fensterinstanzen zugegriffen wird, ist die Typsicherheit gleich null. Wird über den InstData-Zeiger auf Datenelemente zugegriffen, die diese Fensterinstanz nicht kennt, dann sind die Folgen fatal. So kann z.B. ein CUIWINDOW-Zeiger auf eine ListBox an eine Routine übergeben werden, die ein Edit-Control erwartet. Ohne weitere Maßnahmen würde das Programm mit ziemlicher Sicherheit abstürzen.

Aus diesem Grund kennt die CUIWINDOW-Struktur den ''Class''-Zeiger, der auf eine konstante Zeichenkette zeigt, die den Namen der Klasse enthält. Standardmäßig zeigt dieser Zeiger immer auf die Zeichenkette ''WINDOW''. Dies kann jedoch nach dem Anlegen einer Fensterinstanz geändert werden.

Vor dem Zugriff auf ein Datenfeld einer Instanz, sollte deren Klassenzugehörigkeit geprüft werden. Allerdings gibt es bislang noch keinen Mechanismus innerhalb der libCUI, der doppelte Klassennamen verhindert.

Beispiel:

CUIWINDOW*
EditNew(CUIWINDOW* parent, const char* text, 
        int x, int y, int w, int h,
        int len, int id, int sflags, int cflags)
{
   if (parent)
   {
      CUIWINDOW* edit;
      int flags = sflags | CWS_TABSTOP;
      flags &= ~(cflags);

      edit = WindowNew(parent, x, y, w, h, flags);
      edit->Class = "EDIT";
 
      ...
 
      return edit;
   }
   return NULL;
}


void
EditSetText(CUIWINDOW* win, const char* text)
{
   if (win && (strcmp(win->Class, "EDIT") == 0))
   {
      EDITDATA* data = (EDITDATA*) win->InstData;
      strncpy(data->EditText, text, data->Len);
      data->EditText[data->Len] = 0;

      if (win->IsCreated)
      {
         WindowInvalidate(win);
      }
   }
}

Das Code-Fragment zeigt einen Ausschnitt aus der Implementierung des Edit- Kontrollelements, das von der libCUI zur Verfügung gestellt wird. Es ist dabei zu sehen, wie in der Funktion ''EditNew'' neue Instanzen angelegt und mit dem Klassenname ''EDIT'' versehen werden. Beim späteren Zugriff auf eine Fensterinstanz mittels ''EditSetText'' wird der Klassenname geprüft, bevor auf die Daten des Kontrollelements zugegriffen wird.

Fensterstile

Fensterstile werden bei dem Anlegen neuer Fensterinstanzen durch ''WindowNew'' an die Fenster weitergegeben. Die Verknüpfung der einzelnen Stile erfolgt dabei mit dem ''Oder''- Operator ''|''.

In der Regel beeinflusst ein Fensterstil eine Flag-Variable innerhalb der Struktur ''CUIWINDOW''. Der Stil ''CWS_HIDDEN'' bilded sich so z.B. in dem Flag ''IsHidden'' ab. Manche der Stile (Flags) können und dürfen über geeignete API- Funktionen während der Existenz des Fensters geändert werden (z.B. WindowShow()), die überigen, für die keine API- Funktionen zur Verfügung stehen, sollten dagegen so beibehalten werden, wie sie von ''WindowNew'' übernommen wurden.

Die folgende Tabelle zeigt eine übersicht über die Fensterstile, die in der libCUI bisher definiert und mit einer Funktion belegt sind:

Stil Bedeutung
CWS_NONE Platzhalter für ''keine Stilangabe''.
CWS_BORDER Fenster hat einen Rand.
CWS_CAPTION Fenster hat eine Kopfzeile.
CWS_HIDDEN Das Fenster ist verborgen.
CWS_DISABLED Das Fenster wird deaktiviert dargestellt und erlaubt keine Tastatureingabe.
CWS_TABSTOP Das Fenster akzeptiert den Eingabefokus.
CWS_CENTERED Das Fenster wird immer zentriert dargestellt (nur sinnvoll in Kombination mit CWS_POPUP).
CWS_POPUP Das Fenster ist ein Popup-Fenster.
CWS_MAXIMIZED Das Fenster ist maximiert und wird in seiner Größe immer an den Client-Bereich des Vaterfensters (bzw. des Desktop-Fensters) angepasst.
CWS_MINIMIZED Das Fenster ist minimiert (im Effekt wie CWS_HIDDEN).
CWS_STATUSBAR Das Fenster hat eine Statuszeile.
CWS_DEFOK Das Fenster ist das Standard-Kontrollelement der ENTER-Taste. Falls ENTER nicht mit einer speziellen Funktion belegt ist, wird das Kindfenster mit dem Stil CWS_DEFOK gesucht. Handelt es sich dabei um eine Schaltfläche, dann wird diese betätigt.
CWS_DEFCANCEL Wie CWS_DEFOK, nur dass hier die ESCAPE-Taste mit dem Kontrollelement verbunden ist.

 
Die Flags ''CWS_MAXIMIZED'', ''CWS_MINIMIZED'', ''CWS_HIDDEN'' und auch ''CWS_- DISABLED'' können mit den folgenden API-Funktionen während der Existenz des Fensters beeinflusst werden:

int  WindowMaximize(CUIWINDOW* win, int state);
int  WindowMinimize(CUIWINDOW* win, int state);
void WindowHide(CUIWINDOW* win, int state);
void WindowEnable(CUIWINDOW* win, int state);

Hook-Funktionen

Wie bereits in den vorhergehenden Abschnitten mehrfach angedeutet, wird das Verhalten eines Fensters von den Hook-Funktionen bestimmt. Hook-Funktionen werden vom Fenster- Manager aufgerufen, wenn ein Ereignis an ein Fenster gesendet werden soll. Ist dabei eine Hookfunktion nicht zugewiesen, dann tritt die jeweilige Standard-Behandlung in Kraft.

Die folgenden Funktionen werden verwendet um einem Fenster Hook-Funktionen zuzuweisen:

void WindowSetCreateHook(CUIWINDOW* win, HookProc proc);
void WindowSetInitHook(CUIWINDOW* win, HookProc proc);
void WindowSetCanCloseHook(CUIWINDOW* win, BoolHookProc proc);
void WindowSetDestroyHook(CUIWINDOW* win, HookProc proc);

void WindowSetPaintHook(CUIWINDOW* win, HookProc proc);
void WindowSetNcPaintHook(CUIWINDOW* win, Hook2IntProc proc);
void WindowSetSizeHook(CUIWINDOW* win, BoolHookProc proc);
void WindowSetSetFocusHook(CUIWINDOW* win, Hook1PtrProc proc);
void WindowSetKillFocusHook(CUIWINDOW* win, HookProc proc);

void WindowSetKeyHook(CUIWINDOW* win, BoolHook1IntProc proc);

void WindowSetMMoveHook(CUIWINDOW* win, Hook2IntProc proc);
void WindowSetMButtonHook(CUIWINDOW* win, Hook3IntProc proc);
void WindowSetVScrollHook(CUIWINDOW* win, Hook2IntProc proc);
void WindowSetHScrollHook(CUIWINDOW* win, Hook2IntProc proc);

void WindowSetTimerHook(CUIWINDOW* win, Hook1IntProc proc);

CreateHook

Prototyp:
        void CreateHook(void* win);

Der Create-Hook wird immer unmittelbar nach dem Erzeugen eines Fensters (innerhalb von ''WindowCreate'') aufgerufen. Es ist die erste Hook-Funktion die im Laufe eines Lebenszyklus eines Fensters aufgerufen wird.

Der Create-Hook kann verwendet werden, um die Instanz-Daten anzulegen oder um Kindfenster zu erzeugen.

InitHook

Prototyp:
        void InitHook(void* win);

Der Init-Hook wird ebenfalls aus der Funktion ''WindowCreate'' heraus aufgerufen. Allerdings nachdem der CreateHook ausgeführt und das Fenster über den PaintHook (und den NcPaintHook) zum Neuzeichnen aufgefordert wurde.

Der Init-Hook kann verwendet werden, um z.B. einen Anmeldedialog oder Ähnliches darzustellen, der als Popup-Fenster über dem gerade erzeugten Fenster erscheinen soll.

CanCloseHook

Prototyp:
        int CanCloseHook(void* win);

Soll ein Fenster über die API-Funktion ''WindowClose'' geschlossen werden, dann fragt der Fenster- Manager das betroffene Fenster und alle seine Kindfenster an, ob ein Schließen möglich ist. Die Hook- Funktion soll dabei den Wert ''TRUE'' zurückliefern, wenn das Fenster geschlossen werden kann und ''FALSE'' falls nicht. Als Standard für diese Hook-Funktion wird ein Rückgabewert von ''TRUE'' angenommen.

Der CanClose-Hook kann u.a. verwendet werden, um z.B. das Schließen eines Fensters zu verhindern, solange nicht alle Daten gespeichert wurden.

DestroyHook

Prototyp:
        void DestroyHook(void* win);}

Der Destroy-Hook wird aufgerufen, bevor ein Fenster endgültig aus dem Fensterstapel entfernt wird. Es ist die letzt Hook-Funktion, die ein Fenster im Laufe seiner Existenz sieht.

Hier werden üblicherweise die Instanz-Daten freigegeben, die mit dem Fenster verbunden sind. Kindfenster oder die CUIWINDOW- Struktur selbst brauchen und dürfen jedoch nicht freigegeben werden. Dafür sorgt das Fenstersystem selbst.

PaintHook

Prototyp:
        void PaintHook(void* win);

Der Paint-Hook fordert das Fenster zum Neuzeichnen des Client- Bereichs auf. Dies geschieht immer dann, wenn das Fenster erzeugt wurde, wenn sich die Größe des Fensters verändert hat oder wenn das Fenster manuell über ''WindowInvalidate'' als ungültig erklärt wurde.

Zur Textausgabe werden die bekannten curses-Funktionen wie ''waddstr'' o.ä. verwendet, wobei das zugehörige curses-Fenster über das CUIWINDOW-Datenelement ''Win'' angesprochen wird!

NcPaintHook

Prototyp:
        void NcPaintHook(void* win, size x, size y);

Der NcPaint-Hook fordert das Fenster zum Neuzeichnen des Nicht- Client Bereichs (= Fensterrahmen, Titel, Bildlaufleisten etc.) auf. Dies geschieht immer dann, wenn das Fenster erzeugt wurde, wenn sich die Größe des Fensters verändert hat oder wenn das Fenster manuell über ''WindowInvalidate'' als ungültig erklärt wurde.

Achtung: Die libCUI implementiert bereits ein Standardverhalten zum Zeichnen des Nicht-Client Bereichs. Diese Hook-Funktion braucht deshalb nur dann implementiert werden, wenn das Standardverhalten nicht ausreicht.

Zur Textausgabe werden die bekannten curses-Funktionen wie ''waddstr'' o.ä. verwendet, wobei das zugehörige curses-Fenster über das CUIWINDOW-Datenelement ''Frame'' angesprochen wird!

SizeHook

Prototyp:
        int SizeHook(void* win);

Der Size-Hook wird aufgerufen, wenn sich die Größe eines Fensters verändert hat. Falls die Darstellung des Fensters oder die Ausrichtung der Kindfenster von dieser Größenänderung betroffen sind, kann dies bei Bedarf innerhalb dieses Hooks angepasst werden.

Achtung: Die libCUI implementiert bereits ein Standardverhalten für die Größenänderung. Dabei werden alle maximierten Kindfenster auf die Größe des Client-Bereichs angepasst. Falls diese Funktionalität nicht erwünscht ist, sollte die Funktion ''TRUE'' zurückgeben und damit dem Fenster-Manager signalisieren, dass sie die Größenänderung vollständig selbst übernommen hat.

SetFocusHook

Prototyp:
        void SetFocusHook(void* win, void* lastfocus);

Wird aufgerufen, wenn ein Fenster den Eingabefokus erhält. Der Parameter ''lastfocus'' enthält dabei eine Referenz auf das Fenster, das zuletzt im Besitz des Eingabefokus war.

Diese Funktion wird oftmals dazu verwendet, um die Sichtbarkeit des Cursors zu steuern.

KillFocusHook

Prototyp:
        void KillFocusHook(void* win);

Wird aufgerufen, wenn einem Fenster der Eingabefokus entzogen wird.

Diese Funktion wird oftmals dazu verwendet, um die Sichtbarkeit des Cursors zu steuern.

KeyHook

Prototyp:
        int KeyHook(void* win, int key);

Wird immer dann aufgerufen, wenn ein über die Tastatur eingegebenes Zeichen an das Fenster mit dem Eingabefokus gesendet werden soll. Das Fenster kann dann entspechend auf die Taste reagieren und mit dem Rückgabewert ''TRUE'' signalisieren, dass die Taste akzeptiert wurde. Wird statt dessen ''FALSE'' zurückgegeben, wird das Standardverhalten der libCUI aktiv.

Das Standardverhalten steuert die Weitergabe des Eingabefokus mit den Pfeiltasten oder der TAB- Taste, sowie die Standardbehandlung der ENTER- und der ESCAPE- Taste.

MMoveHook

Prototyp:
        void MMoveHook(void* win, int x, int y);

Wird aufgerufen, wenn sich der Mauscursor über dem Fenster bewegt. Dies funktioniert nur dann, wenn die Mausbehandlung beim Aufruf von ''WindowStart'' aktiviert wurde und das Terminal diese Funktion unterstützt.

MButtonHook

Prototyp:
        void MButtonHook(void* win, int x, int y, int flags);

Wird aufgerufen, wenn eine Maustaste über einem Fenster betätigt wurde. Der Parameter ''flags'' gibt dabei den von curses definierten Code für die jeweilige Aktion der Maustasten weiter.

Dies funktioniert nur dann, wenn die Mausbehandlung beim Aufruf von ''WindowStart'' aktiviert wurde und das Terminal diese Funktion unterstützt.

flags Bedeutung
BUTTONx_PRESSED Maustaste x gedrückt
BUTTONx_RELEASED Maustaste x losgelassen
BUTTONx_CLICKED Maustaste x kurzer Klick
BUTTONx_DOUBLE_CLICKED Maustaste x Doppelklick
BUTTONx_TRIPLE_CLICKED Maustaste x Dreifachklick

 
In obenstehender Tabelle ist x ein Platzhalter für Werte zwischen 1 und 4.

VScrollHook

Prototyp:
        void VScrollHook(void* win, int sbcode, int pos);

Wird aufgerufen, wenn die vertikale Bildlaufleiste eines Fensters mit der Maus betätigt wurde. Dabei wird im Parameter ''sbcode'' die Art der Betätigung weitergegeben:

sbcode Bedeutung
SB_LINEDOWN Zeilenweise nach unten scrollen
SB_LINEUP Zeilenweise noch oben scrollen
SB_PAGEDOWN Seitenweise nach unten scrollen
SB_PAGEUP Seitenweise nach oben scrollen
SB_THUMBTRACK Direktes Verschieben auf die Position ''pos''

 
Der Parameter ''pos'' hat nur dann einen sinnvollen Wert, wenn ''sbcode'' den Wert ''SB_- THUMBTRACK'' enthält.

HScrollHook

Prototyp:
        void HScrollHook(void* win, int sbcode, int pos);

Wird aufgerufen, wenn die horizontale Bildlaufleiste eines Fensters mit der Maus betätigt wurde. Dabei wird im Parameter ''sbcode'' die Art der Betätigung weitergegeben:

sbcode Bedeutung
SB_LINEDOWN Spaltenweise nach rechts scrollen
SB_LINEUP Spaltenweise noch links scrollen
SB_PAGEDOWN Seitenweise nach rechts scrollen
SB_PAGEUP Seitenweise nach links scrollen
SB_THUMBTRACK Direktes Verschieben auf die Position ''pos''

 
Der Parameter ''pos'' hat nur dann einen sinnvollen Wert, wenn ''sbcode'' den Wert ''SB_- THUMBTRACK'' enthält.

TimerHook

Prototyp:
        void TimerHook(void* win, int id);

Wird aufgerufen, wenn ein für dieses Fenster aktivierter Window- Timer abgelaufen ist. Über den Parameter ''id'' kann dabei der jeweilige Timer identifiziert werden.

Darstellung und Bildschirm-Update

Wie die curses-Bibliothek, versucht auch die libCUI die Bildschirmausgaben so zu optimieren, dass möglichst wenige Daten an das Terminal gesendet werden müssen. Deshalb erscheint die Darstellung von Textausgaben in einem Fenster nicht unmittelbar am Bildschirm, sondern nur unter bestimmten Umständen:

Paint-Hook
Wird die Textausgabe innerhalb einer Paint- oder NcPaint- Ereignisbehandlung durchgeführt, dann wird anschließend der Bildschirminhalt automatisch als ungültig erklärt. Dies wiederum bewirkt automatisch die Aktualisierung des Terminals. Es handelt sich hierbei um den empfohlenen Standardfall. Soll aufgrund einer Statusänderung der Fensterinhalt verändert werden, kann der Aufruf der Paint-Routine über die Funktion ''WindowInvalidate()'' jederzeit erzwungen werden.

Direkte Textausgabe
Falls jedoch eine direkte Textausgabe in ein Fenster außerhalb der Paint-Routine erforderlich ist, muss anschließend der Bildschirminhalt manuell als ungültig erklärt werden. Dies geschieht mit der API-Funktion ''WindowInvalidateScreen()''. Der Fenster-Manager wird daraufhin die Aktualisierung des Bildschirminhaltes an geeigneter Stelle vornehmen.

Erzwungenes Bildschirmupdate
Muss zu einem definierten Zeitpunkt ein Bildschirmupdate unbedingt sein, dann kann dies mittels der Funktion ''WindowUpdate()'' ausgeführt werden. Allerdings ist zu beachten, dass aus Performance-Sicht erzwungene Bildschirmupdates die schlechteste Lösung sind.

Tastatureingabe und Eingabefokus

Tastatureingaben werden immer an dasjenige Fenster weitergeleitet, das gegenwärtig den Eingabefokus besitzt. Ist für dieses Fenster keine KeyHook- Funktion angemeldet oder gibt diese den Rückgabewert ''FALSE'' zurück, wird das Standardverhalten der libCUI aktiv.

Das Standardverhalten läuft dabei nach folgendem Schema ab:

Pfeil- und Tab-Tasten:
Der Eingabefokus wird an das nächste (bzw. je nach Taste an das vorherige) Kindfenster weitergeleitet, das sichtbar und aktiv ist und zudem den Fokus akzeptiert (CWS_TABSTOP).

Wird das Ende der Fensterliste der Kindfenster erreicht (oder existieren keine Kindfenster im aktuellen Fenster), dann wird die Aufgabe der Fokusweitergabe an das Vaterfenster delegiert. Dies geschieht jedoch nur, solange es sich beim aktuellen Fenster nicht um ein Popup-Fenster handelt.

Im Gegensatz zu normalen Fenstern beginnen Popup-Fenster beim Erreichen des Listenendes die Fokusweitergabe wieder am anderen Ende ihrer Fensterliste. Dadurch bildet die Weiterleitung des Fokus auf der Ebene des Popup-Fensters einen geschlossenen Kreis.

ENTER- und ESC- Tasten:
Es wird dasjenige Kindfenster gesucht, das den Fensterstil CWS_DEFOK (bzw. CWS_DEF- CANCEL) besitzt. Wird ein solches Fenster gefunden und handelt es sich dabei um ein Button- Konrollelement, dann wird dieses betätigt.

Alphanumerische Tasten:
Es wird dasjenige Kindfenster gesucht, das das eingegebene Zeichen als Hot-Key hinterlegt hat. Falls ein solches Fenster gefunden wird, wird ihm der Eingabefokus zugewiesen und zugleich das fragliche Zeichen übergeben. Anderenfalls wird die Tastatureingabe verworfen.

Natürlich kann die Fokusweitergabe auch manuell im Programmcode beeinflusst werden. Es existieren dazu die folgenden Funktionen:

   void WindowSetFocus(CUIWINDOW* win);
   CUIWINDOW* WindowGetFocus(void);
   void WindowFocusNext(CUIWINDOW* win);
   void WindowFocusPrevious(CUIWINDOW* win);

Window-Timer

Soll ein sich zeitlich wiederholender Ablauf programmiert werden, dann kann dazu ein Window-Timer verwendet werden. Ein Window-Timer hat eine Auflösung von 100ms, besitzt aber keinen Anspruch auf Genauigkeit.

Um mit einem Timer zu Arbeiten, muss zunächst ein TimerHook für das entsprechende Fenster angemeldet werden. Dann wird der Timer mit der Funktion ''WindowSetTimer'' gestartet. Die Funktion ''WindowKillTimer'' deaktiviert den Timer wieder.

   void WindowSetTimer(CUIWINDOW* win, int id, int msec);
   void WindowKillTimer(CUIWINDOW* win, int id);

Farben

Wenn beim Funktionsaufruf ''WindowStart'' der Farbmodus aktiviert wurde und zudem die aktuelle Konsole Farben unterstützt, dann können während der Textausgabe in ein Fenster die Vorder- und die Hintergrundfarbe mit der Funktion ''SetColor'' gesetzt werden. Dazu existieren die folgenden Farbkonstanten (die den curses-Konstanten COLOR_XXXX entsprechen):

Konstante Wert Konstante Wert
BLACK 0 DARKGRAY 8
BLACK 1 LIGHTRED 9
GREEN 2 LIGHTGREEN 10
BROWN 3 YELLOW 11
BLUE 4 LIGHTBLUE 12
MAGENTA 5 LIGHTMAGENTA 13
CYAN 6 LIGHTCYAN 14
LIGHTGRAY 7 WHITE 15

Die Funktion ''SetColor'' kennt vier Parameter. Dies sind das curses- Fenster auf das die Farbänderung angewendet werden soll, die Vordergrundfarbe und die Hintergrundfarbe. Der letzte Parameter gibt an, ob die Darstellung im monochromen Modus invers sein soll.

   void SetColor(WINDOW* win, int fcolor, int bcolor, int reverse);

Allerdings kann es durchaus sinnvoll sein, die Farbkonstanten nicht direkt zu verwenden, sondern mit den Farben zu arbeiten, die dem Fenster hinterlegt sind. Dazu existiert innerhalb der CUIWINDOW- Struktur ein Datenelement ''Color'', das ebenfalls eine Struktur mit den folgenden Datenfeldern ist:

Datenfeld Typ Bedeutung
WndColor int normal window background color
WndSelColor int selected text background color
WndTxtColor int normal window text color
SelTxtColor int selected text color
InactTxtColor int inactive window text color
HilightColor int hilight window text color
TitleTxtColor int window caption text color
TitleBkgndColor int window caption background color
StatusTxtColor int window status bar text color
StatusBkgndColor int window status bar bkgnd color

 
Ein Code-Schnipsel aus einer Paint-Routine kann dann wie folgt aussehen:

   if (index == data->SelIndex)
   {
      SetColor(win->Win, win->Color.SelTxtColor, 
               win->Color.WndSelColor, TRUE);
      cursor = y;
   }
   else
   {
      if (win->IsEnabled)
      {
         SetColor(win->Win, win->Color.WndTxtColor, 
                  win->Color.WndColor, FALSE);
      }
      else
      {
         SetColor(win->Win, win->Color.InactTxtColor, 
                  win->Color.WndColor, FALSE);
      }
   }
   for (x = 0; x < rc.W; x++)
   {
      if ((x > 0) && (x <= len))
      {
         wprintw(win->Win, "%c", item->ItemText[x - 1]);
      }
      else if (x == 0)
      {
         mvwprintw(win->Win, y, 0, " ");
      }
      else
      {
         wprintw(win->Win, " ");
      }
   }

Beim Anlegen einer Fensterstruktur erbt das neue Fenster seine Farben von seinem Vaterfenster. Es kann jedoch über die Funktion ''WindowColScheme'' ein abweichendes Farbschema zugewiesen werden. Farbschemata haben Namen und werden über die Funktion ''WindowAddColScheme'' hinzugefügt bzw. geändert. Standardmäßig sind die Schemata ''WINDOW'', ''DESKTOP'', ''DIALOG'', ''MENU'', ''TERMINAL'' und ''HELP'' definiert.

Die Funktionen im überblick:

   void WindowAddColScheme(const char* name, CUIWINCOLOR* colrec);
   int  WindowHasColScheme(const char* name);
   void WindowColScheme(CUIWINDOW* win, const char* name);

Ein Beispiel:

   CUIWINDOW* mynewwin;
   CUIWINCOLOR* colrec;
   
   colrec.WndColor = BLUE;
   colrec.WndSelColor = LIGHTGRAY;
   colrec.WndTxtColor = LIGHTGRAY;
   colrec.SelTxtColor = BLACK;
   colrec.InactTxtColor = DARKGRAY;
   colrec.HilightColor = YELLOW;
   colrec.TitleTxtColor = BLACK;
   colrec.TitleBkgndColor = LIGHTGRAY;
   colrec.StatusTxtColor = BLACK;
   colrec.StatusBkgndColor = LIGHTGRAY;
   WindowAddColScheme("MY_SCHEME", &colrec);

   mynewwin = WindowNew(aparent, 0, 0, 10, 10, CWS_NONE);
   WindowColScheme(mynewwin, "MY_SCHEME");
   WindowCreate(mynewwin);
   ...

Bildlaufleisten

Ein Fenster kann eine vertikale und eine horizontale Bildlaufleiste (Scrollbalken) besitzen. Bildlaufleisten werden verwendet, um dem Anwender zu zeigen, an welcher Stelle innerhalb eines Bereiches (z.B. innerhalb einer Liste oder eines Textes) er sich befindet.

Ist ein Fenster mit der Eigenschaft CWS_BORDER versehen, dann finden die Bildlaufleisten auf dem Fensterrahmen Platz. Anderenfalls erfolgt die Darstellung der Scrollbalken am rechten bzw. unteren Fensterrand. Der Client-Bereich des Fensters wird dabei entsprechend verkleinert.

Mit den folgenden Funktionen werden Bildlaufleisten gesetzt und abgefragt:

   void WindowEnableVScroll(CUIWINDOW* win, int enable);
   void WindowEnableHScroll(CUIWINDOW* win, int enable);
   void WindowSetVScrollRange(CUIWINDOW* win, int range);
   void WindowSetHScrollRange(CUIWINDOW* win, int range);
   void WindowSetVScrollPos(CUIWINDOW* win, int pos);
   void WindowSetHScrollPos(CUIWINDOW* win, int pos);
   int  WindowGetVScrollRange(CUIWINDOW* win);
   int  WindowGetHScrollRange(CUIWINDOW* win);
   int  WindowGetVScrollPos(CUIWINDOW* win);
   int  WindowGetHScrollPos(CUIWINDOW* win);

Mittels ''WindowEnableVScroll'' bzw. ''WindowEnableHScroll'' wird die Sichtbarkeit gesteuert. Hat der Parameter ''enable'' den Wert ''TRUE'' dann wird die jeweilige Bildlaufleiste angezeigt, anderenfalls nicht.

Die Funktionen ''WindowSetVScrollRange'' bzw. ''WindowSetHScrollRange'' geben einer Bildlaufleiste den Scrollbereich vor, wobei der Wert des Parameters ''range'' der Maximalwert ist, den die Position der Bildlaufleiste annehmen kann. Wird z.B. als Bereich der Wert '1' übergeben, dann kann die Position die Werte '0' und '1' annehmen.

Mit ''WindowSetVScrollPos'' bzw. ''WindowSetHScrollPos'' wird die Position innerhalb des Scrollbereichs gesetzt.

Mit den entsprechenden ''Get'' Funktionen können die aktuellen Parameter der Bildlaufleiste abgefragt werden.

Ist die Mausbehandlung aktiv, dann können Bildlaufleisten auch mit der Maus bedient werden. Die Benachrichtgung an ein Fenster erfolgt dabei über den ''VScroll''- Hook bzw. Über den ''HScroll''- Hook des Fensters. Eine gängige Implementierung einer solchen Hook- Funktion sieht wie folgt aus:

   static void
   ListboxVScrollHook(void* w, int sbcode, int pos)
   {
      CUIWINDOW* win = (CUIWINDOW*) w;
      CUIRECT rc;
      int sbpos, range;

      WindowGetClientRect(win, &rc);
      sbpos = WindowGetVScrollPos(win);
      range = WindowGetVScrollRange(win);

      switch(sbcode)
      {
      case SB_LINEUP:
         if (sbpos > 0)
         {
            WindowSetVScrollPos(win, sbpos - 1);
            WindowInvalidate(win);
         }
         break;
      case SB_LINEDOWN:
         if (sbpos < range)
         {
            WindowSetVScrollPos(win, sbpos + 1);
            WindowInvalidate(win);
         }
         break;
      case SB_PAGEUP:
         if (sbpos > 0)
         {
            sbpos -= (rc.H - 1);
            sbpos = (sbpos < 0) ? 0 : sbpos;
            WindowSetVScrollPos(win, sbpos);
            WindowInvalidate(win);
         }
         break;
      case SB_PAGEDOWN:
         if (sbpos < range)
         {
            sbpos += (rc.H - 1);
            sbpos = (sbpos > range) ? range : sbpos;
            WindowSetVScrollPos(win, sbpos);
            WindowInvalidate(win);
         }
         break;
      case SB_THUMBTRACK:
         WindowInvalidate(win);
         break;
      }
   }

Mausbehandlung

Die Behandlung der Maus wird in der libCUI über die Funktion ''WindowStart'' aktiviert. Von da ab erhält ein Fenster, abhängig von den Möglichkeiten die das verwendete Terminal bietet, Informationen über die Mausbewegung und über Aktivitäten der Maustasten. Dazu dienen die beiden Hook- Funktionen ''MMoveHook'' und ''MButtonHook''.

Die Mausereignisse werden immer an dasjenige Fenster gesendet, über dessen Client- Bereich sich der Maus-Cursor befindet. Allerdings kann ein Fenster die Maus ''einfangen'' indem die Funktion ''WindowSetCapture'' aufgerufen wird. Es werden von da an alle Ereignisse an das Fenster mit dem Maus- Capture gesendet, bis die Funktion ''WindowReleaseCapture'' aufgerufen wird.

Wird eine Maustaste über einem Fenster betätigt, erhält dieses den Eingabefokus zugeteilt. Popup-Fenster werden zudem in den Vordergrund gestellt.

Die Behandlung von Mausereignissen im Randbereich (Nicht- Client Bereich) von Fenstern wird von der libCUI selbst verwaltet. Sollte das Standardverhalten nicht gewünscht sein, dann kann ein Fenster über die Hook-Funktionen ''MMoveHookNc'' und ''MButtonHookNc'' eine eigene Implementierung Anstelle der der libCUI einsetzen.

Holger Bruenjes 2016-12-12