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