diff options
Diffstat (limited to '')
-rw-r--r-- | src/gs-star-widget.c | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/src/gs-star-widget.c b/src/gs-star-widget.c new file mode 100644 index 0000000..1d41705 --- /dev/null +++ b/src/gs-star-widget.c @@ -0,0 +1,337 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * vi:set noexpandtab tabstop=8 shiftwidth=8: + * + * Copyright (C) 2013 Richard Hughes <richard@hughsie.com> + * Copyright (C) 2014-2015 Kalev Lember <klember@redhat.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include "config.h" + +#include <math.h> + +#include "gs-common.h" +#include "gs-star-image.h" +#include "gs-star-widget.h" + +typedef struct +{ + gboolean interactive; + gint rating; + guint icon_size; + GtkWidget *box1; + GtkWidget *images[5]; +} GsStarWidgetPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GsStarWidget, gs_star_widget, GTK_TYPE_WIDGET) + +typedef enum { + PROP_ICON_SIZE = 1, + PROP_INTERACTIVE, + PROP_RATING, +} GsStarWidgetProperty; + +enum { + RATING_CHANGED, + SIGNAL_LAST +}; + +static GParamSpec *properties[PROP_RATING + 1] = { 0, }; +static guint signals [SIGNAL_LAST] = { 0 }; + +const gint rate_to_star[] = {20, 40, 60, 80, 100, -1}; + +static void gs_star_widget_refresh (GsStarWidget *star); + +gint +gs_star_widget_get_rating (GsStarWidget *star) +{ + GsStarWidgetPrivate *priv; + g_return_val_if_fail (GS_IS_STAR_WIDGET (star), -1); + priv = gs_star_widget_get_instance_private (star); + return priv->rating; +} + +void +gs_star_widget_set_icon_size (GsStarWidget *star, guint pixel_size) +{ + GsStarWidgetPrivate *priv; + g_return_if_fail (GS_IS_STAR_WIDGET (star)); + priv = gs_star_widget_get_instance_private (star); + + if (priv->icon_size == pixel_size) + return; + + priv->icon_size = pixel_size; + g_object_notify_by_pspec (G_OBJECT (star), properties[PROP_ICON_SIZE]); + gs_star_widget_refresh (star); +} + +static void +gs_star_widget_button_clicked_cb (GtkButton *button, GsStarWidget *star) +{ + GsStarWidgetPrivate *priv; + gint rating; + + priv = gs_star_widget_get_instance_private (star); + if (!priv->interactive) + return; + + rating = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), + "GsStarWidget::value")); + gs_star_widget_set_rating (star, rating); + g_signal_emit (star, signals[RATING_CHANGED], 0, priv->rating); +} + +/* Round to one digit, the same as the GsReviewHistogram */ +#define GS_ROUND(x) (round (((gdouble) (x)) * 10.0) / 10.0) + +/* Update the star styles to display the new rating */ +static void +gs_star_widget_refresh_rating (GsStarWidget *star) +{ + GsStarWidgetPrivate *priv = gs_star_widget_get_instance_private (star); + + if (!gtk_widget_get_realized (GTK_WIDGET (star))) + return; + + for (guint i = 0; i < G_N_ELEMENTS (priv->images); i++) { + GtkWidget *im = GTK_WIDGET (priv->images[i]); + gdouble fraction; + + if (priv->rating >= rate_to_star[i]) + fraction = 1.0; + else if (!i) + fraction = GS_ROUND (priv->rating / 20.0); + else if (priv->rating > rate_to_star[i - 1]) + fraction = GS_ROUND ((priv->rating - rate_to_star[i - 1]) / 20.0); + else + fraction = 0.0; + + gs_star_image_set_fraction (GS_STAR_IMAGE (im), fraction); + } +} + +static void +gs_star_widget_refresh (GsStarWidget *star) +{ + GsStarWidgetPrivate *priv = gs_star_widget_get_instance_private (star); + + if (!gtk_widget_get_realized (GTK_WIDGET (star))) + return; + + /* remove all existing widgets */ + gs_widget_remove_all (priv->box1, (GsRemoveFunc) gtk_box_remove); + + for (guint i = 0; i < G_N_ELEMENTS (priv->images); i++) { + GtkWidget *w; + GtkWidget *im; + + /* create image */ + im = gs_star_image_new (); + gtk_widget_set_size_request (im, (gint) priv->icon_size, (gint) priv->icon_size); + + priv->images[i] = im; + + /* create button */ + if (priv->interactive) { + w = gtk_button_new (); + g_signal_connect (w, "clicked", + G_CALLBACK (gs_star_widget_button_clicked_cb), star); + g_object_set_data (G_OBJECT (w), + "GsStarWidget::value", + GINT_TO_POINTER (rate_to_star[i])); + gtk_button_set_child (GTK_BUTTON (w), im); + gtk_widget_set_visible (im, TRUE); + } else { + w = im; + } + gtk_widget_set_sensitive (w, priv->interactive); + gtk_style_context_add_class (gtk_widget_get_style_context (w), "star"); + gtk_widget_set_visible (w, TRUE); + gtk_box_append (GTK_BOX (priv->box1), w); + } + + gs_star_widget_refresh_rating (star); +} + +void +gs_star_widget_set_interactive (GsStarWidget *star, gboolean interactive) +{ + GsStarWidgetPrivate *priv; + g_return_if_fail (GS_IS_STAR_WIDGET (star)); + priv = gs_star_widget_get_instance_private (star); + + if (priv->interactive == interactive) + return; + + priv->interactive = interactive; + g_object_notify_by_pspec (G_OBJECT (star), properties[PROP_INTERACTIVE]); + gs_star_widget_refresh (star); +} + +void +gs_star_widget_set_rating (GsStarWidget *star, + gint rating) +{ + GsStarWidgetPrivate *priv; + g_return_if_fail (GS_IS_STAR_WIDGET (star)); + priv = gs_star_widget_get_instance_private (star); + + if (priv->rating == rating) + return; + + priv->rating = rating; + g_object_notify_by_pspec (G_OBJECT (star), properties[PROP_RATING]); + gs_star_widget_refresh_rating (star); +} + +static void +gs_star_widget_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GsStarWidget *self = GS_STAR_WIDGET (object); + GsStarWidgetPrivate *priv = gs_star_widget_get_instance_private (self); + + switch ((GsStarWidgetProperty) prop_id) { + case PROP_ICON_SIZE: + g_value_set_uint (value, priv->icon_size); + break; + case PROP_INTERACTIVE: + g_value_set_boolean (value, priv->interactive); + break; + case PROP_RATING: + g_value_set_int (value, priv->rating); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gs_star_widget_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GsStarWidget *self = GS_STAR_WIDGET (object); + + switch ((GsStarWidgetProperty) prop_id) { + case PROP_ICON_SIZE: + gs_star_widget_set_icon_size (self, g_value_get_uint (value)); + break; + case PROP_INTERACTIVE: + gs_star_widget_set_interactive (self, g_value_get_boolean (value)); + break; + case PROP_RATING: + gs_star_widget_set_rating (self, g_value_get_int (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gs_star_widget_realize (GtkWidget *widget) +{ + GTK_WIDGET_CLASS (gs_star_widget_parent_class)->realize (widget); + + /* Create child widgets. */ + gs_star_widget_refresh (GS_STAR_WIDGET (widget)); +} + +static void +gs_star_widget_dispose (GObject *object) +{ + gs_widget_remove_all (GTK_WIDGET (object), NULL); + + G_OBJECT_CLASS (gs_star_widget_parent_class)->dispose (object); +} + +static void +gs_star_widget_init (GsStarWidget *star) +{ + gtk_widget_init_template (GTK_WIDGET (star)); +} + +static void +gs_star_widget_class_init (GsStarWidgetClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = gs_star_widget_dispose; + + widget_class->realize = gs_star_widget_realize; + object_class->get_property = gs_star_widget_get_property; + object_class->set_property = gs_star_widget_set_property; + + /** + * GsStarWidget:icon-size: + * + * Size of the star icons to use in the widget, in pixels. + * + * Since: 3.38 + */ + properties[PROP_ICON_SIZE] = + g_param_spec_uint ("icon-size", + "Icon Size", + "Size of icons to use, in pixels", + 0, G_MAXUINT, 12, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + /** + * GsStarWidget:interactive: + * + * Whether the widget accepts user input to change #GsStarWidget:rating. + * + * Since: 3.38 + */ + properties[PROP_INTERACTIVE] = + g_param_spec_boolean ("interactive", + "Interactive", + "Whether the rating is interactive", + FALSE, + G_PARAM_READWRITE); + + /** + * GsStarWidget:rating: + * + * The rating to display on the widget, as a percentage. `-1` indicates + * that the rating is unknown. + * + * Since: 3.38 + */ + properties[PROP_RATING] = + g_param_spec_int ("rating", + "Rating", + "Rating, out of 100%, or -1 for unknown", + -1, 100, -1, + G_PARAM_READWRITE); + + signals [RATING_CHANGED] = + g_signal_new ("rating-changed", + G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GsStarWidgetClass, rating_changed), + NULL, NULL, g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + + g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Software/gs-star-widget.ui"); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); + gtk_widget_class_bind_template_child_private (widget_class, GsStarWidget, box1); +} + +GtkWidget * +gs_star_widget_new (void) +{ + GsStarWidget *star; + star = g_object_new (GS_TYPE_STAR_WIDGET, NULL); + return GTK_WIDGET (star); +} |