[gtk/wip/otte/listview] gtk-demo: Add a listview demo



commit dfd280acd0225181a80b884d9afd2ee00232da80
Author: Matthias Clasen <mclasen redhat com>
Date:   Sat Jan 5 21:13:26 2019 -0500

    gtk-demo: Add a listview demo
    
    Duplicate the listbox demo with GtkListView,
    and give both demos a "more messages" button,
    to demonstrate scalability.

 demos/gtk-demo/demo.gresource.xml |   6 +
 demos/gtk-demo/listbox.c          | 183 +++++++--------------
 demos/gtk-demo/listview.c         | 323 ++++++++++++++++++++++++++++++++++++++
 demos/gtk-demo/listview.ui        | 302 +++++++++++++++++++++++++++++++++++
 demos/gtk-demo/meson.build        |   3 +-
 demos/gtk-demo/message.c          |  79 ++++++++++
 demos/gtk-demo/message.h          |  34 ++++
 7 files changed, 803 insertions(+), 127 deletions(-)
---
diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml
index bbf603e1ad..4d47a5f626 100644
--- a/demos/gtk-demo/demo.gresource.xml
+++ b/demos/gtk-demo/demo.gresource.xml
@@ -179,6 +179,7 @@
     <file>links.c</file>
     <file>listbox.c</file>
     <file>list_store.c</file>
+    <file>listview.c</file>
     <file>markup.c</file>
     <file>menus.c</file>
     <file>modelbutton.c</file>
@@ -223,6 +224,11 @@
     <file>messages.txt</file>
     <file>apple-red.png</file>
   </gresource>
+  <gresource prefix="/listview">
+    <file>listview.ui</file>
+    <file>messages.txt</file>
+    <file>apple-red.png</file>
+  </gresource>
   <gresource prefix="/popover">
     <file>popover.ui</file>
   </gresource>
diff --git a/demos/gtk-demo/listbox.c b/demos/gtk-demo/listbox.c
index c1dc9ef6a7..e09bd097e2 100644
--- a/demos/gtk-demo/listbox.c
+++ b/demos/gtk-demo/listbox.c
@@ -9,16 +9,11 @@
 #include <stdlib.h>
 #include <string.h>
 
+#include "message.h"
+
 static GdkPixbuf *avatar_pixbuf_other;
 static GtkWidget *window = NULL;
 
-#define GTK_TYPE_MESSAGE                 (gtk_message_get_type ())
-#define GTK_MESSAGE(message)             (G_TYPE_CHECK_INSTANCE_CAST ((message), GTK_TYPE_MESSAGE, 
GtkMessage))
-#define GTK_MESSAGE_CLASS(klass)                 (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_MESSAGE, 
GtkMessageClass))
-#define GTK_IS_MESSAGE(message)                  (G_TYPE_CHECK_INSTANCE_TYPE ((message), GTK_TYPE_MESSAGE))
-#define GTK_IS_MESSAGE_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_MESSAGE))
-#define GTK_MESSAGE_GET_CLASS(obj)         (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_MESSAGE, 
GtkMessageClass))
-
 #define GTK_TYPE_MESSAGE_ROW             (gtk_message_row_get_type ())
 #define GTK_MESSAGE_ROW(message_row)             (G_TYPE_CHECK_INSTANCE_CAST ((message_row), 
GTK_TYPE_MESSAGE_ROW, GtkMessageRow))
 #define GTK_MESSAGE_ROW_CLASS(klass)             (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_MESSAGE_ROW, 
GtkMessageRowClass))
@@ -26,33 +21,10 @@ static GtkWidget *window = NULL;
 #define GTK_IS_MESSAGE_ROW_CLASS(klass)          (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_MESSAGE_ROW))
 #define GTK_MESSAGE_ROW_GET_CLASS(obj)         (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_MESSAGE_ROW, 
GtkMessageRowClass))
 
-typedef struct _GtkMessage   GtkMessage;
-typedef struct _GtkMessageClass  GtkMessageClass;
 typedef struct _GtkMessageRow   GtkMessageRow;
 typedef struct _GtkMessageRowClass  GtkMessageRowClass;
 typedef struct _GtkMessageRowPrivate  GtkMessageRowPrivate;
 
-
-struct _GtkMessage
-{
-  GObject parent;
-
-  guint id;
-  char *sender_name;
-  char *sender_nick;
-  char *message;
-  gint64 time;
-  guint reply_to;
-  char *resent_by;
-  int n_favorites;
-  int n_reshares;
-};
-
-struct _GtkMessageClass
-{
-  GObjectClass parent_class;
-};
-
 struct _GtkMessageRow
 {
   GtkListBoxRow parent;
@@ -83,84 +55,10 @@ struct _GtkMessageRowPrivate
   GtkButton *expand_button;
 };
 
-GType      gtk_message_get_type  (void) G_GNUC_CONST;
 GType      gtk_message_row_get_type  (void) G_GNUC_CONST;
 
-G_DEFINE_TYPE (GtkMessage, gtk_message, G_TYPE_OBJECT);
-
-static void
-gtk_message_finalize (GObject *obj)
-{
-  GtkMessage *msg = GTK_MESSAGE (obj);
-
-  g_free (msg->sender_name);
-  g_free (msg->sender_nick);
-  g_free (msg->message);
-  g_free (msg->resent_by);
-
-  G_OBJECT_CLASS (gtk_message_parent_class)->finalize (obj);
-}
-static void
-gtk_message_class_init (GtkMessageClass *klass)
-{
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  object_class->finalize = gtk_message_finalize;
-}
-
-static void
-gtk_message_init (GtkMessage *msg)
-{
-}
-
-static void
-gtk_message_parse (GtkMessage *msg, const char *str)
-{
-  char **strv;
-  int i;
-
-  strv = g_strsplit (str, "|", 0);
-
-  i = 0;
-  msg->id = strtol (strv[i++], NULL, 10);
-  msg->sender_name = g_strdup (strv[i++]);
-  msg->sender_nick = g_strdup (strv[i++]);
-  msg->message = g_strdup (strv[i++]);
-  msg->time = strtol (strv[i++], NULL, 10);
-  if (strv[i])
-    {
-      msg->reply_to = strtol (strv[i++], NULL, 10);
-      if (strv[i])
-        {
-          if (*strv[i])
-            msg->resent_by = g_strdup (strv[i]);
-          i++;
-          if (strv[i])
-            {
-              msg->n_favorites = strtol (strv[i++], NULL, 10);
-              if (strv[i])
-                {
-                  msg->n_reshares = strtol (strv[i++], NULL, 10);
-                }
-
-            }
-        }
-    }
-
-  g_strfreev (strv);
-}
-
-static GtkMessage *
-gtk_message_new (const char *str)
-{
-  GtkMessage *msg;
-  msg = g_object_new (gtk_message_get_type (), NULL);
-  gtk_message_parse (msg, str);
-  return msg;
-}
-
 G_DEFINE_TYPE_WITH_PRIVATE (GtkMessageRow, gtk_message_row, GTK_TYPE_LIST_BOX_ROW);
 
-
 static void
 gtk_message_row_update (GtkMessageRow *row)
 {
@@ -333,16 +231,49 @@ row_activated (GtkListBox *listbox, GtkListBoxRow *row)
   gtk_message_row_expand (GTK_MESSAGE_ROW (row));
 }
 
-GtkWidget *
-do_listbox (GtkWidget *do_widget)
+static void
+update_count (GtkListBox *listbox, GtkLabel *label)
+{
+  GList *children = gtk_container_get_children (GTK_CONTAINER (listbox));
+  guint n_items = g_list_length (children);
+  g_list_free (children);
+
+  char *text = g_strdup_printf ("%u rows", n_items);
+  gtk_label_set_label (label, text);
+  g_free (text);
+}
+
+static GtkWidget *header_label;
+
+static void
+add_more (GtkListBox *listbox)
 {
-  GtkWidget *scrolled, *listbox, *vbox, *label;
-  GtkMessage *message;
-  GtkMessageRow *row;
   GBytes *data;
   char **lines;
   int i;
 
+  data = g_resources_lookup_data ("/listbox/messages.txt", 0, NULL);
+  lines = g_strsplit (g_bytes_get_data (data, NULL), "\n", 0);
+
+  for (i = 0; lines[i] != NULL && *lines[i]; i++)
+    {
+      GtkMessage *message = gtk_message_new (lines[i]);
+      GtkMessageRow *row = gtk_message_row_new (message);
+      gtk_container_add (GTK_CONTAINER (listbox), GTK_WIDGET (row));
+    }
+
+  g_strfreev (lines);
+  g_bytes_unref (data);
+
+  update_count (listbox, GTK_LABEL (header_label));
+}
+
+GtkWidget *
+do_listbox (GtkWidget *do_widget)
+{
+  GtkWidget *scrolled, *listbox, *vbox, *label;
+  GtkWidget *header, *more;
+
   if (!window)
     {
       avatar_pixbuf_other = gdk_pixbuf_new_from_resource_at_scale ("/listbox/apple-red.png", 32, 32, FALSE, 
NULL);
@@ -350,15 +281,27 @@ do_listbox (GtkWidget *do_widget)
       window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
       gtk_window_set_display (GTK_WINDOW (window),
                               gtk_widget_get_display (do_widget));
-      gtk_window_set_title (GTK_WINDOW (window), "List Box");
-      gtk_window_set_default_size (GTK_WINDOW (window),
-                                   400, 600);
+      gtk_window_set_default_size (GTK_WINDOW (window), 400, 600);
 
       /* NULL window variable when window is closed */
       g_signal_connect (window, "destroy",
                         G_CALLBACK (gtk_widget_destroyed),
                         &window);
 
+      listbox = gtk_list_box_new ();
+
+      header = gtk_header_bar_new ();
+      gtk_header_bar_set_show_title_buttons (GTK_HEADER_BAR (header), TRUE);
+      gtk_header_bar_set_title (GTK_HEADER_BAR (header), "List View");
+      header_label = gtk_label_new ("");
+      gtk_header_bar_pack_start (GTK_HEADER_BAR (header), header_label);
+      more = gtk_button_new_from_icon_name ("list-add");
+
+      g_signal_connect_swapped (more, "clicked", G_CALLBACK (add_more), listbox);
+
+      gtk_header_bar_pack_start (GTK_HEADER_BAR (header), more);
+      gtk_window_set_titlebar (GTK_WINDOW (window), header);
+
       vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
       gtk_container_add (GTK_CONTAINER (window), vbox);
       label = gtk_label_new ("Messages from Gtk+ and friends");
@@ -367,26 +310,14 @@ do_listbox (GtkWidget *do_widget)
       gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_NEVER, 
GTK_POLICY_AUTOMATIC);
       gtk_widget_set_vexpand (scrolled, TRUE);
       gtk_box_pack_start (GTK_BOX (vbox), scrolled);
-      listbox = gtk_list_box_new ();
       gtk_container_add (GTK_CONTAINER (scrolled), listbox);
 
       gtk_list_box_set_sort_func (GTK_LIST_BOX (listbox), (GtkListBoxSortFunc)gtk_message_row_sort, listbox, 
NULL);
       gtk_list_box_set_activate_on_single_click (GTK_LIST_BOX (listbox), FALSE);
       g_signal_connect (listbox, "row-activated", G_CALLBACK (row_activated), NULL);
 
-      data = g_resources_lookup_data ("/listbox/messages.txt", 0, NULL);
-      lines = g_strsplit (g_bytes_get_data (data, NULL), "\n", 0);
-
-      for (i = 0; lines[i] != NULL && *lines[i]; i++)
-        {
-          message = gtk_message_new (lines[i]);
-          row = gtk_message_row_new (message);
-          gtk_widget_show (GTK_WIDGET (row));
-          gtk_container_add (GTK_CONTAINER (listbox), GTK_WIDGET (row));
-        }
-
-      g_strfreev (lines);
-      g_bytes_unref (data);
+      add_more (listbox);
+      update_count (listbox, header_label);
     }
 
   if (!gtk_widget_get_visible (window))
diff --git a/demos/gtk-demo/listview.c b/demos/gtk-demo/listview.c
new file mode 100644
index 0000000000..900b04eb1b
--- /dev/null
+++ b/demos/gtk-demo/listview.c
@@ -0,0 +1,323 @@
+/* List View
+ *
+ * GtkListView allows lists with complicated layouts, using
+ * models to hold the data, and creating rows on demand.
+ */
+
+#include <gtk/gtk.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "message.h"
+
+static GdkPixbuf *avatar_pixbuf_other;
+static GtkWidget *window = NULL;
+
+#define GTK_TYPE_MSG_ROW                 (gtk_msg_row_get_type ())
+#define GTK_MSG_ROW(msg_row)             (G_TYPE_CHECK_INSTANCE_CAST ((msg_row), GTK_TYPE_MSG_ROW, 
GtkMsgRow))
+#define GTK_MSG_ROW_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_MSG_ROW, 
GtkMsgRowClass))
+#define GTK_IS_MSG_ROW(msg_row)                  (G_TYPE_CHECK_INSTANCE_TYPE ((msg_row), GTK_TYPE_MSG_ROW))
+#define GTK_IS_MSG_ROW_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_MSG_ROW))
+#define GTK_MSG_ROW_GET_CLASS(obj)        (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_MSG_ROW, 
GtkMsgRowClass))
+
+typedef struct _GtkMsgRow   GtkMsgRow;
+typedef struct _GtkMsgRowClass  GtkMsgRowClass;
+
+struct _GtkMsgRow
+{
+  GtkBin parent;
+
+  GtkMessage *message;
+  GtkRevealer *details_revealer;
+  GtkImage *avatar_image;
+  GtkWidget *extra_buttons_box;
+  GtkLabel *content_label;
+  GtkLabel *source_name;
+  GtkLabel *source_nick;
+  GtkLabel *short_time_label;
+  GtkLabel *detailed_time_label;
+  GtkBox *resent_box;
+  GtkLinkButton *resent_by_button;
+  GtkLabel *n_favorites_label;
+  GtkLabel *n_reshares_label;
+  GtkButton *expand_button;
+};
+
+struct _GtkMsgRowClass
+{
+  GtkBinClass parent_class;
+};
+
+static GType      gtk_msg_row_get_type  (void) G_GNUC_CONST;
+
+G_DEFINE_TYPE (GtkMsgRow, gtk_msg_row, GTK_TYPE_BIN);
+
+static void
+gtk_msg_row_update (GtkMsgRow *row)
+{
+  GDateTime *t;
+  char *s;
+
+  gtk_label_set_text (row->source_name, row->message->sender_name);
+  gtk_label_set_text (row->source_nick, row->message->sender_nick);
+  gtk_label_set_text (row->content_label, row->message->message);
+  t = g_date_time_new_from_unix_utc (row->message->time);
+  s = g_date_time_format (t, "%e %b %y");
+  gtk_label_set_text (row->short_time_label, s);
+  g_free (s);
+  s = g_date_time_format (t, "%X - %e %b %Y");
+  gtk_label_set_text (row->detailed_time_label, s);
+  g_free (s);
+  g_date_time_unref (t);
+
+  gtk_widget_set_visible (GTK_WIDGET(row->n_favorites_label),
+                          row->message->n_favorites != 0);
+  s = g_strdup_printf ("<b>%d</b>\nFavorites", row->message->n_favorites);
+  gtk_label_set_markup (row->n_favorites_label, s);
+  g_free (s);
+
+  gtk_widget_set_visible (GTK_WIDGET(row->n_reshares_label),
+                          row->message->n_reshares != 0);
+  s = g_strdup_printf ("<b>%d</b>\nReshares", row->message->n_reshares);
+  gtk_label_set_markup (row->n_reshares_label, s);
+  g_free (s);
+
+  gtk_widget_set_visible (GTK_WIDGET (row->resent_box), row->message->resent_by != NULL);
+  if (row->message->resent_by)
+    gtk_button_set_label (GTK_BUTTON (row->resent_by_button), row->message->resent_by);
+
+  if (strcmp (row->message->sender_nick, "@GTKtoolkit") == 0)
+    {
+      gtk_image_set_from_icon_name (row->avatar_image, "gtk3-demo");
+      gtk_image_set_icon_size (row->avatar_image, GTK_ICON_SIZE_LARGE);
+    }
+  else
+    gtk_image_set_from_pixbuf (row->avatar_image, avatar_pixbuf_other);
+
+}
+
+static void
+gtk_msg_row_expand (GtkMsgRow *row)
+{
+  gboolean expand;
+
+  expand = !gtk_revealer_get_reveal_child (row->details_revealer);
+
+  gtk_revealer_set_reveal_child (row->details_revealer, expand);
+  if (expand)
+    gtk_button_set_label (row->expand_button, "Hide");
+  else
+    gtk_button_set_label (row->expand_button, "Expand");
+}
+
+static void
+expand_clicked (GtkMsgRow *row,
+                GtkButton *button)
+{
+  gtk_msg_row_expand (row);
+}
+
+static void
+reshare_clicked (GtkMsgRow *row,
+                 GtkButton *button)
+{
+  row->message->n_reshares++;
+  gtk_msg_row_update (row);
+
+}
+
+static void
+favorite_clicked (GtkMsgRow *row,
+                  GtkButton *button)
+{
+  row->message->n_favorites++;
+  gtk_msg_row_update (row);
+}
+
+static void
+gtk_msg_row_state_flags_changed (GtkWidget    *widget,
+                                 GtkStateFlags previous_state_flags)
+{
+  GtkMsgRow *row = GTK_MSG_ROW (widget);
+  GtkStateFlags flags;
+
+  flags = gtk_widget_get_state_flags (widget);
+
+  gtk_widget_set_visible (row->extra_buttons_box,
+                          flags & (GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_SELECTED));
+
+  GTK_WIDGET_CLASS (gtk_msg_row_parent_class)->state_flags_changed (widget, previous_state_flags);
+}
+
+static void
+gtk_msg_row_finalize (GObject *obj)
+{
+  G_OBJECT_CLASS (gtk_msg_row_parent_class)->finalize(obj);
+}
+
+static void
+gtk_msg_row_class_init (GtkMsgRowClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gtk_msg_row_finalize;
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/listview/listview.ui");
+  gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, content_label);
+  gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, source_name);
+  gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, source_nick);
+  gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, short_time_label);
+  gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, detailed_time_label);
+  gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, extra_buttons_box);
+  gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, details_revealer);
+  gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, avatar_image);
+  gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, resent_box);
+  gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, resent_by_button);
+  gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, n_reshares_label);
+  gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, n_favorites_label);
+  gtk_widget_class_bind_template_child (widget_class, GtkMsgRow, expand_button);
+  gtk_widget_class_bind_template_callback (widget_class, expand_clicked);
+  gtk_widget_class_bind_template_callback (widget_class, reshare_clicked);
+  gtk_widget_class_bind_template_callback (widget_class, favorite_clicked);
+
+  widget_class->state_flags_changed = gtk_msg_row_state_flags_changed;
+}
+
+static void
+row_activated (GtkGestureMultiPress *gesture,
+               int n_press,
+               double x,
+               double y,
+               GtkMsgRow *row)
+{
+  if (n_press == 2)
+    gtk_msg_row_expand (row);
+}
+
+static void
+gtk_msg_row_init (GtkMsgRow *row)
+{
+  GtkGesture *double_click;
+
+  gtk_widget_init_template (GTK_WIDGET (row));
+
+  double_click = gtk_gesture_multi_press_new ();
+  g_signal_connect (double_click, "pressed", G_CALLBACK (row_activated), row);
+  gtk_widget_add_controller (GTK_WIDGET (row), GTK_EVENT_CONTROLLER (double_click));
+}
+
+static int
+gtk_message_sort (gconstpointer a, gconstpointer b, gpointer data)
+{
+  return ((const GtkMessage *)b)->time - ((const GtkMessage *)a)->time;
+}
+
+static void
+bind_msg_row (GObject *list_item, GParamSpec *pspec, gpointer data)
+{
+  GtkMessage *message = (GtkMessage *)gtk_list_item_get_item (GTK_LIST_ITEM (list_item));
+  GtkMsgRow *row = (GtkMsgRow *) gtk_bin_get_child (GTK_BIN (list_item));
+
+  row->message = message;
+  if (message)
+    gtk_msg_row_update (row);
+}
+
+static void
+setup_row (GtkListItem *item,
+           gpointer data)
+{
+  g_signal_connect (item, "notify::item", G_CALLBACK (bind_msg_row), data);
+  gtk_container_add (GTK_CONTAINER (item), g_object_new (gtk_msg_row_get_type (), NULL));
+}
+
+static void
+update_count (GListModel *model, guint position, guint removed, guint added, GtkLabel *label)
+{
+  guint n_items = g_list_model_get_n_items (model);
+  char *text = g_strdup_printf ("%u rows", n_items);
+  gtk_label_set_label (label, text);
+  g_free (text);
+}
+
+static void
+add_more (GListModel *model)
+{
+  GBytes *data;
+  char **lines;
+  int i;
+
+  data = g_resources_lookup_data ("/listview/messages.txt", 0, NULL);
+  lines = g_strsplit (g_bytes_get_data (data, NULL), "\n", 0);
+
+  for (i = 0; lines[i] != NULL && *lines[i]; i++)
+    {
+      GtkMessage *message = gtk_message_new (lines[i]);
+      g_list_store_append (G_LIST_STORE (model), message);
+    }
+
+  g_strfreev (lines);
+  g_bytes_unref (data);
+}
+
+GtkWidget *
+do_listview (GtkWidget *do_widget)
+{
+  GtkWidget *scrolled, *listview, *vbox, *label;
+  GtkWidget *header, *header_label, *more;
+  GListModel *model;
+
+  if (!window)
+    {
+      avatar_pixbuf_other = gdk_pixbuf_new_from_resource_at_scale ("/listbox/apple-red.png", 32, 32, FALSE, 
NULL);
+
+      model = G_LIST_MODEL (g_list_store_new (gtk_message_get_type ()));
+
+      window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+      gtk_window_set_display (GTK_WINDOW (window),
+                              gtk_widget_get_display (do_widget));
+      gtk_window_set_default_size (GTK_WINDOW (window), 400, 600);
+
+      /* NULL window variable when window is closed */
+      g_signal_connect (window, "destroy",
+                        G_CALLBACK (gtk_widget_destroyed),
+                        &window);
+
+      header = gtk_header_bar_new ();
+      gtk_header_bar_set_show_title_buttons (GTK_HEADER_BAR (header), TRUE);
+      gtk_header_bar_set_title (GTK_HEADER_BAR (header), "List View");
+      header_label = gtk_label_new ("");
+      gtk_header_bar_pack_start (GTK_HEADER_BAR (header), header_label);
+      more = gtk_button_new_from_icon_name ("list-add");
+
+      g_signal_connect_swapped (more, "clicked", G_CALLBACK (add_more), model);
+      g_signal_connect (model, "items-changed", G_CALLBACK (update_count), header_label);
+
+      gtk_header_bar_pack_start (GTK_HEADER_BAR (header), more);
+      gtk_window_set_titlebar (GTK_WINDOW (window), header);
+
+      vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+      gtk_container_add (GTK_CONTAINER (window), vbox);
+      label = gtk_label_new ("Messages from Gtk+ and friends");
+      gtk_box_pack_start (GTK_BOX (vbox), label);
+      scrolled = gtk_scrolled_window_new (NULL, NULL);
+      gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), GTK_POLICY_NEVER, 
GTK_POLICY_AUTOMATIC);
+      gtk_widget_set_vexpand (scrolled, TRUE);
+      gtk_box_pack_start (GTK_BOX (vbox), scrolled);
+      listview = gtk_list_view_new ();
+      gtk_container_add (GTK_CONTAINER (scrolled), listview);
+
+      gtk_list_view_set_functions (GTK_LIST_VIEW (listview), setup_row, NULL, NULL, NULL);
+      gtk_list_view_set_model (GTK_LIST_VIEW (listview), G_LIST_MODEL (gtk_sort_list_model_new (model, 
gtk_message_sort, NULL, NULL)));
+
+      add_more (model);
+    }
+
+  if (!gtk_widget_get_visible (window))
+    gtk_widget_show (window);
+  else
+    gtk_widget_destroy (window);
+
+  return window;
+}
diff --git a/demos/gtk-demo/listview.ui b/demos/gtk-demo/listview.ui
new file mode 100644
index 0000000000..08d836cf2b
--- /dev/null
+++ b/demos/gtk-demo/listview.ui
@@ -0,0 +1,302 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface domain="gtk40">
+  <!-- interface-requires gtk+ 3.10 -->
+  <!-- interface-requires gtkdemo 3.10 -->
+  <object class="GtkMenu" id="menu1">
+    <child>
+      <object class="GtkMenuItem" id="menuitem1">
+        <property name="label" translatable="yes">Email message</property>
+        <property name="use-underline">1</property>
+      </object>
+    </child>
+    <child>
+      <object class="GtkMenuItem" id="menuitem2">
+        <property name="label" translatable="yes">Embed message</property>
+        <property name="use-underline">1</property>
+      </object>
+    </child>
+  </object>
+  <template class="GtkMsgRow" parent="GtkBin">
+    <child>
+      <object class="GtkGrid" id="grid1">
+        <property name="hexpand">1</property>
+        <child>
+          <object class="GtkImage" id="avatar_image">
+            <property name="width-request">32</property>
+            <property name="height-request">32</property>
+            <property name="halign">center</property>
+            <property name="valign">start</property>
+            <property name="margin-top">8</property>
+            <property name="margin-bottom">8</property>
+            <property name="margin-start">8</property>
+            <property name="margin-end">8</property>
+            <property name="icon-name">image-missing</property>
+          </object>
+          <packing>
+            <property name="left-attach">0</property>
+            <property name="top-attach">0</property>
+            <property name="height">5</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box1">
+            <property name="hexpand">1</property>
+            <property name="baseline-position">top</property>
+            <child>
+              <object class="GtkButton" id="button2">
+                <property name="can-focus">1</property>
+                <property name="receives-default">1</property>
+                <property name="valign">baseline</property>
+                <property name="relief">none</property>
+                <child>
+                  <object class="GtkLabel" id="source_name">
+                    <property name="valign">baseline</property>
+                    <property name="label" translatable="0">Username</property>
+                    <attributes>
+                      <attribute name="weight" value="bold"/>
+                    </attributes>
+                  </object>
+                </child>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel" id="source_nick">
+                <property name="valign">baseline</property>
+                <property name="label" translatable="0">@nick</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="short_time_label">
+                <property name="valign">baseline</property>
+                <property name="label" translatable="yes">38m</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="pack-type">end</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="left-attach">1</property>
+            <property name="top-attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="content_label">
+            <property name="halign">start</property>
+            <property name="valign">start</property>
+            <property name="xalign">0</property>
+            <property name="yalign">0</property>
+            <property name="label" translatable="0">Message</property>
+            <property name="wrap">1</property>
+            <accessibility>
+              <role type="static"/>
+            </accessibility>
+          </object>
+          <packing>
+            <property name="left-attach">1</property>
+            <property name="top-attach">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="resent_box">
+            <child>
+              <object class="GtkImage" id="image2">
+                <property name="icon-name">media-playlist-repeat</property>
+              </object>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label4">
+                <property name="label" translatable="yes">Resent by</property>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLinkButton" id="resent_by_button">
+                <property name="label" translatable="0">reshareer</property>
+                <property name="can-focus">1</property>
+                <property name="receives-default">1</property>
+                <property name="relief">none</property>
+                <property name="uri">http://www.gtk.org</property>
+              </object>
+              <packing>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="left-attach">1</property>
+            <property name="top-attach">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="box3">
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkButton" id="expand_button">
+                <property name="label" translatable="yes">Expand</property>
+                <property name="can-focus">1</property>
+                <property name="receives-default">1</property>
+                <property name="relief">none</property>
+                <signal name="clicked" handler="expand_clicked" swapped="yes"/>
+              </object>
+            </child>
+            <child>
+              <object class="GtkBox" id="extra_buttons_box">
+                <property name="visible">0</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkButton" id="reply-button">
+                    <property name="label" translatable="yes">Reply</property>
+                    <property name="can-focus">1</property>
+                    <property name="receives-default">1</property>
+                    <property name="relief">none</property>
+                  </object>
+                </child>
+                <child>
+                  <object class="GtkButton" id="reshare-button">
+                    <property name="label" translatable="yes">Reshare</property>
+                    <property name="can-focus">1</property>
+                    <property name="receives-default">1</property>
+                    <property name="relief">none</property>
+                    <signal name="clicked" handler="reshare_clicked" swapped="yes"/>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkButton" id="favorite-buttton">
+                    <property name="label" translatable="yes">Favorite</property>
+                    <property name="can-focus">1</property>
+                    <property name="receives-default">1</property>
+                    <property name="relief">none</property>
+                    <signal name="clicked" handler="favorite_clicked" swapped="yes"/>
+                  </object>
+                  <packing>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkMenuButton" id="more-button">
+                    <property name="can-focus">1</property>
+                    <property name="receives-default">1</property>
+                    <property name="relief">none</property>
+                    <property name="popup">menu1</property>
+                    <child>
+                      <object class="GtkLabel" id="label7">
+                        <property name="label" translatable="yes">More...</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">3</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="left-attach">1</property>
+            <property name="top-attach">3</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkRevealer" id="details_revealer">
+            <child>
+              <object class="GtkBox" id="box5">
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkBox" id="box7">
+                    <property name="margin-top">2</property>
+                    <property name="margin-bottom">2</property>
+                    <property name="spacing">8</property>
+                    <child>
+                      <object class="GtkFrame" id="frame1">
+                        <property name="shadow-type">none</property>
+                        <child>
+                          <object class="GtkLabel" id="n_reshares_label">
+                            <property name="label" translatable="0">&lt;b&gt;2&lt;/b&gt;
+Reshares</property>
+                            <property name="use-markup">1</property>
+                          </object>
+                        </child>
+                        <child type="label_item"/>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkFrame" id="frame2">
+                        <property name="shadow-type">none</property>
+                        <child>
+                          <object class="GtkLabel" id="n_favorites_label">
+                            <property name="label" translatable="0">&lt;b&gt;2&lt;/b&gt;
+FAVORITES</property>
+                            <property name="use-markup">1</property>
+                          </object>
+                        </child>
+                        <child type="label_item"/>
+                      </object>
+                      <packing>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="box6">
+                    <child>
+                      <object class="GtkLabel" id="detailed_time_label">
+                        <property name="label" translatable="0">4:25 AM - 14 Jun 13 </property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="button5">
+                        <property name="label" translatable="yes">Details</property>
+                        <property name="can-focus">1</property>
+                        <property name="receives-default">1</property>
+                        <property name="relief">none</property>
+                        <style>
+                          <class name="dim-label"/>
+                        </style>
+                      </object>
+                      <packing>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="left-attach">1</property>
+            <property name="top-attach">4</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build
index 93c2902575..b03ce6f7bb 100644
--- a/demos/gtk-demo/meson.build
+++ b/demos/gtk-demo/meson.build
@@ -36,6 +36,7 @@ demos = files([
   'infobar.c',
   'links.c',
   'listbox.c',
+  'listview.c',
   'flowbox.c',
   'list_store.c',
   'markup.c',
@@ -76,7 +77,7 @@ demos = files([
 
 gtkdemo_deps = [ libgtk_dep, ]
 
-extra_demo_sources = files(['main.c', 'gtkfishbowl.c', 'fontplane.c', 'gtkgears.c', 'puzzlepiece.c'])
+extra_demo_sources = files(['main.c', 'gtkfishbowl.c', 'fontplane.c', 'gtkgears.c', 'puzzlepiece.c', 
'message.c'])
 
 if harfbuzz_dep.found() and pangoft_dep.found()
   demos += files('font_features.c')
diff --git a/demos/gtk-demo/message.c b/demos/gtk-demo/message.c
new file mode 100644
index 0000000000..64566d4c17
--- /dev/null
+++ b/demos/gtk-demo/message.c
@@ -0,0 +1,79 @@
+
+#include <gtk/gtk.h>
+#include "message.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+G_DEFINE_TYPE (GtkMessage, gtk_message, G_TYPE_OBJECT);
+
+static void
+gtk_message_finalize (GObject *obj)
+{
+  GtkMessage *msg = GTK_MESSAGE (obj);
+
+  g_free (msg->sender_name);
+  g_free (msg->sender_nick);
+  g_free (msg->message);
+  g_free (msg->resent_by);
+
+  G_OBJECT_CLASS (gtk_message_parent_class)->finalize (obj);
+}
+
+static void
+gtk_message_class_init (GtkMessageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  object_class->finalize = gtk_message_finalize;
+}
+
+static void
+gtk_message_init (GtkMessage *msg)
+{
+}
+
+static void
+gtk_message_parse (GtkMessage *msg, const char *str)
+{
+  char **strv;
+  int i;
+
+  strv = g_strsplit (str, "|", 0);
+
+  i = 0;
+  msg->id = strtol (strv[i++], NULL, 10);
+  msg->sender_name = g_strdup (strv[i++]);
+  msg->sender_nick = g_strdup (strv[i++]);
+  msg->message = g_strdup (strv[i++]);
+  msg->time = strtol (strv[i++], NULL, 10);
+  if (strv[i])
+    {
+      msg->reply_to = strtol (strv[i++], NULL, 10);
+      if (strv[i])
+        {
+          if (*strv[i])
+            msg->resent_by = g_strdup (strv[i]);
+          i++;
+          if (strv[i])
+            {
+              msg->n_favorites = strtol (strv[i++], NULL, 10);
+              if (strv[i])
+                {
+                  msg->n_reshares = strtol (strv[i++], NULL, 10);
+                }
+
+            }
+        }
+    }
+
+  g_strfreev (strv);
+}
+
+GtkMessage *
+gtk_message_new (const char *str)
+{
+  GtkMessage *msg;
+  msg = g_object_new (gtk_message_get_type (), NULL);
+  gtk_message_parse (msg, str);
+  return msg;
+}
diff --git a/demos/gtk-demo/message.h b/demos/gtk-demo/message.h
new file mode 100644
index 0000000000..66ff37588f
--- /dev/null
+++ b/demos/gtk-demo/message.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#define GTK_TYPE_MESSAGE                 (gtk_message_get_type ())
+#define GTK_MESSAGE(message)             (G_TYPE_CHECK_INSTANCE_CAST ((message), GTK_TYPE_MESSAGE, 
GtkMessage))
+#define GTK_MESSAGE_CLASS(klass)                 (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_MESSAGE, 
GtkMessageClass))
+#define GTK_IS_MESSAGE(message)                  (G_TYPE_CHECK_INSTANCE_TYPE ((message), GTK_TYPE_MESSAGE))
+#define GTK_IS_MESSAGE_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_MESSAGE))
+#define GTK_MESSAGE_GET_CLASS(obj)         (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_MESSAGE, 
GtkMessageClass))
+
+typedef struct _GtkMessage   GtkMessage;
+typedef struct _GtkMessageClass  GtkMessageClass;
+
+struct _GtkMessage
+{
+  GObject parent;
+
+  guint id;
+  char *sender_name;
+  char *sender_nick;
+  char *message;
+  gint64 time;
+  guint reply_to;
+  char *resent_by;
+  int n_favorites;
+  int n_reshares;
+};
+
+struct _GtkMessageClass
+{
+  GObjectClass parent_class;
+};
+
+GType        gtk_message_get_type  (void) G_GNUC_CONST;
+GtkMessage * gtk_message_new       (const char *str);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]