source: fact/Evidence/GUI.cc@ 12910

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