hythmbox-2.98/widgets/rb-rating.c

No issues found

  1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  2  *
  3  *  Copyright (C) 2002 Olivier Martin <olive.martin@gmail.com>
  4  *
  5  *  This program is free software; you can redistribute it and/or modify
  6  *  it under the terms of the GNU General Public License as published by
  7  *  the Free Software Foundation; either version 2 of the License, or
  8  *  (at your option) any later version.
  9  *
 10  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
 11  *  GStreamer plugins to be used and distributed together with GStreamer
 12  *  and Rhythmbox. This permission is above and beyond the permissions granted
 13  *  by the GPL license by which Rhythmbox is covered. If you modify this code
 14  *  you may extend this exception to your version of the code, but you are not
 15  *  obligated to do so. If you do not wish to do so, delete this exception
 16  *  statement from your version.
 17  *
 18  *  This program is distributed in the hope that it will be useful,
 19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 21  *  GNU General Public License for more details.
 22  *
 23  *  You should have received a copy of the GNU General Public License
 24  *  along with this program; if not, write to the Free Software
 25  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 26  *
 27  */
 28 
 29 #include "config.h"
 30 
 31 #include <string.h>
 32 #include <gtk/gtk.h>
 33 #include <gdk/gdkkeysyms.h>
 34 
 35 #include "rb-rating.h"
 36 #include "rb-rating-helper.h"
 37 #include "rb-stock-icons.h"
 38 #include "rb-cut-and-paste-code.h"
 39 
 40 /* Offset at the beggining of the widget */
 41 #define X_OFFSET 0
 42 
 43 /* Vertical offset */
 44 #define Y_OFFSET 2
 45 
 46 static void rb_rating_class_init (RBRatingClass *class);
 47 static void rb_rating_init (RBRating *label);
 48 static void rb_rating_finalize (GObject *object);
 49 static void rb_rating_get_property (GObject *object,
 50 				    guint param_id,
 51 				    GValue *value,
 52 				    GParamSpec *pspec);
 53 static void rb_rating_set_property (GObject *object,
 54 				    guint param_id,
 55 				    const GValue *value,
 56 				    GParamSpec *pspec);
 57 static void rb_rating_realize (GtkWidget *widget);
 58 static gboolean rb_rating_draw (GtkWidget *widget, cairo_t *cr);
 59 static gboolean rb_rating_focus (GtkWidget *widget, GtkDirectionType direction);
 60 static gboolean rb_rating_set_rating_cb (RBRating *rating, gdouble score);
 61 static gboolean rb_rating_adjust_rating_cb (RBRating *rating, gdouble adjust);
 62 static gboolean rb_rating_button_press_cb (GtkWidget *widget,
 63 					   GdkEventButton *event);
 64 static void rb_rating_get_preferred_width (GtkWidget *widget, int *minimum_width, int *natural_width);
 65 static void rb_rating_get_preferred_height (GtkWidget *widget, int *minimum_height, int *natural_height);
 66 
 67 struct _RBRatingPrivate
 68 {
 69 	double rating;
 70 	RBRatingPixbufs *pixbufs;
 71 };
 72 
 73 G_DEFINE_TYPE (RBRating, rb_rating, GTK_TYPE_WIDGET)
 74 #define RB_RATING_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_RATING, RBRatingPrivate))
 75 
 76 /**
 77  * SECTION:rb-rating
 78  * @short_description: widget for displaying song ratings
 79  *
 80  * This widget displays a rating (0-5 stars) and allows the user to
 81  * alter the rating by clicking.
 82  */
 83 
 84 enum
 85 {
 86 	PROP_0,
 87 	PROP_RATING
 88 };
 89 
 90 enum
 91 {
 92 	RATED,
 93 	SET_RATING,
 94 	ADJUST_RATING,
 95 	LAST_SIGNAL
 96 };
 97 
 98 static guint rb_rating_signals[LAST_SIGNAL] = { 0 };
 99 
100 static void
101 rb_rating_class_init (RBRatingClass *klass)
102 {
103 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
104 	GtkWidgetClass *widget_class;
105 	GtkBindingSet *binding_set;
106 
107 	widget_class = (GtkWidgetClass*) klass;
108 
109 	object_class->finalize = rb_rating_finalize;
110 	object_class->get_property = rb_rating_get_property;
111 	object_class->set_property = rb_rating_set_property;
112 
113 	widget_class->realize = rb_rating_realize;
114 	widget_class->draw = rb_rating_draw;
115 	widget_class->get_preferred_width = rb_rating_get_preferred_width;
116 	widget_class->get_preferred_height = rb_rating_get_preferred_height;
117 	widget_class->button_press_event = rb_rating_button_press_cb;
118 	widget_class->focus = rb_rating_focus;
119 
120 	klass->set_rating = rb_rating_set_rating_cb;
121 	klass->adjust_rating = rb_rating_adjust_rating_cb;
122 
123 	/**
124 	 * RBRating:rating:
125 	 *
126 	 * The rating displayed in the widget, as a floating point value
127 	 * between 0.0 and 5.0.
128 	 */
129 	rb_rating_install_rating_property (object_class, PROP_RATING);
130 
131 	/**
132 	 * RBRating::rated:
133 	 * @rating: the #RBRating
134 	 * @score: the new rating
135 	 *
136 	 * Emitted when the user changes the rating.
137 	 */
138 	rb_rating_signals[RATED] =
139 		g_signal_new ("rated",
140 			      G_OBJECT_CLASS_TYPE (object_class),
141 			      G_SIGNAL_RUN_LAST,
142 			      G_STRUCT_OFFSET (RBRatingClass, rated),
143 			      NULL, NULL,
144 			      g_cclosure_marshal_VOID__DOUBLE,
145 			      G_TYPE_NONE,
146 			      1,
147 			      G_TYPE_DOUBLE);
148 	/**
149 	 * RBRating::set-rating:
150 	 * @rating: the #RBRating
151 	 * @score: the new rating
152 	 *
153 	 * Action signal used to change the rating.
154 	 */
155 	rb_rating_signals[SET_RATING] =
156 		g_signal_new ("set-rating",
157 			      G_OBJECT_CLASS_TYPE (object_class),
158 			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
159 			      G_STRUCT_OFFSET (RBRatingClass, set_rating),
160 			      NULL, NULL,
161 			      g_cclosure_marshal_VOID__DOUBLE,
162 			      G_TYPE_NONE,
163 			      1,
164 			      G_TYPE_DOUBLE);
165 	/**
166 	 * RBRating::adjust-rating:
167 	 * @rating: the #RBRating
168 	 * @adjust: value to add to the rating
169 	 *
170 	 * Action signal used to make a relative adjustment to the
171 	 * rating.
172 	 */
173 	rb_rating_signals[ADJUST_RATING] =
174 		g_signal_new ("adjust-rating",
175 			      G_OBJECT_CLASS_TYPE (object_class),
176 			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
177 			      G_STRUCT_OFFSET (RBRatingClass, adjust_rating),
178 			      NULL, NULL,
179 			      g_cclosure_marshal_VOID__DOUBLE,
180 			      G_TYPE_NONE,
181 			      1,
182 			      G_TYPE_DOUBLE);
183 
184 	binding_set = gtk_binding_set_by_class (klass);
185 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_Home, 0, "set-rating", 1, G_TYPE_DOUBLE, 0.0);
186 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_End, 0, "set-rating", 1, G_TYPE_DOUBLE, (double)RB_RATING_MAX_SCORE);
187 
188 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_equal, 0, "adjust-rating", 1, G_TYPE_DOUBLE, 1.0);
189 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_plus, 0, "adjust-rating", 1, G_TYPE_DOUBLE, 1.0);
190 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Add, 0, "adjust-rating", 1, G_TYPE_DOUBLE, 1.0);
191 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_Right, 0, "adjust-rating", 1, G_TYPE_DOUBLE, 1.0);
192 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Right, 0, "adjust-rating", 1, G_TYPE_DOUBLE, 1.0);
193 	
194 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_minus, 0, "adjust-rating", 1, G_TYPE_DOUBLE, -1.0);
195 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Subtract, 0, "adjust-rating", 1, G_TYPE_DOUBLE, -1.0);
196 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_Left, 0, "adjust-rating", 1, G_TYPE_DOUBLE, -1.0);
197 	gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Left, 0, "adjust-rating", 1, G_TYPE_DOUBLE, -1.0);
198 	
199 	g_type_class_add_private (klass, sizeof (RBRatingPrivate));
200 }
201 
202 static void
203 rb_rating_init (RBRating *rating)
204 {
205 	rating->priv = RB_RATING_GET_PRIVATE (rating);
206 
207 	/* create the needed icons */
208 	rating->priv->pixbufs = rb_rating_pixbufs_new ();
209 	
210 	rb_rating_set_accessible_name (GTK_WIDGET (rating), 0.0);
211 
212 	gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (rating)),
213 				     GTK_STYLE_CLASS_ENTRY);
214 }
215 
216 static void
217 rb_rating_finalize (GObject *object)
218 {
219 	RBRating *rating;
220 
221 	rating = RB_RATING (object);
222 
223 	if (rating->priv->pixbufs != NULL) {
224 		rb_rating_pixbufs_free (rating->priv->pixbufs);
225 	}
226 
227 	G_OBJECT_CLASS (rb_rating_parent_class)->finalize (object);
228 }
229 
230 static void
231 rb_rating_get_property (GObject *object,
232 			guint param_id,
233 			GValue *value,
234 			GParamSpec *pspec)
235 {
236 	RBRating *rating = RB_RATING (object);
237 
238 	switch (param_id) {
239 	case PROP_RATING:
240 		g_value_set_double (value, rating->priv->rating);
241 		break;
242 	default:
243 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
244 		break;
245 	}
246 }
247 
248 static void
249 rb_rating_set_rating (RBRating *rating, gdouble value)
250 {
251 	/* clip to the value rating range */
252 	if (value > RB_RATING_MAX_SCORE) {
253 		value = RB_RATING_MAX_SCORE;
254 	} else if (value < 0.0) {
255 		value = 0.0;
256 	}
257 
258 	rating->priv->rating = value;
259 
260 	/* update accessible object name */
261 	rb_rating_set_accessible_name (GTK_WIDGET (rating), value);
262 
263 	gtk_widget_queue_draw (GTK_WIDGET (rating));
264 }
265 
266 
267 static void
268 rb_rating_set_property (GObject *object,
269 			guint param_id,
270 			const GValue *value,
271 			GParamSpec *pspec)
272 {
273 	RBRating *rating = RB_RATING (object);
274 
275 	switch (param_id) {
276 	case PROP_RATING:
277 		rb_rating_set_rating (rating, g_value_get_double (value));
278 		break;
279 	default:
280 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
281 		break;
282 	}
283 }
284 
285 /**
286  * rb_rating_new:
287  *
288  * Creates a new rating widget
289  *
290  * Return value: a new #RBRating widget.
291  */
292 RBRating *
293 rb_rating_new ()
294 {
295 	RBRating *rating;
296 
297 	rating = g_object_new (RB_TYPE_RATING, NULL);
298 
299 	g_return_val_if_fail (rating->priv != NULL, NULL);
300 
301 	return rating;
302 }
303 
304 static void
305 rb_rating_realize (GtkWidget *widget)
306 {
307 	GtkAllocation allocation;
308 	GdkWindowAttr attributes;
309 	GdkWindow *window;
310 	int attributes_mask;
311 
312 	gtk_widget_set_realized (widget, TRUE);
313 
314 	gtk_widget_get_allocation (widget, &allocation);
315 
316 	attributes.x = allocation.x;
317 	attributes.y = allocation.y;
318 	attributes.width = allocation.width;
319 	attributes.height = allocation.height;
320 	attributes.wclass = GDK_INPUT_OUTPUT;
321 	attributes.window_type = GDK_WINDOW_CHILD;
322 	attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_FOCUS_CHANGE_MASK;
323 	attributes.visual = gtk_widget_get_visual (widget);
324 
325 	attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
326 
327 	window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
328 	gtk_widget_set_window (widget, window);
329 	gdk_window_set_user_data (window, widget);
330 
331 	gtk_widget_set_can_focus (widget, TRUE);
332 }
333 
334 static void
335 rb_rating_get_preferred_width (GtkWidget *widget, int *minimum_width, int *natural_width)
336 {
337 	int icon_size;
338 	int width;
339 
340 	gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &icon_size, NULL);
341 
342 	width = RB_RATING_MAX_SCORE * icon_size + X_OFFSET;
343 	if (minimum_width != NULL)
344 		*minimum_width = width;
345 	if (natural_width != NULL)
346 		*natural_width = width;
347 }
348 
349 static void
350 rb_rating_get_preferred_height (GtkWidget *widget, int *minimum_height, int *natural_height)
351 {
352 	int icon_size;
353 	int height;
354 	gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &icon_size, NULL);
355 
356 	height = icon_size + Y_OFFSET * 2;
357 	if (minimum_height != NULL)
358 		*minimum_height = height;
359 	if (natural_height != NULL)
360 		*natural_height = height;
361 }
362 
363 static gboolean
364 rb_rating_draw (GtkWidget *widget, cairo_t *cr)
365 {
366 	gboolean ret;
367 	GdkWindow *window;
368 	RBRating *rating;
369 	int x = 0;
370 	int y = 0;
371 	int width;
372 	int height;
373 
374 	g_return_val_if_fail (RB_IS_RATING (widget), FALSE);
375 
376 	ret = FALSE;
377 	rating = RB_RATING (widget);
378 
379 	window = gtk_widget_get_window (widget);
380 	width = gdk_window_get_width (window);
381 	height = gdk_window_get_height (window);
382 
383 	gtk_render_background (gtk_widget_get_style_context (widget),
384 			       cr,
385 			       x, y,
386 			       width, height);
387 	gtk_render_frame (gtk_widget_get_style_context (widget),
388 			  cr,
389 			  x, y,
390 			  width, height);
391 
392 	if (gtk_widget_has_focus (widget)) {
393 		int focus_width;
394 		gtk_widget_style_get (widget, "focus-line-width", &focus_width, NULL);
395 
396 		x += focus_width;
397 		y += focus_width;
398 		width -= 2 * focus_width;
399 		height -= 2 * focus_width;
400 
401 		gtk_render_focus (gtk_widget_get_style_context (widget),
402 				  cr,
403 				  x, y,
404 				  width, height);
405 	}
406 
407 	/* draw the stars */
408 	if (rating->priv->pixbufs != NULL) {
409 		ret = rb_rating_render_stars (widget,
410 					      cr,
411 					      rating->priv->pixbufs,
412 					      0, 0,
413 					      X_OFFSET, Y_OFFSET,
414 					      rating->priv->rating,
415 					      FALSE);
416 	}
417 
418 	return ret;
419 }
420 
421 static gboolean
422 rb_rating_button_press_cb (GtkWidget *widget,
423 			   GdkEventButton *event)
424 {
425 	int mouse_x, mouse_y;
426 	double new_rating;
427 	RBRating *rating;
428 	GtkAllocation allocation;
429 	
430 	g_return_val_if_fail (widget != NULL, FALSE);
431 	g_return_val_if_fail (RB_IS_RATING (widget), FALSE);
432 
433 	rating = RB_RATING (widget);
434 
435 	gdk_window_get_device_position (gtk_widget_get_window (widget),
436 					gdk_event_get_source_device ((GdkEvent *)event),
437 					&mouse_x, &mouse_y, NULL);
438 	gtk_widget_get_allocation (widget, &allocation);
439 
440 	new_rating = rb_rating_get_rating_from_widget (widget, mouse_x,
441 						       allocation.width,
442 						       rating->priv->rating);
443 
444 	if (new_rating > -0.0001) {
445 		g_signal_emit (G_OBJECT (rating),
446 			       rb_rating_signals[RATED],
447 			       0, new_rating);
448 	}
449 
450 	gtk_widget_grab_focus (widget);
451 
452 	return FALSE;
453 }
454 
455 static gboolean
456 rb_rating_set_rating_cb (RBRating *rating, gdouble score)
457 {
458 	rb_rating_set_rating (rating, score);
459 	return TRUE;
460 }
461 
462 static gboolean
463 rb_rating_adjust_rating_cb (RBRating *rating, gdouble adjust)
464 {
465 	rb_rating_set_rating (rating, rating->priv->rating + adjust);
466 	return TRUE;
467 }
468 
469 static gboolean
470 rb_rating_focus (GtkWidget *widget, GtkDirectionType direction)
471 {
472 	return (GTK_WIDGET_CLASS (rb_rating_parent_class))->focus (widget, direction);
473 }