source: fact/Evidence/GUI.cc@ 12909

Last change on this file since 12909 was 12894, checked in by ogrimm, 13 years ago
Updated Edd for history plotting of more complex arrays
File size: 34.9 KB
Line 
1/* ============================================================
2
3Edd - Evidence Data Display
4
5Qt-based graphical user interface for the Evidence contron system
6
7EddLineDisplay changes its background colour in case it display
8a DIM status service
9
10April 2010, February 2012, Oliver Grimm
11
12============================================================ */
13
14#include "GUI.h"
15
16class EddDim *Handler;
17
18//
19// History chooser function (opens plot for numeric data, TextHist for all other)
20//
21void OpenHistory(char *Service, int FromIndex, int ToIndex) {
22
23 QString Format;
24 DimBrowser Browser;
25 class EvidenceHistory *Hist = Handler->GetHistory(Service);
26 char *Name, *Fmt;
27
28 // Check if history service available
29 if (strcmp(Service, "Edd/Rate_kBSec") == 0) Format = "F";
30 else if (Hist == NULL || Hist->GetFormat() == NULL) {
31 //QMessageBox::warning(NULL, "Edd Message", QString("Could not retrieve history for service ") + Service ,QMessageBox::Ok);
32 printf("Edd Message: Could not retrieve history for service %s\n", Service);
33 }
34 else Format = Hist->GetFormat();
35
36 // If service currently available, take its format
37 Browser.getServices(Service);
38 if (Browser.getNextService(Name, Fmt) != 0) Format = QString(Fmt);
39
40 Handler->DropHistory(Service);
41
42 // Open new window
43 QMainWindow *M = new QMainWindow;
44 M->setCentralWidget(new QWidget(M));
45 M->setStatusBar(new QStatusBar(M));
46 M->setAttribute(Qt::WA_DeleteOnClose);
47 M->setWindowTitle("Edd History");
48
49 QGridLayout *Layout = new QGridLayout(M->centralWidget());
50
51 if (Format.size() == 1 && Format[0] == 'C') Layout->addWidget(new EddText(Service), 0, 0);
52 else {
53 EddPlot *W = new EddPlot(Service, FromIndex);
54 for (int i=FromIndex+1; i<=ToIndex; i++) {
55 W->AddService(Service,i);
56 Layout->addWidget(new EddLineDisplay(Service, i), 1 + (i-FromIndex)/5, (i-FromIndex)%5);
57 }
58 Layout->addWidget(W, 0, 0, 1, 5);
59 }
60
61 Layout->addWidget(new EddLineDisplay(Service, FromIndex), 1, 0);
62 M->resize(400,450);
63 M->show();
64}
65
66//
67// Set status tip (returns true if service was available)
68//
69bool SetStatus(QWidget *W, QString Name, int Time, QString Format, int Index) {
70
71 QString Status;
72
73 if (Index != -1) Name = Name + "[" + QString::number(Index) + "]";
74
75 if (Time == -1) Status = QString("%1: unavailable").arg(Name);
76 else Status = QString("%1: Last update %2 Format '%3'").arg(Name).arg(QDateTime::fromTime_t(Time).toString()).arg(Format);
77
78 W->setStatusTip(Status);
79
80 return(Time != -1);
81}
82
83
84//////////////////////////////////////////
85// Text display for arbitary DIM service//
86//////////////////////////////////////////
87
88EddLineDisplay::EddLineDisplay(QString Name, int Index, QWidget *P):
89 QLineEdit(P), ServiceName(Name), Index(Index) {
90
91 LastHist = NULL;
92
93 // Widget properties
94 setReadOnly(true);
95 setMaximumWidth(100);
96 ShowAsTime = false;
97 setFrame(false);
98 setAttribute(Qt::WA_DeleteOnClose);
99 QPalette Pal = palette();
100 Pal.setColor(QPalette::Base, Qt::lightGray);
101 setPalette(Pal);
102
103 // Context menu
104 Menu = new QMenu(this);
105 Menu->addAction("Open new history", this, SLOT(MenuOpenHistory()));
106 Menu->addAction("Copy service", this, SLOT(MenuCopyService()));
107 Menu->addAction("Copy data", this, SLOT(MenuCopyData()));
108
109 // Subscribe to service
110 Handler->Subscribe(Name, this, Index);
111}
112
113// Destructor
114EddLineDisplay::~EddLineDisplay() {
115
116 Handler->Unsubscribe(ServiceName, this, Index);
117}
118
119// Update widget
120void EddLineDisplay::Update(const QString &, int Time, const QByteArray &, const QString &Format, const QString &Text, int) {
121
122 // Check if service available
123 QPalette Pal = palette();
124 if (!SetStatus(this, ServiceName, Time, Format, Index)) {
125 setText("n/a");
126 Pal.setColor(QPalette::Base, Qt::lightGray);
127 setPalette(Pal);
128 return;
129 }
130 else Pal.setColor(QPalette::Base, Qt::white);
131
132 // Message service backgound colour determined by severity
133 if (ServiceName.endsWith("/Message")) {
134 switch (Text.section(' ', 0, 0).toInt()) {
135 case 0: Pal.setColor(QPalette::Base, Qt::white); break;
136 case 1: Pal.setColor(QPalette::Base, Qt::yellow); break;
137 case 2: Pal.setColor(QPalette::Base, Qt::red); break;
138 case 3: Pal.setColor(QPalette::Base, Qt::red); break;
139 default: break;
140 }
141 setText(Text.section(' ', 1));
142 }
143 else if (!ShowAsTime) setText(Text);
144 else setText(QDateTime::fromTime_t(Text.toInt()).toString());
145
146 setCursorPosition(0);
147 setPalette(Pal);
148}
149
150// Open plot if mouse release within widget
151void EddLineDisplay::mouseReleaseEvent(QMouseEvent *Event) {
152
153 if (Event->button()!=Qt::LeftButton || !contentsRect().contains(Event->pos())) return;
154
155 // Check if last history plot still open, then raise
156 foreach (QWidget *Widget, QApplication::allWidgets()) {
157 if (Widget == LastHist) {
158 Widget->activateWindow();
159 Widget->raise();
160 return;
161 }
162 }
163
164 // If not, open new plot
165 MenuOpenHistory();
166}
167
168// Handling of mouse press event: Register start position for drag
169void EddLineDisplay::mousePressEvent(QMouseEvent *Event) {
170
171 if (Event->button() == Qt::LeftButton) dragStart = Event->pos();
172}
173
174// Handling of dragging (Drag and MimeData will be deleted by Qt)
175void EddLineDisplay::mouseMoveEvent(QMouseEvent *Event) {
176
177 if ((Event->buttons() & Qt::LeftButton) == 0) return;
178 if ((Event->pos()-dragStart).manhattanLength() < QApplication::startDragDistance()) return;
179
180 QDrag *Drag = new QDrag(this);
181 QMimeData *MimeData = new QMimeData;
182 QByteArray Data;
183 MimeData->setData("Edd/Service", Data.append(ServiceName + " " + QString::number(Index)));
184 Drag->setMimeData(MimeData);
185 Drag->exec();
186}
187
188//
189// Opening context menu
190//
191void EddLineDisplay::contextMenuEvent(QContextMenuEvent *Event) {
192
193 Menu->exec(Event->globalPos());
194}
195
196// Menu: Open history plot
197void EddLineDisplay::MenuOpenHistory() {
198
199 OpenHistory(ServiceName.toAscii().data(), Index);
200}
201
202// Menu: Copy service name
203void EddLineDisplay::MenuCopyService() {
204
205 QMimeData *MimeData = new QMimeData;
206 QByteArray Data;
207 MimeData->setData("Edd/Service", Data.append(ServiceName + " " + QString::number(Index)));
208 QApplication::clipboard()->setMimeData(MimeData);
209}
210
211// Menu: Copy data
212void EddLineDisplay::MenuCopyData() {
213
214 QApplication::clipboard()->setText(text());
215}
216
217
218//////////////////////////////////////////
219// Sending string command to DIM server //
220//////////////////////////////////////////
221
222EddCommand::EddCommand(QString Name, QWidget *P): QLineEdit(P), Name(Name) {
223
224 setToolTip("Send command "+Name);
225 setStatusTip(QString("Command %1").arg(Name));
226
227 connect(this, SIGNAL(returnPressed()), SLOT(SendCommand()));
228}
229
230// Get command format and returns NULL (not empty) QString if not available
231QString EddCommand::GetFormat() {
232
233 char *ItsName, *Format;
234 DimBrowser Browser;
235
236 Browser.getServices(Name.toAscii().data());
237
238 if (Browser.getNextService(ItsName, Format) != DimCOMMAND) return QString();
239 else return Format;
240}
241
242// Send command
243void EddCommand::SendCommand() {
244
245 QByteArray Data;
246
247 // Ignore empty commands
248 if (text().isEmpty()) return;
249
250 // Check if command available and retrieve format
251 QString Format = GetFormat();
252
253 if (Format.isNull()) {
254 QMessageBox::warning(this, "Edd Message", "Command " + Name + " currently unavailable", QMessageBox::Ok);
255 return;
256 }
257
258 // Command has no argument
259 if (Format.isEmpty()) {
260 DimClient::sendCommand(Name.toAscii().data(), NULL, 0);
261 return;
262 }
263
264 // Command has string as argument
265 if (Format == "C") {
266 DimClient::sendCommand(Name.toAscii().data(), text().toAscii().data());
267 return;
268 }
269
270 // Command has structure format, interpret data as hex
271 if (Format.size() > 1) {
272 Data = QByteArray::fromHex(text().toAscii());
273 }
274 else {
275 QDataStream Stream(&Data, QIODevice::WriteOnly);
276 std::vector<std::string> Values = EvidenceServer::Tokenize(text().toStdString());
277
278 for (unsigned int i=0; i<Values.size(); i++) {
279 switch (Format[0].toAscii()) {
280 case 'I':
281 case 'L': Stream << (int) atoi(Values[i].c_str()); break;
282 case 'S': Stream << (short) atoi(Values[i].c_str()); break;
283 case 'F': Stream << (float) atof(Values[i].c_str()); break;
284 case 'D': Stream << (double) atof(Values[i].c_str()); break;
285 case 'X': Stream << (long long) (atof(Values[i].c_str())); break;
286 }
287 }
288 }
289
290 DimClient::sendCommand(Name.toAscii().data(), Data.data(), Data.size());
291 clear();
292}
293
294// Opening context menu
295void EddCommand::contextMenuEvent(QContextMenuEvent *Event) {
296
297 QMenu *Menu = createStandardContextMenu();
298 Menu->addSeparator();
299 Menu->addAction("Command help", this, SLOT(MenuCommandHelp()));
300 Menu->exec(Event->globalPos());
301 delete Menu;
302}
303
304// Help text
305void EddCommand::MenuCommandHelp() {
306
307 QString Format = GetFormat();
308 QString Text = Name;
309
310 if (Format.isNull()) {
311 Text += " is currently unavailable";
312 }
313 else Text += " has format '"+Format+"'";
314
315 Text += "\n\nCommand arguments will be transmitted as string for format 'C',\n"
316 "interpreted as an array of numbers for all other single character formats\n"
317 "and as bytes in hexadecimal encoding for all more complex formats";
318
319 QMessageBox::about(this, "Edd - Command help", Text);
320}
321
322//////////////////////////////////
323// History plot for DIM service //
324//////////////////////////////////
325
326EddPlot::EddPlot(QString Service, int Index, QWidget *P): EddBasePlot(P) {
327
328 // Widget properties
329 setAcceptDrops(true);
330 setAxisScaleDraw(QwtPlot::xBottom, new EddTimeScale);
331
332 // Update time range on plot when axis change (update() results in paintEvent())
333 connect(axisWidget(QwtPlot::xBottom), SIGNAL(scaleDivChanged()), SLOT(update()));
334
335 legend()->setItemMode(QwtLegend::ClickableItem);
336 connect(this, SIGNAL(legendClicked (QwtPlotItem *)), SLOT(LegendClicked(QwtPlotItem *)));
337
338 // Additonal context menu items
339 QAction* Action = Menu->addAction("Paste service", this, SLOT(MenuPasteService()));
340 Menu->removeAction(Action);
341 Menu->insertAction(Menu->actions().value(1), Action);
342
343 Action = Menu->addAction("Show last hour", this, SLOT(MenuShowLastHour()));
344 Menu->insertAction(Menu->actions().value(8), Action);
345 Action = Menu->addAction("Show last day", this, SLOT(MenuShowLastDay()));
346 Menu->insertAction(Menu->actions().value(8), Action);
347
348 // Set timer to regularly update plot if new data available
349 SingleShot = new QTimer(this);
350 connect(SingleShot, SIGNAL(timeout()), this, SLOT(UpdatePlot()));
351 SingleShot->setSingleShot(true);
352
353 // DIM client
354 if (!Service.isEmpty()) AddService(Service, Index);
355}
356
357// Destructor (items with parent widget are automatically deleted)
358EddPlot::~EddPlot() {
359
360 while (!List.isEmpty()) DeleteCurve(List.last().Signal);
361}
362
363// Add history service to plot
364void EddPlot::AddService(QString Name, int Index) {
365
366 // Check if curve already present on plot
367 for (int i=0; i<List.size(); i++) {
368 if (Name == List[i].Name && Index == List[i].Index) {
369 QMessageBox::warning(this, "Edd Message",Name+" ("+QString::number(Index)+") already present",QMessageBox::Ok);
370 return;
371 }
372 }
373
374 // Generate new curve and subscribe to service
375 struct ItemDetails N;
376
377 N.Name = Name;
378 N.Signal = NewCurve(Name+"["+QString::number(Index)+"]");
379 N.Index = Index;
380 List.append(N);
381
382 // Request new history buffer
383 const struct EvidenceHistory::Item *R;
384 class EvidenceHistory *Hist;
385
386 if ((Hist = Handler->GetHistory(Name)) != NULL) {
387 double Number=0;
388 while ((R=Hist->Next()) != NULL) {
389 switch (*(Hist->GetFormat())) {
390 case 'I':
391 case 'L': Number = *((int *) R->Data + N.Index); break;
392 case 'S': Number = *((short *) R->Data + N.Index); break;
393 case 'F': Number = *((float *) R->Data + N.Index); break;
394 case 'D': Number = *((double *) R->Data + N.Index); break;
395 case 'X': Number = *((long long *) R->Data + N.Index); break;
396 default: break;
397 }
398 AddPoint(List.size()-1, R->Time, Number);
399 }
400 }
401 Handler->DropHistory(Name);
402 Handler->Subscribe(Name, this, Index);
403
404 SingleShot->start(100);
405}
406
407// Update widget (must happen in GUI thread)
408void EddPlot::Update(const QString &Name, int Time, const QByteArray &, const QString &Format, const QString &Text, int Index) {
409
410 for (int ItemNo=0; ItemNo<List.size(); ItemNo++) if (List[ItemNo].Name==Name && List[ItemNo].Index==Index) {
411
412 // Append data if service available
413 if (SetStatus(this, Name, Time, Format)) AddPoint(ItemNo, Time, atof(Text.toAscii().data()));
414 NewData = true;
415 }
416}
417
418
419// Add text indicating time range to plot
420void EddPlot::paintEvent(QPaintEvent *) {
421
422 QString Text;
423 QFont Font;
424 QPainter Painter(this);
425
426 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");
427
428 Font.setPointSize(6);
429 Painter.setFont(Font);
430 Painter.drawText(0, height(), Text);
431}
432
433// Drag and drop methods
434void EddPlot::dragEnterEvent(QDragEnterEvent *Event) {
435
436 if (Event->mimeData()->hasFormat("Edd/Service")) Event->acceptProposedAction();
437}
438
439void EddPlot::dropEvent(QDropEvent *Event) {
440
441 QByteArray D(Event->mimeData()->data("Edd/Service"));
442 AddService(D.left(D.lastIndexOf(' ')), D.right(D.size()-D.lastIndexOf(' ')).toInt());
443}
444
445void EddPlot::LegendClicked(QwtPlotItem *Item) {
446
447 QString D(Item->title().text());
448 D.replace('(',' ').chop(1);
449
450 QDrag *Drag = new QDrag(this);
451 QMimeData *MimeData = new QMimeData;
452 QByteArray Data;
453 MimeData->setData("Edd/Service", Data.append(D));
454 Drag->setMimeData(MimeData);
455 Drag->exec();
456}
457
458// Add new service by pasting name
459void EddPlot::MenuPasteService() {
460
461 const QMimeData *D = QApplication::clipboard()->mimeData();
462 if (!D->hasFormat("Edd/Service")) return;
463
464 QByteArray E(D->data("Edd/Service"));
465 AddService(E.left(E.lastIndexOf(' ')), E.right(E.size()-E.lastIndexOf(' ')).toInt());
466}
467
468// Show last hour/last day
469void EddPlot::MenuShowLastHour() {
470
471 setAxisScale(QwtPlot::xBottom, time(NULL)-60*60, time(NULL)+60);
472 //setAxisAutoScale(QwtPlot::yLeft);
473 replot();
474}
475
476void EddPlot::MenuShowLastDay() {
477
478 setAxisScale(QwtPlot::xBottom, time(NULL)-24*3600, time(NULL)+3600);
479 //setAxisAutoScale(QwtPlot::yLeft);
480 replot();
481}
482
483
484// Remove list entry
485void EddPlot::DeleteCurve(QwtPlotCurve *Curve) {
486
487 for (int i=0; i<List.size(); i++) if (List[i].Signal == Curve) {
488 Handler->Unsubscribe(List[i].Name, this, List[i].Index);
489 List.removeAt(i);
490 }
491}
492
493
494//////////////////
495// General plot //
496//////////////////
497
498// Constructor (all slots called in GUI thread, therefore no mutex necessary)
499EddBasePlot::EddBasePlot(QWidget *P): QwtPlot(P) {
500
501 // Widget properties
502 setAttribute(Qt::WA_DeleteOnClose);
503 setAutoReplot(false);
504 setCanvasBackground(EddPlotBackgroundColor);
505 setMargin(15);
506 NewData = false;
507
508 // Plot navigation
509 Zoomer = new QwtPlotZoomer(QwtPlot::xBottom,QwtPlot::yLeft,canvas());
510 connect(Zoomer, SIGNAL(zoomed(const QwtDoubleRect &)), this, SLOT(HandleZoom(const QwtDoubleRect &)));
511 connect(Zoomer, SIGNAL(zoomed(const QwtDoubleRect &)), this, SLOT(ReDoStats()));
512
513 Magnifier = new QwtPlotMagnifier(canvas());
514 Magnifier->setMouseButton(Qt::NoButton,Qt::NoButton);
515 Magnifier->setZoomInKey(Qt::Key_M, Qt::NoModifier);
516 Magnifier->setZoomOutKey(Qt::Key_M, Qt::ShiftModifier);
517
518 Panner = new QwtPlotPanner(canvas());
519 Panner->setMouseButton(Qt::LeftButton, Qt::ShiftModifier);
520 connect(Panner, SIGNAL(panned(int, int)), this, SLOT(ReDoStats()));
521
522 Picker = new QwtPicker(QwtPicker::CornerToCorner|QwtPicker::RectSelection, QwtPicker::RectRubberBand, QwtPicker::AlwaysOff, this);
523 connect(Picker, SIGNAL(selected(const QwtPolygon &)), SLOT(MouseSelection(const QwtPolygon &)));
524
525 // Grid and legend
526 Grid = new QwtPlotGrid;
527 Grid->setMajPen(QPen(Qt::gray, 0, Qt::DotLine));
528 Grid->attach(this);
529
530 insertLegend(new QwtLegend(), QwtPlot::TopLegend, 0.3);
531
532 // Marker for statistics text
533 Stats = new QwtPlotMarker();
534 Stats->setLineStyle(QwtPlotMarker::NoLine);
535 Stats->setLabelAlignment(Qt::AlignLeft | Qt::AlignBottom);
536 Stats->hide();
537 Stats->attach(this);
538
539 // Context menu
540 Menu = new QMenu(this);
541 StripAction = Menu->addAction("Stripchart", this, SLOT(UpdatePlot()));
542 StripAction->setCheckable(true);
543 Menu->addSeparator();
544 YLogAction = Menu->addAction("y scale log", this, SLOT(UpdatePlot()));
545 YLogAction->setCheckable(true);
546 NormAction = Menu->addAction("Normalize", this, SLOT(UpdatePlot()));
547 NormAction->setCheckable(true);
548 StyleAction = Menu->addAction("Draw dots", this, SLOT(UpdatePlot()));
549 StyleAction->setCheckable(true);
550 StatisticsAction = Menu->addAction("Statistics", this, SLOT(ReDoStats()));
551 StatisticsAction->setCheckable(true);
552 Menu->addAction("Zoom out", this, SLOT(MenuZoomOut()));
553 Menu->addAction("Single trace", this, SLOT(MenuSingleTrace()));
554 Menu->addSeparator();
555 Menu->addAction("Set update rate", this, SLOT(MenuSetUpdateRate()));
556 Menu->addSeparator();
557 Menu->addAction("Save as ASCII", this, SLOT(MenuSaveASCII()));
558 Menu->addAction("Save plot", this, SLOT(MenuSave()));
559 Menu->addAction("Print plot", this, SLOT(MenuPrint()));
560 Menu->addAction("Plot help", this, SLOT(MenuPlotHelp()));
561
562 // Set timer to regularly update plot if new data available
563 Timer = new QTimer(this);
564 connect(Timer, SIGNAL(timeout()), this, SLOT(UpdatePlot()));
565 Timer->start(2000);
566}
567
568// Destructor (items with parent widget are automatically deleted)
569EddBasePlot::~EddBasePlot() {
570
571 for (int i=0; i<Items.size(); i++) delete Items[i].Signal;
572 delete Grid;
573}
574
575// Print statistics to plot if requested
576void EddBasePlot::ReDoStats() {
577
578 QString Text;
579 double Mean, Sigma;
580 unsigned long Count = 0;
581
582 // Set visibility of statistics box
583 Stats->setVisible(StatisticsAction->isChecked());
584
585 for (int i=0; i<Items.size(); i++) {
586 Mean = 0;
587 Sigma = 0;
588 Count = 0;
589
590 // Calculate mean and sigma for data points currently visible
591 for (int j=0; j<Items[i].y.size(); j++) {
592 if (!axisScaleDiv(QwtPlot::xBottom)->contains(Items[i].x[j])) continue;
593 Mean += Items[i].y[j];
594 Sigma += Items[i].y[j]*Items[i].y[j];
595 Count++;
596 }
597
598 if (Count == 0) Mean = 0;
599 else Mean /= Count;
600 if (Count < 2) Sigma = 0;
601 else Sigma = sqrt(Count/(Count-1)*(Sigma/Count-Mean*Mean));
602
603 // Prepare string
604 Text += Items[i].Signal->title().text() + ": m " + QString::number(Mean, 'f', 2) + ", s " + QString::number(Sigma, 'f', 2) + "\n";
605 }
606
607 // Replot once to get axis correct
608 replot();
609
610 // Print string to plot
611 QwtText text(Text);
612 text.setFont(QFont("Helvetica", 8));
613 Stats->setValue(axisScaleDiv(QwtPlot::xBottom)->upperBound(), axisScaleDiv(QwtPlot::yLeft)->upperBound());
614 Stats->setLabel(text);
615
616 // Replot again to update text
617 replot();
618}
619
620// Update all curves in plot
621void EddBasePlot::UpdatePlot() {
622
623 double Lower = axisScaleDiv(QwtPlot::xBottom)->lowerBound();
624 double Upper = axisScaleDiv(QwtPlot::xBottom)->upperBound();
625 double MaxTime = DBL_MIN;
626 static QwtSymbol Symbol, Sym1;
627 Symbol.setStyle(QwtSymbol::Ellipse);
628 Symbol.setSize(4);
629
630 // Only update if called by timer if new data is available
631 if (sender() == Timer && !NewData) return;
632 NewData = false;
633
634 // Select engine for linear or logarithmic scale
635 if (!YLogAction->isChecked()) {
636 setAxisScaleEngine(QwtPlot::yLeft, new QwtLinearScaleEngine);
637 }
638 else setAxisScaleEngine(QwtPlot::yLeft, new QwtLog10ScaleEngine);
639
640 for (int ItemNo=0; ItemNo<Items.size(); ItemNo++) {
641 // Determine current maximum value for strip chart plotting
642 if (Items[ItemNo].Signal->boundingRect().right() > MaxTime) {
643 MaxTime = Items[ItemNo].Signal->boundingRect().right();
644 }
645
646 // Set symbol if requested
647 if (StyleAction->isChecked()) Items[ItemNo].Signal->setSymbol(Symbol);
648 else Items[ItemNo].Signal->setSymbol(Sym1);
649
650 // Determine number of data points
651 int DataPoints = Items[ItemNo].x.size();
652 if (DataPoints == 0) continue;
653
654 // Normalize y scale if requested
655 double *y = new double [DataPoints];
656 for (int i=0; i<DataPoints; i++) {
657 y[i] = Items[ItemNo].y[i];
658
659 if (NormAction->isChecked()) {
660 if (Items[ItemNo].Smallest != Items[ItemNo].Largest) {
661 y[i] = (y[i] - Items[ItemNo].Smallest)/(Items[ItemNo].Largest-Items[ItemNo].Smallest);
662 }
663 else y[i] = 1;
664 }
665 }
666
667 // Plot data
668 Items[ItemNo].Signal->setData(Items[ItemNo].x.data(), y, DataPoints);
669 Items[ItemNo].Signal->show();
670 delete[] y;
671
672 // Generate bounding box of all curves for zoomer
673 BBox = BBox.unite(Items[ItemNo].Signal->boundingRect());
674 }
675
676 // Reset zoom base to include all data
677 Zoomer->setZoomBase(BBox);
678
679 // If plot is strip char, only move axis but keep range
680 if (StripAction->isChecked()) {
681 setCanvasBackground(EddPlotBackgroundColor.lighter(90));
682 setAxisScale(QwtPlot::xBottom, Lower+ BBox.right() - MaxTime, Upper + BBox.right() - MaxTime);
683 }
684 else setCanvasBackground(EddPlotBackgroundColor);
685
686 ReDoStats();
687}
688
689// Append curve to plot
690QwtPlotCurve *EddBasePlot::NewCurve(QwtText Title) {
691
692 static Qt::GlobalColor LineColors[] = {Qt::black, Qt::blue, Qt::red, Qt::green, Qt::white,
693 Qt::darkRed, Qt::darkGreen, Qt::darkBlue, Qt::cyan, Qt::darkCyan, Qt::magenta, Qt::darkMagenta,
694 Qt::gray, Qt::darkGray, Qt::lightGray};
695 struct PlotItem N;
696
697 N.Signal = new QwtPlotCurve;
698 N.Signal->attach(this);
699 N.Signal->setTitle(Title);
700 N.Signal->setPen(QColor(LineColors[Items.size() % (sizeof(LineColors)/sizeof(Qt::GlobalColor))]));
701 N.Largest = DBL_MIN;
702 N.Smallest = DBL_MAX;
703 Items.append(N);
704
705 return N.Signal;
706}
707
708// Clear curve data
709void EddBasePlot::ClearCurve(unsigned int Item) {
710
711 if (Item >= (unsigned int) Items.size()) return;
712
713 Items[Item].x.clear();
714 Items[Item].y.clear();
715}
716
717// Append data point
718void EddBasePlot::AddPoint(unsigned int Item, double x, double y) {
719
720 if (Item >= (unsigned int) Items.size()) return;
721
722 Items[Item].x.append(x);
723 Items[Item].y.append(y);
724
725 if (y > Items[Item].Largest) Items[Item].Largest = y;
726 if (y < Items[Item].Smallest) Items[Item].Smallest = y;
727}
728
729// Rescale plot in case selection has been made outside the canvas
730void EddBasePlot::MouseSelection(const QPolygon &P) {
731
732 QwtDoubleInterval xPlot, xMouse, yPlot, yMouse;
733
734 // Shift selected rectangle so that upper left corner is 0/0 on canvas
735 QRect R = P.boundingRect().translated(-plotLayout()->canvasRect().topLeft());
736
737 // Current axis intervals
738 xPlot = axisScaleDiv(QwtPlot::xBottom)->interval();
739 yPlot = axisScaleDiv(QwtPlot::yLeft)->interval();
740
741 // Selected axis intervals
742 xMouse = QwtDoubleInterval(invTransform(QwtPlot::xBottom, R.left()),
743 invTransform(QwtPlot::xBottom, R.right()));
744 yMouse = QwtDoubleInterval(invTransform(QwtPlot::yLeft, R.bottom()),
745 invTransform(QwtPlot::yLeft, R.top()));
746
747 // Selection region outside all axis?
748 if (R.right() < 0 && R.top() > plotLayout()->canvasRect().height()) return;
749
750 // Rescale both axis if selected rectangle encompasses canvas completely
751 if (P.boundingRect().contains(plotLayout()->canvasRect())) {
752 yMouse.setMaxValue(yMouse.maxValue() + yPlot.width());
753 yMouse.setMinValue(yMouse.minValue() - yPlot.width());
754 xMouse.setMinValue(xMouse.minValue() - xPlot.width());
755 xMouse.setMaxValue(xMouse.maxValue() + xPlot.width());
756
757 setAxisScale(QwtPlot::xBottom, xMouse.minValue(), xMouse.maxValue());
758 setAxisScale(QwtPlot::yLeft, yMouse.minValue(), yMouse.maxValue());
759 }
760
761 // Rescale y axis (increase range if selected rectangle larger than axis area)
762 if (R.right() < 0) {
763 if (yMouse.maxValue() > axisScaleDiv(QwtPlot::yLeft)->upperBound()) {
764 yMouse.setMaxValue(yMouse.maxValue() + yPlot.width());
765 }
766 if (yMouse.minValue() < axisScaleDiv(QwtPlot::yLeft)->lowerBound()) {
767 yMouse.setMinValue(yMouse.minValue() - yPlot.width());
768 }
769
770 setAxisScale(QwtPlot::yLeft, yMouse.minValue(), yMouse.maxValue());
771 }
772
773 // Rescale x axis (increase range if selected rectangle larger than axis area)
774 if (R.top() > plotLayout()->canvasRect().height()) {
775 if (xMouse.maxValue() > axisScaleDiv(QwtPlot::xBottom)->upperBound()) {
776 xMouse.setMaxValue(xMouse.maxValue() + xPlot.width());
777 }
778 if (xMouse.minValue() < axisScaleDiv(QwtPlot::xBottom)->lowerBound()) {
779 xMouse.setMinValue(xMouse.minValue() - xPlot.width());
780 }
781
782 setAxisScale(QwtPlot::xBottom, xMouse.minValue(), xMouse.maxValue());
783 }
784
785 // Draw new scales
786 replot();
787}
788
789// Reset graph axes to autoscale when fully unzoomed
790void EddBasePlot::HandleZoom(const QwtDoubleRect &) {
791
792 if(Zoomer->zoomRectIndex() == 0) {
793 setAxisAutoScale(QwtPlot::xBottom);
794 setAxisAutoScale(QwtPlot::yLeft);
795 }
796
797 UpdatePlot();
798}
799
800// Opening context menu
801void EddBasePlot::contextMenuEvent(QContextMenuEvent *Event) {
802
803 Menu->exec(Event->globalPos());
804}
805
806// Zoom completely out
807void EddBasePlot::MenuZoomOut() {
808
809 Zoomer->zoom(0);
810 UpdatePlot();
811}
812
813// Remove all items except one
814void EddBasePlot::MenuSingleTrace() {
815
816 while (Items.size() > 1) {
817 DeleteCurve(Items.last().Signal);
818 delete Items.last().Signal;
819 Items.takeLast();
820 }
821 UpdatePlot();
822}
823
824// Set maximum update rate of plot
825void EddBasePlot::MenuSetUpdateRate() {
826
827 bool OK;
828 double Rate;
829
830 Rate = QInputDialog::getDouble(this, "Edd Request",
831 "Minimum period between plot updates (sec)", (double) Timer->interval()/1000, 0, std::numeric_limits<double>::max(), 2, &OK);
832 if (OK) Timer->setInterval(Rate*1000);
833}
834
835// Save data of plot as test
836void EddBasePlot::MenuSaveASCII() {
837 QString Filename = QFileDialog::getSaveFileName(this,
838 "Filename", ".", "Text files (*.txt *.ascii *.asc);;All files (*)");
839 if (Filename.length() <= 0) return;
840
841 QFile File(Filename);
842 if (!File.open(QFile::WriteOnly | QIODevice::Text | QFile::Truncate)) {
843 QMessageBox::warning(this, "Edd Message","Could not open file for writing.",QMessageBox::Ok);
844 return;
845 }
846
847 // Write x and y data for all signals to file
848 QTextStream Stream(&File);
849
850 for (int ItemNo=0; ItemNo<Items.size(); ItemNo++) {
851 Stream << QString("# ") + Items[ItemNo].Signal->title().text() + ".hist" << endl;
852 for (int i=0; i<Items[ItemNo].Signal->dataSize(); i++) {
853 Stream << Items[ItemNo].x.at(i) << " " << Items[ItemNo].Signal->y(i) << endl;
854 }
855 }
856}
857
858// Print plot
859void EddBasePlot::MenuPrint() {
860
861 QPrinter *Printer = new QPrinter;
862 QPrintDialog *PrintDialog = new QPrintDialog(Printer, this);
863 if (PrintDialog->exec() == QDialog::Accepted) {
864 QPainter Painter(Printer);
865 QPixmap Pixmap = QPixmap::grabWidget(this);
866 Painter.drawPixmap(0, 0, Pixmap);
867 }
868 delete Printer; delete PrintDialog;
869}
870
871// Save plot as image
872void EddBasePlot::MenuSave() {
873
874 QString Filename = QFileDialog::getSaveFileName(this,
875 "Filename of image", "/home/ogrimm/ddd", "Image files (*.bmp *.jpg *.png *.ppm *.tiff *.xbm *.xpm);;All files (*)");
876 if (Filename.length()>0) {
877 QPixmap Pixmap = QPixmap::grabWidget(this);
878 if(!Pixmap.save(Filename)) {
879 QMessageBox::warning(this, "Edd Message","Could not write image file.",QMessageBox::Ok);
880 remove(Filename.toAscii().data());
881 }
882 }
883}
884
885// Help text
886void EddBasePlot::MenuPlotHelp() {
887
888 QMessageBox::about(this, "Edd - Plot help",
889 "Zoom\tMouse wheel\n"
890 "\tKeys m and shift-m\n"
891 "\tSelecting region with left mouse button\n"
892 "\tMiddle button zooms out one level\n"
893 "\tSelecting a range on an axis\n"
894 "\tSelecting whole canvas\n\n"
895 "Pan\tShift and left mouse button\n\n"
896 "ESC cancels selection\n"
897 "Cursor keys move mouse\n\n"
898 "Statistics are calculated over the current x axis extend\n\n"
899 "Items can be added to history plot by drag&drop from\n"
900 "DIM service displays or from other plots legends");
901}
902
903//////////////////////////////////////
904// History text box for DIM service //
905//////////////////////////////////////
906
907// Constructor
908EddText::EddText(QString Name, bool Pure, QWidget *P):
909 QTextEdit(P), Name(Name), Pure(Pure) {
910
911 // Widget properties
912 setReadOnly(true);
913 setAttribute(Qt::WA_DeleteOnClose);
914 setAutoFillBackground(true);
915 setText("connecting...");
916 document()->setMaximumBlockCount(1000);
917 Accumulate = true;
918
919 if (!Pure) {
920 // Get history for this service
921 const struct EvidenceHistory::Item *R;
922 class EvidenceHistory *Hist;
923
924 if ((Hist = Handler->GetHistory(Name)) != NULL) {
925 while ((R=Hist->Next()) != NULL) {
926 moveCursor (QTextCursor::Start);
927 insertPlainText(QString("(")+QDateTime::fromTime_t(R->Time).toString()+") " +
928 QString::fromStdString(EvidenceServer::ToString(Hist->GetFormat(), (void *) R->Data, R->Size)) + "\n");
929 }
930 }
931 Handler->DropHistory(Name);
932 }
933
934 // DIM client
935 Handler->Subscribe(Name, this);
936}
937
938// Destructor
939EddText::~EddText() {
940
941 Handler->Unsubscribe(Name, this);
942}
943
944
945// Update widget (must happen in GUI thread)
946void EddText::Update(const QString &, int Time, const QByteArray &, const QString &Format, const QString &Text, int) {
947
948 QPalette Pal = palette();
949
950 // Check if service available
951 if (!SetStatus(this, Name, Time, Format)) {
952 Pal.setColor(QPalette::Base, Qt::lightGray);
953 setPalette(Pal);
954 setText("n/a");
955 return;
956 }
957
958 Pal.setColor(QPalette::Base, Qt::white);
959 setPalette(Pal);
960 QDateTime Timex = QDateTime::fromTime_t(Time);
961
962 // Clear display in case text should not accumulate
963 if (Accumulate == false) clear();
964
965 if (!Pure) {
966 moveCursor(QTextCursor::Start);
967 insertPlainText(QString("(")+Timex.toString()+QString(") "));
968 insertPlainText(Text + "\n");
969 }
970 else if (Format == "C") insertPlainText(Text);
971}
972
973
974/////////////////////////////
975// Interface to Dim system //
976/////////////////////////////
977EddDim::EddDim() {
978
979 Volume = 0;
980 Period = 10;
981 Mutex = new QMutex(QMutex::Recursive);
982
983 // Timer to calculate data rates
984 QTimer *Timer = new QTimer(this);
985 Timer->connect(Timer, SIGNAL(timeout()), this, SLOT(UpdateStatistics()));
986 Timer->start(Period*1000);
987
988 // Connect to DIM handler
989 if (connect(this, SIGNAL(INT(QString, int, QByteArray, QString)), SLOT(Update(QString, int, QByteArray, QString))) == false) {
990 printf("Failed connection in EddDim()\n");
991 }
992}
993
994// Destructor
995EddDim::~EddDim() {
996
997 QList<QString> L = HistoryList.keys();
998 for(int i=0; i<L.size(); i++) delete HistoryList[L[i]].HistClass;
999 delete Mutex;
1000}
1001
1002// Subscribe to DIM service
1003void EddDim::Subscribe(QString Name, class EddWidget *Instance, int Index) {
1004
1005 // Lock before accessing list
1006 QMutexLocker Locker(Mutex);
1007
1008 // Check if already subscribed to service
1009 if (ServiceList.contains(Name)) {
1010 ServiceList[Name].Subscribers.append(QPair<class EddWidget *, int>(Instance, Index));
1011
1012 if (Index>=0 && Index<ServiceList[Name].Items.size()) {
1013 Instance->Update(Name, ServiceList[Name].TimeStamp, ServiceList[Name].ByteArray, ServiceList[Name].Format, ServiceList[Name].Items[Index]);
1014 }
1015 else Instance->Update(Name, ServiceList[Name].TimeStamp, ServiceList[Name].ByteArray, ServiceList[Name].Format, ServiceList[Name].Text);
1016
1017 return;
1018 }
1019
1020 // Create new entry in service list
1021 ServiceList[Name].ByteArray = QByteArray();
1022 ServiceList[Name].TimeStamp = -1;
1023 ServiceList[Name].Subscribers.append(QPair<class EddWidget *, int>(Instance, Index));
1024 ServiceList[Name].DIMService = new DimStampedInfo(Name.toAscii().data(), INT_MAX, NO_LINK, this);
1025}
1026
1027
1028// Unsubscribe from DIM service
1029void EddDim::Unsubscribe(QString Name, class EddWidget *Instance, int Index) {
1030
1031 // Lock before accessing list
1032 QMutexLocker Locker(Mutex);
1033
1034 if (!ServiceList.contains(Name)) return;
1035
1036 QPair<class EddWidget *, int> P(Instance, Index);
1037
1038 if (ServiceList[Name].Subscribers.contains(P)) {
1039 ServiceList[Name].Subscribers.removeAt(ServiceList[Name].Subscribers.indexOf(P));
1040 }
1041
1042 // If no more needed, drop DIM subsription
1043 if (ServiceList[Name].Subscribers.isEmpty()) {
1044 delete ServiceList[Name].DIMService;
1045 ServiceList.remove(Name);
1046 }
1047}
1048
1049// Ignore service in update
1050void EddDim::Ignore(QString Name, bool Ignore) {
1051
1052 QMutexLocker Locker(&IgnoreMutex);
1053
1054 IgnoreMap[Name] = Ignore;
1055}
1056
1057// Get history buffer
1058class EvidenceHistory *EddDim::GetHistory(QString Name) {
1059
1060 // History already available (only request again if too old)
1061 if (HistoryList.contains(Name)) {
1062 HistoryList[Name].Count++;
1063
1064 if (time(NULL)-HistoryList[Name].LastUpdate < 5) {
1065 HistoryList[Name].HistClass->Rewind();
1066 return HistoryList[Name].HistClass;
1067 }
1068 HistoryList[Name].LastUpdate = time(NULL);
1069 if (HistoryList[Name].HistClass->GetHistory()) return HistoryList[Name].HistClass;
1070 else return NULL;
1071 }
1072
1073 // Create new history class
1074 HistoryList[Name].HistClass = new EvidenceHistory(Name.toStdString());
1075 HistoryList[Name].Count = 1;
1076 HistoryList[Name].LastUpdate = time(NULL);
1077
1078 if (HistoryList[Name].HistClass->GetHistory()) return HistoryList[Name].HistClass;
1079 else return NULL;
1080}
1081
1082// Reduce history usage counter
1083void EddDim::DropHistory(QString Name) {
1084
1085 if (HistoryList.contains(Name)) HistoryList[Name].Count--;
1086}
1087
1088// Update throughput statistics and clear up history memory
1089void EddDim::UpdateStatistics() {
1090
1091 // Lock before accessing internal variables
1092 QMutexLocker Locker(Mutex);
1093
1094 // Remove unused histories after not less than 5 seconds
1095 QList<QString> L = HistoryList.keys();
1096 for(int i=0; i<L.size(); i++) {
1097 if ((HistoryList[L[i]].Count <= 0) && (time(NULL)-HistoryList[L[i]].LastUpdate) > 5) {
1098 delete HistoryList[L[i]].HistClass;
1099 HistoryList.remove(L[i]);
1100 }
1101 }
1102
1103 float Rate = Volume/1024.0/Period;
1104 Volume = 0;
1105
1106 // No unlock because Mutex is recursive
1107 Update("Edd/Rate_kBSec", time(NULL), QByteArray((char *) &Rate, sizeof(Rate)), "F");
1108}
1109
1110
1111// Store service information for usage by Subscribe(), update statistics and emit signal to widgets
1112void EddDim::Update(QString Name, int Time, QByteArray Data, QString Format) {
1113
1114 // Lock before accessing list
1115 QMutexLocker Locker(Mutex);
1116
1117 Volume += Data.size();
1118
1119 // Store service data and update all subscribers
1120 if (ServiceList.contains(Name)) {
1121 ServiceList[Name].TimeStamp = Time;
1122 ServiceList[Name].ByteArray = Data;
1123 ServiceList[Name].Format = Format;
1124 ServiceList[Name].Text = QString::fromStdString(EvidenceServer::ToString(Format.toAscii().data(), Data.data(), Data.size()));
1125 ServiceList[Name].Items = ServiceList[Name].Text.split(" ");
1126
1127 for (int i=0; i<ServiceList[Name].Subscribers.size(); i++) {
1128 QPair<class EddWidget *, int> P = ServiceList[Name].Subscribers[i];
1129
1130 if (P.second >=0 && P.second < ServiceList[Name].Items.size()) {
1131 P.first->Update(Name, Time, Data, Format, ServiceList[Name].Items[P.second], P.second);
1132 }
1133 else P.first->Update(Name, Time, Data, Format, ServiceList[Name].Text, P.second);
1134 }
1135 }
1136}
1137
1138// Handling of DIM service update (Data asynchronouly send to EddDim::Update())
1139void EddDim::infoHandler() {
1140
1141 QMutexLocker Locker(&IgnoreMutex);
1142 bool Ignore = IgnoreMap[getInfo()->getName()];
1143 Locker.unlock();
1144
1145 if (!EvidenceServer::ServiceOK(getInfo())) INT(getInfo()->getName(), -1);
1146 else if (!Ignore) INT(getInfo()->getName(), getInfo()->getTimestamp(), QByteArray((char *) getInfo()->getData(), getInfo()->getSize()), getInfo()->getFormat());
1147}
1148
1149
1150/////////////////////
1151// Open new window //
1152/////////////////////
1153
1154// Constructor
1155EddWindow::EddWindow(QString ButtonName, QString WindowName): QPushButton(ButtonName) {
1156
1157 M = new QMainWindow;
1158 M->setCentralWidget(new QWidget);
1159 M->setStatusBar(new QStatusBar(M));
1160 M->setWindowTitle(WindowName);
1161 L = new QGridLayout(M->centralWidget());
1162
1163 connect(this, SIGNAL(pressed()), M, SLOT(show()));
1164 connect(this, SIGNAL(pressed()), M, SLOT(raise()));
1165}
1166
1167// Return layout
1168QGridLayout *EddWindow::Layout() {
1169
1170 return L;
1171}
1172
1173// Return window
1174QMainWindow *EddWindow::Window() {
1175
1176 return M;
1177}
Note: See TracBrowser for help on using the repository browser.