Gtk2Hs Tutorial A Haskell wrapper around the Gtk+ GUI library suite. %%Date % latex will pick up .ps files, pdflatex .pdf files %!preproc(tex): ".diag_ext" "" %!preproc(xhtml): ".diag_ext" ".png" = Foreword = Gtk2Hs is a Haskell wrapper around the Gtk+ Graphical User Interface library. Gtk+ started its life as the Gimp Tool Kit, but has grown over time to a general-purpose, multi-platform GUI library, with wrappers for many programming languages. Gtk2Hs exposes all relevant functions of Gtk+ and its accompanying libraries in simple and convenient way, by making use of key features of Haskell such as its strong type system, the garbage collector and closures (partially applied functions). The benefits of building user interfaces in Haskell using Gtk2Hs are shorter development times, robust code and a simplified API that enables the programmer to use even the most advanced widgets without a steep learning curve. This manual is meant to be a tutorial as well as a reference. As such, each chapter covers a certain topic, ranging from elementary principles to advanced aspects. Hence, people who are new to Gtk2Hs may want to skip later sections of each chapter. + Introduction + This chapter introduces fundamental concepts of Gtk+ that are necessary to understand the structure of Gtk2Hs programs. For a more hands-on experience, a simple example is used to show the basics. ++ A First Gtk2Hs Program ++ Once Gtk2Hs is installed on the computer you are using, an application with a GUI interface can simply import the Gtk2Hs library. The basic structure of an application is shown in the following "Hello World" example: ``` import Graphics.UI.Gtk main = do initGUI window <- windowNew set window [ windowTitle := "Hello World" ] widgetShowAll window onDestroy window mainQuit mainGUI ``` Gtk2Hs programs can be compiled and run on most current operating systems such as Linux, Mac OS X, Windows, FreeBSD or Solaris. However, so far the library requires the Glasgow Haskell Compiler version 6 or higher. Assuming the program above is stored in a file called ``Hello.hs``, the command to create an executable is as follows: ``$ ghc --make Hello.hs -o hello`` Running the application ``./hello`` will bring up a window not unlike the on below: [HelloWorld.png] ++ Control Flow ++ The structure of most Gtk2Hs applications follow the following template: ``` import Graphics.UI.Gtk main = do initGUI ... ... mainGUI ``` The first line merely imports the library. Then, in ``main``, the library is initialized with ``initGUI``. What follows are application-specific function calls to set up the user interface. Finally a function called ``mainGUI`` is called. This function waits for any user input and calls the Haskell functions that the programmer set up to deal with the user's actions. Note that while ``mainGUI`` is active, the Haskell world stops working unless you use GHC's threaded run-time system. The latter is not without problems which is why ``initGUI`` will print a warning message about using the threaded RTS. Please see the remarks on [threads #control] for further information. ++ UI Elements and their Properties ++ The Hello World program fleshes out the above template with instructions to create a window, i.e. ``window <- windowNew``. The so-called constructor ``windowNew :: IO Window`` creates a new top level window. A window has certain property which are defined in the [Window module http://haskell.org/gtk2hs/current/Graphics/UI/Gtk/Windows/Window.html]. One of these properties is the name that appears in the title bar, documented as ``windowTitle :: WindowClas s self => Attr self String``. This property is accessible through the ``set`` and ``get`` functions. For instance, the line ``set window [ windowTitle := "Hello World" ]`` sets the ``windowTitle`` property of ``window`` to the given string. ++ Layout ++ The next line, ``widgetShowAll window`` places the window onto the screen. Note that there is no need to say how large the window is. Gtk+ will allocated enough space for the window (and user interface elements inside it) so that that everything fits into the window. The allocation of sizes happens when the when ``widgetShowAll`` is called. ++ Widgets, Events and Signals ++ The next line ``onDestroy window mainQuit`` specifies the functionality of the GUI. In Gtk+, the user interface is made up of so-called **widgets**. In the running example, the only widget is ``window``. Gtk+ uses this term for any visible user interface element. The user interacts with these widgets, thereby creating so-called **events** such as mouse movements, clicks, drags or just plain keyboard input. Widgets normally consume these events and emit **signals** signals to the application program. Events often have an effect on widgets, for instance, typing text will insert the text into a text buffer widget. Signals are the effects that an application is interested in, for instance, the fact that the user has finished filling in a text entry box, presumably because enter or tab was pressed. Normally, an application is interested in the signals emitted by the widgets and not the events that reflect how the user currently uses the mouse and keyboard. In the example, the **destroy** signal of the widget ``window`` is connected to the Haskell function ``mainQuit``. The latter is, in turn, a function which is provided by Gtk2Hs. It simply sets a flag in Gtk+ that signals to the main loop that it should exit. Thus, whenever the window is closed, the main loop entered with ``mainGUI`` exists and, since no other instructions follow ``mainGUI``, the program terminates. ++ Containers ++ User interfaces in Gtk+ are built by placing user interface elements into so-called containers which themselves are widgets with the sole purpose of containing other widgets. Our example uses a ``Window`` which, in fact, is merely a container which is able to hold one so-called **child** widget. The child of the window is a property called ``containerChild``. Thus, we can extend our example by creating a button and making this button the child of the window. We augment the "Hello World" example as follows: ``` main = do initGUI window <- windowNew button <- buttonNew set button [ buttonLabel := "Click me!"] onClicked button (putStrLn "Button clicked!") set window [ windowTitle := "Hello World", containerChild := button ] widgetShowAll window onDestroy window mainQuit mainGUI ``` This new example creates a ``Button`` called button and sets its label to "Click me!". The **clicked** signal of the button is then set to execute ``putStrLn "Button clicked!"`` so that the text is printed to the console every time the user clicks the button. The line that is interesting is the attribute ``containerChild`` that is set to ``button``. Since the ``Button`` now resides within the window, the call to ``widgetShowAll`` now operates on the window and the contained button. Thus, enough space is allocated for the window to contain the button with its "Click me!" text. Furthermore, both widgets are shown on screen. Note that it is possible to execute ``widgetShow window`` instead of ``widgetShowAll window``. In this case, only the window is shown without the button. More information on how sizes are allocated and how widgets can be selectively hidden is presented in the section on [containers #containers]. ++ Object Hierarchy ++ The Gtk+ library is written in an object-oriented way. In particular, the different user interface elements form an inheritance tree. At the root of all user interface elements is the ``Widget`` object. The ``Window`` object derives from ``Container`` and from ``Widget`` and, thus, every method that is defined for ``Widget`` can also be used on a ``Window``. For instance, the example uses the functions ``widgetShowAll`` and ``onDestroy`` which both take a ``Widget`` as argument. The type safety of Gtk2Hs is guaranteed by virtue of the Haskell class system. In particular, for every UI element, Gtk2Hs defines a class and makes that UI element and all its derivatives instances. For instance, ``Window`` has an instance for the class ``WindowClass``, ``ContainerClass`` and ``WidgetClass`` whereas ``Widget`` only has an instance for the ``WidgetClass``. All functions that manipulate a ``Window`` are defined such that they accept any argument that is an instance of the ``WindowClass`` without the need for casting. In the example, ``widgetShowAll`` is defined in the ``Widget`` object and can therefore be used on any ``Container`` or, as in this case, any ``Window``. Similarly, the attributes ``windowTitle`` and ``containerChild`` require at least a instance of class ``WindowClass`` and ``ContainerClass``, respectively. Thus, setting them on a ``window :: Window`` is type correct. On the contrary, the **clicked** signal is defined for the ``Button`` class, but not for ``Widgets``. The following code does therefore not type check: ``` window <- windowNew button <- buttonNew set button [ buttonLabel := "Click me!"] set window [ windowTitle := "Hello World", containerChild := button ] [badBut] <- containerGetChildren window onClicked badBut (putStrLn "Button clicked!") ``` Here, the function ``containerGetChildren`` will extract the only child from the window, namely the previously added button. However, Gtk2Hs does not know the types of the contained widgets and, hence, the the type of the extraction function is ``containerGetChildren :: ContainerClass self => self -> IO [Widget]``, that is, the function extracts the children from any container widget, and these children are returned as a list of ``Widget``s. Thus, connecting this **clicked** signal using ``onClicked badBut ...`` is type incorrect since ``onClicked :: ButtonClass b => b -> IO () -> IO (ConnectId b)``, that is, ``onClicked`` expects a widget that is at least a ``Button``, but ``Widget`` is not a ``Button``. What is required here is an explicit cast to make the code type correct: ``` [goodBut] <- containerGetChildren window onClicked (castToButton goodBut) (putStrLn "Button clicked!") ``` The function ``castToButton :: GObjectClass obj => obj -> Button`` tries to convert any ``GObject`` (a class that is even more general than ``Widget``) to a ``Button``. Obviously, this conversion can fail in which case an exception is thrown. + Main Loop, Timers and Threads +[control] TODO: - more on main loop - all details on signals - timers - idle handlers - concurrency - bounded threads + Containers: Widget in Widgets +[containers] TODO - packing concepts of boxes - how space is allocated - selectively hiding widgets in a widget hierarchy - using notebooks to switch between several containers - scrolled windows + Menus and Toolbars +[menu] TODO - creating menus and toolbars - probably implement a combinator library instead of explaining the messy Gtk+ solution + Custom Widgets +[custom] TODO - drawing area - size allocation, configuration, exposure, updating - cairo drawing - text drawing: pango - input focus - mouse and keyboard events + Text Editing +[editing] TODO: TextView widget + Tree and List Widgets +[treelist] The ``TreeView`` widget in Gtk+ displays any data that is organized in rows. Similar to the ``TextView`` widget, it follows the model-view-controller paradigm in that the storage of data is separated from the widget (the view). In contrast to the ``TextView`` widget, there are several views and several stores to choose from. We will start off with the simplest combination, namely a list store and a combo box. ++ The ListStore Model ++ To the programmer the ``ListStore`` looks like a polymorphic list that has to be modified in the IO monad. Keeping the list in the IO monad allows the view to extract values from it whenever the user interface requires it. A Haskell list can be converted to a ``ListStore`` by simply calling the``CellRenderer`` constructor: ``store <- listStoreNew ["red", "green", "magenta"]`` The result is ``store :: ListStore String``, that is, a store that contains Haskell Strings. This store can be changed with functions such as - ``listStoreSetValue``, - ``listStoreInsert``, - ``listStorePrepend``, - ``listStoreAppend``, - etc. For example the action ``listStoreSetValue 2 "blue"`` will replace the entry ``"magenta"`` with ``"blue"`` (without actually traversing the whole list). Given this store, we can now create a widget that uses its content: ``` combo <- comboBoxNewWithModel store ren <- cellRendererTextNew cellLayoutPackEnd combo ren cellLayoutSetAttributes combo ren store (\txt -> [cellText := Just txt]) ``` After creating a combo box with the model from above (i.e. the list store), we create a so-called ``CellRenderer`` that visualizes data from the model in the combo box and add it to the combo box (third line). A ``CellRenderer`` has several attributes which can be set with the store's data. In the example, we set the ``cellText`` attribute to the string in our store. The given function ``\txt -> [cellText := Just txt]`` provides the connection between a row ''txt'' in the model and one or more attributes of the ``CellRenderer``. For example, we can set the background color of the text in each row to a different value, like so: ``` cellLayoutSetAttributes combo ren store (\txt -> [cellText := Just txt, cellBackground := txt]) ``` Many other attributes are possible, depending on the kind of ``CellRenderer``. It might seem a bit clumsy to separate out objects that render cells (``CellRenderer``) from the view itself (ComboBox). However, it is actually possible to insert more than one ``CellRenderer`` into a ComboBox. For example, we might let the user select hats of different color, but also provide the name of the color. ``` [Right rHat, Right gHat, Right bHat] <- mapM pixbufNewFromFile ["redHat.png", "greenHat.png", "blueHat.png"] store <- listStoreNew [(rHat, "red"), (gHat, "green"), (bHat, "blue")] combo <- comboBoxNewWithModel store -- Create a cell with text. ren <- cellRendererTextNew cellLayoutPackEnd combo ren cellLayoutSetAttributes combo ren store (\(_, txt) -> [cellText := Just txt]) -- Create a cell with images. ren' <- cellRendererPixbufNew cellLayoutPackStart combo ren' cellLayoutSetAttributes combo ren' store (\(img, _) -> [cellPixbuf := img]) ``` The result is a combo box that shows the pictures of the hat and the color of each, in textual form, next to it: [images/colorHats.png] Hence, the ability to insert several ``CellRenderer`` makes it possible to create easier-to-use user interface elements. That said, the ComboBox widget has convenience functions like ``comboBoxNewText``, ``comboBoxAppendText`` in case a simple list of strings is sufficient as content. The naming of the function to associate ``CellRenderer`` with the ComboBox might seem odd. The reason for the use of functions starting with ``cellLayout`` is that a CellLayout is an interface of every view, in particular, ComboBox implements the functions defined by CellLayout while itself is derived from Widget. In UML parlance, we have the following structure: [images/ComboRenderer.diag_ext] Note that all renderers derive from the same base class, namely ``CellRenderer``, which defines many attributes itself. While any attribute can be set on a per-row basis through ``cellLayoutSetAttributes``, it is also possible to set an attribute on a ``CellRenderer`` itself. In the above example, we might want to separate the hats from the colors by a few pixels. To do this, we set the ``cellXPad`` attribute of the text ``CellRenderer``: ``` set ren [cellXPad := 5] ``` While setting the attribute on a ``CellRenderer`` directly will affect the whole column, it is possible to override this value for specific rows by supplying a more sophisticated mapping between the model and the renderers when connecting them using ``cellLayoutSetAttributes``. Suppose that no padding should be added whenever the size of the color text is greater than six characters. The attributes of the ``CellRendererText`` could be set as follows: ``` cellLayoutSetAttributes combo ren store (\(_, txt) -> if length txt<=6 then [cellText := Just txt] else [cellText := Just txt, cellXPad := 0]) ``` For some properties the ``CellRenderer`` provides two attributes, one to specify the value of the property and one to determine if it should be set or ignored when rendering the specific cell. Suppose we want to highlight rows that are particularly dangerous with red. We can set the background color of all rows to red (``cellBackground``) but only enable this background color if the entry in the list is deemed to be dangerous (``cellBackgroundSet``): ``` set ren [cellBackground := "red"] cellLayoutSetAttributes combo ren store (\(_, txt) -> (cellBackgroundSet := txt=="black"): if length txt<=6 then [cellText := Just txt] else [cellText := Just txt, cellXPad := 0]) ``` The advantage of this approach is that the background color of dangerous items can be changed without changing the model or the attribute mapping. Care must be taken to set both attributes of properties that are split into the value and an "enabled" flag. ++ Editing and Adding Entries ++ For a choosing among a small set of options, it is best to use a group of RadioButtons. A ComboBox is useful if the number of options is large or if the user should be able add new entries. The list store presented in the last section can be filled by the program, thereby providing a way to choose between a larger or changing number of options. However, Gtk provides a way to let the user directly enter values in addition to choose among a given set. This widget is an extension of the ComboBox called ComboBoxEntry. It allows the user to chose one of the given option or to enter a new text. In the latter case, the new entry can be added to the model as follows: ``` combo <- comboBoxEntryNewWithModel store -- set up renderers here entry <- binGetChild combo entry -- and here we are lacking the API functions ``` TODO: - tree model - combo boxes in within trees, entry completion - custom model