Belle II Software  release-06-02-00
DisplayUI.cc
1 /**************************************************************************
2  * basf2 (Belle II Analysis Software Framework) *
3  * Author: The Belle II Collaboration *
4  * *
5  * See git log for contributors and copyright holders. *
6  * This file is licensed under LGPL-3.0, see LICENSE.md. *
7  **************************************************************************/
8 
9 //needs to be first
10 #include <framework/core/ModuleParam.h>
11 
12 #include <framework/dataobjects/DisplayData.h>
13 #include <display/DisplayUI.h>
14 #include <display/VisualRepMap.h>
15 #include <display/BrowsableWrapper.h>
16 #include <display/SplitGLView.h>
17 #include <display/InfoWidget.h>
18 
19 #include <framework/core/InputController.h>
20 #include <framework/datastore/StoreObjPtr.h>
21 #include <framework/datastore/StoreArray.h>
22 #include <framework/dataobjects/EventMetaData.h>
23 #include <framework/logging/Logger.h>
24 #include <framework/pcore/AsyncWrapper.h>
25 
26 #include <TApplication.h>
27 #include <TCanvas.h>
28 #include <TEveBrowser.h>
29 #include <TEveManager.h>
30 #include <TEveEventManager.h>
31 #include <TEveSelection.h>
32 #include <TEveScene.h>
33 #include <TEveViewer.h>
34 #include <TGButton.h>
35 #include <TGFileBrowser.h>
36 #include <TGFrame.h>
37 #include <TGLabel.h>
38 #include <TGNumberEntry.h>
39 #include <TGMsgBox.h>
40 #include <TGFileDialog.h>
41 #include <TGInputDialog.h>
42 #include <TGTextEntry.h>
43 #include <TGLViewer.h>
44 #include <TGLWidget.h>
45 #include <TObject.h>
46 #include <TROOT.h>
47 #include <TSystem.h>
48 #include <TVirtualX.h>
49 
50 #include <sys/prctl.h>
51 #include <signal.h>
52 
53 #include <cassert>
54 
55 
56 using namespace Belle2;
57 
58 DisplayUI::DisplayUI(bool automatic, bool advance):
59  m_automatic(automatic), m_advance(advance)
60 {
61  //ensure GUI thread goes away when parent dies. (root UI loves deadlocks)
62  prctl(PR_SET_PDEATHSIG, SIGHUP);
63 
64  if (!gEve) {
65  B2INFO("Creating TEve window.");
66  TEveManager::Create(!m_automatic, "I"); //show window in interactive mode, hide file browser
67  }
68 
69  setTitle(); //set default window title
70  TEveBrowser* browser = gEve->GetBrowser();
71  browser->HideBottomTab();
72  browser->StartEmbedding(TRootBrowser::kRight);
73  m_viewPane = new SplitGLView();
74  browser->StopEmbedding();
75 
76  //Without this, our own menu bar entries are not drawn (might appear later)
77  browser->Resize(TGDimension(1200, 1000));
78 
79  m_eventData = new TEveElementList();
80 }
81 
82 
84 {
85  delete m_timer;
86 }
87 
88 void DisplayUI::addParameter(const std::string& label, ModuleParam<bool>& param, int level)
89 {
90  Parameter p;
91  p.m_label = label;
92  p.m_param = &param;
93  p.m_level = level;
94  m_paramList.push_back(p);
95 }
96 
98 {
99  // periodically called by auto-advance timer, but we don't want to freeze UI if no events are there
100  if (!m_nextButton->IsEnabled())
101  return;
103 }
104 
106 {
107  if (!m_prevButton->IsEnabled())
108  return;
110  return;
112 }
113 
114 void DisplayUI::setTitle(const std::string& fileName)
115 {
116  assert(gEve);
117  std::string title("Belle II Event Display");
118  if (!fileName.empty())
119  title += " - " + fileName;
120 
121  TEveBrowser* browser = gEve->GetBrowser();
122  browser->SetWindowName(title.c_str());
123 }
124 void DisplayUI::allowFlaggingEvents(const std::string& description)
125 {
126  std::string label = "Flag this Event";
127  if (!description.empty())
128  label += " for " + description;
129 
130  m_flagEvent = new TGCheckButton(nullptr, label.c_str());
131 }
132 
135 {
136  return m_flagEvent && (m_flagEvent->GetState() == kButtonDown);
137 }
138 
140 {
143 
145  }
146 
147  //change UI state?
148  const long numEntries = InputController::numEntries();
150  m_nextButton->SetEnabled((m_currentEntry + 1 < numEntries) or !InputController::canControlInput());
151  if (m_currentEntry != m_eventNumberWidget->GetIntNumber())
152  m_eventNumberWidget->SetIntNumber(m_currentEntry);
153  if (m_timer and InputController::canControlInput() and !m_nextButton->IsEnabled()) {
154  //reached last file entry in play mode, stop
155  togglePlayPause();
156  }
157  if (m_flagEvent)
158  m_flagEvent->SetState(kButtonUp);
159 
160  StoreObjPtr<EventMetaData> eventMetaData;
161  m_eventLabel->SetTextColor(gROOT->GetColor(kBlack));
162  if (!eventMetaData) {
163  m_eventLabel->SetText("No EventMetaData object available.");
164  } else {
165  time_t secondsSinceEpoch = eventMetaData->getTime() / 1e9;
166  //double subSecondPart = double(eventMetaData->getTime()) / 1e9 - secondsSinceEpoch;
167  char date[30] = "<Invalid time>";
168  if (secondsSinceEpoch == 0)
169  strcpy(date, "");
170  else if (auto gmt = gmtime(&secondsSinceEpoch))
171  strftime(date, 30, "<%Y-%m-%d %H:%M:%S UTC>", gmt);
172  m_eventLabel->SetText(TString::Format("Event: \t\t%u\nRun: \t\t%d\nExperiment: \t%d\n\n%s",
173  eventMetaData->getEvent(),
174  eventMetaData->getRun(), eventMetaData->getExperiment(),
175  date));
176  }
177  m_eventLabel->Resize();
178 
179  gVirtualX->SetCursor(gEve->GetBrowser()->GetId(), gVirtualX->CreateCursor(kPointer));
180 }
181 
182 void DisplayUI::goToEvent(Long_t id)
183 {
184  if (id < 0)
185  id = 0;
186  const long numEntries = InputController::numEntries();
187  if (numEntries > 0 and id >= numEntries)
188  id = numEntries - 1;
189 
190 
191  if (m_currentEntry == id) return;
192 
193  if (!InputController::canControlInput() && m_currentEntry != id - 1) {
194  B2ERROR("Cannot switch to event " << id << ", only works in conjunction with RootInput.");
195  }
196 
197  if (numEntries > 0 && InputController::canControlInput()) {
198  B2DEBUG(100, "Switching to event " << id);
200  } else {
201  m_currentEntry++;
202  }
203  gVirtualX->SetCursor(gEve->GetBrowser()->GetId(), gVirtualX->CreateCursor(kWatch));
204  m_nextButton->SetEnabled(false);
205  m_prevButton->SetEnabled(false);
206 
207 
208  if (m_timer)
209  m_timer->Stop(); //apparently timer only deactivates after processing event, so do it manually
210  //process remaining events to ensure redraw (only needed if called from Timeout())
211  gSystem->ProcessEvents();
212 
213  B2DEBUG(100, "exiting event loop now.");
214  //exit event loop to allow basf2 to go to next event
215  gSystem->ExitLoop();
216 }
217 
218 void DisplayUI::goToEvent(Long_t event, Long_t run, Long_t experiment)
219 {
220  const long numEntries = InputController::numEntries();
221  if (numEntries > 0 && InputController::canControlInput()) {
222  B2DEBUG(100, "Switching to event " << event << " in run " << run << ", experiment " << experiment);
223  gVirtualX->SetCursor(gEve->GetBrowser()->GetId(), gVirtualX->CreateCursor(kWatch));
224  InputController::setNextEntry(experiment, run, event);
225  }
226  B2DEBUG(100, "exiting event loop now.");
227  gSystem->ExitLoop();
228 }
229 
231 {
232  goToEvent(m_eventNumberWidget->GetIntNumber());
233 }
234 
236 {
237  if (m_timer) {
238  m_timer->Stop();
239  m_timer->Start((int)(1000.0 * m_autoAdvanceDelay->GetNumber()));
240  }
241 }
242 
244 {
245  const TString icondir(Form("%s/icons/", gSystem->Getenv("ROOTSYS")));
246  if (m_timer) {
247  //pause
248  delete m_timer;
249  m_timer = nullptr;
250  m_playPauseButton->SetPicture(gClient->GetPicture(icondir + "ed_execute.png"));
251  } else {
252  //play
253  m_timer = new TTimer();
254  const int pollIntervalMs = (int)(1000.0 * m_autoAdvanceDelay->GetNumber());
255  m_timer->Connect("Timeout()", "Belle2::DisplayUI", this, "next()");
256  m_timer->Start(pollIntervalMs);
257  m_playPauseButton->SetPicture(gClient->GetPicture(icondir + "ed_interrupt.png"));
258  }
259 }
260 
262 {
263  StoreObjPtr<EventMetaData> eventMetaData;
264  if (!eventMetaData)
265  return; //this should not actually happen.
266 
267  char returnString[256]; //magic length specified by TGInputDialog. Note that it still overwrites the stack if you paste something long enough.
268  new TGInputDialog(gEve->GetBrowser()->GetClient()->GetDefaultRoot(), gEve->GetBrowser(),
269  "Jump to event '#evt/#run/#exp':",
270  TString::Format("%u/%d/%d", eventMetaData->getEvent(), eventMetaData->getRun(), eventMetaData->getExperiment()),
271  returnString);
272  if (returnString[0] == '\0')
273  return; //cancelled
274 
275  unsigned int event, run, exp;
276  returnString[255] = '\0'; //I don't trust root to terminate the string correctly
277  if (sscanf(returnString, "%u/%u/%u", &event, &run, &exp) != 3) {
278  B2WARNING("Wrong format!");
279  return;
280  }
281  goToEvent(event, run, exp);
282 }
283 
285 {
286  if (!gEve)
287  return;
288  if (m_cumulative) {
289  gEve->AddEvent(new TEveEventManager());
290  } else {
291  if (gEve->GetCurrentEvent())
292  gEve->GetCurrentEvent()->DestroyElements();
293 
294  //make sure we delete projected events with the rest of the event scene
295  m_eventData->DestroyElements();
296  }
297 }
298 
299 void DisplayUI::selectionHandler(TEveElement* eveObj)
300 {
301  if (VisualRepMap::getInstance()->isCurrentlySelecting())
302  return;
303 
304  //B2INFO("in selection handler: " << eveObj->GetElementName());
305 
306  const TObject* representedObject = VisualRepMap::getInstance()->getDataStoreObject(eveObj);
307  if (representedObject)
308  m_viewPane->getInfoWidget()->show(representedObject);
309 
312 }
313 
314 void DisplayUI::handleEvent(Event_t* event)
315 {
316  if (event->fType == kGKeyPress) {
317  //B2DEBUG(100, "event type " << event->fType << ", code: " << event->fCode << ", state: " << event->fState);
318  switch (event->fCode) {
319  case 117: //Page Down
320  next();
321  break;
322  case 112: //Page Up
323  prev();
324  break;
325  case 65: //Space bar
326  togglePlayPause();
327  break;
328  case 47: // s
330  break;
331  case 53: //Ctrl + q
332  if (event->fState & kKeyControlMask)
333  exit();
334  break;
335  }
336  }
337 }
338 
340 {
341  if (!m_guiInitialized) {
342  makeGui();
343  if (AsyncWrapper::isAsync()) {
344  //continually check for new events and enable/disable '->' button accordingly
345  TTimer* t = new TTimer();
346  const int pollIntervalMs = 300;
347  t->Connect("Timeout()", "Belle2::DisplayUI", this, "pollNewEvents()");
348  t->Start(pollIntervalMs);
349  }
350 
351  if (m_advance) {
352  togglePlayPause();
353  }
354 
355  //import the geometry in the projection managers (only needs to be done once)
356  TEveScene* gs = gEve->GetGlobalScene();
357  TEveProjectionManager* rphiManager = getViewPane()->getRPhiMgr();
358  if (rphiManager) {
359  rphiManager->ImportElements(gs);
360  }
361  TEveProjectionManager* rhozManager = getViewPane()->getRhoZMgr();
362  if (rhozManager) {
363  rhozManager->ImportElements(gs);
364  }
365 
366  //We want to do special things when objects are selected
367  gEve->GetSelection()->Connect("SelectionAdded(TEveElement*)", "Belle2::DisplayUI", this, "selectionHandler(TEveElement*)");
368  gEve->GetSelection()->Connect("SelectionRepeated(TEveElement*)", "Belle2::DisplayUI", this, "selectionHandler(TEveElement*)");
369  }
370 
371  updateUI(); //update button state
372 
374 
375  m_eventData->AddElement(getViewPane()->getRPhiMgr()->ImportElements(gEve->GetEventScene()));
376  m_eventData->AddElement(getViewPane()->getRhoZMgr()->ImportElements(gEve->GetEventScene()));
377 
378  for (std::string name : m_hideObjects) {
379  TGListTreeItem* eventItem = gEve->GetListTree()->FindItemByPathname("Event");
380  TGListTreeItem* item = gEve->GetListTree()->FindChildByName(eventItem, name.c_str());
381  if (item) {
382  B2INFO("hiding object '" << name << "'.");
383  TEveElement* eveItem = static_cast<TEveElement*>(item->GetUserData());
384  eveItem->SetRnrSelfChildren(false, false);
385  } else {
386  B2ERROR("hideObjects: '" << name << "' not found.");
387  }
388  }
389 
390  m_reshowCurrentEvent = false;
391  if (!m_automatic) {
392  gEve->Redraw3D(false); //do not reset camera when redrawing
393 
394  //restart auto-advance timer after loading event (interval already set)
395  if (m_timer)
396  m_timer->Start(-1);
397 
398  //make display interactive
399  gApplication->Run(true); //return from Run()
400  //interactive part done, event data removed from scene
401  } else {
402  automaticEvent();
403  }
404 
405 
406  return m_reshowCurrentEvent;
407 }
408 
409 
410 
412 {
413  m_guiInitialized = true;
414  TEveBrowser* browser = gEve->GetBrowser();
415  const int margin = 3;
416 
417  browser->Connect("CloseWindow()", "Belle2::DisplayUI", this, "exit()");
418 
419  //add handler for keyboard events, needs to be done for browser TGFrame as well as frames of all TGLViewers
420  browser->Connect("ProcessedEvent(Event_t*)", "Belle2::DisplayUI", this, "handleEvent(Event_t*)");
421  TEveViewerList* viewers = gEve->GetViewers();
422  TEveElement::List_ci end_it = viewers->EndChildren();
423  for (TEveElement::List_i it = viewers->BeginChildren(); it != end_it; ++it) {
424  TEveViewer* v = static_cast<TEveViewer*>(*it);
425  TGLViewer* glv = v->GetGLViewer();
426  glv->GetGLWidget()->Connect("ProcessedEvent(Event_t*)", "Belle2::DisplayUI", this, "handleEvent(Event_t*)");
427  }
428 
429  //----------Event Control
430  browser->StartEmbedding(TRootBrowser::kLeft);
431 
432  TGMainFrame* frmMain = new TGMainFrame(gClient->GetRoot(), 240, 600);
433  frmMain->SetWindowName("Event control main frame");
434  frmMain->SetCleanup(kDeepCleanup);
435 
436  const TString icondir(Form("%s/icons/", gSystem->Getenv("ROOTSYS")));
437 
438  TGGroupFrame* event_frame = new TGGroupFrame(frmMain);
439  event_frame->SetTitle("Event");
440  {
441  TGHorizontalFrame* hf = new TGHorizontalFrame(event_frame);
442  {
443  m_prevButton = new TGPictureButton(hf, gClient->GetPicture(icondir + "GoBack.gif"));
444  m_prevButton->SetToolTipText("Go to previous event (Page Up)");
445  hf->AddFrame(m_prevButton, new TGLayoutHints(kLHintsLeft | kLHintsCenterY, margin, margin, margin, margin));
446  m_prevButton->Connect("Clicked()", "Belle2::DisplayUI", this, "prev()");
447 
448  const long numEntries = InputController::numEntries();
449  m_eventNumberWidget = new TGNumberEntry(hf, 0, 4, 999, TGNumberFormat::kNESInteger,
450  TGNumberFormat::kNEANonNegative,
451  TGNumberFormat::kNELLimitMinMax,
452  0, numEntries - 1);
454  hf->AddFrame(m_eventNumberWidget, new TGLayoutHints(kLHintsLeft | kLHintsCenterY, margin, margin, margin, margin));
455  //note: parameter of ValueSet signal is _not_ the number just set.
456  m_eventNumberWidget->Connect("ValueSet(Long_t)", "Belle2::DisplayUI", this, "goToEventWidget()");
457  m_eventNumberWidget->GetNumberEntry()->Connect("ReturnPressed()", "Belle2::DisplayUI", this, "goToEventWidget()");
458 
459 
460  if (numEntries > 0) {
461  TGLabel* maxEvents = new TGLabel(hf, TString::Format("/%ld", numEntries - 1));
462  hf->AddFrame(maxEvents, new TGLayoutHints(kLHintsLeft | kLHintsCenterY, margin, margin, margin, margin));
463  }
464 
465  m_nextButton = new TGPictureButton(hf, gClient->GetPicture(icondir + "GoForward.gif"));
466  m_nextButton->SetToolTipText("Go to next event (Page Down)");
467  hf->AddFrame(m_nextButton, new TGLayoutHints(kLHintsLeft | kLHintsCenterY, margin, margin, margin, margin));
468  m_nextButton->Connect("Clicked()", "Belle2::DisplayUI", this, "next()");
469  }
470  event_frame->AddFrame(hf, new TGLayoutHints(kLHintsCenterX | kLHintsCenterY, 0, 0, 0, 0));
471 
472  hf = new TGHorizontalFrame(event_frame);
473  {
474  //const bool file = InputController::canControlInput();
475  const bool async = AsyncWrapper::isAsync();
476 
477  TGLabel* delayLabel = new TGLabel(hf, "Delay (s):");
478  hf->AddFrame(delayLabel, new TGLayoutHints(kLHintsLeft | kLHintsCenterY, margin, margin, margin, margin));
479 
480  const double valueSeconds = async ? 0.5 : 3.5;
481  m_autoAdvanceDelay = new TGNumberEntry(hf, valueSeconds, 3, 999, TGNumberFormat::kNESRealOne,
482  TGNumberFormat::kNEAPositive,
483  TGNumberFormat::kNELLimitMin,
484  0.1); //minimum
485  //m_autoAdvanceDelay->SetState(file or async);
486  hf->AddFrame(m_autoAdvanceDelay, new TGLayoutHints(kLHintsLeft | kLHintsCenterY, margin, margin, margin, margin));
487  //note: parameter of ValueSet signal is _not_ the number just set.
488  m_autoAdvanceDelay->Connect("ValueSet(Long_t)", "Belle2::DisplayUI", this, "autoAdvanceDelayChanged()");
489  m_autoAdvanceDelay->GetNumberEntry()->Connect("ReturnPressed()", "Belle2::DisplayUI", this, "autoAdvanceDelayChanged()");
490 
491  m_playPauseButton = new TGPictureButton(hf, gClient->GetPicture(icondir + "ed_execute.png"));
492  //m_playPauseButton->SetEnabled(file or async);
493  m_playPauseButton->SetToolTipText("Advance automatically to next event after the given delay.");
494  hf->AddFrame(m_playPauseButton, new TGLayoutHints(kLHintsCenterX | kLHintsCenterY, margin, margin, margin, margin));
495  m_playPauseButton->Connect("Clicked()", "Belle2::DisplayUI", this, "togglePlayPause()");
496  }
497  event_frame->AddFrame(hf, new TGLayoutHints(kLHintsCenterX | kLHintsCenterY, 0, 0, 0, 0));
498 
499  TGButton* jumpToEventButton = new TGTextButton(event_frame, "Jump to event/run/exp...");
500  jumpToEventButton->SetEnabled(InputController::canControlInput());
501  jumpToEventButton->SetToolTipText("Find a given entry identified by an event / run / experiment triplet in the current file");
502  event_frame->AddFrame(jumpToEventButton, new TGLayoutHints(kLHintsCenterX | kLHintsCenterY, margin, margin, margin, margin));
503  jumpToEventButton->Connect("Clicked()", "Belle2::DisplayUI", this, "showJumpToEventDialog()");
504 
505  m_eventLabel = new TGLabel(event_frame);
506  event_frame->AddFrame(m_eventLabel, new TGLayoutHints(kLHintsCenterX | kLHintsCenterY, margin, margin, margin, margin));
507 
508  if (m_flagEvent) {
509  TString descr = m_flagEvent->GetString();
510  delete m_flagEvent;
511  m_flagEvent = new TGCheckButton(event_frame, descr);
512  m_flagEvent->SetToolTipText("Set return value to true for this event");
513  m_flagEvent->SetState(kButtonUp);
514  event_frame->AddFrame(m_flagEvent, new TGLayoutHints(kLHintsCenterX | kLHintsCenterY, margin, margin, margin, margin));
515  }
516  }
517  frmMain->AddFrame(event_frame, new TGLayoutHints(kLHintsExpandX, 0, 0, 0, 0));
518 
519  TGGroupFrame* param_frame = new TGGroupFrame(frmMain);
520  param_frame->SetTitle("Options");
521  {
522  const int nParams = m_paramList.size();
523  for (int i = 0; i < nParams; i++) {
524  TGCheckButton* b = new TGCheckButton(param_frame, m_paramList[i].m_label.c_str(), i);
525  b->SetToolTipText(m_paramList[i].m_param->getDescription().c_str());
526  b->SetState(m_paramList[i].m_param->getValue() ? kButtonDown : kButtonUp);
527  b->Connect("Clicked()", "Belle2::DisplayUI", this, TString::Format("handleParameterChange(=%d)", i));
528  int indentation = 15 * m_paramList[i].m_level;
529  param_frame->AddFrame(b, new TGLayoutHints(kLHintsExpandX | kLHintsCenterY, indentation, margin, margin, margin));
530  }
531 
532  }
533  frmMain->AddFrame(param_frame, new TGLayoutHints(kLHintsExpandX, 0, 0, 0, 0));
534 
535 
536  TGGroupFrame* viewer_frame = new TGGroupFrame(frmMain);
537  viewer_frame->SetTitle("Current Viewer");
538  {
539  TGHorizontalFrame* hf = new TGHorizontalFrame(viewer_frame);
540  TGButton* b = 0;
541  {
542  b = new TGTextButton(hf, "Save As...");
543  b->SetToolTipText("Save a bitmap graphic for the current viewer");
544  hf->AddFrame(b, new TGLayoutHints(kLHintsCenterX | kLHintsCenterY, margin + 1, margin, margin, margin));
545  b->Connect("Clicked()", "Belle2::DisplayUI", this, "savePicture()");
546 
547  b = new TGTextButton(hf, "Save As (High-Res)... ");
548  b->SetToolTipText("Save a bitmap graphic for the current viewer with user-specified size");
549  hf->AddFrame(b, new TGLayoutHints(kLHintsCenterX | kLHintsCenterY, margin, margin, margin, margin));
550  b->Connect("Clicked()", "Belle2::DisplayUI", this, "saveHiResPicture()");
551 
552  }
553  viewer_frame->AddFrame(hf, new TGLayoutHints(kLHintsCenterX | kLHintsCenterY, 0, 0, 0, 0));
554 
555  hf = new TGHorizontalFrame(viewer_frame);
556  {
557  b = new TGTextButton(hf, "Dock/Undock Viewer");
558  b->SetToolTipText("Move current viewer into it's own window, or back to its original position");
559  hf->AddFrame(b, new TGLayoutHints(kLHintsCenterX | kLHintsCenterY, margin, margin, margin, margin));
560  b->Connect("Clicked()", "Belle2::DisplayUI", this, "toggleUndock()");
561  }
562  viewer_frame->AddFrame(hf, new TGLayoutHints(kLHintsCenterX | kLHintsCenterY, 0, 0, 0, 0));
563  }
564  frmMain->AddFrame(viewer_frame, new TGLayoutHints(kLHintsExpandX, 0, 0, 0, 0));
565 
566 
567  TGGroupFrame* visOptionsFrame = new TGGroupFrame(frmMain);
568  visOptionsFrame->SetTitle("Visualisation Options");
569  {
570  TGHorizontalFrame* hf = new TGHorizontalFrame(visOptionsFrame);
571  {
572  TGButton* b = new TGTextButton(hf, "Dark/light colors");
573  b->SetToolTipText("Toggle background color");
574  hf->AddFrame(b, new TGLayoutHints(kLHintsCenterX | kLHintsCenterY, margin, margin, margin, margin));
575  b->Connect("Clicked()", "Belle2::DisplayUI", this, "toggleColorScheme()");
576  }
577  visOptionsFrame->AddFrame(hf, new TGLayoutHints(kLHintsCenterX | kLHintsCenterY, 0, 0, 0, 0));
578 
579  {
580  TGCheckButton* c = new TGCheckButton(visOptionsFrame, "Cumulative mode (experimental)");
581  c->SetToolTipText("Do not erase previous event, i.e. show data from multiple events. This is quite unstable and will crash sooner or later.");
582  c->SetState(m_cumulative ? kButtonDown : kButtonUp);
583  c->Connect("Clicked()", "Belle2::DisplayUI", this, "toggleCumulative()");
584  visOptionsFrame->AddFrame(c, new TGLayoutHints(kLHintsExpandX | kLHintsCenterY, 0, margin, margin, margin));
585  }
586  }
587  frmMain->AddFrame(visOptionsFrame, new TGLayoutHints(kLHintsExpandX, 0, 0, 0, 0));
588 
589 
590  TGGroupFrame* automatisation_frame = new TGGroupFrame(frmMain);
591  automatisation_frame->SetTitle("Automatic Saving (experimental)");
592  {
593  TGHorizontalFrame* hf = new TGHorizontalFrame(automatisation_frame);
594  {
595  TGLabel* prefixLabel = new TGLabel(hf, "Prefix:");
596  hf->AddFrame(prefixLabel, new TGLayoutHints(kLHintsLeft | kLHintsCenterY, margin, margin, margin, margin));
597 
598  m_autoFileNamePrefix = new TGTextEntry(hf, "display_");
599  hf->AddFrame(m_autoFileNamePrefix, new TGLayoutHints(kLHintsLeft | kLHintsCenterY, margin, margin, margin, margin));
600  }
601  automatisation_frame->AddFrame(hf, new TGLayoutHints(kLHintsCenterX | kLHintsCenterY, 0, 0, 0, 0));
602 
603  hf = new TGHorizontalFrame(automatisation_frame);
604  {
605  TGLabel* widthLabel = new TGLabel(hf, "Width (px):");
606  hf->AddFrame(widthLabel, new TGLayoutHints(kLHintsLeft | kLHintsCenterY, margin, margin, margin, margin));
607 
608  m_autoPictureWidth = new TGNumberEntry(hf, 800, 5, 998, TGNumberFormat::kNESInteger,
609  TGNumberFormat::kNEANonNegative,
610  TGNumberFormat::kNELLimitMinMax,
611  100, 6000);
612  hf->AddFrame(m_autoPictureWidth, new TGLayoutHints(kLHintsLeft | kLHintsCenterY, margin, margin, margin, margin));
613 
614  TGButton* b = new TGTextButton(hf, "Save PNGs");
615  b->SetToolTipText("Save bitmap graphics for all further events. Cannot be aborted. (EXPERIMENTAL)");
616  hf->AddFrame(b, new TGLayoutHints(kLHintsLeft | kLHintsCenterY, margin, margin, margin, margin));
617  b->Connect("Clicked()", "Belle2::DisplayUI", this, "startAutomaticRun()");
618  }
619  automatisation_frame->AddFrame(hf, new TGLayoutHints(kLHintsCenterX | kLHintsCenterY, 0, 0, 0, 0));
620 
621  }
622  frmMain->AddFrame(automatisation_frame, new TGLayoutHints(kLHintsExpandX, 0, 0, 0, 0));
623 
624  //this will be shown at the very bottom
625  TGGroupFrame* exit_frame = new TGGroupFrame(frmMain);
626  exit_frame->SetTitle("Closing");
627  {
628  TGHorizontalFrame* hf = new TGHorizontalFrame(exit_frame);
629  {
630  TGButton* b = new TGTextButton(hf, " Exit ");
631  b->SetToolTipText("Close the display and stop basf2 after this event.");
632  hf->AddFrame(b, new TGLayoutHints(kLHintsCenterX | kLHintsCenterY, margin, margin, margin, margin));
633  b->Connect("Clicked()", "Belle2::DisplayUI", this, "exit()");
634 
635  }
636  exit_frame->AddFrame(hf, new TGLayoutHints(kLHintsCenterX | kLHintsCenterY, 0, 0, 0, 0));
637 
638  }
639  frmMain->AddFrame(exit_frame, new TGLayoutHints(kLHintsExpandX | kLHintsBottom, 0, 0, 0, 0));
640 
641  //magic to prevent the frame being empty.
642  frmMain->MapSubwindows();
643  frmMain->Resize();
644  frmMain->MapWindow();
645  browser->StopEmbedding("Event Control");
646 }
647 
649 {
650  if (id >= (int)m_paramList.size()) {
651  B2ERROR("widget ID too large!");
652  return;
653  }
654  //toggle value
655  m_paramList[id].m_param->setValue(!m_paramList[id].m_param->getValue());
656 
657  //reprocess current event
658  m_reshowCurrentEvent = true;
659  gSystem->ExitLoop();
660 }
661 
663 {
664  TEveViewerList* viewers = gEve->GetViewers();
665  TEveElement::List_ci end_it = viewers->EndChildren();
666  for (TEveElement::List_i it = viewers->BeginChildren(); it != end_it; ++it) {
667  TEveViewer* v = static_cast<TEveViewer*>(*it);
668  TGLViewer* glv = v->GetGLViewer();
669 
670  bool dark = glv->ColorSet().Background().GetColorIndex() != kWhite;
671  glv->RefLightColorSet().Background().SetColor(kWhite);
672  glv->RefDarkColorSet().Background().SetColor(kBlack);
673  if (dark)
674  glv->UseLightColorSet();
675  else
676  glv->UseDarkColorSet();
677  glv->DoDraw();
678  }
679 }
680 
682 {
683  TGLViewer* activeGLviewer = getViewPane()->getActiveGLViewer();
684  TEveViewerList* viewers = gEve->GetViewers();
685  TEveElement::List_ci end_it = viewers->EndChildren();
686  TEveViewer* activeViewer = nullptr;
687  for (TEveElement::List_i it = viewers->BeginChildren(); it != end_it; ++it) {
688  TEveViewer* v = static_cast<TEveViewer*>(*it);
689  if (v->GetGLViewer() == activeGLviewer) {
690  activeViewer = v;
691  break;
692  }
693  }
694 
695  if (!activeViewer) {
696  B2ERROR("No active viewer. Please select one by clicking on it.");
697  return;
698  }
699 
700  TEveCompositeFrameInMainFrame* packFrame = dynamic_cast<TEveCompositeFrameInMainFrame*>(activeViewer->GetEveFrame());
701  if (!packFrame) {
702  //we're docked
703  activeViewer->UndockWindow();
704  } else {
705  //we're undocked
706  packFrame->MainFrameClosed();
707  }
708 }
709 
710 void DisplayUI::savePicture(bool highres)
711 {
712  const char* filetypes[] = {
713  "PNG (bitmap)", "*.png",
714  "PDF (experimental!)", "*.pdf",
715  "All files", "*",
716  0, 0
717  };
718  TGFileInfo fi;
719  fi.fFileTypes = filetypes;
720 
721  //deleting the pointer crashes, so I'm assuming this is magically cleaned up at some point
722  new TGFileDialog(gEve->GetBrowser()->GetClient()->GetDefaultRoot(), gEve->GetBrowser(), kFDSave, &fi);
723  if (!fi.fFilename)
724  return; //cancelled
725  TGLViewer* v = getViewPane()->getActiveGLViewer();
726  bool success = false;
727  if (!highres) {
728  success = v->SavePicture(fi.fFilename);
729  } else {
730  char returnString[256];
731  new TGInputDialog(gEve->GetBrowser()->GetClient()->GetDefaultRoot(), gEve->GetBrowser(),
732  "Bitmap width (pixels) [Note: Values larger than ~5000 may cause crashes.]",
733  "4000", //default
734  returnString);
735  if (returnString[0] == '\0')
736  return; //cancelled
737  const TString t(returnString);
738  if (!t.IsDigit()) {
739  B2ERROR("Given width is not a number!");
740  return;
741  }
742  const int width = t.Atoi();
743  B2INFO("Saving bitmap (width: " << width << "px)..."); //may take a while
744  success = v->SavePictureWidth(fi.fFilename, width, false); // don't scale pixel objects
745  }
746 
747  if (success) {
748  B2INFO("Saved image in: " << fi.fFilename);
749  } else {
750  new TGMsgBox(gEve->GetBrowser()->GetClient()->GetDefaultRoot(), gEve->GetBrowser(), "Saving image failed",
751  TString::Format("Couldn't save to '%s'! Please verify you used an appropriate image file extension in the file name. Check console output for further information.",
752  fi.fFilename));
753  }
754 
755  //file dialog leaves empty box, redraw
756  gEve->FullRedraw3D(false); //do not reset camera when redrawing
757 }
758 
760 {
761  B2INFO("Starting automatic run.");
762  m_automatic = true;
763 
764  //save current event, too
765  automaticEvent();
766 
767  gSystem->ExitLoop();
768 }
769 
771 {
772  static int i = 0;
773  B2INFO("Saving event " << i);
774 
775  //force immediate redraw
776  gEve->FullRedraw3D();
777 
778  TEveViewerList* viewers = gEve->GetViewers();
779  TEveElement::List_ci end_it = viewers->EndChildren();
780  for (TEveElement::List_i it = viewers->BeginChildren(); it != end_it; ++it) {
781  TEveViewer* v = static_cast<TEveViewer*>(*it);
782  TGLViewer* glv = v->GetGLViewer();
783 
784  TString projectionName(v->GetName());
785  projectionName.ReplaceAll(" viewer", "");
786  projectionName.ReplaceAll("/", "");
787  const int width = m_autoPictureWidth->GetIntNumber();
788  const bool scalePixelObjects = false;
789  TString name = TString::Format("%s_%s_%d.png", m_autoFileNamePrefix->GetText(), projectionName.Data(), i);
790  glv->SavePictureWidth(name, width, scalePixelObjects);
791  }
792 
793  i++;
794 }
795 
797 {
798  if (!AsyncWrapper::isAsync())
799  return;
800  if (!gEve)
801  return;
802 
803  int numEvents = AsyncWrapper::numAvailableEvents();
804  bool state = m_nextButton->IsEnabled();
805  //only call SetEnabled() if state changes (interrupts UI interactions otherwise)
806  if (state != (numEvents > 0))
807  m_nextButton->SetEnabled(numEvents > 0);
808 }
809 
811 {
812  gSystem->ExitLoop();
813  gROOT->SetInterrupt();
814  m_cumulative = false;
815 
816  if (!gEve)
817  return;
818 
819  // avoid emittting signals at end
820  gEve->GetBrowser()->Disconnect();
821 
822  gEve->GetSelection()->Disconnect();
823  gEve->GetHighlight()->Disconnect();
824 
825  gEve->GetBrowser()->UnmapWindow();
826  gEve->GetBrowser()->SendCloseMessage();
827 
828  //stop event processing after current event
829  StoreObjPtr<EventMetaData> eventMetaData;
830  eventMetaData->setEndOfData();
831 }
832 
833 void DisplayUI::showUserData(const DisplayData& displayData)
834 {
835  static std::map<std::string, BrowsableWrapper*> wrapperMap;
836  for (auto& entry : wrapperMap) {
837  entry.second = NULL;
838  }
839 
840  if (!displayData.m_histograms.empty()) {
841  static TGFileBrowser* fileBrowser = NULL;
842  if (!fileBrowser) {
843  gEve->GetBrowser()->StartEmbedding(0);
844  fileBrowser = gEve->GetBrowser()->MakeFileBrowser();
845  gEve->GetBrowser()->StopEmbedding("Histograms");
846 
847 
848  //create new tab with canvas
849  gEve->GetBrowser()->StartEmbedding(TRootBrowser::kRight);
850  TEveWindowSlot* slot = TEveWindow::CreateWindowMainFrame();
851  gEve->GetBrowser()->StopEmbedding();
852  slot->StartEmbedding();
853  new TCanvas;
854  slot->StopEmbedding("Canvas");
855  }
856 
857  //invert pad -> name map
858  const std::map<TVirtualPad*, std::string>& padMap = BrowsableWrapper::getPads();
859  std::map<std::string, TVirtualPad*> nameMap;
860  for (const auto& entry : padMap) {
861  nameMap[entry.second] = entry.first;
862  }
863 
864  for (unsigned int i = 0; i < displayData.m_histograms.size(); i++) {
865  std::string name(displayData.m_histograms.at(i)->GetName());
866  if (!wrapperMap[name])
867  wrapperMap[name] = new BrowsableWrapper(displayData.m_histograms.at(i));
868  else
869  wrapperMap[name]->setWrapped(displayData.m_histograms.at(i));
870  BrowsableWrapper* wrapper = wrapperMap[name];
871 
872  if (nameMap.find(name) != nameMap.end()) {
873  TVirtualPad* oldGpad = gPad;
874  //redraw
875  nameMap[name]->cd();
876  wrapper->Browse(fileBrowser->Browser());
877 
878  //restore state
879  oldGpad->cd();
880  }
881  fileBrowser->Add(wrapper);
882 
883  }
884  }
885 
886  for (const auto& pair : displayData.m_selectedObjects) {
887  //convert the name, index pair back into pointer
888  StoreArray<TObject> array(pair.first);
889  const TObject* obj = array[pair.second];
890  if (obj) {
891  VisualRepMap::getInstance()->select(array[pair.second]);
892  } else {
893  B2WARNING("Cannot select object " << pair.first << "[" << pair.second << "], not found. Is the array available?");
894  }
895  }
896 
897  /*
898  TMacro* m = new TMacro;
899  m->AddLine("{ TEveWindowSlot* currentWindow = gEve->GetWindowManager()->GetCurrentWindowAsSlot(); \
900  if (currentWindow) { \
901  std::cout << \" this is a slot!\\n\"; \
902  currentWindow->StartEmbedding(); \
903  TCanvas* c = new TCanvas; \
904  c->cd(); \
905  currentWindow->StopEmbedding(\"Canvas\"); \
906  } else { std::cout << \" not a slot!\\n\"; } \
907  }");
908  m->SetName("Add canvas to active window");
909  fileBrowser->Add(m);
910  */
911 }
static bool isAsync()
returns true if the current process is on the receiving (async) side of an AsyncWrapper.
Definition: AsyncWrapper.h:57
static int numAvailableEvents()
Retun number of events available in the RingBuffer.
Definition: AsyncWrapper.cc:41
A wrapper for browsable objects to enable automatic redrawing.
static const std::map< TVirtualPad *, std::string > & getPads()
Get list of pads (static).
Add custom information to the display.
Definition: DisplayData.h:55
std::vector< TH1 * > m_histograms
Histograms to be shown in Eve.
Definition: DisplayData.h:111
std::vector< std::pair< std::string, unsigned int > > m_selectedObjects
List of selected objects (array name, index).
Definition: DisplayData.h:113
void clearEvent()
remove all event data in current event.
Definition: DisplayUI.cc:284
void next()
Go to next event.
Definition: DisplayUI.cc:97
TGLabel * m_eventLabel
show event/run/exp number for current event.
Definition: DisplayUI.h:216
void handleEvent(Event_t *event)
Handles keyboard shortcuts.
Definition: DisplayUI.cc:314
long m_currentEntry
Current entry id.
Definition: DisplayUI.h:177
std::vector< Parameter > m_paramList
List of run time configurable module parameters.
Definition: DisplayUI.h:195
void startAutomaticRun()
switch to automatic mode, where visualisations are saved for each event, with no interactive control.
Definition: DisplayUI.cc:759
void autoAdvanceDelayChanged()
m_autoAdvanceDelay was changed, update m_timer if enabled.
Definition: DisplayUI.cc:235
~DisplayUI()
Destructor.
Definition: DisplayUI.cc:83
void togglePlayPause()
Handle Play/Pause button clicks.
Definition: DisplayUI.cc:243
TGButton * m_prevButton
Button to switch to previous event.
Definition: DisplayUI.h:198
void exit()
Close window and exit immediately.
Definition: DisplayUI.cc:810
bool m_automatic
If true, disable interactive control and call automaticEvent() instead.
Definition: DisplayUI.h:186
bool getReturnValue() const
Return value for current event, only makes sense if allowFlaggingEvents(true) was called.
Definition: DisplayUI.cc:134
void setTitle(const std::string &fileName="")
Set title of Eve window.
Definition: DisplayUI.cc:114
DisplayUI(bool automatic=false, bool advance=false)
Constructor.
Definition: DisplayUI.cc:58
TEveElementList * m_eventData
List of event data, including projections.
Definition: DisplayUI.h:225
void automaticEvent()
The actual per-event functionality for automatic saving.
Definition: DisplayUI.cc:770
void selectionHandler(TEveElement *eveObj)
Handle special actions when objects are selected.
Definition: DisplayUI.cc:299
void makeGui()
Build the buttons for event navigation.
Definition: DisplayUI.cc:411
void pollNewEvents()
Check if new events are available, and go to next event.
Definition: DisplayUI.cc:796
TGButton * m_nextButton
Button to switch to next event.
Definition: DisplayUI.h:201
TGPictureButton * m_playPauseButton
Play / Pause button.
Definition: DisplayUI.h:210
TTimer * m_timer
Polling/auto-advance timer.
Definition: DisplayUI.h:231
void handleParameterChange(int id)
Called when one of the module parameters is changed via UI.
Definition: DisplayUI.cc:648
bool m_guiInitialized
Was GUI already built?
Definition: DisplayUI.h:180
SplitGLView * getViewPane()
return right-side pane with viewers.
Definition: DisplayUI.h:133
void toggleUndock()
dock/undock active viewer.
Definition: DisplayUI.cc:681
void updateUI()
Update UI after a new event was loaded, as well as m_currentEntry.
Definition: DisplayUI.cc:139
TGNumberEntry * m_autoAdvanceDelay
Delay for automatic advance, in seconds.
Definition: DisplayUI.h:207
void goToEvent(Long_t id)
Go to event with index id.
Definition: DisplayUI.cc:182
TGNumberEntry * m_eventNumberWidget
Event switcher with numeric entry.
Definition: DisplayUI.h:204
void addParameter(const std::string &label, ModuleParam< bool > &param, int level)
Generate UI elements so the given module parameter can be changed at run time.
Definition: DisplayUI.cc:88
void savePicture(bool highres=false)
Save the current view to a user-defined filename.
Definition: DisplayUI.cc:710
TGCheckButton * m_flagEvent
Show control for flagging events (to set module return value).
Definition: DisplayUI.h:213
void toggleColorScheme()
Toggle between light and dark color scheme for viewers.
Definition: DisplayUI.cc:662
bool startDisplay()
Start interactive display for current event.
Definition: DisplayUI.cc:339
void showUserData(const DisplayData &displayData)
Add user-defined data (histograms, etc.).
Definition: DisplayUI.cc:833
void prev()
Go to previous event.
Definition: DisplayUI.cc:105
TGNumberEntry * m_autoPictureWidth
width of saved PNGs.
Definition: DisplayUI.h:222
void goToEventWidget()
go to the event given by m_eventNumberWidget.
Definition: DisplayUI.cc:230
bool m_reshowCurrentEvent
Show current event again after startDisplay() returns?
Definition: DisplayUI.h:183
bool m_advance
If true, start advancing through the events on startup.
Definition: DisplayUI.h:189
TGTextEntry * m_autoFileNamePrefix
File name prefix (prefix + #event + "_" + projection + ".png").
Definition: DisplayUI.h:219
void showJumpToEventDialog()
Show a dialog to to enter exp, run, event numbers.
Definition: DisplayUI.cc:261
void saveHiResPicture()
alias for savePicture(true).
Definition: DisplayUI.h:130
SplitGLView * m_viewPane
pointer to right-side pane with viewers.
Definition: DisplayUI.h:228
std::vector< std::string > m_hideObjects
objects which are to be hidden (can be manually re-enabled in tree view).
Definition: DisplayUI.h:234
void allowFlaggingEvents(const std::string &description="")
Show control for flagging events (to set module return value).
Definition: DisplayUI.cc:124
bool m_cumulative
If true, DisplayModule shouldn't clear previous data (i.e.
Definition: DisplayUI.h:192
void show(const char *uri="main:", bool clearSelection=true)
Navigate to given URI.
Definition: InfoWidget.cc:93
void update()
reset for new event (try to show same object if it exists).
Definition: InfoWidget.cc:55
static void setNextEntry(long entry)
Set the file entry to be loaded the next time event() is called.
static long numEntries()
Returns total number of entries in the event tree.
static long getCurrentEntry()
returns the entry number currently loaded.
static bool canControlInput()
Is there an input module to be controlled.
static std::string getCurrentFileName()
Return name of current file in loaded chain (or empty string if none loaded).
Responsible for arranging the GL viewers and providing related functionality.
Definition: SplitGLView.h:30
InfoWidget * getInfoWidget() const
text-based info viewer.
Definition: SplitGLView.h:64
TEveProjectionManager * getRPhiMgr() const
return R-Phi projection manager.
Definition: SplitGLView.h:58
TGLEmbeddedViewer * getActiveGLViewer()
return TGLEmbeddedViewer that is active right now.
Definition: SplitGLView.cc:297
TEveProjectionManager * getRhoZMgr() const
return Rho-Z projection manager.
Definition: SplitGLView.h:60
Type-safe access to single objects in the data store.
Definition: StoreObjPtr.h:95
const TObject * getDataStoreObject(TEveElement *elem) const
Get object represented by given visual representation.
Definition: VisualRepMap.cc:56
static VisualRepMap * getInstance()
get instance pointer.
Definition: VisualRepMap.cc:36
void selectRelated(TEveElement *eveObj) const
Select related objects.
void selectOnly(TEveElement *eveObj) const
Deselect all other objects.
void select(const TObject *object) const
Select the representation of the given object.
Definition: VisualRepMap.cc:87
Abstract base class for different kinds of events.
Wraps a module parameter that can be toggled from the UI.
Definition: DisplayUI.h:164