forked from AuroraMiddleware/gtk
8d79a32c50
Change the apis in GtkListView, GtkColumnView and GtkGridView to be explicitly about GtkSelectionModel, to make it obvious that the widgets handle selection. Update all users.
502 lines
16 KiB
C
502 lines
16 KiB
C
/* 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 transforms again: Instead of computing where the angle
|
|
* points to, we just rotate everything and then draw the hand as if it
|
|
* 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 clocks
|
|
* 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 drawn.
|
|
*/
|
|
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 property,
|
|
* 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 *_tz)
|
|
{
|
|
GtkClock *result;
|
|
|
|
result = g_object_new (GTK_TYPE_CLOCK,
|
|
"location", location,
|
|
"timezone", _tz,
|
|
NULL);
|
|
|
|
g_clear_pointer (&_tz, 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 ("Xalapa", g_time_zone_new ("America/Mexico_City"));
|
|
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", location_label);
|
|
gtk_box_append (GTK_BOX (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", picture);
|
|
gtk_box_append (GTK_BOX (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", time_label);
|
|
gtk_box_append (GTK_BOX (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;
|
|
GtkSelectionModel *model;
|
|
|
|
/* This is the normal window setup code every demo does */
|
|
window = gtk_window_new ();
|
|
gtk_window_set_title (GTK_WINDOW (window), "Clocks");
|
|
gtk_window_set_default_size (GTK_WINDOW (window), 600, 400);
|
|
gtk_window_set_display (GTK_WINDOW (window),
|
|
gtk_widget_get_display (do_widget));
|
|
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
|
|
|
|
/* List widgets go into a scrolled window. Always. */
|
|
sw = gtk_scrolled_window_new ();
|
|
gtk_window_set_child (GTK_WINDOW (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);
|
|
|
|
model = GTK_SELECTION_MODEL (gtk_no_selection_new (create_clocks_model ()));
|
|
gridview = gtk_grid_view_new_with_factory (model, factory);
|
|
gtk_scrollable_set_hscroll_policy (GTK_SCROLLABLE (gridview), GTK_SCROLL_NATURAL);
|
|
gtk_scrollable_set_vscroll_policy (GTK_SCROLLABLE (gridview), GTK_SCROLL_NATURAL);
|
|
|
|
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), gridview);
|
|
}
|
|
|
|
if (!gtk_widget_get_visible (window))
|
|
gtk_widget_show (window);
|
|
else
|
|
gtk_window_destroy (GTK_WINDOW (window));
|
|
|
|
return window;
|
|
}
|