/* ============================================================ Edd - Evidence Data Display Qt-based graphical user interface for the Evidence contron system EddLineDisplay changes its background colour in case it display a DIM status service April 2010, February 2012, Oliver Grimm ============================================================ */ #include "GUI.h" class EddDim *Handler; // // History chooser function (opens plot for numeric data, TextHist for all other) // void OpenHistory(char *Service, int FromIndex, int ToIndex) { QString Format; DimBrowser Browser; struct EddDim::HistItem Hist = Handler->GetHistory(Service); char *Name, *Fmt; // Check if history service available if (strcmp(Service, "Edd/Rate_kBSec") == 0) Format = "F"; else Format = Hist.Format; // If service currently available, take its format Browser.getServices(Service); if (Browser.getNextService(Name, Fmt) != 0) Format = QString(Fmt); // Open new window QMainWindow *M = new QMainWindow; M->setCentralWidget(new QWidget(M)); M->setStatusBar(new QStatusBar(M)); M->setAttribute(Qt::WA_DeleteOnClose); M->setWindowTitle("Edd History"); QGridLayout *Layout = new QGridLayout(M->centralWidget()); // If service ends with 'C' make text display, otherwise numeric plot if (Format.endsWith('C', Qt::CaseInsensitive)) Layout->addWidget(new EddText(Service), 0, 0); else { EddPlot *W = new EddPlot(Service, FromIndex); // If service is array and no index given, try to find out how many indices exist and add all if (FromIndex == -1 && Format.size() == 1) { DimCurrentInfo X(Service, NULL, 0); FromIndex = 0; ToIndex = EvidenceServer::Tokenize(EvidenceServer::ToString(Format.toAscii().data(), X.getData(), X.getSize()), " ").size() - 1; } // Add all requested indices for (int i=FromIndex+1; i<=ToIndex; i++) W->AddService(Service,i); Layout->addWidget(W, 0, 0, 1, 5); } M->resize(400,450); M->show(); } // // Set status tip (returns true if service was available) // bool SetStatus(QWidget *W, QString Name, int Time, QString Format, int Index) { QString Status; if (Index != -1) Name = Name + ":" + QString::number(Index); if (Time == -1) Status = QString("%1: unavailable").arg(Name); else Status = QString("%1: Last update %2 Format '%3'").arg(Name).arg(QDateTime::fromTime_t(Time).toString()).arg(Format); W->setStatusTip(Status); return(Time != -1); } ////////////////////////////////////////// // Text display for arbitary DIM service// ////////////////////////////////////////// EddLineDisplay::EddLineDisplay(QString Name, int Index, QWidget *P): QLineEdit(P), ServiceName(Name), Index(Index) { LastHist = NULL; // Widget properties setReadOnly(true); setMaximumWidth(100); ShowAsTime = false; setFrame(false); setAttribute(Qt::WA_DeleteOnClose); QPalette Pal = palette(); Pal.setColor(QPalette::Base, Qt::lightGray); setPalette(Pal); // Context menu Menu = new QMenu(this); Menu->addAction("Open new history", this, SLOT(MenuOpenHistory())); Menu->addAction("Copy service", this, SLOT(MenuCopyService())); Menu->addAction("Copy data", this, SLOT(MenuCopyData())); // Subscribe to service Handler->Subscribe(Name, this, Index); } // Destructor EddLineDisplay::~EddLineDisplay() { Handler->Unsubscribe(ServiceName, this, Index); } // Update widget void EddLineDisplay::Update(const QString &, int Time, const QByteArray &, const QString &Format, const QString &Text, int) { // Check if service available QPalette Pal = palette(); if (!SetStatus(this, ServiceName, Time, Format, Index)) { setText("n/a"); Pal.setColor(QPalette::Base, Qt::lightGray); setPalette(Pal); return; } else Pal.setColor(QPalette::Base, Qt::white); // Message service backgound colour determined by severity if (ServiceName.endsWith("/Message")) { switch (Text.section(' ', 0, 0).toInt()) { case 0: Pal.setColor(QPalette::Base, Qt::white); break; case 1: Pal.setColor(QPalette::Base, Qt::yellow); break; case 2: Pal.setColor(QPalette::Base, Qt::red); break; case 3: Pal.setColor(QPalette::Base, Qt::red); break; default: break; } setText(Text.section(' ', 1)); } else if (!ShowAsTime) { bool OK; double Num = Text.toDouble(&OK); if (OK) setText(QString::number(Num, 'g', 4)); else setText(Text); } else setText(QDateTime::fromTime_t(Text.toInt()).toString()); setCursorPosition(0); setPalette(Pal); } // Open plot if mouse release within widget void EddLineDisplay::mouseReleaseEvent(QMouseEvent *Event) { if (Event->button()!=Qt::LeftButton || !contentsRect().contains(Event->pos())) return; // Check if last history plot still open, then raise foreach (QWidget *Widget, QApplication::allWidgets()) { if (Widget == LastHist) { Widget->activateWindow(); Widget->raise(); return; } } // If not, open new plot MenuOpenHistory(); } // Handling of mouse press event: Register start position for drag void EddLineDisplay::mousePressEvent(QMouseEvent *Event) { if (Event->button() == Qt::LeftButton) dragStart = Event->pos(); } // Handling of dragging (Drag and MimeData will be deleted by Qt) void EddLineDisplay::mouseMoveEvent(QMouseEvent *Event) { if ((Event->buttons() & Qt::LeftButton) == 0) return; if ((Event->pos()-dragStart).manhattanLength() < QApplication::startDragDistance()) return; QDrag *Drag = new QDrag(this); QMimeData *MimeData = new QMimeData; QByteArray Data; MimeData->setData("Edd/Service", Data.append(ServiceName + " " + QString::number(Index))); Drag->setMimeData(MimeData); Drag->exec(); } // // Opening context menu // void EddLineDisplay::contextMenuEvent(QContextMenuEvent *Event) { Menu->exec(Event->globalPos()); } // Menu: Open history plot void EddLineDisplay::MenuOpenHistory() { OpenHistory(ServiceName.toAscii().data(), Index); } // Menu: Copy service name void EddLineDisplay::MenuCopyService() { QMimeData *MimeData = new QMimeData; QByteArray Data; MimeData->setData("Edd/Service", Data.append(ServiceName + " " + QString::number(Index))); QApplication::clipboard()->setMimeData(MimeData); } // Menu: Copy data void EddLineDisplay::MenuCopyData() { QApplication::clipboard()->setText(text()); } ////////////////////////////////////////// // Sending string command to DIM server // ////////////////////////////////////////// EddCommand::EddCommand(QString Name, QWidget *P): QLineEdit(P), Name(Name) { setToolTip("Send command "+Name); setStatusTip(QString("Command %1").arg(Name)); connect(this, SIGNAL(returnPressed()), SLOT(SendCommand())); } // Get command format and returns NULL (not empty) QString if not available QString EddCommand::GetFormat() { char *ItsName, *Format; DimBrowser Browser; Browser.getServices(Name.toAscii().data()); if (Browser.getNextService(ItsName, Format) != DimCOMMAND) return QString(); else return Format; } // Send command void EddCommand::SendCommand() { QByteArray Data; // Ignore empty commands if (text().isEmpty()) return; // Check if command available and retrieve format QString Format = GetFormat(); if (Format.isNull()) { QMessageBox::warning(this, "Edd Message", "Command " + Name + " currently unavailable", QMessageBox::Ok); return; } // Command has no argument if (Format.isEmpty()) { DimClient::sendCommand(Name.toAscii().data(), NULL, 0); return; } // Command has string as argument if (Format == "C") { DimClient::sendCommand(Name.toAscii().data(), text().toAscii().data()); return; } // Command has structure format, interpret data as hex if (Format.size() > 1) { Data = QByteArray::fromHex(text().toAscii()); } else { QDataStream Stream(&Data, QIODevice::WriteOnly); std::vector Values = EvidenceServer::Tokenize(text().toStdString()); for (unsigned int i=0; iaddSeparator(); Menu->addAction("Command help", this, SLOT(MenuCommandHelp())); Menu->exec(Event->globalPos()); delete Menu; } // Help text void EddCommand::MenuCommandHelp() { QString Format = GetFormat(); QString Text = Name; if (Format.isNull()) { Text += " is currently unavailable"; } else Text += " has format '"+Format+"'"; Text += "\n\nCommand arguments will be transmitted as string for format 'C',\n" "interpreted as an array of numbers for all other single character formats\n" "and as bytes in hexadecimal encoding for all more complex formats"; QMessageBox::about(this, "Edd - Command help", Text); } ////////////////////////////////// // History plot for DIM service // ////////////////////////////////// EddPlot::EddPlot(QString Service, int Index, QWidget *P): EddBasePlot(P) { // Widget properties setAcceptDrops(true); setAxisScaleDraw(QwtPlot::xBottom, new EddTimeScale); // Update time range on plot when axis change (update() results in paintEvent()) connect(axisWidget(QwtPlot::xBottom), SIGNAL(scaleDivChanged()), SLOT(update())); // Additonal context menu items QAction* Action = Menu->addAction("Paste service", this, SLOT(MenuPasteService())); Menu->removeAction(Action); Menu->insertAction(Menu->actions().value(1), Action); Action = Menu->addAction("Show last hour", this, SLOT(MenuShowLastHour())); Menu->insertAction(Menu->actions().value(8), Action); Action = Menu->addAction("Show last day", this, SLOT(MenuShowLastDay())); Menu->insertAction(Menu->actions().value(8), Action); Action = Menu->addAction("All as text", this, SLOT(MenuAllAsText())); Menu->insertAction(Menu->actions().value(10), Action); // Set timer to regularly update plot if new data available SingleShot = new QTimer(this); connect(SingleShot, SIGNAL(timeout()), this, SLOT(UpdatePlot())); SingleShot->setSingleShot(true); // DIM client if (!Service.isEmpty()) AddService(Service, Index); } // Destructor (items with parent widget are automatically deleted) EddPlot::~EddPlot() { while (!List.isEmpty()) RemoveService(List.last().Curve); } // Add service to plot void EddPlot::AddService(QString Name, int Index) { if (Index < 0) Index = 0; // Check if curve already present on plot for (int i=0; iremove(N.Curve); legend()->insert(N.Curve, (QWidget *) Legend); // Context menu might delete curve and legend -> seg fault if using direct connection, as legend item deleted connect(Legend, SIGNAL(DeleteCurve(QwtPlotCurve *)), this, SLOT(RemoveService(QwtPlotCurve *)), Qt::QueuedConnection); // Get history struct EddDim::HistItem Hist = Handler->GetHistory(Name); for (int i=0; i N.Index) { AddPoint(List.size()-1, Hist.DataText[i].first, Hist.DataText[i].second.at(N.Index).toFloat()); } } // Subscribe to service and start updating plot after 100 ms (to allow addition of other curves) Handler->Subscribe(Name, this, Index); SingleShot->start(100); } // Update widget (must happen in GUI thread) void EddPlot::Update(const QString &Name, int Time, const QByteArray &, const QString &Format, const QString &Text, int Index) { for (int ItemNo=0; ItemNolowerBound()).toString("d-MMM-yyyy hh:mm:ss") + " to " + QDateTime::fromTime_t((int) axisScaleDiv(QwtPlot::xBottom)->upperBound()).toString("d-MMM-yyyy hh:mm:ss"); Font.setPointSize(6); Painter.setFont(Font); Painter.drawText(0, height(), Text); } // Drag and drop methods void EddPlot::dragEnterEvent(QDragEnterEvent *Event) { if (Event->mimeData()->hasFormat("Edd/Service")) Event->acceptProposedAction(); } void EddPlot::dropEvent(QDropEvent *Event) { QByteArray D(Event->mimeData()->data("Edd/Service")); AddService(D.left(D.lastIndexOf(' ')), D.right(D.size()-D.lastIndexOf(' ')).toInt()); } // Add new service by pasting name void EddPlot::MenuPasteService() { const QMimeData *D = QApplication::clipboard()->mimeData(); if (!D->hasFormat("Edd/Service")) return; QByteArray E(D->data("Edd/Service")); AddService(E.left(E.lastIndexOf(' ')), E.right(E.size()-E.lastIndexOf(' ')).toInt()); } // Show last hour/last day void EddPlot::MenuShowLastHour() { setAxisScale(QwtPlot::xBottom, time(NULL)-60*60, time(NULL)+60); replot(); } void EddPlot::MenuShowLastDay() { setAxisScale(QwtPlot::xBottom, time(NULL)-24*3600, time(NULL)+3600); replot(); } void EddPlot::MenuAllAsText() { QMainWindow *M = new QMainWindow; M->setCentralWidget(new QWidget(M)); M->setStatusBar(new QStatusBar(M)); M->setAttribute(Qt::WA_DeleteOnClose); M->setWindowTitle("Edd Services"); QGridLayout *Layout = new QGridLayout(M->centralWidget()); for (int i=0; iaddWidget(new EddLineDisplay(List[i].Name, List[i].Index), i/10, i%10); } M->resize(400,450); M->show(); } // Remove subscription void EddPlot::RemoveService(QwtPlotCurve *Curve) { for (int i=0; iUnsubscribe(List[i].Name, this, List[i].Index); List.removeAt(i); DeleteCurve(Curve); break; } } ////////////////// // General plot // ////////////////// // Constructor (all slots called in GUI thread, therefore no mutex necessary) EddBasePlot::EddBasePlot(QWidget *P): QwtPlot(P) { // Widget properties setAttribute(Qt::WA_DeleteOnClose); setAutoReplot(false); setCanvasBackground(EddPlotBackgroundColor); setMargin(15); NewData = false; // Plot navigation Zoomer = new QwtPlotZoomer(QwtPlot::xBottom,QwtPlot::yLeft,canvas()); connect(Zoomer, SIGNAL(zoomed(const QwtDoubleRect &)), this, SLOT(HandleZoom(const QwtDoubleRect &))); connect(Zoomer, SIGNAL(zoomed(const QwtDoubleRect &)), this, SLOT(ReDoStats())); Magnifier = new QwtPlotMagnifier(canvas()); Magnifier->setMouseButton(Qt::NoButton,Qt::NoButton); Magnifier->setZoomInKey(Qt::Key_M, Qt::NoModifier); Magnifier->setZoomOutKey(Qt::Key_M, Qt::ShiftModifier); Panner = new QwtPlotPanner(canvas()); Panner->setMouseButton(Qt::LeftButton, Qt::ShiftModifier); connect(Panner, SIGNAL(panned(int, int)), this, SLOT(ReDoStats())); Picker = new QwtPicker(QwtPicker::CornerToCorner|QwtPicker::RectSelection, QwtPicker::RectRubberBand, QwtPicker::AlwaysOff, this); connect(Picker, SIGNAL(selected(const QwtPolygon &)), SLOT(MouseSelection(const QwtPolygon &))); // Grid and legend Grid = new QwtPlotGrid; Grid->setMajPen(QPen(Qt::gray, 0, Qt::DotLine)); Grid->attach(this); insertLegend(new QwtLegend(), QwtPlot::TopLegend, 0.3); // Marker for statistics text Stats = new QwtPlotMarker(); Stats->setLineStyle(QwtPlotMarker::NoLine); Stats->setLabelAlignment(Qt::AlignLeft | Qt::AlignBottom); Stats->hide(); Stats->attach(this); // Context menu Menu = new QMenu(this); StripAction = Menu->addAction("Stripchart", this, SLOT(UpdatePlot())); StripAction->setCheckable(true); Menu->addSeparator(); YLogAction = Menu->addAction("y scale log", this, SLOT(UpdatePlot())); YLogAction->setCheckable(true); NormAction = Menu->addAction("Normalize", this, SLOT(UpdatePlot())); NormAction->setCheckable(true); StyleAction = Menu->addAction("Draw dots", this, SLOT(UpdatePlot())); StyleAction->setCheckable(true); StatisticsAction = Menu->addAction("Statistics", this, SLOT(ReDoStats())); StatisticsAction->setCheckable(true); Menu->addAction("Zoom out", this, SLOT(MenuZoomOut())); Menu->addSeparator(); Menu->addAction("Set update rate", this, SLOT(MenuSetUpdateRate())); Menu->addSeparator(); Menu->addAction("Save as ASCII", this, SLOT(MenuSaveASCII())); Menu->addAction("Save plot", this, SLOT(MenuSave())); Menu->addAction("Print plot", this, SLOT(MenuPrint())); Menu->addAction("Plot help", this, SLOT(MenuPlotHelp())); // Set timer to regularly update plot if new data available Timer = new QTimer(this); connect(Timer, SIGNAL(timeout()), this, SLOT(UpdatePlot())); Timer->start(2000); } // Destructor (items with parent widget are automatically deleted) EddBasePlot::~EddBasePlot() { delete Grid; } // Print statistics to plot if requested void EddBasePlot::ReDoStats() { QString Text; double Mean, Sigma; unsigned long Count = 0; // Set visibility of statistics box Stats->setVisible(StatisticsAction->isChecked()); for (int i=0; icontains(Items[i].x[j])) continue; Mean += Items[i].y[j]; Sigma += Items[i].y[j]*Items[i].y[j]; Count++; } if (Count == 0) Mean = 0; else Mean /= Count; if (Count < 2) Sigma = 0; else Sigma = sqrt(Count/(Count-1)*(Sigma/Count-Mean*Mean)); // Prepare string Text += Items[i].Signal->title().text() + ": m " + QString::number(Mean, 'f', 2) + ", s " + QString::number(Sigma, 'f', 2) + "\n"; } // Replot once to get axis correct replot(); // Print string to plot QwtText text(Text); text.setFont(QFont("Helvetica", 8)); Stats->setValue(axisScaleDiv(QwtPlot::xBottom)->upperBound(), axisScaleDiv(QwtPlot::yLeft)->upperBound()); Stats->setLabel(text); // Replot again to update text replot(); } // Update all curves in plot void EddBasePlot::UpdatePlot() { double Lower = axisScaleDiv(QwtPlot::xBottom)->lowerBound(); double Upper = axisScaleDiv(QwtPlot::xBottom)->upperBound(); double MaxTime = DBL_MIN; static QwtSymbol Symbol, Sym1; Symbol.setStyle(QwtSymbol::Ellipse); Symbol.setSize(4); // Only update if called by timer if new data is available if (sender() == Timer && !NewData) return; NewData = false; // Select engine for linear or logarithmic scale if (!YLogAction->isChecked()) { setAxisScaleEngine(QwtPlot::yLeft, new QwtLinearScaleEngine); } else setAxisScaleEngine(QwtPlot::yLeft, new QwtLog10ScaleEngine); for (int ItemNo=0; ItemNoboundingRect().right() > MaxTime) { MaxTime = Items[ItemNo].Signal->boundingRect().right(); } // Set symbol if requested if (StyleAction->isChecked()) Items[ItemNo].Signal->setSymbol(Symbol); else Items[ItemNo].Signal->setSymbol(Sym1); // Determine number of data points int DataPoints = Items[ItemNo].x.size(); if (DataPoints == 0) continue; // Normalize y scale if requested double *y = new double [DataPoints]; for (int i=0; iisChecked()) { if (Items[ItemNo].Smallest != Items[ItemNo].Largest) { y[i] = (y[i] - Items[ItemNo].Smallest)/(Items[ItemNo].Largest-Items[ItemNo].Smallest); } else y[i] = 1; } } // Plot data Items[ItemNo].Signal->setData(Items[ItemNo].x.data(), y, DataPoints); Items[ItemNo].Signal->show(); delete[] y; // Generate bounding box of all curves for zoomer BBox = BBox.unite(Items[ItemNo].Signal->boundingRect()); } // Reset zoom base to include all data Zoomer->setZoomBase(BBox); // If plot is strip char, only move axis but keep range if (StripAction->isChecked()) { setCanvasBackground(EddPlotBackgroundColor.lighter(90)); setAxisScale(QwtPlot::xBottom, Lower+ BBox.right() - MaxTime, Upper + BBox.right() - MaxTime); } else setCanvasBackground(EddPlotBackgroundColor); ReDoStats(); } // Append curve to plot QwtPlotCurve *EddBasePlot::NewCurve(QwtText Title) { static Qt::GlobalColor LineColors[] = {Qt::black, Qt::blue, Qt::red, Qt::green, Qt::white, Qt::darkRed, Qt::darkGreen, Qt::darkBlue, Qt::cyan, Qt::darkCyan, Qt::magenta, Qt::darkMagenta, Qt::gray, Qt::darkGray, Qt::lightGray}; struct PlotItem N; N.Signal = new QwtPlotCurve; N.Signal->attach(this); N.Signal->setTitle(Title); N.Signal->setPen(QColor(LineColors[Items.size() % (sizeof(LineColors)/sizeof(Qt::GlobalColor))])); N.Largest = DBL_MIN; N.Smallest = DBL_MAX; Items.append(N); return N.Signal; } // Clear curve data void EddBasePlot::ClearCurve(unsigned int Item) { if (Item >= (unsigned int) Items.size()) return; Items[Item].x.clear(); Items[Item].y.clear(); } // Append data point void EddBasePlot::AddPoint(unsigned int Item, double x, double y) { if (Item >= (unsigned int) Items.size()) return; Items[Item].x.append(x); Items[Item].y.append(y); if (y > Items[Item].Largest) Items[Item].Largest = y; if (y < Items[Item].Smallest) Items[Item].Smallest = y; } // Rescale plot in case selection has been made outside the canvas void EddBasePlot::MouseSelection(const QPolygon &P) { QwtDoubleInterval xPlot, xMouse, yPlot, yMouse; // Shift selected rectangle so that upper left corner is 0/0 on canvas QRect R = P.boundingRect().translated(-plotLayout()->canvasRect().topLeft()); // Current axis intervals xPlot = axisScaleDiv(QwtPlot::xBottom)->interval(); yPlot = axisScaleDiv(QwtPlot::yLeft)->interval(); // Selected axis intervals xMouse = QwtDoubleInterval(invTransform(QwtPlot::xBottom, R.left()), invTransform(QwtPlot::xBottom, R.right())); yMouse = QwtDoubleInterval(invTransform(QwtPlot::yLeft, R.bottom()), invTransform(QwtPlot::yLeft, R.top())); // Selection region outside all axis? if (R.right() < 0 && R.top() > plotLayout()->canvasRect().height()) return; // If selected rectangle completely inside canvas taken care of by zoomer if (plotLayout()->canvasRect().contains(P.boundingRect())) return; // Rescale both axis if selected rectangle encompasses canvas completely if (P.boundingRect().contains(plotLayout()->canvasRect())) { yMouse.setMaxValue(yMouse.maxValue() + yPlot.width()); yMouse.setMinValue(yMouse.minValue() - yPlot.width()); xMouse.setMinValue(xMouse.minValue() - xPlot.width()); xMouse.setMaxValue(xMouse.maxValue() + xPlot.width()); setAxisScale(QwtPlot::xBottom, xMouse.minValue(), xMouse.maxValue()); setAxisScale(QwtPlot::yLeft, yMouse.minValue(), yMouse.maxValue()); } // Rescale y axis (increase range if selected rectangle larger than axis area) if (R.right() < 0) { if (yMouse.maxValue() > axisScaleDiv(QwtPlot::yLeft)->upperBound()) { yMouse.setMaxValue(yMouse.maxValue() + yPlot.width()); } if (yMouse.minValue() < axisScaleDiv(QwtPlot::yLeft)->lowerBound()) { yMouse.setMinValue(yMouse.minValue() - yPlot.width()); } setAxisScale(QwtPlot::yLeft, yMouse.minValue(), yMouse.maxValue()); } // Rescale x axis (increase range if selected rectangle larger than axis area) if (R.top() > plotLayout()->canvasRect().height()) { if (xMouse.maxValue() > axisScaleDiv(QwtPlot::xBottom)->upperBound()) { xMouse.setMaxValue(xMouse.maxValue() + xPlot.width()); } if (xMouse.minValue() < axisScaleDiv(QwtPlot::xBottom)->lowerBound()) { xMouse.setMinValue(xMouse.minValue() - xPlot.width()); } setAxisScale(QwtPlot::xBottom, xMouse.minValue(), xMouse.maxValue()); } // Draw new scales replot(); } // Reset graph axes to autoscale when fully unzoomed void EddBasePlot::HandleZoom(const QwtDoubleRect &) { if(Zoomer->zoomRectIndex() == 0) { setAxisAutoScale(QwtPlot::xBottom); setAxisAutoScale(QwtPlot::yLeft); } UpdatePlot(); } // Opening context menu void EddBasePlot::contextMenuEvent(QContextMenuEvent *Event) { Menu->exec(Event->globalPos()); } // Zoom completely out void EddBasePlot::MenuZoomOut() { Zoomer->zoom(0); UpdatePlot(); } // Remove item void EddBasePlot::DeleteCurve(QwtPlotCurve *Curve) { for (int i=0; iinterval()/1000, 0, std::numeric_limits::max(), 2, &OK); if (OK) Timer->setInterval(Rate*1000); } // Save data of plot as test void EddBasePlot::MenuSaveASCII() { QString Filename = QFileDialog::getSaveFileName(this, "Filename", ".", "Text files (*.txt *.ascii *.asc);;All files (*)"); if (Filename.length() <= 0) return; QFile File(Filename); if (!File.open(QFile::WriteOnly | QIODevice::Text | QFile::Truncate)) { QMessageBox::warning(this, "Edd Message","Could not open file for writing.",QMessageBox::Ok); return; } // Write x and y data for all signals to file QTextStream Stream(&File); for (int ItemNo=0; ItemNotitle().text() + ".hist" << endl; for (int i=0; idataSize(); i++) { Stream << Items[ItemNo].x.at(i) << " " << Items[ItemNo].Signal->y(i) << endl; } } } // Print plot void EddBasePlot::MenuPrint() { QPrinter *Printer = new QPrinter; QPrintDialog *PrintDialog = new QPrintDialog(Printer, this); if (PrintDialog->exec() == QDialog::Accepted) { QPainter Painter(Printer); QPixmap Pixmap = QPixmap::grabWidget(this); Painter.drawPixmap(0, 0, Pixmap); } delete Printer; delete PrintDialog; } // Save plot as image void EddBasePlot::MenuSave() { QString Filename = QFileDialog::getSaveFileName(this, "Filename of image", "/home/ogrimm/ddd", "Image files (*.bmp *.jpg *.png *.ppm *.tiff *.xbm *.xpm);;All files (*)"); if (Filename.length()>0) { QPixmap Pixmap = QPixmap::grabWidget(this); if(!Pixmap.save(Filename)) { QMessageBox::warning(this, "Edd Message","Could not write image file.",QMessageBox::Ok); remove(Filename.toAscii().data()); } } } // Help text void EddBasePlot::MenuPlotHelp() { QMessageBox::about(this, "Edd - Plot help", "Zoom\tMouse wheel\n" "\tKeys m and shift-m\n" "\tSelecting region with left mouse button\n" "\tMiddle button zooms out one level\n" "\tSelecting a range on an axis\n" "\tSelecting whole canvas\n\n" "Pan\tShift and left mouse button\n\n" "ESC cancels selection\n" "Cursor keys move mouse\n\n" "Statistics are calculated over the current x axis extend\n\n" "Items can be added to history plot by drag&drop from\n" "DIM service displays or from other plots legends"); } //////////////// // Edd Legend // //////////////// EddLegend::EddLegend(QwtPlotCurve *Curve, EddPlot *Plot): Curve(Curve), Plot(Plot) { // Context menu Menu = new QMenu(this); Menu->addAction("Open in new history", this, SLOT(MenuOpenHistory())); Menu->addAction("Copy service", this, SLOT(MenuCopyService())); Menu->addAction("Normal line", this, SLOT(MenuNormalLine())); Menu->addAction("Thick line", this, SLOT(MenuThickLine())); Menu->addAction("Remove curve", this, SLOT(MenuRemove())); } // // Opening context menu // void EddLegend::contextMenuEvent(QContextMenuEvent *Event) { Menu->exec(Event->globalPos()); } // Handling of mouse press event: Register start position for drag void EddLegend::mousePressEvent(QMouseEvent *Event) { if (Event->button() == Qt::LeftButton) dragStart = Event->pos(); } // Handling of dragging (Drag and MimeData will be deleted by Qt) void EddLegend::mouseMoveEvent(QMouseEvent *Event) { if ((Event->buttons() & Qt::LeftButton) == 0) return; if ((Event->pos()-dragStart).manhattanLength() < QApplication::startDragDistance()) return; QString D(text().text()); D.replace(':',' '); QDrag *Drag = new QDrag(this); QMimeData *MimeData = new QMimeData; QByteArray Data; MimeData->setData("Edd/Service", Data.append(D)); Drag->setMimeData(MimeData); Drag->exec(); } // Handling of mouse release event: Open history void EddLegend::mouseReleaseEvent(QMouseEvent *Event) { if (Event->button() != Qt::LeftButton) return; QString D(text().text()); D.replace(':',' '); QStringList A = D.split(" "); OpenHistory(A[0].toAscii().data(), A[1].toInt()); } // Menu: Open history plot void EddLegend::MenuOpenHistory() { QString D(text().text()); D.replace(':',' '); QStringList A = D.split(" "); OpenHistory(A[0].toAscii().data(), A[1].toInt()); } // Menu: Copy service name void EddLegend::MenuCopyService() { QString D(text().text()); D.replace(':',' '); QMimeData *MimeData = new QMimeData; QByteArray Data; MimeData->setData("Edd/Service", Data.append(D)); QApplication::clipboard()->setMimeData(MimeData); } // Menu: Normal line width void EddLegend::MenuNormalLine() { QPen Pen = Curve->pen(); Pen.setWidth(1); Curve->setPen(Pen); Curve->plot()->replot(); } // Menu: Normal line width void EddLegend::MenuThickLine() { QPen Pen = Curve->pen(); Pen.setWidth(4); Curve->setPen(Pen); Curve->plot()->replot(); } // Menu: Normal line width void EddLegend::MenuRemove() { emit(DeleteCurve(Curve)); } ////////////////////////////////////// // History text box for DIM service // ////////////////////////////////////// // Constructor EddText::EddText(QString Name, bool Pure, QWidget *P): QTextEdit(P), Name(Name), Pure(Pure) { // Widget properties setReadOnly(true); setAttribute(Qt::WA_DeleteOnClose); setAutoFillBackground(true); //setText("connecting..."); document()->setMaximumBlockCount(1000); Accumulate = true; // Get history for this service if (!Pure) { struct EddDim::HistItem Hist = Handler->GetHistory(Name); for (int i=0; iSubscribe(Name, this); } // Destructor EddText::~EddText() { Handler->Unsubscribe(Name, this); } // Update widget (must happen in GUI thread) void EddText::Update(const QString &, int Time, const QByteArray &, const QString &Format, const QString &Text, int) { QPalette Pal = palette(); // Check if service available if (!SetStatus(this, Name, Time, Format)) { Pal.setColor(QPalette::Base, Qt::lightGray); setPalette(Pal); setText("n/a"); return; } Pal.setColor(QPalette::Base, Qt::white); setPalette(Pal); QDateTime Timex = QDateTime::fromTime_t(Time); // Clear display in case text should not accumulate if (Accumulate == false) clear(); if (!Pure) { moveCursor(QTextCursor::Start); insertPlainText(QString("(")+Timex.toString()+QString(") ")); insertPlainText(Text + "\n"); } else if (Format == "C") insertPlainText(Text); } ///////////////////////////// // Interface to Dim system // ///////////////////////////// EddDim::EddDim() { Volume = 0; Period = 10; Mutex = new QMutex(QMutex::Recursive); // Timer to calculate data rates QTimer *Timer = new QTimer(this); Timer->connect(Timer, SIGNAL(timeout()), this, SLOT(UpdateStatistics())); Timer->start(Period*1000); // Connect to DIM handler if (connect(this, SIGNAL(INT(QString, int, QByteArray, QString)), SLOT(Update(QString, int, QByteArray, QString))) == false) { printf("Failed connection in EddDim()\n"); } } // Destructor EddDim::~EddDim() { delete Mutex; } // Subscribe to DIM service void EddDim::Subscribe(QString Name, class EddWidget *Instance, int Index) { // Lock before accessing list QMutexLocker Locker(Mutex); // Check if already subscribed to service if (ServiceList.contains(Name)) { ServiceList[Name].Subscribers.append(QPair(Instance, Index)); if (Index>=0 && IndexUpdate(Name, ServiceList[Name].TimeStamp, ServiceList[Name].ByteArray, ServiceList[Name].Format, ServiceList[Name].Items[Index]); } else Instance->Update(Name, ServiceList[Name].TimeStamp, ServiceList[Name].ByteArray, ServiceList[Name].Format, ServiceList[Name].Text); return; } // Create new entry in service list ServiceList[Name].ByteArray = QByteArray(); ServiceList[Name].TimeStamp = -1; ServiceList[Name].Subscribers.append(QPair(Instance, Index)); ServiceList[Name].DIMService = new DimStampedInfo(Name.toAscii().data(), INT_MAX, NO_LINK, this); } // Unsubscribe from DIM service void EddDim::Unsubscribe(QString Name, class EddWidget *Instance, int Index) { // Lock before accessing list QMutexLocker Locker(Mutex); if (!ServiceList.contains(Name)) return; QPair P(Instance, Index); if (ServiceList[Name].Subscribers.contains(P)) { ServiceList[Name].Subscribers.removeAt(ServiceList[Name].Subscribers.indexOf(P)); } // If no more needed, drop DIM subsription if (ServiceList[Name].Subscribers.isEmpty()) { delete ServiceList[Name].DIMService; ServiceList.remove(Name); } } // Ignore service in update void EddDim::Ignore(QString Name, bool Ignore) { QMutexLocker Locker(&IgnoreMutex); IgnoreMap[Name] = Ignore; } // Get history buffer struct EddDim::HistItem EddDim::GetHistory(QString Name) { // History already available? if (HistoryList.contains(Name)) return HistoryList[Name]; // Create history class to retrieve history data const struct EvidenceHistory::Item *R; class EvidenceHistory *Hist = new EvidenceHistory(Name.toStdString()); Hist->GetHistory(); HistoryList[Name].Time = time(NULL); HistoryList[Name].Format = Hist->GetFormat(); while ((R = Hist->Next()) != NULL) { HistoryList[Name].DataRaw.push_back(QPair(R->Time, QByteArray(R->Data, R->Size))); HistoryList[Name].DataText.push_back(QPair(R->Time, QString::fromStdString(EvidenceServer::ToString(Hist->GetFormat(), R->Data, R->Size)).split(' '))); } delete Hist; return HistoryList[Name]; } // Update throughput statistics and clear up history memory void EddDim::UpdateStatistics() { // Lock before accessing internal variables QMutexLocker Locker(Mutex); // Remove unused histories after not less than 5 seconds QList L = HistoryList.keys(); for(int i=0; i 60) HistoryList.remove(L[i]); } float Rate = Volume/1024.0/Period; Volume = 0; // No unlock because Mutex is recursive Update("Edd/Rate_kBSec", time(NULL), QByteArray((char *) &Rate, sizeof(Rate)), "F"); } // Store service information for usage by Subscribe(), update statistics and emit signal to widgets void EddDim::Update(QString Name, int Time, QByteArray Data, QString Format) { // Lock before accessing list QMutexLocker Locker(Mutex); Volume += Data.size(); // Store service data and update all subscribers if (ServiceList.contains(Name)) { ServiceList[Name].TimeStamp = Time; ServiceList[Name].ByteArray = Data; ServiceList[Name].Format = Format; ServiceList[Name].Text = QString::fromStdString(EvidenceServer::ToString(Format.toAscii().data(), Data.data(), Data.size())); ServiceList[Name].Items = ServiceList[Name].Text.split(" "); for (int i=0; i P = ServiceList[Name].Subscribers[i]; if (P.second >=0 && P.second < ServiceList[Name].Items.size()) { P.first->Update(Name, Time, Data, Format, ServiceList[Name].Items[P.second], P.second); } else P.first->Update(Name, Time, Data, Format, ServiceList[Name].Text, P.second); } } } // Handling of DIM service update (Data asynchronouly send to EddDim::Update()) void EddDim::infoHandler() { QMutexLocker Locker(&IgnoreMutex); bool Ignore = IgnoreMap[getInfo()->getName()]; Locker.unlock(); if (!EvidenceServer::ServiceOK(getInfo())) INT(getInfo()->getName(), -1); else if (!Ignore) INT(getInfo()->getName(), getInfo()->getTimestamp(), QByteArray((char *) getInfo()->getData(), getInfo()->getSize()), getInfo()->getFormat()); } ///////////////////// // Open new window // ///////////////////// // Constructor EddWindow::EddWindow(QString ButtonName, QString WindowName): QPushButton(ButtonName) { M = new QMainWindow; M->setCentralWidget(new QWidget); M->setStatusBar(new QStatusBar(M)); M->setWindowTitle(WindowName); L = new QGridLayout(M->centralWidget()); connect(this, SIGNAL(pressed()), M, SLOT(show())); connect(this, SIGNAL(pressed()), M, SLOT(raise())); } // Return layout QGridLayout *EddWindow::Layout() { return L; } // Return window QMainWindow *EddWindow::Window() { return M; }