source: fact/Evidence/GUI.cc@ 10795

Last change on this file since 10795 was 10280, checked in by ogrimm, 14 years ago
Separated Edd into generic Evidence part and FACT-specific part
File size: 30.4 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 "GUI.h"
16
17class EddDim *Handler;
18
19//
20// History chooser function (opens plot for numeric data, TextHist for all other)
21//
22QWidget *OpenHistory(char *Service, int Index) {
23
24 QString Format;
25 DimBrowser Browser;
26 class EvidenceHistory *Hist = Handler->GetHistory(Service);
27
28 // Check if history service available
29 if (Hist == NULL || Hist->GetFormat() == NULL) {
30 QMessageBox::warning(NULL, "Edd Message", QString("Could not retrieve history for service ") + Service ,QMessageBox::Ok);
31
32 // If service currently available, take its format
33 char *Name, *Fmt;
34
35 Browser.getServices(Service);
36 if (Browser.getNextService(Name, Fmt) != 0) Format = QString(Fmt);
37 else {
38 Handler->DropHistory(Service);
39 return NULL;
40 }
41 }
42
43 if (Format.isEmpty()) Format = Hist->GetFormat();
44 Handler->DropHistory(Service);
45
46 if (Format.size() == 1 && Format[0] != 'C') return new EddPlot(Service, Index);
47 else return new EddText(Service);
48}
49
50//
51// Set status tip (returns true if service was available)
52//
53bool SetStatus(QWidget *W, QString Name, int Time, QString Format, int Index) {
54
55 QString Status;
56
57 if (Index != -1) Name = Name + "(" + QString::number(Index) + ")";
58
59 if (Time == -1) Status = QString("%1: unavailable").arg(Name);
60 else Status = QString("%1: Last update %2 Format '%3'").arg(Name).arg(QDateTime::fromTime_t(Time).toString()).arg(Format);
61
62 W->setStatusTip(Status);
63
64 return(Time != -1);
65}
66
67
68//////////////////////////////////////////
69// Text display for arbitary DIM service//
70//////////////////////////////////////////
71
72EddLineDisplay::EddLineDisplay(QString Name, int Index, QWidget *P):
73 QLineEdit(P), ServiceName(Name), Index(Index) {
74
75 LastHist = NULL;
76
77 // Widget properties
78 setReadOnly(true);
79 setMaximumWidth(100);
80 ShowAsTime = false;
81 setFrame(false);
82 setAttribute(Qt::WA_DeleteOnClose);
83
84 // Connect to DIM handler
85 if (connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) {
86 printf("Failed connection for %s\n", Name.toAscii().data());
87 }
88
89 // Context menu
90 Menu = new QMenu(this);
91 Menu->addAction("Open new history", this, SLOT(MenuOpenHistory()));
92 Menu->addAction("Copy service", this, SLOT(MenuCopyService()));
93 Menu->addAction("Copy data", this, SLOT(MenuCopyData()));
94
95 // Subscribe to service
96 Handler->Subscribe(Name);
97}
98
99// Destructor
100EddLineDisplay::~EddLineDisplay() {
101
102 Handler->Unsubscribe(ServiceName);
103}
104
105// Update widget
106void EddLineDisplay::Update(QString Name, int Time, QByteArray, QString Format, QString Text) {
107
108 if (ServiceName != Name) return;
109
110 // Check if service available
111 QPalette Pal = palette();
112 if (!SetStatus(this, Name, Time, Format, Index)) {
113 setText("n/a");
114 Pal.setColor(QPalette::Base, Qt::lightGray);
115 setPalette(Pal);
116 return;
117 }
118 else Pal.setColor(QPalette::Base, Qt::white);
119
120 // Message service backgound colour determined by severity
121 if (Name.endsWith("/Message")) {
122 switch (Text.section(' ', 0, 0).toInt()) {
123 case 0: Pal.setColor(QPalette::Base, Qt::white); break;
124 case 1: Pal.setColor(QPalette::Base, Qt::yellow); break;
125 case 2: Pal.setColor(QPalette::Base, Qt::red); break;
126 case 3: Pal.setColor(QPalette::Base, Qt::red); break;
127 default: break;
128 }
129 Text = Text.section(' ', 1);
130 }
131 else if (Format[0].toUpper() != 'C' && Format != "I:1;C") Text = Text.section(' ', Index, Index);
132
133 if (!ShowAsTime) setText(Text);
134 else setText(QDateTime::fromTime_t(Text.toInt()).toString());
135
136 setCursorPosition(0);
137 setPalette(Pal);
138}
139
140// Open plot if mouse release within widget
141void EddLineDisplay::mouseReleaseEvent(QMouseEvent *Event) {
142
143 if (Event->button()!=Qt::LeftButton || !contentsRect().contains(Event->pos())) return;
144
145 // Check if last history plot still open, then raise
146 foreach (QWidget *Widget, QApplication::allWidgets()) {
147 if (Widget == LastHist) {
148 Widget->activateWindow();
149 Widget->raise();
150 return;
151 }
152 }
153
154 // If not, open new plot
155 EddLineDisplay::MenuOpenHistory();
156}
157
158// Handling of mouse press event: Register start position for drag
159void EddLineDisplay::mousePressEvent(QMouseEvent *Event) {
160
161 if (Event->button() == Qt::LeftButton) dragStart = Event->pos();
162}
163
164// Handling of dragging (Drag and MimeData will be deleted by Qt)
165void EddLineDisplay::mouseMoveEvent(QMouseEvent *Event) {
166
167 if ((Event->buttons() & Qt::LeftButton) == 0) return;
168 if ((Event->pos()-dragStart).manhattanLength() < QApplication::startDragDistance()) return;
169
170 QDrag *Drag = new QDrag(this);
171 QMimeData *MimeData = new QMimeData;
172 QByteArray Data;
173 MimeData->setData("Edd/Service", Data.append(ServiceName + " " + QString::number(Index)));
174 Drag->setMimeData(MimeData);
175 Drag->exec();
176}
177
178//
179// Opening context menu
180//
181void EddLineDisplay::contextMenuEvent(QContextMenuEvent *Event) {
182
183 Menu->exec(Event->globalPos());
184}
185
186// Menu: Open history plot
187void EddLineDisplay::MenuOpenHistory() {
188
189 LastHist = OpenHistory(ServiceName.toAscii().data(), Index);
190 if (LastHist != NULL) LastHist->show();
191}
192
193// Menu: Copy service name
194void EddLineDisplay::MenuCopyService() {
195
196 QMimeData *MimeData = new QMimeData;
197 QByteArray Data;
198 MimeData->setData("Edd/Service", Data.append(ServiceName + " " + QString::number(Index)));
199 QApplication::clipboard()->setMimeData(MimeData);
200}
201
202// Menu: Copy data
203void EddLineDisplay::MenuCopyData() {
204
205 QApplication::clipboard()->setText(text());
206}
207
208
209//////////////////////////////////////////
210// Sending string command to DIM server //
211//////////////////////////////////////////
212
213EddCommand::EddCommand(QString Name, QWidget *P): QLineEdit(P), Name(Name) {
214
215 setToolTip("Send command "+Name);
216 connect(this, SIGNAL(returnPressed()), SLOT(SendCommand()));
217}
218
219// Send command
220void EddCommand::SendCommand() {
221
222 DimClient::sendCommand(Name.toAscii().data(), text().toAscii().data());
223 clear();
224}
225
226
227//////////////////////////////////
228// History plot for DIM service //
229//////////////////////////////////
230
231EddPlot::EddPlot(QString Service, int Index, QWidget *P): EddBasePlot(P) {
232
233 // Widget properties
234 setAcceptDrops(true);
235 setAxisScaleDraw(QwtPlot::xBottom, new EddTimeScale);
236
237 // Update time range on plot when axis change (update() results in paintEvent())
238 connect(axisWidget(QwtPlot::xBottom), SIGNAL(scaleDivChanged()), SLOT(update()));
239
240 legend()->setItemMode(QwtLegend::ClickableItem);
241 connect(this, SIGNAL(legendClicked (QwtPlotItem *)), SLOT(LegendClicked(QwtPlotItem *)));
242
243 // Connect to DIM handler
244 if (connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) {
245 printf("Failed connection for %s\n", Service.toAscii().data());
246 }
247
248 // Additonal context menu items
249 QAction* Action = Menu->addAction("Paste service", this, SLOT(MenuPasteService()));
250 Menu->removeAction(Action);
251 Menu->insertAction(Menu->actions().value(1), Action);
252
253 // Maximum number if points in curve (will be increased in Update())
254 SizeLimit = 0;
255
256 // DIM client
257 if (!Service.isEmpty()) AddService(Service, Index);
258}
259
260// Destructor (items with parent widget are automatically deleted)
261EddPlot::~EddPlot() {
262
263 while (!List.isEmpty()) DeleteCurve(List.last().Signal);
264}
265
266// Add history service to plot
267void EddPlot::AddService(QString Name, int Index) {
268
269 // Check if curve already present on plot
270 for (int i=0; i<List.size(); i++) {
271 if (Name == List[i].Name && Index == List[i].Index) {
272 QMessageBox::warning(this, "Edd Message",Name+" ("+QString::number(Index)+") already present",QMessageBox::Ok);
273 return;
274 }
275 }
276
277 // Generate new curve and subscribe to service
278 struct ItemDetails N;
279
280 N.Name = Name;
281 N.Signal = NewCurve(Name+"("+QString::number(Index)+")");
282 N.Index = Index;
283 List.append(N);
284
285 Handler->Subscribe(Name);
286}
287
288// Update widget (must happen in GUI thread)
289void EddPlot::Update(QString Name, int Time, QByteArray, QString Format, QString Text) {
290
291 bool DoUpdate = false;
292
293 // Determine which plot item this call belongs to
294 int ItemNo;
295 for (ItemNo=0; ItemNo<List.size(); ItemNo++) if (List[ItemNo].Name == Name) {
296
297 DoUpdate = true;
298
299 // If size limit reached, clear buffer
300 if (List[ItemNo].Signal->dataSize() > SizeLimit) List[ItemNo].Signal->setData(QPolygonF());
301
302 // If buffer empty, request new history buffer
303 if (List[ItemNo].Signal->dataSize() == 0) {
304 int Count=0;
305 const struct EvidenceHistory::Item *R;
306 class EvidenceHistory *Hist;
307
308 if ((Hist = Handler->GetHistory(List[ItemNo].Name)) != NULL) {
309 double Number=0;
310 while ((R=Hist->Next()) != NULL) {
311 switch (*(Hist->GetFormat())) {
312 case 'I':
313 case 'L': Number = *((int *) R->Data + List[ItemNo].Index); break;
314 case 'S': Number = *((short *) R->Data + List[ItemNo].Index); break;
315 case 'F': Number = *((float *) R->Data + List[ItemNo].Index); break;
316 case 'D': Number = *((double *) R->Data + List[ItemNo].Index); break;
317 case 'X': Number = *((long long *) R->Data + List[ItemNo].Index); break;
318 default: break;
319 }
320 AddPoint(ItemNo, R->Time, Number);
321 Count++;
322 }
323
324 // Local buffer at least twice as large as longest history
325 if (SizeLimit < 2*Count) SizeLimit = 2*Count;
326 }
327 Handler->DropHistory(List[ItemNo].Name);
328 }
329
330 // Append data only if service available
331 if (SetStatus(this, Name, Time, Format)) {
332 QString Txt = Text;
333 Txt = Txt.section(' ', List[ItemNo].Index, List[ItemNo].Index);
334 AddPoint(ItemNo, Time, atof(Txt.toAscii().data()));
335 }
336 }
337
338 if (DoUpdate) UpdatePlot();
339}
340
341
342// Add text indicating time range to plot
343void EddPlot::paintEvent(QPaintEvent *) {
344
345 QString Text;
346 QFont Font;
347 QPainter Painter(this);
348
349 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");
350
351 Font.setPointSize(6);
352 Painter.setFont(Font);
353 Painter.drawText(0, height(), Text);
354}
355
356// Drag and drop methods
357void EddPlot::dragEnterEvent(QDragEnterEvent *Event) {
358
359 if (Event->mimeData()->hasFormat("Edd/Service")) Event->acceptProposedAction();
360}
361
362void EddPlot::dropEvent(QDropEvent *Event) {
363
364 QByteArray D(Event->mimeData()->data("Edd/Service"));
365 AddService(D.left(D.lastIndexOf(' ')), D.right(D.size()-D.lastIndexOf(' ')).toInt());
366}
367
368void EddPlot::LegendClicked(QwtPlotItem *Item) {
369
370 QString D(Item->title().text());
371 D.replace('(',' ').chop(1);
372
373 QDrag *Drag = new QDrag(this);
374 QMimeData *MimeData = new QMimeData;
375 QByteArray Data;
376 MimeData->setData("Edd/Service", Data.append(D));
377 Drag->setMimeData(MimeData);
378 Drag->exec();
379}
380
381// Add new service by pasting name
382void EddPlot::MenuPasteService() {
383
384 const QMimeData *D = QApplication::clipboard()->mimeData();
385 if (!D->hasFormat("Edd/Service")) return;
386
387 QByteArray E(D->data("Edd/Service"));
388 AddService(E.left(E.lastIndexOf(' ')), E.right(E.size()-E.lastIndexOf(' ')).toInt());
389}
390
391// Remove list entry
392void EddPlot::DeleteCurve(QwtPlotCurve *Curve) {
393
394 for (int i=0; i<List.size(); i++) if (List[i].Signal == Curve) {
395 Handler->Unsubscribe(List[i].Name);
396 List.removeAt(i);
397 }
398}
399
400
401//////////////////
402// General plot //
403//////////////////
404
405// Constructor (all slots called in GUI thread, therefore no mutex necessary)
406EddBasePlot::EddBasePlot(QWidget *P): QwtPlot(P) {
407
408 // Widget properties
409 setAttribute(Qt::WA_DeleteOnClose);
410 setAutoReplot(false);
411 setCanvasBackground(EddPlotBackgroundColor);
412 setMargin(15);
413
414 // Plot navigation
415 Zoomer = new QwtPlotZoomer(QwtPlot::xBottom,QwtPlot::yLeft,canvas());
416 connect(Zoomer, SIGNAL(zoomed(const QwtDoubleRect &)), this, SLOT(HandleZoom(const QwtDoubleRect &)));
417 connect(Zoomer, SIGNAL(zoomed(const QwtDoubleRect &)), this, SLOT(ReDoStats()));
418
419 Magnifier = new QwtPlotMagnifier(canvas());
420 Magnifier->setMouseButton(Qt::NoButton,Qt::NoButton);
421 Magnifier->setZoomInKey(Qt::Key_M, Qt::NoModifier);
422 Magnifier->setZoomOutKey(Qt::Key_M, Qt::ShiftModifier);
423
424 Panner = new QwtPlotPanner(canvas());
425 Panner->setMouseButton(Qt::LeftButton, Qt::ShiftModifier);
426 connect(Panner, SIGNAL(panned(int, int)), this, SLOT(ReDoStats()));
427
428 Picker = new QwtPicker(QwtPicker::CornerToCorner|QwtPicker::RectSelection, QwtPicker::RectRubberBand, QwtPicker::AlwaysOff, this);
429 connect(Picker, SIGNAL(selected(const QwtPolygon &)), SLOT(MouseSelection(const QwtPolygon &)));
430
431 // Grid and legend
432 Grid = new QwtPlotGrid;
433 Grid->setMajPen(QPen(Qt::gray, 0, Qt::DotLine));
434 Grid->attach(this);
435
436 insertLegend(new QwtLegend(), QwtPlot::TopLegend, 0.3);
437
438 // Marker for statistics text
439 Stats = new QwtPlotMarker();
440 Stats->setLineStyle(QwtPlotMarker::NoLine);
441 Stats->setLabelAlignment(Qt::AlignLeft | Qt::AlignBottom);
442 Stats->hide();
443 Stats->attach(this);
444
445 // Context menu
446 Menu = new QMenu(this);
447 StripAction = Menu->addAction("Stripchart", this, SLOT(UpdatePlot()));
448 StripAction->setCheckable(true);
449 Menu->addSeparator();
450 YLogAction = Menu->addAction("y scale log", this, SLOT(UpdatePlot()));
451 YLogAction->setCheckable(true);
452 NormAction = Menu->addAction("Normalize", this, SLOT(UpdatePlot()));
453 NormAction->setCheckable(true);
454 StyleAction = Menu->addAction("Draw dots", this, SLOT(UpdatePlot()));
455 StyleAction->setCheckable(true);
456 StatisticsAction = Menu->addAction("Statistics", this, SLOT(ReDoStats()));
457 StatisticsAction->setCheckable(true);
458 Menu->addAction("Zoom out", this, SLOT(MenuZoomOut()));
459 Menu->addAction("Single trace", this, SLOT(MenuSingleTrace()));
460 Menu->addSeparator();
461 Menu->addAction("Save as ASCII", this, SLOT(MenuSaveASCII()));
462 Menu->addAction("Save plot", this, SLOT(MenuSave()));
463 Menu->addAction("Print plot", this, SLOT(MenuPrint()));
464 Menu->addAction("Plot help", this, SLOT(MenuPlotHelp()));
465}
466
467// Destructor (items with parent widget are automatically deleted)
468EddBasePlot::~EddBasePlot() {
469
470 for (int i=0; i<Items.size(); i++) delete Items[i].Signal;
471 delete Grid;
472}
473
474// Print statistics to plot if requested
475void EddBasePlot::ReDoStats() {
476
477 QString Text;
478 double Mean, Sigma;
479 unsigned long Count = 0;
480
481 // Set visibility of statistics box
482 Stats->setVisible(StatisticsAction->isChecked());
483
484 for (int i=0; i<Items.size(); i++) {
485 Mean = 0;
486 Sigma = 0;
487 Count = 0;
488
489 // Calculate mean and sigma for data points currently visible
490 for (int j=0; j<Items[i].y.size(); j++) {
491 if (!axisScaleDiv(QwtPlot::xBottom)->contains(Items[i].x[j])) continue;
492 Mean += Items[i].y[j];
493 Sigma += Items[i].y[j]*Items[i].y[j];
494 Count++;
495 }
496
497 if (Count == 0) Mean = 0;
498 else Mean /= Count;
499 if (Count < 2) Sigma = 0;
500 else Sigma = sqrt(Count/(Count-1)*(Sigma/Count-Mean*Mean));
501
502 // Prepare string
503 Text += Items[i].Signal->title().text() + ": m " + QString::number(Mean, 'f', 2) + ", s " + QString::number(Sigma, 'f', 2) + "\n";
504 }
505
506 // Replot once to get axis correct
507 replot();
508
509 // Print string to plot
510 QwtText text(Text);
511 text.setFont(QFont("Helvetica", 8));
512 Stats->setValue(axisScaleDiv(QwtPlot::xBottom)->upperBound(), axisScaleDiv(QwtPlot::yLeft)->upperBound());
513 Stats->setLabel(text);
514
515 // Replot again to update text
516 replot();
517}
518
519// Update all curves in plot
520void EddBasePlot::UpdatePlot() {
521
522 double Lower = axisScaleDiv(QwtPlot::xBottom)->lowerBound();
523 double Upper = axisScaleDiv(QwtPlot::xBottom)->upperBound();
524 double MaxTime = DBL_MIN;
525 static QwtSymbol Symbol, Sym1;
526 Symbol.setStyle(QwtSymbol::Ellipse);
527 Symbol.setSize(4);
528
529 // Select engine for linear or logarithmic scale
530 if (!YLogAction->isChecked()) {
531 setAxisScaleEngine(QwtPlot::yLeft, new QwtLinearScaleEngine);
532 }
533 else setAxisScaleEngine(QwtPlot::yLeft, new QwtLog10ScaleEngine);
534
535 for (int ItemNo=0; ItemNo<Items.size(); ItemNo++) {
536 // Determine current maximum value for strip chart plotting
537 if (Items[ItemNo].Signal->boundingRect().right() > MaxTime) {
538 MaxTime = Items[ItemNo].Signal->boundingRect().right();
539 }
540
541 // Set symbol if requested
542 if (StyleAction->isChecked()) Items[ItemNo].Signal->setSymbol(Symbol);
543 else Items[ItemNo].Signal->setSymbol(Sym1);
544
545 // Determine number of data points
546 int DataPoints = Items[ItemNo].x.size();
547 if (DataPoints == 0) continue;
548
549 // Normalize y scale if requested
550 double *y = new double [DataPoints];
551 for (int i=0; i<DataPoints; i++) {
552 y[i] = Items[ItemNo].y[i];
553
554 if (NormAction->isChecked()) {
555 if (Items[ItemNo].Smallest != Items[ItemNo].Largest) {
556 y[i] = (y[i] - Items[ItemNo].Smallest)/(Items[ItemNo].Largest-Items[ItemNo].Smallest);
557 }
558 else y[i] = 1;
559 }
560 }
561
562 // Plot data
563 Items[ItemNo].Signal->setData(Items[ItemNo].x.data(), y, DataPoints);
564 Items[ItemNo].Signal->show();
565 delete[] y;
566
567 // Generate bounding box of all curves for zoomer
568 BBox = BBox.unite(Items[ItemNo].Signal->boundingRect());
569 }
570
571 // Reset zoom base to include all data
572 Zoomer->setZoomBase(BBox);
573
574 // If plot is strip char, only move axis but keep range
575 if (StripAction->isChecked()) {
576 setCanvasBackground(EddPlotBackgroundColor.lighter(90));
577 setAxisScale(QwtPlot::xBottom, Lower+ BBox.right() - MaxTime, Upper + BBox.right() - MaxTime);
578 }
579 else setCanvasBackground(EddPlotBackgroundColor);
580
581 ReDoStats();
582}
583
584// Append curve to plot
585QwtPlotCurve *EddBasePlot::NewCurve(QwtText Title) {
586
587 static Qt::GlobalColor LineColors[] = {Qt::black, Qt::blue, Qt::red, Qt::green, Qt::white,
588 Qt::darkRed, Qt::darkGreen, Qt::darkBlue, Qt::cyan, Qt::darkCyan, Qt::magenta, Qt::darkMagenta,
589 Qt::gray, Qt::darkGray, Qt::lightGray};
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 << 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// Open new window //
1010/////////////////////
1011
1012// Constructor
1013EddWindow::EddWindow(QString ButtonName, QString WindowName): QPushButton(ButtonName) {
1014
1015 M = new QMainWindow;
1016 M->setCentralWidget(new QWidget);
1017 M->setStatusBar(new QStatusBar(M));
1018 M->setWindowTitle(WindowName);
1019
1020 L = new QGridLayout(M->centralWidget());
1021
1022 connect(this, SIGNAL(pressed()), SLOT(Toggle()));
1023}
1024
1025// Show or hide window
1026void EddWindow::Toggle() {
1027
1028 if (M->isVisible()) M->hide();
1029 else M->show();
1030}
1031
1032// Return layout
1033QGridLayout *EddWindow::Layout() {
1034
1035 return L;
1036}
1037
1038// Return window
1039QMainWindow *EddWindow::Window() {
1040
1041 return M;
1042}
Note: See TracBrowser for help on using the repository browser.