gtk-demo: Rename dropdown demo
authorBenjamin Otte <otte@redhat.com>
Sun, 26 Mar 2023 00:01:29 +0000 (01:01 +0100)
committerBenjamin Otte <otte@redhat.com>
Sun, 26 Mar 2023 00:06:18 +0000 (01:06 +0100)
It's the listview selections demo, so use that name.

demos/gtk-demo/demo.gresource.xml
demos/gtk-demo/dropdown.c [deleted file]
demos/gtk-demo/listview_selections.c [new file with mode: 0644]
demos/gtk-demo/meson.build
demos/gtk-demo/suggestionentry.c

index 206833b185c047db17e0657024f66ae74fcde174..615d782bfef56242d6be7258f7435f466144da55 100644 (file)
@@ -43,7 +43,7 @@
     <file>cssview.css</file>
     <file>reset.css</file>
   </gresource>
-  <gresource prefix="/dropdown">
+  <gresource prefix="/listview_selections">
     <file>suggestionentry.h</file>
     <file>suggestionentry.c</file>
     <file>suggestionentry.css</file>
     <file>cursors.c</file>
     <file>dialog.c</file>
     <file>drawingarea.c</file>
-    <file>dropdown.c</file>
     <file>dnd.c</file>
     <file>editable_cells.c</file>
     <file>entry_completion.c</file>
     <file>listview_clocks.c</file>
     <file>listview_filebrowser.c</file>
     <file>listview_minesweeper.c</file>
+    <file>listview_selections.c</file>
     <file>listview_settings.c</file>
     <file>listview_ucd.c</file>
     <file>listview_weather.c</file>
diff --git a/demos/gtk-demo/dropdown.c b/demos/gtk-demo/dropdown.c
deleted file mode 100644 (file)
index a4d001f..0000000
+++ /dev/null
@@ -1,571 +0,0 @@
-/* Lists/Selections
- *
- * The GtkDropDown widget is a modern alternative to GtkComboBox.
- * It uses list models instead of tree models, and the content is
- * displayed using widgets instead of cell renderers.
- *
- * This example also shows a custom widget that can replace
- * GtkEntryCompletion or GtkComboBoxText. It is not currently
- * part of GTK.
- */
-
-#include <gtk/gtk.h>
-#include "suggestionentry.h"
-
-#define STRING_TYPE_HOLDER (string_holder_get_type ())
-G_DECLARE_FINAL_TYPE (StringHolder, string_holder, STRING, HOLDER, GObject)
-
-struct _StringHolder {
-  GObject parent_instance;
-  char *title;
-  char *icon;
-  char *description;
-};
-
-G_DEFINE_TYPE (StringHolder, string_holder, G_TYPE_OBJECT);
-
-static void
-string_holder_init (StringHolder *holder)
-{
-}
-
-static void
-string_holder_finalize (GObject *object)
-{
-  StringHolder *holder = STRING_HOLDER (object);
-
-  g_free (holder->title);
-  g_free (holder->icon);
-  g_free (holder->description);
-
-  G_OBJECT_CLASS (string_holder_parent_class)->finalize (object);
-}
-
-static void
-string_holder_class_init (StringHolderClass *class)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (class);
-
-  object_class->finalize = string_holder_finalize;
-}
-
-static StringHolder *
-string_holder_new (const char *title, const char *icon, const char *description)
-{
-  StringHolder *holder = g_object_new (STRING_TYPE_HOLDER, NULL);
-  holder->title = g_strdup (title);
-  holder->icon = g_strdup (icon);
-  holder->description = g_strdup (description);
-  return holder;
-}
-
-static void
-strings_setup_item_single_line (GtkSignalListItemFactory *factory,
-                                GtkListItem              *item)
-{
-  GtkWidget *box, *image, *title;
-  GtkWidget *checkmark;
-
-  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
-
-  image = gtk_image_new ();
-  title = gtk_label_new ("");
-  gtk_label_set_xalign (GTK_LABEL (title), 0.0);
-  checkmark = gtk_image_new_from_icon_name ("object-select-symbolic");
-
-  gtk_box_append (GTK_BOX (box), image);
-  gtk_box_append (GTK_BOX (box), title);
-  gtk_box_append (GTK_BOX (box), checkmark);
-
-  g_object_set_data (G_OBJECT (item), "title", title);
-  g_object_set_data (G_OBJECT (item), "image", image);
-  g_object_set_data (G_OBJECT (item), "checkmark", checkmark);
-
-  gtk_list_item_set_child (item, box);
-}
-
-static void
-strings_setup_item_full (GtkSignalListItemFactory *factory,
-                         GtkListItem              *item)
-{
-  GtkWidget *box, *box2, *image, *title, *description;
-  GtkWidget *checkmark;
-
-  image = gtk_image_new ();
-  title = gtk_label_new ("");
-  gtk_label_set_xalign (GTK_LABEL (title), 0.0);
-  description = gtk_label_new ("");
-  gtk_label_set_xalign (GTK_LABEL (description), 0.0);
-  gtk_widget_add_css_class (description, "dim-label");
-  checkmark = gtk_image_new_from_icon_name ("object-select-symbolic");
-
-  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
-  box2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
-
-  gtk_box_append (GTK_BOX (box), image);
-  gtk_box_append (GTK_BOX (box), box2);
-  gtk_box_append (GTK_BOX (box2), title);
-  gtk_box_append (GTK_BOX (box2), description);
-  gtk_box_append (GTK_BOX (box), checkmark);
-
-  g_object_set_data (G_OBJECT (item), "title", title);
-  g_object_set_data (G_OBJECT (item), "image", image);
-  g_object_set_data (G_OBJECT (item), "description", description);
-  g_object_set_data (G_OBJECT (item), "checkmark", checkmark);
-
-  gtk_list_item_set_child (item, box);
-}
-
-static void
-selected_item_changed (GtkDropDown *dropdown,
-                       GParamSpec  *pspec,
-                       GtkListItem *item)
-{
-  GtkWidget *checkmark;
-
-  checkmark = g_object_get_data (G_OBJECT (item), "checkmark");
-
-  if (gtk_drop_down_get_selected_item (dropdown) == gtk_list_item_get_item (item))
-    gtk_widget_set_opacity (checkmark, 1.0);
-  else
-    gtk_widget_set_opacity (checkmark, 0.0);
-}
-
-static void
-strings_bind_item (GtkSignalListItemFactory *factory,
-                   GtkListItem              *item,
-                   gpointer                  data)
-{
-  GtkDropDown *dropdown = data;
-  GtkWidget *image, *title, *description;
-  GtkWidget *checkmark;
-  StringHolder *holder;
-  GtkWidget *popup;
-
-  holder = gtk_list_item_get_item (item);
-
-  title = g_object_get_data (G_OBJECT (item), "title");
-  image = g_object_get_data (G_OBJECT (item), "image");
-  description = g_object_get_data (G_OBJECT (item), "description");
-  checkmark = g_object_get_data (G_OBJECT (item), "checkmark");
-
-  gtk_label_set_label (GTK_LABEL (title), holder->title);
-  if (image)
-    {
-      gtk_image_set_from_icon_name (GTK_IMAGE (image), holder->icon);
-      gtk_widget_set_visible (image, holder->icon != NULL);
-    }
-  if (description)
-    {
-      gtk_label_set_label (GTK_LABEL (description), holder->description);
-      gtk_widget_set_visible (description , holder->description != NULL);
-    }
-
-  popup = gtk_widget_get_ancestor (title, GTK_TYPE_POPOVER);
-  if (popup && gtk_widget_is_ancestor (popup, GTK_WIDGET (dropdown)))
-    {
-      gtk_widget_set_visible (checkmark, TRUE);
-      g_signal_connect (dropdown, "notify::selected-item",
-                        G_CALLBACK (selected_item_changed), item);
-      selected_item_changed (dropdown, NULL, item);
-    }
-  else
-    {
-      gtk_widget_set_visible (checkmark, FALSE);
-    }
-}
-
-static void
-strings_unbind_item (GtkSignalListItemFactory *factory,
-                     GtkListItem              *list_item,
-                     gpointer                  data)
-{
-  GtkDropDown *dropdown = data;
-
-  g_signal_handlers_disconnect_by_func (dropdown, selected_item_changed, list_item);
-}
-
-static GtkListItemFactory *
-strings_factory_new (gpointer data, gboolean full)
-{
-  GtkListItemFactory *factory;
-
-  factory = gtk_signal_list_item_factory_new ();
-  if (full)
-    g_signal_connect (factory, "setup", G_CALLBACK (strings_setup_item_full), data);
-  else
-    g_signal_connect (factory, "setup", G_CALLBACK (strings_setup_item_single_line), data);
-  g_signal_connect (factory, "bind", G_CALLBACK (strings_bind_item), data);
-  g_signal_connect (factory, "unbind", G_CALLBACK (strings_unbind_item), data);
-
-  return factory;
-}
-
-static GListModel *
-strings_model_new (const char *const *titles,
-                   const char *const *icons,
-                   const char *const *descriptions)
-{
-  GListStore *store;
-  int i;
-
-  store = g_list_store_new (STRING_TYPE_HOLDER);
-  for (i = 0; titles[i]; i++)
-    {
-      StringHolder *holder = string_holder_new (titles[i],
-                                                icons ? icons[i] : NULL,
-                                                descriptions ? descriptions[i] : NULL);
-      g_list_store_append (store, holder);
-      g_object_unref (holder);
-    }
-
-  return G_LIST_MODEL (store);
-}
-
-static GtkWidget *
-drop_down_new_from_strings (const char *const *titles,
-                            const char *const *icons,
-                            const char *const *descriptions)
-{
-  GtkWidget *widget;
-  GListModel *model;
-  GtkListItemFactory *factory;
-  GtkListItemFactory *list_factory;
-
-  g_return_val_if_fail (titles != NULL, NULL);
-  g_return_val_if_fail (icons == NULL || g_strv_length ((char **)icons) == g_strv_length ((char **)titles), NULL);
-  g_return_val_if_fail (descriptions == NULL || g_strv_length ((char **)icons) == g_strv_length ((char **)descriptions), NULL);
-
-  model = strings_model_new (titles, icons, descriptions);
-  widget = g_object_new (GTK_TYPE_DROP_DOWN,
-                         "model", model,
-                         NULL);
-  g_object_unref (model);
-
-  factory = strings_factory_new (widget, FALSE);
-  if (icons != NULL || descriptions != NULL)
-    list_factory = strings_factory_new (widget, TRUE);
-  else
-    list_factory = NULL;
-
-  g_object_set (widget,
-                "factory", factory,
-                "list-factory", list_factory,
-                NULL);
-
-  g_object_unref (factory);
-  if (list_factory)
-    g_object_unref (list_factory);
-
-  return widget;
-}
-
-static char *
-get_family_name (gpointer item)
-{
-  return g_strdup (pango_font_family_get_name (PANGO_FONT_FAMILY (item)));
-}
-
-static char *
-get_title (gpointer item)
-{
-  return g_strdup (STRING_HOLDER (item)->title);
-}
-
-static char *
-get_file_name (gpointer item)
-{
-  return g_strdup (g_file_info_get_display_name (G_FILE_INFO (item)));
-}
-
-static void
-setup_item (GtkSignalListItemFactory *factory,
-            GtkListItem              *item)
-{
-  GtkWidget *box;
-  GtkWidget *icon;
-  GtkWidget *label;
-
-  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
-  icon = gtk_image_new ();
-  label = gtk_label_new ("");
-  gtk_label_set_xalign (GTK_LABEL (label), 0);
-  gtk_box_append (GTK_BOX (box), icon);
-  gtk_box_append (GTK_BOX (box), label);
-  gtk_list_item_set_child (item, box);
-}
-
-static void
-bind_item (GtkSignalListItemFactory *factory,
-           GtkListItem              *item)
-{
-  MatchObject *match = MATCH_OBJECT (gtk_list_item_get_item (item));
-  GFileInfo *info = G_FILE_INFO (match_object_get_item (match));
-  GtkWidget *box = gtk_list_item_get_child (item);
-  GtkWidget *icon = gtk_widget_get_first_child (box);
-  GtkWidget *label = gtk_widget_get_last_child (box);
-
-  gtk_image_set_from_gicon (GTK_IMAGE (icon), g_file_info_get_icon (info));
-  gtk_label_set_label (GTK_LABEL (label), g_file_info_get_display_name (info));
-}
-
-static void
-setup_highlight_item (GtkSignalListItemFactory *factory,
-                      GtkListItem              *item)
-{
-  GtkWidget *label;
-
-  label = gtk_label_new ("");
-  gtk_label_set_xalign (GTK_LABEL (label), 0);
-  gtk_list_item_set_child (item, label);
-}
-
-static void
-bind_highlight_item (GtkSignalListItemFactory *factory,
-                     GtkListItem              *item)
-{
-  MatchObject *obj;
-  GtkWidget *label;
-  PangoAttrList *attrs;
-  PangoAttribute *attr;
-  const char *str;
-
-  obj = MATCH_OBJECT (gtk_list_item_get_item (item));
-  label = gtk_list_item_get_child (item);
-
-  str = match_object_get_string (obj);
-
-  gtk_label_set_label (GTK_LABEL (label), str);
-  attrs = pango_attr_list_new ();
-  attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
-  attr->start_index = match_object_get_match_start (obj);
-  attr->end_index = match_object_get_match_end (obj);
-  pango_attr_list_insert (attrs, attr);
-  gtk_label_set_attributes (GTK_LABEL (label), attrs);
-  pango_attr_list_unref (attrs);
-}
-
-static void
-match_func (MatchObject *obj,
-            const char  *search,
-            gpointer     user_data)
-{
-  char *tmp1, *tmp2;
-  char *p;
-
-  tmp1 = g_utf8_normalize (match_object_get_string (obj), -1, G_NORMALIZE_ALL);
-  tmp2 = g_utf8_normalize (search, -1, G_NORMALIZE_ALL);
-
-  if ((p = strstr (tmp1, tmp2)) != NULL)
-    match_object_set_match (obj,
-                            p - tmp1,
-                            (p - tmp1) + g_utf8_strlen (search, -1),
-                            1);
-  else
-    match_object_set_match (obj, 0, 0, 0);
-
-  g_free (tmp1);
-  g_free (tmp2);
-}
-
-GtkWidget *
-do_dropdown (GtkWidget *do_widget)
-{
-  static GtkWidget *window = NULL;
-  GtkWidget *button, *box, *spin, *check, *hbox, *label, *entry;
-  GListModel *model;
-  GtkExpression *expression;
-  GtkListItemFactory *factory;
-  const char * const times[] = { "1 minute", "2 minutes", "5 minutes", "20 minutes", NULL };
-  const char * const many_times[] = {
-    "1 minute", "2 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes",
-    "25 minutes", "30 minutes", "35 minutes", "40 minutes", "45 minutes", "50 minutes",
-    "55 minutes", "1 hour", "2 hours", "3 hours", "5 hours", "6 hours", "7 hours",
-    "8 hours", "9 hours", "10 hours", "11 hours", "12 hours", NULL
-  };
-  const char * const device_titles[] = { "Digital Output", "Headphones", "Digital Output", "Analog Output", NULL };
-  const char * const device_icons[] = {  "audio-card-symbolic", "audio-headphones-symbolic", "audio-card-symbolic", "audio-card-symbolic", NULL };
-  const char * const device_descriptions[] = {
-    "Built-in Audio", "Built-in audio", "Thinkpad Tunderbolt 3 Dock USB Audio", "Thinkpad Tunderbolt 3 Dock USB Audio", NULL
-  };
-  char *cwd;
-  GFile *file;
-  GListModel *dir;
-  GtkStringList *strings;
-
-  if (!window)
-    {
-      window = gtk_window_new ();
-      gtk_window_set_display (GTK_WINDOW (window),
-                              gtk_widget_get_display (do_widget));
-      gtk_window_set_title (GTK_WINDOW (window), "Selections");
-      gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
-      g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
-
-      hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 20);
-
-      gtk_widget_set_margin_start (hbox, 20);
-      gtk_widget_set_margin_end (hbox, 20);
-      gtk_widget_set_margin_top (hbox, 20);
-      gtk_widget_set_margin_bottom (hbox, 20);
-      gtk_window_set_child (GTK_WINDOW (window), hbox);
-
-      box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
-      gtk_box_append (GTK_BOX (hbox), box);
-
-      label = gtk_label_new ("Dropdowns");
-      gtk_widget_add_css_class (label, "title-4");
-      gtk_box_append (GTK_BOX (box), label);
-
-      /* A basic dropdown */
-      button = drop_down_new_from_strings (times, NULL, NULL);
-      gtk_box_append (GTK_BOX (box), button);
-
-      /* A dropdown using an expression to obtain strings */
-      button = drop_down_new_from_strings (many_times, NULL, NULL);
-      gtk_drop_down_set_enable_search (GTK_DROP_DOWN (button), TRUE);
-      expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL,
-                                                0, NULL,
-                                                (GCallback)get_title,
-                                                NULL, NULL);
-      gtk_drop_down_set_expression (GTK_DROP_DOWN (button), expression);
-      gtk_expression_unref (expression);
-      gtk_box_append (GTK_BOX (box), button);
-
-      button = gtk_drop_down_new (NULL, NULL);
-
-      model = G_LIST_MODEL (pango_cairo_font_map_get_default ());
-      gtk_drop_down_set_model (GTK_DROP_DOWN (button), model);
-      gtk_drop_down_set_selected (GTK_DROP_DOWN (button), 0);
-
-      expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL,
-                                                0, NULL,
-                                                (GCallback)get_family_name,
-                                                NULL, NULL);
-      gtk_drop_down_set_expression (GTK_DROP_DOWN (button), expression);
-      gtk_expression_unref (expression);
-      gtk_box_append (GTK_BOX (box), button);
-
-      spin = gtk_spin_button_new_with_range (-1, g_list_model_get_n_items (G_LIST_MODEL (model)), 1);
-      gtk_widget_set_halign (spin, GTK_ALIGN_START);
-      gtk_widget_set_margin_start (spin, 20);
-      g_object_bind_property  (button, "selected", spin, "value", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
-      gtk_box_append (GTK_BOX (box), spin);
-
-      check = gtk_check_button_new_with_label ("Enable search");
-      gtk_widget_set_margin_start (check, 20);
-      g_object_bind_property  (button, "enable-search", check, "active", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
-      gtk_box_append (GTK_BOX (box), check);
-
-      g_object_unref (model);
-
-      /* A dropdown with a separate list factory */
-      button = drop_down_new_from_strings (device_titles, device_icons, device_descriptions);
-      gtk_box_append (GTK_BOX (box), button);
-
-      gtk_box_append (GTK_BOX (hbox), gtk_separator_new (GTK_ORIENTATION_VERTICAL));
-
-      box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
-      gtk_box_append (GTK_BOX (hbox), box);
-
-      label = gtk_label_new ("Suggestions");
-      gtk_widget_add_css_class (label, "title-4");
-      gtk_box_append (GTK_BOX (box), label);
-
-      /* A basic suggestion entry */
-      entry = suggestion_entry_new ();
-      g_object_set (entry, "placeholder-text", "Words with T or G…", NULL);
-      strings = gtk_string_list_new ((const char *[]){
-                                     "GNOME",
-                                     "gnominious",
-                                     "Gnomonic projection",
-                                     "total",
-                                     "totally",
-                                     "toto",
-                                     "tottery",
-                                     "totterer",
-                                     "Totten trust",
-                                     "totipotent",
-                                     "totipotency",
-                                     "totemism",
-                                     "totem pole",
-                                     "Totara",
-                                     "totalizer",
-                                     "totalizator",
-                                     "totalitarianism",
-                                     "total parenteral nutrition",
-                                     "total hysterectomy",
-                                     "total eclipse",
-                                     "Totipresence",
-                                     "Totipalmi",
-                                     "Tomboy",
-                                     "zombie",
-                                     NULL});
-      suggestion_entry_set_model (SUGGESTION_ENTRY (entry), G_LIST_MODEL (strings));
-      g_object_unref (strings);
-
-      gtk_box_append (GTK_BOX (box), entry);
-
-      /* A suggestion entry using a custom model, and no filtering */
-      entry = suggestion_entry_new ();
-
-      cwd = g_get_current_dir ();
-      file = g_file_new_for_path (cwd);
-      dir = G_LIST_MODEL (gtk_directory_list_new ("standard::display-name,standard::content-type,standard::icon,standard::size", file));
-      suggestion_entry_set_model (SUGGESTION_ENTRY (entry), dir);
-      g_object_unref (dir);
-      g_object_unref (file);
-      g_free (cwd);
-
-      expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL,
-                                                0, NULL,
-                                                (GCallback)get_file_name,
-                                                NULL, NULL);
-      suggestion_entry_set_expression (SUGGESTION_ENTRY (entry), expression);
-      gtk_expression_unref (expression);
-
-      factory = gtk_signal_list_item_factory_new ();
-      g_signal_connect (factory, "setup", G_CALLBACK (setup_item), NULL);
-      g_signal_connect (factory, "bind", G_CALLBACK (bind_item), NULL);
-
-      suggestion_entry_set_factory (SUGGESTION_ENTRY (entry), factory);
-      g_object_unref (factory);
-
-      suggestion_entry_set_use_filter (SUGGESTION_ENTRY (entry), FALSE);
-      suggestion_entry_set_show_arrow (SUGGESTION_ENTRY (entry), TRUE);
-
-      gtk_box_append (GTK_BOX (box), entry);
-
-      /* A suggestion entry with match highlighting */
-      entry = suggestion_entry_new ();
-      g_object_set (entry, "placeholder-text", "Destination", NULL);
-
-      strings = gtk_string_list_new ((const char *[]){
-                                     "app-mockups",
-                                     "settings-mockups",
-                                     "os-mockups",
-                                     "software-mockups",
-                                     "mocktails",
-                                     NULL});
-      suggestion_entry_set_model (SUGGESTION_ENTRY (entry), G_LIST_MODEL (strings));
-      g_object_unref (strings);
-
-      gtk_box_append (GTK_BOX (box), entry);
-
-      suggestion_entry_set_match_func (SUGGESTION_ENTRY (entry), match_func, NULL, NULL);
-
-      factory = gtk_signal_list_item_factory_new ();
-      g_signal_connect (factory, "setup", G_CALLBACK (setup_highlight_item), NULL);
-      g_signal_connect (factory, "bind", G_CALLBACK (bind_highlight_item), NULL);
-      suggestion_entry_set_factory (SUGGESTION_ENTRY (entry), factory);
-      g_object_unref (factory);
-
-    }
-
-  if (!gtk_widget_get_visible (window))
-    gtk_widget_set_visible (window, TRUE);
-  else
-    gtk_window_destroy (GTK_WINDOW (window));
-
-  return window;
-}
diff --git a/demos/gtk-demo/listview_selections.c b/demos/gtk-demo/listview_selections.c
new file mode 100644 (file)
index 0000000..fbca3b4
--- /dev/null
@@ -0,0 +1,571 @@
+/* Lists/Selections
+ *
+ * The GtkDropDown widget is a modern alternative to GtkComboBox.
+ * It uses list models instead of tree models, and the content is
+ * displayed using widgets instead of cell renderers.
+ *
+ * This example also shows a custom widget that can replace
+ * GtkEntryCompletion or GtkComboBoxText. It is not currently
+ * part of GTK.
+ */
+
+#include <gtk/gtk.h>
+#include "suggestionentry.h"
+
+#define STRING_TYPE_HOLDER (string_holder_get_type ())
+G_DECLARE_FINAL_TYPE (StringHolder, string_holder, STRING, HOLDER, GObject)
+
+struct _StringHolder {
+  GObject parent_instance;
+  char *title;
+  char *icon;
+  char *description;
+};
+
+G_DEFINE_TYPE (StringHolder, string_holder, G_TYPE_OBJECT);
+
+static void
+string_holder_init (StringHolder *holder)
+{
+}
+
+static void
+string_holder_finalize (GObject *object)
+{
+  StringHolder *holder = STRING_HOLDER (object);
+
+  g_free (holder->title);
+  g_free (holder->icon);
+  g_free (holder->description);
+
+  G_OBJECT_CLASS (string_holder_parent_class)->finalize (object);
+}
+
+static void
+string_holder_class_init (StringHolderClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->finalize = string_holder_finalize;
+}
+
+static StringHolder *
+string_holder_new (const char *title, const char *icon, const char *description)
+{
+  StringHolder *holder = g_object_new (STRING_TYPE_HOLDER, NULL);
+  holder->title = g_strdup (title);
+  holder->icon = g_strdup (icon);
+  holder->description = g_strdup (description);
+  return holder;
+}
+
+static void
+strings_setup_item_single_line (GtkSignalListItemFactory *factory,
+                                GtkListItem              *item)
+{
+  GtkWidget *box, *image, *title;
+  GtkWidget *checkmark;
+
+  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
+
+  image = gtk_image_new ();
+  title = gtk_label_new ("");
+  gtk_label_set_xalign (GTK_LABEL (title), 0.0);
+  checkmark = gtk_image_new_from_icon_name ("object-select-symbolic");
+
+  gtk_box_append (GTK_BOX (box), image);
+  gtk_box_append (GTK_BOX (box), title);
+  gtk_box_append (GTK_BOX (box), checkmark);
+
+  g_object_set_data (G_OBJECT (item), "title", title);
+  g_object_set_data (G_OBJECT (item), "image", image);
+  g_object_set_data (G_OBJECT (item), "checkmark", checkmark);
+
+  gtk_list_item_set_child (item, box);
+}
+
+static void
+strings_setup_item_full (GtkSignalListItemFactory *factory,
+                         GtkListItem              *item)
+{
+  GtkWidget *box, *box2, *image, *title, *description;
+  GtkWidget *checkmark;
+
+  image = gtk_image_new ();
+  title = gtk_label_new ("");
+  gtk_label_set_xalign (GTK_LABEL (title), 0.0);
+  description = gtk_label_new ("");
+  gtk_label_set_xalign (GTK_LABEL (description), 0.0);
+  gtk_widget_add_css_class (description, "dim-label");
+  checkmark = gtk_image_new_from_icon_name ("object-select-symbolic");
+
+  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
+  box2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+
+  gtk_box_append (GTK_BOX (box), image);
+  gtk_box_append (GTK_BOX (box), box2);
+  gtk_box_append (GTK_BOX (box2), title);
+  gtk_box_append (GTK_BOX (box2), description);
+  gtk_box_append (GTK_BOX (box), checkmark);
+
+  g_object_set_data (G_OBJECT (item), "title", title);
+  g_object_set_data (G_OBJECT (item), "image", image);
+  g_object_set_data (G_OBJECT (item), "description", description);
+  g_object_set_data (G_OBJECT (item), "checkmark", checkmark);
+
+  gtk_list_item_set_child (item, box);
+}
+
+static void
+selected_item_changed (GtkDropDown *dropdown,
+                       GParamSpec  *pspec,
+                       GtkListItem *item)
+{
+  GtkWidget *checkmark;
+
+  checkmark = g_object_get_data (G_OBJECT (item), "checkmark");
+
+  if (gtk_drop_down_get_selected_item (dropdown) == gtk_list_item_get_item (item))
+    gtk_widget_set_opacity (checkmark, 1.0);
+  else
+    gtk_widget_set_opacity (checkmark, 0.0);
+}
+
+static void
+strings_bind_item (GtkSignalListItemFactory *factory,
+                   GtkListItem              *item,
+                   gpointer                  data)
+{
+  GtkDropDown *dropdown = data;
+  GtkWidget *image, *title, *description;
+  GtkWidget *checkmark;
+  StringHolder *holder;
+  GtkWidget *popup;
+
+  holder = gtk_list_item_get_item (item);
+
+  title = g_object_get_data (G_OBJECT (item), "title");
+  image = g_object_get_data (G_OBJECT (item), "image");
+  description = g_object_get_data (G_OBJECT (item), "description");
+  checkmark = g_object_get_data (G_OBJECT (item), "checkmark");
+
+  gtk_label_set_label (GTK_LABEL (title), holder->title);
+  if (image)
+    {
+      gtk_image_set_from_icon_name (GTK_IMAGE (image), holder->icon);
+      gtk_widget_set_visible (image, holder->icon != NULL);
+    }
+  if (description)
+    {
+      gtk_label_set_label (GTK_LABEL (description), holder->description);
+      gtk_widget_set_visible (description , holder->description != NULL);
+    }
+
+  popup = gtk_widget_get_ancestor (title, GTK_TYPE_POPOVER);
+  if (popup && gtk_widget_is_ancestor (popup, GTK_WIDGET (dropdown)))
+    {
+      gtk_widget_set_visible (checkmark, TRUE);
+      g_signal_connect (dropdown, "notify::selected-item",
+                        G_CALLBACK (selected_item_changed), item);
+      selected_item_changed (dropdown, NULL, item);
+    }
+  else
+    {
+      gtk_widget_set_visible (checkmark, FALSE);
+    }
+}
+
+static void
+strings_unbind_item (GtkSignalListItemFactory *factory,
+                     GtkListItem              *list_item,
+                     gpointer                  data)
+{
+  GtkDropDown *dropdown = data;
+
+  g_signal_handlers_disconnect_by_func (dropdown, selected_item_changed, list_item);
+}
+
+static GtkListItemFactory *
+strings_factory_new (gpointer data, gboolean full)
+{
+  GtkListItemFactory *factory;
+
+  factory = gtk_signal_list_item_factory_new ();
+  if (full)
+    g_signal_connect (factory, "setup", G_CALLBACK (strings_setup_item_full), data);
+  else
+    g_signal_connect (factory, "setup", G_CALLBACK (strings_setup_item_single_line), data);
+  g_signal_connect (factory, "bind", G_CALLBACK (strings_bind_item), data);
+  g_signal_connect (factory, "unbind", G_CALLBACK (strings_unbind_item), data);
+
+  return factory;
+}
+
+static GListModel *
+strings_model_new (const char *const *titles,
+                   const char *const *icons,
+                   const char *const *descriptions)
+{
+  GListStore *store;
+  int i;
+
+  store = g_list_store_new (STRING_TYPE_HOLDER);
+  for (i = 0; titles[i]; i++)
+    {
+      StringHolder *holder = string_holder_new (titles[i],
+                                                icons ? icons[i] : NULL,
+                                                descriptions ? descriptions[i] : NULL);
+      g_list_store_append (store, holder);
+      g_object_unref (holder);
+    }
+
+  return G_LIST_MODEL (store);
+}
+
+static GtkWidget *
+drop_down_new_from_strings (const char *const *titles,
+                            const char *const *icons,
+                            const char *const *descriptions)
+{
+  GtkWidget *widget;
+  GListModel *model;
+  GtkListItemFactory *factory;
+  GtkListItemFactory *list_factory;
+
+  g_return_val_if_fail (titles != NULL, NULL);
+  g_return_val_if_fail (icons == NULL || g_strv_length ((char **)icons) == g_strv_length ((char **)titles), NULL);
+  g_return_val_if_fail (descriptions == NULL || g_strv_length ((char **)icons) == g_strv_length ((char **)descriptions), NULL);
+
+  model = strings_model_new (titles, icons, descriptions);
+  widget = g_object_new (GTK_TYPE_DROP_DOWN,
+                         "model", model,
+                         NULL);
+  g_object_unref (model);
+
+  factory = strings_factory_new (widget, FALSE);
+  if (icons != NULL || descriptions != NULL)
+    list_factory = strings_factory_new (widget, TRUE);
+  else
+    list_factory = NULL;
+
+  g_object_set (widget,
+                "factory", factory,
+                "list-factory", list_factory,
+                NULL);
+
+  g_object_unref (factory);
+  if (list_factory)
+    g_object_unref (list_factory);
+
+  return widget;
+}
+
+static char *
+get_family_name (gpointer item)
+{
+  return g_strdup (pango_font_family_get_name (PANGO_FONT_FAMILY (item)));
+}
+
+static char *
+get_title (gpointer item)
+{
+  return g_strdup (STRING_HOLDER (item)->title);
+}
+
+static char *
+get_file_name (gpointer item)
+{
+  return g_strdup (g_file_info_get_display_name (G_FILE_INFO (item)));
+}
+
+static void
+setup_item (GtkSignalListItemFactory *factory,
+            GtkListItem              *item)
+{
+  GtkWidget *box;
+  GtkWidget *icon;
+  GtkWidget *label;
+
+  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
+  icon = gtk_image_new ();
+  label = gtk_label_new ("");
+  gtk_label_set_xalign (GTK_LABEL (label), 0);
+  gtk_box_append (GTK_BOX (box), icon);
+  gtk_box_append (GTK_BOX (box), label);
+  gtk_list_item_set_child (item, box);
+}
+
+static void
+bind_item (GtkSignalListItemFactory *factory,
+           GtkListItem              *item)
+{
+  MatchObject *match = MATCH_OBJECT (gtk_list_item_get_item (item));
+  GFileInfo *info = G_FILE_INFO (match_object_get_item (match));
+  GtkWidget *box = gtk_list_item_get_child (item);
+  GtkWidget *icon = gtk_widget_get_first_child (box);
+  GtkWidget *label = gtk_widget_get_last_child (box);
+
+  gtk_image_set_from_gicon (GTK_IMAGE (icon), g_file_info_get_icon (info));
+  gtk_label_set_label (GTK_LABEL (label), g_file_info_get_display_name (info));
+}
+
+static void
+setup_highlight_item (GtkSignalListItemFactory *factory,
+                      GtkListItem              *item)
+{
+  GtkWidget *label;
+
+  label = gtk_label_new ("");
+  gtk_label_set_xalign (GTK_LABEL (label), 0);
+  gtk_list_item_set_child (item, label);
+}
+
+static void
+bind_highlight_item (GtkSignalListItemFactory *factory,
+                     GtkListItem              *item)
+{
+  MatchObject *obj;
+  GtkWidget *label;
+  PangoAttrList *attrs;
+  PangoAttribute *attr;
+  const char *str;
+
+  obj = MATCH_OBJECT (gtk_list_item_get_item (item));
+  label = gtk_list_item_get_child (item);
+
+  str = match_object_get_string (obj);
+
+  gtk_label_set_label (GTK_LABEL (label), str);
+  attrs = pango_attr_list_new ();
+  attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
+  attr->start_index = match_object_get_match_start (obj);
+  attr->end_index = match_object_get_match_end (obj);
+  pango_attr_list_insert (attrs, attr);
+  gtk_label_set_attributes (GTK_LABEL (label), attrs);
+  pango_attr_list_unref (attrs);
+}
+
+static void
+match_func (MatchObject *obj,
+            const char  *search,
+            gpointer     user_data)
+{
+  char *tmp1, *tmp2;
+  char *p;
+
+  tmp1 = g_utf8_normalize (match_object_get_string (obj), -1, G_NORMALIZE_ALL);
+  tmp2 = g_utf8_normalize (search, -1, G_NORMALIZE_ALL);
+
+  if ((p = strstr (tmp1, tmp2)) != NULL)
+    match_object_set_match (obj,
+                            p - tmp1,
+                            (p - tmp1) + g_utf8_strlen (search, -1),
+                            1);
+  else
+    match_object_set_match (obj, 0, 0, 0);
+
+  g_free (tmp1);
+  g_free (tmp2);
+}
+
+GtkWidget *
+do_listview_selections (GtkWidget *do_widget)
+{
+  static GtkWidget *window = NULL;
+  GtkWidget *button, *box, *spin, *check, *hbox, *label, *entry;
+  GListModel *model;
+  GtkExpression *expression;
+  GtkListItemFactory *factory;
+  const char * const times[] = { "1 minute", "2 minutes", "5 minutes", "20 minutes", NULL };
+  const char * const many_times[] = {
+    "1 minute", "2 minutes", "5 minutes", "10 minutes", "15 minutes", "20 minutes",
+    "25 minutes", "30 minutes", "35 minutes", "40 minutes", "45 minutes", "50 minutes",
+    "55 minutes", "1 hour", "2 hours", "3 hours", "5 hours", "6 hours", "7 hours",
+    "8 hours", "9 hours", "10 hours", "11 hours", "12 hours", NULL
+  };
+  const char * const device_titles[] = { "Digital Output", "Headphones", "Digital Output", "Analog Output", NULL };
+  const char * const device_icons[] = {  "audio-card-symbolic", "audio-headphones-symbolic", "audio-card-symbolic", "audio-card-symbolic", NULL };
+  const char * const device_descriptions[] = {
+    "Built-in Audio", "Built-in audio", "Thinkpad Tunderbolt 3 Dock USB Audio", "Thinkpad Tunderbolt 3 Dock USB Audio", NULL
+  };
+  char *cwd;
+  GFile *file;
+  GListModel *dir;
+  GtkStringList *strings;
+
+  if (!window)
+    {
+      window = gtk_window_new ();
+      gtk_window_set_display (GTK_WINDOW (window),
+                              gtk_widget_get_display (do_widget));
+      gtk_window_set_title (GTK_WINDOW (window), "Selections");
+      gtk_window_set_resizable (GTK_WINDOW (window), FALSE);
+      g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
+
+      hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 20);
+
+      gtk_widget_set_margin_start (hbox, 20);
+      gtk_widget_set_margin_end (hbox, 20);
+      gtk_widget_set_margin_top (hbox, 20);
+      gtk_widget_set_margin_bottom (hbox, 20);
+      gtk_window_set_child (GTK_WINDOW (window), hbox);
+
+      box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
+      gtk_box_append (GTK_BOX (hbox), box);
+
+      label = gtk_label_new ("Dropdowns");
+      gtk_widget_add_css_class (label, "title-4");
+      gtk_box_append (GTK_BOX (box), label);
+
+      /* A basic dropdown */
+      button = drop_down_new_from_strings (times, NULL, NULL);
+      gtk_box_append (GTK_BOX (box), button);
+
+      /* A dropdown using an expression to obtain strings */
+      button = drop_down_new_from_strings (many_times, NULL, NULL);
+      gtk_drop_down_set_enable_search (GTK_DROP_DOWN (button), TRUE);
+      expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL,
+                                                0, NULL,
+                                                (GCallback)get_title,
+                                                NULL, NULL);
+      gtk_drop_down_set_expression (GTK_DROP_DOWN (button), expression);
+      gtk_expression_unref (expression);
+      gtk_box_append (GTK_BOX (box), button);
+
+      button = gtk_drop_down_new (NULL, NULL);
+
+      model = G_LIST_MODEL (pango_cairo_font_map_get_default ());
+      gtk_drop_down_set_model (GTK_DROP_DOWN (button), model);
+      gtk_drop_down_set_selected (GTK_DROP_DOWN (button), 0);
+
+      expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL,
+                                                0, NULL,
+                                                (GCallback)get_family_name,
+                                                NULL, NULL);
+      gtk_drop_down_set_expression (GTK_DROP_DOWN (button), expression);
+      gtk_expression_unref (expression);
+      gtk_box_append (GTK_BOX (box), button);
+
+      spin = gtk_spin_button_new_with_range (-1, g_list_model_get_n_items (G_LIST_MODEL (model)), 1);
+      gtk_widget_set_halign (spin, GTK_ALIGN_START);
+      gtk_widget_set_margin_start (spin, 20);
+      g_object_bind_property  (button, "selected", spin, "value", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+      gtk_box_append (GTK_BOX (box), spin);
+
+      check = gtk_check_button_new_with_label ("Enable search");
+      gtk_widget_set_margin_start (check, 20);
+      g_object_bind_property  (button, "enable-search", check, "active", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+      gtk_box_append (GTK_BOX (box), check);
+
+      g_object_unref (model);
+
+      /* A dropdown with a separate list factory */
+      button = drop_down_new_from_strings (device_titles, device_icons, device_descriptions);
+      gtk_box_append (GTK_BOX (box), button);
+
+      gtk_box_append (GTK_BOX (hbox), gtk_separator_new (GTK_ORIENTATION_VERTICAL));
+
+      box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
+      gtk_box_append (GTK_BOX (hbox), box);
+
+      label = gtk_label_new ("Suggestions");
+      gtk_widget_add_css_class (label, "title-4");
+      gtk_box_append (GTK_BOX (box), label);
+
+      /* A basic suggestion entry */
+      entry = suggestion_entry_new ();
+      g_object_set (entry, "placeholder-text", "Words with T or G…", NULL);
+      strings = gtk_string_list_new ((const char *[]){
+                                     "GNOME",
+                                     "gnominious",
+                                     "Gnomonic projection",
+                                     "total",
+                                     "totally",
+                                     "toto",
+                                     "tottery",
+                                     "totterer",
+                                     "Totten trust",
+                                     "totipotent",
+                                     "totipotency",
+                                     "totemism",
+                                     "totem pole",
+                                     "Totara",
+                                     "totalizer",
+                                     "totalizator",
+                                     "totalitarianism",
+                                     "total parenteral nutrition",
+                                     "total hysterectomy",
+                                     "total eclipse",
+                                     "Totipresence",
+                                     "Totipalmi",
+                                     "Tomboy",
+                                     "zombie",
+                                     NULL});
+      suggestion_entry_set_model (SUGGESTION_ENTRY (entry), G_LIST_MODEL (strings));
+      g_object_unref (strings);
+
+      gtk_box_append (GTK_BOX (box), entry);
+
+      /* A suggestion entry using a custom model, and no filtering */
+      entry = suggestion_entry_new ();
+
+      cwd = g_get_current_dir ();
+      file = g_file_new_for_path (cwd);
+      dir = G_LIST_MODEL (gtk_directory_list_new ("standard::display-name,standard::content-type,standard::icon,standard::size", file));
+      suggestion_entry_set_model (SUGGESTION_ENTRY (entry), dir);
+      g_object_unref (dir);
+      g_object_unref (file);
+      g_free (cwd);
+
+      expression = gtk_cclosure_expression_new (G_TYPE_STRING, NULL,
+                                                0, NULL,
+                                                (GCallback)get_file_name,
+                                                NULL, NULL);
+      suggestion_entry_set_expression (SUGGESTION_ENTRY (entry), expression);
+      gtk_expression_unref (expression);
+
+      factory = gtk_signal_list_item_factory_new ();
+      g_signal_connect (factory, "setup", G_CALLBACK (setup_item), NULL);
+      g_signal_connect (factory, "bind", G_CALLBACK (bind_item), NULL);
+
+      suggestion_entry_set_factory (SUGGESTION_ENTRY (entry), factory);
+      g_object_unref (factory);
+
+      suggestion_entry_set_use_filter (SUGGESTION_ENTRY (entry), FALSE);
+      suggestion_entry_set_show_arrow (SUGGESTION_ENTRY (entry), TRUE);
+
+      gtk_box_append (GTK_BOX (box), entry);
+
+      /* A suggestion entry with match highlighting */
+      entry = suggestion_entry_new ();
+      g_object_set (entry, "placeholder-text", "Destination", NULL);
+
+      strings = gtk_string_list_new ((const char *[]){
+                                     "app-mockups",
+                                     "settings-mockups",
+                                     "os-mockups",
+                                     "software-mockups",
+                                     "mocktails",
+                                     NULL});
+      suggestion_entry_set_model (SUGGESTION_ENTRY (entry), G_LIST_MODEL (strings));
+      g_object_unref (strings);
+
+      gtk_box_append (GTK_BOX (box), entry);
+
+      suggestion_entry_set_match_func (SUGGESTION_ENTRY (entry), match_func, NULL, NULL);
+
+      factory = gtk_signal_list_item_factory_new ();
+      g_signal_connect (factory, "setup", G_CALLBACK (setup_highlight_item), NULL);
+      g_signal_connect (factory, "bind", G_CALLBACK (bind_highlight_item), NULL);
+      suggestion_entry_set_factory (SUGGESTION_ENTRY (entry), factory);
+      g_object_unref (factory);
+
+    }
+
+  if (!gtk_widget_get_visible (window))
+    gtk_widget_set_visible (window, TRUE);
+  else
+    gtk_window_destroy (GTK_WINDOW (window));
+
+  return window;
+}
index 8cd428a9697b1d32cb6de3335a00177b2fecfa2f..c03beb39409d18db2e93801b7d6c6d5c4eced859 100644 (file)
@@ -55,7 +55,7 @@ demos = files([
   'listview_colors.c',
   'listview_filebrowser.c',
   'listview_minesweeper.c',
-  'dropdown.c',
+  'listview_selections.c',
   'listview_settings.c',
   'listview_ucd.c',
   'listview_weather.c',
index 8a0b3aa0825076a2d5292f217f64635fe8d5ebdc..f8a28c8375b657b8d5ae8553118c2163e5ff4ce1 100644 (file)
@@ -849,7 +849,7 @@ suggestion_entry_init (SuggestionEntry *self)
       GtkCssProvider *provider;
 
       provider = gtk_css_provider_new ();
-      gtk_css_provider_load_from_resource (provider, "/dropdown/suggestionentry.css");
+      gtk_css_provider_load_from_resource (provider, "/listview_selections/suggestionentry.css");
       gtk_style_context_add_provider_for_display (gdk_display_get_default (),
                                                   GTK_STYLE_PROVIDER (provider),
                                                   800);