/* Lists/Application launcher
 * #Keywords: GtkListItemFactory, GListModel
 *
 * This demo uses the GtkListView widget as a fancy application launcher.
 *
 * It is also a very small introduction to listviews.
 */

#include <gtk/gtk.h>

/* 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 GtkLabel 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 (G_LIST_MODEL (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);

      /* 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 ();

      /* Create the list widget here.
       */
      list = gtk_list_view_new (GTK_SELECTION_MODEL (gtk_single_selection_new (model)), 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);

      /* 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 ();
      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;
}
