4.5 Dialogs, Stock Items and Progress Bars

A dialog is an example of a composite widget. It consists of a window, an upper part which is a vertical box, and an action area which is a horizontal box. By default, both parts are separated by a horizontal separator.

The Dialog widget can be used for pop-up messages to the user, and other similar tasks. The basic functions needed are:

dialogNew :: IO Dialog

dialogRun :: DialogClass self => self -> IO ResponseID

You add buttons into the action area with:

dialogAddButton :: DialogClass self => self -> String -> ResponseId -> IO Button

Any widget can be added in a similar way with dialogAddActionWidget.

The String in dialogAddButton can be the text of the button, but since dialogs are mostly used for standard situations a StockItem will usually be more appropriate.

StockItems are resources which are known throughout Gtk2Hs, such as standard IconSets. You can define your own, but many useful ones are listed in the Graphics.UI.Gtk.General.StockItems module. They have an identifier StockId, which is a type synonym for String. From this identifier a widget (usually a button) with the appropriate standard text and icon is automatically selected.

If you use a StockId when adding a button to a dialog, you can also use a pre-defined ResponseId constructor with the buttons. (ResponseId is not a String.) Customized responses may be constructed with ResponseUser Int.

Whenever a dialog button is pressed, its response is passed to the calling application through dialogRun. According to the Gtk2Hs API documentation dialogRun blocks in a recursive main loop until the dialog either emits the response signal, or is destroyed. The default mode is modal, which means the user cannot access any other window while dialogRun is waiting for a response.

Progress bars are used to show the status of an ongoing operation.

progressBarNew :: IO ProgressBar

Though there is only one type, there are two distinct ways to use a progress bar. If it is known how much of the task has been completed, the fraction (between 0.0 and 1.0 inclusive) can be set with:

progressBarSetFraction :: ProgressBarClass self => self -> Double -> IO ()

This causes the progress bar to be filled in with the specified amount (between 0.0 and 1.0). To trace the progress this function should be called at regular times during the operation.

When it is not known how much of the operation has been completed, the bar can be moved back and forth with:

progressBarPulse :: ProgressBarClass self => self -> IO ()

This function must also be called repeatedly, to show that the activity is going on. There are several other functions to control the display of a progress bar, like orientation, additional text etc.; they are fairly trivial.

Application, however, is not trivial because progress bars are usually applied with timeouts or other such functions to give the illusion of multitasking. With concurrent Haskell you can also use threads and communication between threads.

In the following example we'll simulate an activity using timeoutAdd, which runs a function repeatedly at the interval specified, in milliseconds. The function is passed to timeoutAdd and must return a type of IO Bool. When true the timeout is run again, when false it stops. The priority of timeoutAdd is priorityDefault of type Priority.

timeoutAdd :: IO Bool -> Int -> IO HandlerId

In the example we define the function showPulse, which causes the progress bar to pulse and always returns IO True. The pulse step, the amount which the indicator moves through the bar, is set to 1.0 with progressBarSetPulseStep.

Progress bar pulsing

The example is somewhat atypical of the use of a dialog, since we keep it to show the progress after the user has pressed the apply button. To close the application the dialog must be destroyed by destroying the window. The close and cancel buttons don't work after apply has been selected. If selected, instead of Apply, the first time, the application will close. This is done by testing the response from dialogRun.

If the dialog widget is destroyed, mainQuit is called. As mentioned above, a Dialog consists of a window and two boxes. The boxes must be accessed through special functions, and the progress bar is packed into the upper part using dialogGetUpper. The buttons in a dialog are visible by default, but the widgets in the upper part are not. A Dialog is an instance of the WindowClass, and so we can set the title and/or default length and height if we want.

A trivial feature to watch out for: A widget can only be made visible if its parent is visible. So, to show the progress bar, we use widgetShowAll on the vertical box and not widgetShow on the progress bar.

import Graphics.UI.Gtk

main :: IO ()
main = do

  dia <- dialogNew
  set dia [windowTitle := "Time Flies"]
  dialogAddButton dia stockApply  ResponseApply
  dialogAddButton dia stockCancel ResponseCancel
  dialogAddButton dia stockClose  ResponseClose

  pr <- progressBarNew
  progressBarSetPulseStep pr 1.0

  upbox <- dialogGetUpper dia
  boxPackStart upbox pr PackGrow 10
  widgetShowAll upbox

  answer <- dialogRun dia
  if answer == ResponseApply 
     then do tmhandle <- timeoutAdd (showPulse pr) 500
             return ()
     else widgetDestroy dia

  onDestroy dia mainQuit

showPulse :: ProgressBar -> IO Bool
showPulse b = do progressBarPulse b
                 return True