/* Lists/Characters
 *
 * This demo shows a multi-column representation of some parts
 * of the Unicode Character Database, or UCD. It also demonstrates
 * the use of sections with headings to group items.
 *
 * The dataset used here has 33 796 items.
 */

#include <gtk/gtk.h>
#include "script-names.h"
#include "unicode-names.h"

#define UCD_TYPE_ITEM (ucd_item_get_type ())
G_DECLARE_FINAL_TYPE (UcdItem, ucd_item, UCD, ITEM, GObject)

struct _UcdItem
{
  GObject parent_instance;
  gunichar codepoint;
  const char *name;
  GUnicodeScript script;
};

struct _UcdItemClass
{
  GObjectClass parent_class;
};

enum
{
  PROP_CODEPOINT = 1,
  PROP_NAME,
  PROP_SCRIPT,
  NUM_PROPERTIES,
};

G_DEFINE_TYPE (UcdItem, ucd_item, G_TYPE_OBJECT)

static void
ucd_item_init (UcdItem *item)
{
}

static void
ucd_item_get_property (GObject    *object,
                       guint       prop_id,
                       GValue     *value,
                       GParamSpec *pspec)
{
  UcdItem *item = UCD_ITEM (object);

  switch (prop_id)
    {
    case PROP_CODEPOINT:
      g_value_set_uint (value, item->codepoint);
      break;

    case PROP_NAME:
      g_value_set_string (value, item->name);
      break;

    case PROP_SCRIPT:
      g_value_set_uint (value, item->script);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
ucd_item_class_init (UcdItemClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);

  object_class->get_property = ucd_item_get_property;

  g_object_class_install_property (object_class,
                                   PROP_CODEPOINT,
                                   g_param_spec_uint ("codepoint", NULL, NULL,
                                                      0, G_MAXUINT, 0,
                                                      G_PARAM_READABLE));

  g_object_class_install_property (object_class,
                                   PROP_NAME,
                                   g_param_spec_string ("name", NULL, NULL,
                                                        NULL,
                                                        G_PARAM_READABLE));

  g_object_class_install_property (object_class,
                                   PROP_SCRIPT,
                                   g_param_spec_uint ("script", NULL, NULL,
                                                      0, G_MAXUINT, 0,
                                                      G_PARAM_READABLE));
}

static UcdItem *
ucd_item_new (gunichar    codepoint,
              const char *name)
{
  UcdItem *item;

  item = g_object_new (UCD_TYPE_ITEM, NULL);

  item->codepoint = codepoint;
  item->name = name;
  item->script = g_unichar_get_script (codepoint);

  return item;
}

static gunichar
ucd_item_get_codepoint (UcdItem *item)
{
  return item->codepoint;
}

static const char *
ucd_item_get_name (UcdItem *item)
{
  return item->name;
}

static GUnicodeScript
ucd_item_get_script (UcdItem *item)
{
  return item->script;
}

static GListModel *
ucd_model_new (void)
{
  GBytes *bytes;
  GVariant *v;
  GVariantIter *iter;
  GListStore *store;
  guint u;
  char *name;
  GtkExpression *expression;
  GtkNumericSorter *sorter;
  GtkSortListModel *sort;

  bytes = g_resources_lookup_data ("/listview_ucd_data/ucdnames.data", 0, NULL);
  v = g_variant_ref_sink (g_variant_new_from_bytes (G_VARIANT_TYPE ("a(us)"), bytes, TRUE));

  iter = g_variant_iter_new (v);

  store = g_list_store_new (G_TYPE_OBJECT);
  while (g_variant_iter_next (iter, "(u&s)", &u, &name))
    {
      if (u == 0)
        continue;

      UcdItem *item = ucd_item_new (u, name);
      g_list_store_append (store, item);
      g_object_unref (item);
    }

  g_variant_iter_free (iter);
  g_variant_unref (v);
  g_bytes_unref (bytes);

  expression = gtk_property_expression_new (ucd_item_get_type (),
                                            NULL,
                                            "codepoint");
  sorter = gtk_numeric_sorter_new (expression);
  sort = gtk_sort_list_model_new (G_LIST_MODEL (store), GTK_SORTER (sorter));

  expression = gtk_property_expression_new (ucd_item_get_type (),
                                            NULL,
                                            "script");
  sorter = gtk_numeric_sorter_new (expression);
  gtk_sort_list_model_set_section_sorter (sort, GTK_SORTER (sorter));
  g_object_unref (sorter);

  return G_LIST_MODEL (sort);
}

static void
setup_centered_label (GtkSignalListItemFactory *factory,
                      GObject                  *listitem)
{
  GtkWidget *label;
  label = gtk_inscription_new ("");
  gtk_list_item_set_child (GTK_LIST_ITEM (listitem), label);
}

static void
setup_label (GtkSignalListItemFactory *factory,
             GObject                  *listitem)
{
  GtkWidget *label;
  label = gtk_inscription_new ("");
  gtk_inscription_set_xalign (GTK_INSCRIPTION (label), 0);
  gtk_list_item_set_child (GTK_LIST_ITEM (listitem), label);
}

static void
setup_ellipsizing_label (GtkSignalListItemFactory *factory,
                         GObject                  *listitem)
{
  GtkWidget *label;
  label = gtk_inscription_new ("");
  gtk_inscription_set_xalign (GTK_INSCRIPTION (label), 0);
  gtk_inscription_set_text_overflow (GTK_INSCRIPTION (label), GTK_INSCRIPTION_OVERFLOW_ELLIPSIZE_END);
  gtk_inscription_set_nat_chars (GTK_INSCRIPTION (label), 20);
  gtk_list_item_set_child (GTK_LIST_ITEM (listitem), label);
}

static void
bind_codepoint (GtkSignalListItemFactory *factory,
                GObject                  *listitem)
{
  GtkWidget *label;
  GObject *item;
  gunichar codepoint;
  char buffer[16] = { 0, };

  label = gtk_list_item_get_child (GTK_LIST_ITEM (listitem));
  item = gtk_list_item_get_item (GTK_LIST_ITEM (listitem));
  codepoint = ucd_item_get_codepoint (UCD_ITEM (item));

  g_snprintf (buffer, 10, "%#06x", codepoint);
  gtk_inscription_set_text (GTK_INSCRIPTION (label), buffer);
}

static void
bind_char (GtkSignalListItemFactory *factory,
           GObject                  *listitem)
{
  GtkWidget *label;
  GObject *item;
  gunichar codepoint;
  char buffer[16] = { 0, };

  label = gtk_list_item_get_child (GTK_LIST_ITEM (listitem));
  item = gtk_list_item_get_item (GTK_LIST_ITEM (listitem));
  codepoint = ucd_item_get_codepoint (UCD_ITEM (item));

  if (g_unichar_isprint (codepoint))
    g_unichar_to_utf8 (codepoint, buffer);

  gtk_inscription_set_text (GTK_INSCRIPTION (label), buffer);
}

static void
bind_name (GtkSignalListItemFactory *factory,
           GObject                  *listitem)
{
  GtkWidget *label;
  GObject *item;
  const char *name;

  label = gtk_list_item_get_child (GTK_LIST_ITEM (listitem));
  item = gtk_list_item_get_item (GTK_LIST_ITEM (listitem));
  name = ucd_item_get_name (UCD_ITEM (item));

  gtk_inscription_set_text (GTK_INSCRIPTION (label), name);
}

static void
bind_type (GtkSignalListItemFactory *factory,
           GObject                  *listitem)
{
  GtkWidget *label;
  GObject *item;
  gunichar codepoint;

  label = gtk_list_item_get_child (GTK_LIST_ITEM (listitem));
  item = gtk_list_item_get_item (GTK_LIST_ITEM (listitem));
  codepoint = ucd_item_get_codepoint (UCD_ITEM (item));

  gtk_inscription_set_text (GTK_INSCRIPTION (label), get_unicode_type_name (g_unichar_type (codepoint)));
}

static void
bind_break_type (GtkSignalListItemFactory *factory,
                 GObject                  *listitem)
{
  GtkWidget *label;
  GObject *item;
  gunichar codepoint;

  label = gtk_list_item_get_child (GTK_LIST_ITEM (listitem));
  item = gtk_list_item_get_item (GTK_LIST_ITEM (listitem));
  codepoint = ucd_item_get_codepoint (UCD_ITEM (item));

  gtk_inscription_set_text (GTK_INSCRIPTION (label), get_break_type_name (g_unichar_break_type (codepoint)));
}

static void
bind_combining_class (GtkSignalListItemFactory *factory,
                      GObject                  *listitem)
{
  GtkWidget *label;
  GObject *item;
  gunichar codepoint;

  label = gtk_list_item_get_child (GTK_LIST_ITEM (listitem));
  item = gtk_list_item_get_item (GTK_LIST_ITEM (listitem));
  codepoint = ucd_item_get_codepoint (UCD_ITEM (item));

  gtk_inscription_set_text (GTK_INSCRIPTION (label), get_combining_class_name (g_unichar_combining_class (codepoint)));
}

static void
setup_header (GtkSignalListItemFactory *factory,
              GObject                  *listitem)
{
  GtkWidget *label;

  label = gtk_inscription_new ("");
  gtk_widget_add_css_class (label, "heading");
  gtk_widget_set_margin_start (label, 20);
  gtk_widget_set_margin_end (label, 20);
  gtk_widget_set_margin_top (label, 10);
  gtk_widget_set_margin_bottom (label, 10);
  gtk_inscription_set_xalign (GTK_INSCRIPTION (label), 0);
  gtk_list_header_set_child (GTK_LIST_HEADER (listitem), label);
}

static void
bind_header (GtkSignalListItemFactory *factory,
             GObject                  *listitem)
{
  GtkWidget *label;
  GObject *item;
  GUnicodeScript script;

  label = gtk_list_header_get_child (GTK_LIST_HEADER (listitem));
  item = gtk_list_header_get_item (GTK_LIST_HEADER (listitem));
  script = ucd_item_get_script (UCD_ITEM (item));

  gtk_inscription_set_text (GTK_INSCRIPTION (label), get_script_name (script));
}

static void
selection_changed (GObject    *object,
                   GParamSpec *pspec,
                   GtkWidget  *label)
{
  UcdItem *item;
  guint codepoint;
  char buffer[16] = { 0, };

  item = gtk_single_selection_get_selected_item (GTK_SINGLE_SELECTION (object));
  codepoint = ucd_item_get_codepoint (item);

  if (g_unichar_isprint (codepoint))
    g_unichar_to_utf8 (codepoint, buffer);

  gtk_label_set_text (GTK_LABEL (label), buffer);
}

GtkWidget *
create_ucd_view (GtkWidget *label)
{
  GtkWidget *cv;
  GListModel *ucd_model;
  GtkSingleSelection *selection;
  GtkListItemFactory *factory;
  GtkColumnViewColumn *column;

  ucd_model = ucd_model_new ();

  selection = gtk_single_selection_new (ucd_model);
  gtk_single_selection_set_autoselect (selection, TRUE);
  gtk_single_selection_set_can_unselect (selection, FALSE);
  if (label)
    g_signal_connect (selection, "notify::selected", G_CALLBACK (selection_changed), label);

  cv = gtk_column_view_new (GTK_SELECTION_MODEL (selection));
  gtk_column_view_set_show_column_separators (GTK_COLUMN_VIEW (cv), TRUE);

  factory = gtk_signal_list_item_factory_new ();
  g_signal_connect (factory, "setup", G_CALLBACK (setup_centered_label), NULL);
  g_signal_connect (factory, "bind", G_CALLBACK (bind_codepoint), NULL);
  column = gtk_column_view_column_new ("Codepoint", factory);
  gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);
  g_object_unref (column);

  factory = gtk_signal_list_item_factory_new ();
  g_signal_connect (factory, "setup", G_CALLBACK (setup_centered_label), NULL);
  g_signal_connect (factory, "bind", G_CALLBACK (bind_char), NULL);
  column = gtk_column_view_column_new ("Char", factory);
  gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);
  g_object_unref (column);

  factory = gtk_signal_list_item_factory_new ();
  g_signal_connect (factory, "setup", G_CALLBACK (setup_ellipsizing_label), NULL);
  g_signal_connect (factory, "bind", G_CALLBACK (bind_name), NULL);
  column = gtk_column_view_column_new ("Name", factory);
  gtk_column_view_column_set_resizable (column, TRUE);
  gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);
  g_object_unref (column);

  factory = gtk_signal_list_item_factory_new ();
  g_signal_connect (factory, "setup", G_CALLBACK (setup_ellipsizing_label), NULL);
  g_signal_connect (factory, "bind", G_CALLBACK (bind_type), NULL);
  column = gtk_column_view_column_new ("Type", factory);
  gtk_column_view_column_set_resizable (column, TRUE);
  gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);
  g_object_unref (column);

  factory = gtk_signal_list_item_factory_new ();
  g_signal_connect (factory, "setup", G_CALLBACK (setup_ellipsizing_label), NULL);
  g_signal_connect (factory, "bind", G_CALLBACK (bind_break_type), NULL);
  column = gtk_column_view_column_new ("Break Type", factory);
  gtk_column_view_column_set_resizable (column, TRUE);
  gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);
  g_object_unref (column);

  factory = gtk_signal_list_item_factory_new ();
  g_signal_connect (factory, "setup", G_CALLBACK (setup_label), NULL);
  g_signal_connect (factory, "bind", G_CALLBACK (bind_combining_class), NULL);
  column = gtk_column_view_column_new ("Combining Class", factory);
  gtk_column_view_column_set_resizable (column, TRUE);
  gtk_column_view_append_column (GTK_COLUMN_VIEW (cv), column);
  g_object_unref (column);

  factory = gtk_signal_list_item_factory_new ();
  g_signal_connect (factory, "setup", G_CALLBACK (setup_header), NULL);
  g_signal_connect (factory, "bind", G_CALLBACK (bind_header), NULL);
  gtk_column_view_set_header_factory (GTK_COLUMN_VIEW (cv), factory);
  g_object_unref (factory);

  return cv;
}

static GtkWidget *window;

static void
remove_provider (gpointer data)
{
  GtkStyleProvider *provider = GTK_STYLE_PROVIDER (data);

  gtk_style_context_remove_provider_for_display (gdk_display_get_default (), provider);
  g_object_unref (provider);
}

GtkWidget *
do_listview_ucd (GtkWidget *do_widget)
{
  if (window == NULL)
    {
      GtkWidget *listview, *sw;
      GtkWidget *box, *label;
      GtkCssProvider *provider;

      window = gtk_window_new ();
      gtk_window_set_default_size (GTK_WINDOW (window), 800, 400);
      gtk_window_set_title (GTK_WINDOW (window), "Characters");
      gtk_window_set_display (GTK_WINDOW (window),
                              gtk_widget_get_display (do_widget));
      g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);

      box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
      label = gtk_label_new ("");
      gtk_label_set_width_chars (GTK_LABEL (label), 2);
      gtk_widget_add_css_class (label, "enormous");
      provider = gtk_css_provider_new ();
      gtk_css_provider_load_from_string (provider, "label.enormous { font-size: 80px; }");
      gtk_style_context_add_provider_for_display (gdk_display_get_default (), GTK_STYLE_PROVIDER (provider), 800);
      gtk_widget_set_hexpand (label, TRUE);
      gtk_box_append (GTK_BOX (box), label);

      sw = gtk_scrolled_window_new ();
      gtk_scrolled_window_set_propagate_natural_width (GTK_SCROLLED_WINDOW (sw), TRUE);
      listview = create_ucd_view (label);
      gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), listview);
      gtk_box_prepend (GTK_BOX (box), sw);
      gtk_window_set_child (GTK_WINDOW (window), box);

      g_object_set_data_full (G_OBJECT (window), "provider", provider, remove_provider);
    }

  if (!gtk_widget_get_visible (window))
    gtk_widget_set_visible (window, TRUE);
  else
    gtk_window_destroy (GTK_WINDOW (window));

  return window;
}

