main.cxx
Go to the documentation of this file.
1 /*
2 Main file for NOE, the New nOva Event display. This isn't the entry
3 point (see comments at realmain()), but is where most things happen.
4 
5 Author: Matthew Strait
6 Begun: Sept 2017
7 
8 ======================================================================
9 
10 Code style:
11 
12 * This is mostly C with use of STL containers. I'm not a big fan of
13  the infinitely complex language that C++ has become.
14 
15 * There is a fair amount of global state. Mostly it represents what's
16  being displayed to the user, which is also global state of a sort.
17  You might consider this bad style anyway, but I think it is under
18  control and prevents having to build up too much abstraction that
19  makes the code difficult to understand.
20 
21 * Tradition dictates that code be under 80 or perhaps 72 columns. I
22  like tradition, plus I like splitting my screen into two equal width
23  terminals, which leaves 83 columns after 5 columns are used for line
24  numbers. So this code will be mostly within 80 columns and certainly
25  within 83.
26 
27 * No header guards. Headers do not include other headers, and you
28  must put headers in the right order in source files.
29 ======================================================================
30 
31 TODO:
32 
33 * Ticky box for enabling/disabling tracks on the fly.
34 
35 * Use time of tracks for animations.
36 
37 * Animate by TNS times instead of TDC. Be able to switch between?
38 
39 * Show slices somehow. Probably will implement by having a way to show
40 any kind of rb::Clusters, specified in the fcl, and defaulting to the
41 standard slicer.
42 
43 * Allow applying time window to all events -- useful for spills.
44 
45 */
46 
47 #include <gtk/gtk.h>
48 #include <stdio.h>
49 #include <stdint.h>
50 #include <errno.h>
51 #include <vector>
52 #include <math.h>
53 #include <algorithm>
54 #include <string.h>
55 #include "drawing.h"
56 #include "absgeo.h"
57 #include "event.h"
58 #include "geo.h"
59 #include "tracks.h"
60 #include "vertices.h"
61 #include "hits.h"
62 #include "status.h"
63 #include "zoompan.h"
64 #include "active.h"
65 
66 // Let's see. I believe both detectors read out in increments of 4 TDC units,
67 // but the FD is multiplexed whereas the ND isn't, so any given channel at the
68 // FD can only report every 4 * 2^N TDC units, where I think N = 2.
69 //
70 // TODO: be more clear with the user about what is being displayed when
71 // TDCSTEP != 1
72 static int TDCSTEP = 4;
73 
74 /* The events and the current event index in the vector */
75 extern std::vector<noeevent> theevents;
76 int gevi = 0;
77 
79 
81 extern int nplanes;
82 
83 /* GTK objects owned elsewhere */
84 extern GtkWidget * statbox[NSTATBOXES];
85 extern GtkTextBuffer * stattext[NSTATBOXES];
86 extern GtkWidget * edarea[kXorY];
87 extern cairo_pattern_t * eventpattern[kXorY];
88 
89 /* GTK objects owned here */
90 static GtkWidget * mainwin = NULL, * trackwin = NULL, * vertexwin = NULL;
91 static GtkWidget * animate_checkbox = NULL,
92  * cum_ani_checkbox = NULL,
93  * freerun_checkbox = NULL;
94 static GtkWidget * ueventbut = NULL;
95 static GtkWidget * ueventbox = NULL;
96 static GtkWidget * mintickslider = NULL;
97 static GtkWidget * maxtickslider = NULL;
98 static GtkObject * speedadj = NULL;
99 
100 /* Running flags. */
101 bool ghave_read_all = false;
102 static bool prefetching = false;
103 static bool adjusttick_callback_inhibit = false; // XXX ug
104 
105 /* Ticky boxes flags */
106 // Animate must start false, because the first thing that happens is that we
107 // get two expose events (I don't know why) and I don't want to handle an
108 // animated expose when we haven't drawn yet at all.
109 //
110 // Could eliminate these bools and always consult GTK_TOGGLE_BUTTON::active.
111 // Would that be better?
112 static bool animate = false;
113 static bool cumulative_animation = true;
114 static bool free_running = false;
115 
116 static gulong freeruninterval = 0; // ms. Immediately overwritten.
117 static gulong animationinterval = 0; // ms. Immediately overwritten.
118 static gulong freeruntimeoutid = 0;
119 static gulong animatetimeoutid = 0;
120 static gulong statmsgtimeoutid = 0;
121 
122 // Unhighlight the reconstructed objects that are no longer being moused over
123 // (if any) and highlight the new ones (if any).
125 {
126  // You can't just overdraw a track because it doesn't light up precise
127  // rows of pixels. The way Cairo works, you end up with a thicker
128  // track with bits of both colors in it. XXX This doesn't quite work
129  // right with animations. If a track is highlighted and then there's
130  // an animation step, the bits-of-both-colors problem still appears.
131  // But this is a pretty minor problem.
132  cairo_t * cr[kXorY];
133  for(int i = 0; i < kXorY; i++){
134  cairo_push_group(cr[i] = gdk_cairo_create(edarea[i]->window));
135  cairo_set_source(cr[i], eventpattern[i]);
136  cairo_paint(cr[i]);
137  }
138 
139  DRAWPARS drawpars;
140  drawpars.firsttick = theevents[gevi].current_mintick;
141  drawpars.lasttick = theevents[gevi].current_maxtick;
142  drawpars.clear = true;
143  draw_tracks(cr, &drawpars);
144  draw_vertices(cr, &drawpars);
145 
146  for(int i = 0; i < kXorY; i++){
147  cairo_pop_group_to_source(cr[i]);
148  cairo_paint(cr[i]);
149  cairo_destroy(cr[i]);
150  }
151 }
152 
153 // Unhighlight the cell that is no longer being moused over, indicated by
154 // oldactive_plane/cell, and highlight the new one. Do this instead of a full
155 // redraw of edarea, which is expensive and causes very noticeable lag for the
156 // FD.
157 static void change_highlighted_cell(const int oldactive_plane,
158  const int oldactive_cell)
159 {
160  cairo_t * cr[kXorY];
161  for(int i = 0; i < kXorY; i++){
162  cr[i] = gdk_cairo_create(edarea[i]->window);
163  cairo_set_line_width(cr[i], 1.0);
164  }
165 
166  std::vector<hit> & THEhits = theevents[gevi].hits;
167 
168  // We may need to find any number of hits since more than one hit
169  // can be in the same cell. It is not guaranteed that the same hit
170  // ends up visible, but I'm just going to live with that.
171  for(unsigned int i = 0; i < THEhits.size(); i++){
172  hit & thishit = THEhits[i];
173  if((thishit.plane == oldactive_plane && thishit.cell == oldactive_cell) ||
174  (thishit.plane == active_plane && thishit.cell == active_cell))
175  draw_hit(cr[thishit.plane%2 == 1?kX:kY], thishit, edarea);
176  }
177 
178  // NOTE: In principle we should redraw tracks here since we may have just
179  // stomped on some. However, in practice the visual effect isn't very
180  // noticeable and since it's kinda a pain to do it from here, we'll skip it.
181 
182  for(int i = 0; i < kXorY; i++) cairo_destroy(cr[i]);
183 }
184 
185 void update_active_objects(const noe_view_t V, const int x, const int y)
186 {
187  const int oldactive_plane = active_plane;
188  const int oldactive_cell = active_cell;
189  const int oldactive_track = active_track;
190  const int oldactive_vertex= active_vertex;
191 
192  update_active_indices(V, x, y, TDCSTEP);
193 
194  // Change track first because it starts by redrawing all hits from a saved
195  // cairo_pattern_t.
196  if(oldactive_track != active_track || oldactive_vertex != active_vertex)
198  change_highlighted_cell(oldactive_plane, oldactive_cell);
202 }
203 
204 // To be called periodically and when events are changed to get the
205 // mouse pointer position and highlight objects accordingly without the
206 // user having to jiggle the mouse to generate a motion-notify-event.
207 static gboolean pollmouseover(__attribute__((unused)) gpointer data)
208 {
209  gint x, y;
210  for(int i = 0; i < kXorY; i++){
211  gtk_widget_get_pointer(edarea[i], &x, &y);
212  if(x >= 0 && y >= 0 && x < edarea[i]->allocation.width
213  && y < edarea[i]->allocation.height)
215  }
216  return TRUE;
217 }
218 
219 // Handle the mouse arriving at a spot in the drawing area. Find what cell the
220 // user is pointing at, highlight it, and show information about it. Or
221 // if the left button is down, pan instead.
222 static gboolean mouseover(GtkWidget * widg, GdkEventMotion * gevent,
223  __attribute__((unused)) gpointer data)
224 {
225  if(gevent == NULL) return TRUE; // shouldn't happen
226  if(theevents.empty()) return TRUE; // No coordinates in this case
227 
228  const noe_view_t V = widg == edarea[kX]? kX: kY;
229 
230  if(gevent->state & GDK_BUTTON1_MASK){
231  dopanning(V, gevent);
232  return TRUE;
233  }
234 
235  update_active_objects(V, (int)gevent->x, (int)gevent->y);
236 
237  return TRUE;
238 }
239 
240 // draw_event and to_next_free_run circularly refer to each other...
241 static gboolean to_next_free_run(__attribute__((unused)) gpointer data);
242 
244 {
245  theevents[gevi].current_mintick = theevents[gevi].user_mintick;
246  theevents[gevi].current_maxtick = theevents[gevi].user_maxtick;
247 
248  DRAWPARS drawpars;
249  drawpars.firsttick = theevents[gevi].current_mintick;
250  drawpars.lasttick = theevents[gevi].current_maxtick;
251  drawpars.clear = true;
252  draw_event(&drawpars);
253 
254  // get cells, etc. highlighted a little faster. For tracks, must call
255  // this *after* draw_event so that screentrack is filled. Don't do
256  // this if the speed is 11.
257  if(!free_running || freeruninterval > 1) pollmouseover(NULL);
258 }
259 
260 static gboolean animation_step(__attribute__((unused)) gpointer data)
261 {
262  noeevent & E = theevents[gevi];
263 
264  DRAWPARS drawpars;
265  // Must redraw if we are just starting the animation, or if it is
266  // non-cumulative, i.e. the old hits have to be re-hidden
267  drawpars.clear = E.current_maxtick == theevents[gevi].user_mintick ||
269 
271 
272  // Necessary to make visible_hit() work. Otherwise, hits incorrectly
273  // become visible when moused over.
275 
276  drawpars.firsttick = E.current_maxtick-(TDCSTEP-1);
277  drawpars.lasttick = E.current_maxtick;
278  draw_event(&drawpars);
279 
280  const bool stillanimating =
281  animate && E.current_maxtick < theevents[gevi].user_maxtick;
282 
283  // If we are animating and free running, go directly to the next event
284  // at the end of this one, not worrying about the free run delay. This
285  // avoids the need to switch back and forth between timers. Keep the
286  // animation timer running unless this was the last event, as signaled
287  // by the return value of to_next_free_run().
288  if(!stillanimating && free_running)
289  return to_next_free_run(NULL);
290 
291  // Immediately zero this so that other functions can know that the
292  // animation isn't active.
293  if(!stillanimating) animatetimeoutid = 0;
294 
295  return stillanimating;
296 }
297 
298 static gboolean handle_event()
299 {
300  noeevent & E = theevents[gevi];
302  gtk_spin_button_set_range(GTK_SPIN_BUTTON(maxtickslider), E.mintick, E.maxtick);
303  gtk_spin_button_set_range(GTK_SPIN_BUTTON(mintickslider), E.mintick, E.maxtick);
304  gtk_spin_button_set_value(GTK_SPIN_BUTTON(maxtickslider), E.user_maxtick);
305  gtk_spin_button_set_value(GTK_SPIN_BUTTON(mintickslider), E.user_mintick);
306  gtk_widget_draw(maxtickslider, NULL);
307  gtk_widget_draw(mintickslider, NULL);
309 
310  if(animate){
312  if(animatetimeoutid) g_source_remove(animatetimeoutid);
313 
314  // Do one step immediately to be responsive to the user even if the
315  // speed is set very slow
316  animation_step(NULL);
317 
318  // Use a priority lower than G_PRIORITY_HIGH_IDLE + 10, which is
319  // what GTK uses for resizing. This means that even while animating
320  // furiously, we'll still be responsive to window resizes.
321  animatetimeoutid = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
322  std::max(1, (int)animationinterval), animation_step, NULL, NULL);
323  }
324  else{
325  if(animatetimeoutid) g_source_remove(animatetimeoutid);
326  animatetimeoutid = 0;
328  }
329 
330  return FALSE;
331 }
332 
333 // Used, e.g., with g_timeout_add() to get an event drawn after re-entering the
334 // GTK main loop.
335 static gboolean draw_event_from_timer(__attribute__((unused)) gpointer data)
336 {
338  return FALSE; // don't call me again
339 }
340 
341 // Move 'change' events through the event list. If the requested event is in
342 // the list, this trivially updates the integer 'gevi', the global event
343 // number. Does bounds checking and clamps the result, except in the case the
344 // upper bound is not known (see the "otherwise" case below).
345 //
346 // Returns true if the event is ready to be drawn. Otherwise, issues a request
347 // to fetch another event and does *not* change the global event number. Another
348 // call to get_event() is needed once we have the event.
349 static bool get_event(const int change)
350 {
351  if(gevi+change >= (int)theevents.size()){
352  if(ghave_read_all){
353  gevi = theevents.size() - 1;
354  }
355  else{
356  // NOTE: this does not work with abs(change) > 1, but it doesn't
357  // matter since we never call get_event with a bigger number.
358  //
359  // NOTE: Leaving the GTK loop *seems* to be OK, but I'm not clear
360  // on what happens when user events arrive when we're outside.
361  gtk_main_quit();
362  return false;
363  }
364  }
365  else if(gevi+change < 0){
366  gevi = 0;
367  }
368  else{
369  gevi += change;
370  }
371 
372  return true;
373 }
374 
375 // Called when idle to load events into memory
376 static gboolean prefetch_an_event(__attribute__((unused)) gpointer data)
377 {
378  if(ghave_read_all) return FALSE; // don't call this again
379 
380  if(gtk_events_pending()) return TRUE;
381 
382  // exit GTK event loop to get another event from art
383  prefetching = true;
384  gtk_main_quit();
385  return TRUE;
386 }
387 
388 // Why are you always preparing? You're always preparing! Just go!
390 {
392 }
393 
394 // Display the next or previous event.
395 static void to_next(__attribute__((unused)) GtkWidget * widget,
396  gpointer data)
397 {
399  const bool * const forward = (const bool * const)data;
400  if(get_event((*forward)?1:-1))
401  handle_event();
402 }
403 
404 // Called to move us the next event while free running
405 static gboolean to_next_free_run(__attribute__((unused)) gpointer data)
406 {
407  bool forward = true;
408  to_next(NULL, &forward);
409 
410  const bool atend = gevi == (int)theevents.size()-1 && ghave_read_all;
411 
412  if(atend){
413  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(freerun_checkbox),
414  FALSE);
415  free_running = false;
416  }
417  return free_running && !atend;
418 }
419 
420 // Returns true if the list of events we have *now* has an event with the given
421 // number. No assumptions are made about the ordering of event numbers, although
422 // I'm not sure if it's possible for them to be non-increasing in the current
423 // art design.
424 static bool have_event_by_number(const unsigned int n)
425 {
426  for(unsigned int i = 0; i < theevents.size(); i++)
427  if(theevents[i].nevent == n)
428  return true;
429  return false;
430 }
431 
432 // Blank out the fourth status line that sometimes has error messages
433 static gboolean clear_error_message(__attribute__((unused)) gpointer dt)
434 {
435  set_status(staterror, "");
436  return FALSE;
437 }
438 
439 // Get a user entered event number from the text entry widget
440 static void getuserevent()
441 {
442  errno = 0;
443  char * endptr;
444  const int userevent =
445  strtol(gtk_entry_get_text(GTK_ENTRY(ueventbox)), &endptr, 10);
446 
447  clear_error_message(NULL);
448 
449  if((errno == ERANGE && (userevent == INT_MAX || userevent == INT_MIN))
450  || (errno != 0 && userevent == 0)
451  || endptr == optarg || *endptr != '\0'
452  || !have_event_by_number(userevent)){
453  if(!theevents.empty())
454  set_status(staterror, "Entered event invalid or not available. I have "
455  "events %d through %d%s%s",
456  theevents[0].nevent,
457  theevents[theevents.size()-1].nevent,
458  theevents[theevents.size()-1].nevent-theevents[0].nevent
459  == theevents.size()-1?"":" (not consecutive)",
460  ghave_read_all?"":". I'm still loading events.");
461  if(statmsgtimeoutid) g_source_remove(statmsgtimeoutid);
462  statmsgtimeoutid = g_timeout_add(8e3, clear_error_message, NULL);
463  return;
464  }
465 
466  if(userevent == (int)theevents[gevi].nevent) return;
467 
468  const bool forward = userevent > (int)theevents[gevi].nevent;
469  while(userevent != (int)theevents[gevi].nevent)
470  // Don't go through get_event because we do *not* want to try
471  // getting more events from the file
472  gevi += (forward?1:-1);
473 
475  handle_event();
476 }
477 
478 static void stop_freerun_timer()
479 {
480  if(freeruntimeoutid) g_source_remove(freeruntimeoutid);
481  freeruntimeoutid = 0;
482 }
483 
484 static void start_freerun_timer()
485 {
486  stop_freerun_timer(); // just in case
487 
488  // Do not call g_timeout_add with an interval of zero, since that
489  // seems to be a special case that causes the function to be run
490  // repeatedly *without* returning to the main loop, or without
491  // doing all the things in the main loop that are usually done, or
492  // something. In any case, empirically, it causes multiple (infinite?)
493  // calls to to_next_free_run after gtk_main_quit() has been called,
494  // which locks us up. It is quite possible that by setting the
495  // interval to 1, it only makes it *unlikely* that further events are
496  // processed between gtk_main_quit() and exiting the main loop, in
497  // which case I have a bug that has only been suppressed instead of
498  // fixed.
499  //
500  // See comments on the animation timer for why we use this priority.
501  freeruntimeoutid = g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE,
502  std::max((gulong)1, freeruninterval), to_next_free_run, NULL, NULL);
503 }
504 
505 // Handle the user clicking the "run freely" check box.
506 static void toggle_freerun(__attribute__((unused)) GtkWidget * w,
507  __attribute__((unused)) gpointer dt)
508 {
509  free_running = GTK_TOGGLE_BUTTON(w)->active;
510 
511  // If free running *and* animating, the animation timer will handle
512  // switching to the next event.
514  else stop_freerun_timer();
515 
516  handle_event();
517 }
518 
519 // Handle the user clicking the "cumulative animation" check box.
520 static void toggle_cum_ani(GtkWidget * w,
521  __attribute__((unused)) gpointer dt)
522 {
523  cumulative_animation = GTK_TOGGLE_BUTTON(w)->active;
524 
525  // If switching to cumulative, need to draw all the previous hits. If
526  // switching away, need to blank them all out. In either case, don't wait
527  // until the next animation step, because that appears laggy for the user.
528  // TODO: make that actually work.
529  DRAWPARS drawpars;
531  drawpars.firsttick = theevents[gevi].user_mintick;
532  drawpars.lasttick = theevents[gevi].current_maxtick;
533  }
534  else{
535  theevents[gevi].current_mintick = theevents[gevi].current_maxtick;
536  drawpars.firsttick = theevents[gevi].current_mintick;
537  drawpars.lasttick = theevents[gevi].current_maxtick;
538  }
539  drawpars.clear = !cumulative_animation;
540  draw_event(&drawpars);
541 }
542 
543 // Convert the abstract "speed" number from the user into a delay.
544 static void set_intervals(const int speednum)
545 {
546  freeruninterval = (int)pow(10, 6.5 - speednum/2.0);
547  if(speednum == 11) freeruninterval = 1;
548 
549  // No point in trying to go faster than ~50Hz since the monitor won't
550  // keep up (to say nothing of the human eye). I've found it not very
551  // useful to have things flicking past so fast you only barely see
552  // them. Limit to ~3Hz. Control higher speeds exclusively with the
553  // TDCSTEP. This has the added benefit of putting several ticks on the
554  // screen at once, which makes it easier to see interesting things.
555  switch(speednum < 1?1:speednum > 11?11:speednum){
556  case 1: animationinterval = 3000; TDCSTEP = 1; break;
557  case 2: animationinterval = 3000; TDCSTEP = 2; break;
558  case 3: animationinterval = 3000; TDCSTEP = 4; break;
559  case 4: animationinterval = 1000; TDCSTEP = 4; break;
560  case 5: animationinterval = 1000; TDCSTEP = 8; break;
561  case 6: animationinterval = 333; TDCSTEP = 8; break;
562  case 7: animationinterval = 333; TDCSTEP = 32; break;
563  case 8: animationinterval = 333; TDCSTEP = 128; break;
564  case 9: animationinterval = 333; TDCSTEP = 1024; break;
565  case 10: animationinterval = 333; TDCSTEP = 4096; break;
566  case 11: animationinterval = 333; TDCSTEP = 16384; break;
567  }
568 }
569 
570 // Handle the user clicking the "animate" check box.
571 static void toggle_animate(GtkWidget * w, __attribute__((unused)) gpointer dt)
572 {
573  animate = GTK_TOGGLE_BUTTON(w)->active;
574  if(animate){
575  // If free running *and* animating, the animation timer will handle
576  // switching to the next event.
578  set_intervals(gtk_adjustment_get_value(GTK_ADJUSTMENT(speedadj)));
579  }
580  else{
581  // If we stop animating, the free run timer has to take over free running
583  TDCSTEP = 1;
584  }
585 
586  handle_event();
587 }
588 
589 static void restart_animation(__attribute__((unused)) GtkWidget * w,
590  __attribute__((unused)) gpointer d)
591 {
592  // Assume that if the user wants the animation restarted, then the
593  // user wants animation.
594  animate = true;
595  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(animate_checkbox), TRUE);
596  handle_event();
597 }
598 
599 // XXX redraws of tracks and vertices when you do this has an undesired
600 // effect of thickening their lines.
601 static void adjusttick(GtkWidget * wg, const gpointer dt)
602 {
603  if(adjusttick_callback_inhibit) return;
604 
605  noeevent & E = theevents[gevi];
606 
607  const bool adjmax = *(bool *)dt;
608 
609  // TODO: respond intelligently if the user gives a maximum less
610  // than the minimum. Currently does something dumb.
611 
612  (adjmax? E.user_maxtick: E.user_mintick)
613  = gtk_adjustment_get_value(GTK_ADJUSTMENT(wg));
614 
615  const int32_t oldcurrent_maxtick = E.current_maxtick;
616  const int32_t oldcurrent_mintick = E.current_mintick;
617  if(animate){
620  }
621  else{
624  }
625 
626  DRAWPARS drawpars;
627  drawpars.clear = E.current_maxtick < oldcurrent_maxtick ||
628  E.current_mintick > oldcurrent_mintick;
629 
630  // TODO can optimize this to only draw the new range
631  drawpars.firsttick = E.current_mintick;
632  drawpars.lasttick = E.current_maxtick;
633 
634  // Restart the animation if the minimum has changed, OR if the maximum
635  // has changed but we've finished animating, OR if the maximum has
636  // changed and is now less than where we were. TODO: This could be
637  // better.
638  if(animate && (!adjmax || animatetimeoutid == 0 ||
639  oldcurrent_maxtick < E.current_maxtick))
640  restart_animation(NULL, NULL);
641  else
642  draw_event(&drawpars);
643 }
644 
645 // Respond to changes in the spin button for animation/free running speed
646 static void adjustspeed(GtkWidget * wg,
647  __attribute__((unused)) const gpointer dt)
648 {
649  set_intervals(gtk_adjustment_get_value(GTK_ADJUSTMENT(wg)));
650 
653 
654  if(animatetimeoutid) g_source_remove(animatetimeoutid);
655  if(animate)
657  g_timeout_add(std::max(1, (int)animationinterval),
658  animation_step, NULL);
659  else
660  animatetimeoutid = 0;
661 }
662 
663 // Called when the window is resized or moved. The buttons don't redraw
664 // themselves when the window is resized, so we have to get it done. I
665 // don't really want to call this 100 times when a user slowly resizes a window
666 // that takes a long time to draw, but nor do I want to write a complex system
667 // for dealing with that case...
668 static gboolean redraw_window(GtkWidget * mainwin, GdkEventConfigure * event,
669  __attribute__((unused)) gpointer d)
670 {
671  static int oldwidth = event->width, oldheight = event->height;
672  static bool first = true;
673  const bool need_redraw =
674  first || event->width > oldwidth || event->height > oldheight;
675  first = false;
676  oldwidth = event->width, oldheight = event->height;
677 
678  if(need_redraw) gtk_widget_queue_draw(mainwin);
679 
680  return !need_redraw; // FALSE means *do* propagate this to children
681 }
682 
683 static void close_window()
684 {
685  // We could quit gently:
686  // gtk_main_quit(); exit(0);
687  // But there is nothing to save, so just drop everything quickly.
688  _exit(0);
689 }
690 
691 static void openvertexwin()
692 {
693  gtk_widget_show_all(vertexwin);
694 }
695 
696 static void opentrackwin()
697 {
698  gtk_widget_show_all(trackwin);
699 }
700 
701 /**********************************************************************/
702 /* Widget setup */
703 /**********************************************************************/
704 
705 static GtkWidget * make_tickslider(const bool ismax)
706 {
707  const int initialticknum = 0;
708  GtkObject * const tickadj = gtk_adjustment_new
709  (initialticknum, 0, 1000, 1, 100, 0);
710  GtkWidget * tickslider = gtk_spin_button_new(GTK_ADJUSTMENT(tickadj), 10, 0);
711  g_signal_connect(tickadj, "value_changed", G_CALLBACK(adjusttick),
712  new bool(ismax));
713  gtk_entry_set_max_length (GTK_ENTRY(tickslider), 6);
714  gtk_entry_set_width_chars(GTK_ENTRY(tickslider), 6);
715  return tickslider;
716 }
717 
718 static GtkWidget * make_speedslider()
719 {
720  const int initialspeednum = 6;
721  speedadj = gtk_adjustment_new (initialspeednum, 1, 11, 1, 1, 0);
722  set_intervals(initialspeednum);
723  g_signal_connect(speedadj, "value_changed", G_CALLBACK(adjustspeed), NULL);
724 
725  GtkWidget * const speedslider
726  = gtk_spin_button_new(GTK_ADJUSTMENT(speedadj), 10, 0);
727  gtk_entry_set_max_length (GTK_ENTRY(speedslider), 2);
728  gtk_entry_set_width_chars(GTK_ENTRY(speedslider), 2);
729  return speedslider;
730 }
731 
732 static void set_bg_color_to_main(GtkWidget * widg)
733 {
734  static GdkColor * color = NULL;
735  if(color == NULL){
736  color = new GdkColor;
737  gtk_widget_realize(mainwin); // because we may not have drawn the window yet
738  gtk_style_lookup_color(gtk_widget_get_style(mainwin), "bg_color", color);
739  }
740  gtk_widget_modify_base(widg, GTK_STATE_NORMAL, color);
741 }
742 
743 static void makestatbox(const int i)
744 {
745  statbox[i] = gtk_text_view_new();
746  stattext[i] = gtk_text_buffer_new(0);
747  gtk_text_view_set_buffer(GTK_TEXT_VIEW(statbox[i]), stattext[i]);
748  gtk_text_view_set_editable(GTK_TEXT_VIEW(statbox[i]), false);
749  gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(statbox[i]), false);
751 }
752 
753 static GtkWidget * make_ticklabel(const bool ismax)
754 {
755  GtkWidget * ticklabel = gtk_text_view_new();
756  GtkTextBuffer * ticklabeltext = gtk_text_buffer_new(0);
757  gtk_text_view_set_justification(GTK_TEXT_VIEW(ticklabel), GTK_JUSTIFY_CENTER);
758  const char * const ticklabelbuf = ismax?"Max Tick":"Min Tick";
759  gtk_text_buffer_set_text(ticklabeltext, ticklabelbuf, strlen(ticklabelbuf));
760  gtk_text_view_set_buffer(GTK_TEXT_VIEW(ticklabel), ticklabeltext);
761  gtk_text_view_set_editable(GTK_TEXT_VIEW(ticklabel), false);
762  set_bg_color_to_main(ticklabel);
763  return ticklabel;
764 }
765 
766 static GtkWidget * make_speedlabel()
767 {
768  GtkWidget * speedlabel = gtk_text_view_new();
769  GtkTextBuffer * speedlabeltext = gtk_text_buffer_new(0);
770  gtk_text_view_set_justification(GTK_TEXT_VIEW(speedlabel), GTK_JUSTIFY_CENTER);
771  const char * const speedlabelbuf = "Speed";
772  gtk_text_buffer_set_text(speedlabeltext, speedlabelbuf, strlen(speedlabelbuf));
773  gtk_text_view_set_buffer(GTK_TEXT_VIEW(speedlabel), speedlabeltext);
774  gtk_text_view_set_editable(GTK_TEXT_VIEW(speedlabel), false);
775  set_bg_color_to_main(speedlabel);
776  return speedlabel;
777 }
778 
779 static GtkWidget * make_ueventbox()
780 {
781  GtkWidget * ueventbox = gtk_entry_new();
782  gtk_entry_set_max_length(GTK_ENTRY(ueventbox), 20);//length of a int64
783  gtk_entry_set_width_chars(GTK_ENTRY(ueventbox), 5);
784  g_signal_connect(ueventbox, "activate", G_CALLBACK(getuserevent), NULL);
785  return ueventbox;
786 }
787 
788 static GtkWidget * make_aux_win(const char * const name, const statcontents si)
789 {
790  GtkWidget * w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
791  char winname[1024];
792  snprintf(winname, 1024, "NOE: %s", name);
793  gtk_window_set_title(GTK_WINDOW(w), winname);
794  gtk_window_set_default_size(GTK_WINDOW(w), 400, 68 /* four lines for me */);
795  g_signal_connect(w, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
796 
797  GtkWidget * tab = gtk_table_new(1, 1, FALSE); // necessary?
798  gtk_container_add(GTK_CONTAINER(w), tab);
799 
800  gtk_table_attach(GTK_TABLE(tab), statbox[si], 0, 1, 0, 1,
801  GtkAttachOptions(GTK_EXPAND | GTK_FILL), GTK_SHRINK, 0, 0);
802 
803  return w;
804 }
805 
806 // Sets up the GTK windows, add user event hooks, start the necessary
807 // timer(s), draw the first event.
808 static void setup()
809 {
810  gtk_init(NULL, NULL);
811  mainwin = gtk_window_new(GTK_WINDOW_TOPLEVEL);
812  gtk_window_set_title(GTK_WINDOW(mainwin), "NOE: New nOva Event viewer");
813  g_signal_connect(mainwin, "delete-event", G_CALLBACK(close_window), 0);
814 
815  g_signal_connect(mainwin,"configure-event",G_CALLBACK(redraw_window),NULL);
816  for(int i = 0; i < kXorY; i++){
817  edarea[i] = gtk_drawing_area_new();
818  g_signal_connect(edarea[i],"expose-event",G_CALLBACK(redraw_event),NULL);
819  g_signal_connect(edarea[i], "motion-notify-event", G_CALLBACK(mouseover), NULL);
820  g_signal_connect(edarea[i], "scroll-event", G_CALLBACK(dozooming), new bool(i));
821  g_signal_connect(edarea[i], "button-press-event",
822  G_CALLBACK(mousebuttonpress), NULL);
823  gtk_widget_set_events(edarea[i], gtk_widget_get_events(edarea[i])
824  | GDK_POINTER_MOTION_HINT_MASK
825  | GDK_POINTER_MOTION_MASK
826  | GDK_BUTTON_PRESS_MASK
827  | GDK_SCROLL_MASK);
828  }
829  setboxes();
831 
832  GtkWidget * next = gtk_button_new_with_mnemonic("_Next Event");
833  g_signal_connect(next, "clicked", G_CALLBACK(to_next), new bool(true));
834 
835  GtkWidget * prev = gtk_button_new_with_mnemonic("_Previous Event");
836  g_signal_connect(prev, "clicked", G_CALLBACK(to_next), new bool(false));
837 
838  animate_checkbox = gtk_check_button_new_with_mnemonic("_Animate");
839  cum_ani_checkbox = gtk_check_button_new_with_mnemonic("_Cumulative animation");
840  freerun_checkbox = gtk_check_button_new_with_mnemonic("_Free running");
841 
842  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(animate_checkbox),
843  animate);
844  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cum_ani_checkbox),
846  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(freerun_checkbox),
847  free_running);
848 
849  g_signal_connect(animate_checkbox, "toggled", G_CALLBACK(toggle_animate),NULL);
850  g_signal_connect(cum_ani_checkbox, "toggled", G_CALLBACK(toggle_cum_ani),NULL);
851  g_signal_connect(freerun_checkbox, "toggled", G_CALLBACK(toggle_freerun),NULL);
852 
853  GtkWidget * re_an_button = gtk_button_new_with_mnemonic("_Restart animation");
854  g_signal_connect(re_an_button, "clicked", G_CALLBACK(restart_animation), NULL);
855 
858  GtkWidget * const maxticklabel = make_ticklabel(true);
859  GtkWidget * const minticklabel = make_ticklabel(false);
860  GtkWidget * const speedslider = make_speedslider();
861  GtkWidget * const speedlabel = make_speedlabel();
863 
864  ueventbut = gtk_button_new_with_mnemonic("_Go to event");
865  g_signal_connect(ueventbut, "clicked", G_CALLBACK(getuserevent), NULL);
866 
867  const int nrow = 8, ncol = 11;
868  GtkWidget * tab = gtk_table_new(nrow, ncol, FALSE);
869  gtk_container_add(GTK_CONTAINER(mainwin), tab);
870 
871  GtkWidget * top_row_widgets[ncol] = {
873  re_an_button, freerun_checkbox, speedslider, ueventbox, ueventbut};
874 
875  GtkWidget * second_row_widgets[ncol] = {
876  NULL, NULL, minticklabel, maxticklabel, NULL,
877  NULL, NULL, NULL, speedlabel, NULL, NULL};
878 
879  for(int c = 0; c < ncol; c++){
880  gtk_table_attach(GTK_TABLE(tab), top_row_widgets[c], c, c+1, 0, 1,
881  GtkAttachOptions(GTK_EXPAND | GTK_FILL), GTK_SHRINK, 0, 0);
882 
883  if(second_row_widgets[c] != NULL)
884  gtk_table_attach(GTK_TABLE(tab),second_row_widgets[c],c,c+1,1,2,
885  GtkAttachOptions(GTK_EXPAND | GTK_FILL), GTK_SHRINK, 0, 0);
886  }
887 
888  for(int i = 0; i < NSTATBOXES; i++) makestatbox(i);
889 
890  gtk_table_attach(GTK_TABLE(tab), statbox[statrunevent], 0, ncol-1, 2, 3,
891  GtkAttachOptions(GTK_EXPAND | GTK_FILL), GTK_SHRINK, 0, 0);
892  gtk_table_attach(GTK_TABLE(tab), statbox[stattiming], 0, ncol-1, 3, 4,
893  GtkAttachOptions(GTK_EXPAND | GTK_FILL), GTK_SHRINK, 0, 0);
894  gtk_table_attach(GTK_TABLE(tab), statbox[stathit], 0, ncol-1, 4, 5,
895  GtkAttachOptions(GTK_EXPAND | GTK_FILL), GTK_SHRINK, 0, 0);
896  gtk_table_attach(GTK_TABLE(tab), statbox[staterror], 0, ncol-1, 5, 6,
897  GtkAttachOptions(GTK_EXPAND | GTK_FILL), GTK_SHRINK, 0, 0);
898 
899  trackwin = make_aux_win("Tracks" , stattrack );
900  vertexwin = make_aux_win("Vertices", statvertex);
901 
902  GtkWidget * const tracksbut = gtk_button_new_with_mnemonic("Show _track info");
903  g_signal_connect(tracksbut, "clicked", G_CALLBACK(opentrackwin), NULL);
904 
905  GtkWidget * const vertexbut = gtk_button_new_with_mnemonic("Show _vertex info");
906  g_signal_connect(vertexbut, "clicked", G_CALLBACK(openvertexwin), NULL);
907 
908  gtk_table_attach(GTK_TABLE(tab), tracksbut, ncol-1, ncol, 2, 4,
909  GtkAttachOptions(GTK_EXPAND | GTK_FILL),
910  GtkAttachOptions(GTK_EXPAND | GTK_FILL), 0, 0);
911 
912  gtk_table_attach(GTK_TABLE(tab), vertexbut, ncol-1, ncol, 4, 6,
913  GtkAttachOptions(GTK_EXPAND | GTK_FILL),
914  GtkAttachOptions(GTK_EXPAND | GTK_FILL), 0, 0);
915 
916  for(int i = 0; i < kXorY; i++)
917  gtk_table_attach(GTK_TABLE(tab), edarea[i], 0, ncol,
918  6+2*i, 7+2*i,
919  GtkAttachOptions(GTK_EXPAND | GTK_FILL),
920  GtkAttachOptions(GTK_EXPAND | GTK_FILL), 0, 0);
921 
922  gtk_table_attach(GTK_TABLE(tab), gtk_hseparator_new(), 0, ncol,
923  7, 8,
924  GtkAttachOptions(GTK_EXPAND | GTK_FILL),
925  GtkAttachOptions(GTK_SHRINK), 0, 0);
926 
927  // This isn't the size I want, but along with requesting the size of the
928  // edarea widgets, it has the desired effect, at least more or less.
929  gtk_window_set_default_size(GTK_WINDOW(mainwin), 400, 300);
930 
931  gtk_widget_show_all(mainwin);
932 
933  get_event(0);
934  handle_event();
935 
936  // This is a hack that gets all objects drawn fully. It seems that the window
937  // initially getting set to the size of the ND and then resized to the size
938  // of the FD fails to have the expected effect and the objects outside the
939  // original area don't get redrawn. Maybe this is is an xmonad-only problem?
940  // This is a case of a window resizing itself, and maybe window managers are
941  // supposed to send an expose event in this case, but xmonad doesn't. Or maybe
942  // they aren't supposed to according to the spec, but all the other ones do...
943  gtk_widget_queue_draw(mainwin);
944 
945  if(!ghave_read_all) g_timeout_add(20, prefetch_an_event, NULL);
946 
947  g_timeout_add(500, pollmouseover, NULL);
948 }
949 
950 /*********************************************************************/
951 /* Public functions */
952 /*********************************************************************/
953 
954 // If we could ask for art events from art, this would be the entry point to
955 // the program. However, art only allows access to the art events through its
956 // own event loop, so we have to read events in that loop, then run the GTK
957 // event loop for a while, then break out of that and go back to art's event
958 // loop to get more events, etc. Each time, we return here.
959 //
960 // If there are no more events to read from art, have_read_all will be true
961 // and we will know that we should stay in the GTK event loop.
962 void realmain(const bool have_read_all)
963 {
964  if(have_read_all) ghave_read_all = true;
965  static bool first = true;
966  if(first){
967  first = false;
968  setup();
969  }
970  else if(prefetching){
972  prefetching = false;
973  }
974  else{
975  get_event(1);
976  g_timeout_add(0, draw_event_from_timer, NULL);
977  }
978  gtk_main();
979 }
static gboolean redraw_window(GtkWidget *mainwin, GdkEventConfigure *event, __attribute__((unused)) gpointer d)
Definition: main.cxx:668
T max(const caf::Proxy< T > &a, T b)
void update_active_objects(const noe_view_t V, const int x, const int y)
Definition: main.cxx:185
const XML_Char * name
Definition: expat.h:151
GtkWidget * edarea[kXorY]
Definition: drawing.cxx:20
static void adjusttick(GtkWidget *wg, const gpointer dt)
Definition: main.cxx:601
int32_t user_maxtick
Definition: event.h:53
void setboxes()
Definition: geo.cxx:149
static gboolean clear_error_message(__attribute__((unused)) gpointer dt)
Definition: main.cxx:433
void draw_event(TCanvas *theCanvas, TH2F *h[2], const MatchableEvent &sum)
Definition: dump_event.C:76
static bool get_event(const int change)
Definition: main.cxx:349
int32_t current_maxtick
Definition: event.h:69
gboolean redraw_event(__attribute__((unused)) GtkWidget *widg, __attribute__((unused)) GdkEventExpose *ee, __attribute__((unused)) gpointer data)
Definition: drawing.cxx:101
static void openvertexwin()
Definition: main.cxx:691
Supply basic geometry functions.
static gulong freeruntimeoutid
Definition: main.cxx:118
static GtkWidget * ueventbox
Definition: main.cxx:95
Definition: geo.h:1
uint16_t plane
Definition: event.h:2
static gboolean draw_event_from_timer(__attribute__((unused)) gpointer data)
Definition: main.cxx:335
constexpr T pow(T x)
Definition: pow.h:75
static bool adjusttick_callback_inhibit
Definition: main.cxx:103
Definition: event.h:41
uint16_t cell
Definition: event.h:2
static void setup()
Definition: main.cxx:808
void draw_vertices(cairo_t **cr, const DRAWPARS *const drawpars)
Definition: vertices.cxx:49
static gulong freeruninterval
Definition: main.cxx:116
static bool have_event_by_number(const unsigned int n)
Definition: main.cxx:424
bool clear
Definition: drawing.h:5
void dopanning(const noe_view_t V, GdkEventMotion *gevent)
Definition: zoompan.cxx:31
static GtkWidget * maxtickslider
Definition: main.cxx:97
static GtkWidget * make_aux_win(const char *const name, const statcontents si)
Definition: main.cxx:788
static void opentrackwin()
Definition: main.cxx:696
static void toggle_cum_ani(GtkWidget *w, __attribute__((unused)) gpointer dt)
Definition: main.cxx:520
int32_t current_mintick
Definition: event.h:69
static bool prefetching
Definition: main.cxx:102
static gboolean animation_step(__attribute__((unused)) gpointer data)
Definition: main.cxx:260
static GtkWidget * make_ueventbox()
Definition: main.cxx:779
const Var kY([](const caf::SRProxy *sr){float tmp=0.f;if(sr->mc.nu.empty()) return tmp;tmp=sr->mc.nu[0].y;return tmp;})
static gboolean mouseover(GtkWidget *widg, GdkEventMotion *gevent, __attribute__((unused)) gpointer data)
Definition: main.cxx:222
static void draw_whole_user_event()
Definition: main.cxx:243
static void change_highlighted_reco()
Definition: main.cxx:124
const XML_Char const XML_Char * data
Definition: expat.h:268
#define FALSE
Definition: crcmodel.h:85
static GtkWidget * freerun_checkbox
Definition: main.cxx:93
int active_vertex
Definition: main.cxx:78
static bool animate
Definition: main.cxx:112
Definition: Cand.cxx:23
static void to_next(__attribute__((unused)) GtkWidget *widget, gpointer data)
Definition: main.cxx:395
static void set_bg_color_to_main(GtkWidget *widg)
Definition: main.cxx:732
static void prepare_to_swich_events()
Definition: main.cxx:389
static gboolean pollmouseover(__attribute__((unused)) gpointer data)
Definition: main.cxx:207
std::vector< noeevent > theevents
Definition: noe_module.cc:28
void set_eventn_status_vertex()
Definition: status.cxx:130
int32_t maxtick
Definition: event.h:48
static void close_window()
Definition: main.cxx:683
void set_status(const int boxn, const char *format,...)
Definition: status.cxx:30
int gevi
Definition: main.cxx:76
static void start_freerun_timer()
Definition: main.cxx:484
void draw_tracks(cairo_t **cr, const DRAWPARS *const drawpars)
Definition: tracks.cxx:47
void prev()
Definition: show_event.C:91
gboolean mousebuttonpress(__attribute__((unused)) GtkWidget *widg, GdkEventMotion *gevent, __attribute__((unused)) gpointer data)
Definition: zoompan.cxx:21
__attribute__((unused)) static std
Float_t E
Definition: plot.C:20
static GtkWidget * ueventbut
Definition: main.cxx:94
void update_active_indices(const noe_view_t V, const int x, const int y, const int TDCSTEP)
Definition: active.cxx:115
static void toggle_freerun(__attribute__((unused)) GtkWidget *w, __attribute__((unused)) gpointer dt)
Definition: main.cxx:506
Float_t d
Definition: plot.C:236
static int TDCSTEP
Definition: main.cxx:72
static GtkWidget * mainwin
Definition: main.cxx:90
static void stop_freerun_timer()
Definition: main.cxx:478
static gulong animationinterval
Definition: main.cxx:117
int ncells_perplane
Definition: geo.cxx:22
static gulong statmsgtimeoutid
Definition: main.cxx:120
static GtkWidget * animate_checkbox
Definition: main.cxx:91
static void toggle_animate(GtkWidget *w, __attribute__((unused)) gpointer dt)
Definition: main.cxx:571
GtkTextBuffer * stattext[NSTATBOXES]
Definition: status.cxx:11
static void restart_animation(__attribute__((unused)) GtkWidget *w, __attribute__((unused)) gpointer d)
Definition: main.cxx:589
static gboolean handle_event()
Definition: main.cxx:298
void request_edarea_size()
Definition: drawing.cxx:52
static void makestatbox(const int i)
Definition: main.cxx:743
statcontents
Definition: status.h:1
GtkWidget * statbox[NSTATBOXES]
Definition: status.cxx:12
static void change_highlighted_cell(const int oldactive_plane, const int oldactive_cell)
Definition: main.cxx:157
int first_mucatcher
Definition: geo.cxx:21
Int_t nevent
Definition: macro.C:10
int active_plane
Definition: main.cxx:78
static GtkWidget * make_ticklabel(const bool ismax)
Definition: main.cxx:753
void set_eventn_status_runevent()
Definition: status.cxx:42
void draw_hit(cairo_t *cr, const hit &thishit, GtkWidget **edarea)
Definition: hits.cxx:67
static bool free_running
Definition: main.cxx:114
Definition: event.h:1
static bool cumulative_animation
Definition: main.cxx:113
static GtkWidget * make_tickslider(const bool ismax)
Definition: main.cxx:705
static GtkWidget * vertexwin
Definition: main.cxx:90
void realmain(const bool have_read_all)
Definition: main.cxx:962
int32_t mintick
Definition: event.h:48
static gboolean to_next_free_run(__attribute__((unused)) gpointer data)
Definition: main.cxx:405
#define TRUE
Definition: crcmodel.h:86
static void set_intervals(const int speednum)
Definition: main.cxx:544
static void adjustspeed(GtkWidget *wg, __attribute__((unused)) const gpointer dt)
Definition: main.cxx:646
Definition: geo.h:1
int nplanes
Definition: geom.C:145
void set_eventn_status_hit()
Definition: status.cxx:86
gboolean dozooming(GtkWidget *widg, GdkEventScroll *gevent, gpointer data)
Definition: zoompan.cxx:55
noe_view_t
Definition: geo.h:1
static GtkWidget * trackwin
Definition: main.cxx:90
static GtkWidget * cum_ani_checkbox
Definition: main.cxx:92
T min(const caf::Proxy< T > &a, T b)
static gulong animatetimeoutid
Definition: main.cxx:119
static GtkWidget * make_speedlabel()
Definition: main.cxx:766
int32_t firsttick
Definition: drawing.h:4
int active_cell
Definition: main.cxx:78
int32_t user_mintick
Definition: event.h:53
Float_t w
Definition: plot.C:20
void set_eventn_status_track()
Definition: status.cxx:154
int32_t lasttick
Definition: drawing.h:4
void next()
Definition: show_event.C:84
Definition: status.h:4
static GtkObject * speedadj
Definition: main.cxx:98
int errno
Definition: errno.cpp:12
int active_track
Definition: main.cxx:78
static GtkWidget * mintickslider
Definition: main.cxx:96
static void getuserevent()
Definition: main.cxx:440
static gboolean prefetch_an_event(__attribute__((unused)) gpointer data)
Definition: main.cxx:376
static GtkWidget * make_speedslider()
Definition: main.cxx:718
cairo_pattern_t * eventpattern[kXorY]
Definition: drawing.cxx:21
bool ghave_read_all
Definition: main.cxx:101