[gtk/wip/otte/listview: 141/155] gtk-demo: Add a Clocks demo
- From: Benjamin Otte <otte src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/otte/listview: 141/155] gtk-demo: Add a Clocks demo
- Date: Tue, 4 Feb 2020 19:47:00 +0000 (UTC)
commit 1a165f0e4d932d97f82ec27e87bb9b455cd4ebc0
Author: Benjamin Otte <otte redhat com>
Date: Sun Nov 24 08:07:33 2019 +0100
gtk-demo: Add a Clocks demo
This demo is meant to showcase expressions.
It also needs the fixes in glib 2.64 to work properly.
demos/gtk-demo/demo.gresource.xml | 1 +
demos/gtk-demo/listview_clocks.c | 488 ++++++++++++++++++++++++++++++++++++++
demos/gtk-demo/meson.build | 3 +-
3 files changed, 491 insertions(+), 1 deletion(-)
---
diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml
index e5f8debce1..1ffdbb5a27 100644
--- a/demos/gtk-demo/demo.gresource.xml
+++ b/demos/gtk-demo/demo.gresource.xml
@@ -209,6 +209,7 @@
<file>links.c</file>
<file>listbox.c</file>
<file>listview_applauncher.c</file>
+ <file>listview_clocks.c</file>
<file>listview_filebrowser.c</file>
<file>listview_minesweeper.c</file>
<file>listview_settings.c</file>
diff --git a/demos/gtk-demo/listview_clocks.c b/demos/gtk-demo/listview_clocks.c
new file mode 100644
index 0000000000..b463839051
--- /dev/null
+++ b/demos/gtk-demo/listview_clocks.c
@@ -0,0 +1,488 @@
+/* Lists/Clocks
+ *
+ * This demo displays the time in different timezones.
+ *
+ * The goal is to show how to set up expressions that track changes
+ * in objects and make them update widgets.
+ *
+ * For that, we create a GtkClock object that updates its time every
+ * second and then use various ways to display that time.
+ *
+ * Typically, this will be done using GtkBuilder .ui files with the
+ * help of the <binding> tag, but this demo shows the code that runs
+ * behind that.
+ */
+
+#include <gtk/gtk.h>
+
+#define GTK_TYPE_CLOCK (gtk_clock_get_type ())
+G_DECLARE_FINAL_TYPE (GtkClock, gtk_clock, GTK, CLOCK, GObject)
+
+/* This is our object. It's just a timezone */
+typedef struct _GtkClock GtkClock;
+struct _GtkClock
+{
+ GObject parent_instance;
+
+ /* We allow this to be NULL for the local timezone */
+ GTimeZone *timezone;
+ /* Name of the location we're displaying time for */
+ char *location;
+};
+
+enum {
+ PROP_0,
+ PROP_LOCATION,
+ PROP_TIME,
+ PROP_TIMEZONE,
+
+ N_PROPS
+};
+
+/* This function returns the current time in the clock's timezone.
+ * Note that this returns a new object every time, so we need to
+ * remember to unref it after use. */
+static GDateTime *
+gtk_clock_get_time (GtkClock *clock)
+{
+ if (clock->timezone)
+ return g_date_time_new_now (clock->timezone);
+ else
+ return g_date_time_new_now_local ();
+}
+
+/* Here, we implement the functionality required by the GdkPaintable interface.
+ * This way we have a trivial way to display an analog clock.
+ * It also allows demonstrating how to directly use objects in the listview
+ * later by making this object do something interesting. */
+static void
+gtk_clock_snapshot (GdkPaintable *paintable,
+ GdkSnapshot *snapshot,
+ double width,
+ double height)
+{
+ GtkClock *self = GTK_CLOCK (paintable);
+ GDateTime *time;
+ GskRoundedRect outline;
+
+#define BLACK ((GdkRGBA) { 0, 0, 0, 1 })
+
+ /* save/restore() is necessary so we can undo the transforms we start
+ * out with. */
+ gtk_snapshot_save (snapshot);
+
+ /* First, we move the (0, 0) point to the center of the area so
+ * we can draw everything relative to it. */
+ gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (width / 2, height / 2));
+ /* Next we scale it, so that we can pretend that the clock is
+ * 100px in size. That way, we don't need to do any complicated
+ * math later.
+ * We use MIN() here so that we use the smaller dimension for sizing.
+ * That way we don't overdraw but keep the aspect ratio. */
+ gtk_snapshot_scale (snapshot, MIN (width, height) / 100.0, MIN (width, height) / 100.0);
+ /* Now we have a circle with diameter 100px (and radius 50px) that
+ * has its (0, 0) point at the center.
+ * Let's draw a simple clock into it. */
+
+ time = gtk_clock_get_time (self);
+
+ /* First, draw a circle. This is a neat little trick to draw a circle
+ * without requiring Cairo. */
+ gsk_rounded_rect_init_from_rect (&outline, &GRAPHENE_RECT_INIT(-50, -50, 100, 100), 50);
+ gtk_snapshot_append_border (snapshot,
+ &outline,
+ (float[4]) { 4, 4, 4, 4 },
+ (GdkRGBA [4]) { BLACK, BLACK, BLACK, BLACK });
+
+ /* Next, draw the hour hand.
+ * We do this using tranforms again: Instead of computing where the angle points
+ * to, we just rotate everything and then draw the hand as if if was :00.
+ * We don't even need to care about am/pm here because rotations just work. */
+ gtk_snapshot_save (snapshot);
+ gtk_snapshot_rotate (snapshot, 30 * g_date_time_get_hour (time) + 0.5 * g_date_time_get_minute (time));
+ gsk_rounded_rect_init_from_rect (&outline, &GRAPHENE_RECT_INIT(-2, -23, 4, 25), 2);
+ gtk_snapshot_push_rounded_clip (snapshot, &outline);
+ gtk_snapshot_append_color (snapshot, &BLACK, &outline.bounds);
+ gtk_snapshot_pop (snapshot);
+ gtk_snapshot_restore (snapshot);
+
+ /* And the same as above for the minute hand. Just make this one longer
+ * so people can tell the hands apart. */
+ gtk_snapshot_save (snapshot);
+ gtk_snapshot_rotate (snapshot, 6 * g_date_time_get_minute (time));
+ gsk_rounded_rect_init_from_rect (&outline, &GRAPHENE_RECT_INIT(-2, -43, 4, 45), 2);
+ gtk_snapshot_push_rounded_clip (snapshot, &outline);
+ gtk_snapshot_append_color (snapshot, &BLACK, &outline.bounds);
+ gtk_snapshot_pop (snapshot);
+ gtk_snapshot_restore (snapshot);
+
+ /* and finally, the second indicator. */
+ gtk_snapshot_save (snapshot);
+ gtk_snapshot_rotate (snapshot, 6 * g_date_time_get_second (time));
+ gsk_rounded_rect_init_from_rect (&outline, &GRAPHENE_RECT_INIT(-2, -43, 4, 10), 2);
+ gtk_snapshot_push_rounded_clip (snapshot, &outline);
+ gtk_snapshot_append_color (snapshot, &BLACK, &outline.bounds);
+ gtk_snapshot_pop (snapshot);
+ gtk_snapshot_restore (snapshot);
+
+ /* And finally, don't forget to restore the initial save() that we did for
+ * the initial transformations. */
+ gtk_snapshot_restore (snapshot);
+
+ g_date_time_unref (time);
+}
+
+/* Our desired size is 100px. That sounds okay for an analog clock */
+static int
+gtk_clock_get_intrinsic_width (GdkPaintable *paintable)
+{
+ return 100;
+}
+
+static int
+gtk_clock_get_intrinsic_height (GdkPaintable *paintable)
+{
+ return 100;
+}
+
+/* Initialize the paintable interface. This way we turn our clock objects
+ * into objects that can be drawn.
+ * There are more functions to this interface to define desired size,
+ * but this is enough.
+ */
+static void
+gtk_clock_paintable_init (GdkPaintableInterface *iface)
+{
+ iface->snapshot = gtk_clock_snapshot;
+ iface->get_intrinsic_width = gtk_clock_get_intrinsic_width;
+ iface->get_intrinsic_height = gtk_clock_get_intrinsic_height;
+}
+
+/* Finally, we define the type. The important part is adding the paintable
+ * interface, so GTK knows that this object can indeed be drawm.
+ */
+G_DEFINE_TYPE_WITH_CODE (GtkClock, gtk_clock, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
+ gtk_clock_paintable_init))
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+static void
+gtk_clock_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkClock *self = GTK_CLOCK (object);
+
+ switch (property_id)
+ {
+ case PROP_LOCATION:
+ g_value_set_string (value, self->location);
+ break;
+
+ case PROP_TIME:
+ g_value_take_boxed (value, gtk_clock_get_time (self));
+ break;
+
+ case PROP_TIMEZONE:
+ g_value_set_boxed (value, self->timezone);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_clock_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkClock *self = GTK_CLOCK (object);
+
+ switch (property_id)
+ {
+ case PROP_LOCATION:
+ self->location = g_value_dup_string (value);
+ break;
+
+ case PROP_TIMEZONE:
+ self->timezone = g_value_dup_boxed (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+/* This is the list of all the ticking clocks */
+static GSList *ticking_clocks = NULL;
+/* This is the id of the timeout source that is updating all ticking clocks */
+static guint ticking_clock_id = 0;
+
+/* Every second, this function is called to tell everybody that the
+ * clocks are ticking.
+ */
+static gboolean
+gtk_clock_tick (gpointer unused)
+{
+ GSList *l;
+
+ for (l = ticking_clocks; l; l = l->next)
+ {
+ GtkClock *clock = l->data;
+
+ /* We will now return a different value for the time porperty,
+ * so notify about that.
+ */
+ g_object_notify_by_pspec (G_OBJECT (clock), properties[PROP_TIME]);
+ /* We will also draw the hands of the clock differently.
+ * So notify about that, too.
+ */
+ gdk_paintable_invalidate_contents (GDK_PAINTABLE (clock));
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static void
+gtk_clock_stop_ticking (GtkClock *self)
+{
+ ticking_clocks = g_slist_remove (ticking_clocks, self);
+
+ /* If no clock is remaining, stop running the tick updates */
+ if (ticking_clocks == NULL && ticking_clock_id != 0)
+ g_clear_handle_id (&ticking_clock_id, g_source_remove);
+}
+
+static void
+gtk_clock_start_ticking (GtkClock *self)
+{
+ /* if no clock is ticking yet, start */
+ if (ticking_clock_id == 0)
+ ticking_clock_id = g_timeout_add_seconds (1, gtk_clock_tick, NULL);
+
+ ticking_clocks = g_slist_prepend (ticking_clocks, self);
+}
+
+static void
+gtk_clock_finalize (GObject *object)
+{
+ GtkClock *self = GTK_CLOCK (object);
+
+ gtk_clock_stop_ticking (self);
+
+ g_free (self->location);
+ g_clear_pointer (&self->timezone, g_time_zone_unref);
+
+ G_OBJECT_CLASS (gtk_clock_parent_class)->finalize (object);
+}
+
+static void
+gtk_clock_class_init (GtkClockClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->get_property = gtk_clock_get_property;
+ gobject_class->set_property = gtk_clock_set_property;
+ gobject_class->finalize = gtk_clock_finalize;
+
+ properties[PROP_LOCATION] =
+ g_param_spec_string ("location", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ properties[PROP_TIME] =
+ g_param_spec_boxed ("time", NULL, NULL, G_TYPE_DATE_TIME, G_PARAM_READABLE);
+ properties[PROP_TIMEZONE] =
+ g_param_spec_boxed ("timezone", NULL, NULL, G_TYPE_TIME_ZONE, G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, properties);
+}
+
+static void
+gtk_clock_init (GtkClock *self)
+{
+ gtk_clock_start_ticking (self);
+}
+
+static GtkClock *
+gtk_clock_new (const char *location,
+ GTimeZone *timezone)
+{
+ GtkClock *result;
+
+ result = g_object_new (GTK_TYPE_CLOCK,
+ "location", location,
+ "timezone", timezone,
+ NULL);
+
+ g_clear_pointer (&timezone, g_time_zone_unref);
+
+ return result;
+}
+
+static GListModel *
+create_clocks_model (void)
+{
+ GListStore *result;
+ GtkClock *clock;
+
+ result = g_list_store_new (GTK_TYPE_CLOCK);
+
+ /* local time */
+ clock = gtk_clock_new ("local", NULL);
+ g_list_store_append (result, clock);
+ g_object_unref (clock);
+ /* UTC time */
+ clock = gtk_clock_new ("UTC", g_time_zone_new_utc ());
+ g_list_store_append (result, clock);
+ g_object_unref (clock);
+ /* A bunch of timezones with GTK hackers */
+ clock = gtk_clock_new ("San Francisco", g_time_zone_new ("America/Los_Angeles"));
+ g_list_store_append (result, clock);
+ g_object_unref (clock);
+ clock = gtk_clock_new ("Boston", g_time_zone_new ("America/New_York"));
+ g_list_store_append (result, clock);
+ g_object_unref (clock);
+ clock = gtk_clock_new ("London", g_time_zone_new ("Europe/London"));
+ g_list_store_append (result, clock);
+ g_object_unref (clock);
+ clock = gtk_clock_new ("Berlin", g_time_zone_new ("Europe/Berlin"));
+ g_list_store_append (result, clock);
+ g_object_unref (clock);
+ clock = gtk_clock_new ("Moscow", g_time_zone_new ("Europe/Moscow"));
+ g_list_store_append (result, clock);
+ g_object_unref (clock);
+ clock = gtk_clock_new ("New Delhi", g_time_zone_new ("Asia/Kolkata"));
+ g_list_store_append (result, clock);
+ g_object_unref (clock);
+ clock = gtk_clock_new ("Shanghai", g_time_zone_new ("Asia/Shanghai"));
+ g_list_store_append (result, clock);
+ g_object_unref (clock);
+
+ return G_LIST_MODEL (result);
+}
+
+static char *
+convert_time_to_string (GObject *image,
+ GDateTime *time,
+ gpointer unused)
+{
+ return g_date_time_format (time, "%x\n%X");
+}
+
+/* And this function is the crux for this whole demo.
+ * It shows how to use expressions to set up bindings.
+ */
+static void
+setup_listitem_cb (GtkListItemFactory *factory,
+ GtkListItem *list_item)
+{
+ GtkWidget *box, *picture, *location_label, *time_label;
+ GtkExpression *clock_expression, *expression;
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_list_item_set_child (list_item, box);
+
+ /* First, we create an expression that gets us the clock from the listitem:
+ * 1. Create an expression that gets the list item.
+ * 2. Use that expression's "item" property to get the clock
+ */
+ expression = gtk_constant_expression_new (GTK_TYPE_LIST_ITEM, list_item);
+ clock_expression = gtk_property_expression_new (GTK_TYPE_LIST_ITEM, expression, "item");
+
+ /* Bind the clock's location to a label.
+ * This is easy: We just get the "location" property of the clock.
+ */
+ expression = gtk_property_expression_new (GTK_TYPE_CLOCK,
+ gtk_expression_ref (clock_expression),
+ "location");
+ /* Now create the label and bind the expression to it. */
+ location_label = gtk_label_new (NULL);
+ gtk_expression_bind (expression, location_label, "label");
+ gtk_container_add (GTK_CONTAINER (box), location_label);
+
+
+ /* Here we bind the item itself to a GdkPicture.
+ * This is simply done by using the clock expression itself.
+ */
+ expression = gtk_expression_ref (clock_expression);
+ /* Now create the widget and bind the expression to it. */
+ picture = gtk_picture_new ();
+ gtk_expression_bind (expression, picture, "paintable");
+ gtk_container_add (GTK_CONTAINER (box), picture);
+
+
+ /* And finally, everything comes together.
+ * We create a label for displaying the time as text.
+ * For that, we need to transform the "GDateTime" of the
+ * time property into a string so that the label can display it.
+ */
+ expression = gtk_property_expression_new (GTK_TYPE_CLOCK,
+ gtk_expression_ref (clock_expression),
+ "time");
+ expression = gtk_cclosure_expression_new (G_TYPE_STRING,
+ NULL,
+ 1, (GtkExpression *[1]) { expression },
+ G_CALLBACK (convert_time_to_string),
+ NULL, NULL);
+ /* Now create the label and bind the expression to it. */
+ time_label = gtk_label_new (NULL);
+ gtk_expression_bind (expression, time_label, "label");
+ gtk_container_add (GTK_CONTAINER (box), time_label);
+
+ gtk_expression_unref (clock_expression);
+}
+
+static GtkWidget *window = NULL;
+
+GtkWidget *
+do_listview_clocks (GtkWidget *do_widget)
+{
+ if (window == NULL)
+ {
+ GtkWidget *gridview, *sw;
+ GtkListItemFactory *factory;
+ GListModel *model;
+ GtkNoSelection *selection;
+
+ /* This is the normal window setup code every demo does */
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_display (GTK_WINDOW (window),
+ gtk_widget_get_display (do_widget));
+ g_signal_connect (window, "destroy",
+ G_CALLBACK (gtk_widget_destroyed), &window);
+
+ /* List widgets go into a scrolled window. Always. */
+ sw = gtk_scrolled_window_new (NULL, NULL);
+ gtk_container_add (GTK_CONTAINER (window), sw);
+
+ /* Create the factory that creates the listitems. Because we
+ * used bindings above during setup, we only need to connect
+ * to the setup signal.
+ * The bindings take care of the bind step.
+ */
+ factory = gtk_signal_list_item_factory_new ();
+ g_signal_connect (factory, "setup", G_CALLBACK (setup_listitem_cb), NULL);
+
+ gridview = gtk_grid_view_new_with_factory (factory);
+ gtk_scrollable_set_hscroll_policy (GTK_SCROLLABLE (gridview), GTK_SCROLL_NATURAL);
+ gtk_scrollable_set_vscroll_policy (GTK_SCROLLABLE (gridview), GTK_SCROLL_NATURAL);
+
+ model = create_clocks_model ();
+ selection = gtk_no_selection_new (model);
+ gtk_grid_view_set_model (GTK_GRID_VIEW (gridview), G_LIST_MODEL (selection));
+ gtk_container_add (GTK_CONTAINER (sw), gridview);
+ g_object_unref (selection);
+ g_object_unref (model);
+ }
+
+ if (!gtk_widget_get_visible (window))
+ gtk_widget_show (window);
+ else
+ gtk_widget_destroy (window);
+
+ return window;
+}
diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build
index 2cdd61e217..4a32d6555b 100644
--- a/demos/gtk-demo/meson.build
+++ b/demos/gtk-demo/meson.build
@@ -43,6 +43,7 @@ demos = files([
'flowbox.c',
'list_store.c',
'listview_applauncher.c',
+ 'listview_clocks.c',
'listview_filebrowser.c',
'listview_minesweeper.c',
'listview_settings.c',
@@ -100,7 +101,7 @@ extra_demo_sources = files(['main.c',
if harfbuzz_dep.found() and pangoft_dep.found()
demos += files('font_features.c')
extra_demo_sources += files(['script-names.c', 'language-names.c'])
- gtkdemo_deps += [ harfbuzz_dep, ]
+ gtkdemo_deps += [ harfbuzz_dep, epoxy_dep ]
endif
if os_unix
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]