%% LyX 1.5.4 created this file. For more info, see http://www.lyx.org/. %% Do not edit unless you really know what you are doing. \documentclass[a4paper,twocolumn,ngerman]{article} \usepackage[T1]{fontenc} \usepackage[utf8]{inputenc} \setlength{\parskip}{\medskipamount} \setlength{\parindent}{0pt} \usepackage{url} \usepackage{graphicx} \makeatletter %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Textclass specific LaTeX commands. \newenvironment{lyxcode} {\begin{list}{}{ \setlength{\rightmargin}{\leftmargin} \setlength{\listparindent}{0pt}% needed for AMS classes \raggedright \setlength{\itemsep}{0pt} \setlength{\parsep}{0pt} \normalfont\ttfamily}% \item[]} {\end{list}} \usepackage{babel} \makeatother \begin{document} \title{Widgets zum Anfassen - GUI-Skripting mit Forth und GTK\texttt{+}} \ifx\shorttitle\undefined\else \shorttitle{Widgets zum Anfassen} \fi \author{Manfred Mahlow} % (manfred.mahlow@forth-ev.de) \maketitle PC, Laptop, Notebook, PDA - grafische Oberflächen überall. Da kommt früher oder später der Wunsch auf, schnell mal eine (einfache) grafische Benutzerschnittstelle (GUI) für ein kleines Tool oder für die Anzeige von Daten schreiben zu können, wie das z.B. mit der Skriptspache TCL/TK möglich ist. Aber, es sollte eben mit Forth möglich sein, mit möglichst kompakter \emph{forthiger} Syntax. \begin{multicols}{2} Das war der Ausgangspunkt als ich begann, mir Gedanken über GUI-Programmierung mit Forth zu machen. Ein erstes Ergebnis, eine Programmierschnittstelle (API) für das GTK\texttt{+}-Toolkit, habe ich auf der Jahrestagung 2008 der Forth-Gesellschaft vorgestellt. \section*{Warum GTK\texttt{+} ?} GTK\texttt{+} ist ein weit verbreitetes GUI-Toolkit, das für Linux und Windows verfügbar ist. Es ist die Basis für den GNOME- und den XFCE-Desktop, wird für Fenstermanager und viele Applikationen verwendet und ist auch auf PDAs zu finden. GTK\texttt{+} hat ein objektorientiertes Design, ist aber in C geschrieben und kann relativ leicht in andere Programmiersprachen eingebunden werden, so auch in Forth. \section*{Eine GTK\texttt{+}-API für Forth} Die Implementierung der GTK\texttt{+}-API orientiert sich an folgenden Leitlinien: \begin{enumerate} \item Für eine erste Implementierung sollte ein möglichst kleines 32 Bit Forth System für Linux verwendet werden, leicht zu verstehen und leicht zu ändern. \item Die Implementierung sollte objektorientiert erfolgen, mit möglichst kompakter \emph{forthiger} Syntax. \item Da das GTK\texttt{+}-Toolkit sehr umfassend ist, wird eine modulare Struktur der API angestrebt. \item GTK\texttt{+}-Basisfunktionen und GTK\texttt{+}-Klassen werden als Quelltextmodule implementiert, die bei Bedarf geladen werden können. \item Pro GTK\texttt{+}-Klasse wird nur eine kleine Teilmenge der Instanzvariablen und Methoden implementiert, die für einfache Anwendungen ausreicht. Weitere Instanzvariablen und Methoden können später hinzugefügt werden, wenn sie für eine Applikation tatsächlich benötigt werden. \item Klassen, Instanzvariablen und Methoden erhalten in Forth, soweit sinnvoll, die gleichen Namen, wie in C. \item Das Klassen-Präfix von Methodennamen wird in Forth wegen der objektorientierten Implementierung weggelassen. \item Die GTK\texttt{+} Methoden \_get und \_set werden in Forth auf fetch (@) und store (!) abgebildet. \end{enumerate} \section*{Ein Forth für die GTK\texttt{+}-API} Auf der Suche nach einem möglichst kleinen leicht zu verstehenden und leicht zu ändernden 32 Bit Forth System für Linux bin ich auf Reva Forth von Ron Aron gestoßen, das weitgehend meinen Vorstellungen entsprach. Aus Reva 6.0 habe ich dann cspForth abgeleitet, ein Testsystem für OOP mit Forth. Es unterstützt - wie Reva 6.0 - das bedarfsabhängige Laden von Quelltextmodulen, das Importieren von Funktionen aus Shared Libraries sowie das Speichern des jeweiligen Systemzustands als ausführbare Datei. Darüber hinaus hat cspForth zwei besondere Eigenschaften, durch die es sich von den meisten anderen Forth Systemen unterscheidet: \begin{enumerate} \item cspForth unterstützt zwei Suchreihenfolgen, die bekannte Suchreihenfolge für Vokabulare und Wortlisten und eine zusätzliche Suchreihenfolge für Klassen, Objekte und Schnittstellen. \item cspForth unterstützt Preludes. Ein Prelude ist ein Forth-Wort, das einem anderen Forth-Wort so zugeordnet ist, dass es im Ausführungsmodus oder im Compilermodus ausgeführt wird, bevor das Wort, dem es zugeordnet ist, selbst ausgeführt oder compiliert wird. \end{enumerate} Diese beiden Eigenschaften sind die Basis für eine als Quelltextmodul ladbare OOP-Erweiterung, die \emph{Context Switching Preludes} verwendet, um Objekte mit den Instanzvariablen und Methoden ihrer Klasse zu verbinden: \begin{itemize} \item Einem Objekt wird seine Klasse als Prelude zugewiesen. Wenn diese ausgeführt wird, erzeugt und aktiviert sie eine klassenspezifische Suchreihenfolge, die den Zugriff auf die Instanzvariablen und Methoden freigibt. \item Die Instanzvariablen einer Klasse sind selbst wieder Objekte einer Klasse, mit dem gerade beschriebenen Verhalten. \item Die Methoden einer Klasse haben ein anderes Verhalten. Einer Methode wird ein Prelude zugewiesen, das die klassenspezifische Suchreihenfolge, in der die Methode gefunden wird, wieder deaktiviert (oder ändert). \item Standard-Suchreihenfolge ist die Suchreihenfolge für Vokabulare und Wortlisten. \end{itemize} \section*{Stand der Implementierung} Die meisten einfachen Widget-Klassen des GTK\texttt{+}-Toolkits sind bereits implementiert und zu fast jeder Klasse gibt es mindestens ein Anwendungsbeispiel (siehe Abbildung \ref{fig:Beispiele-einfacher-GtkWidgets}). Weitere Widget-Klassen werden nach und nach implementiert. Ein Tutorium und Hilfetexte für die bereits implementierten Klassen sollen erstmal Vorrang haben. \section*{Von C nach Forth - Ein Beispiel} Für das \emph{Hello World Beispiel} aus dem GTK\texttt{+}-Tutorium ist im Listing~1 der Forth-Code dem C-Code gegenüber gestellt. Der Forth-Code ist mit cspForth ausführbar und erzeugt das GUI der Abbildung~\ref{fig:HelloWorld}. Der C-Code ist als Kommentar enthalten. % \begin{figure*}[tbh] \begin{center} \includegraphics{2008-02/Abbildung1sw} \caption{\label{fig:HelloWorld} Der Hello-World--Dialog} \end{center} \end{figure*} Der C-Code beginnt in Zeile 3 mit einer include-Anweisung, welche die GTK+-Bibliotheken einbindet. Auch in Forth werden die GTK\texttt{+}-Bibliotheken verwendet. Ihre Funktionen werden durch Laden der benötigten GTK\texttt{+}-Klassen importiert (Zeilen 5,6), die als Quelltextmodule implementiert sind. Für das Beispiel werden die Klassen \emph{GtkWindow}, für das Hauptfenster der Anwendung, und \emph{GtkButton}, für den Knopf, benötigt. Zeile 7 legt fest, dass nachfolgende Definitionen zum Vokabular \emph{oop} hinzugefügt werden. Das Vokabular \emph{gtk api} - es enthält die Wörter der GTK\texttt{+}-Schnittstelle - wird in die Suchreihenfolge aufgenommen und es wird die Zahlenbasis festgelegt. GUI-Programme sind durch Ereignisse gesteuerte Programme. Wenn ein (bekanntes) Ereignis eintritt, wird ein bestimmter Programmcode ausgeführt. In GTK\texttt{+} ist dieser Mechanismus durch Signale und Signal-Handler (ausführbarer Code) implementiert, die an GtkWidgets gebunden werden. Trifft ein Signal ein, das mit einem Signal-Handler verknüpft wurde, wird dieser von GTK\texttt{+} mit definierten Parametern aufgerufen. Im betrachteten Beispiel soll auf die Ereignisse \emph{Anwendungsfenster schließen} und \emph{Hello World Knopf gedrückt} reagiert werden. Dazu müssen zwei Signal-Handler definiert werden. In C werden sie als Funktionen des Typs static void definiert, in Forth als normale Forth Wörter, die dann einem Callback-Handler zugewiesen werden, der sie im Hintergrund (mit eigenem Stack) ausführt. In Zeile 16 wird der Signal-Handler für den Knopf definiert und dem Callback-Handler \emph{cb.hello} zugewiesen, der ihn mit zwei Parametern und einer Stacktiefe von 20 im Hintergrund ausführen wird. Ganz analog wird in Zeile 25 ein Callback-Handler \emph{cb.destroy} definiert, der beim Schließen des Anwendungsfensters ausgeführt werden soll. Er ruft dann die Funktion \emph{gtk~quit} auf (gtk\_main\_quit in C). In Zeile 29 beginnt die Hauptfunktion des C-Programms. Bevor das GTK\texttt{+}-Toolkit verwendet werden kann, muss es initialisiert werden. In C erledigt das die Funktion gtk\_init (Zeile 31). In Forth erfolgt die Initialisierung im Hintergrund, wenn die GTK\texttt{+}-API (\emph{gtk api}) geladen wird. In C wird auf GtkWidgets über Zeiger zugegriffen (Zeile 32,33). In Forth treten an die Stelle der Zeiger Objekte der jeweiligen Widget-Klasse. Für das Hauptfenster des Beispiels wird ein Objekt der Klasse GtkWindow erzeugt (Zeile 35) und für den Knopf ein Objekt der Klasse GtkButton (Zeile 36). In Analogie zur Struktur des C-Codes fassen wir auch den Forth-Code zu einer Funktion (zu einem Wort) zusammen (Zeile 39). In Zeile 47 wird das in Zeile 35 erzeugte Objekt \emph{window} initialisiert. Die \emph{init}-Methode erzeugt ein \emph{GtkWindow} vom Typ GTK\_WINDOW\_TOPLEVEL als Hauptfenster des Programms und weist dessen Adresse - in Forth \emph{widget identifier (wid)} genannt - dem Objekt zu. In Zeile 48 werden das \emph{destroy}-Signal und der Callback-Handler \emph{cb.destroy} an das Hauptfenster \emph{window} gebunden. Damit wird festgelegt, das der Callback-Handler \emph{cb.destroy} (Zeile 25) ausgeführt wird, wenn das Fenster ein destroy-Signal erhält. GTK\texttt{+} legt dann den \emph{widget identifier (wid)} des Fensters und einen Datenzeiger (hier 0) auf den Stack des Callback-Handlers. Beide werden hier vom Signal-Handler aber nicht verwendet. Die \emph{signal~connect}-Methode übergibt einen Identifier auf dem Datenstack, der hier nicht gebraucht wird. Zeile 54 ist ein Beispiel für den Zugriff auf eine Instanzvariable eines GtkWidgets. Die Breite des inneren Rands des Hauptfensters wird auf 10 Pixel gesetzt. Der entsprechende C-Code in Zeile 52 mag im Vergleich etwas verwirrend sein, da auf das Hauptfenster vom Typ \emph{GtkWindow} mit einer Methode der Klasse \emph{GtkContainer} zugegriffen wird. Das liegt daran, dass die Instanzvariable \emph{border-width} nicht in der Klasse GtkWindow sondern in der Klasse GtkContainer definiert ist. Logisch erbt die Klasse GtkWindow von der Klasse GtkContainer. Da sich diese Vererbung aber in C nicht direkt abbilden lässt, muss eine Typumwandlung erfolgen und die Methodennamen müssen mit einem Klassen-Präfix versehen werden. In C muss ein Programmierer also immer die GTK\texttt{+}-Klassenhierarchie beachten und die notwendigen Typumwandlungen und Methodenzuweisungen selbst vornehmen. In Forth ist das einfacher. Die GTK\texttt{+}-Klassenhierarchie wurde hier bereits bei der objektorientierten Implementierung der API berücksichtigt. So erbt z.B. die Klasse GtkWindow auch alle Instanzvariablen und Methoden der Klasse GtkContainer. In Zeile 63 wird das in Zeile 36 erzeugte Objekt \emph{button} initialisiert. Die \emph{init}-Methode erzeugt einen \emph{GtkButton} mit der Beschriftung \emph{Hallo World}, weist dessen Adresse - in Forth \emph{widget identifier (wid)} genannt - dem Objekt zu und übergibt sie außerdem auf dem Stack. Die Phrase \emph{window add} findet die Adresse auf dem Stack und packt den adressierten GtkButton in das Hauptfenster. In der Zeile 64 werden das \emph{clicked}-Signal und der Callback-Handler \emph{cb.hello} an den GtkButton gebunden. So wird festgelegt, das der Callback-Handler \emph{cb.hello} (Zeile 16) ausgeführt wird, wenn der GtkButton mit der Maus oder mit der Eingabetaste aktviert wird. GTK\texttt{+} übergibt dann den \emph{widget identifier (wid)} des GtkButton und einen Datenzeiger (hier 0) an den Callback-Handler. Beide werden hier vom Signal-Handler aber nicht verwendet. Alle GtkWidgets sind nun erzeugt aber noch nicht zu sehen. Sie werden durch den Code in Zeile 70 sichtbar gemacht. Die Methode \emph{show all} ist in der Klasse \emph{GtkWidget} definiert. Sie macht das angegebene Widget und alle darin enthaltenen Widgets sichtbar. In Forth erbt die Klasse GtkWindow diese Methode von der Klasse GtkWidget. In C muss wieder eine Typumwandlung erfolgen und dem Methodennamen muss das Klassen-Präfix vorangestellt werden(Zeile 68). Mit den Zeilen 74 bis 76 wird der C-Code abgeschlossen. gtk\_main ist die GTK\texttt{+}-Hauptschleife, die auf Ereignisse wartet und dann den zugeordneten Code ausführt. Sie wird erst dann verlassen, wenn die Funktion gtk\_main\_quit ausgeführt wird. Das geschieht durch den in Zeile 20 definierten Signal-Handler destroy, der ausgeführt wird, wenn das Hauptfenster geschlossen wird. Das C-Programm gibt dann die Kontrolle an die aufrufende Ebene zurück. Der entsprechend Forth-Code folgt in den Zeilen 78 u. 79, allerdings mit abweichender Funktion: Wenn der cspForth-Prozess, der den Forth-Code ausführt, in einem Terminal gestartet wurde, muss keine GTK\texttt{+}-Hauptschleife aufgerufen werden, weil die Ereignisverarbeitung dann im Hintergrund erfolgt, während auf die Eingabe einer Kommandozeile gewartet wird. Wird der cspForth-Prozess nicht in einem Terminal gestartet, z.B. weil der Forth-Code per \emph{Drag and Drop} direkt an cspForth übergeben wird, erfolgt keine Ereignisverarbeitung im Hintergrund. Dann muss auch in Forth explizit eine GTK\texttt{+}-Hauptschleife gestartet werden. Der Code in Zeile 78 stellt das sicher. In Zeile 81 wird nun das gerade definierte Forth-Wort main ausgeführt. Es erzeugt und initialisiert die Widgets und stellt, wie gerade beschrieben, die Ereignisverarbeitung sicher. Die nachfolgende Phrase \emph{oop ??} ist nur relevant, wenn der cspForth-Prozess in einem Terminal gestartet wurde. Sie zeigt dann das Vokabular \emph{oop} und den aktuellen Systemzustand im Terminal an (siehe Abbildung \ref{fig:Listing-1 geladen}) und wir können interaktiv auf die GtkWidgets zugreifen - Widgets zum Anfassen. % \begin{figure*}[tbh] \begin{minipage}[b]{0.5\textwidth} \begin{center} \includegraphics[scale=0.45]{2008-02/Abbildung2sw} \caption{\label{fig:Listing-1 geladen}Status nach dem Laden von Listing 1} \end{center} \end{minipage} %\end{figure*} % %\begin{figure*}[tbh] \begin{minipage}[b]{0.5\textwidth} \begin{center} \includegraphics[scale=0.45]{2008-02/Abbildung3sw} \caption{\label{fig:Klassenhierarchie-des-Hauptfensters}Klassenhierarchie des Hauptfensters} \end{center} \end{minipage} \end{figure*} In Forth ist ein GtkWidget ein Objekt. Im interaktiven Modus aktiviert ein Objekt seine klassenspezifische Suchreihenfolge (seine Klasenhierarchie) und übergibt seinen Identifier (oid) auf dem Datenstack. Mit der Methode ??? kann man sich beide anzeigen lassen (Abbildung \ref{fig:Klassenhierarchie-des-Hauptfensters}). Man kann also sehen, über welche Instanzvariablen und Methoden auf ein Objekt zugegriffen werden kann und man kann das auch tun! \emph{Hinweis: Mit dem Wort .. kann man einen Klassenkontext wieder verlassen.} Also fröhliches Experimentieren, aber vorher besser die cspForth README-Datei lesen. Es gibt auch eine kontextsensitive Hilfefunktion. Mit h kann man sich den Glossary-Eintrag für das Worte anzeigen lassen, vorausgesetzt, ein Glossary-Eintrag wurde bereits geschrieben, was noch nicht für alle Wörter der GTK\texttt{+}-API der Fall ist. Zur Jahrestagung 2008 der Forth-Gesellschaft hatte ich einen Entwicklungsschnappschuss zusammengestellt, mit dem man in die GTK\texttt{+}-Programmierung mit Forth einsteigen kann. Das cspForth System mit der GTK\texttt{+}-API kann vom Wiki der Forth-Gesellschaft heruntergeladen werden. Kommentare, Anregungen, Kritik aber auch Mitarbeit sind sehr willkommen. Das Beispiel mag den Eindruck vermittelt haben, dass man für die GUI-Programmierung mit Forth auch mit C vertraut sein muss. Das ist aber nicht generell der Fall. Soweit alle benötigten GTK\texttt{+}-Klassen in Forth bereits implementiert sind, muss man sich mit C nicht befassen. Nur wenn man die API erweitern will oder C-Beispiele übernehmen möchte, muss man sich mit dem Zusammenhang von C und Forth befassen. Entfernt man den C-Code aus Listing 1 bleibt das sehr überschaubare Listing 2 über. \section*{Wie soll es weitergehen?} Im Wiki der Forthgesellschaft werde ich ein Projekt für die GTK\texttt{+}-Programmierung mit Forth einrichten. Dort werde ich ein Tutorium, weitere Beispiele und dann und wann einen neuen Entwicklungsschnappschuss veröffentlichen. Weiter wäre es interessant, die GTK\texttt{+}-API auch für andere Forth Systeme zu implementieren, z.B. für ein in C implementiertes Forth, das nicht nur auf PC-Systemen läuft (der cspForth-Kern ist in Assembler geschrieben und damit nicht so einfach auf andere Prozessoren zu portieren). Ich denke dabei an PDAs, wie z.B. das Nokia N810. Wer hat daran Interesse? \begin{thebibliography}{1} \bibitem{key-16}GTK\texttt{+} Dokumentation. \url{http://www.gtk.org} \bibitem{key-17}Matthias Warkus: GNOME 2.0 - Das Entwickler-Handbuch. Galileo Press GmbH, 2003 \end{thebibliography} \end{multicols} % \onecolumn % \begin{figure*}[tbh] \begin{center} \includegraphics[scale=0.45]{2008-02/Beispiele-sw} \caption{\label{fig:Beispiele-einfacher-GtkWidgets}Beispiele implementierter GtkWidgets} \end{center} \end{figure*} % \appendix \subsection*{Listing 1:} \begin{lyxcode} ~1~~~\textbackslash{}~Listing~1:~GTK+-Beispiel~\char`\"{}Hello~World\char`\"{}~in~C~und~Forth ~2 ~3~~~\textbackslash{}~\#include~ ~4 ~5~~~needs~GtkWindow ~6~~~needs~GtkButton ~7~~~oop~definitions~gtk~api~decimal ~8 ~9 10 11~~~\textbackslash{}~static~void~hello(~GtkWidget~{*}widget,~gpointer~data~) 12~~~\textbackslash{}~\{ 13~~~\textbackslash{}~g\_print~(\char`\"{}Hello~World\textbackslash{}n\char`\"{}); 14~~~\textbackslash{}~\} 15 16~~~::~(~wid~data~-{}-~)~.\char`\"{}~Hello~World\char`\"{}~cr~;~~2~20~cb~cb.hello 17 18 19 20~~~\textbackslash{}~static~void~destroy(~GtkWidget~{*}widget,~gpointer~data~) 21~~~\textbackslash{}~\{ 22~~~\textbackslash{}~gtk\_main\_quit~(); 23~~~\textbackslash{}~\} 24 25~~~::~(~wid~data~-{}-~)~gtk~quit~;~~2~20~cb~cb.destroy 26 27 28 29~~~\textbackslash{}~int~main(~int~argc,~char~{*}argv{[}]~) 30~~~\textbackslash{}~\{ 31~~~\textbackslash{}~gtk\_init~(\&argc,~\&argv); 32~~~\textbackslash{}~GtkWindow~{*}window; 33~~~\textbackslash{}~GtkButton~{*}button; 34 35~~~GtkWindow~new~window 36~~~GtkButton~new~button 38 39~~~:~main~(~-{}-~) 40~ 41 42 43~~~\textbackslash{}~window~=~gtk\_window\_new~(GTK\_WINDOW\_TOPLEVEL); 44~~~\textbackslash{}~g\_signal\_connect~(G\_OBJECT~(window),~\char`\"{}destroy\char`\"{}, 45~~~\textbackslash{}~G\_CALLBACK~(destroy),~NULL); 46 47~~~~~GTK\_WINDOW\_TOPLEVEL~window~init 48~~~~~\char`\"{}~destroy\char`\"{}~cb.destroy~0~window~signal~connect~drop 49 50 51~ 52~~~\textbackslash{}~gtk\_container\_set\_border\_width~(GTK\_CONTAINER~(window),~10); 53 54~~~~~10~window~border-width~! 55 56 57 58~~~\textbackslash{}~button~=~gtk\_button\_new\_with\_label~(\char`\"{}Hello~World\char`\"{}); 59~~~\textbackslash{}~gtk\_container\_add~(GTK\_CONTAINER~(window),~button); 60~~~\textbackslash{}~g\_signal\_connect~(G\_OBJECT~(button),~\char`\"{}clicked\char`\"{}, 61~~~\textbackslash{}~G\_CALLBACK~(hello),~NULL); 62 63~~~~~\char`\"{}~Hello~World~\char`\"{}~button~init~window~add 64~~~~~\char`\"{}~clicked\char`\"{}~cb.hello~0~button~signal~connect~drop 65 66 67 68~~~\textbackslash{}~gtk\_widget\_show\_all~(window); 69 70~~~~~window~show~all 71 72 73 74~~~\textbackslash{}~gtk\_main~(); 75~~~\textbackslash{}~return~0; 76~~~\textbackslash{}~\} 77 78~~~~~term?~0if~gtk~main~bye~then 79~~~; 80 81~~~main~~oop~?? \end{lyxcode} \subsection*{Listing 2:} \begin{lyxcode} ~1~~~\textbackslash{}~Listing~2:~~Gtk+-Beispiel~\char`\"{}Hello~World\char`\"{}~in~Forth ~2 ~3~~~needs~GtkWindow ~4~~~needs~GtkButton ~5~~~ ~6~~~oop~definitions~~gtk~api~~decimal ~7 ~8~~~::~(~wid~data~-{}-~)~.\char`\"{}~Hello~World\char`\"{}~cr~;~~2~20~cb~cb.hello ~9 10~~~::~(~wid~data~-{}-~)~gtk~quit~;~~2~20~cb~cb.destroy 11 12~~~GtkWindow~new~window 13~~~GtkButton~new~button 14 15~~~:~main~(~-{}-~) 16~~~~~~~GTK\_WINDOW\_TOPLEVEL~window~init 17~~~~~~~\char`\"{}~destroy\char`\"{}~cb.destroy~0~window~signal~connect~drop 18~~~~~~~10~window~border-width~! 19~~~~~~~\char`\"{}~~Hello~World~\char`\"{}~button~init~~window~add 20~~~~~~~\char`\"{}~clicked\char`\"{}~cb.hello~0~button~signal~connect~drop 21~~~~~~~window~show~all 22~~~~~~~term?~0if~~gtk~main~bye~~then 23~~~; 24 25~~~main~~oop~??~ \end{lyxcode} \end{document}