/* ============================================================ 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, (char *) 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); // Horizontal time scale setAxisScaleDraw(QwtPlot::xBottom, new EddDateScale()); // 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 update plot after new data added 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; iGetHistory(Name); for (int i=0; i N.Index) { AddPoint(List.size()-1, double(Hist.DataText[i].first)*1000, 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); } // 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; } } // 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; ItemNomimeData()->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)*1000, (time(NULL)+60)*1000); replot(); } void EddPlot::MenuShowLastDay() { setAxisScale(QwtPlot::xBottom, (time(NULL)-24*3600)*1000, (time(NULL)+3600)*1000); 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(); } // // Reimplementation of QwtDateScaleDraw::label() // QwtText EddDateScale::label(double Value) const { QString Format; if (scaleDiv().range() > 30*24*60*60*1000.0) Format = "d-MMM\n yyyy"; else if (scaleDiv().range() > 1*24*60*60*1000.0) Format = " h 'h'\nd-MMM"; else if (scaleDiv().range() > 60*60*1000.0) Format = "hh:mm"; else Format = "hh:mm:ss"; return QwtDate::toString(toDateTime(Value), Format, QwtDate::FirstThursday); } ////////////////// // 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); NewData = false; // Plot navigation Zoomer = new QwtPlotZoomer(QwtPlot::xBottom,QwtPlot::yLeft,canvas()); Zoomer->setTrackerMode(QwtPicker::AlwaysOff); connect(Zoomer, SIGNAL(zoomed(const QRectF &)), this, SLOT(HandleZoom(const QRectF &))); connect(Zoomer, SIGNAL(zoomed(const QRectF &)), this, SLOT(ReDoStats())); Magnifier = new QwtPlotMagnifier(canvas()); Magnifier->setMouseButton(Qt::NoButton, Qt::NoModifier); 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::RectRubberBand, QwtPicker::AlwaysOff, this); Picker->setStateMachine(new QwtPickerDragRectMachine); connect(Picker, SIGNAL(selected(const QPolygon &)), SLOT(MouseSelection(const QPolygon &))); // Grid and legend Grid = new QwtPlotGrid; Grid->setMajorPen(QPen(Qt::gray, 0, Qt::DotLine)); Grid->attach(this); Legend = new EddLegend(); insertLegend(Legend, 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; ititle().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; // 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 QwtLogScaleEngine); for (int ItemNo=0; ItemNoboundingRect().right() > MaxTime) { MaxTime = Items[ItemNo].Signal->boundingRect().right(); } // Set symbol if requested if (StyleAction->isChecked()) { QwtSymbol *Symbol = new QwtSymbol(); Symbol->setStyle(QwtSymbol::Ellipse); Symbol->setSize(4); Items[ItemNo].Signal->setSymbol(Symbol); } else Items[ItemNo].Signal->setSymbol(new QwtSymbol); // 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->setSamples(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); // Set footer QwtText Text; QFont Font; Text = QDateTime::fromMSecsSinceEpoch(axisScaleDiv(QwtPlot::xBottom).lowerBound()).toString("d-MMM-yyyy hh:mm:ss") + " to " + QDateTime::fromMSecsSinceEpoch(axisScaleDiv(QwtPlot::xBottom).upperBound()).toString("d-MMM-yyyy hh:mm:ss") + (StripAction->isChecked() ? " (Stripchart)" : ""); Font.setPointSize(6); Text.setFont(Font); setFooter(Text); // For EddScope (not a history plot, no time scale) this action is removed // Recalculate statistics 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(Title); N.Signal->attach(this); N.Signal->setPen(QColor(LineColors[Items.size() % (sizeof(LineColors)/sizeof(Qt::GlobalColor))])); N.Largest = DBL_MIN; N.Smallest = DBL_MAX; Items.append(N); QVariant ItemInfo = itemToInfo(N.Signal); ((EddLegendLabel *) Legend->legendWidget(ItemInfo))->Curve = N.Signal; // Context menu might delete curve and legend -> seg fault if using direct connection, as legend item deleted connect(((EddLegendLabel *) Legend->legendWidget(ItemInfo)), SIGNAL(DeleteCurve(QwtPlotCurve *)), SLOT(RemoveService(QwtPlotCurve *)), Qt::QueuedConnection); return N.Signal; } // Remove item void EddBasePlot::DeleteCurve(QwtPlotCurve *Curve) { for (int i=0; i= (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_integer) { QwtInterval xPlot, xMouse, yPlot, yMouse; QPolygonF P(P_integer); // Shift selected rectangle so that upper left corner is 0/0 on canvas QRectF R = P.boundingRect().translated(-plotLayout()->canvasRect().topLeft()); // Current axis intervals xPlot = axisScaleDiv(QwtPlot::xBottom).interval(); yPlot = axisScaleDiv(QwtPlot::yLeft).interval(); // Selected axis intervals xMouse = QwtInterval(invTransform(QwtPlot::xBottom, R.left()), invTransform(QwtPlot::xBottom, R.right())); yMouse = QwtInterval(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 QRectF &) { if(Zoomer->zoomRectIndex() == 0) { setAxisAutoScale(QwtPlot::xBottom); setAxisAutoScale(QwtPlot::yLeft); } UpdatePlot(); } // Mouse events: Release click highlights curve if mouse did not move (which is zoom) void EddBasePlot::mousePressEvent(QMouseEvent *) { MouseMoved = false; } void EddBasePlot::mouseMoveEvent(QMouseEvent *) { MouseMoved = true; } void EddBasePlot::mouseReleaseEvent(QMouseEvent *Event) { double Dist, MinDistance = std::numeric_limits::infinity(); int Index = -1; if (Event->button() != Qt::LeftButton || MouseMoved) return; // Check which curve is closest for (int i=0; iclosestPoint(canvas()->mapFromGlobal(QCursor::pos()), &Dist) != -1 && Dist < MinDistance) { MinDistance = Dist; Index = i; } } // Toggle 'thick line' action if (Index != -1) { ((EddLegendLabel *) Legend->legendWidget(itemToInfo(Items[Index].Signal)))->ThickLineAction->toggle(); } } // Opening context menu void EddBasePlot::contextMenuEvent(QContextMenuEvent *Event) { Menu->exec(Event->globalPos()); } // Zoom completely out void EddBasePlot::MenuZoomOut() { Zoomer->zoom(0); UpdatePlot(); } // Set maximum update rate of plot void EddBasePlot::MenuSetUpdateRate() { bool OK; double Rate; Rate = QInputDialog::getDouble(this, "Edd Request", "Minimum period between plot updates (sec)", (double) Timer->interval()/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 (unsigned int i=0; idataSize(); i++) { //Stream << Items[ItemNo].x.at(i) << " " << Items[ItemNo].Signal->y(i) << endl; Stream << Items[ItemNo].x.at(i) << " " << Items[ItemNo].Signal->sample(i).y() << 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" "Clicking near a curve or on legend highlights\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 and LegendLabel // //////////////////////////////// // Reimplementation to return EddLegendLabel widget QWidget *EddLegend::createWidget(const QwtLegendData &) const { return new EddLegendLabel(); } // Constructor for EddLegendLabel class EddLegendLabel::EddLegendLabel() { // Context menu Menu = new QMenu(this); Menu->addAction("Open in new history", this, SLOT(MenuOpenHistory())); Menu->addAction("Copy service", this, SLOT(MenuCopyService())); ThickLineAction = new QAction("Thick line", this); ThickLineAction->setCheckable(true); connect(ThickLineAction, SIGNAL(toggled(bool)), SLOT(MenuThickLine(bool))); Menu->addAction(ThickLineAction); Menu->addAction("Remove curve", this, SLOT(MenuRemove())); Curve = NULL; } // Opening context menu void EddLegendLabel::contextMenuEvent(QContextMenuEvent *Event) { Menu->exec(Event->globalPos()); } // Handling of mouse press event: Register start position for drag void EddLegendLabel::mousePressEvent(QMouseEvent *Event) { if (Event->button() == Qt::LeftButton) dragStart = Event->pos(); } // Handling of dragging (Drag and MimeData will be deleted by Qt) void EddLegendLabel::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: Mark line void EddLegendLabel::mouseReleaseEvent(QMouseEvent *Event) { if (Event->button() != Qt::LeftButton) return; ThickLineAction->toggle(); } // Menu: Open history plot void EddLegendLabel::MenuOpenHistory() { QString D(text().text()); D.replace(':',' '); QStringList A = D.split(" "); OpenHistory(A[0].toAscii().data(), A[1].toInt()); } // Menu: Copy service name void EddLegendLabel::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: Thick line width void EddLegendLabel::MenuThickLine(bool State) { if (Curve == NULL) { printf("Warning: No QwtPlotCurve set in EddLegendLabel::MenuThickLine(), programming error\n"); return; } // Set pxel width of curve QPen Pen = Curve->pen(); Pen.setWidth(State ? 4:1); Curve->setPen(Pen); Curve->plot()->replot(); // Highlight legend entry QFont Font = font(); Font.setWeight(State ? QFont::Bold:QFont::Normal); setFont(Font); } // Menu: Normal line width void EddLegendLabel::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); 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; }