GnomeAppBar: A Trivial Composite Widget

This section quickly describes the GnomeAppBar widget; GnomeAppBar demonstrates how to bundle a pre-packed container and some special functionality into a single new object. the section called GnomeAppBar in the chapter called The Main Window: GnomeApp describes GnomeAppBar from a user's point of view.

A composite widget derives from some kind of container, then adds child widgets and sets up callbacks to implement some sort of functionality. GnomeAppBar derives from GtkHBox; the box is packed with a progress bar and/or a status line. GnomeAppBar has members in its instance struct to store a stack of status messages, and it adds some signals to the class struct for use with its "interactive" mode.

As an aside, GnomeAppBar does not follow the GTK+/Gnome naming conventions; because Bar is capitalized, the functions and macros should have an underscore, i.e. app_bar rather than appbar. Don't copy this aspect of the widget.

Here's the implementation of gnome_appbar_new():


GtkWidget* 
gnome_appbar_new (gboolean has_progress,
                  gboolean has_status,
                  GnomePreferencesType interactivity)
{
  GnomeAppBar * ab = gtk_type_new (gnome_appbar_get_type ());

  gnome_appbar_construct(ab, has_progress, has_status, interactivity);

  return GTK_WIDGET(ab);
}

void
gnome_appbar_construct(GnomeAppBar * ab,
                       gboolean has_progress,
                       gboolean has_status,
                       GnomePreferencesType interactivity)
{
  GtkBox *box;


  g_return_if_fail( ((has_status == FALSE) && 
                     (interactivity == GNOME_PREFERENCES_NEVER)) ||
                    (has_status == TRUE)); 

  box = GTK_BOX (ab);

  box->spacing = GNOME_PAD_SMALL;
  box->homogeneous = FALSE;

  if (has_progress)
    ab->progress = gtk_progress_bar_new();
  else
    ab->progress = NULL;

  /*
   * If the progress meter goes on the right then we place it after we
   * create the status line.
   */
  if (has_progress && !gnome_preferences_get_statusbar_meter_on_right ())
    gtk_box_pack_start (box, ab->progress, FALSE, FALSE, 0);

  if ( has_status ) {
    if ( (interactivity == GNOME_PREFERENCES_ALWAYS) ||
         ( (interactivity == GNOME_PREFERENCES_USER) &&
           gnome_preferences_get_statusbar_interactive()) ) {
      ab->interactive = TRUE;
   
      ab->status = gtk_entry_new();

      gtk_signal_connect (GTK_OBJECT(ab->status), "delete_text",
                          GTK_SIGNAL_FUNC(entry_delete_text_cb),
                          ab);
      gtk_signal_connect (GTK_OBJECT(ab->status), "insert_text",
                          GTK_SIGNAL_FUNC(entry_insert_text_cb),
                          ab);
      gtk_signal_connect_after(GTK_OBJECT(ab->status), "key_press_event",
                               GTK_SIGNAL_FUNC(entry_key_press_cb),
                               ab);
      gtk_signal_connect(GTK_OBJECT(ab->status), "activate",
                         GTK_SIGNAL_FUNC(entry_activate_cb),
                         ab);

      /* no prompt now */
      gtk_entry_set_editable(GTK_ENTRY(ab->status), FALSE);

      gtk_box_pack_start (box, ab->status, TRUE, TRUE, 0);
    }
    else {
      GtkWidget * frame;
      
      ab->interactive = FALSE;

      frame = gtk_frame_new (NULL);
      gtk_frame_set_shadow_type (GTK_FRAME(frame), GTK_SHADOW_IN);
      
      ab->status = gtk_label_new ("");
      gtk_misc_set_alignment (GTK_MISC (ab->status), 0.0, 0.0);
      
      gtk_box_pack_start (box, frame, TRUE, TRUE, 0);
      gtk_container_add (GTK_CONTAINER(frame), ab->status);
      
      gtk_widget_show (frame);
    }
  }
  else {
    ab->status = NULL;
    ab->interactive = FALSE;
  }

  if (has_progress && gnome_preferences_get_statusbar_meter_on_right ())
    gtk_box_pack_start (box, ab->progress, FALSE, FALSE, 0);

  if (ab->status) gtk_widget_show (ab->status);
  if (ab->progress) gtk_widget_show(ab->progress);
}
    

Most of this code could be in the instance initializer; it's in the constructor instead because it's dependent on the arguments passed to gnome_appbar_new(). There's not much to explain here; the code is straightforward. Do notice that gtk_widget_show() is called for each child widget; this ensures that the right thing happens when the user calls gtk_widget_show() on GnomeAppBar. Another approach would be to override the map method and map all children (normally, containers such as GtkBox only map children that have been shown). When you're writing a composite container, keep the gtk_widget_show_all() function in mind; never rely on hiding child widgets, because the user might accidentally show them.

A composite widget is just a special case of extending a base widget with additional functionality. You can extend widgets without adding new children to them; for example, GtkClock extends GtkLabel by constantly changing the label to reflect the time.