From 669a6ddeaa07c81c844385098cf1d93dac497694 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 4 Jun 2020 15:44:39 -0400 Subject: [PATCH 1/2] gtk-demo: Bring back the applauncher demo Just without the coverflow. It was a well-documented demo, so it is useful to keep around. --- demos/gtk-demo/demo.gresource.xml | 1 + demos/gtk-demo/listview_applauncher.c | 202 ++++++++++++++++++++++++++ demos/gtk-demo/meson.build | 1 + 3 files changed, 204 insertions(+) create mode 100644 demos/gtk-demo/listview_applauncher.c diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml index fc0c1465a4..400ecbf208 100644 --- a/demos/gtk-demo/demo.gresource.xml +++ b/demos/gtk-demo/demo.gresource.xml @@ -215,6 +215,7 @@ infobar.c links.c listbox.c + listview_applauncher.c listview_colors.c listview_clocks.c listview_filebrowser.c diff --git a/demos/gtk-demo/listview_applauncher.c b/demos/gtk-demo/listview_applauncher.c new file mode 100644 index 0000000000..5666d235c1 --- /dev/null +++ b/demos/gtk-demo/listview_applauncher.c @@ -0,0 +1,202 @@ +/* Lists/Application launcher + * + * This demo uses the GtkListView widget as a fancy application launcher. + * + * It is also a very small introduction to listviews. + */ + +#include + +/* This is the function that creates the #GListModel that we need. + * GTK list widgets need a #GListModel to display, as models support change + * notifications. + * Unfortunately various older APIs do not provide list models, so we create + * our own. + */ +static GListModel * +create_application_list (void) +{ + GListStore *store; + GList *apps, *l; + + /* We use a #GListStore here, which is a simple array-like list implementation + * for manual management. + * List models need to know what type of data they provide, so we need to + * provide the type here. As we want to do a list of applications, #GAppInfo + * is the object we provide. + */ + store = g_list_store_new (G_TYPE_APP_INFO); + + apps = g_app_info_get_all (); + + for (l = apps; l; l = l->next) + g_list_store_append (store, l->data); + + g_list_free_full (apps, g_object_unref); + + return G_LIST_MODEL (store); +} + +/* This is the function we use for setting up new listitems to display. + * We add just an #GtkImage and a #GtkKabel here to display the application's + * icon and name, as this is just a simple demo. + */ +static void +setup_listitem_cb (GtkListItemFactory *factory, + GtkListItem *list_item) +{ + GtkWidget *box; + GtkWidget *image; + GtkWidget *label; + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + image = gtk_image_new (); + gtk_image_set_icon_size (GTK_IMAGE (image), GTK_ICON_SIZE_LARGE); + gtk_box_append (GTK_BOX (box), image); + label = gtk_label_new (""); + gtk_box_append (GTK_BOX (box), label); + gtk_list_item_set_child (list_item, box); +} + +/* Here we need to prepare the listitem for displaying its item. We get the + * listitem already set up from the previous function, so we can reuse the + * #GtkImage widget we set up above. + * We get the item - which we know is a #GAppInfo because it comes out of + * the model we set up above, grab its icon and display it. + */ +static void +bind_listitem_cb (GtkListItemFactory *factory, + GtkListItem *list_item) +{ + GtkWidget *image; + GtkWidget *label; + GAppInfo *app_info; + + image = gtk_widget_get_first_child (gtk_list_item_get_child (list_item)); + label = gtk_widget_get_next_sibling (image); + app_info = gtk_list_item_get_item (list_item); + + gtk_image_set_from_gicon (GTK_IMAGE (image), g_app_info_get_icon (app_info)); + gtk_label_set_label (GTK_LABEL (label), g_app_info_get_display_name (app_info)); +} + +/* In more complex code, we would also need functions to unbind and teardown + * the listitem, but this is simple code, so the default implementations are + * enough. If we had connected signals, this step would have been necessary. + * + * The #GtkSignalListItemFactory documentation contains more information about + * this step. + */ + +/* This function is called whenever an item in the list is activated. This is + * the simple way to allow reacting to the Enter key or double-clicking on a + * listitem. + * Of course, it is possible to use far more complex interactions by turning + * off activation and adding buttons or other widgets in the setup function + * above, but this is a simple demo, so we'll use the simple way. + */ +static void +activate_cb (GtkListView *list, + guint position, + gpointer unused) +{ + GAppInfo *app_info; + GdkAppLaunchContext *context; + GError *error = NULL; + + app_info = g_list_model_get_item (gtk_list_view_get_model (list), position); + + /* Prepare the context for launching the application and launch it. This + * code is explained in detail in the documentation for #GdkAppLaunchContext + * and #GAppInfo. + */ + context = gdk_display_get_app_launch_context (gtk_widget_get_display (GTK_WIDGET (list))); + if (!g_app_info_launch (app_info, + NULL, + G_APP_LAUNCH_CONTEXT (context), + &error)) + { + GtkWidget *dialog; + + /* And because error handling is important, even a simple demo has it: + * We display an error dialog that something went wrong. + */ + dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (list))), + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "Could not launch %s", g_app_info_get_display_name (app_info)); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", error->message); + g_clear_error (&error); + gtk_widget_show (dialog); + } + + g_object_unref (context); + g_object_unref (app_info); +} + +static GtkWidget *window = NULL; + +GtkWidget * +do_listview_applauncher (GtkWidget *do_widget) +{ + if (window == NULL) + { + GtkWidget *list, *sw; + GListModel *model; + GtkListItemFactory *factory; + + /* Create a window and set a few defaults */ + window = gtk_window_new (); + gtk_window_set_default_size (GTK_WINDOW (window), 640, 320); + gtk_window_set_display (GTK_WINDOW (window), + gtk_widget_get_display (do_widget)); + gtk_window_set_title (GTK_WINDOW (window), "Application Launcher"); + g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window); + + /* The #GtkListitemFactory is what is used to create #GtkListItems + * to display the data from the model. So it is absolutely necessary + * to create one. + * We will use a #GtkSignalListItemFactory because it is the simplest + * one to use. Different ones are available for different use cases. + * The most powerful one is #GtkBuilderListItemFactory which uses + * #GtkBuilder .ui files, so it requires little code. + */ + factory = gtk_signal_list_item_factory_new (); + g_signal_connect (factory, "setup", G_CALLBACK (setup_listitem_cb), NULL); + g_signal_connect (factory, "bind", G_CALLBACK (bind_listitem_cb), NULL); + + /* Create the list widget here. + */ + list = gtk_list_view_new_with_factory (factory); + /* We connect the activate signal here. It's the function we defined + * above for launching the selected application. + */ + g_signal_connect (list, "activate", G_CALLBACK (activate_cb), NULL); + + /* And of course we need to set the data model. Here we call the function + * we wrote above that gives us the list of applications. Then we set + * it on the list widget. + * The list will now take items from the model and use the factory + * to create as many listitems as it needs to show itself to the user. + */ + model = create_application_list (); + gtk_list_view_set_model (GTK_LIST_VIEW (list), model); + g_object_unref (model); + + /* List widgets should always be contained in a #GtkScrolledWindow, + * because otherwise they might get too large or they might not + * be scrollable. + */ + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_window_set_child (GTK_WINDOW (window), sw); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), list); + } + + if (!gtk_widget_get_visible (window)) + gtk_widget_show (window); + else + gtk_window_destroy (GTK_WINDOW (window)); + + return window; +} diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build index 1a9ee4aee0..eb6513591b 100644 --- a/demos/gtk-demo/meson.build +++ b/demos/gtk-demo/meson.build @@ -42,6 +42,7 @@ demos = files([ 'listbox.c', 'flowbox.c', 'list_store.c', + 'listview_applauncher.c', 'listview_clocks.c', 'listview_colors.c', 'listview_filebrowser.c', From 2437c5a0aec47fc59525bce1f6e6b5cdbc5dbf77 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 4 Jun 2020 20:11:35 -0400 Subject: [PATCH 2/2] listview: Add an example to the docs The example is an excerpt from the applauncher demo in gtk4-demo. --- gtk/gtklistview.c | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index beeef875b8..484f5b172c 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -62,6 +62,60 @@ * * To learn more about the list widget framework, see the [overview](#ListWidget). * + * An example of using GtkListView: + * |[ + * static void + * setup_listitem_cb (GtkListItemFactory *factory, + * GtkListItem *list_item) + * { + * GtkWidget *image; + * + * image = gtk_image_new (); + * gtk_image_set_icon_size (GTK_IMAGE (image), GTK_ICON_SIZE_LARGE); + * gtk_list_item_set_child (list_item, image); + * } + * + * static void + * bind_listitem_cb (GtkListItemFactory *factory, + * GtkListItem *list_item) + * { + * GtkWidget *image; + * GAppInfo *app_info; + * + * image = gtk_list_item_get_child (list_item); + * app_info = gtk_list_item_get_item (list_item); + * gtk_image_set_from_gicon (GTK_IMAGE (image), g_app_info_get_icon (app_info)); + * } + * + * static void + * activate_cb (GtkListView *list, + * guint position, + * gpointer unused) + * { + * GAppInfo *app_info; + * + * app_info = g_list_model_get_item (gtk_list_view_get_model (list), position); + * g_app_info_launch (app_info, NULL, NULL, NULL); + * g_object_unref (app_info); + * } + * + * ... + * + * factory = gtk_signal_list_item_factory_new (); + * g_signal_connect (factory, "setup", G_CALLBACK (setup_listitem_cb), NULL); + * g_signal_connect (factory, "bind", G_CALLBACK (bind_listitem_cb), NULL); + * + * list = gtk_list_view_new_with_factory (factory); + * + * g_signal_connect (list, "activate", G_CALLBACK (activate_cb), NULL); + * + * model = create_application_list (); + * gtk_list_view_set_model (GTK_LIST_VIEW (list), model); + * g_object_unref (model); + * + * gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), list); + * ]| + * * # CSS nodes * * |[