Update FSF address
[lttv.git] / lttv / modules / gui / lttvwindow / lttvwindow / timeentry.c
CommitLineData
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
b9ce0bad
YB
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
16 * MA 02110-1301, USA.
4172f013
YB
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
27enum {
28 SIGNAL_TIME_CHANGED,
29 LAST_SIGNAL
30};
31
32static void timeentry_class_init(TimeentryClass *klass);
33static void timeentry_init(Timeentry *ttt);
34
35static guint timeentry_signals[LAST_SIGNAL] = { 0 };
36static unsigned int MAX_NANOSECONDS = 999999999;
37
38static void on_spinner_value_changed (GtkSpinButton *spinbutton,
39 gpointer user_data);
40
41static gboolean on_label_click(GtkWidget *widget,
42 GdkEventButton *event,
43 gpointer data);
44static void on_menu_copy(gpointer data);
45static void on_menu_paste(gpointer callback_data,
46 guint callback_action,
47 GtkWidget *widget);
48
49static void clipboard_receive(GtkClipboard *clipboard,
50 const gchar *text,
51 gpointer data);
52
53GType 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
80static 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
92static 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
172void 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
184static 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
214void 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, &current_seconds, &current_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
258void 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
307void 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
315static void
316on_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
331static 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
364static 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
386static 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
399static 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
447GtkWidget*
448timeentry_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}
This page took 0.041093 seconds and 4 git commands to generate.