From 017a5e3d5be6869abe485ea0c631160d1fa999b9 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 28 Jan 2006 06:03:50 +0000 Subject: [PATCH] More work on GtkAssistant by Carlos Garnacho: 2006-01-28 Matthias Clasen More work on GtkAssistant by Carlos Garnacho: * demos/gtk-demo/Makefile.am: * demos/gtk-demo/assistant.c: Add a GtkAssistant demo. * gtk/gtkassistant.c: Handle focus, several small fixes to the flow computations. --- ChangeLog | 10 + ChangeLog.pre-2-10 | 10 + demos/gtk-demo/Makefile.am | 1 + demos/gtk-demo/assistant.c | 165 +++++++++ docs/reference/ChangeLog | 6 + docs/reference/gtk/gtk-docs.sgml | 2 + .../reference/gtk/migrating-GtkAssistant.sgml | 170 ++++++++++ docs/reference/gtk/tmpl/gtkassistant.sgml | 315 ++++++++++++++++++ gtk/gtkassistant.c | 91 +++-- 9 files changed, 749 insertions(+), 21 deletions(-) create mode 100644 demos/gtk-demo/assistant.c create mode 100644 docs/reference/gtk/migrating-GtkAssistant.sgml create mode 100644 docs/reference/gtk/tmpl/gtkassistant.sgml diff --git a/ChangeLog b/ChangeLog index f30f8154e0..55cab24d3b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2006-01-28 Matthias Clasen + + More work on GtkAssistant by Carlos Garnacho: + + * demos/gtk-demo/Makefile.am: + * demos/gtk-demo/assistant.c: Add a GtkAssistant demo. + + * gtk/gtkassistant.c: Handle focus, several small fixes to the + flow computations. + 2006-01-27 Federico Mena Quintero Fixes bug #328820: diff --git a/ChangeLog.pre-2-10 b/ChangeLog.pre-2-10 index f30f8154e0..55cab24d3b 100644 --- a/ChangeLog.pre-2-10 +++ b/ChangeLog.pre-2-10 @@ -1,3 +1,13 @@ +2006-01-28 Matthias Clasen + + More work on GtkAssistant by Carlos Garnacho: + + * demos/gtk-demo/Makefile.am: + * demos/gtk-demo/assistant.c: Add a GtkAssistant demo. + + * gtk/gtkassistant.c: Handle focus, several small fixes to the + flow computations. + 2006-01-27 Federico Mena Quintero Fixes bug #328820: diff --git a/demos/gtk-demo/Makefile.am b/demos/gtk-demo/Makefile.am index 04038edb74..c3bd17f55f 100644 --- a/demos/gtk-demo/Makefile.am +++ b/demos/gtk-demo/Makefile.am @@ -6,6 +6,7 @@ democodedir=$(datadir)/gtk-2.0/demo ## demo app, which means alphabetized by demo title, not filename demos = \ appwindow.c \ + assistant.c \ button_box.c \ changedisplay.c \ clipboard.c \ diff --git a/demos/gtk-demo/assistant.c b/demos/gtk-demo/assistant.c new file mode 100644 index 0000000000..f5c69dd021 --- /dev/null +++ b/demos/gtk-demo/assistant.c @@ -0,0 +1,165 @@ +/* Assistant + * + * Demonstrates a sample multistep assistant. Assistants are used to divide + * an operation into several simpler sequential steps, and to guide the user + * through these steps. + */ + +#include +#include "demo-common.h" + +static GtkWidget *assistant = NULL; + +static void +on_assistant_apply (GtkWidget *widget, gpointer data) +{ + /* Apply here changes, this is a fictional + example, so we just do nothing here */ +} + +static void +on_assistant_close_cancel (GtkWidget *widget, gpointer data) +{ + GtkWidget **assistant = (GtkWidget **) data; + + gtk_widget_destroy (*assistant); + *assistant = NULL; +} + +static void +on_assistant_prepare (GtkWidget *widget, GtkWidget *page, gpointer data) +{ + gint current_page, n_pages; + gchar *title; + + current_page = gtk_assistant_get_current_page (GTK_ASSISTANT (widget)); + n_pages = gtk_assistant_get_n_pages (GTK_ASSISTANT (widget)); + + title = g_strdup_printf ("Sample assistant (%d of %d)", current_page + 1, n_pages); + gtk_window_set_title (GTK_WINDOW (widget), title); + g_free (title); +} + +static void +on_entry_changed (GtkWidget *widget, gpointer data) +{ + GtkAssistant *assistant = GTK_ASSISTANT (data); + GtkWidget *current_page; + gint page_number; + const gchar *text; + + page_number = gtk_assistant_get_current_page (assistant); + current_page = gtk_assistant_get_nth_page (assistant, page_number); + text = gtk_entry_get_text (GTK_ENTRY (widget)); + + if (text && *text) + gtk_assistant_set_page_complete (assistant, current_page, TRUE); + else + gtk_assistant_set_page_complete (assistant, current_page, FALSE); +} + +static void +create_page1 (GtkWidget *assistant) +{ + GtkWidget *box, *label, *entry; + GdkPixbuf *pixbuf; + + box = gtk_hbox_new (FALSE, 12); + gtk_container_set_border_width (GTK_CONTAINER (box), 12); + + label = gtk_label_new ("You must fill out this entry to continue:"); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0); + + entry = gtk_entry_new (); + gtk_box_pack_start (GTK_BOX (box), entry, TRUE, TRUE, 0); + g_signal_connect (G_OBJECT (entry), "changed", + G_CALLBACK (on_entry_changed), assistant); + + gtk_widget_show_all (box); + gtk_assistant_append_page (GTK_ASSISTANT (assistant), box); + gtk_assistant_set_page_title (GTK_ASSISTANT (assistant), box, "Page 1"); + gtk_assistant_set_page_type (GTK_ASSISTANT (assistant), box, GTK_ASSISTANT_PAGE_INTRO); + + pixbuf = gtk_widget_render_icon (assistant, GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG, NULL); + gtk_assistant_set_page_header_image (GTK_ASSISTANT (assistant), box, pixbuf); + g_object_unref (pixbuf); +} + +static void +create_page2 (GtkWidget *assistant) +{ + GtkWidget *box, *checkbutton; + GdkPixbuf *pixbuf; + + box = gtk_vbox_new (12, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (box), 12); + + checkbutton = gtk_check_button_new_with_label ("This is optional data, you may continue " + "even if you do not check this"); + gtk_box_pack_start (GTK_BOX (box), checkbutton, FALSE, FALSE, 0); + + gtk_widget_show_all (box); + gtk_assistant_append_page (GTK_ASSISTANT (assistant), box); + gtk_assistant_set_page_complete (GTK_ASSISTANT (assistant), box, TRUE); + gtk_assistant_set_page_title (GTK_ASSISTANT (assistant), box, "Page 2"); + + pixbuf = gtk_widget_render_icon (assistant, GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG, NULL); + gtk_assistant_set_page_header_image (GTK_ASSISTANT (assistant), box, pixbuf); + g_object_unref (pixbuf); +} + +static void +create_page3 (GtkWidget *assistant) +{ + GtkWidget *label; + GdkPixbuf *pixbuf; + + label = gtk_label_new ("This is a confirmation page, press 'Apply' to apply changes"); + + gtk_widget_show (label); + gtk_assistant_append_page (GTK_ASSISTANT (assistant), label); + gtk_assistant_set_page_type (GTK_ASSISTANT (assistant), label, GTK_ASSISTANT_PAGE_CONFIRM); + gtk_assistant_set_page_complete (GTK_ASSISTANT (assistant), label, TRUE); + gtk_assistant_set_page_title (GTK_ASSISTANT (assistant), label, "Confirmation"); + + pixbuf = gtk_widget_render_icon (assistant, GTK_STOCK_DIALOG_INFO, GTK_ICON_SIZE_DIALOG, NULL); + gtk_assistant_set_page_header_image (GTK_ASSISTANT (assistant), label, pixbuf); + g_object_unref (pixbuf); +} + +GtkWidget* +do_assistant (GtkWidget *do_widget) +{ + if (!assistant) + { + assistant = gtk_assistant_new (); + + gtk_window_set_default_size (GTK_WINDOW (assistant), -1, 300); + + gtk_window_set_screen (GTK_WINDOW (assistant), + gtk_widget_get_screen (do_widget)); + + create_page1 (assistant); + create_page2 (assistant); + create_page3 (assistant); + + g_signal_connect (G_OBJECT (assistant), "cancel", + G_CALLBACK (on_assistant_close_cancel), &assistant); + g_signal_connect (G_OBJECT (assistant), "close", + G_CALLBACK (on_assistant_close_cancel), &assistant); + g_signal_connect (G_OBJECT (assistant), "apply", + G_CALLBACK (on_assistant_apply), NULL); + g_signal_connect (G_OBJECT (assistant), "prepare", + G_CALLBACK (on_assistant_prepare), NULL); + } + + if (!GTK_WIDGET_VISIBLE (assistant)) + gtk_widget_show (assistant); + else + { + gtk_widget_destroy (assistant); + assistant = NULL; + } + + return assistant; +} diff --git a/docs/reference/ChangeLog b/docs/reference/ChangeLog index c987c4e041..3654f95c51 100644 --- a/docs/reference/ChangeLog +++ b/docs/reference/ChangeLog @@ -1,3 +1,9 @@ +2006-01-28 Matthias Clasen + + * gtk/gtk-docs.sgml: + * gtk/migrating-GtkAssistant.sgml: Add a migration guide + GnomeDruid --> GtkAssistant. (Carlos Garnacho) + 2006-01-27 Federico Mena Quintero * gtk/tmpl/gtkfilechooser.sgml: Mention that ~ is also a default diff --git a/docs/reference/gtk/gtk-docs.sgml b/docs/reference/gtk/gtk-docs.sgml index b0f0116360..145d586f04 100644 --- a/docs/reference/gtk/gtk-docs.sgml +++ b/docs/reference/gtk/gtk-docs.sgml @@ -199,6 +199,7 @@ + @@ -589,6 +590,7 @@ that is, GUI components such as GtkButton or >k-migrating-GtkIconView; >k-migrating-GtkAboutDialog; >k-migrating-GtkColorButton; + >k-migrating-GtkAssistant; diff --git a/docs/reference/gtk/migrating-GtkAssistant.sgml b/docs/reference/gtk/migrating-GtkAssistant.sgml new file mode 100644 index 0000000000..d5ee6e8bd2 --- /dev/null +++ b/docs/reference/gtk/migrating-GtkAssistant.sgml @@ -0,0 +1,170 @@ + + + + Carlos + Garnacho + +
+ carlosg@gnome.org +
+
+
+
+ + Migrating from GnomeDruid to GtkAssistant + + + Since version 2.10, GTK+ provides the GtkAssistant widget as a replacement + for the GnomeDruid widget in the libgnomeui library. + + + + Conceptually, both GtkAssistant and GnomeDruid + do the same task, but there are several areas where the API has been completely + redesigned, so this chapter covers the main changes between both widgets. + + +
+ Inserting pages + + + GnomeDruid was implemented as a container for + GnomeDruidPage abstract objects, which are implemented by the + GnomeDruidPageEdge and GnomeDruidPageStandard + widgets. Instead, GtkAssistant allows any widget to be a page, and implements + per-page settings (such as page type or title) as child properties. So instead of: + + + +/* Page 1 */ +page = gnome_druid_page_edge_new (GNOME_EDGE_START); +gnome_druid_page_edge_set_test (GNOME_DRUID_PAGE_EDGE (page), + "Welcome to the assistant, it will make your life easier"); +gtk_widget_show (page); +gnome_druid_append_page (GNOME_DRUID (druid), GNOME_DRUID_PAGE (page)); + +/* Page 2 */ +page = gnome_druid_page_standard_new (); +gtk_container_add (GTK_CONTAINER (GNOME_DRUID_PAGE_STANDARD (page)->vbox, + create_page1 ()); +gtk_widget_show_all (page); +gnome_druid_append_page (GNOME_DRUID (druid), GNOME_DRUID_PAGE (page)); + +/* Page 3 */ +page = gnome_druid_page_edge_new (GNOME_EDGE_FINISH); +gnome_druid_page_edge_set_test (GNOME_DRUID_PAGE_EDGE (page), + "Now you are done, your life is easier"); +gtk_widget_show (page); +gnome_druid_append_page (GNOME_DRUID (druid), GNOME_DRUID_PAGE (page)); + + + + You have to write: + + + +gtk_assistant_append_page (GTK_ASSISTANT (assistant), + gtk_label_new ("Welcome to the assistant, it will make your life easier")); +gtk_assistant_append_page (GTK_ASSISTANT (assistant), + create_page1 ()); +gtk_assistant_append_page (GTK_ASSISTANT (assistant), + gtk_label_new ("Now you are done, your life is easier"); + +
+ +
+ Decorating the assistant pages + + + To decorate your assistant pages, GtkAssistant provides similar functions + to GnomeDruid, so you have to transform code like this: + + + +gnome_druid_page_edge_set_title (GNOME_DRUID_PAGE_EDGE (page), "Welcome"); +gnome_druid_page_edge_set_logo (GNOME_DRUID_PAGE_EDGE (page), logo_pixbuf); +gnome_druid_page_edge_set_watermark (GNOME_DRUID_PAGE_EDGE (page), watermark_pixbuf); + + + + Into this: + + + +gtk_assistant_set_page_title (GTK_ASSISTANT (assistant), page_widget, "Welcome"); +gtk_assistant_set_page_header_image (GTK_ASSISTANT (assistant), page_widget, logo_pixbuf); +gtk_assistant_set_page_side_image (GTK_ASSISTANT (assistant), page_widget, watermark_pixbuf); + + + + Where page_widget is the widget used as a page. + +
+ +
+ Setting the page flow + + + Here is the area where GtkAssistant and GnomeDruid + differ the most. While GnomeDruid used the "next" and "back" signals from the + GnomeDruidPage, GtkAssistant uses the following + techniques: + + + + + gtk_assistant_set_forward_page_func (): Allows to define a GtkAssistantPageFunc to let the + assistant know which will be the following page given the current page. + + + gtk_assistant_set_page_complete (): Lets the assistant know whether the specified page is complete + or not, updating buttons state accordingly. + + + gtk_assistant_set_page_type (): Lets the assistant know the page role and update the buttons + state accordingly. Pages can have the following roles: + + Intro + Content + Progress + Confirmation + Summary + + + + + + A sample GtkAssistantPageFunc could look like this: + + + +static gint +forward_page_function (gint current_page, + gpointer data) +{ + switch (current_page) + { + case 0: + return 1; + case 1: + if (check_page1_data ()) + return 2; + else + return 3; + case 2: + return 3; + default: + return -1; + } +} + + +
+
+ + diff --git a/docs/reference/gtk/tmpl/gtkassistant.sgml b/docs/reference/gtk/tmpl/gtkassistant.sgml new file mode 100644 index 0000000000..6a1a77e75f --- /dev/null +++ b/docs/reference/gtk/tmpl/gtkassistant.sgml @@ -0,0 +1,315 @@ + +GtkAssistant + + +guiding users through multi-step operations + + + + + + + + + + + + + + + + + + + + + + + + + +@assistant: the object which received the signal. + + + + + + +@assistant: the object which received the signal. + + + + + + +@assistant: the object which received the signal. + + + + + + +@assistant: the object which received the signal. +@widget: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@Returns: + + + + + + + +@assistant: +@Returns: + + + + + + + +@assistant: +@page_num: + + + + + + + +@assistant: +@Returns: + + + + + + + +@assistant: +@page_num: +@Returns: + + + + + + + +@assistant: +@page: +@Returns: + + + + + + + +@assistant: +@page: +@Returns: + + + + + + + +@assistant: +@page: +@position: +@Returns: + + + + + + + +@current_page: +@data: +@Returns: + + + + + + + +@assistant: +@page_func: +@data: +@destroy: + + + + + + + +@GTK_ASSISTANT_PAGE_CONTENT: +@GTK_ASSISTANT_PAGE_INTRO: +@GTK_ASSISTANT_PAGE_CONFIRM: +@GTK_ASSISTANT_PAGE_SUMMARY: +@GTK_ASSISTANT_PAGE_PROGRESS: + + + + + + +@assistant: +@page: +@type: + + + + + + + +@assistant: +@page: +@Returns: + + + + + + + +@assistant: +@page: +@title: + + + + + + + +@assistant: +@page: +@Returns: + + + + + + + +@assistant: +@page: +@pixbuf: + + + + + + + +@assistant: +@page: +@Returns: + + + + + + + +@assistant: +@page: +@pixbuf: + + + + + + + +@assistant: +@page: +@Returns: + + + + + + + +@assistant: +@page: +@complete: + + + + + + + +@assistant: +@page: +@Returns: + + + + + + + +@assistant: +@child: + + + + + + + +@assistant: +@child: + + diff --git a/gtk/gtkassistant.c b/gtk/gtkassistant.c index 18a1c69a7a..1100456166 100644 --- a/gtk/gtkassistant.c +++ b/gtk/gtkassistant.c @@ -92,6 +92,8 @@ static gboolean gtk_assistant_delete_event (GtkWidget *widget, GdkEventAny *event); static gboolean gtk_assistant_expose (GtkWidget *widget, GdkEventExpose *event); +static gboolean gtk_assistant_focus (GtkWidget *widget, + GtkDirectionType direction); static void gtk_assistant_add (GtkContainer *container, GtkWidget *page); static void gtk_assistant_remove (GtkContainer *container, @@ -158,6 +160,7 @@ gtk_assistant_class_init (GtkAssistantClass *class) widget_class->unmap = gtk_assistant_unmap; widget_class->delete_event = gtk_assistant_delete_event; widget_class->expose_event = gtk_assistant_expose; + widget_class->focus = gtk_assistant_focus; container_class->add = gtk_assistant_add; container_class->remove = gtk_assistant_remove; @@ -212,10 +215,9 @@ gtk_assistant_class_init (GtkAssistantClass *class) * * A handler for the ::apply signal should carry out the actions for which the * wizard has collected data. If the action takes a long time to complete, you - * might consider to put a page displaying the progress of the operation after the - * confirmation page with the apply button. - * - * Return value: %TRUE to suppress the default behavior + * might consider to put a page of type GTK_ASSISTANT_PAGE_PROGRESS after the + * confirmation page and handle this operation within the "prepare" signal of + * the progress page. * * Since: 2.10 */ @@ -232,7 +234,9 @@ gtk_assistant_class_init (GtkAssistantClass *class) * GtkAssistant::close: * @assistant: the #GtkAssistant * - * The ::close signal is emitted when the close button is clicked. + * The ::close signal is emitted either when the close button of + * a summary page is clicked, or when the apply button in the last + * page in the flow (of type GTK_ASSISTANT_PAGE_CONFIRM) is clicked. * * Since: 2.10 */ @@ -366,11 +370,13 @@ default_forward_function (gint current_page, gpointer data) page_info = (GtkAssistantPage *) page_node->data; - while (!GTK_WIDGET_VISIBLE (page_info->page)) + while (page_node && !GTK_WIDGET_VISIBLE (page_info->page)) { page_node = page_node->next; - page_info = (GtkAssistantPage *) page_node->data; current_page++; + + if (page_node) + page_info = (GtkAssistantPage *) page_node->data; } return current_page; @@ -380,17 +386,18 @@ static void compute_last_button_state (GtkAssistant *assistant) { GtkAssistantPrivate *priv = assistant->priv; - GtkAssistantPage *page_info; + GtkAssistantPage *page_info, *current_page_info; gint count, page_num, n_pages; count = 0; page_num = gtk_assistant_get_current_page (assistant); n_pages = gtk_assistant_get_n_pages (assistant); - page_info = g_list_nth_data (priv->pages, page_num); + current_page_info = page_info = g_list_nth_data (priv->pages, page_num); while (page_num >= 0 && page_num < n_pages && - (page_info->type == GTK_ASSISTANT_PAGE_CONTENT) && - page_info->complete && count < n_pages) + page_info->type == GTK_ASSISTANT_PAGE_CONTENT && + (count == 0 || page_info->complete) && + count < n_pages) { page_num = (priv->forward_function) (page_num, priv->forward_function_data); page_info = g_list_nth_data (priv->pages, page_num); @@ -406,7 +413,11 @@ compute_last_button_state (GtkAssistant *assistant) if (count > 1 && (page_info->type == GTK_ASSISTANT_PAGE_CONFIRM || page_info->type == GTK_ASSISTANT_PAGE_SUMMARY)) - gtk_widget_show (assistant->last); + { + gtk_widget_show (assistant->last); + gtk_widget_set_sensitive (assistant->last, + current_page_info->complete); + } else gtk_widget_hide (assistant->last); } @@ -1134,7 +1145,7 @@ gtk_assistant_map (GtkWidget *widget) { page_node = priv->pages; - while (!GTK_WIDGET_VISIBLE (((GtkAssistantPage *) page_node->data)->page)) + while (page_node && !GTK_WIDGET_VISIBLE (((GtkAssistantPage *) page_node->data)->page)) page_node = page_node->next; if (page_node) @@ -1276,6 +1287,43 @@ gtk_assistant_expose (GtkWidget *widget, return FALSE; } +static gboolean +gtk_assistant_focus (GtkWidget *widget, + GtkDirectionType direction) +{ + GtkAssistantPrivate *priv; + GtkContainer *container; + + container = GTK_CONTAINER (widget); + priv = GTK_ASSISTANT (widget)->priv; + + /* we only have to care about 2 widgets, action area and the current page */ + if (container->focus_child == priv->action_area) + { + if (!gtk_widget_child_focus (priv->action_area, direction) && + !gtk_widget_child_focus (priv->current_page->page, direction)) + { + /* if we're leaving the action area and the current page hasn't + any focusable widget, clear focus and go back to the action area */ + gtk_container_set_focus_child (GTK_CONTAINER (priv->action_area), NULL); + gtk_widget_child_focus (priv->action_area, direction); + } + } + else + { + if (!gtk_widget_child_focus (priv->current_page->page, direction) && + !gtk_widget_child_focus (priv->action_area, direction)) + { + /* if we're leaving the current page and there isn't nothing focusable + in the action area, try to clear focus and go back to the page */ + gtk_window_set_focus (GTK_WINDOW (widget), NULL); + gtk_widget_child_focus (priv->current_page->page, direction); + } + } + + return TRUE; +} + static void gtk_assistant_add (GtkContainer *container, GtkWidget *page) @@ -1625,11 +1673,16 @@ gtk_assistant_set_forward_page_func (GtkAssistant *assistant, priv->forward_function_data = assistant; priv->forward_data_destroy = NULL; } + + /* Page flow has possibly changed, so the + buttons state might need to change too */ + if (priv->current_page) + _set_assistant_buttons_state (assistant); } /** * gtk_assistant_add_action_widget: - * @dialog: a #GtkAssistant + * @assistant: a #GtkAssistant * @child: a #GtkWidget * * Adds a widget to the action area of a #GtkAssistant. @@ -1655,7 +1708,7 @@ gtk_assistant_add_action_widget (GtkAssistant *assistant, /** * gtk_assistant_remove_action_widget: - * @dialog: a #GtkAssistant + * @assistant: a #GtkAssistant * @child: a #GtkWidget * * Removes a widget from the action area of a #GtkAssistant. @@ -1989,7 +2042,7 @@ gtk_assistant_get_page_side_image (GtkAssistant *assistant, * gtk_assistant_set_page_complete: * @assistant: a #GtkAssistant * @page: a page of @assitant - * @pixbuf: the new header image @page + * @complete: the completeness status of the page * * Sets whether @page contents are complete. This will make * @assistant update the buttons state to be able to continue the task. @@ -2022,11 +2075,7 @@ gtk_assistant_set_page_complete (GtkAssistant *assistant, /* Always set buttons state, a change in a future page might change current page buttons */ if (priv->current_page) - { - /* Always set buttons state, a change in a future page - might change current page buttons */ - _set_assistant_buttons_state (assistant); - } + _set_assistant_buttons_state (assistant); gtk_widget_child_notify (page, "complete"); }