Commit | Line | Data |
---|---|---|
4172f013 YB |
1 | /* This file is part of the Linux Trace Toolkit viewer |
2 | * Copyright (C) 2010 Yannick Brosseau | |
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 Version 2 as | |
6 | * published by the Free Software Foundation; | |
7 | * | |
8 | * This program is distributed in the hope that it will be useful, | |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | * GNU General Public License for more details. | |
12 | * | |
13 | * You should have received a copy of the GNU General Public License | |
14 | * along with this program; if not, write to the Free Software | |
15 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, | |
16 | * MA 02111-1307, USA. | |
17 | */ | |
18 | ||
19 | #include "timeentry.h" | |
20 | ||
21 | #include <string.h> | |
22 | #include <stdlib.h> | |
23 | #include <ctype.h> | |
24 | #include <gtk/gtksignal.h> | |
25 | ||
26 | enum { | |
27 | SIGNAL_TIME_CHANGED, | |
28 | LAST_SIGNAL | |
29 | }; | |
30 | ||
31 | static void timeentry_class_init(TimeentryClass *klass); | |
32 | static void timeentry_init(Timeentry *ttt); | |
33 | ||
34 | static guint timeentry_signals[LAST_SIGNAL] = { 0 }; | |
35 | static unsigned int MAX_NANOSECONDS = 999999999; | |
36 | ||
37 | static void on_spinner_value_changed (GtkSpinButton *spinbutton, | |
38 | gpointer user_data); | |
39 | ||
40 | static gboolean on_label_click(GtkWidget *widget, | |
41 | GdkEventButton *event, | |
42 | gpointer data); | |
43 | static void on_menu_copy(gpointer data); | |
44 | static void on_menu_paste(gpointer callback_data, | |
45 | guint callback_action, | |
46 | GtkWidget *widget); | |
47 | ||
48 | static void clipboard_receive(GtkClipboard *clipboard, | |
49 | const gchar *text, | |
50 | gpointer data); | |
51 | ||
52 | GType timeentry_get_type(void) | |
53 | { | |
54 | static GType te_type = 0; | |
55 | ||
56 | if (!te_type) { | |
57 | const GTypeInfo te_info = | |
58 | { | |
59 | sizeof (TimeentryClass), | |
60 | NULL, /* base_init */ | |
61 | NULL, /* base_finalize */ | |
62 | (GClassInitFunc) timeentry_class_init, | |
63 | NULL, /* class_finalize */ | |
64 | NULL, /* class_data */ | |
65 | sizeof (Timeentry), | |
66 | 0, /* n_preallocs */ | |
67 | (GInstanceInitFunc) timeentry_init, | |
68 | }; | |
69 | ||
70 | te_type = g_type_register_static (GTK_TYPE_HBOX, | |
71 | "Timeentry", | |
72 | &te_info, | |
73 | 0); | |
74 | } | |
75 | ||
76 | return te_type; | |
77 | } | |
78 | ||
79 | static void timeentry_class_init(TimeentryClass *klass) | |
80 | { | |
81 | timeentry_signals[SIGNAL_TIME_CHANGED] = g_signal_new ("time-changed", | |
82 | G_TYPE_FROM_CLASS (klass), | |
83 | G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, | |
84 | G_STRUCT_OFFSET (TimeentryClass, timeentry), | |
85 | NULL, | |
86 | NULL, | |
87 | g_cclosure_marshal_VOID__VOID, | |
88 | G_TYPE_NONE, 0); | |
89 | } | |
90 | ||
91 | static void timeentry_init(Timeentry *timeentry) | |
92 | { | |
93 | ||
94 | /* Set default minmax */ | |
95 | timeentry->min_seconds = 0; | |
96 | timeentry->min_nanoseconds = 0; | |
97 | timeentry->max_seconds = 1; | |
98 | timeentry->max_nanoseconds = 1; | |
99 | ||
100 | /* Add main label*/ | |
101 | timeentry->main_label = gtk_label_new(NULL); | |
102 | gtk_widget_show(timeentry->main_label); | |
103 | ||
104 | timeentry->main_label_box = gtk_event_box_new(); | |
105 | gtk_widget_show(timeentry->main_label_box); | |
106 | gtk_container_add(GTK_CONTAINER(timeentry->main_label_box), timeentry->main_label); | |
107 | ||
108 | gtk_widget_set_tooltip_text(timeentry->main_label_box, "Paste time here"); | |
109 | ||
110 | /* Add seconds spinner */ | |
111 | timeentry->seconds_spinner = gtk_spin_button_new_with_range(timeentry->min_seconds, | |
112 | timeentry->max_seconds, | |
113 | 1.0); | |
114 | gtk_spin_button_set_digits(GTK_SPIN_BUTTON(timeentry->seconds_spinner), 0); | |
115 | gtk_spin_button_set_snap_to_ticks(GTK_SPIN_BUTTON(timeentry->seconds_spinner), TRUE); | |
116 | gtk_widget_show(timeentry->seconds_spinner); | |
117 | ||
118 | /* Add nanoseconds spinner */ | |
119 | /* TODO ybrosseau 2010-11-24: Add wrap management */ | |
120 | timeentry->nanoseconds_spinner = gtk_spin_button_new_with_range(timeentry->min_nanoseconds, | |
121 | timeentry->max_nanoseconds, | |
122 | 1.0); | |
123 | gtk_spin_button_set_digits(GTK_SPIN_BUTTON(timeentry->nanoseconds_spinner), 0); | |
124 | gtk_spin_button_set_snap_to_ticks(GTK_SPIN_BUTTON(timeentry->nanoseconds_spinner), TRUE); | |
125 | gtk_widget_show(timeentry->nanoseconds_spinner); | |
126 | ||
127 | /* s and ns labels */ | |
128 | timeentry->s_label = gtk_label_new("s "); | |
129 | gtk_widget_show(timeentry->s_label); | |
130 | timeentry->ns_label = gtk_label_new("ns "); | |
131 | gtk_widget_show(timeentry->ns_label); | |
132 | ||
133 | /* Pack everything */ | |
134 | gtk_box_pack_start (GTK_BOX (timeentry), timeentry->main_label_box, FALSE, FALSE, 0); | |
135 | gtk_box_pack_start (GTK_BOX (timeentry), timeentry->seconds_spinner, FALSE, FALSE, 0); | |
136 | gtk_box_pack_start (GTK_BOX (timeentry), timeentry->s_label, FALSE, FALSE, 1); | |
137 | gtk_box_pack_start (GTK_BOX (timeentry), timeentry->nanoseconds_spinner, FALSE, FALSE, 0); | |
138 | gtk_box_pack_start (GTK_BOX (timeentry), timeentry->ns_label, FALSE, FALSE, 1); | |
139 | ||
140 | timeentry->seconds_changed_handler_id = | |
141 | g_signal_connect ((gpointer) timeentry->seconds_spinner, "value-changed", | |
142 | G_CALLBACK (on_spinner_value_changed), | |
143 | timeentry); | |
144 | ||
145 | timeentry->nanoseconds_changed_handler_id = | |
146 | g_signal_connect ((gpointer) timeentry->nanoseconds_spinner, "value-changed", | |
147 | G_CALLBACK (on_spinner_value_changed), | |
148 | timeentry); | |
149 | ||
150 | /* Add pasting callbacks */ | |
151 | g_signal_connect ((gpointer) timeentry->main_label_box, "button-press-event", | |
152 | G_CALLBACK (on_label_click), | |
153 | timeentry); | |
154 | ||
155 | /* Create pasting context-menu */ | |
156 | GtkItemFactory *item_factory; | |
157 | /* Our menu, an array of GtkItemFactoryEntry structures that defines each menu item */ | |
158 | GtkItemFactoryEntry menu_items[] = { | |
159 | { "/Copy time", NULL, on_menu_copy, 0, "<Item>" }, | |
160 | { "/Paste time", NULL, on_menu_paste, 0, "<Item>" }, | |
161 | }; | |
162 | ||
163 | gint nmenu_items = sizeof (menu_items) / sizeof (menu_items[0]); | |
164 | ||
165 | item_factory = gtk_item_factory_new (GTK_TYPE_MENU, "<main_label>", | |
166 | NULL); | |
167 | gtk_item_factory_create_items (item_factory, nmenu_items, menu_items, timeentry); | |
168 | timeentry->main_label_context_menu = gtk_item_factory_get_widget (item_factory, "<main_label>"); | |
169 | } | |
170 | ||
171 | void timeentry_set_main_label (Timeentry *timeentry, | |
172 | const gchar *str) | |
173 | { | |
174 | g_return_if_fail (IS_TIMEENTRY (timeentry)); | |
175 | ||
176 | g_object_freeze_notify (G_OBJECT (timeentry)); | |
177 | ||
178 | gtk_label_set_label(GTK_LABEL(timeentry->main_label), str); | |
179 | ||
180 | g_object_thaw_notify (G_OBJECT (timeentry)); | |
181 | } | |
182 | ||
183 | static void timeentry_update_nanoseconds_spinner_range(Timeentry *timeentry, | |
184 | unsigned long current_seconds) | |
185 | { | |
186 | if (current_seconds > timeentry->min_seconds && current_seconds < timeentry->max_seconds) { | |
187 | /* We are not at a limit, set the spinner to full range */ | |
188 | gtk_spin_button_set_range(GTK_SPIN_BUTTON(timeentry->nanoseconds_spinner), | |
189 | 0, | |
190 | MAX_NANOSECONDS); | |
191 | } else if (timeentry->min_seconds == timeentry->max_seconds) { | |
192 | /* special case were the time span is less than a second */ | |
193 | gtk_spin_button_set_range(GTK_SPIN_BUTTON(timeentry->nanoseconds_spinner), | |
194 | timeentry->min_nanoseconds, | |
195 | timeentry->max_nanoseconds); | |
196 | ||
197 | } else if (current_seconds <= timeentry->min_seconds) { | |
198 | /* We are a the start limit */ | |
199 | gtk_spin_button_set_range(GTK_SPIN_BUTTON(timeentry->nanoseconds_spinner), | |
200 | timeentry->min_nanoseconds, | |
201 | MAX_NANOSECONDS); | |
202 | } else if (current_seconds >= timeentry->max_seconds) { | |
203 | /* We are a the stop limit */ | |
204 | gtk_spin_button_set_range(GTK_SPIN_BUTTON(timeentry->nanoseconds_spinner), | |
205 | 0, | |
206 | timeentry->max_nanoseconds); | |
207 | } else { | |
208 | /* Should never happen */ | |
209 | g_assert(FALSE); | |
210 | } | |
211 | } | |
212 | ||
213 | void timeentry_set_minmax_time(Timeentry *timeentry, | |
214 | unsigned long min_seconds, | |
215 | unsigned long min_nanoseconds, | |
216 | unsigned long max_seconds, | |
217 | unsigned long max_nanoseconds) | |
218 | { | |
219 | unsigned long current_seconds; | |
220 | unsigned long current_nanoseconds; | |
221 | ||
222 | timeentry_get_time(timeentry, ¤t_seconds, ¤t_nanoseconds); | |
223 | ||
224 | if (min_seconds > max_seconds || | |
225 | (min_seconds == max_seconds && min_nanoseconds > max_nanoseconds)) { | |
226 | return; | |
227 | } | |
228 | ||
229 | timeentry->min_seconds = min_seconds; | |
230 | timeentry->min_nanoseconds = min_nanoseconds; | |
231 | timeentry->max_seconds = max_seconds; | |
232 | timeentry->max_nanoseconds = max_nanoseconds; | |
233 | ||
234 | /* Disable the widgets if there is no range possible */ | |
235 | if (min_seconds == max_seconds && | |
236 | min_nanoseconds == max_nanoseconds) { | |
237 | gtk_widget_set_sensitive(timeentry->seconds_spinner, FALSE); | |
238 | gtk_widget_set_sensitive(timeentry->nanoseconds_spinner, FALSE); | |
239 | ||
240 | } else { | |
241 | gtk_widget_set_sensitive(timeentry->seconds_spinner, TRUE); | |
242 | gtk_widget_set_sensitive(timeentry->nanoseconds_spinner, TRUE); | |
243 | } | |
244 | ||
245 | /* Set the new time range */ | |
246 | gtk_spin_button_set_range(GTK_SPIN_BUTTON(timeentry->seconds_spinner), | |
247 | timeentry->min_seconds, | |
248 | timeentry->max_seconds); | |
249 | ||
250 | timeentry_update_nanoseconds_spinner_range(timeentry, | |
251 | current_seconds); | |
252 | ||
253 | /* Update time if necessary */ | |
254 | timeentry_set_time(timeentry, current_seconds, current_nanoseconds); | |
255 | } | |
256 | ||
257 | void timeentry_set_time(Timeentry *timeentry, | |
258 | unsigned long seconds, | |
259 | unsigned long nanoseconds) | |
260 | { | |
261 | /* Set the passed time in the valid range */ | |
262 | if (seconds < timeentry->min_seconds) { | |
263 | seconds = timeentry->min_seconds; | |
264 | nanoseconds = timeentry->min_nanoseconds; | |
265 | ||
266 | } | |
267 | if (seconds == timeentry->min_seconds && | |
268 | nanoseconds < timeentry->min_nanoseconds) { | |
269 | nanoseconds = timeentry->min_nanoseconds; | |
270 | } | |
271 | if (seconds > timeentry->max_seconds) { | |
272 | seconds = timeentry->max_seconds; | |
273 | nanoseconds = timeentry->max_nanoseconds; | |
274 | } | |
275 | if (seconds == timeentry->max_seconds && | |
276 | nanoseconds > timeentry->max_nanoseconds) { | |
277 | nanoseconds = timeentry->max_nanoseconds; | |
278 | } | |
279 | ||
280 | if ((gtk_spin_button_get_value (GTK_SPIN_BUTTON(timeentry->seconds_spinner)) == seconds) && | |
281 | (gtk_spin_button_get_value (GTK_SPIN_BUTTON(timeentry->nanoseconds_spinner)) == nanoseconds)) { | |
282 | /* No update needed, don't update the spinners */ | |
283 | return; | |
284 | } | |
285 | ||
286 | /* Block the spinner changed signal when we set the time to them */ | |
287 | g_signal_handler_block(timeentry->seconds_spinner, | |
288 | timeentry->seconds_changed_handler_id); | |
289 | g_signal_handler_block(timeentry->nanoseconds_spinner, | |
290 | timeentry->nanoseconds_changed_handler_id); | |
291 | ||
292 | gtk_spin_button_set_value(GTK_SPIN_BUTTON(timeentry->seconds_spinner), seconds); | |
293 | timeentry_update_nanoseconds_spinner_range(timeentry, seconds); | |
294 | gtk_spin_button_set_value(GTK_SPIN_BUTTON(timeentry->nanoseconds_spinner), nanoseconds); | |
295 | ||
296 | g_signal_handler_unblock(timeentry->nanoseconds_spinner, | |
297 | timeentry->nanoseconds_changed_handler_id); | |
298 | g_signal_handler_unblock(timeentry->seconds_spinner, | |
299 | timeentry->seconds_changed_handler_id); | |
300 | ||
301 | /* Send the time changed signal */ | |
302 | g_signal_emit(timeentry, | |
303 | timeentry_signals[SIGNAL_TIME_CHANGED], 0); | |
304 | } | |
305 | ||
306 | void timeentry_get_time (Timeentry *timeentry, | |
307 | unsigned long *seconds, | |
308 | unsigned long *nanoseconds) | |
309 | { | |
310 | *seconds = gtk_spin_button_get_value (GTK_SPIN_BUTTON(timeentry->seconds_spinner)); | |
311 | *nanoseconds = gtk_spin_button_get_value (GTK_SPIN_BUTTON(timeentry->nanoseconds_spinner)); | |
312 | } | |
313 | ||
314 | static void | |
315 | on_spinner_value_changed (GtkSpinButton *spinbutton, | |
316 | gpointer user_data) | |
317 | { | |
318 | Timeentry *timeentry = (Timeentry *)user_data; | |
319 | unsigned long current_seconds; | |
320 | ||
321 | /* Manage min/max values of the nanoseconds spinner */ | |
322 | current_seconds = gtk_spin_button_get_value (GTK_SPIN_BUTTON(timeentry->seconds_spinner)); | |
323 | timeentry_update_nanoseconds_spinner_range(timeentry, | |
324 | current_seconds); | |
325 | ||
326 | g_signal_emit(timeentry, | |
327 | timeentry_signals[SIGNAL_TIME_CHANGED], 0); | |
328 | } | |
329 | ||
330 | static gboolean on_label_click(GtkWidget *widget, | |
331 | GdkEventButton *event, | |
332 | gpointer data) | |
333 | { | |
334 | Timeentry *timeentry = (Timeentry *)data; | |
335 | ||
336 | /* Only take button presses */ | |
337 | if (event->type != GDK_BUTTON_PRESS) | |
338 | return FALSE; | |
339 | ||
340 | ||
341 | if (event->button == 3) { | |
342 | /* Right button click - popup menu */ | |
343 | ||
344 | /* Show the menu */ | |
345 | gtk_menu_popup (GTK_MENU(timeentry->main_label_context_menu), NULL, NULL, | |
346 | NULL, NULL, event->button, event->time); | |
347 | ||
348 | return TRUE; | |
349 | ||
350 | } else if (event->button == 2) { | |
351 | /* Middle button click - paste PRIMARY */ | |
352 | ||
353 | GtkClipboard *clip = gtk_clipboard_get_for_display(gdk_display_get_default(), | |
354 | GDK_SELECTION_PRIMARY); | |
355 | gtk_clipboard_request_text(clip, | |
356 | (GtkClipboardTextReceivedFunc)clipboard_receive, | |
357 | (gpointer)timeentry); | |
358 | } | |
359 | ||
360 | return 0; | |
361 | } | |
362 | ||
363 | static void on_menu_copy(gpointer callback_data) | |
364 | { | |
365 | Timeentry *timeentry = (Timeentry *)callback_data; | |
366 | const int CLIP_BUFFER_SIZE = 100; | |
367 | gchar buffer[CLIP_BUFFER_SIZE]; | |
368 | ||
369 | unsigned long seconds, nseconds; | |
370 | timeentry_get_time(timeentry, &seconds, &nseconds); | |
371 | snprintf(buffer, CLIP_BUFFER_SIZE, "%lu.%lu", seconds, nseconds); | |
372 | ||
373 | /* Set the CLIPBOARD */ | |
374 | GtkClipboard *clip = gtk_clipboard_get_for_display(gdk_display_get_default(), | |
375 | GDK_SELECTION_CLIPBOARD); | |
376 | ||
377 | gtk_clipboard_set_text(clip, buffer, -1); | |
378 | ||
379 | /* Set it also in the PRIMARY buffer (for middle click) */ | |
380 | clip = gtk_clipboard_get_for_display(gdk_display_get_default(), | |
381 | GDK_SELECTION_PRIMARY); | |
382 | gtk_clipboard_set_text(clip, buffer, -1); | |
383 | } | |
384 | ||
385 | static void on_menu_paste(gpointer callback_data, | |
386 | guint callback_action, | |
387 | GtkWidget *widget) | |
388 | { | |
389 | Timeentry *timeentry = (Timeentry *)callback_data; | |
390 | ||
391 | GtkClipboard *clip = gtk_clipboard_get_for_display(gdk_display_get_default(), | |
392 | GDK_SELECTION_CLIPBOARD); | |
393 | gtk_clipboard_request_text(clip, | |
394 | (GtkClipboardTextReceivedFunc)clipboard_receive, | |
395 | (gpointer)timeentry); | |
396 | } | |
397 | ||
398 | static void clipboard_receive(GtkClipboard *clipboard, | |
399 | const gchar *text, | |
400 | gpointer data) | |
401 | { | |
402 | const int CLIP_BUFFER_SIZE = 100; | |
403 | if (text == NULL) { | |
404 | return; | |
405 | } | |
406 | Timeentry *timeentry = (Timeentry *)data; | |
407 | gchar buffer[CLIP_BUFFER_SIZE]; | |
408 | gchar *ptr = buffer, *ptr_sec, *ptr_nsec; | |
409 | ||
410 | strncpy(buffer, text, CLIP_BUFFER_SIZE); | |
411 | g_debug("Timeentry clipboard receive: %s", buffer); | |
412 | ||
413 | while (!isdigit(*ptr) && ptr < buffer+CLIP_BUFFER_SIZE-1) { | |
414 | ptr++; | |
415 | } | |
416 | /* remove leading junk */ | |
417 | ptr_sec = ptr; | |
418 | while (isdigit(*ptr) && ptr < buffer+CLIP_BUFFER_SIZE-1) { | |
419 | ptr++; | |
420 | } | |
421 | /* read all the first number */ | |
422 | *ptr = '\0'; | |
423 | ||
424 | if (ptr == ptr_sec) { | |
425 | /* No digit in the input, exit */ | |
426 | return; | |
427 | } | |
428 | ptr++; | |
429 | ||
430 | while (!isdigit(*ptr) && ptr < buffer+CLIP_BUFFER_SIZE-1) { | |
431 | ptr++; | |
432 | } | |
433 | /* remove leading junk */ | |
434 | ptr_nsec = ptr; | |
435 | while (isdigit(*ptr) && ptr < buffer+CLIP_BUFFER_SIZE-1) { | |
436 | ptr++; | |
437 | } | |
438 | /* read all the first number */ | |
439 | *ptr = '\0'; | |
440 | ||
441 | timeentry_set_time(timeentry, | |
442 | strtoul(ptr_sec, NULL, 10), | |
443 | strtoul(ptr_nsec, NULL, 10)); | |
444 | } | |
445 | ||
446 | GtkWidget* | |
447 | timeentry_new (const gchar *label) | |
448 | { | |
449 | ||
450 | Timeentry *timeentry = g_object_new (TIMEENTRY_TYPE, NULL); | |
451 | ||
452 | if (label && *label) | |
453 | timeentry_set_main_label (timeentry, label); | |
454 | ||
455 | return GTK_WIDGET(timeentry); | |
456 | } |