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