Blame src/glade-intro.c

Packit 1e8aac
/*
Packit 1e8aac
 * glade-intro.c
Packit 1e8aac
 *
Packit 1e8aac
 * Copyright (C) 2017 Juan Pablo Ugarte
Packit 1e8aac
 *
Packit 1e8aac
 * This library is free software; you can redistribute it and/or modify it
Packit 1e8aac
 * under the terms of the GNU Lesser General Public License as
Packit 1e8aac
 * published by the Free Software Foundation; either version 2.1 of
Packit 1e8aac
 * the License, or (at your option) any later version.
Packit 1e8aac
 *
Packit 1e8aac
 * This library is distributed in the hope that it will be useful, but
Packit 1e8aac
 * WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 1e8aac
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 1e8aac
 * Lesser General Public License for more details.
Packit 1e8aac
 *
Packit 1e8aac
 * You should have received a copy of the GNU Lesser General Public
Packit 1e8aac
 * License along with this program; if not, write to the Free Software
Packit 1e8aac
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Packit 1e8aac
 *
Packit 1e8aac
 * Authors:
Packit 1e8aac
 *   Juan Pablo Ugarte <juanpablougarte@gmail.com>
Packit 1e8aac
 */
Packit 1e8aac
Packit 1e8aac
#include "glade-intro.h"
Packit 1e8aac
Packit 1e8aac
typedef struct
Packit 1e8aac
{
Packit 1e8aac
  GtkWidget          *widget;
Packit 1e8aac
  const gchar        *name;
Packit 1e8aac
  const gchar        *widget_name;
Packit 1e8aac
  const gchar        *text;
Packit 1e8aac
  GladeIntroPosition  position;
Packit 1e8aac
  gint                delay;
Packit 1e8aac
} ScriptNode;
Packit 1e8aac
Packit 1e8aac
typedef struct
Packit 1e8aac
{
Packit 1e8aac
  GtkWidget  *toplevel;
Packit 1e8aac
Packit 1e8aac
  GList      *script;      /* List of (ScriptNode *) */
Packit 1e8aac
  GHashTable *widgets;     /* Table with all named widget in toplevel */
Packit 1e8aac
Packit 1e8aac
  GtkPopover *popover;     /* Popover to show the script text */
Packit 1e8aac
Packit 1e8aac
  guint       timeout_id;  /* Timeout id for running the script */
Packit 1e8aac
  GList      *current;     /* Current script node */
Packit 1e8aac
Packit 1e8aac
  gboolean    hiding_node;
Packit 1e8aac
} GladeIntroPrivate;
Packit 1e8aac
Packit 1e8aac
struct _GladeIntro
Packit 1e8aac
{
Packit 1e8aac
  GObject parent_instance;
Packit 1e8aac
};
Packit 1e8aac
Packit 1e8aac
enum
Packit 1e8aac
{
Packit 1e8aac
  PROP_0,
Packit 1e8aac
  PROP_TOPLEVEL,
Packit 1e8aac
  PROP_STATE,
Packit 1e8aac
Packit 1e8aac
  N_PROPERTIES
Packit 1e8aac
};
Packit 1e8aac
Packit 1e8aac
enum
Packit 1e8aac
{
Packit 1e8aac
  SHOW_NODE,
Packit 1e8aac
  HIDE_NODE,
Packit 1e8aac
Packit 1e8aac
  LAST_SIGNAL
Packit 1e8aac
};
Packit 1e8aac
Packit 1e8aac
static guint intro_signals[LAST_SIGNAL] = { 0 };
Packit 1e8aac
Packit 1e8aac
static GParamSpec *properties[N_PROPERTIES];
Packit 1e8aac
Packit 1e8aac
G_DEFINE_TYPE_WITH_PRIVATE (GladeIntro, glade_intro, G_TYPE_OBJECT);
Packit 1e8aac
Packit 1e8aac
#define GET_PRIVATE(d) ((GladeIntroPrivate *) glade_intro_get_instance_private((GladeIntro*)d))
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
glade_intro_init (GladeIntro *intro)
Packit 1e8aac
{
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
glade_intro_finalize (GObject *object)
Packit 1e8aac
{
Packit 1e8aac
  GladeIntroPrivate *priv = GET_PRIVATE (object);
Packit 1e8aac
Packit 1e8aac
  if (priv->timeout_id)
Packit 1e8aac
    {
Packit 1e8aac
      g_source_remove (priv->timeout_id);
Packit 1e8aac
      priv->timeout_id = 0;
Packit 1e8aac
    }
Packit 1e8aac
Packit 1e8aac
  gtk_popover_set_relative_to (priv->popover, NULL);
Packit 1e8aac
  g_clear_object (&priv->popover);
Packit 1e8aac
Packit 1e8aac
  G_OBJECT_CLASS (glade_intro_parent_class)->finalize (object);
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
glade_intro_set_property (GObject      *object,
Packit 1e8aac
                          guint         prop_id,
Packit 1e8aac
                          const GValue *value,
Packit 1e8aac
                          GParamSpec   *pspec)
Packit 1e8aac
{
Packit 1e8aac
  g_return_if_fail (GLADE_IS_INTRO (object));
Packit 1e8aac
Packit 1e8aac
  switch (prop_id)
Packit 1e8aac
    {
Packit 1e8aac
      case PROP_TOPLEVEL:
Packit 1e8aac
        glade_intro_set_toplevel (GLADE_INTRO (object), g_value_get_object (value));
Packit 1e8aac
      break;
Packit 1e8aac
      default:
Packit 1e8aac
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
Packit 1e8aac
      break;
Packit 1e8aac
    }
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
glade_intro_get_property (GObject    *object,
Packit 1e8aac
                          guint       prop_id,
Packit 1e8aac
                          GValue     *value,
Packit 1e8aac
                          GParamSpec *pspec)
Packit 1e8aac
{
Packit 1e8aac
  GladeIntroPrivate *priv;
Packit 1e8aac
Packit 1e8aac
  g_return_if_fail (GLADE_IS_INTRO (object));
Packit 1e8aac
  priv = GET_PRIVATE (object);
Packit 1e8aac
Packit 1e8aac
  switch (prop_id)
Packit 1e8aac
    {
Packit 1e8aac
      case PROP_TOPLEVEL:
Packit 1e8aac
        g_value_set_object (value, priv->toplevel);
Packit 1e8aac
      break;
Packit 1e8aac
      case PROP_STATE:
Packit 1e8aac
        if (priv->timeout_id)
Packit 1e8aac
          g_value_set_enum (value, GLADE_INTRO_STATE_PLAYING);
Packit 1e8aac
        else if (priv->current)
Packit 1e8aac
          g_value_set_enum (value, GLADE_INTRO_STATE_PAUSED);
Packit 1e8aac
        else
Packit 1e8aac
          g_value_set_enum (value, GLADE_INTRO_STATE_NULL);
Packit 1e8aac
      break;
Packit 1e8aac
      default:
Packit 1e8aac
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
Packit 1e8aac
      break;
Packit 1e8aac
    }
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static GType
Packit 1e8aac
glade_intro_state_get_type (void)
Packit 1e8aac
{
Packit 1e8aac
    static GType etype = 0;
Packit 1e8aac
    if (G_UNLIKELY(etype == 0)) {
Packit 1e8aac
        static const GEnumValue values[] = {
Packit 1e8aac
            { GLADE_INTRO_STATE_NULL, "GLADE_INTRO_STATE_NULL", "null" },
Packit 1e8aac
            { GLADE_INTRO_STATE_PLAYING, "GLADE_INTRO_STATE_PLAYING", "playing" },
Packit 1e8aac
            { GLADE_INTRO_STATE_PAUSED, "GLADE_INTRO_STATE_PAUSED", "paused" },
Packit 1e8aac
            { 0, NULL, NULL }
Packit 1e8aac
        };
Packit 1e8aac
        etype = g_enum_register_static (g_intern_static_string ("GladeIntroStatus"), values);
Packit 1e8aac
    }
Packit 1e8aac
    return etype;
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
glade_intro_class_init (GladeIntroClass *klass)
Packit 1e8aac
{
Packit 1e8aac
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
Packit 1e8aac
Packit 1e8aac
  object_class->finalize = glade_intro_finalize;
Packit 1e8aac
  object_class->set_property = glade_intro_set_property;
Packit 1e8aac
  object_class->get_property = glade_intro_get_property;
Packit 1e8aac
Packit 1e8aac
  /* Properties */
Packit 1e8aac
  properties[PROP_TOPLEVEL] =
Packit 1e8aac
    g_param_spec_object ("toplevel", "Toplevel",
Packit 1e8aac
                         "The main toplevel from where to get the widgets",
Packit 1e8aac
                         GTK_TYPE_WINDOW,
Packit 1e8aac
                         G_PARAM_READWRITE);
Packit 1e8aac
  properties[PROP_STATE] =
Packit 1e8aac
    g_param_spec_enum ("state", "State",
Packit 1e8aac
                       "Playback state",
Packit 1e8aac
                       glade_intro_state_get_type (),
Packit 1e8aac
                       GLADE_INTRO_STATE_NULL,
Packit 1e8aac
                       G_PARAM_READABLE);
Packit 1e8aac
Packit 1e8aac
  intro_signals[SHOW_NODE] =
Packit 1e8aac
    g_signal_new ("show-node", G_OBJECT_CLASS_TYPE (klass), 0, 0,
Packit 1e8aac
                  NULL, NULL, NULL,
Packit 1e8aac
                  G_TYPE_NONE, 2,
Packit 1e8aac
                  G_TYPE_STRING,
Packit 1e8aac
                  GTK_TYPE_WIDGET);
Packit 1e8aac
  intro_signals[HIDE_NODE] =
Packit 1e8aac
    g_signal_new ("hide-node", G_OBJECT_CLASS_TYPE (klass), 0, 0,
Packit 1e8aac
                  NULL, NULL, NULL,
Packit 1e8aac
                  G_TYPE_NONE, 2,
Packit 1e8aac
                  G_TYPE_STRING,
Packit 1e8aac
                  GTK_TYPE_WIDGET);
Packit 1e8aac
Packit 1e8aac
  g_object_class_install_properties (object_class, N_PROPERTIES, properties);
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
/* Public API */
Packit 1e8aac
Packit 1e8aac
GladeIntro *
Packit 1e8aac
glade_intro_new (GtkWindow *toplevel)
Packit 1e8aac
{
Packit 1e8aac
  return (GladeIntro*) g_object_new (GLADE_TYPE_INTRO, "toplevel", toplevel, NULL);
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
get_toplevel_widgets (GtkWidget *widget, gpointer data)
Packit 1e8aac
{
Packit 1e8aac
  const gchar *name;
Packit 1e8aac
Packit 1e8aac
  if ((name = gtk_widget_get_name (widget)) &&
Packit 1e8aac
      g_strcmp0 (name, G_OBJECT_TYPE_NAME (widget)))
Packit 1e8aac
    g_hash_table_insert (GET_PRIVATE (data)->widgets, (gpointer)name, widget);
Packit 1e8aac
Packit 1e8aac
  if (GTK_IS_CONTAINER (widget))
Packit 1e8aac
    gtk_container_forall (GTK_CONTAINER (widget), get_toplevel_widgets, data);
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
void
Packit 1e8aac
glade_intro_set_toplevel (GladeIntro *intro, GtkWindow *toplevel)
Packit 1e8aac
{
Packit 1e8aac
  GladeIntroPrivate *priv;
Packit 1e8aac
Packit 1e8aac
  g_return_if_fail (GLADE_IS_INTRO (intro));
Packit 1e8aac
  priv = GET_PRIVATE (intro);
Packit 1e8aac
Packit 1e8aac
  g_clear_object (&priv->toplevel);
Packit 1e8aac
  g_clear_pointer (&priv->widgets, g_hash_table_unref);
Packit 1e8aac
Packit 1e8aac
  if (toplevel)
Packit 1e8aac
    {
Packit 1e8aac
      priv->toplevel = g_object_ref (toplevel);
Packit 1e8aac
      priv->widgets = g_hash_table_new (g_str_hash, g_str_equal);
Packit 1e8aac
      gtk_container_forall (GTK_CONTAINER (toplevel), get_toplevel_widgets, intro);
Packit 1e8aac
    }
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
void
Packit 1e8aac
glade_intro_script_add (GladeIntro         *intro,
Packit 1e8aac
                        const gchar        *name,
Packit 1e8aac
                        const gchar        *widget,
Packit 1e8aac
                        const gchar        *text,
Packit 1e8aac
                        GladeIntroPosition  position,
Packit 1e8aac
                        gdouble             delay)
Packit 1e8aac
{
Packit 1e8aac
  GladeIntroPrivate *priv;
Packit 1e8aac
  ScriptNode *node;
Packit 1e8aac
Packit 1e8aac
  g_return_if_fail (GLADE_IS_INTRO (intro));
Packit 1e8aac
  priv = GET_PRIVATE (intro);
Packit 1e8aac
Packit 1e8aac
  node = g_new0 (ScriptNode, 1);
Packit 1e8aac
  node->name        = name;
Packit 1e8aac
  node->widget_name = widget;
Packit 1e8aac
  node->text        = text;
Packit 1e8aac
  node->position    = position;
Packit 1e8aac
  node->delay       = delay * 1000;
Packit 1e8aac
Packit 1e8aac
  priv->script = g_list_append (priv->script, node);
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static gboolean script_play (gpointer data);
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
on_popover_closed (GtkPopover *popover, GladeIntro *intro)
Packit 1e8aac
{
Packit 1e8aac
  glade_intro_pause (intro);
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static void
Packit 1e8aac
hide_current_node (GladeIntro *intro)
Packit 1e8aac
{
Packit 1e8aac
  GladeIntroPrivate *priv = GET_PRIVATE (intro);
Packit 1e8aac
  ScriptNode *node;
Packit 1e8aac
Packit 1e8aac
  if (priv->hiding_node)
Packit 1e8aac
    return;
Packit 1e8aac
  priv->hiding_node = TRUE;
Packit 1e8aac
  if (priv->popover)
Packit 1e8aac
    {
Packit 1e8aac
      g_signal_handlers_disconnect_by_func (priv->popover, on_popover_closed, intro);
Packit 1e8aac
      gtk_popover_popdown (priv->popover);
Packit 1e8aac
      g_clear_object (&priv->popover);
Packit 1e8aac
    }
Packit 1e8aac
Packit 1e8aac
  if (priv->current && (node = priv->current->data))
Packit 1e8aac
    {
Packit 1e8aac
      if (node->widget)
Packit 1e8aac
        gtk_style_context_remove_class (gtk_widget_get_style_context (node->widget),
Packit 1e8aac
                                        "glade-intro-highlight");
Packit 1e8aac
      g_signal_emit (intro, intro_signals[HIDE_NODE], 0, node->name, node->widget);
Packit 1e8aac
    }
Packit 1e8aac
Packit 1e8aac
  /* Set next node */
Packit 1e8aac
  priv->current = (priv->current) ? g_list_next (priv->current) : NULL;
Packit 1e8aac
Packit 1e8aac
  priv->hiding_node = FALSE;
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static gboolean
Packit 1e8aac
script_transition (gpointer data)
Packit 1e8aac
{
Packit 1e8aac
  GladeIntroPrivate *priv = GET_PRIVATE (data);
Packit 1e8aac
Packit 1e8aac
  priv->timeout_id = g_timeout_add (250, script_play, data);
Packit 1e8aac
  hide_current_node (data);
Packit 1e8aac
Packit 1e8aac
  return G_SOURCE_REMOVE;
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static GtkWidget *
Packit 1e8aac
glade_intro_popover_new (GladeIntro *intro, const gchar *text)
Packit 1e8aac
{
Packit 1e8aac
  GtkWidget *popover, *box, *image, *label;
Packit 1e8aac
Packit 1e8aac
  popover = gtk_popover_new (NULL);
Packit 1e8aac
  label = gtk_label_new (text);
Packit 1e8aac
  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
Packit 1e8aac
  image = gtk_image_new_from_icon_name ("dialog-information-symbolic", GTK_ICON_SIZE_DIALOG);
Packit 1e8aac
Packit 1e8aac
  gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
Packit 1e8aac
  gtk_label_set_max_width_chars (GTK_LABEL (label), 28);
Packit 1e8aac
Packit 1e8aac
  gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 0);
Packit 1e8aac
  gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
Packit 1e8aac
  gtk_container_add (GTK_CONTAINER (popover), box);
Packit 1e8aac
Packit 1e8aac
  gtk_style_context_add_class (gtk_widget_get_style_context (popover), "glade-intro");
Packit 1e8aac
  g_signal_connect (popover, "closed", G_CALLBACK (on_popover_closed), intro);
Packit 1e8aac
Packit 1e8aac
  gtk_widget_show_all (box);
Packit 1e8aac
Packit 1e8aac
  return popover;
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
static gboolean
Packit 1e8aac
script_play (gpointer data)
Packit 1e8aac
{
Packit 1e8aac
  GladeIntroPrivate *priv = GET_PRIVATE (data);
Packit 1e8aac
  GtkStyleContext *context;
Packit 1e8aac
  ScriptNode *node;
Packit 1e8aac
Packit 1e8aac
  priv->timeout_id = 0;
Packit 1e8aac
Packit 1e8aac
  if (!priv->current || !(node = priv->current->data))
Packit 1e8aac
    return G_SOURCE_REMOVE;
Packit 1e8aac
Packit 1e8aac
  node->widget = NULL;
Packit 1e8aac
Packit 1e8aac
  if (node->widget_name &&
Packit 1e8aac
      (node->widget = g_hash_table_lookup (priv->widgets, node->widget_name)) &&
Packit 1e8aac
      node->text)
Packit 1e8aac
    {
Packit 1e8aac
      /* Ensure the widget is visible */
Packit 1e8aac
      if (!gtk_widget_is_visible (node->widget))
Packit 1e8aac
        {
Packit 1e8aac
          GtkWidget *parent;
Packit 1e8aac
          /* if the widget is inside a popover pop it up */
Packit 1e8aac
          if ((parent = gtk_widget_get_ancestor (node->widget, GTK_TYPE_POPOVER)))
Packit 1e8aac
            gtk_popover_popup (GTK_POPOVER (parent));
Packit 1e8aac
        }
Packit 1e8aac
Packit 1e8aac
      context = gtk_widget_get_style_context (node->widget);
Packit 1e8aac
      gtk_style_context_add_class (context, "glade-intro-highlight");
Packit 1e8aac
Packit 1e8aac
      /* Create popover */
Packit 1e8aac
      priv->popover = g_object_ref_sink (glade_intro_popover_new (data, node->text));
Packit 1e8aac
      gtk_popover_set_relative_to (priv->popover, node->widget);
Packit 1e8aac
Packit 1e8aac
      if (node->position == GLADE_INTRO_BOTTOM)
Packit 1e8aac
        gtk_popover_set_position (priv->popover, GTK_POS_BOTTOM);
Packit 1e8aac
      else if (node->position == GLADE_INTRO_LEFT)
Packit 1e8aac
        gtk_popover_set_position (priv->popover, GTK_POS_LEFT);
Packit 1e8aac
      else if (node->position == GLADE_INTRO_RIGHT)
Packit 1e8aac
        gtk_popover_set_position (priv->popover, GTK_POS_RIGHT);
Packit 1e8aac
      else if (node->position == GLADE_INTRO_CENTER)
Packit 1e8aac
        {
Packit 1e8aac
          GdkRectangle rect = {
Packit 1e8aac
            gtk_widget_get_allocated_width (node->widget)/2,
Packit 1e8aac
            gtk_widget_get_allocated_height (node->widget)/2,
Packit 1e8aac
            4, 4
Packit 1e8aac
          };
Packit 1e8aac
Packit 1e8aac
          gtk_popover_set_pointing_to (priv->popover, &rect);
Packit 1e8aac
          gtk_popover_set_position (priv->popover, GTK_POS_TOP);
Packit 1e8aac
        }
Packit 1e8aac
    }
Packit 1e8aac
Packit 1e8aac
  g_signal_emit (data, intro_signals[SHOW_NODE], 0, node->name, node->widget);
Packit 1e8aac
Packit 1e8aac
  if (priv->popover)
Packit 1e8aac
    gtk_popover_popup (priv->popover);
Packit 1e8aac
Packit 1e8aac
  priv->timeout_id = g_timeout_add (node->delay, script_transition, data);
Packit 1e8aac
Packit 1e8aac
  return G_SOURCE_REMOVE;
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
void
Packit 1e8aac
glade_intro_play (GladeIntro *intro)
Packit 1e8aac
{
Packit 1e8aac
  GladeIntroPrivate *priv;
Packit 1e8aac
Packit 1e8aac
  g_return_if_fail (GLADE_IS_INTRO (intro));
Packit 1e8aac
  priv = GET_PRIVATE (intro);
Packit 1e8aac
Packit 1e8aac
  if (priv->script == NULL)
Packit 1e8aac
    return;
Packit 1e8aac
Packit 1e8aac
  if (priv->current == NULL)
Packit 1e8aac
    priv->current = priv->script;
Packit 1e8aac
Packit 1e8aac
  script_play (intro);
Packit 1e8aac
Packit 1e8aac
  g_object_notify_by_pspec (G_OBJECT (intro), properties[PROP_STATE]);
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
void
Packit 1e8aac
glade_intro_pause (GladeIntro *intro)
Packit 1e8aac
{
Packit 1e8aac
  GladeIntroPrivate *priv;
Packit 1e8aac
Packit 1e8aac
  g_return_if_fail (GLADE_IS_INTRO (intro));
Packit 1e8aac
  priv = GET_PRIVATE (intro);
Packit 1e8aac
Packit 1e8aac
  if (priv->timeout_id)
Packit 1e8aac
    g_source_remove (priv->timeout_id);
Packit 1e8aac
Packit 1e8aac
  priv->timeout_id = 0;
Packit 1e8aac
  hide_current_node (intro);
Packit 1e8aac
Packit 1e8aac
  g_object_notify_by_pspec (G_OBJECT (intro), properties[PROP_STATE]);
Packit 1e8aac
}
Packit 1e8aac
Packit 1e8aac
void
Packit 1e8aac
glade_intro_stop (GladeIntro *intro)
Packit 1e8aac
{
Packit 1e8aac
  GladeIntroPrivate *priv;
Packit 1e8aac
Packit 1e8aac
  g_return_if_fail (GLADE_IS_INTRO (intro));
Packit 1e8aac
  priv = GET_PRIVATE (intro);
Packit 1e8aac
Packit 1e8aac
  glade_intro_pause (intro);
Packit 1e8aac
  priv->current = NULL;
Packit 1e8aac
Packit 1e8aac
  g_object_notify_by_pspec (G_OBJECT (intro), properties[PROP_STATE]);
Packit 1e8aac
}