source: fact/Evidence/GUI.cc@ 11088

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