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 }