feaf29747b06cad4480d37bf59e12c9921ffc1c0
[lttv.git] / lttv / modules / gui / lttvwindow / lttvwindow / timeentry.c
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, &current_seconds, &current_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 }
This page took 0.045252 seconds and 3 git commands to generate.