source: fact/Evidence/Edd/Edd.cc@ 10105

Last change on this file since 10105 was 10105, checked in by ogrimm, 12 years ago
Fixed warning if alarm server down
File size: 54.8 KB
Line 
1
2/* ============================================================
3
4Edd - Evidence Data Display
5
6Qt-based graphical user interface for the Evidence contron system
7
8EddLineDisplay changes its background colour in case it display
9a DIM status service
10
11April 2010, Oliver Grimm
12
13============================================================ */
14
15#include "Edd.h"
16
17Qt::GlobalColor LineColors[] = {Qt::black, Qt::blue, Qt::red, Qt::green, Qt::white,
18 Qt::darkRed, Qt::darkGreen, Qt::darkBlue, Qt::cyan,
19 Qt::darkCyan, Qt::magenta, Qt::darkMagenta,
20 Qt::gray, Qt::darkGray, Qt::lightGray};
21
22
23class EddDim *Handler;
24QString DRSBoard = "drsdaq";
25
26// History chooser function (opens plot for numeric data, TextHist for all other)
27QWidget *OpenHistory(char *Service, int Index) {
28
29 QString Format;
30 DimBrowser Browser;
31 class EvidenceHistory *Hist = Handler->GetHistory(Service);
32
33 // Check if history service available
34 if (Hist == NULL || Hist->GetFormat() == NULL) {
35 QMessageBox::warning(NULL, "Edd Message", QString("Could not retrieve history for service ") + Service ,QMessageBox::Ok);
36
37 // If service currently available, take its format
38 char *Name, *Fmt;
39
40 Browser.getServices(Service);
41 if (Browser.getNextService(Name, Fmt) != 0) Format = QString(Fmt);
42 else {
43 Handler->DropHistory(Service);
44 return NULL;
45 }
46 }
47
48 if (Format.isEmpty()) Format = Hist->GetFormat();
49 Handler->DropHistory(Service);
50
51 if (Format.size() == 1 && Format[0] != 'C') return new EddPlot(Service, Index);
52 else return new EddText(Service);
53}
54
55// Set status tip (returns true if service was available)
56bool SetStatus(QWidget *W, QString Name, int Time, QString Format, int Index) {
57
58 QString Status;
59
60 if (Index != -1) Name = Name + "(" + QString::number(Index) + ")";
61
62 if (Time == -1) Status = QString("%1: unavailable").arg(Name);
63 else Status = QString("%1: Last update %2 Format '%3'").arg(Name).arg(QDateTime::fromTime_t(Time).toString()).arg(Format);
64
65 W->setStatusTip(Status);
66
67 return(Time != -1);
68}
69
70
71//////////////////////////////////////////
72// Text display for arbitary DIM service//
73//////////////////////////////////////////
74
75EddLineDisplay::EddLineDisplay(QString Name, int Index, QWidget *P):
76 QLineEdit(P), ServiceName(Name), Index(Index) {
77
78 LastHist = NULL;
79
80 // Widget properties
81 setReadOnly(true);
82 setMaximumWidth(100);
83 ShowAsTime = false;
84 setFrame(false);
85 setAttribute(Qt::WA_DeleteOnClose);
86
87 // Connect to DIM handler
88 if (connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) {
89 printf("Failed connection for %s\n", Name.toAscii().data());
90 }
91
92 // Context menu
93 Menu = new QMenu(this);
94 Menu->addAction("Open new history", this, SLOT(MenuOpenHistory()));
95 Menu->addAction("Copy service", this, SLOT(MenuCopyService()));
96 Menu->addAction("Copy data", this, SLOT(MenuCopyData()));
97
98 // Subscribe to service
99 Handler->Subscribe(Name);
100}
101
102// Destructor
103EddLineDisplay::~EddLineDisplay() {
104
105 Handler->Unsubscribe(ServiceName);
106}
107
108// Update widget
109void EddLineDisplay::Update(QString Name, int Time, QByteArray, QString Format, QString Text) {
110
111 if (ServiceName != Name) return;
112
113 // Check if service available
114 QPalette Pal = palette();
115 if (!SetStatus(this, Name, Time, Format, Index)) {
116 setText("n/a");
117 Pal.setColor(QPalette::Base, Qt::lightGray);
118 setPalette(Pal);
119 return;
120 }
121 else Pal.setColor(QPalette::Base, Qt::white);
122
123 // Message service backgound colour determined by severity
124 if (Name.endsWith("/Message")) {
125 switch (Text.section(' ', 0, 0).toInt()) {
126 case 0: Pal.setColor(QPalette::Base, Qt::white); break;
127 case 1: Pal.setColor(QPalette::Base, Qt::yellow); break;
128 case 2: Pal.setColor(QPalette::Base, Qt::red); break;
129 case 3: Pal.setColor(QPalette::Base, Qt::red); break;
130 default: break;
131 }
132 Text = Text.section(' ', 1);
133 }
134 else if (Format[0].toUpper() != 'C' && Format != "I:1;C") Text = Text.section(' ', Index, Index);
135
136 if (!ShowAsTime) setText(Text);
137 else setText(QDateTime::fromTime_t(Text.toInt()).toString());
138
139 setCursorPosition(0);
140 setPalette(Pal);
141}
142
143// Open plot if mouse release within widget
144void EddLineDisplay::mouseReleaseEvent(QMouseEvent *Event) {
145
146 if (Event->button()!=Qt::LeftButton || !contentsRect().contains(Event->pos())) return;
147
148 // Check if last history plot still open, then raise
149 foreach (QWidget *Widget, QApplication::allWidgets()) {
150 if (Widget == LastHist) {
151 Widget->activateWindow();
152 Widget->raise();
153 return;
154 }
155 }
156
157 // If not, open new plot
158 EddLineDisplay::MenuOpenHistory();
159}
160
161// Handling of mouse press event: Register start position for drag
162void EddLineDisplay::mousePressEvent(QMouseEvent *Event) {
163
164 if (Event->button() == Qt::LeftButton) dragStart = Event->pos();
165}
166
167// Handling of dragging (Drag and MimeData will be deleted by Qt)
168void EddLineDisplay::mouseMoveEvent(QMouseEvent *Event) {
169
170 if ((Event->buttons() & Qt::LeftButton) == 0) return;
171 if ((Event->pos()-dragStart).manhattanLength() < QApplication::startDragDistance()) return;
172
173 QDrag *Drag = new QDrag(this);
174 QMimeData *MimeData = new QMimeData;
175 QByteArray Data;
176 MimeData->setData("Edd/Service", Data.append(ServiceName + " " + QString::number(Index)));
177 Drag->setMimeData(MimeData);
178 Drag->exec();
179}
180
181//
182// Opening context menu
183//
184void EddLineDisplay::contextMenuEvent(QContextMenuEvent *Event) {
185
186 Menu->exec(Event->globalPos());
187}
188
189// Menu: Open history plot
190void EddLineDisplay::MenuOpenHistory() {
191
192 LastHist = OpenHistory(ServiceName.toAscii().data(), Index);
193 if (LastHist != NULL) LastHist->show();
194}
195
196// Menu: Copy service name
197void EddLineDisplay::MenuCopyService() {
198
199 QMimeData *MimeData = new QMimeData;
200 QByteArray Data;
201 MimeData->setData("Edd/Service", Data.append(ServiceName + " " + QString::number(Index)));
202 QApplication::clipboard()->setMimeData(MimeData);
203}
204
205// Menu: Copy data
206void EddLineDisplay::MenuCopyData() {
207
208 QApplication::clipboard()->setText(text());
209}
210
211
212//////////////////////////////////////////
213// Sending string command to DIM server //
214//////////////////////////////////////////
215
216EddCommand::EddCommand(QString Name, QWidget *P): QLineEdit(P), Name(Name) {
217
218 setToolTip("Send command "+Name);
219 connect(this, SIGNAL(returnPressed()), SLOT(SendCommand()));
220}
221
222// Send command
223void EddCommand::SendCommand() {
224
225 DimClient::sendCommand(Name.toAscii().data(), text().toAscii().data());
226 clear();
227}
228
229
230//////////////////////////////////
231// History plot for DIM service //
232//////////////////////////////////
233
234EddPlot::EddPlot(QString Service, int Index, QWidget *P): EddBasePlot(P) {
235
236 // Widget properties
237 setAcceptDrops(true);
238 setAxisScaleDraw(QwtPlot::xBottom, new EddTimeScale);
239
240 // Update time range on plot when axis change (update() results in paintEvent())
241 connect(axisWidget(QwtPlot::xBottom), SIGNAL(scaleDivChanged()), SLOT(update()));
242
243 legend()->setItemMode(QwtLegend::ClickableItem);
244 connect(this, SIGNAL(legendClicked (QwtPlotItem *)), SLOT(LegendClicked(QwtPlotItem *)));
245
246 // Connect to DIM handler
247 if (connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) {
248 printf("Failed connection for %s\n", Service.toAscii().data());
249 }
250
251 // Additonal context menu items
252 QAction* Action = Menu->addAction("Paste service", this, SLOT(MenuPasteService()));
253 Menu->removeAction(Action);
254 Menu->insertAction(Menu->actions().value(1), Action);
255
256 // Maximum number if points in curve (will be increased in Update())
257 SizeLimit = 0;
258
259 // DIM client
260 if (!Service.isEmpty()) AddService(Service, Index);
261}
262
263// Destructor (items with parent widget are automatically deleted)
264EddPlot::~EddPlot() {
265
266 while (!List.isEmpty()) DeleteCurve(List.last().Signal);
267}
268
269// Add history service to plot
270void EddPlot::AddService(QString Name, int Index) {
271
272 // Check if curve already present on plot
273 for (int i=0; i<List.size(); i++) {
274 if (Name == List[i].Name && Index == List[i].Index) {
275 QMessageBox::warning(this, "Edd Message",Name+" ("+QString::number(Index)+") already present",QMessageBox::Ok);
276 return;
277 }
278 }
279
280 // Generate new curve and subscribe to service
281 struct ItemDetails N;
282
283 N.Name = Name;
284 N.Signal = NewCurve(Name+"("+QString::number(Index)+")");
285 N.Index = Index;
286 List.append(N);
287
288 Handler->Subscribe(Name);
289}
290
291// Update widget (must happen in GUI thread)
292void EddPlot::Update(QString Name, int Time, QByteArray, QString Format, QString Text) {
293
294 bool DoUpdate = false;
295
296 // Determine which plot item this call belongs to
297 int ItemNo;
298 for (ItemNo=0; ItemNo<List.size(); ItemNo++) if (List[ItemNo].Name == Name) {
299
300 DoUpdate = true;
301
302 // If size limit reached, clear buffer
303 if (List[ItemNo].Signal->dataSize() > SizeLimit) List[ItemNo].Signal->setData(QPolygonF());
304
305 // If buffer empty, request new history buffer
306 if (List[ItemNo].Signal->dataSize() == 0) {
307 int Count=0;
308 const struct EvidenceHistory::Item *R;
309 class EvidenceHistory *Hist;
310
311 if ((Hist = Handler->GetHistory(List[ItemNo].Name)) != NULL) {
312 double Number=0;
313 while ((R=Hist->Next()) != NULL) {
314 switch (*(Hist->GetFormat())) {
315 case 'I':
316 case 'L': Number = *((int *) R->Data + List[ItemNo].Index); break;
317 case 'S': Number = *((short *) R->Data + List[ItemNo].Index); break;
318 case 'F': Number = *((float *) R->Data + List[ItemNo].Index); break;
319 case 'D': Number = *((double *) R->Data + List[ItemNo].Index); break;
320 case 'X': Number = *((long long *) R->Data + List[ItemNo].Index); break;
321 default: break;
322 }
323 AddPoint(ItemNo, R->Time, Number);
324 Count++;
325 }
326
327 // Local buffer at least twice as large as longest history
328 if (SizeLimit < 2*Count) SizeLimit = 2*Count;
329 }
330 Handler->DropHistory(List[ItemNo].Name);
331 }
332
333 // Append data only if service available
334 if (SetStatus(this, Name, Time, Format)) {
335 QString Txt = Text;
336 Txt = Txt.section(' ', List[ItemNo].Index, List[ItemNo].Index);
337 AddPoint(ItemNo, Time, atof(Txt.toAscii().data()));
338 }
339 }
340
341 if (DoUpdate) UpdatePlot();
342}
343
344
345// Add text indicating time range to plot
346void EddPlot::paintEvent(QPaintEvent *) {
347
348 QString Text;
349 QFont Font;
350 QPainter Painter(this);
351
352 Text = QDateTime::fromTime_t((int) axisScaleDiv(QwtPlot::xBottom)->lowerBound()).toString("d-MMM-yyyy hh:mm:ss") + " to " + QDateTime::fromTime_t((int) axisScaleDiv(QwtPlot::xBottom)->upperBound()).toString("d-MMM-yyyy hh:mm:ss");
353
354 Font.setPointSize(6);
355 Painter.setFont(Font);
356 Painter.drawText(0, height(), Text);
357}
358
359// Drag and drop methods
360void EddPlot::dragEnterEvent(QDragEnterEvent *Event) {
361
362 if (Event->mimeData()->hasFormat("Edd/Service")) Event->acceptProposedAction();
363}
364
365void EddPlot::dropEvent(QDropEvent *Event) {
366
367 QByteArray D(Event->mimeData()->data("Edd/Service"));
368 AddService(D.left(D.lastIndexOf(' ')), D.right(D.size()-D.lastIndexOf(' ')).toInt());
369}
370
371void EddPlot::LegendClicked(QwtPlotItem *Item) {
372
373 QString D(Item->title().text());
374 D.replace('(',' ').chop(1);
375
376 QDrag *Drag = new QDrag(this);
377 QMimeData *MimeData = new QMimeData;
378 QByteArray Data;
379 MimeData->setData("Edd/Service", Data.append(D));
380 Drag->setMimeData(MimeData);
381 Drag->exec();
382}
383
384// Add new service by pasting name
385void EddPlot::MenuPasteService() {
386
387 const QMimeData *D = QApplication::clipboard()->mimeData();
388 if (!D->hasFormat("Edd/Service")) return;
389
390 QByteArray E(D->data("Edd/Service"));
391 AddService(E.left(E.lastIndexOf(' ')), E.right(E.size()-E.lastIndexOf(' ')).toInt());
392}
393
394// Remove list entry
395void EddPlot::DeleteCurve(QwtPlotCurve *Curve) {
396
397 for (int i=0; i<List.size(); i++) if (List[i].Signal == Curve) {
398 Handler->Unsubscribe(List[i].Name);
399 List.removeAt(i);
400 }
401}
402
403
404//////////////////
405// General plot //
406//////////////////
407
408// Constructor (all slots called in GUI thread, therefore no mutex necessary)
409EddBasePlot::EddBasePlot(QWidget *P): QwtPlot(P) {
410
411 // Widget properties
412 setAttribute(Qt::WA_DeleteOnClose);
413 setAutoReplot(false);
414 setCanvasBackground(EddPlotBackgroundColor);
415 setMargin(15);
416
417 // Plot navigation
418 Zoomer = new QwtPlotZoomer(QwtPlot::xBottom,QwtPlot::yLeft,canvas());
419 connect(Zoomer, SIGNAL(zoomed(const QwtDoubleRect &)), this, SLOT(HandleZoom(const QwtDoubleRect &)));
420 connect(Zoomer, SIGNAL(zoomed(const QwtDoubleRect &)), this, SLOT(ReDoStats()));
421
422 Magnifier = new QwtPlotMagnifier(canvas());
423 Magnifier->setMouseButton(Qt::NoButton,Qt::NoButton);
424 Magnifier->setZoomInKey(Qt::Key_M, Qt::NoModifier);
425 Magnifier->setZoomOutKey(Qt::Key_M, Qt::ShiftModifier);
426
427 Panner = new QwtPlotPanner(canvas());
428 Panner->setMouseButton(Qt::LeftButton, Qt::ShiftModifier);
429 connect(Panner, SIGNAL(panned(int, int)), this, SLOT(ReDoStats()));
430
431 Picker = new QwtPicker(QwtPicker::CornerToCorner|QwtPicker::RectSelection, QwtPicker::RectRubberBand, QwtPicker::AlwaysOff, this);
432 connect(Picker, SIGNAL(selected(const QwtPolygon &)), SLOT(MouseSelection(const QwtPolygon &)));
433
434 // Grid and legend
435 Grid = new QwtPlotGrid;
436 Grid->setMajPen(QPen(Qt::gray, 0, Qt::DotLine));
437 Grid->attach(this);
438
439 insertLegend(new QwtLegend(), QwtPlot::TopLegend, 0.3);
440
441 // Marker for statistics text
442 Stats = new QwtPlotMarker();
443 Stats->setLineStyle(QwtPlotMarker::NoLine);
444 Stats->setLabelAlignment(Qt::AlignLeft | Qt::AlignBottom);
445 Stats->hide();
446 Stats->attach(this);
447
448 // Context menu
449 Menu = new QMenu(this);
450 StripAction = Menu->addAction("Stripchart", this, SLOT(UpdatePlot()));
451 StripAction->setCheckable(true);
452 Menu->addSeparator();
453 YLogAction = Menu->addAction("y scale log", this, SLOT(UpdatePlot()));
454 YLogAction->setCheckable(true);
455 NormAction = Menu->addAction("Normalize", this, SLOT(UpdatePlot()));
456 NormAction->setCheckable(true);
457 StyleAction = Menu->addAction("Draw dots", this, SLOT(UpdatePlot()));
458 StyleAction->setCheckable(true);
459 StatisticsAction = Menu->addAction("Statistics", this, SLOT(ReDoStats()));
460 StatisticsAction->setCheckable(true);
461 Menu->addAction("Zoom out", this, SLOT(MenuZoomOut()));
462 Menu->addAction("Single trace", this, SLOT(MenuSingleTrace()));
463 Menu->addSeparator();
464 Menu->addAction("Save as ASCII", this, SLOT(MenuSaveASCII()));
465 Menu->addAction("Save plot", this, SLOT(MenuSave()));
466 Menu->addAction("Print plot", this, SLOT(MenuPrint()));
467 Menu->addAction("Plot help", this, SLOT(MenuPlotHelp()));
468}
469
470// Destructor (items with parent widget are automatically deleted)
471EddBasePlot::~EddBasePlot() {
472
473 for (int i=0; i<Items.size(); i++) delete Items[i].Signal;
474 delete Grid;
475}
476
477// Print statistics to plot if requested
478void EddBasePlot::ReDoStats() {
479
480 QString Text;
481 double Mean, Sigma;
482 unsigned long Count = 0;
483
484 // Set visibility of statistics box
485 Stats->setVisible(StatisticsAction->isChecked());
486
487 for (int i=0; i<Items.size(); i++) {
488 Mean = 0;
489 Sigma = 0;
490 Count = 0;
491
492 // Calculate mean and sigma for data points currently visible
493 for (int j=0; j<Items[i].y.size(); j++) {
494 if (!axisScaleDiv(QwtPlot::xBottom)->contains(Items[i].x[j])) continue;
495 Mean += Items[i].y[j];
496 Sigma += Items[i].y[j]*Items[i].y[j];
497 Count++;
498 }
499
500 if (Count == 0) Mean = 0;
501 else Mean /= Count;
502 if (Count < 2) Sigma = 0;
503 else Sigma = sqrt(Count/(Count-1)*(Sigma/Count-Mean*Mean));
504
505 // Prepare string
506 Text += Items[i].Signal->title().text() + ": m " + QString::number(Mean, 'f', 2) + ", s " + QString::number(Sigma, 'f', 2) + "\n";
507 }
508
509 // Replot once to get axis correct
510 replot();
511
512 // Print string to plot
513 QwtText text(Text);
514 text.setFont(QFont("Helvetica", 8));
515 Stats->setValue(axisScaleDiv(QwtPlot::xBottom)->upperBound(), axisScaleDiv(QwtPlot::yLeft)->upperBound());
516 Stats->setLabel(text);
517
518 // Replot again to update text
519 replot();
520}
521
522// Update all curves in plot
523void EddBasePlot::UpdatePlot() {
524
525 double Lower = axisScaleDiv(QwtPlot::xBottom)->lowerBound();
526 double Upper = axisScaleDiv(QwtPlot::xBottom)->upperBound();
527 double MaxTime = DBL_MIN;
528 static QwtSymbol Symbol, Sym1;
529 Symbol.setStyle(QwtSymbol::Ellipse);
530 Symbol.setSize(4);
531
532 // Select engine for linear or logarithmic scale
533 if (!YLogAction->isChecked()) {
534 setAxisScaleEngine(QwtPlot::yLeft, new QwtLinearScaleEngine);
535 }
536 else setAxisScaleEngine(QwtPlot::yLeft, new QwtLog10ScaleEngine);
537
538 for (int ItemNo=0; ItemNo<Items.size(); ItemNo++) {
539 // Determine current maximum value for strip chart plotting
540 if (Items[ItemNo].Signal->boundingRect().right() > MaxTime) {
541 MaxTime = Items[ItemNo].Signal->boundingRect().right();
542 }
543
544 // Set symbol if requested
545 if (StyleAction->isChecked()) Items[ItemNo].Signal->setSymbol(Symbol);
546 else Items[ItemNo].Signal->setSymbol(Sym1);
547
548 // Determine number of data points
549 int DataPoints = Items[ItemNo].x.size();
550 if (DataPoints == 0) continue;
551
552 // Normalize y scale if requested
553 double *y = new double [DataPoints];
554 for (int i=0; i<DataPoints; i++) {
555 y[i] = Items[ItemNo].y[i];
556
557 if (NormAction->isChecked()) {
558 if (Items[ItemNo].Smallest != Items[ItemNo].Largest) {
559 y[i] = (y[i] - Items[ItemNo].Smallest)/(Items[ItemNo].Largest-Items[ItemNo].Smallest);
560 }
561 else y[i] = 1;
562 }
563 }
564
565 // Plot data
566 Items[ItemNo].Signal->setData(Items[ItemNo].x.data(), y, DataPoints);
567 Items[ItemNo].Signal->show();
568 delete[] y;
569
570 // Generate bounding box of all curves for zoomer
571 BBox = BBox.unite(Items[ItemNo].Signal->boundingRect());
572 }
573
574 // Reset zoom base to include all data
575 Zoomer->setZoomBase(BBox);
576
577 // If plot is strip char, only move axis but keep range
578 if (StripAction->isChecked()) {
579 setCanvasBackground(EddPlotBackgroundColor.lighter(90));
580 setAxisScale(QwtPlot::xBottom, Lower+ BBox.right() - MaxTime, Upper + BBox.right() - MaxTime);
581 }
582 else setCanvasBackground(EddPlotBackgroundColor);
583
584 ReDoStats();
585}
586
587// Append curve to plot
588QwtPlotCurve *EddBasePlot::NewCurve(QwtText Title) {
589
590 struct PlotItem N;
591
592 N.Signal = new QwtPlotCurve;
593 N.Signal->attach(this);
594 N.Signal->setTitle(Title);
595 N.Signal->setPen(QColor(LineColors[Items.size() % (sizeof(LineColors)/sizeof(Qt::GlobalColor))]));
596 N.Largest = DBL_MIN;
597 N.Smallest = DBL_MAX;
598 Items.append(N);
599
600 return N.Signal;
601}
602
603// Clear curve data
604void EddBasePlot::ClearCurve(unsigned int Item) {
605
606 if (Item >= (unsigned int) Items.size()) return;
607
608 Items[Item].x.clear();
609 Items[Item].y.clear();
610}
611
612// Append data point
613void EddBasePlot::AddPoint(unsigned int Item, double x, double y) {
614
615 if (Item >= (unsigned int) Items.size()) return;
616
617 Items[Item].x.append(x);
618 Items[Item].y.append(y);
619
620 if (y > Items[Item].Largest) Items[Item].Largest = y;
621 if (y < Items[Item].Smallest) Items[Item].Smallest = y;
622}
623
624// Rescale plot in case selection has been made outside the canvas
625void EddBasePlot::MouseSelection(const QPolygon &P) {
626
627 QwtDoubleInterval xPlot, xMouse, yPlot, yMouse;
628
629 // Shift selected rectangle so that upper left corner is 0/0 on canvas
630 QRect R = P.boundingRect().translated(-plotLayout()->canvasRect().topLeft());
631
632 // Current axis intervals
633 xPlot = axisScaleDiv(QwtPlot::xBottom)->interval();
634 yPlot = axisScaleDiv(QwtPlot::yLeft)->interval();
635
636 // Selected axis intervals
637 xMouse = QwtDoubleInterval(invTransform(QwtPlot::xBottom, R.left()),
638 invTransform(QwtPlot::xBottom, R.right()));
639 yMouse = QwtDoubleInterval(invTransform(QwtPlot::yLeft, R.bottom()),
640 invTransform(QwtPlot::yLeft, R.top()));
641
642 // Selection region outside all axis?
643 if (R.right() < 0 && R.top() > plotLayout()->canvasRect().height()) return;
644
645 // Rescale both axis if selected rectangle encompasses canvas completely
646 if (P.boundingRect().contains(plotLayout()->canvasRect())) {
647 yMouse.setMaxValue(yMouse.maxValue() + yPlot.width());
648 yMouse.setMinValue(yMouse.minValue() - yPlot.width());
649 xMouse.setMinValue(xMouse.minValue() - xPlot.width());
650 xMouse.setMaxValue(xMouse.maxValue() + xPlot.width());
651
652 setAxisScale(QwtPlot::xBottom, xMouse.minValue(), xMouse.maxValue());
653 setAxisScale(QwtPlot::yLeft, yMouse.minValue(), yMouse.maxValue());
654 }
655
656 // Rescale y axis (increase range if selected rectangle larger than axis area)
657 if (R.right() < 0) {
658 if (yMouse.maxValue() > axisScaleDiv(QwtPlot::yLeft)->upperBound()) {
659 yMouse.setMaxValue(yMouse.maxValue() + yPlot.width());
660 }
661 if (yMouse.minValue() < axisScaleDiv(QwtPlot::yLeft)->lowerBound()) {
662 yMouse.setMinValue(yMouse.minValue() - yPlot.width());
663 }
664
665 setAxisScale(QwtPlot::yLeft, yMouse.minValue(), yMouse.maxValue());
666 }
667
668 // Rescale x axis (increase range if selected rectangle larger than axis area)
669 if (R.top() > plotLayout()->canvasRect().height()) {
670 if (xMouse.maxValue() > axisScaleDiv(QwtPlot::xBottom)->upperBound()) {
671 xMouse.setMaxValue(xMouse.maxValue() + xPlot.width());
672 }
673 if (xMouse.minValue() < axisScaleDiv(QwtPlot::xBottom)->lowerBound()) {
674 xMouse.setMinValue(xMouse.minValue() - xPlot.width());
675 }
676
677 setAxisScale(QwtPlot::xBottom, xMouse.minValue(), xMouse.maxValue());
678 }
679
680 // Draw new scales
681 replot();
682}
683
684// Reset graph axes to autoscale when fully unzoomed
685void EddBasePlot::HandleZoom(const QwtDoubleRect &) {
686
687 if(Zoomer->zoomRectIndex() == 0) {
688 setAxisAutoScale(QwtPlot::xBottom);
689 setAxisAutoScale(QwtPlot::yLeft);
690 }
691
692 UpdatePlot();
693}
694
695// Opening context menu
696void EddBasePlot::contextMenuEvent(QContextMenuEvent *Event) {
697
698 Menu->exec(Event->globalPos());
699}
700
701// Zoom completely out
702void EddBasePlot::MenuZoomOut() {
703
704 Zoomer->zoom(0);
705 UpdatePlot();
706}
707
708// Remove all items except one
709void EddBasePlot::MenuSingleTrace() {
710
711 while (Items.size() > 1) {
712 DeleteCurve(Items.last().Signal);
713 delete Items.last().Signal;
714 Items.takeLast();
715 }
716 UpdatePlot();
717}
718
719// Save data of plot as test
720void EddBasePlot::MenuSaveASCII() {
721 QString Filename = QFileDialog::getSaveFileName(this,
722 "Filename", ".", "Text files (*.txt *.ascii *.asc);;All files (*)");
723 if (Filename.length() <= 0) return;
724
725 QFile File(Filename);
726 if (!File.open(QFile::WriteOnly | QIODevice::Text | QFile::Truncate)) {
727 QMessageBox::warning(this, "Edd Message","Could not open file for writing.",QMessageBox::Ok);
728 return;
729 }
730
731 // Write x and y data for all signals to file
732 QTextStream Stream(&File);
733
734 for (int ItemNo=0; ItemNo<Items.size(); ItemNo++) {
735 Stream << QString("# ") + Items[ItemNo].Signal->title().text() + ".hist" << endl;
736 for (int i=0; i<Items[ItemNo].Signal->dataSize(); i++) {
737 Stream << (int) Items[ItemNo].x.at(i) << " " << Items[ItemNo].Signal->y(i) << endl;
738 }
739 }
740}
741
742// Print plot
743void EddBasePlot::MenuPrint() {
744
745 QPrinter *Printer = new QPrinter;
746 QPrintDialog *PrintDialog = new QPrintDialog(Printer, this);
747 if (PrintDialog->exec() == QDialog::Accepted) {
748 QPainter Painter(Printer);
749 QPixmap Pixmap = QPixmap::grabWidget(this);
750 Painter.drawPixmap(0, 0, Pixmap);
751 }
752 delete Printer; delete PrintDialog;
753}
754
755// Save plot as image
756void EddBasePlot::MenuSave() {
757
758 QString Filename = QFileDialog::getSaveFileName(this,
759 "Filename of image", "/home/ogrimm/ddd", "Image files (*.bmp *.jpg *.png *.ppm *.tiff *.xbm *.xpm);;All files (*)");
760 if (Filename.length()>0) {
761 QPixmap Pixmap = QPixmap::grabWidget(this);
762 if(!Pixmap.save(Filename)) {
763 QMessageBox::warning(this, "Edd Message","Could not write image file.",QMessageBox::Ok);
764 remove(Filename.toAscii().data());
765 }
766 }
767}
768
769// Help text
770void EddBasePlot::MenuPlotHelp() {
771
772 QMessageBox::about(this, "Edd - Plot navigation",
773 "Zoom\tMouse wheel\n"
774 "\tKeys m and shift-m\n"
775 "\tSelecting region with left mouse button\n"
776 "\tMiddle button zooms out one level\n"
777 "\tSelecting a range on an axis\n"
778 "\tSelecting whole canvas\n\n"
779 "Pan\tShift and left mouse button\n\n"
780 "ESC cancels selection\n"
781 "Cursor keys move mouse\n\n"
782 "Statistics are calculated over the current x axis extend");
783}
784
785//////////////////////////////////////
786// History text box for DIM service //
787//////////////////////////////////////
788
789// Constructor
790EddText::EddText(QString Name, bool Pure, QWidget *P):
791 QTextEdit(P), Name(Name), Pure(Pure) {
792
793 // Widget properties
794 setReadOnly(true);
795 setAttribute(Qt::WA_DeleteOnClose);
796 setAutoFillBackground(true);
797 document()->setMaximumBlockCount(1000);
798 Accumulate = true;
799
800 // Connect to DIM handler
801 if (connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) {
802 printf("Failed connection for %s\n", Name.toAscii().data());
803 }
804
805 if (!Pure) {
806 // Get history for this service
807 const struct EvidenceHistory::Item *R;
808 class EvidenceHistory *Hist;
809
810 if ((Hist = Handler->GetHistory(Name)) != NULL) {
811 while ((R=Hist->Next()) != NULL) {
812 moveCursor (QTextCursor::Start);
813 insertPlainText(QString("(")+QDateTime::fromTime_t(R->Time).toString()+") " +
814 QString::fromStdString(EvidenceServer::ToString(Hist->GetFormat(), (void *) R->Data, R->Size)) + "\n");
815 }
816 }
817 Handler->DropHistory(Name);
818 }
819
820 // DIM client
821 Handler->Subscribe(Name);
822}
823
824// Destructor
825EddText::~EddText() {
826
827 Handler->Unsubscribe(Name);
828}
829
830
831// Update widget (must happen in GUI thread)
832void EddText::Update(QString Name, int Time, QByteArray, QString Format, QString Text) {
833
834 if (this->Name != Name) return;
835 QPalette Pal = palette();
836
837 // Check if service available
838 if (!SetStatus(this, Name, Time, Format)) {
839 Pal.setColor(QPalette::Base, Qt::lightGray);
840 setPalette(Pal);
841 return;
842 }
843
844 Pal.setColor(QPalette::Base, Qt::white);
845 setPalette(Pal);
846 QDateTime Timex = QDateTime::fromTime_t(Time);
847
848 // Clear display in case text should not accumulate
849 if (Accumulate == false) clear();
850
851 if (!Pure) {
852 moveCursor(QTextCursor::Start);
853 insertPlainText(QString("(")+Timex.toString()+QString(") "));
854 insertPlainText(Text + "\n");
855 }
856 else if (Format == "C") insertPlainText(Text);
857}
858
859
860/////////////////////////////
861// Interface to Dim system //
862/////////////////////////////
863EddDim::EddDim() {
864
865 Mutex = new QMutex(QMutex::Recursive);
866 Volume = 0;
867
868 // Timer to calculate data rates
869 QTimer *Timer = new QTimer(this);
870 Timer->connect(Timer, SIGNAL(timeout()), this, SLOT(UpdateStatistics()));
871 Timer->start(10000);
872
873 // Connect to DIM handler
874 if (connect(this, SIGNAL(INT(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) {
875 printf("Failed connection in EddDim()\n");
876 }
877}
878
879// Destructor
880EddDim::~EddDim() {
881
882 QList<QString> L = HistoryList.keys();
883 for(int i=0; i<L.size(); i++) delete HistoryList[L[i]].HistClass;
884
885 delete Mutex;
886}
887
888// Subscribe to DIM service
889void EddDim::Subscribe(QString Name) {
890
891 // Lock before accessing list
892 QMutexLocker Locker(Mutex);
893
894 // If already subscribed to service, increase usage count and reemit data for new subscriber
895 if (ServiceList.contains(Name)) {
896 ServiceList[Name].Count++;
897 YEP(Name, ServiceList[Name].TimeStamp, ServiceList[Name].ByteArray, ServiceList[Name].Format, ServiceList[Name].Text);
898 return;
899 }
900
901 // Create new entry in service list
902 ServiceList[Name].ByteArray = QByteArray();
903 ServiceList[Name].TimeStamp = -1;
904 ServiceList[Name].Count = 1;
905 ServiceList[Name].DIMService = new DimStampedInfo(Name.toAscii().data(), INT_MAX, NO_LINK, this);
906 return;
907}
908
909// Unsubsribe from DIM service
910void EddDim::Unsubscribe(QString Name) {
911
912 // Lock before accessing list
913 QMutexLocker Locker(Mutex);
914
915 if (ServiceList.contains(Name)) ServiceList[Name].Count--;
916
917 if (ServiceList[Name].Count == 0) {
918 delete ServiceList[Name].DIMService;
919 ServiceList.remove(Name);
920 return;
921 }
922}
923
924// Get history buffer
925class EvidenceHistory *EddDim::GetHistory(QString Name) {
926
927 // History already available (only request again if too old)
928 if (HistoryList.contains(Name)) {
929 HistoryList[Name].Count++;
930
931 if (time(NULL)-HistoryList[Name].LastUpdate < 5) {
932 HistoryList[Name].HistClass->Rewind();
933 return HistoryList[Name].HistClass;
934 }
935 HistoryList[Name].LastUpdate = time(NULL);
936 if (HistoryList[Name].HistClass->GetHistory()) return HistoryList[Name].HistClass;
937 else return NULL;
938 }
939
940 // Create new history class
941 HistoryList[Name].HistClass = new EvidenceHistory(Name.toStdString());
942 HistoryList[Name].Count = 1;
943 HistoryList[Name].LastUpdate = time(NULL);
944
945 if (HistoryList[Name].HistClass->GetHistory()) return HistoryList[Name].HistClass;
946 else return NULL;
947}
948
949// Reduce history usage counter
950void EddDim::DropHistory(QString Name) {
951
952 if (HistoryList.contains(Name)) HistoryList[Name].Count--;
953}
954
955// Update throughput statistics and clear up history memory
956void EddDim::UpdateStatistics() {
957
958 // Lock before accessing internal variables
959 QMutexLocker Locker(Mutex);
960
961 // Remove unused histories after not less than 5 seconds
962 QList<QString> L = HistoryList.keys();
963 for(int i=0; i<L.size(); i++) {
964 if ((HistoryList[L[i]].Count <= 0) && (time(NULL)-HistoryList[L[i]].LastUpdate) > 5) {
965 delete HistoryList[L[i]].HistClass;
966 HistoryList.remove(L[i]);
967 }
968 }
969
970 float Rate = Volume/1024.0/10;
971 Volume = 0;
972 YEP("Edd/Rate_kBSec", time(NULL), QByteArray::number(Rate), "F", QString::number(Rate));
973}
974
975// Store service information for usage by Subscribe(), update statistics and emit signal to widgets
976void EddDim::Update(QString Name, int Time, QByteArray Data, QString Format, QString Text) {
977
978 // Lock before accessing list
979 QMutexLocker Locker(Mutex);
980
981 // Store service data
982 if (ServiceList.contains(Name)) {
983 ServiceList[Name].TimeStamp = Time;
984 ServiceList[Name].ByteArray = Data;
985 ServiceList[Name].Format = Format;
986 ServiceList[Name].Text = Text;
987 }
988
989 // Update statistics only for actual Dim services
990 if (!Name.startsWith("Edd/")) Volume += Data.size();
991
992 // Emit signal to all widgets
993 YEP(Name, Time, Data, Format, Text);
994}
995
996// Handling of DIM service update
997// No locking allowed. Signal triggers only EddDim::Update() when the main event loop is idle.
998void EddDim::infoHandler() {
999
1000 if (!EvidenceServer::ServiceOK(getInfo())) INT(getInfo()->getName(), -1);
1001 else {
1002 INT(getInfo()->getName(), getInfo()->getTimestamp(), QByteArray((char *) getInfo()->getData(),
1003 getInfo()->getSize()), getInfo()->getFormat(), QString::fromStdString(EvidenceServer::ToString(getInfo()->getFormat(), getInfo()->getData(), getInfo()->getSize())));
1004 }
1005}
1006
1007//
1008//
1009// ====== FACT specific part ======
1010//
1011//
1012
1013////////////////////////
1014// Event oscilloscope //
1015////////////////////////
1016
1017// Constructor
1018EventScope::EventScope(QWidget *P): EddBasePlot(P), PixelMap("../../config/PixelMap.txt", false) {
1019
1020 Name = DRSBoard+"/EventData";
1021 Active = false;
1022
1023 Tmpfile = tmpfile();
1024 if(Tmpfile == NULL) {
1025 QMessageBox::warning(this, "Edd Message", "Could not open temporary file.", QMessageBox::Ok);
1026 }
1027
1028 // Open file with RawDataCTX
1029 RD = new RawDataCTX(true);
1030 ErrCode = CTX_NOTOPEN;
1031
1032 // Context menu
1033 PhysPipeAction = new QAction("Plot physical pipeline", this);
1034 PhysPipeAction->setCheckable(true);
1035 connect(PhysPipeAction, SIGNAL(triggered()), SLOT(PlotTraces()));
1036 Menu->insertAction(StripAction, PhysPipeAction);
1037 Menu->removeAction(StripAction);
1038
1039 // Initial trace
1040 AddTrace(0,0,0);
1041
1042 // Connect to DIM handler
1043 if (connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) {
1044 printf("Failed connection for %s\n", Name.toAscii().data());
1045 }
1046 SetActive(true);
1047}
1048
1049// Destructor (items with parent widget are automatically deleted)
1050EventScope::~EventScope() {
1051
1052 SetActive(false);
1053 while (!List.isEmpty()) DeleteCurve(List.last().Signal);
1054 delete RD;
1055 if (Tmpfile != NULL) fclose(Tmpfile);
1056}
1057
1058// Add trace
1059void EventScope::AddTrace(int Board, int Chip, int Channel) {
1060
1061 struct ItemDetails N;
1062
1063 N.Signal = NewCurve(QString::number(Board)+","+QString::number(Chip)+","+ QString::number(Channel)+ " (" + DRS_to_Pixel(Board, Chip, Channel).c_str() + ")");
1064 N.Board = Board;
1065 N.Chip = Chip;
1066 N.Channel = Channel;
1067 N.Trigger = new QwtPlotMarker();
1068 N.Trigger->setSymbol(QwtSymbol(QwtSymbol::Diamond, QBrush(N.Signal->pen().color()), N.Signal->pen(), QSize(10,10)));
1069 N.Trigger->attach(this);
1070
1071 if (List.isEmpty()) {
1072 QPen Pen = N.Signal->pen();
1073 Pen.setWidth(2);
1074 N.Signal->setPen(Pen);
1075 }
1076 List.append(N);
1077
1078 PlotTraces();
1079}
1080
1081// Update last trace (to reflect current setting of spin boxes in DAQ page)
1082void EventScope::UpdateFirst(int Board, int Chip, int Channel) {
1083
1084 if (List.isEmpty()) return;
1085
1086 List.first().Signal->setTitle(QString::number(Board)+","+QString::number(Chip)+","+ QString::number(Channel) + " (" + DRS_to_Pixel(Board, Chip, Channel).c_str() + ")");
1087 List.first().Board = Board;
1088 List.first().Chip = Chip;
1089 List.first().Channel = Channel;
1090
1091 PlotTraces();
1092}
1093
1094// Update event buffer
1095void EventScope::Update(QString Name, int Time, QByteArray Data, QString Format, QString) {
1096
1097 if (Name != this->Name) return;
1098
1099 // Check if service available
1100 if (!SetStatus(this, Name, Time, Format)) return;
1101 if (Data.size() < (int) sizeof(RunHeader)) return;
1102
1103 // Open tempory file and write event data to this file
1104 QTemporaryFile File;
1105 if (!File.open()) {
1106 QMessageBox::warning(this, "Edd Message","Could not open temporary file.",QMessageBox::Ok);
1107 return;
1108 }
1109 if (File.write(Data) == -1) {
1110 QMessageBox::warning(this, "Edd Message","Could not write data to temporary file.",QMessageBox::Ok);
1111 return;
1112 }
1113
1114 // Prepare temporary file for run header
1115 ftruncate(fileno(Tmpfile), 0);
1116 rewind(Tmpfile);
1117
1118 // Open file with RawDataCTX
1119 switch (ErrCode = RD->OpenDataFile(File.fileName().toAscii().data(), Tmpfile)) {
1120 case CTX_FOPEN: QMessageBox::warning(this, "Edd Message","Could not open file.",QMessageBox::Ok);
1121 return;
1122 case CTX_RHEADER: QMessageBox::warning(this, "Edd Message","Could not read run header.",QMessageBox::Ok);
1123 return;
1124 case CTX_BSTRUCT: QMessageBox::warning(this, "Edd Message","Could not read board structures.",QMessageBox::Ok);
1125 return;
1126 default: break;
1127 }
1128
1129 // Emit signal containing run header
1130 rewind(Tmpfile);
1131 QTextStream Stream(Tmpfile);
1132 emit(RunHeaderChanged(Stream.readAll()));
1133
1134 // Prepare temporary file for event header
1135 ftruncate(fileno(Tmpfile), 0);
1136 rewind(Tmpfile);
1137
1138 // Write event header text to file
1139 if (RD->ReadEvent(0, Tmpfile) != CTX_OK) {
1140 QMessageBox::warning(this, "Edd Warning","Could not read event.",QMessageBox::Ok);
1141 return;
1142 }
1143
1144 // Add trigger cells to file
1145 fprintf(Tmpfile, "\nTrigger cells:");
1146 int *TrigCells = (int *) RD->Data;
1147 for (unsigned int i=0; i<RD->RHeader->NBoards; i++) {
1148 fprintf(Tmpfile, "\n Board %d ", i);
1149 for (unsigned int j=0; j<RD->RHeader->NChips; j++) fprintf(Tmpfile, "%d ", *(TrigCells++));
1150 }
1151
1152 // Emit signal containing run header
1153 rewind(Tmpfile);
1154 emit(EventHeaderChanged(Stream.readAll()));
1155
1156 PlotTraces();
1157}
1158
1159// Update curves
1160void EventScope::PlotTraces() {
1161
1162 double x,y;
1163 unsigned int Cell, Trig;
1164
1165 // Only process if valid data in RawDataCTX class
1166 if (!Active || ErrCode != CTX_OK) return;
1167
1168 // Set x axis title
1169 if (PhysPipeAction->isChecked()) setAxisTitle(QwtPlot::xBottom, "Time from start of pipeline (ns)");
1170 else setAxisTitle(QwtPlot::xBottom, "Time from trigger minus one revolution (ns)");
1171
1172 // Loop through event data to update event scope
1173 RunHeader *R = RD->RHeader;
1174 for (int i=0; i<List.size(); i++) {
1175
1176 ClearCurve(i);
1177
1178 // Check if current event contains requested trace
1179 if (List[i].Board>=R->NBoards || List[i].Chip>=R->NChips || List[i].Channel>=R->NChannels) continue;
1180
1181 // Set trigger marker visibility
1182 List[i].Trigger->setVisible(PhysPipeAction->isChecked());
1183
1184 // Determine trigger cell
1185 Trig = *((int *) RD->Data + List[i].Board*R->NChips + List[i].Chip);
1186
1187 for (unsigned int j=0; j<R->Samples; j++) {
1188
1189 if (PhysPipeAction->isChecked()) Cell = (j - Trig) % 1024;
1190 else Cell = j;
1191
1192 x = j / RD->BStruct[List[i].Board].NomFreq;
1193 y = *((short *) (RD->Data + R->NBoards*R->NChips*sizeof(int)) +
1194 List[i].Board*R->NChips*R->NChannels*R->Samples + List[i].Chip*R->NChannels*R->Samples +
1195 List[i].Channel*R->Samples + Cell) * RD->BStruct[List[i].Board].ScaleFactor;
1196
1197 AddPoint(i, x, y);
1198
1199 // Set trigger point indicator
1200 if (Trig == j) List[i].Trigger->setValue(x, y);
1201 }
1202 }
1203
1204 UpdatePlot();
1205
1206 // Loop through event data for pixel display
1207 QVector<double> Pixel(R->NBoards*R->NChips*R->NChannels);
1208 int Count = 0;
1209
1210 for (unsigned int Board=0; Board<R->NBoards; Board++) {
1211 for (unsigned int Chip=0; Chip<R->NChips; Chip++) {
1212 for (unsigned int Channel=0; Channel<R->NChannels; Channel++) {
1213 Pixel[Count] = DBL_MIN;
1214
1215 for (unsigned int i=0; i<R->Samples; i++) {
1216 y = *((short *) (RD->Data + R->NBoards*R->NChips*sizeof(int)) +
1217 Board*R->NChips*R->NChannels*R->Samples + Chip*R->NChannels*R->Samples +
1218 Channel*R->Samples + i) * RD->BStruct[Board].ScaleFactor;
1219
1220 if (y > Pixel[Count]) Pixel[Count] = y;
1221 }
1222 Count++;
1223 }}}
1224
1225 emit(PixelData(Pixel));
1226}
1227
1228// Remove list entry
1229void EventScope::DeleteCurve(QwtPlotCurve *Curve) {
1230
1231 for (int i=0; i<List.size(); i++) if (List[i].Signal == Curve) {
1232 delete List[i].Trigger;
1233 List.removeAt(i);
1234 }
1235}
1236
1237// Set display active (if inactive, disconnect from server)
1238void EventScope::SetActive(bool State) {
1239
1240 if (State && !Active) Handler->Subscribe(DRSBoard+"/EventData");
1241 if (!State && Active) Handler->Unsubscribe(DRSBoard+"/EventData");
1242 Active = State;
1243}
1244
1245
1246//------------------------------------------------------------------
1247//**************************** Tab pages ***************************
1248//------------------------------------------------------------------
1249
1250//
1251// Environment page
1252//
1253TP_Environment::TP_Environment() {
1254
1255 QGridLayout *Layout = new QGridLayout(this);
1256 setAttribute(Qt::WA_DeleteOnClose);
1257
1258 // Status display
1259 EddLineDisplay *Line = new EddLineDisplay("ARDUINO/Message");
1260 Line->setMaximumWidth(200);
1261 Layout->addWidget(Line, 0, 0, 1, 2);
1262
1263 // Generate plot and data displays
1264 EddPlot *Plot = new EddPlot();
1265 for (int i=0; i<10; i++) {
1266 Line = new EddLineDisplay("ARDUINO/Data", i);
1267 Layout->addWidget(Line, i%5+1, i/5, 1, 1);
1268 Plot->AddService("ARDUINO/Data", i);
1269 }
1270 Layout->addWidget(Plot, 0, 2, 9, 7);
1271
1272 // Night sky monitor
1273 Line = new EddLineDisplay("SQM/Message");
1274 Line->setMaximumWidth(200);
1275 Layout->addWidget(Line, 6, 0, 1, 2);
1276
1277 Line = new EddLineDisplay("SQM/NSB");
1278 Layout->addWidget(Line, 7, 0, 1, 1);
1279}
1280
1281//
1282// Bias page
1283//
1284TP_Bias::TP_Bias() {
1285
1286 QGridLayout *Layout = new QGridLayout(this);
1287 setAttribute(Qt::WA_DeleteOnClose);
1288 EddLineDisplay *Line;
1289
1290 EddPlot *Plot = new EddPlot();
1291 Plot->setMinimumWidth(400);
1292 for (int i=0; i<18; i++) {
1293 Line = new EddLineDisplay("Bias/VOLT/ID00", i+64);
1294 Layout->addWidget(Line, i%9+1, 0+i/9, 1, 1);
1295 Plot->AddService("Bias/VOLT/ID00", i+64);
1296
1297 Line = new EddLineDisplay("Bias/VOLT/ID00", i+96);
1298 Layout->addWidget(Line, i%9+1, 2+i/9, 1, 1);
1299 Plot->AddService("Bias/VOLT/ID00",i+96);
1300 }
1301
1302 Layout->addWidget(Plot, 0, 4, 12, 3);
1303 Line = new EddLineDisplay("Bias/Message");
1304 Line->setMaximumWidth(200);
1305 Layout->addWidget(Line, 0, 0, 1, 3);
1306
1307 EddCommand *Command = new EddCommand("Bias/Command");
1308 Layout->addWidget(Command, 10, 0, 1, 4);
1309
1310 EddText *Text = new EddText("Bias/ConsoleOut", true);
1311 Text->setFixedWidth(400);
1312 Layout->addWidget(Text, 11, 0, 4, 4);
1313
1314 QWidget *Button = new QPushButton("Currents");
1315 Layout->addWidget(Button, 13, 4, 1, 1);
1316 connect(Button, SIGNAL(pressed()), SLOT(BiasCurrents()));
1317}
1318
1319void TP_Bias::BiasCurrents() {
1320
1321 QMainWindow *M = new QMainWindow;
1322 M->setCentralWidget(new QWidget);
1323 M->setStatusBar(new QStatusBar(M));
1324 M->setWindowTitle("Edd - Bias currents");
1325 M->setAttribute(Qt::WA_DeleteOnClose);
1326
1327 QGridLayout *Layout = new QGridLayout(M->centralWidget());
1328 EddLineDisplay *Line;
1329 EddPlot *Plot = new EddPlot();
1330
1331 for (int i=0; i<36; i++) {
1332 Line = new EddLineDisplay("Bias/MICROAMP/ID00", i+64);
1333 Line->setMaximumWidth(60);
1334 Layout->addWidget(Line, i%9, 0+i/9, 1, 1);
1335 Plot->AddService("Bias/MICROAMP/ID00", i+64);
1336 }
1337 Layout->addWidget(Plot, 0, 4, 30, 12);
1338
1339 M->show();
1340}
1341
1342//
1343// Feedback page
1344//
1345TP_Feedback::TP_Feedback() {
1346
1347 setAttribute(Qt::WA_DeleteOnClose);
1348 QGridLayout *Layout = new QGridLayout(this);
1349 EddLineDisplay *Line;
1350
1351 EddPlot *Plot = new EddPlot();
1352 for (int i=0; i<36; i++) {
1353 Line = new EddLineDisplay("Feedback/Average", i);
1354 Line->setMaximumWidth(60);
1355 Layout->addWidget(Line, i%9+2, 0+i/9, 1, 1);
1356 Plot->AddService("Feedback/Average", i);
1357 }
1358 Layout->addWidget(Plot, 0, 4, 12, 10);
1359
1360 Line = new EddLineDisplay("Feedback/Message");
1361 Line->setMaximumWidth(200);
1362 Layout->addWidget(Line, 0, 0, 1, 2);
1363
1364 Line = new EddLineDisplay("Feedback/State");
1365 Line->setMaximumWidth(150);
1366 Layout->addWidget(Line, 1, 0, 1, 2);
1367 Line = new EddLineDisplay("Feedback/Count");
1368 Line->setMaximumWidth(60);
1369 Layout->addWidget(Line, 1, 2);
1370
1371 QWidget *Button = new QPushButton("Details");
1372 Layout->addWidget(Button, 12, 0, 1, 1);
1373 connect(Button, SIGNAL(pressed()), SLOT(FeedbackDetails()));
1374}
1375
1376void TP_Feedback::FeedbackDetails() {
1377
1378 QMainWindow *M = new QMainWindow;
1379 M->setCentralWidget(new QWidget);
1380 M->setStatusBar(new QStatusBar(M));
1381 M->setWindowTitle("Edd - Feedback Details");
1382 M->setAttribute(Qt::WA_DeleteOnClose);
1383
1384 QGridLayout *Layout = new QGridLayout(M->centralWidget());
1385 EddLineDisplay *Line;
1386 EddPlot *Plot = new EddPlot();
1387
1388 for (int i=0; i<36; i++) {
1389 Line = new EddLineDisplay("Feedback/Sigma", i);
1390 Line->setMaximumWidth(60);
1391 Layout->addWidget(Line, i%9, 0+i/9, 1, 1);
1392 Plot->AddService("Feedback/Sigma", i);
1393
1394 Line = new EddLineDisplay("Feedback/Target", i);
1395 Line->setMaximumWidth(60);
1396 Layout->addWidget(Line, i%9+10, 0+i/9, 1, 1);
1397
1398 Line = new EddLineDisplay("Feedback/Response", i);
1399 Line->setMaximumWidth(60);
1400 Layout->addWidget(Line, i%9+20, 0+i/9, 1, 1);
1401 }
1402 Layout->addWidget(Plot, 0, 4, 30, 12);
1403
1404 M->show();
1405}
1406
1407//
1408// Event scope page
1409//
1410TP_DAQ::TP_DAQ() {
1411
1412 EddLineDisplay *Line;
1413
1414 setAttribute(Qt::WA_DeleteOnClose);
1415 QGridLayout *Layout = new QGridLayout(this);
1416
1417 // Run-related information
1418 Line = new EddLineDisplay("drsdaq/RunNumber");
1419 Line->setMaximumWidth(100);
1420 Layout->addWidget(Line, 0, 1, 1, 1);
1421 Line = new EddLineDisplay("drsdaq/EventNumber");
1422 Line->setMaximumWidth(100);
1423 Layout->addWidget(Line, 0, 2, 1, 1);
1424 Line = new EddLineDisplay("drsdaq/RunSizeMB");
1425 Line->setMaximumWidth(100);
1426 Layout->addWidget(Line, 0, 3, 1, 1);
1427 Line = new EddLineDisplay("drsdaq/FileSizeMB");
1428 Line->setMaximumWidth(100);
1429 Layout->addWidget(Line, 0, 4, 1, 1);
1430 Line = new EddLineDisplay("drsdaq/FileName");
1431 Line->setMaximumWidth(200);
1432 Layout->addWidget(Line, 0, 5, 1, 1);
1433
1434 // Event scope
1435 Scope = new EventScope;
1436 Scope->setMinimumWidth(700);
1437
1438 // Text boxes for run and event header
1439 RunHeaderDisplay = new QPlainTextEdit();
1440 EventHeaderDisplay = new QPlainTextEdit();
1441 RunHeaderDisplay->setReadOnly(true);
1442 EventHeaderDisplay->setReadOnly(true);
1443
1444 // Tab widget
1445 QTabWidget *TabWidget = new QTabWidget();
1446 TabWidget->addTab(Scope, "&Signals");
1447 TabWidget->addTab(RunHeaderDisplay, "&Run Header");
1448 TabWidget->addTab(EventHeaderDisplay, "&Event Header");
1449 Layout->addWidget(TabWidget, 1, 1, 5, 5);
1450
1451 // Channel number
1452 Channel = new QSpinBox;
1453 connect(Channel, SIGNAL(valueChanged(int)), SLOT(UpdateScope(int)));
1454 Channel->setToolTip("DRS channel number");
1455
1456 // Chip number
1457 Chip = new QSpinBox;
1458 connect(Chip, SIGNAL(valueChanged(int)), SLOT(UpdateScope(int)));
1459 Chip->setToolTip("DRS chip number");
1460
1461 // Board number
1462 Board = new QSpinBox;
1463 connect(Board, SIGNAL(valueChanged(int)), SLOT(UpdateScope(int)));
1464 Board->setToolTip("DRS board number");
1465
1466 // Pixel ID
1467 PixelID = new QLineEdit;
1468 PixelID->setMaximumWidth(60);
1469 connect(PixelID, SIGNAL(returnPressed()), SLOT(TranslatePixelID()));
1470 PixelID->setToolTip("Pixel identification");
1471
1472 // Layout of pixel addressing widgets
1473 QFormLayout *FormLayout = new QFormLayout();
1474 FormLayout->setRowWrapPolicy(QFormLayout::WrapAllRows);
1475 FormLayout->addRow("Channel", Channel);
1476 FormLayout->addRow("Chip", Chip);
1477 FormLayout->addRow("Board", Board);
1478 FormLayout->addRow("Pixel ID", PixelID);
1479 Layout->addLayout(FormLayout, 0, 0, 2, 1);
1480
1481 // Add trace permanently
1482 QPushButton *Button = new QPushButton("Keep trace");
1483 Button->setToolTip("Keep trace in display");
1484 Button->setMaximumWidth(80);
1485 Layout->addWidget(Button, 2, 0);
1486 connect(Button, SIGNAL(clicked()), SLOT(KeepCurrent()));
1487
1488 // Stop/start
1489 StartStopButton = new QPushButton("Stop");
1490 StartStopButton->setToolTip("Start/stop display");
1491 StartStopButton->setMaximumWidth(80);
1492 StartStopButton->setCheckable(true);
1493 QPalette Palette = StartStopButton->palette();
1494 Palette.setColor(QPalette::ButtonText, Qt::blue);
1495 Palette.setColor(QPalette::Button, Qt::green);
1496 StartStopButton->setPalette(Palette);
1497 StartStopButton->setFont(QFont("Times", 10, QFont::Bold));
1498 Layout->addWidget(StartStopButton, 4, 0);
1499 connect(StartStopButton, SIGNAL(toggled(bool)), SLOT(StartStop(bool)));
1500
1501 // Button to show event display
1502 QPushButton *PixDisplay = new QPushButton("Pixel display");
1503 PixDisplay->setFont(QFont("Times", 10, QFont::Bold));
1504 PixDisplay->setToolTip("Show event display window");
1505 PixDisplay->setMaximumWidth(80);
1506 Layout->addWidget(PixDisplay, 5, 0);
1507 connect(PixDisplay, SIGNAL(clicked()), SLOT(ShowPixelDisplay()));
1508
1509 // Event display window
1510 Display = new QWidget();
1511 Display->setWindowTitle("Edd - Event display");
1512
1513 Pixel = new QPushButton *[MAXPIXEL];
1514 int Count = 0;
1515 double x,y;
1516
1517 for (int ix=-22; ix<=22; ix++) for (int iy=-19; iy<=20; iy++) {
1518 if (Count == MAXPIXEL) break;
1519
1520 x = ix*0.866;
1521 y = iy - (ix%2==0 ? 0.5:0);
1522 if ((pow(x,2)+pow(y,2) >= 395) && !(abs(ix)==22 && iy==7)) continue;
1523
1524 Pixel[Count] = new QPushButton(Display);
1525 Pixel[Count]->setAutoFillBackground(true);
1526 Pixel[Count]->setGeometry((int) (x*12.5 + 250), (int) (y*12.5 + 250), 10, 10);
1527 Pixel[Count]->show();
1528 Count++;
1529 }
1530
1531 StartStop(false);
1532 connect(Scope, SIGNAL(PixelData(QVector<double>)), SLOT(SetPixelData(QVector<double>)));
1533}
1534
1535TP_DAQ::~TP_DAQ() {
1536
1537 delete[] Pixel;
1538}
1539
1540// Translate pixel ID to board, chip, channel
1541void TP_DAQ::TranslatePixelID() {
1542
1543 if (Scope->Pixel_to_DRSboard(PixelID->text().toStdString()) == 999999999) {
1544 QMessageBox::warning(this, "Edd Message","Pixel ID unknown.",QMessageBox::Ok);
1545 }
1546 else {
1547 Board->setValue(Scope->Pixel_to_DRSboard(PixelID->text().toStdString()));
1548 Chip->setValue(Scope->Pixel_to_DRSchip(PixelID->text().toStdString()));
1549 Channel->setValue(Scope->Pixel_to_DRSchannel(PixelID->text().toStdString()));
1550 }
1551}
1552
1553// Keep current trace
1554void TP_DAQ::KeepCurrent() {
1555
1556 Scope->AddTrace(Board->value(), Chip->value(), Channel->value());
1557}
1558
1559// Start/stop event acquisition
1560void TP_DAQ::StartStop(bool State) {
1561
1562 Scope->SetActive(!State);
1563 StartStopButton->setText(State ? "Start" : "Stop");
1564 if (!State) {
1565 connect(Scope, SIGNAL(RunHeaderChanged(QString)), RunHeaderDisplay, SLOT(setPlainText(QString)));
1566 connect(Scope, SIGNAL(EventHeaderChanged(QString)), EventHeaderDisplay, SLOT(setPlainText(QString)));
1567 }
1568 else {
1569 disconnect(Scope, SIGNAL(RunHeaderChanged(QString)), RunHeaderDisplay, SLOT(setPlainText(QString)));
1570 disconnect(Scope, SIGNAL(EventHeaderChanged(QString)), EventHeaderDisplay, SLOT(setPlainText(QString)));
1571 }
1572}
1573
1574// Update event scope
1575void TP_DAQ::UpdateScope(int) {
1576
1577 // Update pixel ID
1578 PixelID->setText(Scope->DRS_to_Pixel(Board->value(), Chip->value(), Channel->value()).c_str());
1579 // Update first trace
1580 Scope->UpdateFirst(Board->value(), Chip->value(), Channel->value());
1581}
1582
1583// Show/hide pixel display
1584void TP_DAQ::ShowPixelDisplay() {
1585
1586 Display->show();
1587 Display->raise();
1588}
1589
1590void TP_DAQ::SetPixelData(QVector<double> Data) {
1591
1592 QwtLinearColorMap Map;
1593
1594 for (int i=0; i<Data.size(); i++) {
1595 Pixel[i]->setPalette(QPalette(Map.color(QwtDoubleInterval(300, 400), Data[i])));
1596 }
1597}
1598
1599
1600//
1601// Evidence page
1602//
1603TP_Evidence::TP_Evidence() {
1604
1605 setAttribute(Qt::WA_DeleteOnClose);
1606 QGridLayout *Layout = new QGridLayout(this);
1607 EddLineDisplay *Line;
1608 EddText *Text;
1609
1610 Line = new EddLineDisplay("Alarm/Message");
1611 Line->setMaximumWidth(200);
1612 Layout->addWidget(Line, 0, 0, 1, 2);
1613
1614 QPushButton *Button = new QPushButton();
1615 Button->setText("ON/OFF");
1616 Button->setCheckable(true);
1617 connect(Button, SIGNAL(toggled(bool)), SLOT(ToggleAlarm(bool)));
1618 Layout->addWidget(Button, 0, 3, 1, 1);
1619
1620 Line = new EddLineDisplay("Alarm/MasterAlarm");
1621 Layout->addWidget(Line, 0, 1, 1, 1);
1622
1623 Text = new EddText("Alarm/Summary", true);
1624 Text->Accumulate = false;
1625 Text->setMaximumWidth(200);
1626 Text->setMaximumHeight(150);
1627 Layout->addWidget(Text, 1, 0, 1, 2);
1628
1629 Line = new EddLineDisplay("DColl/Message");
1630 Line->setMaximumWidth(200);
1631 Layout->addWidget(Line, 3, 0, 1, 2);
1632
1633 Line = new EddLineDisplay("DColl/DataSizeMB");
1634 Layout->addWidget(Line, 4, 0, 1, 1);
1635
1636 Line = new EddLineDisplay("DColl/LogSizeMB");
1637 Layout->addWidget(Line, 4, 1, 1, 1);
1638
1639 Line = new EddLineDisplay("DColl/CurrentFile");
1640 Line->setMaximumWidth(400);
1641 Layout->addWidget(Line, 5, 0, 1, 3);
1642
1643 Line = new EddLineDisplay("Config/Message");
1644 Line->setMaximumWidth(200);
1645 Layout->addWidget(Line, 6, 0, 1, 2);
1646
1647 Line = new EddLineDisplay("Config/ModifyTime");
1648 Line->setMaximumWidth(200);
1649 Line->ShowAsTime = true;
1650 Layout->addWidget(Line, 7, 0, 1, 1);
1651
1652 Button = new QPushButton();
1653 Button->setText("eLogBook");
1654 connect(Button, SIGNAL(released()), SLOT(StartELog()));
1655 Layout->addWidget(Button, 6, 1, 1, 1);
1656
1657 Button = new QPushButton();
1658 Button->setText("Start DIM browser");
1659 connect(Button, SIGNAL(released()), SLOT(StartDIMBrowser()));
1660 Layout->addWidget(Button, 7, 1, 1, 1);
1661
1662 Line = new EddLineDisplay("Edd/Rate_kBSec");
1663 Layout->addWidget(Line, 8, 0, 1, 1);
1664}
1665
1666// Toggle state of Alarm server
1667void TP_Evidence::ToggleAlarm(bool State) {
1668
1669 if (State) DimClient::sendCommandNB((char *) "Alarm/Switch", (char *) "off");
1670 else DimClient::sendCommandNB((char *) "Alarm/Switch", (char *) "on");
1671}
1672
1673// Start DIM Browser
1674void TP_Evidence::StartDIMBrowser() {
1675
1676 QProcess::startDetached("did", QStringList(), QString(getenv("DIMDIR"))+"/linux/");
1677}
1678
1679// Start eLogBook
1680void TP_Evidence::StartELog() {
1681
1682 QProcess::startDetached("firefox http://fact.ethz.ch/FACTelog/index.jsp");
1683}
1684
1685
1686//--------------------------------------------------------------------
1687//*************************** Main GUI *******************************
1688//--------------------------------------------------------------------
1689//
1690// All widgets have ultimately Central as parent.
1691//
1692GUI::GUI() {
1693
1694 Handler = new EddDim();
1695
1696 // Set features of main window
1697 Central = new QWidget(this);
1698 setCentralWidget(Central);
1699 setStatusBar(new QStatusBar(this));
1700 setWindowTitle("Edd - Evidence Data Display");
1701
1702 // Arrangement in tabs
1703 TabWidget = new QTabWidget(Central);
1704 TabWidget->setTabsClosable(true);
1705 connect(TabWidget, SIGNAL(tabCloseRequested(int)), SLOT(DetachTab(int)));
1706 TabWidget->addTab(new TP_DAQ, "Event scope " + DRSBoard);
1707 TabWidget->addTab(new TP_Bias, "Bias");
1708 TabWidget->addTab(new TP_Feedback, "Feedback");
1709 TabWidget->addTab(new TP_Environment, "Environment");
1710 TabWidget->addTab(new TP_Evidence, "Evidence");
1711
1712 // Menu bar
1713 QMenu* Menu = menuBar()->addMenu("&Menu");
1714 Menu->addAction("New history plot", this, SLOT(MenuNewHistory()));
1715 Menu->addSeparator();
1716 Menu->addAction("About", this, SLOT(MenuAbout()));
1717 Menu->addSeparator();
1718 QAction* QuitAction = Menu->addAction("Quit", qApp, SLOT(quit()));
1719 QuitAction->setShortcut(Qt::CTRL + Qt::Key_Q);
1720
1721 // Show main window
1722 resize(TabWidget->sizeHint()*1.1);
1723 show();
1724
1725 // Set timer to regularly check the master alarm
1726 QTimer *Timer = new QTimer(this);
1727 connect(Timer, SIGNAL(timeout()), this, SLOT(CheckAlarm()));
1728 Timer->start(5000);
1729}
1730
1731GUI::~GUI() {
1732 delete Central;
1733}
1734
1735
1736void GUI::MenuAbout() {
1737 QString Rev(SVN_REVISION);
1738 Rev.remove(0,1).chop(2);
1739
1740 QMessageBox::about(this, "About Edd","Evidence Data Display\n\n"
1741 "Written by Oliver Grimm, IPP, ETH Zurich\n"
1742 "This version compiled "__DATE__" ("+Rev+")\n\n"
1743 "Graphical user interface implemented with Qt and Qwt.\n"
1744 "Evidence control system based on DIM (http://dim.web.cern.ch).\n\n"
1745 "Comments to oliver.grimm@phys.ethz.ch.");
1746}
1747
1748// Open request for new history plot
1749void GUI::MenuNewHistory() {
1750
1751 QStringList List;
1752 char *Name, *Format;
1753 int Type;
1754 bool OK;
1755
1756 // Find all DIM services and sort
1757 getServices("*");
1758 while ((Type = getNextService(Name, Format)) != 0) {
1759 if (Type==DimSERVICE) List.append(Name);
1760 }
1761 List.sort();
1762
1763 // Open dialog and open history window
1764 QString Result = QInputDialog::getItem(this, "Edd Request",
1765 "Enter DIM service name", List, 0, true, &OK);
1766 if (OK && !Result.isEmpty()) {
1767 Result = Result.trimmed();
1768 QWidget *Hist = OpenHistory(Result.toAscii().data(), 0);
1769 if (Hist != NULL) Hist->show();
1770 }
1771}
1772
1773// Open tab as separate window
1774void GUI::DetachTab(int Tab) {
1775
1776 QWidget *W = NULL;
1777 QMainWindow *M = new QMainWindow;
1778
1779 M->setCentralWidget(new QWidget(M));
1780 M->setStatusBar(new QStatusBar(M));
1781
1782 switch(Tab) {
1783 case 0: W = new TP_DAQ; break;
1784 case 1: W = new TP_Bias; break;
1785 case 2: W = new TP_Feedback; break;
1786 case 3: W = new TP_Environment; break;
1787 case 4: W = new TP_Evidence; break;
1788 default: break;
1789 }
1790
1791 if (W == NULL) {
1792 delete M->centralWidget();
1793 delete M;
1794 return;
1795 }
1796
1797 W->setParent(M);
1798 M->resize(size());
1799 M->setWindowTitle("Edd - " + TabWidget->tabText(Tab));
1800 M->show();
1801}
1802
1803// Check alarm level and if Alarm server is alive
1804void GUI::CheckAlarm() {
1805
1806 static int WarnedLevel = 0;
1807 static bool AlarmServerWarned = false;
1808
1809 // === Check service Alarm/MasterAlarm ===
1810 DimCurrentInfo MasterAlarm("Alarm/MasterAlarm", -1);
1811
1812 if (MasterAlarm.getInt() > WarnedLevel) {
1813 QSound::play(QApplication::applicationDirPath() + "/Error.wav");
1814
1815 // Construct warning message box
1816 QMessageBox Box;
1817 Box.setWindowTitle("Edd Alarm");
1818 Box.setText("Service 'Alarm/MasterAlarm' is at " + QString::number(MasterAlarm.getInt()));
1819 Box.setInformativeText("Warn again for the same alarm level?");
1820 Box.setIcon(QMessageBox::Warning);
1821 Box.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
1822 Box.setDefaultButton(QMessageBox::No);
1823
1824 // Check if user wants to reset warned level
1825 if (Box.exec() == QMessageBox::Yes) WarnedLevel = 0;
1826 else WarnedLevel = MasterAlarm.getInt();
1827 }
1828
1829 // If MasterAlam decreased, lower also warned level
1830 if (MasterAlarm.getInt() < WarnedLevel && MasterAlarm.getInt() >=0 ) WarnedLevel = MasterAlarm.getInt();
1831
1832 // === Check Alarm server ===
1833 DimCurrentInfo ServerList("DIS_DNS/SERVER_LIST", NO_LINK);
1834 std::string Result = EvidenceServer::ToString((char *) "C", ServerList.getData(), ServerList.getSize());
1835
1836 // Warn if SERVER_LIST does not contain alarm server
1837 if (Result.find("Alarm@") == std::string::npos && !AlarmServerWarned) {
1838 QMessageBox Box;
1839 Box.setWindowTitle("Edd Alarm");
1840 Box.setText("Alarm server is unavailable or in error");
1841 Box.setIcon(QMessageBox::Critical);
1842 Box.setStandardButtons(QMessageBox::Ok);
1843 Box.setDefaultButton(QMessageBox::Ok);
1844 Box.exec();
1845
1846 AlarmServerWarned = true;
1847 }
1848
1849 if (Result.find("Alarm@") != std::string::npos) AlarmServerWarned = false;
1850}
1851
1852// Quit application when clicking close button on window
1853void GUI::closeEvent(QCloseEvent *) {
1854
1855 qApp->quit();
1856}
1857
1858
1859//**************************** Main program ***************************
1860int main(int argc, char *argv[]) {
1861
1862 if (argc > 1) DRSBoard = "FADctrl";
1863
1864 QApplication app(argc, argv);
1865 GUI MainWindow;
1866
1867 return app.exec();
1868}
Note: See TracBrowser for help on using the repository browser.