source: fact/Evidence/GUI.cc@ 11004

Last change on this file since 11004 was 10984, checked in by ogrimm, 13 years ago
Edd allows to set plot update rate
File size: 31.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 "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
52//
53// Set status tip (returns true if service was available)
54//
55bool SetStatus(QWidget *W, QString Name, int Time, QString Format, int Index) {
56
57 QString Status;
58
59 if (Index != -1) Name = Name + "(" + QString::number(Index) + ")";
60
61 if (Time == -1) Status = QString("%1: unavailable").arg(Name);
62 else Status = QString("%1: Last update %2 Format '%3'").arg(Name).arg(QDateTime::fromTime_t(Time).toString()).arg(Format);
63
64 W->setStatusTip(Status);
65
66 return(Time != -1);
67}
68
69
70//////////////////////////////////////////
71// Text display for arbitary DIM service//
72//////////////////////////////////////////
73
74EddLineDisplay::EddLineDisplay(QString Name, int Index, QWidget *P):
75 QLineEdit(P), ServiceName(Name), Index(Index) {
76
77 LastHist = NULL;
78
79 // Widget properties
80 setReadOnly(true);
81 setMaximumWidth(100);
82 ShowAsTime = false;
83 setFrame(false);
84 setAttribute(Qt::WA_DeleteOnClose);
85 setText("connecting...");
86
87 // Connect to DIM handler
88 if (connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString)), Qt::QueuedConnection) == 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)), Qt::QueuedConnection) == 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 // Set timer to regularly update plot if new data available
257 SingleShot = new QTimer(this);
258 connect(SingleShot, SIGNAL(timeout()), this, SLOT(UpdatePlot()));
259 SingleShot->setSingleShot(true);
260
261 // DIM client
262 if (!Service.isEmpty()) AddService(Service, Index);
263}
264
265// Destructor (items with parent widget are automatically deleted)
266EddPlot::~EddPlot() {
267
268 while (!List.isEmpty()) DeleteCurve(List.last().Signal);
269}
270
271// Add history service to plot
272void EddPlot::AddService(QString Name, int Index) {
273
274 // Check if curve already present on plot
275 for (int i=0; i<List.size(); i++) {
276 if (Name == List[i].Name && Index == List[i].Index) {
277 QMessageBox::warning(this, "Edd Message",Name+" ("+QString::number(Index)+") already present",QMessageBox::Ok);
278 return;
279 }
280 }
281
282 // Generate new curve and subscribe to service
283 struct ItemDetails N;
284
285 N.Name = Name;
286 N.Signal = NewCurve(Name+"("+QString::number(Index)+")");
287 N.Index = Index;
288 List.append(N);
289
290 // Request new history buffer
291 const struct EvidenceHistory::Item *R;
292 class EvidenceHistory *Hist;
293
294 if ((Hist = Handler->GetHistory(Name)) != NULL) {
295 double Number=0;
296 while ((R=Hist->Next()) != NULL) {
297 switch (*(Hist->GetFormat())) {
298 case 'I':
299 case 'L': Number = *((int *) R->Data + N.Index); break;
300 case 'S': Number = *((short *) R->Data + N.Index); break;
301 case 'F': Number = *((float *) R->Data + N.Index); break;
302 case 'D': Number = *((double *) R->Data + N.Index); break;
303 case 'X': Number = *((long long *) R->Data + N.Index); break;
304 default: break;
305 }
306 AddPoint(List.size()-1, R->Time, Number);
307 }
308 }
309 Handler->DropHistory(Name);
310 Handler->Subscribe(Name);
311
312 SingleShot->start(100);
313}
314
315// Update widget (must happen in GUI thread)
316void EddPlot::Update(QString Name, int Time, QByteArray, QString Format, QString Text) {
317
318 for (int ItemNo=0; ItemNo<List.size(); ItemNo++) if (List[ItemNo].Name == Name) {
319
320 // Append data if service available
321 if (SetStatus(this, Name, Time, Format)) {
322 QString Txt = Text;
323 Txt = Txt.section(' ', List[ItemNo].Index, List[ItemNo].Index);
324 AddPoint(ItemNo, Time, atof(Txt.toAscii().data()));
325 }
326
327 NewData = true;
328 }
329}
330
331
332// Add text indicating time range to plot
333void EddPlot::paintEvent(QPaintEvent *) {
334
335 QString Text;
336 QFont Font;
337 QPainter Painter(this);
338
339 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");
340
341 Font.setPointSize(6);
342 Painter.setFont(Font);
343 Painter.drawText(0, height(), Text);
344}
345
346// Drag and drop methods
347void EddPlot::dragEnterEvent(QDragEnterEvent *Event) {
348
349 if (Event->mimeData()->hasFormat("Edd/Service")) Event->acceptProposedAction();
350}
351
352void EddPlot::dropEvent(QDropEvent *Event) {
353
354 QByteArray D(Event->mimeData()->data("Edd/Service"));
355 AddService(D.left(D.lastIndexOf(' ')), D.right(D.size()-D.lastIndexOf(' ')).toInt());
356}
357
358void EddPlot::LegendClicked(QwtPlotItem *Item) {
359
360 QString D(Item->title().text());
361 D.replace('(',' ').chop(1);
362
363 QDrag *Drag = new QDrag(this);
364 QMimeData *MimeData = new QMimeData;
365 QByteArray Data;
366 MimeData->setData("Edd/Service", Data.append(D));
367 Drag->setMimeData(MimeData);
368 Drag->exec();
369}
370
371// Add new service by pasting name
372void EddPlot::MenuPasteService() {
373
374 const QMimeData *D = QApplication::clipboard()->mimeData();
375 if (!D->hasFormat("Edd/Service")) return;
376
377 QByteArray E(D->data("Edd/Service"));
378 AddService(E.left(E.lastIndexOf(' ')), E.right(E.size()-E.lastIndexOf(' ')).toInt());
379}
380
381// Remove list entry
382void EddPlot::DeleteCurve(QwtPlotCurve *Curve) {
383
384 for (int i=0; i<List.size(); i++) if (List[i].Signal == Curve) {
385 Handler->Unsubscribe(List[i].Name);
386 List.removeAt(i);
387 }
388}
389
390
391//////////////////
392// General plot //
393//////////////////
394
395// Constructor (all slots called in GUI thread, therefore no mutex necessary)
396EddBasePlot::EddBasePlot(QWidget *P): QwtPlot(P) {
397
398 // Widget properties
399 setAttribute(Qt::WA_DeleteOnClose);
400 setAutoReplot(false);
401 setCanvasBackground(EddPlotBackgroundColor);
402 setMargin(15);
403 NewData = false;
404
405 // Plot navigation
406 Zoomer = new QwtPlotZoomer(QwtPlot::xBottom,QwtPlot::yLeft,canvas());
407 connect(Zoomer, SIGNAL(zoomed(const QwtDoubleRect &)), this, SLOT(HandleZoom(const QwtDoubleRect &)));
408 connect(Zoomer, SIGNAL(zoomed(const QwtDoubleRect &)), this, SLOT(ReDoStats()));
409
410 Magnifier = new QwtPlotMagnifier(canvas());
411 Magnifier->setMouseButton(Qt::NoButton,Qt::NoButton);
412 Magnifier->setZoomInKey(Qt::Key_M, Qt::NoModifier);
413 Magnifier->setZoomOutKey(Qt::Key_M, Qt::ShiftModifier);
414
415 Panner = new QwtPlotPanner(canvas());
416 Panner->setMouseButton(Qt::LeftButton, Qt::ShiftModifier);
417 connect(Panner, SIGNAL(panned(int, int)), this, SLOT(ReDoStats()));
418
419 Picker = new QwtPicker(QwtPicker::CornerToCorner|QwtPicker::RectSelection, QwtPicker::RectRubberBand, QwtPicker::AlwaysOff, this);
420 connect(Picker, SIGNAL(selected(const QwtPolygon &)), SLOT(MouseSelection(const QwtPolygon &)));
421
422 // Grid and legend
423 Grid = new QwtPlotGrid;
424 Grid->setMajPen(QPen(Qt::gray, 0, Qt::DotLine));
425 Grid->attach(this);
426
427 insertLegend(new QwtLegend(), QwtPlot::TopLegend, 0.3);
428
429 // Marker for statistics text
430 Stats = new QwtPlotMarker();
431 Stats->setLineStyle(QwtPlotMarker::NoLine);
432 Stats->setLabelAlignment(Qt::AlignLeft | Qt::AlignBottom);
433 Stats->hide();
434 Stats->attach(this);
435
436 // Context menu
437 Menu = new QMenu(this);
438 StripAction = Menu->addAction("Stripchart", this, SLOT(UpdatePlot()));
439 StripAction->setCheckable(true);
440 Menu->addSeparator();
441 YLogAction = Menu->addAction("y scale log", this, SLOT(UpdatePlot()));
442 YLogAction->setCheckable(true);
443 NormAction = Menu->addAction("Normalize", this, SLOT(UpdatePlot()));
444 NormAction->setCheckable(true);
445 StyleAction = Menu->addAction("Draw dots", this, SLOT(UpdatePlot()));
446 StyleAction->setCheckable(true);
447 StatisticsAction = Menu->addAction("Statistics", this, SLOT(ReDoStats()));
448 StatisticsAction->setCheckable(true);
449 Menu->addAction("Zoom out", this, SLOT(MenuZoomOut()));
450 Menu->addAction("Single trace", this, SLOT(MenuSingleTrace()));
451 Menu->addSeparator();
452 Menu->addAction("Set update rate", this, SLOT(MenuSetUpdateRate()));
453 Menu->addSeparator();
454 Menu->addAction("Save as ASCII", this, SLOT(MenuSaveASCII()));
455 Menu->addAction("Save plot", this, SLOT(MenuSave()));
456 Menu->addAction("Print plot", this, SLOT(MenuPrint()));
457 Menu->addAction("Plot help", this, SLOT(MenuPlotHelp()));
458
459 // Set timer to regularly update plot if new data available
460 Timer = new QTimer(this);
461 connect(Timer, SIGNAL(timeout()), this, SLOT(UpdatePlot()));
462 Timer->start(2000);
463}
464
465// Destructor (items with parent widget are automatically deleted)
466EddBasePlot::~EddBasePlot() {
467
468 for (int i=0; i<Items.size(); i++) delete Items[i].Signal;
469 delete Grid;
470}
471
472// Print statistics to plot if requested
473void EddBasePlot::ReDoStats() {
474
475 QString Text;
476 double Mean, Sigma;
477 unsigned long Count = 0;
478
479 // Set visibility of statistics box
480 Stats->setVisible(StatisticsAction->isChecked());
481
482 for (int i=0; i<Items.size(); i++) {
483 Mean = 0;
484 Sigma = 0;
485 Count = 0;
486
487 // Calculate mean and sigma for data points currently visible
488 for (int j=0; j<Items[i].y.size(); j++) {
489 if (!axisScaleDiv(QwtPlot::xBottom)->contains(Items[i].x[j])) continue;
490 Mean += Items[i].y[j];
491 Sigma += Items[i].y[j]*Items[i].y[j];
492 Count++;
493 }
494
495 if (Count == 0) Mean = 0;
496 else Mean /= Count;
497 if (Count < 2) Sigma = 0;
498 else Sigma = sqrt(Count/(Count-1)*(Sigma/Count-Mean*Mean));
499
500 // Prepare string
501 Text += Items[i].Signal->title().text() + ": m " + QString::number(Mean, 'f', 2) + ", s " + QString::number(Sigma, 'f', 2) + "\n";
502 }
503
504 // Replot once to get axis correct
505 replot();
506
507 // Print string to plot
508 QwtText text(Text);
509 text.setFont(QFont("Helvetica", 8));
510 Stats->setValue(axisScaleDiv(QwtPlot::xBottom)->upperBound(), axisScaleDiv(QwtPlot::yLeft)->upperBound());
511 Stats->setLabel(text);
512
513 // Replot again to update text
514 replot();
515}
516
517// Update all curves in plot
518void EddBasePlot::UpdatePlot() {
519
520 double Lower = axisScaleDiv(QwtPlot::xBottom)->lowerBound();
521 double Upper = axisScaleDiv(QwtPlot::xBottom)->upperBound();
522 double MaxTime = DBL_MIN;
523 static QwtSymbol Symbol, Sym1;
524 Symbol.setStyle(QwtSymbol::Ellipse);
525 Symbol.setSize(4);
526
527 // Only update if called by timer if new data is available
528 if (sender() == Timer && !NewData) return;
529 NewData = false;
530
531 // Select engine for linear or logarithmic scale
532 if (!YLogAction->isChecked()) {
533 setAxisScaleEngine(QwtPlot::yLeft, new QwtLinearScaleEngine);
534 }
535 else setAxisScaleEngine(QwtPlot::yLeft, new QwtLog10ScaleEngine);
536
537 for (int ItemNo=0; ItemNo<Items.size(); ItemNo++) {
538 // Determine current maximum value for strip chart plotting
539 if (Items[ItemNo].Signal->boundingRect().right() > MaxTime) {
540 MaxTime = Items[ItemNo].Signal->boundingRect().right();
541 }
542
543 // Set symbol if requested
544 if (StyleAction->isChecked()) Items[ItemNo].Signal->setSymbol(Symbol);
545 else Items[ItemNo].Signal->setSymbol(Sym1);
546
547 // Determine number of data points
548 int DataPoints = Items[ItemNo].x.size();
549 if (DataPoints == 0) continue;
550
551 // Normalize y scale if requested
552 double *y = new double [DataPoints];
553 for (int i=0; i<DataPoints; i++) {
554 y[i] = Items[ItemNo].y[i];
555
556 if (NormAction->isChecked()) {
557 if (Items[ItemNo].Smallest != Items[ItemNo].Largest) {
558 y[i] = (y[i] - Items[ItemNo].Smallest)/(Items[ItemNo].Largest-Items[ItemNo].Smallest);
559 }
560 else y[i] = 1;
561 }
562 }
563
564 // Plot data
565 Items[ItemNo].Signal->setData(Items[ItemNo].x.data(), y, DataPoints);
566 Items[ItemNo].Signal->show();
567 delete[] y;
568
569 // Generate bounding box of all curves for zoomer
570 BBox = BBox.unite(Items[ItemNo].Signal->boundingRect());
571 }
572
573 // Reset zoom base to include all data
574 Zoomer->setZoomBase(BBox);
575
576 // If plot is strip char, only move axis but keep range
577 if (StripAction->isChecked()) {
578 setCanvasBackground(EddPlotBackgroundColor.lighter(90));
579 setAxisScale(QwtPlot::xBottom, Lower+ BBox.right() - MaxTime, Upper + BBox.right() - MaxTime);
580 }
581 else setCanvasBackground(EddPlotBackgroundColor);
582
583 ReDoStats();
584}
585
586// Append curve to plot
587QwtPlotCurve *EddBasePlot::NewCurve(QwtText Title) {
588
589 static Qt::GlobalColor LineColors[] = {Qt::black, Qt::blue, Qt::red, Qt::green, Qt::white,
590 Qt::darkRed, Qt::darkGreen, Qt::darkBlue, Qt::cyan, Qt::darkCyan, Qt::magenta, Qt::darkMagenta,
591 Qt::gray, Qt::darkGray, Qt::lightGray};
592 struct PlotItem N;
593
594 N.Signal = new QwtPlotCurve;
595 N.Signal->attach(this);
596 N.Signal->setTitle(Title);
597 N.Signal->setPen(QColor(LineColors[Items.size() % (sizeof(LineColors)/sizeof(Qt::GlobalColor))]));
598 N.Largest = DBL_MIN;
599 N.Smallest = DBL_MAX;
600 Items.append(N);
601
602 return N.Signal;
603}
604
605// Clear curve data
606void EddBasePlot::ClearCurve(unsigned int Item) {
607
608 if (Item >= (unsigned int) Items.size()) return;
609
610 Items[Item].x.clear();
611 Items[Item].y.clear();
612}
613
614// Append data point
615void EddBasePlot::AddPoint(unsigned int Item, double x, double y) {
616
617 if (Item >= (unsigned int) Items.size()) return;
618
619 Items[Item].x.append(x);
620 Items[Item].y.append(y);
621
622 if (y > Items[Item].Largest) Items[Item].Largest = y;
623 if (y < Items[Item].Smallest) Items[Item].Smallest = y;
624}
625
626// Rescale plot in case selection has been made outside the canvas
627void EddBasePlot::MouseSelection(const QPolygon &P) {
628
629 QwtDoubleInterval xPlot, xMouse, yPlot, yMouse;
630
631 // Shift selected rectangle so that upper left corner is 0/0 on canvas
632 QRect R = P.boundingRect().translated(-plotLayout()->canvasRect().topLeft());
633
634 // Current axis intervals
635 xPlot = axisScaleDiv(QwtPlot::xBottom)->interval();
636 yPlot = axisScaleDiv(QwtPlot::yLeft)->interval();
637
638 // Selected axis intervals
639 xMouse = QwtDoubleInterval(invTransform(QwtPlot::xBottom, R.left()),
640 invTransform(QwtPlot::xBottom, R.right()));
641 yMouse = QwtDoubleInterval(invTransform(QwtPlot::yLeft, R.bottom()),
642 invTransform(QwtPlot::yLeft, R.top()));
643
644 // Selection region outside all axis?
645 if (R.right() < 0 && R.top() > plotLayout()->canvasRect().height()) return;
646
647 // Rescale both axis if selected rectangle encompasses canvas completely
648 if (P.boundingRect().contains(plotLayout()->canvasRect())) {
649 yMouse.setMaxValue(yMouse.maxValue() + yPlot.width());
650 yMouse.setMinValue(yMouse.minValue() - yPlot.width());
651 xMouse.setMinValue(xMouse.minValue() - xPlot.width());
652 xMouse.setMaxValue(xMouse.maxValue() + xPlot.width());
653
654 setAxisScale(QwtPlot::xBottom, xMouse.minValue(), xMouse.maxValue());
655 setAxisScale(QwtPlot::yLeft, yMouse.minValue(), yMouse.maxValue());
656 }
657
658 // Rescale y axis (increase range if selected rectangle larger than axis area)
659 if (R.right() < 0) {
660 if (yMouse.maxValue() > axisScaleDiv(QwtPlot::yLeft)->upperBound()) {
661 yMouse.setMaxValue(yMouse.maxValue() + yPlot.width());
662 }
663 if (yMouse.minValue() < axisScaleDiv(QwtPlot::yLeft)->lowerBound()) {
664 yMouse.setMinValue(yMouse.minValue() - yPlot.width());
665 }
666
667 setAxisScale(QwtPlot::yLeft, yMouse.minValue(), yMouse.maxValue());
668 }
669
670 // Rescale x axis (increase range if selected rectangle larger than axis area)
671 if (R.top() > plotLayout()->canvasRect().height()) {
672 if (xMouse.maxValue() > axisScaleDiv(QwtPlot::xBottom)->upperBound()) {
673 xMouse.setMaxValue(xMouse.maxValue() + xPlot.width());
674 }
675 if (xMouse.minValue() < axisScaleDiv(QwtPlot::xBottom)->lowerBound()) {
676 xMouse.setMinValue(xMouse.minValue() - xPlot.width());
677 }
678
679 setAxisScale(QwtPlot::xBottom, xMouse.minValue(), xMouse.maxValue());
680 }
681
682 // Draw new scales
683 replot();
684}
685
686// Reset graph axes to autoscale when fully unzoomed
687void EddBasePlot::HandleZoom(const QwtDoubleRect &) {
688
689 if(Zoomer->zoomRectIndex() == 0) {
690 setAxisAutoScale(QwtPlot::xBottom);
691 setAxisAutoScale(QwtPlot::yLeft);
692 }
693
694 UpdatePlot();
695}
696
697// Opening context menu
698void EddBasePlot::contextMenuEvent(QContextMenuEvent *Event) {
699
700 Menu->exec(Event->globalPos());
701}
702
703// Zoom completely out
704void EddBasePlot::MenuZoomOut() {
705
706 Zoomer->zoom(0);
707 UpdatePlot();
708}
709
710// Remove all items except one
711void EddBasePlot::MenuSingleTrace() {
712
713 while (Items.size() > 1) {
714 DeleteCurve(Items.last().Signal);
715 delete Items.last().Signal;
716 Items.takeLast();
717 }
718 UpdatePlot();
719}
720
721// Set maximum update rate of plot
722void EddBasePlot::MenuSetUpdateRate() {
723
724 bool OK;
725 double Rate;
726
727 Rate = QInputDialog::getDouble(this, "Edd Request",
728 "Minimum period between plot updates (sec)", (double) Timer->interval()/1000, 0, std::numeric_limits<double>::max(), 2, &OK);
729 if (OK) Timer->setInterval(Rate*1000);
730}
731
732// Save data of plot as test
733void EddBasePlot::MenuSaveASCII() {
734 QString Filename = QFileDialog::getSaveFileName(this,
735 "Filename", ".", "Text files (*.txt *.ascii *.asc);;All files (*)");
736 if (Filename.length() <= 0) return;
737
738 QFile File(Filename);
739 if (!File.open(QFile::WriteOnly | QIODevice::Text | QFile::Truncate)) {
740 QMessageBox::warning(this, "Edd Message","Could not open file for writing.",QMessageBox::Ok);
741 return;
742 }
743
744 // Write x and y data for all signals to file
745 QTextStream Stream(&File);
746
747 for (int ItemNo=0; ItemNo<Items.size(); ItemNo++) {
748 Stream << QString("# ") + Items[ItemNo].Signal->title().text() + ".hist" << endl;
749 for (int i=0; i<Items[ItemNo].Signal->dataSize(); i++) {
750 Stream << Items[ItemNo].x.at(i) << " " << Items[ItemNo].Signal->y(i) << endl;
751 }
752 }
753}
754
755// Print plot
756void EddBasePlot::MenuPrint() {
757
758 QPrinter *Printer = new QPrinter;
759 QPrintDialog *PrintDialog = new QPrintDialog(Printer, this);
760 if (PrintDialog->exec() == QDialog::Accepted) {
761 QPainter Painter(Printer);
762 QPixmap Pixmap = QPixmap::grabWidget(this);
763 Painter.drawPixmap(0, 0, Pixmap);
764 }
765 delete Printer; delete PrintDialog;
766}
767
768// Save plot as image
769void EddBasePlot::MenuSave() {
770
771 QString Filename = QFileDialog::getSaveFileName(this,
772 "Filename of image", "/home/ogrimm/ddd", "Image files (*.bmp *.jpg *.png *.ppm *.tiff *.xbm *.xpm);;All files (*)");
773 if (Filename.length()>0) {
774 QPixmap Pixmap = QPixmap::grabWidget(this);
775 if(!Pixmap.save(Filename)) {
776 QMessageBox::warning(this, "Edd Message","Could not write image file.",QMessageBox::Ok);
777 remove(Filename.toAscii().data());
778 }
779 }
780}
781
782// Help text
783void EddBasePlot::MenuPlotHelp() {
784
785 QMessageBox::about(this, "Edd - Plot navigation",
786 "Zoom\tMouse wheel\n"
787 "\tKeys m and shift-m\n"
788 "\tSelecting region with left mouse button\n"
789 "\tMiddle button zooms out one level\n"
790 "\tSelecting a range on an axis\n"
791 "\tSelecting whole canvas\n\n"
792 "Pan\tShift and left mouse button\n\n"
793 "ESC cancels selection\n"
794 "Cursor keys move mouse\n\n"
795 "Statistics are calculated over the current x axis extend");
796}
797
798//////////////////////////////////////
799// History text box for DIM service //
800//////////////////////////////////////
801
802// Constructor
803EddText::EddText(QString Name, bool Pure, QWidget *P):
804 QTextEdit(P), Name(Name), Pure(Pure) {
805
806 // Widget properties
807 setReadOnly(true);
808 setAttribute(Qt::WA_DeleteOnClose);
809 setAutoFillBackground(true);
810 setText("connecting...");
811 document()->setMaximumBlockCount(1000);
812 Accumulate = true;
813
814 // Connect to DIM handler
815 if (connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString)), Qt::QueuedConnection) == false) {
816 printf("Failed connection for %s\n", Name.toAscii().data());
817 }
818
819 if (!Pure) {
820 // Get history for this service
821 const struct EvidenceHistory::Item *R;
822 class EvidenceHistory *Hist;
823
824 if ((Hist = Handler->GetHistory(Name)) != NULL) {
825 while ((R=Hist->Next()) != NULL) {
826 moveCursor (QTextCursor::Start);
827 insertPlainText(QString("(")+QDateTime::fromTime_t(R->Time).toString()+") " +
828 QString::fromStdString(EvidenceServer::ToString(Hist->GetFormat(), (void *) R->Data, R->Size)) + "\n");
829 }
830 }
831 Handler->DropHistory(Name);
832 }
833
834 // DIM client
835 Handler->Subscribe(Name);
836}
837
838// Destructor
839EddText::~EddText() {
840
841 Handler->Unsubscribe(Name);
842}
843
844
845// Update widget (must happen in GUI thread)
846void EddText::Update(QString Name, int Time, QByteArray, QString Format, QString Text) {
847
848 if (this->Name != Name) return;
849 QPalette Pal = palette();
850
851 // Check if service available
852 if (!SetStatus(this, Name, Time, Format)) {
853 Pal.setColor(QPalette::Base, Qt::lightGray);
854 setPalette(Pal);
855 setText("n/a");
856 return;
857 }
858
859 Pal.setColor(QPalette::Base, Qt::white);
860 setPalette(Pal);
861 QDateTime Timex = QDateTime::fromTime_t(Time);
862
863 // Clear display in case text should not accumulate
864 if (Accumulate == false) clear();
865
866 if (!Pure) {
867 moveCursor(QTextCursor::Start);
868 insertPlainText(QString("(")+Timex.toString()+QString(") "));
869 insertPlainText(Text + "\n");
870 }
871 else if (Format == "C") insertPlainText(Text);
872}
873
874
875/////////////////////////////
876// Interface to Dim system //
877/////////////////////////////
878EddDim::EddDim() {
879
880 Mutex = new QMutex(QMutex::Recursive);
881 Volume = 0;
882
883 // Timer to calculate data rates
884 QTimer *Timer = new QTimer(this);
885 Timer->connect(Timer, SIGNAL(timeout()), this, SLOT(UpdateStatistics()));
886 Timer->start(10000);
887
888 // Connect to DIM handler
889 if (connect(this, SIGNAL(INT(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) {
890 printf("Failed connection in EddDim()\n");
891 }
892
893 // To ensure responsiveness of GUI, subscriptions handled only when no GUI events (timeout 0)
894 Timer = new QTimer(this);
895 connect(Timer, SIGNAL(timeout()), SLOT(MakeSubscriptions()));
896 Timer->start();
897}
898
899// Destructor
900EddDim::~EddDim() {
901
902 QList<QString> L = HistoryList.keys();
903 for(int i=0; i<L.size(); i++) delete HistoryList[L[i]].HistClass;
904
905 delete Mutex;
906}
907
908// Subscribe to DIM service (actual subscription handled in worker thread below)
909void EddDim::Subscribe(QString Name) {
910
911 // Lock before accessing list
912 QMutexLocker Locker(Mutex);
913 WaitingList.append(Name);
914}
915
916// Subscriptions handled only when no GUI events waiting (launched by timer, see constructor)
917void EddDim::MakeSubscriptions() {
918
919 if (WaitingList.isEmpty()) return;
920
921 // Lock before accessing list
922 QMutexLocker Locker(Mutex);
923
924 QString Name = WaitingList.first();
925 WaitingList.removeFirst();
926
927 // If already subscribed to service, increase usage count
928 if (ServiceList.contains(Name)) {
929 ServiceList[Name].Count++;
930 //YEP(Name, ServiceList[Name].TimeStamp, ServiceList[Name].ByteArray, ServiceList[Name].Format, ServiceList[Name].Text);
931 return;
932 }
933
934 // Create new entry in service list
935 ServiceList[Name].ByteArray = QByteArray();
936 ServiceList[Name].TimeStamp = -1;
937 ServiceList[Name].Count = 1;
938 ServiceList[Name].DIMService = new DimStampedInfo(Name.toAscii().data(), INT_MAX, NO_LINK, this);
939}
940
941
942// Unsubsribe from DIM service
943void EddDim::Unsubscribe(QString Name) {
944
945 // Lock before accessing list
946 QMutexLocker Locker(Mutex);
947
948 if (ServiceList.contains(Name)) ServiceList[Name].Count--;
949
950 if (ServiceList[Name].Count == 0) {
951 delete ServiceList[Name].DIMService;
952 ServiceList.remove(Name);
953 return;
954 }
955}
956
957// Get history buffer
958class EvidenceHistory *EddDim::GetHistory(QString Name) {
959
960 // History already available (only request again if too old)
961 if (HistoryList.contains(Name)) {
962 HistoryList[Name].Count++;
963
964 if (time(NULL)-HistoryList[Name].LastUpdate < 5) {
965 HistoryList[Name].HistClass->Rewind();
966 return HistoryList[Name].HistClass;
967 }
968 HistoryList[Name].LastUpdate = time(NULL);
969 if (HistoryList[Name].HistClass->GetHistory()) return HistoryList[Name].HistClass;
970 else return NULL;
971 }
972
973 // Create new history class
974 HistoryList[Name].HistClass = new EvidenceHistory(Name.toStdString());
975 HistoryList[Name].Count = 1;
976 HistoryList[Name].LastUpdate = time(NULL);
977
978 if (HistoryList[Name].HistClass->GetHistory()) return HistoryList[Name].HistClass;
979 else return NULL;
980}
981
982// Reduce history usage counter
983void EddDim::DropHistory(QString Name) {
984
985 if (HistoryList.contains(Name)) HistoryList[Name].Count--;
986}
987
988// Update throughput statistics and clear up history memory
989void EddDim::UpdateStatistics() {
990
991 // Lock before accessing internal variables
992 QMutexLocker Locker(Mutex);
993
994 // Remove unused histories after not less than 5 seconds
995 QList<QString> L = HistoryList.keys();
996 for(int i=0; i<L.size(); i++) {
997 if ((HistoryList[L[i]].Count <= 0) && (time(NULL)-HistoryList[L[i]].LastUpdate) > 5) {
998 delete HistoryList[L[i]].HistClass;
999 HistoryList.remove(L[i]);
1000 }
1001 }
1002
1003 float Rate = Volume/1024.0/10;
1004 Volume = 0;
1005 YEP("Edd/Rate_kBSec", time(NULL), QByteArray::number(Rate), "F", QString::number(Rate));
1006}
1007
1008// Force reemit of all buffered DIM data (work around for widget update problem)
1009void EddDim::ForceEmit() {
1010
1011 // Lock before accessing list
1012 QMutexLocker Locker(Mutex);
1013
1014 QMap<QString, struct Item>::const_iterator i = ServiceList.constBegin();
1015
1016 while (i != ServiceList.constEnd()) {
1017 YEP(i.key(), i.value().TimeStamp, i.value().ByteArray, i.value().Format, i.value().Text);
1018 ++i;
1019 }
1020}
1021
1022// Store service information for usage by Subscribe(), update statistics and emit signal to widgets
1023void EddDim::Update(QString Name, int Time, QByteArray Data, QString Format, QString Text) {
1024
1025 // Lock before accessing list
1026 QMutexLocker Locker(Mutex);
1027
1028 // Store service data
1029 if (ServiceList.contains(Name)) {
1030 ServiceList[Name].TimeStamp = Time;
1031 ServiceList[Name].ByteArray = Data;
1032 ServiceList[Name].Format = Format;
1033 ServiceList[Name].Text = Text;
1034 }
1035
1036 // Update statistics only for actual Dim services
1037 if (!Name.startsWith("Edd/")) Volume += Data.size();
1038
1039 // Emit signal to all widgets
1040 YEP(Name, Time, Data, Format, Text);
1041}
1042
1043// Handling of DIM service update
1044// No locking allowed. Signal triggers only EddDim::Update() when the main event loop is idle.
1045void EddDim::infoHandler() {
1046
1047 if (!EvidenceServer::ServiceOK(getInfo())) INT(getInfo()->getName(), -1);
1048 else {
1049 INT(getInfo()->getName(), getInfo()->getTimestamp(), QByteArray((char *) getInfo()->getData(),
1050 getInfo()->getSize()), getInfo()->getFormat(), QString::fromStdString(EvidenceServer::ToString(getInfo()->getFormat(), getInfo()->getData(), getInfo()->getSize())));
1051 }
1052}
1053
1054
1055/////////////////////
1056// Open new window //
1057/////////////////////
1058
1059// Constructor
1060EddWindow::EddWindow(QString ButtonName, QString WindowName): QPushButton(ButtonName) {
1061
1062 M = new QMainWindow;
1063 M->setCentralWidget(new QWidget);
1064 M->setStatusBar(new QStatusBar(M));
1065 M->setWindowTitle(WindowName);
1066
1067 L = new QGridLayout(M->centralWidget());
1068
1069 connect(this, SIGNAL(pressed()), SLOT(Toggle()));
1070}
1071
1072// Show or hide window
1073void EddWindow::Toggle() {
1074
1075 if (M->isVisible()) M->hide();
1076 else {
1077 M->show();
1078 M->raise();
1079 }
1080}
1081
1082// Return layout
1083QGridLayout *EddWindow::Layout() {
1084
1085 return L;
1086}
1087
1088// Return window
1089QMainWindow *EddWindow::Window() {
1090
1091 return M;
1092}
Note: See TracBrowser for help on using the repository browser.