Setting up a GtkGridView

, ,

I’m trying to figure out how to setup GtkGridView to display an array of images and managed to get it working. But it would display the directory to the file instead of the image file itself.

Below is a part of my code. Please help me figure this out.

#include <gtk-4.0/gtk/gtk.h>
#include <gtk-4.0/gtk/gtknoselection.h>
#include <gtk-4.0/gtk/gtkexpression.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include <stdlib.h>

#define GAME_HOME_DIR "/home/theeast/Games/"
#define GAME_COVER "cover.png"

static void print_error(const char *function_name,
                        GError     **ptr_error) {
  GError *error = ptr_error ? *ptr_error : NULL;
  g_print("%s failed", function_name);

  if(error) {
    g_print(": %s", error->message);
    g_clear_error(ptr_error);
  }

  g_print("\n");
}

static GList *enumerate_child_dirs(const char *dirs) {
  GFile *dir = NULL;
  GFileEnumerator *direnum = NULL;
  GList *results = NULL;
  GError *error = NULL;

  dir = g_file_new_for_path(dirs);
  direnum = g_file_enumerate_children(dir, 
                                      "standard::*", 
                                      G_FILE_QUERY_INFO_NONE, 
                                      NULL, &error);

  if(!direnum) {
    print_error("g_file_enumerate_children", &error);
    goto cleanup;
  }

  while(TRUE) {
    GFileInfo *info;

    if(!g_file_enumerator_iterate(direnum, &info,
                                  NULL, NULL, &error)) {
      print_error("g_file_enumerator_iterate", &error);
      goto cleanup;
    }

    if(!info)
      break;

    if(g_file_info_get_file_type(info) == G_FILE_TYPE_DIRECTORY)
      results = g_list_prepend(results, g_strdup(g_file_info_get_name(info)));
  }

  cleanup:
    g_clear_object(&direnum);
    g_clear_object(&dir);

  return g_list_reverse(results);
}

static GListModel *create_cover_model(void) {
  GList *child_dirs = enumerate_child_dirs(GAME_HOME_DIR);
  GListStore *result;
  GtkWidget *box, *cover;
  char *cover_file, *start_script, *stop_script;

  result = g_list_store_new(GTK_TYPE_IMAGE);

  for(GList *l = child_dirs; l != NULL; l = l->next) {
    box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_box_set_homogeneous(GTK_BOX(box), TRUE);

    cover_file = g_build_filename(GAME_HOME_DIR,
                                  (const char*) l->data,
                                  "cover.png", NULL);

    cover = gtk_image_new_from_file(cover_file);
    gtk_image_set_pixel_size(GTK_IMAGE(cover), 256);
    gtk_box_append(GTK_BOX(box), cover);

    g_list_store_append(result, cover);
    g_object_ref_sink(cover);

    g_clear_pointer(&cover_file, g_free);
  }

  g_list_free_full(child_dirs, g_free);

  return G_LIST_MODEL(result);
}

static void setup_cover_cb(GtkListItemFactory *factory,
                           GtkListItem        *list_item) {
  GtkWidget *box, *cover, *label;
  GtkExpression *cover_expression, *expression;

  box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
  gtk_list_item_set_child(list_item, box);

  expression = gtk_constant_expression_new(GTK_TYPE_LIST_ITEM, list_item);
  cover_expression = gtk_property_expression_new(GTK_TYPE_LIST_ITEM, expression, "item");

  expression = gtk_property_expression_new(GTK_TYPE_IMAGE, 
                                           gtk_expression_ref(cover_expression), 
                                           "file");
  label = gtk_label_new(NULL);
  gtk_expression_bind(expression, label, "label", label);
  gtk_box_append(GTK_BOX(box), label);

  expression = gtk_expression_ref(cover_expression);
  cover = gtk_image_new();
  gtk_expression_bind(expression, cover, "paintable", cover);
  gtk_box_append(GTK_BOX(box), cover);

  expression = gtk_property_expression_new(GTK_TYPE_IMAGE, 
                                           gtk_expression_ref(cover_expression), 
                                           "file");
  expression = gtk_cclosure_expression_new(G_TYPE_STRING, 
                                           NULL, 
                                           1, (GtkExpression *[1]) {expression}, 
                                           G_CALLBACK(print_error), 
                                           NULL, NULL);

  gtk_expression_unref(cover_expression);
}

GtkWidget *brewery_catalog(void) {
  GtkWidget *swin;
  GtkWidget *grid;
  GtkListItemFactory *factory;
  GtkSelectionModel *model;

  swin = gtk_scrolled_window_new();
  gtk_scrolled_window_set_has_frame(GTK_SCROLLED_WINDOW(swin), TRUE);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(swin), 
                                 GTK_POLICY_AUTOMATIC, 
                                 GTK_POLICY_AUTOMATIC);
  gtk_widget_set_vexpand(swin, TRUE);

  model = GTK_SELECTION_MODEL(gtk_no_selection_new(create_cover_model()));

  factory = gtk_signal_list_item_factory_new();
  g_signal_connect(factory, "setup", G_CALLBACK(setup_cover_cb), NULL);

  grid = gtk_grid_view_new(model, factory);
  // gtk_grid_view_set_enable_rubberband(GTK_GRID_VIEW(grid), FALSE);
  gtk_scrollable_set_hscroll_policy(GTK_SCROLLABLE(grid), GTK_SCROLL_NATURAL);
  gtk_scrollable_set_vscroll_policy(GTK_SCROLLABLE(grid), GTK_SCROLL_NATURAL);

  gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(swin), grid);

  return swin;
}

The below design seems wrong to me. The point of the scalable list views is to be, well, scalable - meaning precisely that they avoid creating widgets for every item in the model, and having all such widgets alive at all times. But here you create an image and a seemingly pointless box for every file, and put those in the model.

I think you should instead have the model store only the paths, and have the bind handler load the images, create widgets from them, etc.

As for the specific problem, I’m afraid it’s not clear from your description and lack of a screenshot of the wrong behaviour, and I don’t think many readers will be willing to fill in the missing code to get it running and try to debug/fix it for you. If you need that help, you should try to reduce it down to a minimal, complete sample. :slight_smile:

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.