source: fact/Evidence/GUI.cc@ 14604

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