source: fact/Evidence/GUI.cc@ 14106

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