1 /*
2  * Copyright © 2018 Red Hat, Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Joaquim Rocha <jrocha@redhat.com>
18  *          Carlos Garnacho <carlosg@gnome.org>
19  */
20 #include "config.h"
21 #include "cc-clock.h"
22 
23 #include <math.h>
24 
25 #define CLOCK_RADIUS       50
26 #define CLOCK_LINE_WIDTH   10
27 #define CLOCK_LINE_PADDING 10
28 #define EXTRA_SPACE        2
29 
30 typedef struct _CcClock CcClock;
31 
32 struct _CcClock
33 {
34   GtkWidget parent_instance;
35   guint duration;
36   gint64 start_time;
37   gboolean running;
38 };
39 
40 enum
41 {
42   PROP_DURATION = 1,
43   N_PROPS
44 };
45 
46 static GParamSpec *props[N_PROPS] = { 0, };
47 
48 enum {
49   FINISHED,
50   N_SIGNALS
51 };
52 
53 static guint signals[N_SIGNALS] = { 0, };
54 
G_DEFINE_TYPE(CcClock,cc_clock,GTK_TYPE_WIDGET)55 G_DEFINE_TYPE (CcClock, cc_clock, GTK_TYPE_WIDGET)
56 
57 static gint64
58 cc_clock_get_time_diff (CcClock *clock)
59 {
60   GdkFrameClock *frame_clock;
61   gint64 current_time;
62 
63   frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (clock));
64   current_time = gdk_frame_clock_get_frame_time (frame_clock);
65 
66   return current_time - clock->start_time;
67 }
68 
69 static gdouble
cc_clock_get_angle(CcClock * clock)70 cc_clock_get_angle (CcClock *clock)
71 {
72   gint64 time_diff;
73 
74   time_diff = cc_clock_get_time_diff (clock);
75 
76   if (time_diff > clock->duration * 1000)
77     return 360;
78 
79   return ((gdouble) time_diff / (clock->duration * 1000)) * 360;
80 }
81 
82 static gboolean
cc_clock_draw(GtkWidget * widget,cairo_t * cr)83 cc_clock_draw (GtkWidget *widget,
84                cairo_t   *cr)
85 {
86   GtkAllocation allocation;
87   gdouble angle;
88 
89   gtk_widget_get_allocation (widget, &allocation);
90   angle = cc_clock_get_angle (CC_CLOCK (widget));
91 
92   cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
93   cairo_paint (cr);
94   cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
95 
96   /* Draw the clock background */
97   cairo_arc (cr, allocation.width / 2, allocation.height / 2, CLOCK_RADIUS / 2, 0.0, 2.0 * M_PI);
98   cairo_set_source_rgb (cr, 0.5, 0.5, 0.5);
99   cairo_fill_preserve (cr);
100   cairo_stroke (cr);
101 
102   cairo_set_line_width (cr, CLOCK_LINE_WIDTH);
103 
104   cairo_arc (cr,
105              allocation.width / 2,
106              allocation.height / 2,
107              (CLOCK_RADIUS - CLOCK_LINE_WIDTH - CLOCK_LINE_PADDING) / 2,
108              3 * M_PI_2,
109              3 * M_PI_2 + angle * M_PI / 180.0);
110   cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
111   cairo_stroke (cr);
112 
113   return TRUE;
114 }
115 
116 static void
cc_clock_stop(CcClock * clock)117 cc_clock_stop (CcClock *clock)
118 {
119   GdkFrameClock *frame_clock;
120 
121   if (!clock->running)
122     return;
123 
124   frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (clock));
125 
126   gdk_frame_clock_end_updating (frame_clock);
127   clock->running = FALSE;
128 }
129 
130 static void
on_frame_clock_update(CcClock * clock)131 on_frame_clock_update (CcClock *clock)
132 {
133   gint64 time_diff;
134 
135   if (!clock->running)
136     return;
137 
138   time_diff = cc_clock_get_time_diff (clock);
139 
140   if (time_diff > clock->duration * 1000)
141     {
142       g_signal_emit (clock, signals[FINISHED], 0);
143       cc_clock_stop (clock);
144     }
145 
146   gtk_widget_queue_draw (GTK_WIDGET (clock));
147 }
148 
149 static void
cc_clock_map(GtkWidget * widget)150 cc_clock_map (GtkWidget *widget)
151 {
152   GdkFrameClock *frame_clock;
153 
154   GTK_WIDGET_CLASS (cc_clock_parent_class)->map (widget);
155 
156   frame_clock = gtk_widget_get_frame_clock (widget);
157   g_signal_connect_object (frame_clock, "update",
158                            G_CALLBACK (on_frame_clock_update),
159                            widget, G_CONNECT_SWAPPED);
160   cc_clock_reset (CC_CLOCK (widget));
161 }
162 
163 static void
cc_clock_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)164 cc_clock_set_property (GObject      *object,
165                        guint         prop_id,
166                        const GValue *value,
167                        GParamSpec   *pspec)
168 {
169   CcClock *clock = CC_CLOCK (object);
170 
171   switch (prop_id)
172     {
173     case PROP_DURATION:
174       clock->duration = g_value_get_uint (value);
175       break;
176     default:
177       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
178       break;
179     }
180 }
181 
182 static void
cc_clock_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)183 cc_clock_get_property (GObject    *object,
184                        guint       prop_id,
185                        GValue     *value,
186                        GParamSpec *pspec)
187 {
188   CcClock *clock = CC_CLOCK (object);
189 
190   switch (prop_id)
191     {
192     case PROP_DURATION:
193       g_value_set_uint (value, clock->duration);
194       break;
195     default:
196       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
197       break;
198     }
199 }
200 
201 static void
cc_clock_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)202 cc_clock_get_preferred_width (GtkWidget *widget,
203                               gint      *minimum,
204                               gint      *natural)
205 {
206   if (minimum)
207     *minimum = CLOCK_RADIUS + EXTRA_SPACE;
208   if (natural)
209     *natural = CLOCK_RADIUS + EXTRA_SPACE;
210 }
211 
212 static void
cc_clock_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)213 cc_clock_get_preferred_height (GtkWidget *widget,
214                                gint      *minimum,
215                                gint      *natural)
216 {
217   if (minimum)
218     *minimum = CLOCK_RADIUS + EXTRA_SPACE;
219   if (natural)
220     *natural = CLOCK_RADIUS + EXTRA_SPACE;
221 }
222 
223 static void
cc_clock_class_init(CcClockClass * klass)224 cc_clock_class_init (CcClockClass *klass)
225 {
226   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
227   GObjectClass *object_class = G_OBJECT_CLASS (klass);
228 
229   object_class->set_property = cc_clock_set_property;
230   object_class->get_property = cc_clock_get_property;
231 
232   widget_class->map = cc_clock_map;
233   widget_class->draw = cc_clock_draw;
234   widget_class->get_preferred_width = cc_clock_get_preferred_width;
235   widget_class->get_preferred_height = cc_clock_get_preferred_height;
236 
237   signals[FINISHED] =
238     g_signal_new ("finished",
239                   CC_TYPE_CLOCK,
240                   G_SIGNAL_RUN_LAST,
241                   0, NULL, NULL, NULL,
242                   G_TYPE_NONE, 0);
243 
244   props[PROP_DURATION] =
245     g_param_spec_uint ("duration",
246                        "Duration",
247                        "Duration",
248                        0, G_MAXUINT, 0,
249                        G_PARAM_READWRITE |
250                        G_PARAM_STATIC_STRINGS |
251                        G_PARAM_CONSTRUCT_ONLY);
252 
253   g_object_class_install_properties (object_class, N_PROPS, props);
254 }
255 
256 static void
cc_clock_init(CcClock * clock)257 cc_clock_init (CcClock *clock)
258 {
259   gtk_widget_set_has_window (GTK_WIDGET (clock), FALSE);
260 }
261 
262 GtkWidget *
cc_clock_new(guint duration)263 cc_clock_new (guint duration)
264 {
265   return g_object_new (CC_TYPE_CLOCK,
266                        "duration", duration,
267                        NULL);
268 }
269 
270 void
cc_clock_reset(CcClock * clock)271 cc_clock_reset (CcClock *clock)
272 {
273   GdkFrameClock *frame_clock;
274 
275   if (!gtk_widget_get_mapped (GTK_WIDGET (clock)))
276     return;
277 
278   frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (clock));
279 
280   cc_clock_stop (clock);
281 
282   clock->running = TRUE;
283   clock->start_time = g_get_monotonic_time ();
284   gdk_frame_clock_begin_updating (frame_clock);
285 }
286 
287 void
cc_clock_set_duration(CcClock * clock,guint duration)288 cc_clock_set_duration (CcClock *clock,
289                        guint    duration)
290 {
291   clock->duration = duration;
292   g_object_notify (G_OBJECT (clock), "duration");
293   cc_clock_reset (clock);
294 }
295 
296 guint
cc_clock_get_duration(CcClock * clock)297 cc_clock_get_duration (CcClock *clock)
298 {
299   return clock->duration;
300 }
301