Create  Edit  FrontPage  Index  Search  Changes  History  RSS  Login

tut-gtk2-dialog-custom

Creating Your Own Dialogs

A dialog is a special type of Gtk::Window that is used to supplement the top-level window. Dialog widgets are split in half by horizontal separator. The top half is where you place the main part of the dialog's user interface. The bottom half, called the "action area" holds a collection of buttons. When clicked, each button will emit a unique response identifier that tells the programmer which button was pressed.

Gtk::Dialog

Dialog boxes are a convenient way to prompt the user for some input, like to display a message, ask a question, etc. Ruby/GTK treats a dialog as a window split vertically. The top section is a vertical box, and is where widgets such as a Gtk::Label or a Gtk::Entry should be packed. The bottom area is known as the action_area, and is generally used for packing buttons into the dialog which may perform actions such as cancel, ok, or apply. The two areas are separated by an optional Gtk::HSeparator.

Gtk::Dialog boxes are created with a call to Gtk::Dialog.new. To access the two primary areas of the dialogue you can use Gtk::Dialog#vbox and Gtk::Dialog#action_area. The action area holds all the buttons along the bottom of the dialog. Though you could manually add buttons to the Gtk::Dialog#action_area by calling Gtk::HButtonBox#add_child and Gtk::ButtonBox#Style (see: Button Box), it is recommended that you use the methods provided by Gtk::Dialog. You can think of the Gtk::Dialog as a vertical box, where both the action_area and the separator are packed at the end, so you should use "Gtk::Dialog#vbox .pack_start" or "Gtk::Dialog#vbox .pack_start_default" to add widgets to a Gtk::Dialog. However, currently (as of Ruby 1.8.6 and Ruby-GNOME2 rel.: 2-0.17.0-rc1) there seems to be a problem in this area, and I suggest you use "Gtk::Dialog#vbox .add" instead, since only a single widget can ve added to the Gtk::Dialog#vbox.

You add buttons to Gtk::Dialog using Gtk::Dialog.new, Gtk::Dialog#add_button, Gtk::Dialog#add_buttons, or Gtk::Dialog#add_action_widget. When clicking the button a signal called "response" with a response ID that you specified is emitted.

If you want to block waiting for a dialog to return before returning control flow to your code, you can call Gtk::Dialog#run. This function enters a recursive main loop and waits for the user to respond to the dialog, returning the response ID corresponding to the button the user clicked.

Though for a simple dialogue like the one in the following example, you can build your own dialogue in reality you'd probably use Gtk::MessageDialog to save yourself some work. But you'd need to create the dialog contents manually if you had more than a simple message in the dialog.

dialog-custom.png

Let's look at the program:

#!/usr/bin/env ruby
require 'gtk2'

# Create a new message dialogue telling the user that button was clicked.
def button_clicked (parent)
  dialog = Gtk::Dialog.new(
      "Information",
      parent,
      Gtk::Dialog::MODAL,
      [ Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK ]
  )
  dialog.has_separator = false
  label = Gtk::Label.new("The button was clicked!")
  image = Gtk::Image.new(Gtk::Stock::DIALOG_INFO, Gtk::IconSize::DIALOG)

  hbox = Gtk::HBox.new(false, 5)
  hbox.border_width = 10
  hbox.pack_start_defaults(image);
  hbox.pack_start_defaults(label);

  # Add the message in a label, and show everything we've added to the dialog.
  # dialog.vbox.pack_start_defaults(hbox) # Also works, however dialog.vbox
                                          # limits a single item (element).
  dialog.vbox.add(hbox)
  dialog.show_all
  dialog.run
  dialog.destroy
end

window = Gtk::Window.new
window.border_width = 10
window.set_size_request(200, -1)
window.title = "Dialogs"
window.signal_connect('delete_event') { false }
window.signal_connect('destroy') { Gtk.main_quit }

button = Gtk::Button.new("_Click Me")
button.signal_connect('clicked') { button_clicked(window) }

window.add(button)
window.show_all
Gtk.main

Creating the Dialog

The first thing you need to do when the dialogue triggering button is pressed in the main window is create the Gtk::Dialog widget. The first two parameters specify the title of the dialogue window and the reference to the parent window.

Gtk::Dialog.new(title,
                parent,
                flags,
                [ buton_face, response_id ],
                ...
)

The dialogue will be set as the transient window of the parent window, which allows the window manager to centre the dialogue over the main window and keep it on top if required. You can set this parameter to nil if you do not want the parent window to be known or recognized by the dialogue. Next, you can specify one or more dialogue flags which are defined at Gtk::Dialog#Flags. There are three available values:

  • MODAL: Force the dialogue to remain in focus on top of the parent window until closed. The user will be prevented from interacting with the parent. See also Gtk::Window#set_modal(true).
  • DESTROY_WITH_PARENT: Destroy the dialogue when the the parent parent is destroyed, but do not force the dialogue to be in focus. This will create a non-modal dialogue unless you call Gtk::Dialog#run, See also: Gtk::Window#set_destroy_with_parent.
  • NO_SEPARATOR: If set, no separator bar will be placed between the buttons in the action area and the dialogue contents.

Response Identifiers

When a dialogue is fully constructed, one method of showing the dialogue is by calling Gtk::Dialog#run {...}. This method will return an integer called response identifier when complete. It will also prevent the user from interacting with anything outside the dialogue until it is either destroyed or an action area button is clicked.

run {|response| ... }

Blocks in a recursive main loop until the dialogue either emits the response signal, or user closes the dialogue (i.e. it is destroyed) Regardless of what flags you set, the dialogue will always be modal when you call this method because it calls Gtk::Window#set_modal. If the dialogue is is manually destroyed by the user during the call to Gtk::Dialog#run, Gtk::Dialog returns Gtk::Dialog::RESPONSE_NONE. Otherwise, it returns the response ID from signal emission referring to the button that was clicked. Destroying the dialogue during Gtk::Dialog#run is a very bad idea, because your post-run code won't know whether the dialogue was destroyed or not. Before entering the recursive main loop, Gtk::Dialog#run calls Gtk::Widget#show on the dialogue for you. Note that you still need to show any children of the dialogue yourself. After Gtk::Dialog#run returns, you are responsible for hiding or destroying the dialogue if you wish to do so.

Typical usage of this method might be:

dialog.run do |response|
  case response
    when Gtk::Dialog::RESPONSE_ACCEPT
      do_application_specific_something()
    else
      do_nothing_since_dialog_was_cancelled()
  end
  dialog.destroy
end
  • {|response| ... } : A block or nothing.
    • response: response ID.
  • Returns : response ID
ResponseType

The following response types are returned from Ruby/GTK dialogs, and you can also use them yourself if you like. Ruby/GTK will never assign a meaning to positive response IDs; these are entirely user-defined. But for convenience, you can use the response IDs in the Gtk::Dialog#ResponseType enumeration (these all have values less than zero). If a dialog receives a delete event, the "response" signal will be emitted with a response ID of Gtk::Dialog::RESPONSE_NONE.


  • RESPONSE_NONE (-1): - The dialogue was destroyed by window manager or programatically with Gtk::Widget#destroy. This is also returned if the response_id is not set.
  • RESPONSE_REJECT (-2): - This identifier is not associated with buttons in built-in dialogues, but you are free to use it yourself.
  • RESPONSE_ACCEPT (-3): - This identifier is not associated with buttons in built-in dialogues, but you are free to use it yourself.
  • RESPONSE_DELETE_EVENT (-4): - Each dialogue is automatically connected to the delete_event signal. While Gtk::Dialog#run is running, this identifier will be returned,but the delete_event will be prevented from destroying the window as normally.
  • RESPONSE_OK (-5): - A Gtk::Stock::OK button was clicked in a built-in dialogue. You are free to use this button or any of the following in your own dialogues.
  • RESPONSE_CANCEL (-6): - A Gtk::Stock::CANCEL button was clicked in a built-in dialogue.
  • RESPONSE_CLOSE (-7): - A Gtk::Stock::CLOSE button was clicked in a built-in dialogue.
  • RESPONSE_YES (-8): - A Gtk::Stock::YES button was clicked in a built-in dialogue.
  • RESPONSE_NO (-9): - A Gtk::Stock::NO button was clicked in a built-in dialogue.
  • RESPONSE_APPLY (-10) - A Gtk::Stock::APPLY button was clicked in a built-in dialogue.
  • RESPONSE_HELP (-11) - A Gtk::Stock::HELP button was clicked in a built-in dialogue.

Modal or Nonmodal Message Dialogs

We have seen that when we create a new dialogue, the argument flags can be set to either MODAL or DESTROY_WITH_PARENT. However, this argument alone is not the deciding factor whether a dialogue widget will be modal or nonmodal. We have learnt that regardless of what you set the argument flags to, the dialogue will always be modal when you call Gtk::Dialog#run method, because it internally calls Gtk::Window#set_modal. So in order to create a nonmodal dialogue, we should do three things: first, we should remove the call to the Gtk::Dialog#run method from our button_clicked function, and second we need to connect to Gtk::Dialog's response signal, and lastly we should change the flags field to DESTROY_WITH_PARENT:

Following is our modified button_clicked function (actually it is a method too, only the object to which it belongs is the ultimate top-level Ruby object called Object and it only looks like a global function).

# Create a new message dialogue telling the user that button was clicked.
def button_clicked (parent)
  dialog = Gtk::Dialog.new(
      "Information",
      parent,
      Gtk::Dialog::DESTROY_WITH_PARENT,
      [ Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK ]
  )
  dialog.has_separator = false
  label = Gtk::Label.new("The button was clicked!")
  image = Gtk::Image.new(Gtk::Stock::DIALOG_INFO, Gtk::IconSize::DIALOG)

  hbox = Gtk::HBox.new(false, 5)
  hbox.border_width = 10
  hbox.pack_start_defaults(image);
  hbox.pack_start_defaults(label);

  dialog.vbox.add(hbox)
  dialog.show_all

  dialog.signal_connect('response') { Gtk.widget_destroy }
end

You should change our original example program to include this modified button_clicked method and run it. Then try clicking the the button in the main window multiple times. This will not only show you how to create multiple instances of the same dialogue but only demonstrate, that you still have access to the main window from a nonmodal dialogue.

The Gtk::Image Widget

In our example program we use another new widget, namely the Gtk::Image, we did not discussed yet. Images can be loaded in a variety of ways, but one advantage of Gtk::Image is that it will display Gtk::Stock::MISSING_IMAGE icon if the loading has failed. It is also derived from Gtk::Widget, so unlike other images such as Gdk::Pixbuf, it can be added as a child of a container. In our example we created the Gtk::Image from Gtk::Stock item:

image = Gtk::Image.new(Gtk::Stock::DIALOG_INFO, Gtk::IconSize::DIALOG)

When loading the image we also needed the size parameter. GTK+ will automatically look for a stock icon for the given size and resize the image if one with the specified size is not found. Available size arguments are specified by the Gtk::IconSize:

GtkIconSize

Sizes for stock Gtk::Image objects


As you can see, stock Gtk::Image objects are usually used for smaller images, such as those that appear in buttons, menus, and dialogues, since stock images are provided in a discrete number of standard sizes.

There exist a many different Gtk::Image constructors. Especially important for us is the Gtk::Image.new(value), which can take four differen types of arguments" nil, file-name, pixbuff and pixbuf animation. In our later examples we will often deal with images initialized from files an from pixbuf's. Initializing an image from a pixbuf creates a new Gtk::Image widget out of a previously initialized GdkPixbuf. Unlike the initialization from a file, you can use this method to easily figure out whether the image is successfully loaded, since you first have to create a Gdk::Pixbuf.

Gtk::Image.new(value = nil)
Creates a new Gtk::Image.
  • value
    • nil - Creates a new empty Gtk::Image.
    • filename(String) - Creates a new Gtk::Image displaying the file filename. If the file isn't found or can't be loaded, the resulting Gtk::Image will display a "broken image" icon. This function never returns nil, it always returns a valid Gtk::Image widget. If the file contains an animation, the image will contain an animation. If you need to detect failures to load the file, use Gdk::Pixbuf.new(filename) to load the file yourself, then create the Gtk::Image from the pixbuf. (Or for animations, use Gdk::PixbufAnimation.new(filenam. The storage type (Gtk::Image#storage_type) of the returned image is not defined, it will be whatever is appropriate for displaying the file.
    • Gdk::Pixbuf - Creates a new Gtk::Image from Gdk::Pixbuf. This function just creates an Gtk::Image. The Gtk::Image created will not react to state changes. Should you want that, you should use Gtk::IconSet.
    • Gdk::PixbufAnimation - Creates a Gtk::Image displaying the given animation. The Gtk::Image does not assume a reference to the animation; you still need to unref it if you own references. Gtk::Image will add its own reference rather than adopting yours.

Another Dialogue Example

So far you have created only a simple message dialogues spawned when a user clicked a dialogue triggering widget from scratch. It is time to create a more elaborate dialogue. But this time it will be without the parent window. There are also a few new minor tricks you will get acquainted with about how to obtain system information by utilizing Glib utilities. Of course this information is not changed, but is merely printed on the screen if user clicks the OK button. This example illustrates the fact, that regardless of the complexity of the dialogue, the basic principles of how responses are handled remain the same. Note that the dialogue is implemented as modal, but could as well be nonmodal, however this would not be of any use since the dialogue itself is the application's top-level window.

dialog-custom-02.png

#!/usr/bin/env ruby
require 'gtk2'

# Create a new message dialogue telling the user that 
# button was clicked.

dialog = Gtk::Dialog.new(
    "Edit User Information",
    nil,
    Gtk::Dialog::MODAL,
    [ Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK ],
    [ Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL ]
)
dialog.default_response = Gtk::Dialog::RESPONSE_OK
label1 = Gtk::Label.new("User Name")
label2 = Gtk::Label.new("Real Name")
label3 = Gtk::Label.new("Home Dir")
label4 = Gtk::Label.new("Host Name")

user = Gtk::Entry.new
real = Gtk::Entry.new
home = Gtk::Entry.new
host = Gtk::Entry.new
user.text = (GLib::user_name)
real.text = (GLib::real_name)
home.text = (GLib::home_dir)
host.text = (GLib::host_name)

table = Gtk::Table.new(4, 2, false)
table.attach_defaults(label1, 0, 1, 0, 1)
table.attach_defaults(label2, 0, 1, 1, 2)
table.attach_defaults(label3, 0, 1, 2, 3)
table.attach_defaults(label4, 0, 1, 3, 4) 
table.attach_defaults(user,   1, 2, 0, 1)
table.attach_defaults(real,   1, 2, 1, 2)
table.attach_defaults(home,   1, 2, 2, 3)
table.attach_defaults(host,   1, 2, 3, 4)
table.row_spacings = 5
table.column_spacings = 5
table.border_width = 10

dialog.vbox.add(table)
dialog.show_all

# Run the dialog and output the data if user okays it
dialog.run do |response|
  if response == Gtk::Dialog::RESPONSE_OK
    print "User Name: %s\n" % [user.text]
    print "Real Name: %s\n" % [real.text]
    print "Home Directory: %s\n" % [home.text]
    print "Host Name: %s\n" % [host.text]
  end
end
dialog.destroy

In our example we had only a single button to care about but to process multiple responses would be no different:

dialog.run do |response|
  case response
    when Gtk::Dialog::RESPONSE_REJECT
      do_something()
    when Gtk::Dialog::RESPONSE_ACCEPT
      do_something_else()
    else
      do_nothing_since_dialog_was_cancelled()
  end
  dialog.destroy
end
Last modified:2009/01/25 05:45:58
Keyword(s):
References:[tut-gtk2-dialog] [tut-gtk]