Personal tools

Gtk2Hs/Tutorials/Intro

From HaskellWiki

< Gtk2Hs | Tutorials(Difference between revisions)
Jump to: navigation, search
(Stepping through "Hello World": change <hask> to <code> to avoid new highlighter bug)
(Use ''widgetShowAll'' instead of ''widgetShow'' to avoid "Why is my window empty?" surprises if someone extends the example code with other widgets (just happened to me...))
 
(2 intermediate revisions by 2 users not shown)
Line 18: Line 18:
 
Here's the most basic Gtk2Hs prog:
 
Here's the most basic Gtk2Hs prog:
   
import Graphics.UI.Gtk
+
<haskell>
  +
import Graphics.UI.Gtk
 
 
main = do
+
main = do
 
initGUI
 
initGUI
 
window <- windowNew
 
window <- windowNew
widgetShow window
+
widgetShowAll window
 
mainGUI
 
mainGUI
  +
</haskell>
   
 
=== Compiling "Hello World" ===
 
=== Compiling "Hello World" ===
Line 34: Line 35:
 
=== Stepping through "Hello World" ===
 
=== Stepping through "Hello World" ===
   
<code>initGUI</code> must be called once before calling any other Gtk2Hs functions. Therefore it is usually called near the beginning of <code>main</code>.
+
<hask>initGUI</hask> must be called once before calling any other Gtk2Hs functions. Therefore it is usually called near the beginning of <hask>main</hask>.
   
<code>window <- windowNew</code> creates a top-level window and uses the identifier <code>window</code> to refer to it and pass it around later. Top-level windows are usually used as application windows, for example browser windows, terminal windows, and editor windows are top-level windows. Creation brings the window into internal existence but still not full existence: for one, the window is not shown, and most internal allocations required for showing are not done yet. Full existence will happen at the next line…
+
<hask>window <- windowNew</hask> creates a top-level window and uses the identifier <hask>window</hask> to refer to it and pass it around later. Top-level windows are usually used as application windows, for example browser windows, terminal windows, and editor windows are top-level windows. Creation brings the window into internal existence but still not full existence: for one, the window is not shown, and most internal allocations required for showing are not done yet. Full existence will happen at the next line…
   
 
(Meanwhile, between the above and below is a customary place to change settings of the window and add stuff to it, although you could also do them later.)
 
(Meanwhile, between the above and below is a customary place to change settings of the window and add stuff to it, although you could also do them later.)
   
<code>widgetShow window</code> completes the necessary internal allocations and shows the window. (<hask>widgetShow</hask> is very general and can show other things too — Widgets, which are covered in a later section.)
+
<hask>widgetShowAll window</hask> completes the necessary internal allocations and shows the window and all its children. (<hask>widgetShowAll</hask> is very general and can show other things too — Widgets, which are covered in a later section.)
   
<code>mainGUI</code> is a loop, and at this point it looks like an infinite loop to you — you find that this program seems to hang. The next section will present a normal way to exit the loop. Meanwhile, what is the significance of this loop?
+
<hask>mainGUI</hask> is a loop, and at this point it looks like an infinite loop to you — you find that this program seems to hang. The next section will present a normal way to exit the loop. Meanwhile, what is the significance of this loop?
   
 
The loop is required to process input, for example mouse movements and clicks, key presses and releases, windows getting moved or resized, losing focus, gaining focus, requests for redraws. The loop receives all this input and takes corresponding default actions; it can also call functions you write (in addition or instead). More on this in the next section.
 
The loop is required to process input, for example mouse movements and clicks, key presses and releases, windows getting moved or resized, losing focus, gaining focus, requests for redraws. The loop receives all this input and takes corresponding default actions; it can also call functions you write (in addition or instead). More on this in the next section.
Line 48: Line 49:
 
== Signals ==
 
== Signals ==
   
The concept of event driven GUI programming. What GObject signals are
+
Here we modify the basic program to allow for quitting: closing the window quits the program.
(without necessarily mentioning the term GObject yet)
+
  +
<haskell>
  +
import Graphics.UI.Gtk
  +
import Control.Monad.Trans(liftIO)
  +
  +
main = do
  +
initGUI
  +
window <- windowNew
  +
window `on` deleteEvent $ liftIO mainQuit >> return False
  +
-- i.e., on window deleteEvent (liftIO mainQuit >> return False)
  +
widgetShowAll window
  +
mainGUI
  +
</haskell>
  +
  +
Roughly: Register our handler (procedure) to be called when the user closes our window. Our handler calls <hask>mainQuit</hask>, which tells the loop <hask>mainGUI</hask> to quit at its next convenient point (which is ''not'' immediately). <hask>return False</hask> says we do not want to override other handlers associated with closing this window, in particular GTK+'s own code to actually get rid of the window! <hask>liftIO</hask> is needed because our handler runs inside some <hask>ReaderT r IO</hask> monad but <hask>mainQuit</hask> is just <hask>IO</hask>.
  +
  +
And now the detailed theory:
  +
  +
=== Event dispatch loop ===
  +
  +
Input from GUI is asynchronous and more importantly arbitrarily ordered. The console paradigm of “wait for name, then wait for phone number” does not apply; the user may very well type in a bit of name, then a bit of phone number, then a bit of name again. In the early days when concurrent programming was not popular, the forefathers decided to use this ''event dispatch loop'': wait for any input event at all (key press, mouse move), classify its type (is it key press?) and destination (is it for name?), use those to look up handlers to call, call them, loop back. Now a handler just needs to update some buffer and the screen. You, as programmer, may also register your own handlers to be called. Thus you work in the paradigm of ''event-driven programming'' and ''callbacks''.
  +
  +
(Nowadays this may be better organized as several loops in several threads so each thread-loop minds one logical aspect. But mutual exclusions concerning input device queries and screen updates justify the perpetuation of a mother loop.)
  +
  +
A twist is added to aid high-level programming. For example you would rather not carefully track the detailed event sequence of “mouse button is down over the OK button”, “now mouse button is up over the OK button”; you prefer one single “the OK button is clicked” to register your handler with. (Not to mention that there are also ways to “click” the OK button by a keyboard shortcut and numerous accessibility aids. You can't possibly track them all.) As another example, you also prefer one single “user wants to close” event, which we use above. This is facilitated by default handlers of GTK+ that do the tedious tracking and generate the derived events “the OK button is clicked”, “user wants to close”. So we usually register with these derived events and not raw input events.
  +
  +
The event dispatch loop of Gtk2Hs is started by <hask>mainGUI</hask>. It ends some time after <hask>mainQuit</hask> is called.
  +
  +
=== Signals, events, handlers ===
  +
  +
In GTK+ and Gtk2Hs terminology, events are called ''signals''. Gtk2Hs adds static type safety over GTK+ in that <hask>Signal</hask> types specify handler types you may register. Examples:
  +
  +
<haskell>
  +
focus :: WidgetClass self => Signal self (DirectionType -> IO Bool)
  +
</haskell>
  +
  +
It says every handler for <hask>focus</hask> must have type <hask>DirectionType -> IO Bool</hask>. Example registration of a well-typed handler:
  +
  +
<haskell>
  +
window `on` focus $ \dirtype -> putStrLn "focused!" >> return False
  +
</haskell>
  +
  +
Another example:
  +
  +
<haskell>
  +
showSignal :: WidgetClass self => Signal self (IO ())
  +
</haskell>
  +
  +
It says every handler for <hask>showSignal</hask> must have type <hask>IO ()</hask>. Example registration of a well-typed handler:
  +
  +
<haskell>
  +
window `on` showSignal $ putStrLn "shown!"
  +
</haskell>
  +
  +
There is a special but dominating group of signals in Gtk2Hs documentation called “events”. This refers to the signal type
  +
  +
<haskell>
  +
Signal self (EventM e Bool)
  +
</haskell>
  +
  +
After expanding a type synonym, it is really
  +
  +
<haskell>
  +
Signal self (ReaderT (Ptr e) IO Bool)
  +
</haskell>
  +
  +
I.e., valid handlers take on a rather special type. That is all. But this special case is pervasive in Gtk2Hs, so we want to see its examples.
  +
  +
As an example, the type of <hask>deleteEvent</hask> we used is
  +
  +
<haskell>
  +
deleteEvent :: WidgetClass self => Signal self (EventM EAny Bool)
  +
</haskell>
  +
  +
The <hask>EAny</hask> part is an abstract type to add type safety and possibly hold data for Gtk2Hs internal use. What matters to us is that firstly <hask>EventM</hask> is a <hask>ReaderT</hask> over <hask>IO</hask>, therefore often we have to use <hask>liftIO</hask>; and secondly we have to return a boolean: <hask>False</hask> to mean we don't override other handlers, <hask>True</hask> to mean we override other handlers. An example of registering a slightly longer handler:
  +
  +
<haskell>
  +
window `on` deleteEvent $ do
  +
liftIO (putStrLn "closing")
  +
liftIO mainQuit
  +
-- could merge the above two
  +
return False
  +
</haskell>
  +
  +
Here is another example, a signal for resize and/or move:
  +
  +
<haskell>
  +
configureEvent :: WidgetClass self => Signal self (EventM EConfigure Bool)
  +
</haskell>
  +
  +
This example is more interesting because the <hask>EConfigure</hask> part actually does something. In a handler for resizing, you probably want to know new size, which is given by:
  +
  +
<haskell>
  +
eventSize :: EventM EConfigure (Int, Int)
  +
</haskell>
  +
  +
This query has the perfect type! So an example of registering a handler to snoop but not interfere goes like:
  +
  +
<haskell>
  +
window `on` configureEvent $ do
  +
(width, height) <- eventSize
  +
liftIO (putStrLn (show width ++ " x " ++ show height))
  +
return False
  +
</haskell>
  +
  +
Type safey kicks in when you notice that you cannot do this in a handler for <hask>deleteEvent</hask>: <hask>deleteEvent</hask> has <hask>EAny</hask> but not <hask>EConfigure</hask>, the types don't match.
   
 
== Widget types ==
 
== Widget types ==

Latest revision as of 18:57, 9 February 2011

Note: this page is under construction. Feel free to help out!

Contents

[edit] 1 Introduction

As with any tutorial, certain assumptions are inherent in the code and instructions presented below. In this case the primary assumptions are:

  • You are reasonably familiar with Haskell and in particular, writing code using the IO Monad.
  • You have successfully installed Gtk2Hs.
  • You believe using Glade to design your user interface is a good idea.

Bon chance, mes amis!

[edit] 2 "Hello World" with Gtk2Hs

Start with the most basic working hello world app. Then we compile it...

Here's the most basic Gtk2Hs prog:

import Graphics.UI.Gtk
 
main = do
   initGUI
   window <- windowNew
   widgetShowAll window
   mainGUI

[edit] 2.1 Compiling "Hello World"

How to compile it with GHC on linux/mac/win32

> ghc --make Hello.hs -o hello

[edit] 2.2 Stepping through "Hello World"

initGUI
must be called once before calling any other Gtk2Hs functions. Therefore it is usually called near the beginning of
main
.
window <- windowNew
creates a top-level window and uses the identifier
window
to refer to it and pass it around later. Top-level windows are usually used as application windows, for example browser windows, terminal windows, and editor windows are top-level windows. Creation brings the window into internal existence but still not full existence: for one, the window is not shown, and most internal allocations required for showing are not done yet. Full existence will happen at the next line…

(Meanwhile, between the above and below is a customary place to change settings of the window and add stuff to it, although you could also do them later.)

widgetShowAll window
completes the necessary internal allocations and shows the window and all its children. (
widgetShowAll
is very general and can show other things too — Widgets, which are covered in a later section.)
mainGUI
is a loop, and at this point it looks like an infinite loop to you — you find that this program seems to hang. The next section will present a normal way to exit the loop. Meanwhile, what is the significance of this loop?

The loop is required to process input, for example mouse movements and clicks, key presses and releases, windows getting moved or resized, losing focus, gaining focus, requests for redraws. The loop receives all this input and takes corresponding default actions; it can also call functions you write (in addition or instead). More on this in the next section.

[edit] 3 Signals

Here we modify the basic program to allow for quitting: closing the window quits the program.

import Graphics.UI.Gtk
import Control.Monad.Trans(liftIO)
 
main = do
   initGUI
   window <- windowNew
   window `on` deleteEvent $ liftIO mainQuit >> return False
   -- i.e., on window deleteEvent (liftIO mainQuit >> return False)
   widgetShowAll window
   mainGUI
Roughly: Register our handler (procedure) to be called when the user closes our window. Our handler calls
mainQuit
, which tells the loop
mainGUI
to quit at its next convenient point (which is not immediately).
return False
says we do not want to override other handlers associated with closing this window, in particular GTK+'s own code to actually get rid of the window!
liftIO
is needed because our handler runs inside some
ReaderT r IO
monad but
mainQuit
is just
IO
.

And now the detailed theory:

[edit] 3.1 Event dispatch loop

Input from GUI is asynchronous and more importantly arbitrarily ordered. The console paradigm of “wait for name, then wait for phone number” does not apply; the user may very well type in a bit of name, then a bit of phone number, then a bit of name again. In the early days when concurrent programming was not popular, the forefathers decided to use this event dispatch loop: wait for any input event at all (key press, mouse move), classify its type (is it key press?) and destination (is it for name?), use those to look up handlers to call, call them, loop back. Now a handler just needs to update some buffer and the screen. You, as programmer, may also register your own handlers to be called. Thus you work in the paradigm of event-driven programming and callbacks.

(Nowadays this may be better organized as several loops in several threads so each thread-loop minds one logical aspect. But mutual exclusions concerning input device queries and screen updates justify the perpetuation of a mother loop.)

A twist is added to aid high-level programming. For example you would rather not carefully track the detailed event sequence of “mouse button is down over the OK button”, “now mouse button is up over the OK button”; you prefer one single “the OK button is clicked” to register your handler with. (Not to mention that there are also ways to “click” the OK button by a keyboard shortcut and numerous accessibility aids. You can't possibly track them all.) As another example, you also prefer one single “user wants to close” event, which we use above. This is facilitated by default handlers of GTK+ that do the tedious tracking and generate the derived events “the OK button is clicked”, “user wants to close”. So we usually register with these derived events and not raw input events.

The event dispatch loop of Gtk2Hs is started by
mainGUI
. It ends some time after
mainQuit
is called.

[edit] 3.2 Signals, events, handlers

In GTK+ and Gtk2Hs terminology, events are called signals. Gtk2Hs adds static type safety over GTK+ in that
Signal
types specify handler types you may register. Examples:
focus :: WidgetClass self => Signal self (DirectionType -> IO Bool)
It says every handler for
focus
must have type
DirectionType -> IO Bool
. Example registration of a well-typed handler:
window `on` focus $ \dirtype -> putStrLn "focused!" >> return False

Another example:

showSignal :: WidgetClass self => Signal self (IO ())
It says every handler for
showSignal
must have type
IO ()
. Example registration of a well-typed handler:
window `on` showSignal $ putStrLn "shown!"

There is a special but dominating group of signals in Gtk2Hs documentation called “events”. This refers to the signal type

Signal self (EventM e Bool)

After expanding a type synonym, it is really

Signal self (ReaderT (Ptr e) IO Bool)

I.e., valid handlers take on a rather special type. That is all. But this special case is pervasive in Gtk2Hs, so we want to see its examples.

As an example, the type of
deleteEvent
we used is
deleteEvent :: WidgetClass self => Signal self (EventM EAny Bool)
The
EAny
part is an abstract type to add type safety and possibly hold data for Gtk2Hs internal use. What matters to us is that firstly
EventM
is a
ReaderT
over
IO
, therefore often we have to use
liftIO
; and secondly we have to return a boolean:
False
to mean we don't override other handlers,
True
to mean we override other handlers. An example of registering a slightly longer handler:
 window `on` deleteEvent $ do
   liftIO (putStrLn "closing")
   liftIO mainQuit
   -- could merge the above two
   return False

Here is another example, a signal for resize and/or move:

configureEvent :: WidgetClass self => Signal self (EventM EConfigure Bool)
This example is more interesting because the
EConfigure
part actually does something. In a handler for resizing, you probably want to know new size, which is given by:
eventSize :: EventM EConfigure (Int, Int)

This query has the perfect type! So an example of registering a handler to snoop but not interfere goes like:

window `on` configureEvent $ do
   (width, height) <- eventSize
   liftIO (putStrLn (show width ++ " x " ++ show height))
   return False
Type safey kicks in when you notice that you cannot do this in a handler for
deleteEvent
:
deleteEvent
has
EAny
but not
EConfigure
, the types don't match.

[edit] 4 Widget types

The way that the class heiriaharcy is represented in Haskell using type classes. Breif mention of up casting & downcasting. We'll come back to downcasting in the glade section.

[edit] 5 Attributes

Show the "set object [ attr := value ]" style syntax. Reccomend it over the more verbose "objectSetAttr object value" style.

[edit] 6 Using Glade

We want to encourage people to use Glade to do their widget layout rather than doing it all in code. We can explain manual widget layout as a more advanced topic next.

Redo the hello world program using Glade.

[edit] 7 Widget layout

Doing widget layout in code rather than with Glade.

[edit] 8 Basic concepts

Brief explanation of basic and general concepts. With references to the API functions whenever possible. (probably in form of examples?).

 - signal
 - event
 - window
 - widget
 - container
 - box
 - layout
 - button
 - label

The idea is to get the user with as general and useful concepts as possible so it makes it easier for him to surf the API since the beginning and write simple code.