source: fact/Evidence/GUI.cc@ 12018

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