From fcdc5030bd8f9127801884c52224432e14dc0100 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 31 May 2019 04:52:13 +0200 Subject: [PATCH] gtk-demo: Introduce awards We need a way to get a useful listbox, so here we go! --- demos/gtk-demo/award.c | 247 ++++++++++++++++++++++++++++++ demos/gtk-demo/award.h | 18 +++ demos/gtk-demo/awardlistitem.ui | 11 ++ demos/gtk-demo/awards.ui | 89 +++++++++++ demos/gtk-demo/awardview.c | 48 ++++++ demos/gtk-demo/demo.gresource.xml | 7 + demos/gtk-demo/listbox.c | 10 +- demos/gtk-demo/main.c | 4 + demos/gtk-demo/meson.build | 2 + demos/gtk-demo/password_entry.c | 16 +- demos/gtk-demo/sliding_puzzle.c | 25 ++- 11 files changed, 472 insertions(+), 5 deletions(-) create mode 100644 demos/gtk-demo/award.c create mode 100644 demos/gtk-demo/award.h create mode 100644 demos/gtk-demo/awardlistitem.ui create mode 100644 demos/gtk-demo/awards.ui create mode 100644 demos/gtk-demo/awardview.c diff --git a/demos/gtk-demo/award.c b/demos/gtk-demo/award.c new file mode 100644 index 0000000000..389bfc6790 --- /dev/null +++ b/demos/gtk-demo/award.c @@ -0,0 +1,247 @@ +/* + * Copyright © 2018 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "award.h" + +struct _GtkAward +{ + GObject parent; + + char *explanation; + char *name; + char *title; + GDateTime *granted; /* or NULL if not granted */ +}; + +enum { + PROP_0, + PROP_EXPLANATION, + PROP_NAME, + PROP_TITLE, + PROP_GRANTED, + + N_PROPS, +}; + +static GParamSpec *properties[N_PROPS] = { NULL, }; + +G_DEFINE_TYPE (GtkAward, gtk_award, G_TYPE_OBJECT) + +static void +gtk_award_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) + +{ + GtkAward *self = GTK_AWARD (object); + + switch (prop_id) + { + case PROP_EXPLANATION: + self->explanation = g_value_dup_string (value); + break; + + case PROP_NAME: + self->name = g_value_dup_string (value); + break; + + case PROP_TITLE: + self->title = g_value_dup_string (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_award_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GtkAward *self = GTK_AWARD (object); + + switch (prop_id) + { + case PROP_EXPLANATION: + g_value_set_string (value, self->explanation); + break; + + case PROP_NAME: + g_value_set_string (value, self->name); + break; + + case PROP_TITLE: + g_value_set_string (value, self->title); + break; + + case PROP_GRANTED: + g_value_set_boxed (value, self->granted); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gtk_award_dispose (GObject *object) +{ + GtkAward *self = GTK_AWARD (object); + + g_clear_pointer (&self->name, g_free); + g_clear_pointer (&self->title, g_free); + g_clear_pointer (&self->granted, g_date_time_unref); + + G_OBJECT_CLASS (gtk_award_parent_class)->dispose (object); +} + +static void +gtk_award_class_init (GtkAwardClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->set_property = gtk_award_set_property; + gobject_class->get_property = gtk_award_get_property; + gobject_class->dispose = gtk_award_dispose; + + properties[PROP_EXPLANATION] = + g_param_spec_string ("explanation", + "Explanation", + "How to get the title", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + properties[PROP_NAME] = + g_param_spec_string ("name", + "Name", + "internal name of the award", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + properties[PROP_TITLE] = + g_param_spec_string ("title", + "Title", + "user-visible title", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + properties[PROP_GRANTED] = + g_param_spec_boxed ("granted", + "Granted", + "Timestamp the award was granted or NULL if not granted yet", + G_TYPE_DATE_TIME, + G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPS, properties); +} + +static void +gtk_award_init (GtkAward *self) +{ +} + +GListModel * +gtk_award_get_list (void) +{ + static GListModel *list = NULL; + + if (list == NULL) + { + GtkBuilder *builder; + + g_type_ensure (GTK_TYPE_AWARD); + builder = gtk_builder_new_from_resource ("/awards.ui"); + list = G_LIST_MODEL (gtk_builder_get_object (builder, "list")); + g_object_ref (list); + g_object_unref (builder); + } + + return g_object_ref (list); +} + +const char * +gtk_award_get_name (GtkAward *award) +{ + return award->name; +} + +const char * +gtk_award_get_title (GtkAward *award) +{ + return award->title; +} + +GDateTime * +gtk_award_get_granted (GtkAward *award) +{ + return award->granted; +} + +GtkAward * +award_find (const char *name) +{ + GListModel *list; + GtkAward *self; + guint i; + + list = gtk_award_get_list (); + g_object_unref (list); + + for (i = 0; i < g_list_model_get_n_items (list); i++) + { + self = g_list_model_get_item (list, i); + g_object_unref (self); + + if (g_ascii_strcasecmp (name, self->name) == 0) + return self; + } + + return NULL; +} + +void +award (const char *name) +{ + GtkAward *self; + GNotification *notification; + + self = award_find (name); + if (self == NULL) + { + g_warning ("Did not find award \"%s\"", name); + return; + } + + if (self->granted) + return; + + self->granted = g_date_time_new_now_utc (); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_GRANTED]); + + notification = g_notification_new ("You won an award!"); + g_notification_set_body (notification, self->title); + g_application_send_notification (g_application_get_default (), NULL, notification); + g_object_unref (notification); +} + diff --git a/demos/gtk-demo/award.h b/demos/gtk-demo/award.h new file mode 100644 index 0000000000..0c4cca01f2 --- /dev/null +++ b/demos/gtk-demo/award.h @@ -0,0 +1,18 @@ +#ifndef __AWARD_H__ +#define __AWARD_H__ + +#include + +#define GTK_TYPE_AWARD (gtk_award_get_type ()) + +G_DECLARE_FINAL_TYPE (GtkAward, gtk_award, GTK, AWARD, GObject) + +GListModel * gtk_award_get_list (void); + +const char * gtk_award_get_name (GtkAward *award); +const char * gtk_award_get_title (GtkAward *award); +GDateTime * gtk_award_get_granted (GtkAward *award); + +void award (const char *name); + +#endif /* __AWARD_H__ */ diff --git a/demos/gtk-demo/awardlistitem.ui b/demos/gtk-demo/awardlistitem.ui new file mode 100644 index 0000000000..8e2a1415fd --- /dev/null +++ b/demos/gtk-demo/awardlistitem.ui @@ -0,0 +1,11 @@ + + + + diff --git a/demos/gtk-demo/awards.ui b/demos/gtk-demo/awards.ui new file mode 100644 index 0000000000..5979c9253a --- /dev/null +++ b/demos/gtk-demo/awards.ui @@ -0,0 +1,89 @@ + + + + GtkAward + + + demo-inspector + + You got a high-rise double-pump carburetor. + Launch the inspector + + + + + demo-start + + After this, there is no turning back. + Start gtk-demo + + + + + + listbox-reshare + + Trying to make fetch happen + Reshare a tweet + + + + + listbox-100th-row + + The ever impressive, long contained, often imitated, but never duplicated Genie of the lamp. + Select a 100th row in a list + + + + + + password-best + + I've got the same combination on my luggage! + Use "12345" as the password + + + + + password-correct + + Night Shark 1-1-5 + Correctly enter a password + + + + + + puzzle-give-up + + Big Mistake. Big. Huge! + Close the puzzle without finishing it + + + + + puzzle-solve + + That was totally wicked! + Solve a puzzle + + + + + puzzle-solve-animated + + A surprise to be sure but a welcome one. + Solve an animated puzzle + + + + + puzzle-solve-large + + Science isn't about WHY. It's about WHY NOT?! + Solve a puzzle with at least 20 pieces + + + + diff --git a/demos/gtk-demo/awardview.c b/demos/gtk-demo/awardview.c new file mode 100644 index 0000000000..30831c9d56 --- /dev/null +++ b/demos/gtk-demo/awardview.c @@ -0,0 +1,48 @@ +/* Awards + * + * This demo demonstrates how to use lists to show the awards you have collected + * while exploring this demo. + * + */ + +#include + +/* Include the header for accessing the awards */ +#include "award.h" + +static GtkWidget *window = NULL; + +GtkWidget * +do_awardview (GtkWidget *do_widget) +{ + if (!window) + { + GtkWidget *sw, *listview; + GListModel *list; + + window = gtk_window_new (); + gtk_window_set_display (GTK_WINDOW (window), + gtk_widget_get_display (do_widget)); + gtk_window_set_title (GTK_WINDOW (window), "Awards"); + gtk_window_set_default_size (GTK_WINDOW (window), 400, 300); + g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_window_set_child (GTK_WINDOW (window), sw); + + listview = gtk_list_view_new_with_factory ( + gtk_builder_list_item_factory_new_from_resource (NULL, "/awardview/awardlistitem.ui")); + list = gtk_award_get_list (); + gtk_list_view_set_model (GTK_LIST_VIEW (listview), list); + g_object_unref (list); + gtk_list_view_set_show_separators (GTK_LIST_VIEW (listview), TRUE); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), listview); + } + + if (!gtk_widget_get_visible (window)) + gtk_widget_show (window); + else + gtk_window_destroy (GTK_WINDOW (window)); + + return window; +} diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml index 9d66c91078..50477c13f5 100644 --- a/demos/gtk-demo/demo.gresource.xml +++ b/demos/gtk-demo/demo.gresource.xml @@ -1,5 +1,8 @@ + + awards.ui + main.ui @@ -8,6 +11,9 @@ application.ui menus.ui + + awardlistitem.ui + demo.ui @@ -160,6 +166,7 @@ application_demo.c + awardview.c assistant.c builder.c clipboard.c diff --git a/demos/gtk-demo/listbox.c b/demos/gtk-demo/listbox.c index 69cbb8b334..1f56ff9faa 100644 --- a/demos/gtk-demo/listbox.c +++ b/demos/gtk-demo/listbox.c @@ -8,6 +8,7 @@ #include #include #include +#include "award.h" static GdkPixbuf *avatar_pixbuf_other; static GtkWidget *window = NULL; @@ -234,9 +235,9 @@ reshare_clicked (GtkMessageRow *row, { GtkMessageRowPrivate *priv = row->priv; + award ("listbox-reshare"); priv->message->n_reshares++; gtk_message_row_update (row); - } static void @@ -255,11 +256,14 @@ gtk_message_row_state_flags_changed (GtkWidget *widget, { GtkMessageRowPrivate *priv = GTK_MESSAGE_ROW (widget)->priv; GtkStateFlags flags; + gboolean visible; flags = gtk_widget_get_state_flags (widget); - gtk_widget_set_visible (priv->extra_buttons_box, - flags & (GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_SELECTED)); + visible = flags & (GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_SELECTED) ? TRUE : FALSE; + gtk_widget_set_visible (priv->extra_buttons_box, visible); + if (visible && gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (widget)) % 100 == 99) + award ("listbox-100th-row"); GTK_WIDGET_CLASS (gtk_message_row_parent_class)->state_flags_changed (widget, previous_state_flags); } diff --git a/demos/gtk-demo/main.c b/demos/gtk-demo/main.c index 9d802b8c1f..a54e791844 100644 --- a/demos/gtk-demo/main.c +++ b/demos/gtk-demo/main.c @@ -8,6 +8,7 @@ #include #include +#include "award.h" #include "demos.h" static GtkWidget *info_view; @@ -113,6 +114,7 @@ activate_inspector (GSimpleAction *action, gpointer user_data) { gtk_window_set_interactive_debugging (TRUE); + award ("demo-inspector"); } static void @@ -1031,6 +1033,8 @@ activate (GApplication *app) gtk_tree_view_collapse_all (GTK_TREE_VIEW (treeview)); + award ("demo-start"); + gtk_widget_show (GTK_WIDGET (window)); g_object_unref (builder); diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build index f4d73ff327..984b4b0429 100644 --- a/demos/gtk-demo/meson.build +++ b/demos/gtk-demo/meson.build @@ -3,6 +3,7 @@ demos = files([ 'application_demo.c', 'assistant.c', + 'awardview.c', 'builder.c', 'clipboard.c', 'combobox.c', @@ -86,6 +87,7 @@ demos = files([ gtkdemo_deps = [ libgtk_dep, ] extra_demo_sources = files(['main.c', + 'award.c', 'gtkfishbowl.c', 'fontplane.c', 'gtkgears.c', diff --git a/demos/gtk-demo/password_entry.c b/demos/gtk-demo/password_entry.c index fd72e148a8..3f5b28539a 100644 --- a/demos/gtk-demo/password_entry.c +++ b/demos/gtk-demo/password_entry.c @@ -11,6 +11,8 @@ #include #include +#include "award.h" + static GtkWidget *entry; static GtkWidget *entry2; static GtkWidget *button; @@ -25,6 +27,18 @@ update_button (GObject *object, gtk_widget_set_sensitive (button, text[0] != '\0' && g_str_equal (text, text2)); + + if (g_str_equal (text, text2) && + g_ascii_strcasecmp (text, "12345") == 0) + award ("password-best"); +} + +static void +button_pressed (GtkButton *widget, + GtkWidget *window) +{ + award ("password-correct"); + gtk_window_destroy (GTK_WINDOW (window)); } GtkWidget * @@ -74,7 +88,7 @@ do_password_entry (GtkWidget *do_widget) button = gtk_button_new_with_mnemonic ("_Done"); gtk_widget_add_css_class (button, "suggested-action"); - g_signal_connect_swapped (button, "clicked", G_CALLBACK (gtk_window_destroy), window); + g_signal_connect (button, "clicked", G_CALLBACK (button_pressed), window); gtk_widget_set_sensitive (button, FALSE); gtk_header_bar_pack_end (GTK_HEADER_BAR (header), button); diff --git a/demos/gtk-demo/sliding_puzzle.c b/demos/gtk-demo/sliding_puzzle.c index 098048eb5d..a6605514eb 100644 --- a/demos/gtk-demo/sliding_puzzle.c +++ b/demos/gtk-demo/sliding_puzzle.c @@ -11,6 +11,9 @@ #include "puzzlepiece.h" #include "paintable.h" +/* Give out awards */ +#include "award.h" + static GtkWidget *window = NULL; static GtkWidget *frame = NULL; static GtkWidget *choices = NULL; @@ -156,6 +159,14 @@ check_solved (GtkWidget *grid) picture = gtk_grid_get_child_at (GTK_GRID (grid), pos_x, pos_y); gtk_picture_set_paintable (GTK_PICTURE (picture), piece); + /* Hand out a bunch of awards + */ + award ("puzzle-solve"); + if ((gdk_paintable_get_flags (piece) & GDK_PAINTABLE_STATIC_CONTENTS) == 0) + award ("puzzle-solve-animated"); + if (height * width > 20) + award ("puzzle-solve-large"); + return TRUE; } @@ -390,6 +401,18 @@ add_choice (GtkWidget *container, gtk_flow_box_insert (GTK_FLOW_BOX (container), icon, -1); } +static void +widget_destroyed (gpointer data, + GObject *widget) +{ + if (data) + *(gpointer *) data = NULL; + + if (!solved) + award ("puzzle-give-up"); +} + + GtkWidget * do_sliding_puzzle (GtkWidget *do_widget) { @@ -460,7 +483,7 @@ do_sliding_puzzle (GtkWidget *do_widget) gtk_window_set_title (GTK_WINDOW (window), "Sliding Puzzle"); gtk_window_set_titlebar (GTK_WINDOW (window), header); gtk_window_set_default_size (GTK_WINDOW (window), 400, 300); - g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window); + g_object_weak_ref (G_OBJECT (window), widget_destroyed, &window); frame = gtk_aspect_frame_new (0.5, 0.5, (float) gdk_paintable_get_intrinsic_aspect_ratio (puzzle), FALSE); gtk_window_set_child (GTK_WINDOW (window), frame);