Line |
Branch |
Exec |
Source |
1 |
|
|
/* -*- mode: c; c-basic-offset: 2; indent-tabs-mode: nil; -*- */ |
2 |
|
|
/* cc-time-entry.c |
3 |
|
|
* |
4 |
|
|
* Copyright 2020 Purism SPC |
5 |
|
|
* |
6 |
|
|
* This program is free software: you can redistribute it and/or modify |
7 |
|
|
* it under the terms of the GNU General Public License as published by |
8 |
|
|
* the Free Software Foundation, either version 2 of the License, or |
9 |
|
|
* (at your option) any later version. |
10 |
|
|
* |
11 |
|
|
* This program is distributed in the hope that it will be useful, |
12 |
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 |
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 |
|
|
* GNU General Public License for more details. |
15 |
|
|
* |
16 |
|
|
* You should have received a copy of the GNU General Public License |
17 |
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
18 |
|
|
* |
19 |
|
|
* Author(s): |
20 |
|
|
* Mohammed Sadiq <sadiq@sadiqpk.org> |
21 |
|
|
* |
22 |
|
|
* SPDX-License-Identifier: GPL-3.0-or-later |
23 |
|
|
*/ |
24 |
|
|
|
25 |
|
|
#undef G_LOG_DOMAIN |
26 |
|
|
#define G_LOG_DOMAIN "cc-time-entry" |
27 |
|
|
|
28 |
|
|
#ifdef HAVE_CONFIG_H |
29 |
|
|
# include "config.h" |
30 |
|
|
#endif |
31 |
|
|
|
32 |
|
|
#include <gtk/gtk.h> |
33 |
|
|
#include <glib/gi18n.h> |
34 |
|
|
|
35 |
|
|
#include "cc-time-entry.h" |
36 |
|
|
|
37 |
|
|
#define SEPARATOR_INDEX 2 |
38 |
|
|
#define END_INDEX 4 |
39 |
|
|
#define EMIT_CHANGED_TIMEOUT 100 |
40 |
|
|
|
41 |
|
|
|
42 |
|
|
struct _CcTimeEntry |
43 |
|
|
{ |
44 |
|
|
GtkWidget parent_instance; |
45 |
|
|
|
46 |
|
|
GtkWidget *text; |
47 |
|
|
|
48 |
|
|
guint insert_text_id; |
49 |
|
|
guint time_changed_id; |
50 |
|
|
int hour; /* Range: 0-23 in 24H and 1-12 in 12H with is_am set/unset */ |
51 |
|
|
int minute; |
52 |
|
|
gboolean is_am_pm; |
53 |
|
|
gboolean is_am; /* AM if TRUE. PM if FALSE. valid iff is_am_pm set */ |
54 |
|
|
}; |
55 |
|
|
|
56 |
|
|
|
57 |
|
|
static void editable_insert_text_cb (GtkText *text, |
58 |
|
|
char *new_text, |
59 |
|
|
gint new_text_length, |
60 |
|
|
gint *position, |
61 |
|
|
CcTimeEntry *self); |
62 |
|
|
|
63 |
|
|
static void gtk_editable_interface_init (GtkEditableInterface *iface); |
64 |
|
|
|
65 |
|
✗ |
G_DEFINE_TYPE_WITH_CODE (CcTimeEntry, cc_time_entry, GTK_TYPE_WIDGET, |
66 |
|
|
G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE, gtk_editable_interface_init)); |
67 |
|
|
|
68 |
|
|
enum { |
69 |
|
|
CHANGE_VALUE, |
70 |
|
|
TIME_CHANGED, |
71 |
|
|
N_SIGNALS |
72 |
|
|
}; |
73 |
|
|
|
74 |
|
|
static guint signals[N_SIGNALS]; |
75 |
|
|
|
76 |
|
|
static gboolean |
77 |
|
✗ |
emit_time_changed (CcTimeEntry *self) |
78 |
|
|
{ |
79 |
|
✗ |
self->time_changed_id = 0; |
80 |
|
|
|
81 |
|
✗ |
g_signal_emit (self, signals[TIME_CHANGED], 0); |
82 |
|
|
|
83 |
|
✗ |
return G_SOURCE_REMOVE; |
84 |
|
|
} |
85 |
|
|
|
86 |
|
|
static void |
87 |
|
✗ |
time_entry_fill_time (CcTimeEntry *self) |
88 |
|
|
{ |
89 |
|
✗ |
g_autofree gchar *str = NULL; |
90 |
|
|
|
91 |
|
✗ |
g_assert (CC_IS_TIME_ENTRY (self)); |
92 |
|
|
|
93 |
|
✗ |
str = g_strdup_printf ("%02dā¶%02d", self->hour, self->minute); |
94 |
|
|
|
95 |
|
✗ |
g_signal_handlers_block_by_func (self->text, editable_insert_text_cb, self); |
96 |
|
✗ |
gtk_editable_set_text (GTK_EDITABLE (self->text), str); |
97 |
|
✗ |
g_signal_handlers_unblock_by_func (self->text, editable_insert_text_cb, self); |
98 |
|
✗ |
} |
99 |
|
|
|
100 |
|
|
static void |
101 |
|
✗ |
cursor_position_changed_cb (CcTimeEntry *self) |
102 |
|
|
{ |
103 |
|
|
int current_pos; |
104 |
|
|
|
105 |
|
✗ |
g_assert (CC_IS_TIME_ENTRY (self)); |
106 |
|
|
|
107 |
|
✗ |
current_pos = gtk_editable_get_position (GTK_EDITABLE (self)); |
108 |
|
|
|
109 |
|
✗ |
g_signal_handlers_block_by_func (self->text, cursor_position_changed_cb, self); |
110 |
|
|
|
111 |
|
|
/* If cursor is on ā:ā move to the next field */ |
112 |
|
✗ |
if (current_pos == SEPARATOR_INDEX) |
113 |
|
✗ |
gtk_editable_set_position (GTK_EDITABLE (self->text), current_pos + 1); |
114 |
|
|
|
115 |
|
|
/* If cursor is after the last digit and without selection, move to last digit */ |
116 |
|
✗ |
if (current_pos > END_INDEX && |
117 |
|
✗ |
!gtk_editable_get_selection_bounds (GTK_EDITABLE (self->text), NULL, NULL)) |
118 |
|
✗ |
gtk_editable_set_position (GTK_EDITABLE (self->text), END_INDEX); |
119 |
|
|
|
120 |
|
✗ |
g_signal_handlers_unblock_by_func (self->text, cursor_position_changed_cb, self); |
121 |
|
✗ |
} |
122 |
|
|
|
123 |
|
|
static void |
124 |
|
✗ |
entry_selection_changed_cb (CcTimeEntry *self) |
125 |
|
|
{ |
126 |
|
|
GtkEditable *editable; |
127 |
|
|
|
128 |
|
✗ |
g_assert (CC_IS_TIME_ENTRY (self)); |
129 |
|
|
|
130 |
|
✗ |
editable = GTK_EDITABLE (self->text); |
131 |
|
|
|
132 |
|
✗ |
g_signal_handlers_block_by_func (self->text, cursor_position_changed_cb, self); |
133 |
|
|
|
134 |
|
|
/* If cursor is after the last digit and without selection, move to last digit */ |
135 |
|
✗ |
if (gtk_editable_get_position (editable) > END_INDEX && |
136 |
|
✗ |
!gtk_editable_get_selection_bounds (editable, NULL, NULL)) |
137 |
|
✗ |
gtk_editable_set_position (editable, END_INDEX); |
138 |
|
|
|
139 |
|
✗ |
g_signal_handlers_unblock_by_func (self->text, cursor_position_changed_cb, self); |
140 |
|
✗ |
} |
141 |
|
|
|
142 |
|
|
static void |
143 |
|
✗ |
editable_insert_text_cb (GtkText *text, |
144 |
|
|
char *new_text, |
145 |
|
|
gint new_text_length, |
146 |
|
|
gint *position, |
147 |
|
|
CcTimeEntry *self) |
148 |
|
|
{ |
149 |
|
✗ |
g_assert (CC_IS_TIME_ENTRY (self)); |
150 |
|
|
|
151 |
|
✗ |
if (new_text_length == -1) |
152 |
|
✗ |
new_text_length = strlen (new_text); |
153 |
|
|
|
154 |
|
✗ |
if (new_text_length == 5) |
155 |
|
|
{ |
156 |
|
✗ |
const gchar *text = gtk_editable_get_text (GTK_EDITABLE (self)); |
157 |
|
|
guint16 text_length; |
158 |
|
|
|
159 |
|
✗ |
text_length = g_utf8_strlen (text, -1); |
160 |
|
|
|
161 |
|
|
/* Return if the text matches XX:XX template (where X is a number) */ |
162 |
|
✗ |
if (text_length == 0 && |
163 |
|
✗ |
strstr (new_text, "0123456789:") == new_text + new_text_length && |
164 |
|
✗ |
strchr (new_text, ':') == strrchr (new_text, ':')) |
165 |
|
✗ |
return; |
166 |
|
|
} |
167 |
|
|
|
168 |
|
|
/* Insert text if single digit number */ |
169 |
|
✗ |
if (new_text_length == 1 && |
170 |
|
✗ |
strspn (new_text, "0123456789")) |
171 |
|
|
{ |
172 |
|
|
int pos, number; |
173 |
|
|
|
174 |
|
✗ |
pos = *position; |
175 |
|
✗ |
number = *new_text - '0'; |
176 |
|
|
|
177 |
|
✗ |
if (pos == 0) |
178 |
|
✗ |
self->hour = self->hour % 10 + number * 10; |
179 |
|
✗ |
else if (pos == 1) |
180 |
|
✗ |
self->hour = self->hour / 10 * 10 + number; |
181 |
|
✗ |
else if (pos == 3) |
182 |
|
✗ |
self->minute = self->minute % 10 + number * 10; |
183 |
|
✗ |
else if (pos == 4) |
184 |
|
✗ |
self->minute = self->minute / 10 * 10 + number; |
185 |
|
|
|
186 |
|
✗ |
if (self->is_am_pm) |
187 |
|
✗ |
self->hour = CLAMP (self->hour, 1, 12); |
188 |
|
|
else |
189 |
|
✗ |
self->hour = CLAMP (self->hour, 0, 23); |
190 |
|
|
|
191 |
|
✗ |
self->minute = CLAMP (self->minute, 0, 59); |
192 |
|
|
|
193 |
|
✗ |
g_signal_stop_emission_by_name (text, "insert-text"); |
194 |
|
✗ |
time_entry_fill_time (self); |
195 |
|
✗ |
*position = pos + 1; |
196 |
|
|
|
197 |
|
✗ |
g_clear_handle_id (&self->time_changed_id, g_source_remove); |
198 |
|
✗ |
self->time_changed_id = g_timeout_add (EMIT_CHANGED_TIMEOUT, |
199 |
|
|
(GSourceFunc)emit_time_changed, self); |
200 |
|
✗ |
return; |
201 |
|
|
} |
202 |
|
|
|
203 |
|
|
/* Warn otherwise */ |
204 |
|
✗ |
g_signal_stop_emission_by_name (text, "insert-text"); |
205 |
|
✗ |
gtk_widget_error_bell (GTK_WIDGET (self)); |
206 |
|
|
} |
207 |
|
|
|
208 |
|
|
|
209 |
|
|
static gboolean |
210 |
|
✗ |
change_value_cb (GtkWidget *widget, |
211 |
|
|
GVariant *arguments, |
212 |
|
|
gpointer user_data) |
213 |
|
|
{ |
214 |
|
✗ |
CcTimeEntry *self = CC_TIME_ENTRY (widget); |
215 |
|
|
GtkScrollType type; |
216 |
|
|
int position; |
217 |
|
|
|
218 |
|
✗ |
g_assert (CC_IS_TIME_ENTRY (self)); |
219 |
|
|
|
220 |
|
✗ |
type = g_variant_get_int32 (arguments); |
221 |
|
✗ |
position = gtk_editable_get_position (GTK_EDITABLE (self)); |
222 |
|
|
|
223 |
|
✗ |
if (position > SEPARATOR_INDEX) |
224 |
|
|
{ |
225 |
|
✗ |
if (type == GTK_SCROLL_STEP_UP) |
226 |
|
✗ |
self->minute++; |
227 |
|
|
else |
228 |
|
✗ |
self->minute--; |
229 |
|
|
|
230 |
|
✗ |
if (self->minute >= 60) |
231 |
|
✗ |
self->minute = 0; |
232 |
|
✗ |
else if (self->minute <= -1) |
233 |
|
✗ |
self->minute = 59; |
234 |
|
|
} |
235 |
|
|
else |
236 |
|
|
{ |
237 |
|
✗ |
if (type == GTK_SCROLL_STEP_UP) |
238 |
|
✗ |
self->hour++; |
239 |
|
|
else |
240 |
|
✗ |
self->hour--; |
241 |
|
|
|
242 |
|
✗ |
if (self->is_am_pm) |
243 |
|
|
{ |
244 |
|
✗ |
if (self->hour > 12) |
245 |
|
✗ |
self->hour = 1; |
246 |
|
✗ |
else if (self->hour < 1) |
247 |
|
✗ |
self->hour = 12; |
248 |
|
|
} |
249 |
|
|
else |
250 |
|
|
{ |
251 |
|
✗ |
if (self->hour >= 24) |
252 |
|
✗ |
self->hour = 0; |
253 |
|
✗ |
else if (self->hour <= -1) |
254 |
|
✗ |
self->hour = 23; |
255 |
|
|
} |
256 |
|
|
} |
257 |
|
|
|
258 |
|
✗ |
time_entry_fill_time (self); |
259 |
|
✗ |
gtk_editable_set_position (GTK_EDITABLE (self), position); |
260 |
|
|
|
261 |
|
✗ |
g_clear_handle_id (&self->time_changed_id, g_source_remove); |
262 |
|
✗ |
self->time_changed_id = g_timeout_add (EMIT_CHANGED_TIMEOUT, |
263 |
|
|
(GSourceFunc)emit_time_changed, self); |
264 |
|
|
|
265 |
|
✗ |
return GDK_EVENT_STOP; |
266 |
|
|
} |
267 |
|
|
|
268 |
|
|
static void |
269 |
|
✗ |
value_changed_cb (CcTimeEntry *self, |
270 |
|
|
GtkScrollType type) |
271 |
|
|
{ |
272 |
|
✗ |
g_autoptr(GVariant) value; |
273 |
|
|
|
274 |
|
✗ |
g_assert (CC_IS_TIME_ENTRY (self)); |
275 |
|
|
|
276 |
|
✗ |
value = g_variant_new_int32 (type); |
277 |
|
|
|
278 |
|
✗ |
change_value_cb (GTK_WIDGET (self), value, NULL); |
279 |
|
✗ |
} |
280 |
|
|
|
281 |
|
|
static void |
282 |
|
✗ |
on_text_cut_clipboard_cb (GtkText *text, |
283 |
|
|
CcTimeEntry *self) |
284 |
|
|
{ |
285 |
|
✗ |
gtk_widget_error_bell (GTK_WIDGET (self)); |
286 |
|
✗ |
g_signal_stop_emission_by_name (text, "cut-clipboard"); |
287 |
|
✗ |
} |
288 |
|
|
|
289 |
|
|
static void |
290 |
|
✗ |
on_text_delete_from_cursor_cb (GtkText *text, |
291 |
|
|
GtkDeleteType *type, |
292 |
|
|
gint count, |
293 |
|
|
CcTimeEntry *self) |
294 |
|
|
{ |
295 |
|
✗ |
gtk_widget_error_bell (GTK_WIDGET (self)); |
296 |
|
✗ |
g_signal_stop_emission_by_name (text, "delete-from-cursor"); |
297 |
|
✗ |
} |
298 |
|
|
|
299 |
|
|
static void |
300 |
|
✗ |
on_text_move_cursor_cb (GtkText *text, |
301 |
|
|
GtkMovementStep step, |
302 |
|
|
gint count, |
303 |
|
|
gboolean extend, |
304 |
|
|
CcTimeEntry *self) |
305 |
|
|
{ |
306 |
|
|
int current_pos; |
307 |
|
|
|
308 |
|
✗ |
current_pos = gtk_editable_get_position (GTK_EDITABLE (self)); |
309 |
|
|
|
310 |
|
|
/* If cursor is on ā:ā move backward/forward depending on the current movement */ |
311 |
|
✗ |
if ((step == GTK_MOVEMENT_LOGICAL_POSITIONS || |
312 |
|
✗ |
step == GTK_MOVEMENT_VISUAL_POSITIONS) && |
313 |
|
✗ |
current_pos + count == SEPARATOR_INDEX) |
314 |
|
✗ |
count > 0 ? count++ : count--; |
315 |
|
|
|
316 |
|
✗ |
g_signal_handlers_block_by_func (text, on_text_move_cursor_cb, self); |
317 |
|
✗ |
gtk_editable_set_position (GTK_EDITABLE (text), current_pos + count); |
318 |
|
✗ |
g_signal_handlers_unblock_by_func (text, on_text_move_cursor_cb, self); |
319 |
|
|
|
320 |
|
✗ |
g_signal_stop_emission_by_name (text, "move-cursor"); |
321 |
|
✗ |
} |
322 |
|
|
|
323 |
|
|
static void |
324 |
|
✗ |
on_text_paste_clipboard_cb (GtkText *text, |
325 |
|
|
CcTimeEntry *self) |
326 |
|
|
{ |
327 |
|
✗ |
gtk_widget_error_bell (GTK_WIDGET (self)); |
328 |
|
✗ |
g_signal_stop_emission_by_name (text, "paste-clipboard"); |
329 |
|
✗ |
} |
330 |
|
|
|
331 |
|
|
static void |
332 |
|
✗ |
on_text_toggle_overwrite_cb (GtkText *text, |
333 |
|
|
CcTimeEntry *self) |
334 |
|
|
{ |
335 |
|
✗ |
gtk_widget_error_bell (GTK_WIDGET (self)); |
336 |
|
✗ |
g_signal_stop_emission_by_name (text, "toggle-overwrite"); |
337 |
|
✗ |
} |
338 |
|
|
|
339 |
|
|
static gboolean |
340 |
|
✗ |
on_key_pressed_cb (CcTimeEntry *self, |
341 |
|
|
guint keyval, |
342 |
|
|
guint keycode, |
343 |
|
|
GdkModifierType state) |
344 |
|
|
{ |
345 |
|
|
/* Allow entering numbers */ |
346 |
|
✗ |
if (!(state & GDK_SHIFT_MASK) && |
347 |
|
✗ |
((keyval >= GDK_KEY_KP_0 && keyval <= GDK_KEY_KP_9) || |
348 |
|
✗ |
(keyval >= GDK_KEY_0 && keyval <= GDK_KEY_9))) |
349 |
|
✗ |
return GDK_EVENT_PROPAGATE; |
350 |
|
|
|
351 |
|
|
/* Allow navigation keys */ |
352 |
|
✗ |
if ((keyval >= GDK_KEY_Left && keyval <= GDK_KEY_Down) || |
353 |
|
✗ |
(keyval >= GDK_KEY_KP_Left && keyval <= GDK_KEY_KP_Down) || |
354 |
|
✗ |
keyval == GDK_KEY_Home || |
355 |
|
✗ |
keyval == GDK_KEY_End || |
356 |
|
|
keyval == GDK_KEY_Menu) |
357 |
|
✗ |
return GDK_EVENT_PROPAGATE; |
358 |
|
|
|
359 |
|
✗ |
if (state & (GDK_CONTROL_MASK | GDK_ALT_MASK)) |
360 |
|
✗ |
return GDK_EVENT_PROPAGATE; |
361 |
|
|
|
362 |
|
✗ |
if (keyval == GDK_KEY_Tab) |
363 |
|
|
{ |
364 |
|
|
/* If focus is on Hour field skip to minute field */ |
365 |
|
✗ |
if (gtk_editable_get_position (GTK_EDITABLE (self)) <= 1) |
366 |
|
|
{ |
367 |
|
✗ |
gtk_editable_set_position (GTK_EDITABLE (self), SEPARATOR_INDEX + 1); |
368 |
|
|
|
369 |
|
✗ |
return GDK_EVENT_STOP; |
370 |
|
|
} |
371 |
|
|
|
372 |
|
✗ |
return GDK_EVENT_PROPAGATE; |
373 |
|
|
} |
374 |
|
|
|
375 |
|
|
/* Shift-Tab */ |
376 |
|
✗ |
if (keyval == GDK_KEY_ISO_Left_Tab) |
377 |
|
|
{ |
378 |
|
|
/* If focus is on Minute field skip back to Hour field */ |
379 |
|
✗ |
if (gtk_editable_get_position (GTK_EDITABLE (self)) >= 2) |
380 |
|
|
{ |
381 |
|
✗ |
gtk_editable_set_position (GTK_EDITABLE (self), 0); |
382 |
|
|
|
383 |
|
✗ |
return GDK_EVENT_STOP; |
384 |
|
|
} |
385 |
|
|
|
386 |
|
✗ |
return GDK_EVENT_PROPAGATE; |
387 |
|
|
} |
388 |
|
|
|
389 |
|
✗ |
return GDK_EVENT_STOP; |
390 |
|
|
} |
391 |
|
|
|
392 |
|
|
static GtkEditable * |
393 |
|
✗ |
cc_time_entry_get_delegate (GtkEditable *editable) |
394 |
|
|
{ |
395 |
|
✗ |
CcTimeEntry *self = CC_TIME_ENTRY (editable); |
396 |
|
✗ |
return GTK_EDITABLE (self->text); |
397 |
|
|
} |
398 |
|
|
|
399 |
|
|
static void |
400 |
|
✗ |
gtk_editable_interface_init (GtkEditableInterface *iface) |
401 |
|
|
{ |
402 |
|
✗ |
iface->get_delegate = cc_time_entry_get_delegate; |
403 |
|
✗ |
} |
404 |
|
|
|
405 |
|
|
static void |
406 |
|
✗ |
cc_time_entry_constructed (GObject *object) |
407 |
|
|
{ |
408 |
|
✗ |
CcTimeEntry *self = CC_TIME_ENTRY (object); |
409 |
|
|
PangoAttrList *list; |
410 |
|
|
PangoAttribute *attribute; |
411 |
|
|
|
412 |
|
✗ |
G_OBJECT_CLASS (cc_time_entry_parent_class)->constructed (object); |
413 |
|
|
|
414 |
|
✗ |
gtk_widget_set_direction (GTK_WIDGET (self->text), GTK_TEXT_DIR_LTR); |
415 |
|
✗ |
time_entry_fill_time (CC_TIME_ENTRY (object)); |
416 |
|
|
|
417 |
|
✗ |
list = pango_attr_list_new (); |
418 |
|
|
|
419 |
|
✗ |
attribute = pango_attr_size_new (PANGO_SCALE * 32); |
420 |
|
✗ |
pango_attr_list_insert (list, attribute); |
421 |
|
|
|
422 |
|
✗ |
attribute = pango_attr_weight_new (PANGO_WEIGHT_LIGHT); |
423 |
|
✗ |
pango_attr_list_insert (list, attribute); |
424 |
|
|
|
425 |
|
|
/* Use tabular(monospace) letters */ |
426 |
|
✗ |
attribute = pango_attr_font_features_new ("tnum"); |
427 |
|
✗ |
pango_attr_list_insert (list, attribute); |
428 |
|
|
|
429 |
|
✗ |
gtk_text_set_attributes (GTK_TEXT (self->text), list); |
430 |
|
|
|
431 |
|
✗ |
pango_attr_list_unref (list); |
432 |
|
✗ |
} |
433 |
|
|
|
434 |
|
|
static void |
435 |
|
✗ |
cc_time_entry_dispose (GObject *object) |
436 |
|
|
{ |
437 |
|
✗ |
CcTimeEntry *self = CC_TIME_ENTRY (object); |
438 |
|
|
|
439 |
|
✗ |
gtk_editable_finish_delegate (GTK_EDITABLE (self)); |
440 |
|
✗ |
g_clear_pointer (&self->text, gtk_widget_unparent); |
441 |
|
|
|
442 |
|
✗ |
G_OBJECT_CLASS (cc_time_entry_parent_class)->dispose (object); |
443 |
|
✗ |
} |
444 |
|
|
|
445 |
|
|
static void |
446 |
|
✗ |
cc_time_entry_get_property (GObject *object, |
447 |
|
|
guint property_id, |
448 |
|
|
GValue *value, |
449 |
|
|
GParamSpec *pspec) |
450 |
|
|
{ |
451 |
|
✗ |
if (gtk_editable_delegate_get_property (object, property_id, value, pspec)) |
452 |
|
✗ |
return; |
453 |
|
|
|
454 |
|
✗ |
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
455 |
|
|
} |
456 |
|
|
|
457 |
|
|
static void |
458 |
|
✗ |
cc_time_entry_set_property (GObject *object, |
459 |
|
|
guint property_id, |
460 |
|
|
const GValue *value, |
461 |
|
|
GParamSpec *pspec) |
462 |
|
|
{ |
463 |
|
✗ |
if (gtk_editable_delegate_set_property (object, property_id, value, pspec)) |
464 |
|
✗ |
return; |
465 |
|
|
|
466 |
|
✗ |
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
467 |
|
|
} |
468 |
|
|
|
469 |
|
|
static void |
470 |
|
✗ |
cc_time_entry_class_init (CcTimeEntryClass *klass) |
471 |
|
|
{ |
472 |
|
✗ |
GObjectClass *object_class = G_OBJECT_CLASS (klass); |
473 |
|
✗ |
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
474 |
|
|
|
475 |
|
✗ |
object_class->constructed = cc_time_entry_constructed; |
476 |
|
✗ |
object_class->dispose = cc_time_entry_dispose; |
477 |
|
✗ |
object_class->get_property = cc_time_entry_get_property; |
478 |
|
✗ |
object_class->set_property = cc_time_entry_set_property; |
479 |
|
|
|
480 |
|
✗ |
signals[CHANGE_VALUE] = |
481 |
|
✗ |
g_signal_new ("change-value", |
482 |
|
|
G_TYPE_FROM_CLASS (klass), |
483 |
|
|
G_SIGNAL_ACTION, |
484 |
|
|
0, NULL, NULL, |
485 |
|
|
NULL, |
486 |
|
|
G_TYPE_NONE, 1, |
487 |
|
|
GTK_TYPE_SCROLL_TYPE); |
488 |
|
|
|
489 |
|
✗ |
signals[TIME_CHANGED] = |
490 |
|
✗ |
g_signal_new ("time-changed", |
491 |
|
|
G_TYPE_FROM_CLASS (klass), |
492 |
|
|
G_SIGNAL_RUN_FIRST, |
493 |
|
|
0, NULL, NULL, |
494 |
|
|
NULL, |
495 |
|
|
G_TYPE_NONE, 0); |
496 |
|
|
|
497 |
|
✗ |
gtk_editable_install_properties (object_class, 1); |
498 |
|
|
|
499 |
|
✗ |
gtk_widget_class_set_css_name (widget_class, "entry"); |
500 |
|
✗ |
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); |
501 |
|
|
|
502 |
|
✗ |
gtk_widget_class_add_binding (widget_class, GDK_KEY_Up, 0, |
503 |
|
|
change_value_cb, "i", GTK_SCROLL_STEP_UP); |
504 |
|
✗ |
gtk_widget_class_add_binding (widget_class, GDK_KEY_KP_Up, 0, |
505 |
|
|
change_value_cb, "i", GTK_SCROLL_STEP_UP); |
506 |
|
✗ |
gtk_widget_class_add_binding (widget_class, GDK_KEY_Down, 0, |
507 |
|
|
change_value_cb, "i", GTK_SCROLL_STEP_DOWN); |
508 |
|
✗ |
gtk_widget_class_add_binding (widget_class, GDK_KEY_KP_Down, 0, |
509 |
|
|
change_value_cb, "i", GTK_SCROLL_STEP_DOWN); |
510 |
|
✗ |
} |
511 |
|
|
|
512 |
|
|
static void |
513 |
|
✗ |
cc_time_entry_init (CcTimeEntry *self) |
514 |
|
|
{ |
515 |
|
|
GtkEventController *key_controller; |
516 |
|
|
|
517 |
|
✗ |
key_controller = gtk_event_controller_key_new (); |
518 |
|
✗ |
gtk_event_controller_set_propagation_phase (key_controller, GTK_PHASE_CAPTURE); |
519 |
|
✗ |
g_signal_connect_swapped (key_controller, "key-pressed", G_CALLBACK (on_key_pressed_cb), self); |
520 |
|
✗ |
gtk_widget_add_controller (GTK_WIDGET (self), key_controller); |
521 |
|
|
|
522 |
|
✗ |
self->text = g_object_new (GTK_TYPE_TEXT, |
523 |
|
|
"input-purpose", GTK_INPUT_PURPOSE_DIGITS, |
524 |
|
|
"input-hints", GTK_INPUT_HINT_NO_EMOJI, |
525 |
|
|
"overwrite-mode", TRUE, |
526 |
|
|
"xalign", 0.5, |
527 |
|
|
"max-length", 5, |
528 |
|
|
NULL); |
529 |
|
✗ |
gtk_widget_set_parent (self->text, GTK_WIDGET (self)); |
530 |
|
✗ |
gtk_editable_init_delegate (GTK_EDITABLE (self)); |
531 |
|
✗ |
g_object_connect (self->text, |
532 |
|
|
"signal::cut-clipboard", on_text_cut_clipboard_cb, self, |
533 |
|
|
"signal::delete-from-cursor", on_text_delete_from_cursor_cb, self, |
534 |
|
|
"signal::insert-text", editable_insert_text_cb, self, |
535 |
|
|
"signal::move-cursor", on_text_move_cursor_cb, self, |
536 |
|
|
"swapped-signal::notify::cursor-position", cursor_position_changed_cb, self, |
537 |
|
|
"swapped-signal::notify::selection-bound", entry_selection_changed_cb, self, |
538 |
|
|
"signal::paste-clipboard", on_text_paste_clipboard_cb, self, |
539 |
|
|
"signal::toggle-overwrite", on_text_toggle_overwrite_cb, self, |
540 |
|
|
NULL); |
541 |
|
✗ |
g_signal_connect (self, "change-value", |
542 |
|
|
G_CALLBACK (value_changed_cb), self); |
543 |
|
✗ |
} |
544 |
|
|
|
545 |
|
|
GtkWidget * |
546 |
|
✗ |
cc_time_entry_new (void) |
547 |
|
|
{ |
548 |
|
✗ |
return g_object_new (CC_TYPE_TIME_ENTRY, NULL); |
549 |
|
|
} |
550 |
|
|
|
551 |
|
|
void |
552 |
|
✗ |
cc_time_entry_set_time (CcTimeEntry *self, |
553 |
|
|
guint hour, |
554 |
|
|
guint minute) |
555 |
|
|
{ |
556 |
|
|
gboolean is_am_pm; |
557 |
|
|
|
558 |
|
✗ |
g_return_if_fail (CC_IS_TIME_ENTRY (self)); |
559 |
|
|
|
560 |
|
✗ |
if (cc_time_entry_get_hour (self) == hour && |
561 |
|
✗ |
cc_time_entry_get_minute (self) == minute) |
562 |
|
✗ |
return; |
563 |
|
|
|
564 |
|
✗ |
is_am_pm = cc_time_entry_get_am_pm (self); |
565 |
|
✗ |
cc_time_entry_set_am_pm (self, FALSE); |
566 |
|
|
|
567 |
|
✗ |
self->hour = CLAMP (hour, 0, 23); |
568 |
|
✗ |
self->minute = CLAMP (minute, 0, 59); |
569 |
|
|
|
570 |
|
✗ |
cc_time_entry_set_am_pm (self, is_am_pm); |
571 |
|
|
|
572 |
|
✗ |
g_signal_emit (self, signals[TIME_CHANGED], 0); |
573 |
|
✗ |
time_entry_fill_time (self); |
574 |
|
|
} |
575 |
|
|
|
576 |
|
|
guint |
577 |
|
✗ |
cc_time_entry_get_hour (CcTimeEntry *self) |
578 |
|
|
{ |
579 |
|
✗ |
g_return_val_if_fail (CC_IS_TIME_ENTRY (self), 0); |
580 |
|
|
|
581 |
|
✗ |
if (!self->is_am_pm) |
582 |
|
✗ |
return self->hour; |
583 |
|
|
|
584 |
|
✗ |
if (self->is_am && self->hour == 12) |
585 |
|
✗ |
return 0; |
586 |
|
✗ |
else if (self->is_am || self->hour == 12) |
587 |
|
✗ |
return self->hour; |
588 |
|
|
else |
589 |
|
✗ |
return self->hour + 12; |
590 |
|
|
} |
591 |
|
|
|
592 |
|
|
guint |
593 |
|
✗ |
cc_time_entry_get_minute (CcTimeEntry *self) |
594 |
|
|
{ |
595 |
|
✗ |
g_return_val_if_fail (CC_IS_TIME_ENTRY (self), 0); |
596 |
|
|
|
597 |
|
✗ |
return self->minute; |
598 |
|
|
} |
599 |
|
|
|
600 |
|
|
gboolean |
601 |
|
✗ |
cc_time_entry_get_is_am (CcTimeEntry *self) |
602 |
|
|
{ |
603 |
|
✗ |
g_return_val_if_fail (CC_IS_TIME_ENTRY (self), FALSE); |
604 |
|
|
|
605 |
|
✗ |
if (self->is_am_pm) |
606 |
|
✗ |
return self->is_am; |
607 |
|
|
|
608 |
|
✗ |
return self->hour < 12; |
609 |
|
|
} |
610 |
|
|
|
611 |
|
|
void |
612 |
|
✗ |
cc_time_entry_set_is_am (CcTimeEntry *self, |
613 |
|
|
gboolean is_am) |
614 |
|
|
{ |
615 |
|
✗ |
g_return_if_fail (CC_IS_TIME_ENTRY (self)); |
616 |
|
|
|
617 |
|
✗ |
self->is_am = !!is_am; |
618 |
|
✗ |
g_signal_emit (self, signals[TIME_CHANGED], 0); |
619 |
|
|
} |
620 |
|
|
|
621 |
|
|
gboolean |
622 |
|
✗ |
cc_time_entry_get_am_pm (CcTimeEntry *self) |
623 |
|
|
{ |
624 |
|
✗ |
g_return_val_if_fail (CC_IS_TIME_ENTRY (self), FALSE); |
625 |
|
|
|
626 |
|
✗ |
return self->is_am_pm; |
627 |
|
|
} |
628 |
|
|
|
629 |
|
|
void |
630 |
|
✗ |
cc_time_entry_set_am_pm (CcTimeEntry *self, |
631 |
|
|
gboolean is_am_pm) |
632 |
|
|
{ |
633 |
|
✗ |
g_return_if_fail (CC_IS_TIME_ENTRY (self)); |
634 |
|
|
|
635 |
|
✗ |
if (self->is_am_pm == !!is_am_pm) |
636 |
|
✗ |
return; |
637 |
|
|
|
638 |
|
✗ |
if (self->hour < 12) |
639 |
|
✗ |
self->is_am = TRUE; |
640 |
|
|
else |
641 |
|
✗ |
self->is_am = FALSE; |
642 |
|
|
|
643 |
|
✗ |
if (is_am_pm) |
644 |
|
|
{ |
645 |
|
✗ |
if (self->hour == 0) |
646 |
|
✗ |
self->hour = 12; |
647 |
|
✗ |
else if (self->hour > 12) |
648 |
|
✗ |
self->hour = self->hour - 12; |
649 |
|
|
} |
650 |
|
|
else |
651 |
|
|
{ |
652 |
|
✗ |
if (self->hour == 12 && self->is_am) |
653 |
|
✗ |
self->hour = 0; |
654 |
|
✗ |
else if (!self->is_am) |
655 |
|
✗ |
self->hour = self->hour + 12; |
656 |
|
|
} |
657 |
|
|
|
658 |
|
✗ |
self->is_am_pm = !!is_am_pm; |
659 |
|
✗ |
time_entry_fill_time (self); |
660 |
|
|
} |
661 |
|
|
|