retab so every tab is 2 spaces long
[lttv.git] / ltt / branches / poly / lttv / modules / guiControlFlow / Drawing.c
CommitLineData
fa2c4dbe 1
76a67e8a 2#include <gtk/gtk.h>
3#include <gdk/gdk.h>
f0d936c0 4
831a876d 5#include <lttv/processTrace.h>
f66eba62 6#include <lttv/gtkTraceSet.h>
7#include <lttv/hook.h>
831a876d 8
f66eba62 9#include "Drawing.h"
10#include "CFV.h"
11#include "CFV-private.h"
12#include "Event_Hooks.h"
6d5ed1c3 13
14#define g_info(format...) g_log (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, format)
15#define g_debug(format...) g_log (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, format)
16
f0d936c0 17/*****************************************************************************
18 * Drawing functions *
19 *****************************************************************************/
20
831a876d 21//FIXME Colors will need to be dynamic. Graphic context part not done so far.
f0d936c0 22typedef enum
23{
a56a1ba4 24 RED,
25 GREEN,
26 BLUE,
27 WHITE,
28 BLACK
f0d936c0 29
30} ControlFlowColors;
31
32/* Vector of unallocated colors */
33static GdkColor CF_Colors [] =
34{
a56a1ba4 35 { 0, 0xffff, 0x0000, 0x0000 }, // RED
36 { 0, 0x0000, 0xffff, 0x0000 }, // GREEN
37 { 0, 0x0000, 0x0000, 0xffff }, // BLUE
38 { 0, 0xffff, 0xffff, 0xffff }, // WHITE
39 { 0, 0x0000, 0x0000, 0x0000 } // BLACK
f0d936c0 40};
41
42
831a876d 43/* Function responsible for updating the exposed area.
44 * It must call processTrace() to ask for this update.
432a7065 45 * Note : this function cannot clear the background, because it may
46 * erase drawing already present (SAFETY).
831a876d 47 */
4c69e0cc 48void drawing_data_request(Drawing_t *Drawing,
a56a1ba4 49 GdkPixmap **Pixmap,
50 gint x, gint y,
51 gint width,
52 gint height)
847b479d 53{
d9b7ca88 54 if(width < 0) return ;
55 if(height < 0) return ;
a56a1ba4 56 ControlFlowData *control_flow_data =
57 (ControlFlowData*)g_object_get_data(
58 G_OBJECT(
14963be0 59 Drawing->drawing_area),
68997a22 60 "control_flow_data");
a56a1ba4 61
62 LttTime start, end;
ba90bc77 63 LttTime window_end = ltt_time_add(control_flow_data->time_window.time_width,
64 control_flow_data->time_window.start_time);
a56a1ba4 65
66 g_critical("req : window_end : %u, %u", window_end.tv_sec,
67 window_end.tv_nsec);
68
ba90bc77 69 g_critical("req : time width : %u, %u", control_flow_data->time_window.time_width.tv_sec,
70 control_flow_data->time_window.time_width.tv_nsec);
a56a1ba4 71
72 g_critical("x is : %i, x+width is : %i", x, x+width);
73
14963be0 74 convert_pixels_to_time(Drawing->drawing_area->allocation.width, x,
ba90bc77 75 &control_flow_data->time_window.start_time,
a56a1ba4 76 &window_end,
77 &start);
78
14963be0 79 convert_pixels_to_time(Drawing->drawing_area->allocation.width, x + width,
ba90bc77 80 &control_flow_data->time_window.start_time,
a56a1ba4 81 &window_end,
82 &end);
83
84 LttvTracesetContext * tsc =
ba90bc77 85 get_traceset_context(control_flow_data->mw);
a56a1ba4 86
432a7065 87 //send_test_process(
68997a22 88 //guicontrolflow_get_process_list(Drawing->control_flow_data),
a56a1ba4 89 //Drawing);
f66eba62 90 //send_test_drawing(
68997a22 91 //guicontrolflow_get_process_list(Drawing->control_flow_data),
a56a1ba4 92 //Drawing, *Pixmap, x, y, width, height);
93
94 // Let's call processTrace() !!
95 EventRequest event_request; // Variable freed at the end of the function.
68997a22 96 event_request.control_flow_data = control_flow_data;
a56a1ba4 97 event_request.time_begin = start;
98 event_request.time_end = end;
99 event_request.x_begin = x;
100 event_request.x_end = x+width;
101
102 g_critical("req : start : %u, %u", event_request.time_begin.tv_sec,
103 event_request.time_begin.tv_nsec);
104
105 g_critical("req : end : %u, %u", event_request.time_end.tv_sec,
106 event_request.time_end.tv_nsec);
107
108 LttvHooks *event = lttv_hooks_new();
109 LttvHooks *after_event = lttv_hooks_new();
110 LttvHooks *after_traceset = lttv_hooks_new();
111 lttv_hooks_add(after_traceset, after_data_request, &event_request);
112 lttv_hooks_add(event, draw_event_hook, &event_request);
113 //Modified by xiangxiu: state update hooks are added by the main window
ba90bc77 114 //state_add_event_hooks_api(control_flow_data->mw);
a56a1ba4 115 lttv_hooks_add(after_event, draw_after_hook, &event_request);
116
117 lttv_process_traceset_seek_time(tsc, start);
118 // FIXME : would like to place the after_traceset hook after the traceset,
119 // but the traceset context state is not valid anymore.
120 lttv_traceset_context_add_hooks(tsc,
121 // NULL, after_traceset, NULL, NULL, NULL, NULL,
122 NULL, NULL, NULL, NULL, NULL, NULL,
123 NULL, after_traceset, NULL, event, after_event);
124 lttv_process_traceset(tsc, end, G_MAXULONG);
125 //after_data_request((void*)&event_request,(void*)tsc);
126 lttv_traceset_context_remove_hooks(tsc,
127 //NULL, after_traceset, NULL, NULL, NULL, NULL,
128 NULL, NULL, NULL, NULL, NULL, NULL,
129 NULL, after_traceset, NULL, event, after_event);
130 //Modified by xiangxiu: state update hooks are removed by the main window
ba90bc77 131 //state_remove_event_hooks_api(control_flow_data->mw);
a56a1ba4 132
133 lttv_hooks_destroy(after_traceset);
134 lttv_hooks_destroy(event);
135 lttv_hooks_destroy(after_event);
136
137
847b479d 138}
a56a1ba4 139
847b479d 140/* Callbacks */
141
142
143/* Create a new backing pixmap of the appropriate size */
bd24a9af 144/* As the scaling will always change, it's of no use to copy old
145 * pixmap.
146 */
847b479d 147static gboolean
148configure_event( GtkWidget *widget, GdkEventConfigure *event,
a56a1ba4 149 gpointer user_data)
f0d936c0 150{
847b479d 151 Drawing_t *Drawing = (Drawing_t*)user_data;
f0d936c0 152
86c520a7 153
a56a1ba4 154 /* First, get the new time interval of the main window */
155 /* we assume (see documentation) that the main window
156 * has updated the time interval before this configure gets
157 * executed.
158 */
68997a22 159 get_time_window(Drawing->control_flow_data->mw,
160 &Drawing->control_flow_data->time_window);
a56a1ba4 161
f7afe191 162 /* New Pixmap, size of the configure event */
bd24a9af 163 //GdkPixmap *Pixmap = gdk_pixmap_new(widget->window,
a56a1ba4 164 // widget->allocation.width + SAFETY,
165 // widget->allocation.height + SAFETY,
166 // -1);
167
f7afe191 168 g_critical("drawing configure event");
a56a1ba4 169 g_critical("New draw size : %i by %i",widget->allocation.width, widget->allocation.height);
170
171
172 if (Drawing->Pixmap)
bd24a9af 173 gdk_pixmap_unref(Drawing->Pixmap);
a56a1ba4 174
f7afe191 175 /* If no old Pixmap present */
bd24a9af 176 //if(Drawing->Pixmap == NULL)
847b479d 177 {
a56a1ba4 178 Drawing->Pixmap = gdk_pixmap_new(
179 widget->window,
180 widget->allocation.width + SAFETY,
181 widget->allocation.height + SAFETY,
182 //ProcessList_get_height
68997a22 183 // (GuiControlFlow_get_process_list(Drawing->control_flow_data)),
a56a1ba4 184 -1);
185 Drawing->width = widget->allocation.width;
186 Drawing->height = widget->allocation.height;
187
188
189 // Clear the image
190 gdk_draw_rectangle (Drawing->Pixmap,
191 widget->style->white_gc,
192 TRUE,
193 0, 0,
194 widget->allocation.width+SAFETY,
195 widget->allocation.height+SAFETY);
196
197 //g_info("init data request");
198
199
200 /* Initial data request */
201 // Do not need to ask for data of 1 pixel : not synchronized with
202 // main window time at this moment.
203 drawing_data_request(Drawing, &Drawing->Pixmap, 0, 0,
204 widget->allocation.width,
205 widget->allocation.height);
206
207 Drawing->width = widget->allocation.width;
208 Drawing->height = widget->allocation.height;
209
210 return TRUE;
bd24a9af 211
212
847b479d 213
214 }
bd24a9af 215#ifdef NOTUSE
847b479d 216// /* Draw empty background */
217// gdk_draw_rectangle (Pixmap,
a56a1ba4 218// widget->style->black_gc,
219// TRUE,
220// 0, 0,
221// widget->allocation.width,
222// widget->allocation.height);
223
224 /* Copy old data to new pixmap */
225 gdk_draw_drawable (Pixmap,
226 widget->style->white_gc,
227 Drawing->Pixmap,
228 0, 0,
229 0, 0,
230 -1, -1);
231
80a52ff8 232 if (Drawing->Pixmap)
233 gdk_pixmap_unref(Drawing->Pixmap);
234
235 Drawing->Pixmap = Pixmap;
a56a1ba4 236
237 // Clear the bottom part of the image (SAFETY)
432a7065 238 gdk_draw_rectangle (Pixmap,
a56a1ba4 239 widget->style->white_gc,
240 TRUE,
241 0, Drawing->height+SAFETY,
242 Drawing->width+SAFETY, // do not overlap
243 (widget->allocation.height) - Drawing->height);
bd24a9af 244
a56a1ba4 245 // Clear the right part of the image (SAFETY)
432a7065 246 gdk_draw_rectangle (Pixmap,
a56a1ba4 247 widget->style->white_gc,
248 TRUE,
249 Drawing->width+SAFETY, 0,
250 (widget->allocation.width) - Drawing->width, // do not overlap
251 Drawing->height+SAFETY);
252
253 /* Clear the backgound for data request, but not SAFETY */
254 gdk_draw_rectangle (Pixmap,
14963be0 255 Drawing->drawing_area->style->white_gc,
a56a1ba4 256 TRUE,
257 Drawing->width + SAFETY, 0,
258 widget->allocation.width - Drawing->width, // do not overlap
259 widget->allocation.height+SAFETY);
bd24a9af 260
432a7065 261 /* Request data for missing space */
a56a1ba4 262 g_info("missing data request");
432a7065 263 drawing_data_request(Drawing, &Pixmap, Drawing->width, 0,
a56a1ba4 264 widget->allocation.width - Drawing->width,
265 widget->allocation.height);
266
847b479d 267 Drawing->width = widget->allocation.width;
268 Drawing->height = widget->allocation.height;
269
270 return TRUE;
bd24a9af 271#endif //NOTUSE
847b479d 272}
273
274
275/* Redraw the screen from the backing pixmap */
276static gboolean
277expose_event( GtkWidget *widget, GdkEventExpose *event, gpointer user_data )
278{
279 Drawing_t *Drawing = (Drawing_t*)user_data;
a56a1ba4 280 ControlFlowData *control_flow_data =
281 (ControlFlowData*)g_object_get_data(
282 G_OBJECT(widget),
68997a22 283 "control_flow_data");
8b90e648 284
847b479d 285 g_critical("drawing expose event");
a56a1ba4 286
287 guint x=0;
ba90bc77 288 LttTime* current_time =
a56a1ba4 289 guicontrolflow_get_current_time(control_flow_data);
290
ba90bc77 291 LttTime window_end = ltt_time_add(control_flow_data->time_window.time_width,
292 control_flow_data->time_window.start_time);
a56a1ba4 293
294 convert_time_to_pixels(
ba90bc77 295 control_flow_data->time_window.start_time,
a56a1ba4 296 window_end,
ba90bc77 297 *current_time,
a56a1ba4 298 widget->allocation.width,
299 &x);
300
847b479d 301 gdk_draw_pixmap(widget->window,
a56a1ba4 302 widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
303 Drawing->Pixmap,
304 event->area.x, event->area.y,
305 event->area.x, event->area.y,
306 event->area.width, event->area.height);
307
308 if(x >= event->area.x && x <= event->area.x+event->area.width)
309 {
310 GdkGC *gc = gdk_gc_new(control_flow_data->Drawing->Pixmap);
311 gdk_gc_copy(gc, widget->style->black_gc);
312
313 drawing_draw_line(NULL, widget->window,
314 x, event->area.y,
315 x, event->area.y+event->area.height,
316 gc);
317 gdk_gc_unref(gc);
318 }
847b479d 319 return FALSE;
320}
321
8b90e648 322/* mouse click */
323static gboolean
324button_press_event( GtkWidget *widget, GdkEventButton *event, gpointer user_data )
325{
a56a1ba4 326 ControlFlowData *control_flow_data =
327 (ControlFlowData*)g_object_get_data(
328 G_OBJECT(widget),
68997a22 329 "control_flow_data");
a56a1ba4 330 Drawing_t *Drawing = control_flow_data->Drawing;
8b90e648 331
332
a56a1ba4 333 g_critical("click");
334 if(event->button == 1)
335 {
336 LttTime time;
8b90e648 337
ba90bc77 338 LttTime window_end = ltt_time_add(control_flow_data->time_window.time_width,
339 control_flow_data->time_window.start_time);
8b90e648 340
341
a56a1ba4 342 /* left mouse button click */
343 g_critical("x click is : %f", event->x);
8b90e648 344
a56a1ba4 345 convert_pixels_to_time(widget->allocation.width, (guint)event->x,
ba90bc77 346 &control_flow_data->time_window.start_time,
a56a1ba4 347 &window_end,
348 &time);
8b90e648 349
ba90bc77 350 set_current_time(control_flow_data->mw, &time);
8b90e648 351
a56a1ba4 352 }
353
354 return FALSE;
8b90e648 355}
356
357
358
359
68997a22 360Drawing_t *drawing_construct(ControlFlowData *control_flow_data)
847b479d 361{
a56a1ba4 362 Drawing_t *Drawing = g_new(Drawing_t, 1);
363
14963be0 364 Drawing->drawing_area = gtk_drawing_area_new ();
68997a22 365 Drawing->control_flow_data = control_flow_data;
a56a1ba4 366
367 Drawing->pango_layout =
14963be0 368 gtk_widget_create_pango_layout(Drawing->drawing_area, NULL);
a56a1ba4 369
14963be0 370 //gtk_widget_set_size_request(Drawing->drawing_area->window, 50, 50);
a56a1ba4 371 g_object_set_data_full(
14963be0 372 G_OBJECT(Drawing->drawing_area),
a56a1ba4 373 "Link_Drawing_Data",
374 Drawing,
375 (GDestroyNotify)drawing_destroy);
376
14963be0 377 //gtk_widget_modify_bg( Drawing->drawing_area,
a56a1ba4 378 // GTK_STATE_NORMAL,
379 // &CF_Colors[BLACK]);
380
14963be0 381 //gdk_window_get_geometry(Drawing->drawing_area->window,
a56a1ba4 382 // NULL, NULL,
383 // &(Drawing->width),
384 // &(Drawing->height),
385 // -1);
386
387 //Drawing->Pixmap = gdk_pixmap_new(
14963be0 388 // Drawing->drawing_area->window,
a56a1ba4 389 // Drawing->width,
390 // Drawing->height,
391 // Drawing->depth);
392
393 Drawing->Pixmap = NULL;
394
14963be0 395// Drawing->Pixmap = gdk_pixmap_new(Drawing->drawing_area->window,
396// Drawing->drawing_area->allocation.width,
397// Drawing->drawing_area->allocation.height,
a56a1ba4 398// -1);
399
14963be0 400 gtk_widget_add_events(Drawing->drawing_area, GDK_BUTTON_PRESS_MASK);
a56a1ba4 401
14963be0 402 g_signal_connect (G_OBJECT(Drawing->drawing_area),
a56a1ba4 403 "configure_event",
404 G_CALLBACK (configure_event),
405 (gpointer)Drawing);
406
14963be0 407 g_signal_connect (G_OBJECT(Drawing->drawing_area),
a56a1ba4 408 "expose_event",
409 G_CALLBACK (expose_event),
410 (gpointer)Drawing);
411
14963be0 412 g_signal_connect (G_OBJECT(Drawing->drawing_area),
a56a1ba4 413 "button-press-event",
414 G_CALLBACK (button_press_event),
415 (gpointer)Drawing);
416
417
418 return Drawing;
f0d936c0 419}
420
4c69e0cc 421void drawing_destroy(Drawing_t *Drawing)
f0d936c0 422{
423
a56a1ba4 424 // Do not unref here, Drawing_t destroyed by it's widget.
14963be0 425 //g_object_unref( G_OBJECT(Drawing->drawing_area));
a56a1ba4 426
427 g_free(Drawing->pango_layout);
428 g_free(Drawing);
f0d936c0 429}
430
4c69e0cc 431GtkWidget *drawing_get_widget(Drawing_t *Drawing)
76a67e8a 432{
14963be0 433 return Drawing->drawing_area;
76a67e8a 434}
435
f66eba62 436/* convert_pixels_to_time
f0d936c0 437 *
f66eba62 438 * Convert from window pixel and time interval to an absolute time.
f0d936c0 439 */
fa2c4dbe 440void convert_pixels_to_time(
a56a1ba4 441 gint width,
442 guint x,
443 LttTime *window_time_begin,
444 LttTime *window_time_end,
445 LttTime *time)
f0d936c0 446{
a56a1ba4 447 LttTime window_time_interval;
448
449 window_time_interval = ltt_time_sub(*window_time_end,
308711e5 450 *window_time_begin);
a56a1ba4 451 *time = ltt_time_mul(window_time_interval, (x/(float)width));
452 *time = ltt_time_add(*window_time_begin, *time);
fa2c4dbe 453}
454
455
456
457void convert_time_to_pixels(
a56a1ba4 458 LttTime window_time_begin,
459 LttTime window_time_end,
460 LttTime time,
461 int width,
462 guint *x)
fa2c4dbe 463{
a56a1ba4 464 LttTime window_time_interval;
465 float interval_float, time_float;
466
467 window_time_interval = ltt_time_sub(window_time_end,window_time_begin);
468
469 time = ltt_time_sub(time, window_time_begin);
470
471 interval_float = ltt_time_to_double(window_time_interval);
472 time_float = ltt_time_to_double(time);
473
474 *x = (guint)(time_float/interval_float * width);
475
f0d936c0 476}
477
a56a1ba4 478void drawing_refresh ( Drawing_t *Drawing,
479 guint x, guint y,
480 guint width, guint height)
847b479d 481{
a56a1ba4 482 g_info("Drawing.c : drawing_refresh %u, %u, %u, %u", x, y, width, height);
483 GdkRectangle update_rect;
484
485 gdk_draw_drawable(
14963be0 486 Drawing->drawing_area->window,
487 Drawing->drawing_area->
488 style->fg_gc[GTK_WIDGET_STATE (Drawing->drawing_area)],
a56a1ba4 489 GDK_DRAWABLE(Drawing->Pixmap),
490 x, y,
491 x, y,
492 width, height);
493
494 update_rect.x = 0 ;
495 update_rect.y = 0 ;
496 update_rect.width = Drawing->width;
497 update_rect.height = Drawing->height ;
14963be0 498 gtk_widget_draw( Drawing->drawing_area, &update_rect);
f7afe191 499
847b479d 500}
501
502
a56a1ba4 503void drawing_draw_line( Drawing_t *Drawing,
504 GdkPixmap *Pixmap,
505 guint x1, guint y1,
506 guint x2, guint y2,
507 GdkGC *GC)
847b479d 508{
a56a1ba4 509 gdk_draw_line (Pixmap,
510 GC,
511 x1, y1, x2, y2);
847b479d 512}
513
514
fa2c4dbe 515
516
4c69e0cc 517void drawing_resize(Drawing_t *Drawing, guint h, guint w)
f0d936c0 518{
a56a1ba4 519 Drawing->height = h ;
520 Drawing->width = w ;
521
14963be0 522 gtk_widget_set_size_request ( Drawing->drawing_area,
a56a1ba4 523 Drawing->width,
524 Drawing->height);
525
526
f0d936c0 527}
847b479d 528
529
5f16133f 530/* Insert a square corresponding to a new process in the list */
531/* Applies to whole Drawing->width */
4c69e0cc 532void drawing_insert_square(Drawing_t *Drawing,
a56a1ba4 533 guint y,
534 guint height)
5f16133f 535{
a56a1ba4 536 //GdkRectangle update_rect;
5f16133f 537
a56a1ba4 538 /* Allocate a new pixmap with new height */
14963be0 539 GdkPixmap *Pixmap = gdk_pixmap_new(Drawing->drawing_area->window,
a56a1ba4 540 Drawing->width + SAFETY,
541 Drawing->height + height + SAFETY,
542 -1);
543
544 /* Copy the high region */
545 gdk_draw_drawable (Pixmap,
14963be0 546 Drawing->drawing_area->style->black_gc,
a56a1ba4 547 Drawing->Pixmap,
548 0, 0,
549 0, 0,
550 Drawing->width + SAFETY, y);
5f16133f 551
552
553
5f16133f 554
a56a1ba4 555 /* add an empty square */
556 gdk_draw_rectangle (Pixmap,
14963be0 557 Drawing->drawing_area->style->white_gc,
a56a1ba4 558 TRUE,
559 0, y,
560 Drawing->width + SAFETY, // do not overlap
561 height);
5f16133f 562
563
5f16133f 564
a56a1ba4 565 /* copy the bottom of the region */
566 gdk_draw_drawable (Pixmap,
14963be0 567 Drawing->drawing_area->style->black_gc,
a56a1ba4 568 Drawing->Pixmap,
569 0, y,
570 0, y + height,
571 Drawing->width+SAFETY, Drawing->height - y + SAFETY);
5f16133f 572
573
574
5f16133f 575
a56a1ba4 576 if (Drawing->Pixmap)
577 gdk_pixmap_unref(Drawing->Pixmap);
5f16133f 578
a56a1ba4 579 Drawing->Pixmap = Pixmap;
580
581 Drawing->height+=height;
582
583 /* Rectangle to update, from new Drawing dimensions */
584 //update_rect.x = 0 ;
585 //update_rect.y = y ;
586 //update_rect.width = Drawing->width;
587 //update_rect.height = Drawing->height - y ;
14963be0 588 //gtk_widget_draw( Drawing->drawing_area, &update_rect);
5f16133f 589}
590
591
592/* Remove a square corresponding to a removed process in the list */
4c69e0cc 593void drawing_remove_square(Drawing_t *Drawing,
a56a1ba4 594 guint y,
595 guint height)
5f16133f 596{
a56a1ba4 597 //GdkRectangle update_rect;
598
599 /* Allocate a new pixmap with new height */
600 GdkPixmap *Pixmap = gdk_pixmap_new(
14963be0 601 Drawing->drawing_area->window,
a56a1ba4 602 Drawing->width + SAFETY,
603 Drawing->height - height + SAFETY,
604 -1);
605
606 /* Copy the high region */
607 gdk_draw_drawable (Pixmap,
14963be0 608 Drawing->drawing_area->style->black_gc,
a56a1ba4 609 Drawing->Pixmap,
610 0, 0,
611 0, 0,
612 Drawing->width + SAFETY, y);
613
614
615
616 /* Copy up the bottom of the region */
617 gdk_draw_drawable (Pixmap,
14963be0 618 Drawing->drawing_area->style->black_gc,
a56a1ba4 619 Drawing->Pixmap,
620 0, y + height,
621 0, y,
622 Drawing->width, Drawing->height - y - height + SAFETY);
623
624
625 if (Drawing->Pixmap)
626 gdk_pixmap_unref(Drawing->Pixmap);
627
628 Drawing->Pixmap = Pixmap;
629
630 Drawing->height-=height;
631
632 /* Rectangle to update, from new Drawing dimensions */
633 //update_rect.x = 0 ;
634 //update_rect.y = y ;
635 //update_rect.width = Drawing->width;
636 //update_rect.height = Drawing->height - y ;
14963be0 637 //gtk_widget_draw( Drawing->drawing_area, &update_rect);
5f16133f 638}
189a5d08 639
640
This page took 0.054017 seconds and 4 git commands to generate.