~pantheon-photos/pantheon-photos/trunk

« back to all changes in this revision

Viewing changes to src/sidebar/Rating.vala

  • Committer: RabbitBot
  • Author(s): Maddie May, madelynn-r-may
  • Date: 2014-08-27 06:02:55 UTC
  • mfrom: (2546.1.21 fix-1332978)
  • Revision ID: rabbitbot-20140827060255-4619a5y4s9mqk8qs
Moves all photo metadata context menu editors to a right sidebar

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/***
 
2
    Copyright (C) 2012 Lucas Baudin <xapantu@gmail.com>
 
3
    Copyright (C) 2012-2014 Victor Martinez <victoreduardm@gmail.com>
 
4
 
 
5
    This program or library is free software; you can redistribute it
 
6
    and/or modify it under the terms of the GNU Lesser General Public
 
7
    License as published by the Free Software Foundation; either
 
8
    version 3 of the License, or (at your option) any later version.
 
9
 
 
10
    This library is distributed in the hope that it will be useful,
 
11
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 
13
    Lesser General Public License for more details.
 
14
 
 
15
    You should have received a copy of the GNU Lesser General
 
16
    Public License along with this library; if not, write to the
 
17
    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 
18
    Boston, MA 02110-1301 USA.
 
19
***/
 
20
 
 
21
/**
 
22
 * A star rating widget.
 
23
 *
 
24
 *
 
25
 */
 
26
public class PhotoRatingWidget : Gtk.EventBox {
 
27
    /**
 
28
     * Emitted when a new rating is selected.
 
29
     *
 
30
     * @param new_rating The new rating.
 
31
     *
 
32
     */
 
33
    public signal void rating_changed (int new_rating);
 
34
 
 
35
    /**
 
36
     * Whether to use symbolic star icons.
 
37
     *
 
38
     *
 
39
     */
 
40
    public bool symbolic {
 
41
        get {
 
42
            return renderer.symbolic;
 
43
        } set {
 
44
            renderer.symbolic = value;
 
45
        }
 
46
    }
 
47
 
 
48
    /**
 
49
     * Pixel size of star icons.
 
50
     *
 
51
     *
 
52
     */
 
53
    public int icon_size {
 
54
        get {
 
55
            return renderer.icon_size;
 
56
        } set {
 
57
            renderer.icon_size = value;
 
58
        }
 
59
    }
 
60
 
 
61
    /**
 
62
     * Total number of stars. It also represents the maximum rating possible.
 
63
     * That is, possible ratings are between 0 and //n_stars//.
 
64
     *
 
65
     * Allowed values: >= 0. Default: 5.
 
66
     *
 
67
     */
 
68
    public int n_stars {
 
69
        get {
 
70
            return renderer.n_stars;
 
71
        } set {
 
72
            renderer.n_stars = value;
 
73
        }
 
74
    }
 
75
 
 
76
    /**
 
77
     * Spacing inserted between star icons.
 
78
     *
 
79
     *
 
80
     */
 
81
    public int star_spacing {
 
82
        get {
 
83
            return renderer.star_spacing;
 
84
        } set {
 
85
            renderer.star_spacing = value;
 
86
        }
 
87
    }
 
88
 
 
89
 
 
90
    private int _rating = 0;
 
91
 
 
92
    /**
 
93
     * Current selected rating.
 
94
     *
 
95
     *
 
96
     */
 
97
    public int rating {
 
98
        get {
 
99
            return _rating;
 
100
        } set {
 
101
            _rating = value.clamp (0, n_stars);
 
102
            update_rating (_rating);
 
103
        }
 
104
    }
 
105
 
 
106
    internal double rating_offset {
 
107
        get {
 
108
            return renderer.rating_offset;
 
109
        } set {
 
110
            renderer.rating_offset = value;
 
111
        }
 
112
    }
 
113
 
 
114
    internal int item_width {
 
115
        get {
 
116
            return renderer.item_width;
 
117
        }
 
118
    }
 
119
 
 
120
    /**
 
121
     * Whether the widget will be centered with respect to its allocation.
 
122
     *
 
123
     *
 
124
     */
 
125
    public bool centered {
 
126
        get;
 
127
        set;
 
128
        default = false;
 
129
    }
 
130
 
 
131
    private PhotoRatingRenderer renderer;
 
132
    private int hover_rating = 0;
 
133
 
 
134
    /**
 
135
     * Creates a new Rating widget.
 
136
     *
 
137
     * @param centered Whether the widget should be centered with respect to its allocation.
 
138
     * @param size Pixel size of star icons.
 
139
     * @param symbolic Whether to use symbolic icons.
 
140
     *
 
141
     */
 
142
    public PhotoRatingWidget (bool centered, int size, bool symbolic = false) {
 
143
        this.centered = centered;
 
144
        this.renderer = new PhotoRatingRenderer (size, symbolic, get_style_context ());
 
145
        visible_window = false;
 
146
 
 
147
        add_events (Gdk.EventMask.BUTTON_PRESS_MASK
 
148
                    | Gdk.EventMask.BUTTON_RELEASE_MASK
 
149
                    | Gdk.EventMask.POINTER_MOTION_MASK
 
150
                    | Gdk.EventMask.LEAVE_NOTIFY_MASK);
 
151
 
 
152
        state_flags_changed.connect_after (() => {
 
153
            renderer.render ();
 
154
        });
 
155
 
 
156
        renderer.render.connect_after (() => {
 
157
            compute_size ();
 
158
            queue_draw ();
 
159
        });
 
160
    }
 
161
 
 
162
    private void compute_size () {
 
163
        this.set_size_request (renderer.width, renderer.height);
 
164
    }
 
165
 
 
166
    public override bool motion_notify_event (Gdk.EventMotion event) {
 
167
        int x_offset = 0;
 
168
 
 
169
        if (centered) {
 
170
            Gtk.Allocation al;
 
171
            get_allocation (out al);
 
172
            x_offset = (al.width - width_request) / 2;
 
173
        }
 
174
 
 
175
        hover_rating = renderer.get_new_rating (event.x - x_offset);
 
176
        update_rating (hover_rating);
 
177
        return true;
 
178
    }
 
179
 
 
180
    public override bool button_press_event (Gdk.EventButton event) {
 
181
        rating = hover_rating;
 
182
        rating_changed (rating);
 
183
        return true;
 
184
    }
 
185
 
 
186
    public override bool leave_notify_event (Gdk.EventCrossing ev) {
 
187
        update_rating (rating);
 
188
        return true;
 
189
    }
 
190
 
 
191
    internal void update_rating (int fake_rating) {
 
192
        renderer.rating = fake_rating;
 
193
        queue_draw ();
 
194
    }
 
195
 
 
196
    public override bool draw (Cairo.Context context) {
 
197
        Gtk.Allocation al;
 
198
        get_allocation (out al);
 
199
       // try {
 
200
            Gdk.cairo_set_source_pixbuf (context,
 
201
                                         renderer.canvas,
 
202
                                         centered ? (al.width - width_request) / 2 : 0,
 
203
                                         centered ? (al.height - height_request) / 2 : 0);
 
204
      //  } catch {
 
205
 
 
206
       // }
 
207
        context.paint ();
 
208
        return false;
 
209
    }
 
210
}
 
211
 
 
212
/**
 
213
 * A menu item that contains a rating widget.
 
214
 *
 
215
 *
 
216
 */
 
217
public class PhotoRatingMenuItem : Gtk.MenuItem {
 
218
    /**
 
219
     * Current displayed rating. Note that you should read this value
 
220
     * after the {@link Gtk.MenuItem.activate} signal is emitted.
 
221
     *
 
222
     *
 
223
     */
 
224
    public int rating_value {
 
225
        get {
 
226
            return rating.rating;
 
227
        } set {
 
228
            rating.rating = value;
 
229
        }
 
230
    }
 
231
 
 
232
    private PhotoRatingWidget rating;
 
233
 
 
234
    /**
 
235
     * Creates a new rating menu item.
 
236
     *
 
237
     *
 
238
     */
 
239
    public PhotoRatingMenuItem (int icon_size = 16) {
 
240
        rating = new PhotoRatingWidget (false, icon_size, false);
 
241
        add (rating);
 
242
 
 
243
        // Workaround. Move the offset one star to the left for menuitems.
 
244
        rating.rating_offset = - (double) rating.item_width - (double) rating.star_spacing;
 
245
 
 
246
        this.state_flags_changed.connect (() => {
 
247
            // Suppress SELECTED and PRELIGHT states, since these are usually obtrusive
 
248
            var selected_flags = Gtk.StateFlags.SELECTED | Gtk.StateFlags.PRELIGHT;
 
249
            if ((get_state_flags () & selected_flags) != 0)
 
250
                unset_state_flags (selected_flags);
 
251
        });
 
252
    }
 
253
 
 
254
    public override bool motion_notify_event (Gdk.EventMotion ev) {
 
255
        rating.motion_notify_event (ev);
 
256
        rating.queue_draw ();
 
257
        return true;
 
258
    }
 
259
 
 
260
    public override bool button_press_event (Gdk.EventButton ev) {
 
261
        rating.button_press_event (ev);
 
262
        activate ();
 
263
        return true;
 
264
    }
 
265
 
 
266
    public override bool leave_notify_event (Gdk.EventCrossing ev) {
 
267
        rating.update_rating (rating_value);
 
268
        return true;
 
269
    }
 
270
}
 
271
 
 
272
/**
 
273
 * This class makes setting the rating from a cell possible. Unlike the other widgets,
 
274
 * it only allows doing so by clicking over a star.
 
275
 *
 
276
 * When the rating changes by activating (i.e. clicking) the cell renderer, it doesn't re-draw itself
 
277
 * automatically and/or apply the new rating right away, since there could be client code wanting
 
278
 * to check the new value. Instead, it passes the responsability off to the rating_changed signal handler.
 
279
 * That signal handler ''must'' take care of setting the new rating on the proper cell, and only then
 
280
 * the new value will take effect.
 
281
 *
 
282
 *
 
283
 */
 
284
public class PhotoCellRendererRating : Gtk.CellRendererPixbuf {
 
285
    /**
 
286
     * New rating was set. It is only emmited when the rating changes by activating the renderer.
 
287
     *
 
288
     * @param new_rating new selected rating.
 
289
     * @param widget Widget that captured the event (e.g. a {@link Gtk.TreeView}).
 
290
     * @param path string representation of the tree path for the cell where the event occurred.
 
291
     *
 
292
     */
 
293
    public signal void rating_changed (int new_rating, Gtk.Widget widget, string path);
 
294
 
 
295
    private PhotoRatingRenderer renderer;
 
296
 
 
297
    /**
 
298
     * Creates a new rating cell renderer.
 
299
     *
 
300
     * @param icon_size Pixel size of the star icons
 
301
     *
 
302
     */
 
303
    public PhotoCellRendererRating (int icon_size = 16) {
 
304
        this.xalign = 0.0f;
 
305
        this.mode = Gtk.CellRendererMode.ACTIVATABLE;
 
306
 
 
307
        renderer = new PhotoRatingRenderer (icon_size, true, null);
 
308
 
 
309
        // We'll only redraw from render() for performance reasons
 
310
        renderer.delayed_render_mode = true;
 
311
 
 
312
        // Set rating to 1 star and render to init the 'width' property
 
313
        rating = 1;
 
314
        renderer.render ();
 
315
        update_pixbuf ();
 
316
    }
 
317
 
 
318
    /**
 
319
     * Spacing inserted between star icons.
 
320
     *
 
321
     *
 
322
     */
 
323
    public int star_spacing {
 
324
        get {
 
325
            return renderer.star_spacing;
 
326
        } set {
 
327
            renderer.star_spacing = value;
 
328
        }
 
329
    }
 
330
 
 
331
    private uint _rating = 0;
 
332
 
 
333
 
 
334
    /**
 
335
     * Current displayed rating.
 
336
     *
 
337
     *
 
338
     */
 
339
    public uint rating {
 
340
        get {
 
341
            return _rating;
 
342
        } set {
 
343
            _rating = value;
 
344
            renderer.rating = _rating;
 
345
        }
 
346
    }
 
347
 
 
348
    /**
 
349
     * Total number of stars. It also represents the maximum rating possible.
 
350
     * That is, possible ratings are between 0 and //n_stars//.
 
351
     *
 
352
     * Allowed values: >= 0. Default: 5.
 
353
     *
 
354
     */
 
355
    public int n_stars {
 
356
        get {
 
357
            return renderer.n_stars;
 
358
        } set {
 
359
            renderer.n_stars = value;
 
360
        }
 
361
    }
 
362
 
 
363
    private void update_pixbuf () {
 
364
        this.pixbuf = renderer.canvas;
 
365
        this.set_fixed_size (this.pixbuf.width, this.pixbuf.height);
 
366
    }
 
367
 
 
368
    public override void render (Cairo.Context ctx, Gtk.Widget widget,
 
369
                                 Gdk.Rectangle background_area, Gdk.Rectangle cell_area,
 
370
                                 Gtk.CellRendererState flags) {
 
371
        var style_context = widget.get_style_context ();
 
372
        var state = style_context.get_state ();
 
373
        int old_n_stars = n_stars;
 
374
 
 
375
        // Only draw stars of 0-rating if the cursor is over the cell
 
376
        if (_rating == 0 && (state & (Gtk.StateFlags.SELECTED | Gtk.StateFlags.PRELIGHT)) == 0)
 
377
            return;
 
378
 
 
379
        // Only show the filled stars if the row is neither selected nor mouseovered
 
380
        if (0 < _rating && (state & (Gtk.StateFlags.SELECTED | Gtk.StateFlags.PRELIGHT)) == 0)
 
381
            n_stars = (int) rating;
 
382
 
 
383
        renderer.style_context = style_context;
 
384
        renderer.render ();
 
385
        update_pixbuf ();
 
386
        base.render (ctx, widget, background_area, cell_area, flags);
 
387
        n_stars = old_n_stars;
 
388
    }
 
389
 
 
390
    public override bool activate (Gdk.Event event, Gtk.Widget widget, string path,
 
391
                                   Gdk.Rectangle background_area, Gdk.Rectangle cell_area,
 
392
                                   Gtk.CellRendererState flags) {
 
393
        int old_rating = (int) rating;
 
394
        int new_rating = renderer.get_new_rating (event.button.x - cell_area.x);
 
395
 
 
396
        // If the user clicks again over the same star, decrease the rating (i.e. "unset" the star)
 
397
        if (new_rating == old_rating && new_rating > 0)
 
398
            new_rating--;
 
399
 
 
400
        // emit signal
 
401
        rating_changed (new_rating, widget, path);
 
402
 
 
403
        return true;
 
404
    }
 
405
}
 
406
 
 
407
public class PhotoRatingRenderer : Object {
 
408
    /**
 
409
     * Whether to delay the rendering of the rating until the next call
 
410
     * to render() after a property change. This is recommended in cases
 
411
     * where there's an extensive amount of drawing and the renderer's
 
412
     * properties are constantly changing; for example, when used by
 
413
     * a Gtk.CellRenderer in a Gtk.TreeView; in such case, it's desirable
 
414
     * to have the renderer re-draw its pixbuf only on the next call to
 
415
     * Gtk.CellRenderer.render.
 
416
     *
 
417
     * Default value: false.
 
418
     *
 
419
     */
 
420
    public bool delayed_render_mode {
 
421
        get;
 
422
        set;
 
423
        default = false;
 
424
    }
 
425
 
 
426
    /**
 
427
     * The pixbuf containing the stars. Should not be modified.
 
428
     *
 
429
     * To listen for changes on this property, connect to the render() signal.
 
430
     * If you need to modify this pixbuf, create a copy first.
 
431
     *
 
432
     *
 
433
     */
 
434
    public Gdk.Pixbuf canvas {
 
435
        get;
 
436
        private set;
 
437
    }
 
438
 
 
439
    /**
 
440
     * Rating value to render.
 
441
     *
 
442
     * Default value: 0.
 
443
     *
 
444
     */
 
445
    public uint rating {
 
446
        get;
 
447
        set;
 
448
        default = 0;
 
449
    }
 
450
 
 
451
    /**
 
452
     * Maximum possible rating. This represents the total number of stars
 
453
     * that will be rendered.
 
454
     *
 
455
     * Default value: 5.
 
456
     *
 
457
     */
 
458
    public int n_stars {
 
459
        get;
 
460
        set;
 
461
        default = 5;
 
462
    }
 
463
 
 
464
    /**
 
465
     * The number of pixels inserted between star icons.
 
466
     *
 
467
     * Default value: 3.
 
468
     *
 
469
     */
 
470
    public int star_spacing {
 
471
        get;
 
472
        set;
 
473
        default = 3;
 
474
    }
 
475
 
 
476
    /**
 
477
     * Total width of rating pixbuf (in pixels).
 
478
     *
 
479
     *
 
480
     */
 
481
    public int width {
 
482
        get;
 
483
        private set;
 
484
        default = 0;
 
485
    }
 
486
 
 
487
    /**
 
488
     * Total height of rating pixbuf (in pixels).
 
489
     *
 
490
     *
 
491
     */
 
492
    public int height {
 
493
        get;
 
494
        private set;
 
495
        default = 0;
 
496
    }
 
497
 
 
498
    /**
 
499
     * The width of each rating star (in pixels).
 
500
     *
 
501
     *
 
502
     */
 
503
    public int item_width {
 
504
        get;
 
505
        private set;
 
506
        default = 0;
 
507
    }
 
508
 
 
509
    /**
 
510
     * The height of each rating star (in pixels).
 
511
     *
 
512
     *
 
513
     */
 
514
    public int item_height {
 
515
        get;
 
516
        private set;
 
517
        default = 0;
 
518
    }
 
519
 
 
520
    /**
 
521
     * Pixel size of rating icons.
 
522
     *
 
523
     *
 
524
     */
 
525
    public int icon_size {
 
526
        get;
 
527
        set;
 
528
        default = 16;
 
529
    }
 
530
 
 
531
    /**
 
532
     * Whether to use symbolic star icons.
 
533
     *
 
534
     *
 
535
     */
 
536
    public bool symbolic {
 
537
        get;
 
538
        set;
 
539
        default = false;
 
540
    }
 
541
 
 
542
    /**
 
543
     * Whether to append a //plus// icon at the end of the star rating.
 
544
     *
 
545
     * Default value: false.
 
546
     *
 
547
     */
 
548
    public bool add_plus_sign {
 
549
        get;
 
550
        set;
 
551
        default = false;
 
552
    }
 
553
 
 
554
    /**
 
555
     * Background color.
 
556
     *
 
557
     * Default value: transparent.
 
558
     *
 
559
     */
 
560
    public Gdk.RGBA background_color {
 
561
        get {
 
562
            return bg_color;
 
563
        } set {
 
564
            bg_color = value;
 
565
 
 
566
            // Convert RGBA from 0-1.0 value range to 0-255 (8-bit representation) by
 
567
            // simply multiplying values. Then we move the bits to their correct positions,
 
568
            // since we'll use a 32 bit integer to represent four 8-bit portions.
 
569
            // Please note the loss of precision.
 
570
            uint r = ((uint) (value.red * 255)) << 24;
 
571
            uint g = ((uint) (value.green * 255)) << 16;
 
572
            uint b = ((uint) (value.blue * 255)) << 8;
 
573
            uint a = ((uint) (value.alpha * 255));
 
574
 
 
575
            // bit positions do not overlap, so this is safe.
 
576
            pixel_color_rgba = r | g | b | a;
 
577
        }
 
578
    }
 
579
 
 
580
    /**
 
581
     * Style context to use as reference for drawing.
 
582
     *
 
583
     *
 
584
     */
 
585
    public Gtk.StyleContext? style_context {
 
586
        get {
 
587
            return current_context;
 
588
        } set {
 
589
            if (value != current_context) {
 
590
                if (current_context != null)
 
591
                    current_context.changed.disconnect (on_style_changed);
 
592
 
 
593
                current_context = value;
 
594
 
 
595
                if (current_context != null)
 
596
                    current_context.changed.connect (on_style_changed);
 
597
 
 
598
                on_style_changed ();
 
599
            }
 
600
        }
 
601
    }
 
602
 
 
603
    internal double rating_offset {
 
604
        get;
 
605
        set;
 
606
        default = 0;
 
607
    }
 
608
 
 
609
    // Icon cache. It stores the pixbufs rendered for every state until the
 
610
    // style information changes.
 
611
    private Gee.HashMap<int, Gdk.Pixbuf> starred_pixbufs;
 
612
    private Gee.HashMap<int, Gdk.Pixbuf> not_starred_pixbufs;
 
613
    private Gee.HashMap<int, Gdk.Pixbuf> plus_sign_pixbufs;
 
614
    private Gdk.RGBA bg_color;
 
615
    private uint pixel_color_rgba;
 
616
    private Gtk.StyleContext? current_context;
 
617
    // Whether a property has changed or not. Used to avoid unnecessary work in render()
 
618
    private bool property_changed = true;
 
619
 
 
620
    /**
 
621
     * Creates a rating renderer.
 
622
     *
 
623
     * @param icon_size pixel size of star icons
 
624
     * @param symbolic Whether to use symbolic icons
 
625
     * @param context style context to use as reference for rendering, or //null//.
 
626
     *
 
627
     */
 
628
    public PhotoRatingRenderer (int icon_size, bool symbolic, Gtk.StyleContext? context) {
 
629
        starred_pixbufs = new Gee.HashMap<int, Gdk.Pixbuf> ();
 
630
        not_starred_pixbufs = new Gee.HashMap<int, Gdk.Pixbuf> ();
 
631
        plus_sign_pixbufs = new Gee.HashMap<int, Gdk.Pixbuf> ();
 
632
 
 
633
        this.symbolic = symbolic;
 
634
        this.icon_size = icon_size;
 
635
        this.style_context = context;
 
636
        this.background_color = { 0, 0, 0, 0 };
 
637
 
 
638
        // Initial rendering. This is important; it will connect a handler
 
639
        // to the notify() signal, and will also init some properties, such
 
640
        // as item_width, item_height, width, height, etc.
 
641
        assert (property_changed);
 
642
        render ();
 
643
        assert (!property_changed);
 
644
    }
 
645
 
 
646
    public virtual signal void render () {
 
647
        if (!property_changed)
 
648
            return;
 
649
 
 
650
        disable_property_notify ();
 
651
 
 
652
        Gtk.StateFlags state = Gtk.StateFlags.NORMAL;
 
653
 
 
654
        // Only consider actual state if the stars should be symbolic.
 
655
        // Otherwise we consider the single state (NORMAL) set above.
 
656
        if (symbolic && style_context != null)
 
657
            state = style_context.get_state ();
 
658
 
 
659
        var starred_pix = starred_pixbufs.get (state);
 
660
        var not_starred_pix = not_starred_pixbufs.get (state);
 
661
        var plus_sign_pix = plus_sign_pixbufs.get (state);
 
662
 
 
663
        // if no cached star pixbufs were found, render them.
 
664
        var factory = Granite.Services.IconFactory.get_default ();
 
665
 
 
666
        if (starred_pix == null) {
 
667
            string starred = symbolic ? "starred-symbolic" : "starred";
 
668
            starred_pix = factory.load_symbolic_icon (style_context, starred, icon_size);
 
669
            starred_pixbufs.set (state, starred_pix);
 
670
        }
 
671
 
 
672
        if (not_starred_pix == null) {
 
673
            string not_starred = symbolic ? "non-starred-symbolic" : "non-starred";
 
674
            not_starred_pix = factory.load_symbolic_icon (style_context, not_starred, icon_size);
 
675
            not_starred_pixbufs.set (state, not_starred_pix);
 
676
        }
 
677
 
 
678
        if (add_plus_sign && plus_sign_pix == null)
 
679
            plus_sign_pix = factory.load_symbolic_icon (style_context, "list-add-symbolic", icon_size);
 
680
 
 
681
        if (starred_pix != null && not_starred_pix != null) {
 
682
            // Compute size
 
683
            item_width  = int.max (starred_pix.width, not_starred_pix.width);
 
684
            item_height = int.max (starred_pix.height, not_starred_pix.height);
 
685
 
 
686
            int new_width = (item_width + star_spacing) * n_stars - star_spacing;
 
687
            int new_height = item_height;
 
688
 
 
689
            if (add_plus_sign) {
 
690
                new_width += star_spacing + plus_sign_pix.width;
 
691
                new_height = int.max (new_height, plus_sign_pix.height);
 
692
            }
 
693
 
 
694
            // Generate canvas pixbuf
 
695
            if (canvas == null || new_width != width || new_height != height) {
 
696
                width = new_width;
 
697
                height = new_height;
 
698
                canvas = new Gdk.Pixbuf (Gdk.Colorspace.RGB, true, 8, width, height);
 
699
            }
 
700
 
 
701
            if (canvas != null) {
 
702
                var star_canvas = canvas.copy ();
 
703
                star_canvas.fill ((uint) 0xffffff00);
 
704
 
 
705
                // Render
 
706
                for (int i = 0; i <= n_stars; i++) {
 
707
                    Gdk.Pixbuf to_copy = null;
 
708
 
 
709
                    if (i == n_stars) {
 
710
                        if (!add_plus_sign)
 
711
                            break;
 
712
                        to_copy = plus_sign_pix;
 
713
                    } else if (i < rating) {
 
714
                        to_copy = starred_pix;
 
715
                    } else {
 
716
                        to_copy = not_starred_pix;
 
717
                    }
 
718
 
 
719
                    assert (to_copy != null);
 
720
 
 
721
                    int dest_x = i * (item_width + (i > 0 ? star_spacing : 0)), dest_y = 0;
 
722
                    to_copy.copy_area (0, 0, item_width, item_height, star_canvas, dest_x, dest_y);
 
723
                }
 
724
 
 
725
                canvas.fill (pixel_color_rgba);
 
726
                star_canvas.composite (canvas, 0, 0, canvas.width, canvas.height,
 
727
                                       0, 0, 1, 1, Gdk.InterpType.BILINEAR, 255);
 
728
            } else {
 
729
                warning ("NULL rating canvas");
 
730
            }
 
731
        }
 
732
 
 
733
        // No more work to do until the next property change
 
734
        property_changed = false;
 
735
 
 
736
        enable_property_notify ();
 
737
    }
 
738
 
 
739
    private inline void disable_property_notify () {
 
740
        notify.disconnect (on_property_changed);
 
741
    }
 
742
 
 
743
    private inline void enable_property_notify () {
 
744
        notify.connect (on_property_changed);
 
745
    }
 
746
 
 
747
    /*
 
748
     * Returns a new rating value between 0 and n_stars, based on the cursor position
 
749
     * relative to the left side of the widget (x = 0).
 
750
     *
 
751
     * LEGEND:
 
752
     * X : A STAR
 
753
     * - : SPACE
 
754
     *
 
755
     * |   x_offset   | | spacing | | spacing | | spacing | | spacing  ...  | remaining space...
 
756
     * <-------------> X --------- X --------- X --------- X --------- ... X ------------->
 
757
     * ... 0 stars    |   1 star  |  2 stars  |  3 stars  |   4 stars  ...| n_stars stars...
 
758
     *
 
759
     * The first row in the graphic above represents the values involved:
 
760
     * - x_offset : the value added in front of the first star.
 
761
     * - spacing  : space inserted between stars (star_spacing).
 
762
     * - n_stars  : total number of stars. It also represents the maximum rating.
 
763
     *
 
764
     * As you can see, you can modify the placement of the invisible value separators ("|")
 
765
     * by changing the value of x_offset. For instance, if you wanted the next star to be activated
 
766
     * when the cursor is at least halfway towards it, just modify x_offset. It should be similar
 
767
     * for other cases as well. 'rating_offset' uses exactly that mechanism to apply its value.
 
768
     */
 
769
    internal int get_new_rating (double x) {
 
770
        int x_offset = 0;
 
771
 
 
772
        x_offset -= (int) rating_offset;
 
773
 
 
774
        int cursor_x_pos = (int) x;
 
775
        int new_rating = 0;
 
776
 
 
777
        for (int i = 0; i < n_stars; i++) {
 
778
            if (cursor_x_pos > x_offset + i * (item_width + star_spacing))
 
779
                new_rating ++;
 
780
        }
 
781
 
 
782
        return new_rating;
 
783
    }
 
784
 
 
785
    private void on_style_changed () {
 
786
        // Invalidate old cached pixbufs
 
787
        starred_pixbufs.clear ();
 
788
        not_starred_pixbufs.clear ();
 
789
        plus_sign_pixbufs.clear ();
 
790
    }
 
791
 
 
792
    private void on_property_changed () {
 
793
        property_changed = true;
 
794
 
 
795
        if (!delayed_render_mode)
 
796
            render ();
 
797
    }
 
798
}
 
799
 
 
800