/* dzl-progress-menu-button.c
*
* Copyright (C) 2016 Christian Hergert <chergert@redhat.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define G_LOG_DOMAIN "dzl-progress-menu-button"
#include "config.h"
#include "animation/dzl-animation.h"
#include "animation/dzl-box-theatric.h"
#include "widgets/dzl-progress-icon.h"
#include "widgets/dzl-progress-menu-button.h"
typedef struct
{
GtkMenuButton parent_instance;
DzlProgressIcon *icon;
const gchar *theatric_icon_name;
gdouble progress;
guint transition_duration;
guint show_theatric : 1;
guint suppress_theatric : 1;
} DzlProgressMenuButtonPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (DzlProgressMenuButton, dzl_progress_menu_button, GTK_TYPE_MENU_BUTTON)
enum {
PROP_0,
PROP_PROGRESS,
PROP_SHOW_THEATRIC,
PROP_THEATRIC_ICON_NAME,
PROP_TRANSITION_DURATION,
N_PROPS
};
static GParamSpec *properties [N_PROPS];
static void dzl_progress_menu_button_begin_theatrics (DzlProgressMenuButton *self);
GtkWidget *
dzl_progress_menu_button_new (void)
{
return g_object_new (DZL_TYPE_PROGRESS_MENU_BUTTON, NULL);
}
gdouble
dzl_progress_menu_button_get_progress (DzlProgressMenuButton *self)
{
DzlProgressMenuButtonPrivate *priv = dzl_progress_menu_button_get_instance_private (self);
g_return_val_if_fail (DZL_IS_PROGRESS_MENU_BUTTON (self), 0.0);
return priv->progress;
}
void
dzl_progress_menu_button_set_progress (DzlProgressMenuButton *self,
gdouble progress)
{
DzlProgressMenuButtonPrivate *priv = dzl_progress_menu_button_get_instance_private (self);
g_return_if_fail (DZL_IS_PROGRESS_MENU_BUTTON (self));
g_return_if_fail (progress >= 0.0);
g_return_if_fail (progress <= 1.0);
if (progress != priv->progress)
{
priv->progress = progress;
dzl_progress_icon_set_progress (priv->icon, progress);
if (progress == 1.0)
dzl_progress_menu_button_begin_theatrics (self);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PROGRESS]);
}
}
gboolean
dzl_progress_menu_button_get_show_theatric (DzlProgressMenuButton *self)
{
DzlProgressMenuButtonPrivate *priv = dzl_progress_menu_button_get_instance_private (self);
g_return_val_if_fail (DZL_IS_PROGRESS_MENU_BUTTON (self), FALSE);
return priv->show_theatric;
}
void
dzl_progress_menu_button_set_show_theatric (DzlProgressMenuButton *self,
gboolean show_theatric)
{
DzlProgressMenuButtonPrivate *priv = dzl_progress_menu_button_get_instance_private (self);
g_return_if_fail (DZL_IS_PROGRESS_MENU_BUTTON (self));
show_theatric = !!show_theatric;
if (priv->show_theatric != show_theatric)
{
priv->show_theatric = show_theatric;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHOW_THEATRIC]);
}
}
static gboolean
begin_theatrics_from_main (gpointer user_data)
{
DzlProgressMenuButton *self = user_data;
GtkAllocation rect;
g_assert (DZL_IS_PROGRESS_MENU_BUTTON (self));
/* Ignore if still ont allocated */
gtk_widget_get_allocation (GTK_WIDGET (self), &rect);
if (rect.x != -1 && rect.y != -1)
dzl_progress_menu_button_begin_theatrics (self);
return G_SOURCE_REMOVE;
}
static void
dzl_progress_menu_button_begin_theatrics (DzlProgressMenuButton *self)
{
DzlProgressMenuButtonPrivate *priv = dzl_progress_menu_button_get_instance_private (self);
g_autoptr(GIcon) icon = NULL;
DzlBoxTheatric *theatric;
GtkAllocation rect;
g_assert (DZL_IS_PROGRESS_MENU_BUTTON (self));
if (!priv->show_theatric || priv->transition_duration == 0 || priv->suppress_theatric)
return;
gtk_widget_get_allocation (GTK_WIDGET (self), &rect);
if (rect.x == -1 && rect.y == -1)
{
/* Delay this until our widget has been mapped/realized/displayed */
gdk_threads_add_idle_full (G_PRIORITY_LOW,
begin_theatrics_from_main,
g_object_ref (self), g_object_unref);
return;
}
rect.x = 0;
rect.y = 0;
icon = g_themed_icon_new (priv->theatric_icon_name);
theatric = g_object_new (DZL_TYPE_BOX_THEATRIC,
"alpha", 1.0,
"height", rect.height,
"icon", icon,
"target", self,
"width", rect.width,
"x", rect.x,
"y", rect.y,
NULL);
dzl_object_animate_full (theatric,
DZL_ANIMATION_EASE_OUT_CUBIC,
priv->transition_duration,
gtk_widget_get_frame_clock (GTK_WIDGET (self)),
g_object_unref,
theatric,
"x", rect.x - 60,
"width", rect.width + 120,
"y", rect.y,
"height", rect.height + 120,
"alpha", 0.0,
NULL);
priv->suppress_theatric = TRUE;
}
static void
dzl_progress_menu_button_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
DzlProgressMenuButton *self = DZL_PROGRESS_MENU_BUTTON (object);
DzlProgressMenuButtonPrivate *priv = dzl_progress_menu_button_get_instance_private (self);
switch (prop_id)
{
case PROP_PROGRESS:
g_value_set_double (value, priv->progress);
break;
case PROP_SHOW_THEATRIC:
g_value_set_boolean (value, priv->show_theatric);
break;
case PROP_THEATRIC_ICON_NAME:
g_value_set_static_string (value, priv->theatric_icon_name);
break;
case PROP_TRANSITION_DURATION:
g_value_set_uint (value, priv->transition_duration);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
dzl_progress_menu_button_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
DzlProgressMenuButton *self = DZL_PROGRESS_MENU_BUTTON (object);
DzlProgressMenuButtonPrivate *priv = dzl_progress_menu_button_get_instance_private (self);
switch (prop_id)
{
case PROP_PROGRESS:
dzl_progress_menu_button_set_progress (self, g_value_get_double (value));
break;
case PROP_SHOW_THEATRIC:
dzl_progress_menu_button_set_show_theatric (self, g_value_get_double (value));
break;
case PROP_THEATRIC_ICON_NAME:
priv->theatric_icon_name = g_intern_string (g_value_get_string (value));
break;
case PROP_TRANSITION_DURATION:
priv->transition_duration = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
dzl_progress_menu_button_class_init (DzlProgressMenuButtonClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = dzl_progress_menu_button_get_property;
object_class->set_property = dzl_progress_menu_button_set_property;
properties [PROP_PROGRESS] =
g_param_spec_double ("progress",
"Progress",
"Progress",
0.0, 1.0, 0.0,
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
properties [PROP_SHOW_THEATRIC] =
g_param_spec_boolean ("show-theatric",
"Show Theatric",
"Show Theatric",
TRUE,
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
properties [PROP_THEATRIC_ICON_NAME] =
g_param_spec_string ("theatric-icon-name",
"Theatric Icon Name",
"Theatric Icon Name",
"folder-download-symbolic",
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_TRANSITION_DURATION] =
g_param_spec_uint ("transition-duration",
"Transition Duration",
"Transition Duration",
0,
5000,
750,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
dzl_progress_menu_button_init (DzlProgressMenuButton *self)
{
DzlProgressMenuButtonPrivate *priv = dzl_progress_menu_button_get_instance_private (self);
priv->theatric_icon_name = g_intern_static_string ("folder-download-symbolic");
priv->show_theatric = TRUE;
priv->transition_duration = 750;
priv->icon = g_object_new (DZL_TYPE_PROGRESS_ICON,
"visible", TRUE,
NULL);
g_signal_connect (priv->icon,
"destroy",
G_CALLBACK (gtk_widget_destroyed),
&priv->icon);
gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (priv->icon));
}
/**
* dzl_progress_menu_button_reset_theatrics:
* @self: a #DzlProgressMenuButton
*
* To avoid suprious animations from the button, you must call this function any
* time you want to allow animations to continue. This is because animations are
* automatically started upon reaching a progress of 1.0.
*
* If you are performing operations in the background, calling this function
* every time you add an operation is a good strategry.
*/
void
dzl_progress_menu_button_reset_theatrics (DzlProgressMenuButton *self)
{
DzlProgressMenuButtonPrivate *priv = dzl_progress_menu_button_get_instance_private (self);
g_return_if_fail (DZL_IS_PROGRESS_MENU_BUTTON (self));
priv->suppress_theatric = FALSE;
}