GSI Object Oriented Online Offline (Go4)  GO4-6.3.0
TGo4AnalysisWindow.cpp
Go to the documentation of this file.
1 // $Id$
2 //-----------------------------------------------------------------------
3 // The GSI Online Offline Object Oriented (Go4) Project
4 // Experiment Data Processing at EE department, GSI
5 //-----------------------------------------------------------------------
6 // Copyright (C) 2000- GSI Helmholtzzentrum fuer Schwerionenforschung GmbH
7 // Planckstr. 1, 64291 Darmstadt, Germany
8 // Contact: http://go4.gsi.de
9 //-----------------------------------------------------------------------
10 // This software can be used under the license agreements as stated
11 // in Go4License.txt file which is part of the distribution.
12 //-----------------------------------------------------------------------
13 
14 #include "TGo4AnalysisWindow.h"
15 
16 #include <iostream>
17 
18 #include "TSystem.h"
19 #include "TList.h"
20 
21 #include <QtCore/QTimer>
22 #include <QtCore/QTextStream>
23 #include <QtCore/QProcess>
24 
25 #include <QHBoxLayout>
26 #include <QGridLayout>
27 #include <QTextEdit>
28 #include <QFileDialog>
29 #include <QToolButton>
30 #include <QDateTime>
31 #include <QLabel>
32 #include <QCloseEvent>
33 
34 #include "TGo4QSettings.h"
35 #include "TGo4AnalysisProxy.h"
36 #include "TGo4BrowserProxy.h"
38 #include "QGo4CommandsHistory.h"
39 
40 TGo4AnalysisWindow::TGo4AnalysisWindow(QWidget *parent, const char *name, bool needoutput, bool needkillbtn) :
41  QGo4Widget( parent, name)
42 {
43  setCanDestroyWidget(false);
44  setAcceptDrops(false);
45  fAnalysisProcess = nullptr;
46  fxOutput = nullptr;
47  outputBuffer = "";
48  fiMaxOuputSize = 0;
49  fbShowTimestamps = kFALSE;
50  fxTimeFormat = "yyyy-MM-dd hh:mm:ss";
51  fxCmdHist = nullptr;
52  fHasLink = false;
53  fTerminateOnClose = false;
54 
55  fNewObjectForEditor = true;
56 
57  setWindowTitle("Analysis Terminal");
58 
59  setFont(go4sett->getTermFont());
60 
61  if (needoutput) {
62 
63  resize(700, 400);
64  setWindowIcon(QIcon(":/icons/analysiswin.png"));
65  QGridLayout* layout = new QGridLayout( this );
66  layout->setContentsMargins(11,11,11,11);
67  layout->setSpacing(6);
68 
69  fxOutput = new QTextEdit();
70  fxOutput->setUndoRedoEnabled(false);
71  fxOutput->setAutoFormatting(QTextEdit::AutoNone);
72  fxOutput->setWordWrapMode(QTextOption::NoWrap);
73  fxOutput->setReadOnly(true);
74  layout->addWidget(fxOutput, 0, 0);
75 
78 
79  QHBoxLayout *box1 = new QHBoxLayout();
80  box1->addWidget(new QLabel("Press enter to execute.", this), 1);
81  CreateCmdLine(box1);
82  layout->addLayout(box1, 1, 0);
83 
84  QHBoxLayout *box2 = new QHBoxLayout();
85  CreateButtons(box2, needkillbtn);
86  layout->addLayout(box2, 2, 0);
87 
89  } else {
90 
91  QHBoxLayout *box = new QHBoxLayout(this);
92 
93  CreateButtons(box, needkillbtn);
94 
95  CreateCmdLine(box);
96 
97  adjustSize();
98  }
99 }
100 
101 
103 {
106 }
107 
108 
109 void TGo4AnalysisWindow::CreateCmdLine(QHBoxLayout* box)
110 {
111  fxCmdHist = new QGo4CommandsHistory(this, "commandslist");
112  fxCmdHist->setToolTip("CINT command for analysis process. Note: '@' means 'TGo4Analysis::Instance()->' . A leading '$' will invoke Python script.");
114  fxCmdHist->setMinimumSize( QSize( 220, 25 ) );
115 
116  QStringList histlist = go4sett->getCommandsHistoryAnalysis();
117  fxCmdHist->addItems(histlist);
118  fxCmdHist->setEditText(QString());
119 
120  box->addWidget(fxCmdHist, HasOutput() ? 3 : 1);
121 
122  QToolButton* MacroSearch = new QToolButton( this );
123  MacroSearch->setMinimumSize( QSize( 30, 25 ) );
124  MacroSearch->setMaximumSize( QSize( 30, 25 ) );
125  MacroSearch->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
126  MacroSearch->setIcon( QIcon(":/icons/findfile.png" ) );
127  MacroSearch->setToolTip("Search root or python macro on disk.");
128  QObject::connect(MacroSearch, &QToolButton::clicked, this, &TGo4AnalysisWindow::FileDialog_Macro);
129  box->addWidget(MacroSearch,1);
130 }
131 
132 void TGo4AnalysisWindow::CreateButtons(QHBoxLayout* box, bool needkillbtn)
133 {
134  if (needkillbtn) {
135  QToolButton* KillProcess = new QToolButton( this );
136  KillProcess->setMinimumSize( QSize( 30, 25 ) );
137  KillProcess->setMaximumSize( QSize( 30, 25 ) );
138  KillProcess->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
139  KillProcess->setIcon( QIcon( ":/icons/killanal.png" ) );
140  KillProcess->setToolTip("Apply Ctrl+C in the analysis terminal.");
141  QObject::connect(KillProcess, &QToolButton::clicked, this, &TGo4AnalysisWindow::RequestTerminate);
142  box->addWidget(KillProcess);
143  }
144 
145  if (HasOutput()) {
146  QToolButton* ClearButton = new QToolButton( this );
147  ClearButton->setMinimumSize( QSize( 30, 25 ) );
148  ClearButton->setMaximumSize( QSize( 30, 25 ) );
149  ClearButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
150  ClearButton->setIcon( QIcon( ":/icons/clear.png" ) );
151  ClearButton->setToolTip("Clear Terminal Window.");
152  QObject::connect(ClearButton, &QToolButton::clicked, this, &TGo4AnalysisWindow::ClearAnalysisOutput);
153  box->addItem(new QSpacerItem(1,1));
154  box->addWidget(ClearButton,1);
155 
156  QToolButton* ScrollEndButton = new QToolButton( this );
157  ScrollEndButton->setMinimumSize( QSize( 30, 25 ) );
158  ScrollEndButton->setMaximumSize( QSize( 30, 25 ) );
159  ScrollEndButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
160  ScrollEndButton->setIcon( QIcon( ":/icons/shiftdown.png" ) );
161  ScrollEndButton->setToolTip("Scroll to end of Terminal Window. (keyboard: end)");
162  QObject::connect(ScrollEndButton, &QToolButton::clicked, this, &TGo4AnalysisWindow::ScrollEndAnalysisOutput);
163  box->addWidget(ScrollEndButton,1);
164  }
165 
166  QToolButton* PrintHistoButton = new QToolButton( this );
167  PrintHistoButton->setMinimumSize( QSize( 30, 25 ) );
168  PrintHistoButton->setMaximumSize( QSize( 30, 25 ) );
169  PrintHistoButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
170  PrintHistoButton->setIcon( QIcon( ":/icons/hislist.png" ) );
171  PrintHistoButton->setToolTip("Print list of all histograms.");
172  QObject::connect(PrintHistoButton, &QToolButton::clicked, this, &TGo4AnalysisWindow::PrintHistograms);
173  box->addWidget(PrintHistoButton,1);
174 
175  QToolButton* PrintConnyButton = new QToolButton( this );
176  PrintConnyButton->setMinimumSize( QSize( 30, 25 ) );
177  PrintConnyButton->setMaximumSize( QSize( 30, 25 ) );
178  PrintConnyButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
179  PrintConnyButton->setIcon( QIcon( ":/icons/condlist.png" ) );
180  PrintConnyButton->setToolTip("Print list of all conditions.");
181  QObject::connect(PrintConnyButton, &QToolButton::clicked, this, &TGo4AnalysisWindow::PrintConditions);
182  box->addWidget(PrintConnyButton,1);
183 
184  QToolButton* PrintEventButton = new QToolButton( this );
185  PrintEventButton->setMinimumSize( QSize( 30, 25 ) );
186  PrintEventButton->setMaximumSize( QSize( 30, 25 ) );
187  PrintEventButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
188  PrintEventButton->setIcon( QIcon( ":/icons/zoom.png" ) );
189  PrintEventButton->setToolTip("Start Event Inspection panel");
190  QObject::connect(PrintEventButton, &QToolButton::clicked, this, &TGo4AnalysisWindow::PrintEvent);
191  box->addWidget(PrintEventButton,1);
192 }
193 
195 {
196  //if (Browser())
197  // Browser()->ToggleMonitoring(0);
198  CloseMDIParentSlot(); // JAM remove top level window when changing connection from client to server
199 }
200 
201 
203 {
204  return fxOutput != nullptr;
205 }
206 
208 {
209  fiMaxOuputSize = sz;
210 }
211 
213 {
214  if (!fxOutput)
215  return;
216 
217  unsigned int buflen = outputBuffer.length();
218 
219  if (fiMaxOuputSize > 0) {
220 
221  // size remaining after cut of text
222  unsigned int cutlength = fiMaxOuputSize / 2;
223 
224  if (buflen > 0) {
225  unsigned int outlen = fxOutput->toPlainText().length();
226  if (buflen + outlen < fiMaxOuputSize) {
227  fxOutput->append(outputBuffer);
228  // fxOutput->moveCursor(QTextCursor::End);
229  // ^JAM just for test, we dont keep this since one may want inspect history during new printouts
230  } else if (buflen >= cutlength) {
231  outputBuffer.remove(0, buflen - cutlength);
232  fxOutput->setText(outputBuffer);
233  fxOutput->moveCursor(QTextCursor::End);
234  } else {
235  QString curr = fxOutput->toPlainText();
236  curr.remove(0, cutlength - buflen);
237  curr += outputBuffer;
238  fxOutput->setText(curr);
239  fxOutput->moveCursor(QTextCursor::End);
240  }
241  }
242  } else {
243  if (buflen > 0) {
244  fxOutput->append(outputBuffer);
245  // fxOutput->moveCursor(QTextCursor::End);
246  // ^JAM just for test, we dont keep this since one may want inspect history during new printouts
247  }
248  }
249  outputBuffer = "";
250  QTimer::singleShot(100, this, &TGo4AnalysisWindow::updateTerminalOutput);
251 }
252 
254 {
255  if (fAnalysisProcess) {
256  QByteArray ba = fAnalysisProcess->readAllStandardOutput();
257  QString buf(ba);
258  AppendOutputBuffer(buf,0);
259  }
260 }
261 
262 
264 {
265  if (fAnalysisProcess) {
266  QByteArray ba = fAnalysisProcess->readAllStandardError();
267  QString buf(ba);
268  AppendOutputBuffer(buf,1);
269  }
270 }
271 
272 
273 void TGo4AnalysisWindow::AddTimeStamp(QString& buf, int prio)
274 {
275  QString pre = prio>0 ? "!":"*";
276  QString format= QString("GO4-%1> [%2]\n").arg(pre).arg(fxTimeFormat);
277  buf = QDateTime::currentDateTime().toString(format)+ buf;
278 }
279 
280 
281 void TGo4AnalysisWindow::AppendOutputBuffer(const QString& value, int prio)
282 {
283  if(fbShowTimestamps || prio>1)
284  {
285  QString buf=value;
286  AddTimeStamp(buf,prio);
287  outputBuffer.append(buf);
288  }
289  else
290  {
291  outputBuffer.append(value);
292  }
293 }
294 
295 void TGo4AnalysisWindow::ExtractProgArgs(QString &progname, QStringList &args)
296 {
297  QString quoted;
298 
299  int first = progname.indexOf("\"");
300  int last = progname.lastIndexOf("\"");
301  if ((first > 0) && (last > first)) {
302  quoted = progname.mid(first+1, last-first-1);
303  progname.resize(first);
304  }
305 
306 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
307  args = progname.split(" ",QString::SkipEmptyParts);
308 #else
309  args = progname.split(" ",Qt::SkipEmptyParts);
310 #endif
311  if (args.size() > 0) {
312  progname = args.front();
313  args.pop_front();
314  }
315  if (quoted.length() > 0)
316  args.push_back(quoted);
317 }
318 
319 
320 void TGo4AnalysisWindow::StartAnalysisShell(const char *text, const char *workdir, bool aschildprocess)
321 {
323 
324  setWindowTitle("Analysis Terminal");
325 
326  // IMPORTANT - process should be child of analysis window
327  // to be terminated when analysis window closed or Ctrl-C is pressed
328 
329  fAnalysisProcess = new QProcess(aschildprocess ? this : nullptr);
330  QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
331  fAnalysisProcess->setProcessEnvironment(env);
332 
333  QObject::connect(fAnalysisProcess, &QProcess::readyReadStandardOutput, this, &TGo4AnalysisWindow::readFromStdout);
334  QObject::connect(fAnalysisProcess, &QProcess::readyReadStandardError, this, &TGo4AnalysisWindow::readFromStderr);
335  if (workdir) fAnalysisProcess->setWorkingDirectory(workdir);
336 
337  QString progname = text;
338  QStringList args;
339 
340  TGo4AnalysisWindow::ExtractProgArgs(progname, args);
341 
342  fAnalysisProcess->start(progname, args);
343 
344  if (fAnalysisProcess->state() == QProcess::NotRunning) {
345  std::cerr << "Fatal error. Could not start the Analysis" << std::endl;
347  } else {
348  fTerminateOnClose = aschildprocess;
349 #ifdef _MSC_VER
350  setWindowTitle("Analysis Terminal [with QProcess]");
351 #else
352  setWindowTitle(QString("Analysis Terminal [pid:%1]").arg(fAnalysisProcess->processId()));
353 #endif
354  }
355 }
356 
358 {
359  ServiceCall("TerminateAnalysis");
360 }
361 
363 {
364  if (!fAnalysisProcess) return;
365  AppendOutputBuffer("\nTerminate process ... \n\n",2);
366  fAnalysisProcess->terminate();
367  if (fAnalysisProcess->state() == QProcess::Running)
368  fAnalysisProcess->kill();
369  delete fAnalysisProcess;
370  fAnalysisProcess = nullptr;
371 }
372 
374 {
375  if (fxOutput)
376  fxOutput->clear();
377 }
378 
380 {
381  if (fxOutput)
382  fxOutput->moveCursor(QTextCursor::End);
383 }
384 
386 {
387  if (!fxOutput) return;
388  QFileDialog fd(this,
389  "Save analysis terminal output",
390  "", "Plain text (*.txt)");
391  fd.setFileMode( QFileDialog::AnyFile );
392  fd.setAcceptMode(QFileDialog::AcceptSave);
393 
394  if (fd.exec() != QDialog::Accepted) return;
395 
396  QStringList flst = fd.selectedFiles();
397  if (flst.isEmpty()) return;
398  QString fileName = flst[0];
399 
400  if(!fileName.endsWith(".txt")) fileName.append(".txt");
401  QFile NewFile(fileName);
402  NewFile.open( QIODevice::ReadWrite | QIODevice::Append );
403  QTextStream t( &NewFile );
404  t << fxOutput->toPlainText() << "\n";
405  NewFile.close();
406 }
407 
409 {
410  QString cmd = fxCmdHist->currentText();
411  if (cmd.isEmpty()) return;
412 
413  TGo4ServerProxy *anal = GetAnalysis();
414  if (anal) {
415  anal->ExecuteLine(cmd.toLatin1().constData());
417  }
418 }
419 
420 
422 {
423  QFileDialog fd( this,
424  "Select ROOT macro for analysis task",
425  "", "CINT Macro(*.C);;Python script(*.py)");
426  fd.setFileMode( QFileDialog::ExistingFile);
427 
428  if (fd.exec() != QDialog::Accepted) return;
429  QStringList flst = fd.selectedFiles();
430  if (flst.isEmpty()) return;
431  bool iscint = fd.selectedNameFilter().contains(".C");
432  bool ispyth = fd.selectedNameFilter().contains(".py");
433  QString cmd;
434  if(iscint)
435  {
436  cmd = QString(".x ") + flst[0];
437  }
438  else if(ispyth)
439  {
440  cmd = QString("$") + flst[0];
441  }
442  else
443  {
444  // never come here, but anyway...
445  cmd = QString(".x ") + flst[0];
446  if(!cmd.endsWith(".C")) cmd.append(".C");
447  }
448  int index = fxCmdHist->findText(cmd);
449  if(index<0) {
450  fxCmdHist->insertItem(-1,cmd);
451  index = fxCmdHist->findText(cmd);
452  }
453  fxCmdHist->setCurrentIndex(index);
454 }
455 
457 {
458  const QString cmd = "@PrintHistograms()";
459  int index = fxCmdHist->findText(cmd);
460  if (index < 0) {
461  fxCmdHist->insertItem(-1, cmd);
462  index = fxCmdHist->findText(cmd);
463  }
464  fxCmdHist->setCurrentIndex(index);
465  CommandSlot();
466 }
467 
469 {
470  const QString cmd = "@PrintConditions()";
471  int index = fxCmdHist->findText(cmd);
472  if (index < 0) {
473  fxCmdHist->insertItem(-1, cmd);
474  index = fxCmdHist->findText(cmd);
475  }
476  fxCmdHist->setCurrentIndex(index);
477  CommandSlot();
478 }
479 
481 {
482  ServiceCall("StartEventInfo");
483 }
484 
486 {
487  // can be called only once when window is created
488  AddLink(slot, "DebugOutput");
489  fHasLink = true;
490 }
491 
493 {
494  // can be called only once when window is created
495  AddLink(slot, "ObjectUpdateCmd");
496  fHasLink = true;
497 }
498 
499 void TGo4AnalysisWindow::WaitForNewObject(bool isobjectforeditor)
500 {
501  fNewObjectForEditor = isobjectforeditor;
502 }
503 
504 void TGo4AnalysisWindow::linkedObjectUpdated(const char *linkname, TObject *obj)
505 {
506  if (strcmp(linkname, "ObjectUpdateCmd") == 0) {
507  TGo4AnalysisObjectResult* res = dynamic_cast<TGo4AnalysisObjectResult*>(obj);
508  if (!res) return;
510  const char *itemname = res->GetObjectFullName();
511  TClass *cl = Browser()->ItemClass(itemname);
512  if (cl) InformThatObjectCreated(itemname, cl);
513  if (!fNewObjectForEditor) EditItem(itemname);
514  fNewObjectForEditor = true;
515  }
516 
517  if (strcmp(linkname, "DebugOutput") == 0) {
518 
519  TList *lst = dynamic_cast<TList *> (obj);
520 
521  TListIter iter(lst, kFALSE);
522 
523  while (auto obj = iter()) {
524  if (obj == lst->First()) continue;
525  AppendOutputBuffer(obj->GetName());
526  AppendOutputBuffer("\n");
527  }
528  }
529 }
530 
531 void TGo4AnalysisWindow::linkedObjectRemoved(const char *linkname)
532 {
533  fHasLink = false;
534 
535  if (!HasOutput() || (strcmp(linkname, "DebugOutput") == 0))
536  ServiceCall("CloseAnalysisWindow");
537 }
538 
539 void TGo4AnalysisWindow::resizeEvent(QResizeEvent * e)
540 {
541  // store size of top widget - JAM only if not within dock window (analysis server)
542  // size of top widget will be restored when new panel is created
543  if(HasOutput())
544  go4sett->storePanelSize(parentWidget(), "AnalysisWindow");
545 }
546 
547 void TGo4AnalysisWindow::closeEvent(QCloseEvent* e)
548 {
549  e->ignore(); // destroying this would mix up the upper level management
550  QWidget *mdi = parentWidget();
551  if (mdi)
552  mdi->hide(); // instead of destroying, we just hide it when X is clicked. JAM
553 }
554 
void StartAnalysisShell(const char *cmd, const char *workdir=nullptr, bool aschildprocess=false)
QStringList getCommandsHistoryAnalysis()
void setCommandsHistoryAnalysis(const QStringList &commands)
void CreateCmdLine(QHBoxLayout *)
QStringList getHistory(int max=50)
void AddTimeStamp(QString &buf, int prio=0)
QGo4CommandsHistory * fxCmdHist
static void ExtractProgArgs(QString &prog, QStringList &args)
QString getTermTimeFormat()
void storePanelSize(QWidget *w, const QString &kind="ViewPanel")
const char * GetObjectFullName() const
void ServiceCall(const char *name, void *par=nullptr)
Definition: QGo4Widget.cpp:398
void linkedObjectRemoved(const char *linkname) override
void closeEvent(QCloseEvent *e) override
void resizeEvent(QResizeEvent *) override
void setCanDestroyWidget(bool on=true)
Definition: QGo4Widget.h:201
TClass * ItemClass(const char *name)
void linkedObjectUpdated(const char *linkname, TObject *obj) override
TGo4BrowserProxy * Browser()
Definition: QGo4Widget.cpp:219
void EditItem(const QString &itemname)
Definition: QGo4Widget.cpp:345
void WorkWithDebugOutput(TGo4Slot *slot)
virtual void ExecuteLine(const char *line)
bool getTermShowTimestamp()
TGo4QSettings * go4sett
void WorkWithUpdateObjectCmd(TGo4Slot *slot)
void InformThatObjectCreated(const char *itemname, TClass *cl)
Definition: QGo4Widget.cpp:282
TGo4ServerProxy * GetAnalysis(const char *itemname=nullptr)
Definition: QGo4Widget.cpp:386
TGo4AnalysisWindow(QWidget *parent=nullptr, const char *name=nullptr, bool needoutput=false, bool needkillbtn=true)
unsigned int fiMaxOuputSize
void AddLink(const char *itemname, const char *linkname)
Definition: QGo4Widget.cpp:117
void AppendOutputBuffer(const QString &value, int prio=0)
void CreateButtons(QHBoxLayout *, bool)
void CloseMDIParentSlot()
Definition: QGo4Widget.cpp:79
int getTermHistorySize()
void WaitForNewObject(bool isobjectforeditor)