source: fact/Evidence/GUI.cc@ 13155

Last change on this file since 13155 was 12942, checked in by ogrimm, 13 years ago
GUI bug fix for negative array index
File size: 36.7 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 AddPoint(List.size()-1, Hist.DataText[i].first, Hist.DataText[i].second.at(N.Index).toFloat());
394 }
395
396 // Subscribe to service and start updating plot after 100 ms (to allow addition of other curves)
397 Handler->Subscribe(Name, this, Index);
398 SingleShot->start(100);
399}
400
401// Update widget (must happen in GUI thread)
402void EddPlot::Update(const QString &Name, int Time, const QByteArray &, const QString &Format, const QString &Text, int Index) {
403
404 for (int ItemNo=0; ItemNo<List.size(); ItemNo++) if (List[ItemNo].Name==Name && List[ItemNo].Index==Index) {
405
406 // Append data if service available
407 if (SetStatus(this, Name, Time, Format)) AddPoint(ItemNo, Time, atof(Text.toAscii().data()));
408 NewData = true;
409 }
410}
411
412// Add text indicating time range to plot
413void EddPlot::paintEvent(QPaintEvent *) {
414
415 QString Text;
416 QFont Font;
417 QPainter Painter(this);
418
419 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");
420
421 Font.setPointSize(6);
422 Painter.setFont(Font);
423 Painter.drawText(0, height(), Text);
424}
425
426// Drag and drop methods
427void EddPlot::dragEnterEvent(QDragEnterEvent *Event) {
428
429 if (Event->mimeData()->hasFormat("Edd/Service")) Event->acceptProposedAction();
430}
431
432void EddPlot::dropEvent(QDropEvent *Event) {
433
434 QByteArray D(Event->mimeData()->data("Edd/Service"));
435 AddService(D.left(D.lastIndexOf(' ')), D.right(D.size()-D.lastIndexOf(' ')).toInt());
436}
437
438// Add new service by pasting name
439void EddPlot::MenuPasteService() {
440
441 const QMimeData *D = QApplication::clipboard()->mimeData();
442 if (!D->hasFormat("Edd/Service")) return;
443
444 QByteArray E(D->data("Edd/Service"));
445 AddService(E.left(E.lastIndexOf(' ')), E.right(E.size()-E.lastIndexOf(' ')).toInt());
446}
447
448// Show last hour/last day
449void EddPlot::MenuShowLastHour() {
450
451 setAxisScale(QwtPlot::xBottom, time(NULL)-60*60, time(NULL)+60);
452 replot();
453}
454
455void EddPlot::MenuShowLastDay() {
456
457 setAxisScale(QwtPlot::xBottom, time(NULL)-24*3600, time(NULL)+3600);
458 replot();
459}
460
461void EddPlot::MenuAllAsText() {
462
463 QMainWindow *M = new QMainWindow;
464 M->setCentralWidget(new QWidget(M));
465 M->setStatusBar(new QStatusBar(M));
466 M->setAttribute(Qt::WA_DeleteOnClose);
467 M->setWindowTitle("Edd Services");
468
469 QGridLayout *Layout = new QGridLayout(M->centralWidget());
470
471 for (int i=0; i<List.size(); i++) {
472 Layout->addWidget(new EddLineDisplay(List[i].Name, List[i].Index), i/10, i%10);
473 }
474
475 M->resize(400,450);
476 M->show();
477}
478
479
480// Remove subscription
481void EddPlot::RemoveService(QwtPlotCurve *Curve) {
482
483 for (int i=0; i<List.size(); i++) if (List[i].Curve == Curve) {
484 Handler->Unsubscribe(List[i].Name, this, List[i].Index);
485 List.removeAt(i);
486 DeleteCurve(Curve);
487 break;
488 }
489}
490
491
492//////////////////
493// General plot //
494//////////////////
495
496// Constructor (all slots called in GUI thread, therefore no mutex necessary)
497EddBasePlot::EddBasePlot(QWidget *P): QwtPlot(P) {
498
499 // Widget properties
500 setAttribute(Qt::WA_DeleteOnClose);
501 setAutoReplot(false);
502 setCanvasBackground(EddPlotBackgroundColor);
503 setMargin(15);
504 NewData = false;
505
506 // Plot navigation
507 Zoomer = new QwtPlotZoomer(QwtPlot::xBottom,QwtPlot::yLeft,canvas());
508 connect(Zoomer, SIGNAL(zoomed(const QwtDoubleRect &)), this, SLOT(HandleZoom(const QwtDoubleRect &)));
509 connect(Zoomer, SIGNAL(zoomed(const QwtDoubleRect &)), this, SLOT(ReDoStats()));
510
511 Magnifier = new QwtPlotMagnifier(canvas());
512 Magnifier->setMouseButton(Qt::NoButton,Qt::NoButton);
513 Magnifier->setZoomInKey(Qt::Key_M, Qt::NoModifier);
514 Magnifier->setZoomOutKey(Qt::Key_M, Qt::ShiftModifier);
515
516 Panner = new QwtPlotPanner(canvas());
517 Panner->setMouseButton(Qt::LeftButton, Qt::ShiftModifier);
518 connect(Panner, SIGNAL(panned(int, int)), this, SLOT(ReDoStats()));
519
520 Picker = new QwtPicker(QwtPicker::CornerToCorner|QwtPicker::RectSelection, QwtPicker::RectRubberBand, QwtPicker::AlwaysOff, this);
521 connect(Picker, SIGNAL(selected(const QwtPolygon &)), SLOT(MouseSelection(const QwtPolygon &)));
522
523 // Grid and legend
524 Grid = new QwtPlotGrid;
525 Grid->setMajPen(QPen(Qt::gray, 0, Qt::DotLine));
526 Grid->attach(this);
527
528 insertLegend(new QwtLegend(), QwtPlot::TopLegend, 0.3);
529
530 // Marker for statistics text
531 Stats = new QwtPlotMarker();
532 Stats->setLineStyle(QwtPlotMarker::NoLine);
533 Stats->setLabelAlignment(Qt::AlignLeft | Qt::AlignBottom);
534 Stats->hide();
535 Stats->attach(this);
536
537 // Context menu
538 Menu = new QMenu(this);
539 StripAction = Menu->addAction("Stripchart", this, SLOT(UpdatePlot()));
540 StripAction->setCheckable(true);
541 Menu->addSeparator();
542 YLogAction = Menu->addAction("y scale log", this, SLOT(UpdatePlot()));
543 YLogAction->setCheckable(true);
544 NormAction = Menu->addAction("Normalize", this, SLOT(UpdatePlot()));
545 NormAction->setCheckable(true);
546 StyleAction = Menu->addAction("Draw dots", this, SLOT(UpdatePlot()));
547 StyleAction->setCheckable(true);
548 StatisticsAction = Menu->addAction("Statistics", this, SLOT(ReDoStats()));
549 StatisticsAction->setCheckable(true);
550 Menu->addAction("Zoom out", this, SLOT(MenuZoomOut()));
551 Menu->addSeparator();
552 Menu->addAction("Set update rate", this, SLOT(MenuSetUpdateRate()));
553 Menu->addSeparator();
554 Menu->addAction("Save as ASCII", this, SLOT(MenuSaveASCII()));
555 Menu->addAction("Save plot", this, SLOT(MenuSave()));
556 Menu->addAction("Print plot", this, SLOT(MenuPrint()));
557 Menu->addAction("Plot help", this, SLOT(MenuPlotHelp()));
558
559 // Set timer to regularly update plot if new data available
560 Timer = new QTimer(this);
561 connect(Timer, SIGNAL(timeout()), this, SLOT(UpdatePlot()));
562 Timer->start(2000);
563}
564
565// Destructor (items with parent widget are automatically deleted)
566EddBasePlot::~EddBasePlot() {
567
568 delete Grid;
569}
570
571// Print statistics to plot if requested
572void EddBasePlot::ReDoStats() {
573
574 QString Text;
575 double Mean, Sigma;
576 unsigned long Count = 0;
577
578 // Set visibility of statistics box
579 Stats->setVisible(StatisticsAction->isChecked());
580
581 for (int i=0; i<Items.size(); i++) {
582 Mean = 0;
583 Sigma = 0;
584 Count = 0;
585
586 // Calculate mean and sigma for data points currently visible
587 for (int j=0; j<Items[i].y.size(); j++) {
588 if (!axisScaleDiv(QwtPlot::xBottom)->contains(Items[i].x[j])) continue;
589 Mean += Items[i].y[j];
590 Sigma += Items[i].y[j]*Items[i].y[j];
591 Count++;
592 }
593
594 if (Count == 0) Mean = 0;
595 else Mean /= Count;
596 if (Count < 2) Sigma = 0;
597 else Sigma = sqrt(Count/(Count-1)*(Sigma/Count-Mean*Mean));
598
599 // Prepare string
600 Text += Items[i].Signal->title().text() + ": m " + QString::number(Mean, 'f', 2) + ", s " + QString::number(Sigma, 'f', 2) + "\n";
601 }
602
603 // Replot once to get axis correct
604 replot();
605
606 // Print string to plot
607 QwtText text(Text);
608 text.setFont(QFont("Helvetica", 8));
609 Stats->setValue(axisScaleDiv(QwtPlot::xBottom)->upperBound(), axisScaleDiv(QwtPlot::yLeft)->upperBound());
610 Stats->setLabel(text);
611
612 // Replot again to update text
613 replot();
614}
615
616// Update all curves in plot
617void EddBasePlot::UpdatePlot() {
618
619 double Lower = axisScaleDiv(QwtPlot::xBottom)->lowerBound();
620 double Upper = axisScaleDiv(QwtPlot::xBottom)->upperBound();
621 double MaxTime = DBL_MIN;
622 static QwtSymbol Symbol, Sym1;
623 Symbol.setStyle(QwtSymbol::Ellipse);
624 Symbol.setSize(4);
625
626 // Only update if called by timer if new data is available
627 if (sender() == Timer && !NewData) return;
628 NewData = false;
629
630 // Select engine for linear or logarithmic scale
631 if (!YLogAction->isChecked()) {
632 setAxisScaleEngine(QwtPlot::yLeft, new QwtLinearScaleEngine);
633 }
634 else setAxisScaleEngine(QwtPlot::yLeft, new QwtLog10ScaleEngine);
635
636 for (int ItemNo=0; ItemNo<Items.size(); ItemNo++) {
637 // Determine current maximum value for strip chart plotting
638 if (Items[ItemNo].Signal->boundingRect().right() > MaxTime) {
639 MaxTime = Items[ItemNo].Signal->boundingRect().right();
640 }
641
642 // Set symbol if requested
643 if (StyleAction->isChecked()) Items[ItemNo].Signal->setSymbol(Symbol);
644 else Items[ItemNo].Signal->setSymbol(Sym1);
645
646 // Determine number of data points
647 int DataPoints = Items[ItemNo].x.size();
648 if (DataPoints == 0) continue;
649
650 // Normalize y scale if requested
651 double *y = new double [DataPoints];
652 for (int i=0; i<DataPoints; i++) {
653 y[i] = Items[ItemNo].y[i];
654
655 if (NormAction->isChecked()) {
656 if (Items[ItemNo].Smallest != Items[ItemNo].Largest) {
657 y[i] = (y[i] - Items[ItemNo].Smallest)/(Items[ItemNo].Largest-Items[ItemNo].Smallest);
658 }
659 else y[i] = 1;
660 }
661 }
662
663 // Plot data
664 Items[ItemNo].Signal->setData(Items[ItemNo].x.data(), y, DataPoints);
665 Items[ItemNo].Signal->show();
666 delete[] y;
667
668 // Generate bounding box of all curves for zoomer
669 BBox = BBox.unite(Items[ItemNo].Signal->boundingRect());
670 }
671
672 // Reset zoom base to include all data
673 Zoomer->setZoomBase(BBox);
674
675 // If plot is strip char, only move axis but keep range
676 if (StripAction->isChecked()) {
677 setCanvasBackground(EddPlotBackgroundColor.lighter(90));
678 setAxisScale(QwtPlot::xBottom, Lower+ BBox.right() - MaxTime, Upper + BBox.right() - MaxTime);
679 }
680 else setCanvasBackground(EddPlotBackgroundColor);
681
682 ReDoStats();
683}
684
685// Append curve to plot
686QwtPlotCurve *EddBasePlot::NewCurve(QwtText Title) {
687
688 static Qt::GlobalColor LineColors[] = {Qt::black, Qt::blue, Qt::red, Qt::green, Qt::white,
689 Qt::darkRed, Qt::darkGreen, Qt::darkBlue, Qt::cyan, Qt::darkCyan, Qt::magenta, Qt::darkMagenta,
690 Qt::gray, Qt::darkGray, Qt::lightGray};
691 struct PlotItem N;
692
693 N.Signal = new QwtPlotCurve;
694 N.Signal->attach(this);
695 N.Signal->setTitle(Title);
696 N.Signal->setPen(QColor(LineColors[Items.size() % (sizeof(LineColors)/sizeof(Qt::GlobalColor))]));
697 N.Largest = DBL_MIN;
698 N.Smallest = DBL_MAX;
699 Items.append(N);
700
701 return N.Signal;
702}
703
704// Clear curve data
705void EddBasePlot::ClearCurve(unsigned int Item) {
706
707 if (Item >= (unsigned int) Items.size()) return;
708
709 Items[Item].x.clear();
710 Items[Item].y.clear();
711}
712
713// Append data point
714void EddBasePlot::AddPoint(unsigned int Item, double x, double y) {
715
716 if (Item >= (unsigned int) Items.size()) return;
717
718 Items[Item].x.append(x);
719 Items[Item].y.append(y);
720
721 if (y > Items[Item].Largest) Items[Item].Largest = y;
722 if (y < Items[Item].Smallest) Items[Item].Smallest = y;
723}
724
725// Rescale plot in case selection has been made outside the canvas
726void EddBasePlot::MouseSelection(const QPolygon &P) {
727
728 QwtDoubleInterval xPlot, xMouse, yPlot, yMouse;
729
730 // Shift selected rectangle so that upper left corner is 0/0 on canvas
731 QRect R = P.boundingRect().translated(-plotLayout()->canvasRect().topLeft());
732
733 // Current axis intervals
734 xPlot = axisScaleDiv(QwtPlot::xBottom)->interval();
735 yPlot = axisScaleDiv(QwtPlot::yLeft)->interval();
736
737 // Selected axis intervals
738 xMouse = QwtDoubleInterval(invTransform(QwtPlot::xBottom, R.left()),
739 invTransform(QwtPlot::xBottom, R.right()));
740 yMouse = QwtDoubleInterval(invTransform(QwtPlot::yLeft, R.bottom()),
741 invTransform(QwtPlot::yLeft, R.top()));
742
743 // Selection region outside all axis?
744 if (R.right() < 0 && R.top() > plotLayout()->canvasRect().height()) return;
745
746 // If selected rectangle completely inside canvas taken care of by zoomer
747 if (plotLayout()->canvasRect().contains(P.boundingRect())) return;
748
749 // Rescale both axis if selected rectangle encompasses canvas completely
750 if (P.boundingRect().contains(plotLayout()->canvasRect())) {
751 yMouse.setMaxValue(yMouse.maxValue() + yPlot.width());
752 yMouse.setMinValue(yMouse.minValue() - yPlot.width());
753 xMouse.setMinValue(xMouse.minValue() - xPlot.width());
754 xMouse.setMaxValue(xMouse.maxValue() + xPlot.width());
755
756 setAxisScale(QwtPlot::xBottom, xMouse.minValue(), xMouse.maxValue());
757 setAxisScale(QwtPlot::yLeft, yMouse.minValue(), yMouse.maxValue());
758 }
759
760 // Rescale y axis (increase range if selected rectangle larger than axis area)
761 if (R.right() < 0) {
762 if (yMouse.maxValue() > axisScaleDiv(QwtPlot::yLeft)->upperBound()) {
763 yMouse.setMaxValue(yMouse.maxValue() + yPlot.width());
764 }
765 if (yMouse.minValue() < axisScaleDiv(QwtPlot::yLeft)->lowerBound()) {
766 yMouse.setMinValue(yMouse.minValue() - yPlot.width());
767 }
768
769 setAxisScale(QwtPlot::yLeft, yMouse.minValue(), yMouse.maxValue());
770 }
771
772 // Rescale x axis (increase range if selected rectangle larger than axis area)
773 if (R.top() > plotLayout()->canvasRect().height()) {
774 if (xMouse.maxValue() > axisScaleDiv(QwtPlot::xBottom)->upperBound()) {
775 xMouse.setMaxValue(xMouse.maxValue() + xPlot.width());
776 }
777 if (xMouse.minValue() < axisScaleDiv(QwtPlot::xBottom)->lowerBound()) {
778 xMouse.setMinValue(xMouse.minValue() - xPlot.width());
779 }
780
781 setAxisScale(QwtPlot::xBottom, xMouse.minValue(), xMouse.maxValue());
782 }
783
784 // Draw new scales
785 replot();
786}
787
788// Reset graph axes to autoscale when fully unzoomed
789void EddBasePlot::HandleZoom(const QwtDoubleRect &) {
790
791 if(Zoomer->zoomRectIndex() == 0) {
792 setAxisAutoScale(QwtPlot::xBottom);
793 setAxisAutoScale(QwtPlot::yLeft);
794 }
795
796 UpdatePlot();
797}
798
799// Opening context menu
800void EddBasePlot::contextMenuEvent(QContextMenuEvent *Event) {
801
802 Menu->exec(Event->globalPos());
803}
804
805// Zoom completely out
806void EddBasePlot::MenuZoomOut() {
807
808 Zoomer->zoom(0);
809 UpdatePlot();
810}
811
812
813// Remove item
814void EddBasePlot::DeleteCurve(QwtPlotCurve *Curve) {
815
816 for (int i=0; i<Items.size(); i++) if (Items[i].Signal == Curve) {
817 delete Curve;
818 Items.takeAt(i);
819 break;
820 }
821 UpdatePlot();
822}
823
824// Set maximum update rate of plot
825void EddBasePlot::MenuSetUpdateRate() {
826
827 bool OK;
828 double Rate;
829
830 Rate = QInputDialog::getDouble(this, "Edd Request",
831 "Minimum period between plot updates (sec)", (double) Timer->interval()/1000, 0, std::numeric_limits<double>::max(), 2, &OK);
832 if (OK) Timer->setInterval(Rate*1000);
833}
834
835// Save data of plot as test
836void EddBasePlot::MenuSaveASCII() {
837 QString Filename = QFileDialog::getSaveFileName(this,
838 "Filename", ".", "Text files (*.txt *.ascii *.asc);;All files (*)");
839 if (Filename.length() <= 0) return;
840
841 QFile File(Filename);
842 if (!File.open(QFile::WriteOnly | QIODevice::Text | QFile::Truncate)) {
843 QMessageBox::warning(this, "Edd Message","Could not open file for writing.",QMessageBox::Ok);
844 return;
845 }
846
847 // Write x and y data for all signals to file
848 QTextStream Stream(&File);
849
850 for (int ItemNo=0; ItemNo<Items.size(); ItemNo++) {
851 Stream << QString("# ") + Items[ItemNo].Signal->title().text() + ".hist" << endl;
852 for (int i=0; i<Items[ItemNo].Signal->dataSize(); i++) {
853 Stream << Items[ItemNo].x.at(i) << " " << Items[ItemNo].Signal->y(i) << endl;
854 }
855 }
856}
857
858// Print plot
859void EddBasePlot::MenuPrint() {
860
861 QPrinter *Printer = new QPrinter;
862 QPrintDialog *PrintDialog = new QPrintDialog(Printer, this);
863 if (PrintDialog->exec() == QDialog::Accepted) {
864 QPainter Painter(Printer);
865 QPixmap Pixmap = QPixmap::grabWidget(this);
866 Painter.drawPixmap(0, 0, Pixmap);
867 }
868 delete Printer; delete PrintDialog;
869}
870
871// Save plot as image
872void EddBasePlot::MenuSave() {
873
874 QString Filename = QFileDialog::getSaveFileName(this,
875 "Filename of image", "/home/ogrimm/ddd", "Image files (*.bmp *.jpg *.png *.ppm *.tiff *.xbm *.xpm);;All files (*)");
876 if (Filename.length()>0) {
877 QPixmap Pixmap = QPixmap::grabWidget(this);
878 if(!Pixmap.save(Filename)) {
879 QMessageBox::warning(this, "Edd Message","Could not write image file.",QMessageBox::Ok);
880 remove(Filename.toAscii().data());
881 }
882 }
883}
884
885// Help text
886void EddBasePlot::MenuPlotHelp() {
887
888 QMessageBox::about(this, "Edd - Plot help",
889 "Zoom\tMouse wheel\n"
890 "\tKeys m and shift-m\n"
891 "\tSelecting region with left mouse button\n"
892 "\tMiddle button zooms out one level\n"
893 "\tSelecting a range on an axis\n"
894 "\tSelecting whole canvas\n\n"
895 "Pan\tShift and left mouse button\n\n"
896 "ESC cancels selection\n"
897 "Cursor keys move mouse\n\n"
898 "Statistics are calculated over the current x axis extend\n\n"
899 "Items can be added to history plot by drag&drop from\n"
900 "DIM service displays or from other plots legends");
901}
902
903
904////////////////
905// Edd Legend //
906////////////////
907
908EddLegend::EddLegend(QwtPlotCurve *Curve, EddPlot *Plot): Curve(Curve), Plot(Plot) {
909
910 // Context menu
911 Menu = new QMenu(this);
912 Menu->addAction("Open in new history", this, SLOT(MenuOpenHistory()));
913 Menu->addAction("Copy service", this, SLOT(MenuCopyService()));
914 Menu->addAction("Normal line", this, SLOT(MenuNormalLine()));
915 Menu->addAction("Thick line", this, SLOT(MenuThickLine()));
916 Menu->addAction("Remove curve", this, SLOT(MenuRemove()));
917}
918
919//
920// Opening context menu
921//
922void EddLegend::contextMenuEvent(QContextMenuEvent *Event) {
923
924 Menu->exec(Event->globalPos());
925}
926
927// Handling of mouse press event: Register start position for drag
928void EddLegend::mousePressEvent(QMouseEvent *Event) {
929
930 if (Event->button() == Qt::LeftButton) dragStart = Event->pos();
931}
932
933// Handling of dragging (Drag and MimeData will be deleted by Qt)
934void EddLegend::mouseMoveEvent(QMouseEvent *Event) {
935
936 if ((Event->buttons() & Qt::LeftButton) == 0) return;
937 if ((Event->pos()-dragStart).manhattanLength() < QApplication::startDragDistance()) return;
938
939 QString D(text().text());
940 D.replace(':',' ');
941
942 QDrag *Drag = new QDrag(this);
943 QMimeData *MimeData = new QMimeData;
944 QByteArray Data;
945 MimeData->setData("Edd/Service", Data.append(D));
946 Drag->setMimeData(MimeData);
947 Drag->exec();
948}
949
950// Handling of mouse release event: Open history
951void EddLegend::mouseReleaseEvent(QMouseEvent *Event) {
952
953 if (Event->button() != Qt::LeftButton) return;
954
955 QString D(text().text());
956 D.replace(':',' ');
957 QStringList A = D.split(" ");
958
959 OpenHistory(A[0].toAscii().data(), A[1].toInt());
960}
961
962// Menu: Open history plot
963void EddLegend::MenuOpenHistory() {
964
965 QString D(text().text());
966 D.replace(':',' ');
967 QStringList A = D.split(" ");
968
969 OpenHistory(A[0].toAscii().data(), A[1].toInt());
970}
971
972// Menu: Copy service name
973void EddLegend::MenuCopyService() {
974
975 QString D(text().text());
976 D.replace(':',' ');
977
978 QMimeData *MimeData = new QMimeData;
979 QByteArray Data;
980 MimeData->setData("Edd/Service", Data.append(D));
981 QApplication::clipboard()->setMimeData(MimeData);
982}
983
984// Menu: Normal line width
985void EddLegend::MenuNormalLine() {
986
987 QPen Pen = Curve->pen();
988 Pen.setWidth(1);
989 Curve->setPen(Pen);
990 Curve->plot()->replot();
991}
992
993// Menu: Normal line width
994void EddLegend::MenuThickLine() {
995
996 QPen Pen = Curve->pen();
997 Pen.setWidth(4);
998 Curve->setPen(Pen);
999 Curve->plot()->replot();
1000}
1001
1002// Menu: Normal line width
1003void EddLegend::MenuRemove() {
1004
1005 emit(DeleteCurve(Curve));
1006}
1007
1008//////////////////////////////////////
1009// History text box for DIM service //
1010//////////////////////////////////////
1011
1012// Constructor
1013EddText::EddText(QString Name, bool Pure, QWidget *P):
1014 QTextEdit(P), Name(Name), Pure(Pure) {
1015
1016 // Widget properties
1017 setReadOnly(true);
1018 setAttribute(Qt::WA_DeleteOnClose);
1019 setAutoFillBackground(true);
1020 //setText("connecting...");
1021 document()->setMaximumBlockCount(1000);
1022 Accumulate = true;
1023
1024 // Get history for this service
1025 if (!Pure) {
1026 struct EddDim::HistItem Hist = Handler->GetHistory(Name);
1027
1028 for (int i=0; i<Hist.DataText.size(); i++) {
1029 moveCursor (QTextCursor::Start);
1030 insertPlainText(QString("(")+QDateTime::fromTime_t(Hist.DataText[i].first).toString()+") " +
1031 QString(Hist.DataRaw[i].second) + "\n");
1032 }
1033 }
1034
1035 // DIM client
1036 Handler->Subscribe(Name, this);
1037}
1038
1039// Destructor
1040EddText::~EddText() {
1041
1042 Handler->Unsubscribe(Name, this);
1043}
1044
1045
1046// Update widget (must happen in GUI thread)
1047void EddText::Update(const QString &, int Time, const QByteArray &, const QString &Format, const QString &Text, int) {
1048
1049 QPalette Pal = palette();
1050
1051 // Check if service available
1052 if (!SetStatus(this, Name, Time, Format)) {
1053 Pal.setColor(QPalette::Base, Qt::lightGray);
1054 setPalette(Pal);
1055 setText("n/a");
1056 return;
1057 }
1058
1059 Pal.setColor(QPalette::Base, Qt::white);
1060 setPalette(Pal);
1061 QDateTime Timex = QDateTime::fromTime_t(Time);
1062
1063 // Clear display in case text should not accumulate
1064 if (Accumulate == false) clear();
1065
1066 if (!Pure) {
1067 moveCursor(QTextCursor::Start);
1068 insertPlainText(QString("(")+Timex.toString()+QString(") "));
1069 insertPlainText(Text + "\n");
1070 }
1071 else if (Format == "C") insertPlainText(Text);
1072}
1073
1074
1075/////////////////////////////
1076// Interface to Dim system //
1077/////////////////////////////
1078EddDim::EddDim() {
1079
1080 Volume = 0;
1081 Period = 10;
1082 Mutex = new QMutex(QMutex::Recursive);
1083
1084 // Timer to calculate data rates
1085 QTimer *Timer = new QTimer(this);
1086 Timer->connect(Timer, SIGNAL(timeout()), this, SLOT(UpdateStatistics()));
1087 Timer->start(Period*1000);
1088
1089 // Connect to DIM handler
1090 if (connect(this, SIGNAL(INT(QString, int, QByteArray, QString)), SLOT(Update(QString, int, QByteArray, QString))) == false) {
1091 printf("Failed connection in EddDim()\n");
1092 }
1093}
1094
1095// Destructor
1096EddDim::~EddDim() {
1097
1098 delete Mutex;
1099}
1100
1101// Subscribe to DIM service
1102void EddDim::Subscribe(QString Name, class EddWidget *Instance, int Index) {
1103
1104 // Lock before accessing list
1105 QMutexLocker Locker(Mutex);
1106
1107 // Check if already subscribed to service
1108 if (ServiceList.contains(Name)) {
1109 ServiceList[Name].Subscribers.append(QPair<class EddWidget *, int>(Instance, Index));
1110
1111 if (Index>=0 && Index<ServiceList[Name].Items.size()) {
1112 Instance->Update(Name, ServiceList[Name].TimeStamp, ServiceList[Name].ByteArray, ServiceList[Name].Format, ServiceList[Name].Items[Index]);
1113 }
1114 else Instance->Update(Name, ServiceList[Name].TimeStamp, ServiceList[Name].ByteArray, ServiceList[Name].Format, ServiceList[Name].Text);
1115
1116 return;
1117 }
1118
1119 // Create new entry in service list
1120 ServiceList[Name].ByteArray = QByteArray();
1121 ServiceList[Name].TimeStamp = -1;
1122 ServiceList[Name].Subscribers.append(QPair<class EddWidget *, int>(Instance, Index));
1123 ServiceList[Name].DIMService = new DimStampedInfo(Name.toAscii().data(), INT_MAX, NO_LINK, this);
1124}
1125
1126
1127// Unsubscribe from DIM service
1128void EddDim::Unsubscribe(QString Name, class EddWidget *Instance, int Index) {
1129
1130 // Lock before accessing list
1131 QMutexLocker Locker(Mutex);
1132
1133 if (!ServiceList.contains(Name)) return;
1134
1135 QPair<class EddWidget *, int> P(Instance, Index);
1136
1137 if (ServiceList[Name].Subscribers.contains(P)) {
1138 ServiceList[Name].Subscribers.removeAt(ServiceList[Name].Subscribers.indexOf(P));
1139 }
1140
1141 // If no more needed, drop DIM subsription
1142 if (ServiceList[Name].Subscribers.isEmpty()) {
1143 delete ServiceList[Name].DIMService;
1144 ServiceList.remove(Name);
1145 }
1146}
1147
1148// Ignore service in update
1149void EddDim::Ignore(QString Name, bool Ignore) {
1150
1151 QMutexLocker Locker(&IgnoreMutex);
1152 IgnoreMap[Name] = Ignore;
1153}
1154
1155// Get history buffer
1156struct EddDim::HistItem EddDim::GetHistory(QString Name) {
1157
1158 // History already available?
1159 if (HistoryList.contains(Name)) return HistoryList[Name];
1160
1161 // Create history class to retrieve history data
1162 const struct EvidenceHistory::Item *R;
1163 class EvidenceHistory *Hist = new EvidenceHistory(Name.toStdString());
1164 Hist->GetHistory();
1165 HistoryList[Name].Time = time(NULL);
1166 HistoryList[Name].Format = Hist->GetFormat();
1167
1168 while ((R = Hist->Next()) != NULL) {
1169 HistoryList[Name].DataRaw.push_back(QPair<int, QByteArray>(R->Time, QByteArray(R->Data, R->Size)));
1170 HistoryList[Name].DataText.push_back(QPair<int, QStringList>(R->Time, QString::fromStdString(EvidenceServer::ToString(Hist->GetFormat(), R->Data, R->Size)).split(' ')));
1171 }
1172
1173 delete Hist;
1174
1175 return HistoryList[Name];
1176}
1177
1178
1179// Update throughput statistics and clear up history memory
1180void EddDim::UpdateStatistics() {
1181
1182 // Lock before accessing internal variables
1183 QMutexLocker Locker(Mutex);
1184
1185 // Remove unused histories after not less than 5 seconds
1186 QList<QString> L = HistoryList.keys();
1187 for(int i=0; i<L.size(); i++) {
1188 if ((time(NULL)-HistoryList[L[i]].Time) > 60) HistoryList.remove(L[i]);
1189 }
1190
1191 float Rate = Volume/1024.0/Period;
1192 Volume = 0;
1193
1194 // No unlock because Mutex is recursive
1195 Update("Edd/Rate_kBSec", time(NULL), QByteArray((char *) &Rate, sizeof(Rate)), "F");
1196}
1197
1198
1199// Store service information for usage by Subscribe(), update statistics and emit signal to widgets
1200void EddDim::Update(QString Name, int Time, QByteArray Data, QString Format) {
1201
1202 // Lock before accessing list
1203 QMutexLocker Locker(Mutex);
1204
1205 Volume += Data.size();
1206
1207 // Store service data and update all subscribers
1208 if (ServiceList.contains(Name)) {
1209 ServiceList[Name].TimeStamp = Time;
1210 ServiceList[Name].ByteArray = Data;
1211 ServiceList[Name].Format = Format;
1212 ServiceList[Name].Text = QString::fromStdString(EvidenceServer::ToString(Format.toAscii().data(), Data.data(), Data.size()));
1213 ServiceList[Name].Items = ServiceList[Name].Text.split(" ");
1214
1215 for (int i=0; i<ServiceList[Name].Subscribers.size(); i++) {
1216 QPair<class EddWidget *, int> P = ServiceList[Name].Subscribers[i];
1217
1218 if (P.second >=0 && P.second < ServiceList[Name].Items.size()) {
1219 P.first->Update(Name, Time, Data, Format, ServiceList[Name].Items[P.second], P.second);
1220 }
1221 else P.first->Update(Name, Time, Data, Format, ServiceList[Name].Text, P.second);
1222 }
1223 }
1224}
1225
1226// Handling of DIM service update (Data asynchronouly send to EddDim::Update())
1227void EddDim::infoHandler() {
1228
1229 QMutexLocker Locker(&IgnoreMutex);
1230 bool Ignore = IgnoreMap[getInfo()->getName()];
1231 Locker.unlock();
1232
1233 if (!EvidenceServer::ServiceOK(getInfo())) INT(getInfo()->getName(), -1);
1234 else if (!Ignore) INT(getInfo()->getName(), getInfo()->getTimestamp(), QByteArray((char *) getInfo()->getData(), getInfo()->getSize()), getInfo()->getFormat());
1235}
1236
1237
1238/////////////////////
1239// Open new window //
1240/////////////////////
1241
1242// Constructor
1243EddWindow::EddWindow(QString ButtonName, QString WindowName): QPushButton(ButtonName) {
1244
1245 M = new QMainWindow;
1246 M->setCentralWidget(new QWidget);
1247 M->setStatusBar(new QStatusBar(M));
1248 M->setWindowTitle(WindowName);
1249 L = new QGridLayout(M->centralWidget());
1250
1251 connect(this, SIGNAL(pressed()), M, SLOT(show()));
1252 connect(this, SIGNAL(pressed()), M, SLOT(raise()));
1253}
1254
1255// Return layout
1256QGridLayout *EddWindow::Layout() {
1257
1258 return L;
1259}
1260
1261// Return window
1262QMainWindow *EddWindow::Window() {
1263
1264 return M;
1265}
Note: See TracBrowser for help on using the repository browser.