/* ============================================================ 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, Oliver Grimm ============================================================ */ #include "Edd.h" 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}; class EddDim *Handler; // History chooser function (opens plot for numeric data, TextHist for all other) QWidget *OpenHistory(char *Service, int Index) { class EvidenceHistory *Hist = Handler->GetHistory(Service); // Check if hitory service available if (Hist == NULL || Hist->GetFormat() == NULL) { QMessageBox::warning(NULL, "Edd Message", QString("Could not retrieve history for service ") + Service ,QMessageBox::Ok); Handler->DropHistory(Service); return NULL; } QString Format = Hist->GetFormat(); Handler->DropHistory(Service); //if (strlen(Hist->GetFormat()) == 1 && *Hist->GetFormat() != 'C') return new EddPlot(Service, Index); if (Format.size() == 1 && Format[0] != 'C') return new EddPlot(Service, Index); else return new EddText(Service); } // 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); if (W->parent() == NULL) W->setToolTip(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); // Connect to DIM handler if (connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) { printf("Failed connection for %s\n", Name.toAscii().data()); } // 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); } // Destructor EddLineDisplay::~EddLineDisplay() { Handler->Unsubscribe(ServiceName); } // Update widget void EddLineDisplay::Update(QString Name, int Time, QByteArray, QString Format, QString Text) { if (ServiceName != Name) return; QPalette Pal = palette(); // Check if service available if (!SetStatus(this, Name, Time, Format, Index)) { setText("n/a"); Pal.setColor(QPalette::Base, Qt::lightGray); setPalette(Pal); return; } // Message service backgound colour determined by severity if (Name.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; } Text = Text.section(' ', 1); } else if (Format[0].toUpper() != 'C') Text = Text.section(' ', Index, Index); if (!ShowAsTime) 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 EddLineDisplay::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() { LastHist = OpenHistory(ServiceName.toAscii().data(), Index); if (LastHist != NULL) LastHist->show(); } // 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); connect(this, SIGNAL(returnPressed()), SLOT(SendCommand())); } // Send command void EddCommand::SendCommand() { DimClient::sendCommand(Name.toAscii().data(), text().toAscii().data()); clear(); } ////////////////////////////////// // History plot for DIM service // ////////////////////////////////// EddPlot::EddPlot(QString DIMService, 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())); legend()->setItemMode(QwtLegend::ClickableItem); connect(this, SIGNAL(legendClicked (QwtPlotItem *)), SLOT(LegendClicked(QwtPlotItem *))); // Connect to DIM handler if (connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) { printf("Failed connection for %s\n", DIMService.toAscii().data()); } // Additonal context menu items QAction* Action = Menu->addAction("Paste service", this, SLOT(MenuPasteService())); Menu->removeAction(Action); Menu->insertAction(Menu->actions().value(1), Action); // Maximum number if points in curve (will be increased in Update()) SizeLimit = 0; // DIM client if (!DIMService.isEmpty()) AddService(DIMService, Index); } // Destructor (items with parent widget are automatically deleted) EddPlot::~EddPlot() { while (!List.isEmpty()) DeleteCurve(List.last().Signal); } // Add history service to plot void EddPlot::AddService(QString Name, int Index) { // Check if curve already present on plot for (int i=0; iSubscribe(Name); } // Update widget (must happen in GUI thread) void EddPlot::Update(QString Name, int Time, QByteArray, QString Format, QString Text) { // Determine which plot item this call belongs to int ItemNo; for (ItemNo=0; ItemNodataSize() > SizeLimit) List[ItemNo].Signal->setData(QPolygonF()); // If buffer empty, request new history buffer if (List[ItemNo].Signal->dataSize() == 0) { int Count=0; const struct EvidenceHistory::Item *R; class EvidenceHistory *Hist; if ((Hist = Handler->GetHistory(List[ItemNo].Name)) != NULL) { double Number=0; while ((R=Hist->Next()) != NULL) { switch (*(Hist->GetFormat())) { case 'I': case 'L': Number = *((int *) R->Data + List[ItemNo].Index); break; case 'S': Number = *((short *) R->Data + List[ItemNo].Index); break; case 'F': Number = *((float *) R->Data + List[ItemNo].Index); break; case 'D': Number = *((double *) R->Data + List[ItemNo].Index); break; case 'X': Number = *((long long *) R->Data + List[ItemNo].Index); break; default: break; } AddPoint(ItemNo, R->Time, Number); Count++; } // Local buffer at least twice as large as longest history if (SizeLimit < 2*Count) SizeLimit = 2*Count; } Handler->DropHistory(List[ItemNo].Name); } // Appen data only if service available if (SetStatus(this, Name, Time, Format)) { QString Txt = Text; Txt = Txt.section(' ', List[ItemNo].Index, List[ItemNo].Index); AddPoint(ItemNo, Time, atof(Txt.toAscii().data())); } } UpdatePlot(); } // Add text indicating time range to plot void EddPlot::paintEvent(QPaintEvent *) { QString Text; QFont Font; QPainter Painter(this); 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"); 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()); } void EddPlot::LegendClicked(QwtPlotItem *Item) { QString D(Item->title().text()); D.replace('(',' ').chop(1); QDrag *Drag = new QDrag(this); QMimeData *MimeData = new QMimeData; QByteArray Data; MimeData->setData("Edd/Service", Data.append(D)); Drag->setMimeData(MimeData); Drag->exec(); } // 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()); } // Remove list entry void EddPlot::DeleteCurve(QwtPlotCurve *Curve) { for (int i=0; iUnsubscribe(List[i].Name); List.removeAt(i); } } ////////////////// // 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); // Plot navigation Zoomer = new QwtPlotZoomer(QwtPlot::xBottom,QwtPlot::yLeft,canvas()); connect(Zoomer, SIGNAL(zoomed(const QwtDoubleRect &)), this, SLOT(HandleZoom(const QwtDoubleRect &))); 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); 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); // 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); Menu->addAction("Zoom out", this, SLOT(MenuZoomOut())); Menu->addAction("Single trace", this, SLOT(MenuSingleTrace())); 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())); } // Destructor (items with parent widget are automatically deleted) EddBasePlot::~EddBasePlot() { for (int i=0; ilowerBound(); double Upper = axisScaleDiv(QwtPlot::xBottom)->upperBound(); double MaxTime = DBL_MIN; static QwtSymbol Symbol, Sym1; Symbol.setStyle(QwtSymbol::Ellipse); Symbol.setSize(4); // 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); replot(); } // Append curve to plot QwtPlotCurve *EddBasePlot::NewCurve(QwtText Title) { 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; // 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 (R.top() < plotLayout()->canvasRect().top()) { yMouse.setMaxValue(yMouse.maxValue() + yPlot.width()); } if (R.bottom() > plotLayout()->canvasRect().bottom()) { 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 (R.left() < plotLayout()->canvasRect().left()) { xMouse.setMinValue(xMouse.minValue() - xPlot.width()); } if (R.right() > plotLayout()->canvasRect().right()) { xMouse.setMaxValue(xMouse.maxValue() + 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); } } // Opening context menu void EddBasePlot::contextMenuEvent(QContextMenuEvent *Event) { Menu->exec(Event->globalPos()); } // Zoom completely out void EddBasePlot::MenuZoomOut() { Zoomer->zoom(0); UpdatePlot(); } // Remove all items except one void EddBasePlot::MenuSingleTrace() { while (Items.size() > 1) { DeleteCurve(Items.last().Signal); delete Items.last().Signal; Items.takeLast(); } UpdatePlot(); } // 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 << (int) 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 navigation", "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"); } ////////////////////////////////////// // 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; // Connect to DIM handler if (connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) { printf("Failed connection for %s\n", Name.toAscii().data()); } if (!Pure) { // Get history for this service const struct EvidenceHistory::Item *R; class EvidenceHistory *Hist; if ((Hist = Handler->GetHistory(Name)) != NULL) { while ((R=Hist->Next()) != NULL) { moveCursor (QTextCursor::Start); insertPlainText(QString("(")+QDateTime::fromTime_t(R->Time).toString()+") "); insertPlainText(QString((char *) R->Data) + "\n"); } } Handler->DropHistory(Name); } // DIM client Handler->Subscribe(Name); } // Destructor EddText::~EddText() { Handler->Unsubscribe(Name); } // Update widget (must happen in GUI thread) void EddText::Update(QString Name, int Time, QByteArray, QString Format, QString Text) { if (this->Name != Name) return; QPalette Pal = palette(); // Check if service available if (!SetStatus(this, Name, Time, Format)) { Pal.setColor(QPalette::Base, Qt::lightGray); setPalette(Pal); 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() { Mutex = new QMutex(QMutex::Recursive); Volume = 0; // Timer to calculate data rates QTimer *Timer = new QTimer(this); Timer->connect(Timer, SIGNAL(timeout()), this, SLOT(UpdateStatistics())); Timer->start(10000); // Connect to DIM handler if (connect(this, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) { printf("Failed connection in EddDim()\n"); } } EddDim::~EddDim() { for (int i=0; iRewind(); return HistoryList[i].HistClass; } HistoryList[i].LastUpdate = time(NULL); if (HistoryList[i].HistClass->GetHistory()) return HistoryList[i].HistClass; else return NULL; } // Create new history class struct HistItem New; New.Name = Name; New.HistClass = new EvidenceHistory(Name.toStdString()); New.Count = 1; New.LastUpdate = time(NULL); HistoryList.append(New); if (New.HistClass->GetHistory()) return New.HistClass; else return NULL; } // Reduce history usage counter void EddDim::DropHistory(QString Name) { for (int i=0; i 5) { delete HistoryList[i].HistClass; HistoryList.removeAt(i); } } // Lock before accessing internal variables QMutexLocker Locker(Mutex); float Rate = Volume/1024.0/10; YEP("Edd/Rate_kBSec", time(NULL), QByteArray::number(Rate), "F", QString::number(Rate)); Volume = 0; } // Store service information for usage by Subscribe() and update statistics void EddDim::Update(QString Name, int Time, QByteArray Data, QString Format, QString Text) { // Lock before accessing list QMutexLocker Locker(Mutex); for (int i=0; igetName(), -1); else { char *Text = EvidenceServer::ToString(getInfo()); YEP(getInfo()->getName(), getInfo()->getTimestamp(), QByteArray((char *) getInfo()->getData(), getInfo()->getSize()), getInfo()->getFormat(), Text); free(Text); } } // // // ====== FACT specific part ====== // // //////////////////////// // Event oscilloscope // //////////////////////// // Constructor EventScope::EventScope(QWidget *P): EddBasePlot(P), PixelMap("../../config/PixelMap.txt", false) { Name = "drsdaq/EventData"; Tmpfile = tmpfile(); if(Tmpfile == NULL) { QMessageBox::warning(this, "Edd Message", "Could not open temporary file.", QMessageBox::Ok); } // Open file with RawDataCTX RD = new RawDataCTX(true); ErrCode = CTX_NOTOPEN; // Context menu PhysPipeAction = new QAction("Plot physical pipeline", this); PhysPipeAction->setCheckable(true); connect(PhysPipeAction, SIGNAL(triggered()), SLOT(PlotTraces())); Menu->insertAction(StripAction, PhysPipeAction); Menu->removeAction(StripAction); // Initial trace AddTrace(0,0,0); // Connect to DIM handler if (connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) { printf("Failed connection for %s\n", Name.toAscii().data()); } Handler->Subscribe(Name); } // Destructor (items with parent widget are automatically deleted) EventScope::~EventScope() { Handler->Unsubscribe(Name); while (!List.isEmpty()) DeleteCurve(List.last().Signal); delete RD; if (Tmpfile != NULL) fclose(Tmpfile); } // Add trace void EventScope::AddTrace(int Board, int Chip, int Channel) { struct ItemDetails N; N.Signal = NewCurve(QString::number(Board)+","+QString::number(Chip)+","+ QString::number(Channel)+ " (" + DRS_to_Pixel(Board, Chip, Channel).c_str() + ")"); N.Board = Board; N.Chip = Chip; N.Channel = Channel; N.Trigger = new QwtPlotMarker(); N.Trigger->setSymbol(QwtSymbol(QwtSymbol::Diamond, QBrush(N.Signal->pen().color()), N.Signal->pen(), QSize(10,10))); N.Trigger->attach(this); if (List.isEmpty()) { QPen Pen = N.Signal->pen(); Pen.setWidth(2); N.Signal->setPen(Pen); } List.append(N); PlotTraces(); } // Update last trace (to reflect current setting of spin boxes in DAQ page) void EventScope::UpdateFirst(int Board, int Chip, int Channel) { if (List.isEmpty()) return; List.first().Signal->setTitle(QString::number(Board)+","+QString::number(Chip)+","+ QString::number(Channel) + " (" + DRS_to_Pixel(Board, Chip, Channel).c_str() + ")"); List.first().Board = Board; List.first().Chip = Chip; List.first().Channel = Channel; PlotTraces(); } // Update event buffer void EventScope::Update(QString Name, int Time, QByteArray Data, QString Format, QString) { if (Name != this->Name) return; // Check if service available if (!SetStatus(this, Name, Time, Format)) return; if (Data.size() < (int) sizeof(RunHeader)) return; // Open tempory file and write event data to this file QTemporaryFile File; if (!File.open()) { QMessageBox::warning(this, "Edd Message","Could not open temporary file.",QMessageBox::Ok); return; } if (File.write(Data) == -1) { QMessageBox::warning(this, "Edd Message","Could not write data to temporary file.",QMessageBox::Ok); return; } // Prepare temporary file for run header ftruncate(fileno(Tmpfile), 0); rewind(Tmpfile); // Open file with RawDataCTX switch (ErrCode = RD->OpenDataFile(File.fileName().toAscii().data(), Tmpfile)) { case CTX_FOPEN: QMessageBox::warning(this, "Edd Message","Could not open file.",QMessageBox::Ok); return; case CTX_RHEADER: QMessageBox::warning(this, "Edd Message","Could not read run header.",QMessageBox::Ok); return; case CTX_BSTRUCT: QMessageBox::warning(this, "Edd Message","Could not read board structures.",QMessageBox::Ok); return; default: break; } // Emit signal containing run header rewind(Tmpfile); QTextStream Stream(Tmpfile); emit(RunHeaderChanged(Stream.readAll())); // Prepare temporary file for event header ftruncate(fileno(Tmpfile), 0); rewind(Tmpfile); if (RD->ReadEvent(0, Tmpfile) != CTX_OK) { QMessageBox::warning(this, "Edd Warning","Could not read event.",QMessageBox::Ok); return; } // Emit signal containing run header rewind(Tmpfile); emit(EventHeaderChanged(Stream.readAll())); PlotTraces(); } // Update curves void EventScope::PlotTraces() { double x,y; unsigned int Cell, Trig; // Only process if valid data in RawDataCTX class if (ErrCode != CTX_OK) return; // Set x axis title if (PhysPipeAction->isChecked()) setAxisTitle(QwtPlot::xBottom, "Time from start of pipeline (ns)"); else setAxisTitle(QwtPlot::xBottom, "Time from trigger minus one revolution (ns)"); // Loop through event data to update event scope RunHeader *R = RD->RHeader; for (int i=0; i=R->NBoards || List[i].Chip>=R->NChips || List[i].Channel>=R->NChannels) continue; // Set trigger marker visibility List[i].Trigger->setVisible(PhysPipeAction->isChecked()); // Determine trigger cell Trig = *((int *) RD->Data + List[i].Board*R->NChips + List[i].Chip); for (unsigned int j=0; jSamples; j++) { if (PhysPipeAction->isChecked()) Cell = (j + Trig) % 1024; else Cell = j; x = j / RD->BStruct[List[i].Board].NomFreq; y = *((short *) (RD->Data + R->NBoards*R->NChips*sizeof(int)) + List[i].Board*R->NChips*R->NChannels*R->Samples + List[i].Chip*R->NChannels*R->Samples + List[i].Channel*R->Samples + Cell) * RD->BStruct[List[i].Board].ScaleFactor; AddPoint(i, x, y); // Set trigger point indicator if (Trig == j/*+R->Offset*/) List[i].Trigger->setValue(x, y); } } UpdatePlot(); // Loop through event data for pixel display QVector Pixel(R->NBoards*R->NChips*R->NChannels); int Count = 0; for (unsigned int Board=0; BoardNBoards; Board++) { for (unsigned int Chip=0; ChipNChips; Chip++) { for (unsigned int Channel=0; ChannelNChannels; Channel++) { Pixel[Count] = DBL_MIN; for (unsigned int i=0; iSamples; i++) { y = *((short *) (RD->Data + R->NBoards*R->NChips*sizeof(int)) + Board*R->NChips*R->NChannels*R->Samples + Chip*R->NChannels*R->Samples + Channel*R->Samples + i) * RD->BStruct[Board].ScaleFactor; if (y > Pixel[Count]) Pixel[Count] = y; } Count++; }}} emit(PixelData(Pixel)); } // Remove list entry void EventScope::DeleteCurve(QwtPlotCurve *Curve) { for (int i=0; isetMaximumWidth(200); Layout->addWidget(Line, 0, 0, 1, 2); // Generate plot and data displays EddPlot *Plot = new EddPlot(); for (int i=0; i<10; i++) { Line = new EddLineDisplay("ARDUINO/Data", i); Layout->addWidget(Line, i%5+1, i/5, 1, 1); Plot->AddService("ARDUINO/Data", i); } Layout->addWidget(Plot, 0, 2, 8, 7); // Night sky monitor Line = new EddLineDisplay("SQM/NSB"); Layout->addWidget(Line, 6, 0, 1, 1); } // // Bias page // TP_Bias::TP_Bias() { QGridLayout *Layout = new QGridLayout(this); setAttribute(Qt::WA_DeleteOnClose); EddLineDisplay *Line; EddPlot *Plot = new EddPlot(); Plot->setMinimumWidth(400); for (int i=0; i<18; i++) { Line = new EddLineDisplay("Bias/VOLT/ID00", i); Layout->addWidget(Line, i%9+1, 0+i/9, 1, 1); Plot->AddService("Bias/VOLT/ID00", i); Line = new EddLineDisplay("Bias/VOLT/ID00", i+32); Layout->addWidget(Line, i%9+1, 2+i/9, 1, 1); Plot->AddService("Bias/VOLT/ID00",i+32); } Layout->addWidget(Plot, 0, 4, 12, 3); Line = new EddLineDisplay("Bias/Message"); Line->setMaximumWidth(200); Layout->addWidget(Line, 0, 0, 1, 3); EddCommand *Command = new EddCommand("Bias/Command"); Layout->addWidget(Command, 10, 0, 1, 4); EddText *Text = new EddText("Bias/Textout", true); Text->setFixedWidth(400); Layout->addWidget(Text, 11, 0, 4, 4); } // // Feedback page // TP_Feedback::TP_Feedback() { setAttribute(Qt::WA_DeleteOnClose); QGridLayout *Layout = new QGridLayout(this); EddLineDisplay *Line; EddPlot *Plot = new EddPlot(); for (int i=0; i<36; i++) { Line = new EddLineDisplay("drsdaq/Average", i); Line->setMaximumWidth(60); Layout->addWidget(Line, i%9+1, 0+i/9, 1, 1); Plot->AddService("drsdaq/Average", i); } Layout->addWidget(Plot, 0, 4, 12, 10); //Graph = new EddPlot(); //for (int i=0; i<36; i++) { //Text = Text.sprintf("Feedback/Sigma/ID%.2d/%.2d-%.3d",i/16, (i%16)/8, i%8); //Graph->AddService(Text); //} //FeedbackLayout->addWidget(Graph, 10, 0, 10, 3); Line = new EddLineDisplay("drsdaq/Message"); Line->setMaximumWidth(200); Layout->addWidget(Line, 0, 0, 1, 2); Line = new EddLineDisplay("drsdaq/Count"); Line->setMaximumWidth(60); Layout->addWidget(Line, 0, 2); QWidget *Button = new QPushButton("Details"); Layout->addWidget(Button, 10, 0, 1, 1); connect(Button, SIGNAL(pressed()), SLOT(FeedbackDetails())); } void TP_Feedback::FeedbackDetails() { setAttribute(Qt::WA_DeleteOnClose); EddLineDisplay *Line; QWidget *Widget = new QWidget(); QGridLayout *Layout = new QGridLayout(Widget); EddPlot *Plot = new EddPlot(); for (int i=0; i<36; i++) { Line = new EddLineDisplay("drsdaq/Sigma", i); Line->setMaximumWidth(50); Layout->addWidget(Line, i%9+1, 0+i/9, 1, 1); Plot->AddService("drsdaq/Sigma", i); } Layout->addWidget(Plot, 0, 4, 12, 10); Widget->show(); } // // Event scope page // TP_DAQ::TP_DAQ() { setAttribute(Qt::WA_DeleteOnClose); QGridLayout *Layout = new QGridLayout(this); // Event scope Scope = new EventScope; Scope->setMinimumWidth(700); // Text boxes for run and event header RunHeaderDisplay = new QPlainTextEdit(); EventHeaderDisplay = new QPlainTextEdit(); RunHeaderDisplay->setReadOnly(true); EventHeaderDisplay->setReadOnly(true); // Tab widget QTabWidget *TabWidget = new QTabWidget(); TabWidget->addTab(Scope, "&Signals"); TabWidget->addTab(RunHeaderDisplay, "&Run Header"); TabWidget->addTab(EventHeaderDisplay, "&Event Header"); Layout->addWidget(TabWidget, 0, 1, 5, 3); connect(Scope, SIGNAL(RunHeaderChanged(QString)), RunHeaderDisplay, SLOT(setPlainText(QString))); connect(Scope, SIGNAL(EventHeaderChanged(QString)), EventHeaderDisplay, SLOT(setPlainText(QString))); // Channel number Channel = new QSpinBox; connect(Channel, SIGNAL(valueChanged(int)), SLOT(UpdateScope(int))); Channel->setToolTip("DRS channel number"); // Chip number Chip = new QSpinBox; connect(Chip, SIGNAL(valueChanged(int)), SLOT(UpdateScope(int))); Chip->setToolTip("DRS chip number"); // Board number Board = new QSpinBox; connect(Board, SIGNAL(valueChanged(int)), SLOT(UpdateScope(int))); Board->setToolTip("DRS board number"); // Pixel ID PixelID = new QLineEdit; PixelID->setMaximumWidth(60); connect(PixelID, SIGNAL(returnPressed()), SLOT(TranslatePixelID())); PixelID->setToolTip("Pixel identification"); // Layout of pixel addressing widgets QFormLayout *FormLayout = new QFormLayout(); FormLayout->setRowWrapPolicy(QFormLayout::WrapAllRows); FormLayout->addRow("Channel", Channel); FormLayout->addRow("Chip", Chip); FormLayout->addRow("Board", Board); FormLayout->addRow("Pixel ID", PixelID); Layout->addLayout(FormLayout, 0, 0, 2, 1); // Add trace permanently QPushButton *Button = new QPushButton("Keep trace"); Button->setToolTip("Keep trace in display"); Button->setMaximumWidth(80); Layout->addWidget(Button, 2, 0); connect(Button, SIGNAL(clicked()), SLOT(KeepCurrent())); // Button to show event display QPushButton *PixDisplay = new QPushButton("Pixel display"); PixDisplay->setFont(QFont("Times", 10, QFont::Bold)); PixDisplay->setToolTip("Show event display window"); Layout->addWidget(PixDisplay, 4, 0); connect(PixDisplay, SIGNAL(clicked()), SLOT(ShowPixelDisplay())); // Event display window Display = new QWidget(); Display->setWindowTitle("Edd - Event display"); Pixel = new QPushButton *[MAXPIXEL]; int Count = 0; double x,y; for (int ix=-22; ix<=22; ix++) for (int iy=-19; iy<=20; iy++) { if (Count == MAXPIXEL) break; x = ix*0.866; y = iy - (ix%2==0 ? 0.5:0); if ((pow(x,2)+pow(y,2) >= 395) && !(abs(ix)==22 && iy==7)) continue; Pixel[Count] = new QPushButton(Display); Pixel[Count]->setAutoFillBackground(true); Pixel[Count]->setGeometry(x*12.5 + 250, y*12.5 + 250, 10, 10); Pixel[Count]->show(); Count++; } connect(Scope, SIGNAL(PixelData(QVector)), SLOT(SetPixelData(QVector))); } TP_DAQ::~TP_DAQ() { delete[] Pixel; } // Translate pixel ID to board, chip, channel void TP_DAQ::TranslatePixelID() { if (Scope->Pixel_to_DRSboard(PixelID->text().toStdString()) == 999999999) { QMessageBox::warning(this, "Edd Message","Pixel ID unknown.",QMessageBox::Ok); } else { Board->setValue(Scope->Pixel_to_DRSboard(PixelID->text().toStdString())); Chip->setValue(Scope->Pixel_to_DRSchip(PixelID->text().toStdString())); Channel->setValue(Scope->Pixel_to_DRSchannel(PixelID->text().toStdString())); } } // Update event scope void TP_DAQ::KeepCurrent() { Scope->AddTrace(Board->value(), Chip->value(), Channel->value()); } // Update event scope void TP_DAQ::UpdateScope(int) { // Update pixel ID PixelID->setText(Scope->DRS_to_Pixel(Board->value(), Chip->value(), Channel->value()).c_str()); // Update first trace Scope->UpdateFirst(Board->value(), Chip->value(), Channel->value()); } // Show/hide pixel display void TP_DAQ::ShowPixelDisplay() { Display->show(); Display->raise(); } void TP_DAQ::SetPixelData(QVector Data) { QwtLinearColorMap Map; for (int i=0; isetPalette(QPalette(Map.color(QwtDoubleInterval(300, 400), Data[i]))); } } // // Evidence page // TP_Evidence::TP_Evidence() { setAttribute(Qt::WA_DeleteOnClose); QGridLayout *Layout = new QGridLayout(this); EddLineDisplay *Line; EddText *Text; Line = new EddLineDisplay("Alarm/Message"); Line->setMaximumWidth(200); Layout->addWidget(Line, 0, 0, 1, 2); Line = new EddLineDisplay("Alarm/MasterAlarm"); Layout->addWidget(Line, 0, 1, 1, 1); Text = new EddText("Alarm/Summary", true); Text->Accumulate = false; Text->setMaximumWidth(200); Text->setMaximumHeight(150); Layout->addWidget(Text, 1, 0, 1, 2); Line = new EddLineDisplay("DColl/Message"); Line->setMaximumWidth(200); Layout->addWidget(Line, 3, 0, 1, 2); Line = new EddLineDisplay("DColl/DataSizeMB"); Layout->addWidget(Line, 4, 0, 1, 1); Line = new EddLineDisplay("DColl/LogSizeMB"); Layout->addWidget(Line, 4, 1, 1, 1); Line = new EddLineDisplay("DColl/CurrentFile"); Line->setMaximumWidth(400); Layout->addWidget(Line, 5, 0, 1, 3); Line = new EddLineDisplay("Config/Message"); Line->setMaximumWidth(200); Layout->addWidget(Line, 6, 0, 1, 2); Line = new EddLineDisplay("Config/ModifyTime"); Line->setMaximumWidth(200); Line->ShowAsTime = true; Layout->addWidget(Line, 7, 0, 1, 1); QPushButton *Button = new QPushButton(); Button->setText("Start DIM browser"); connect(Button, SIGNAL(released()), SLOT(StartDIMBrowser())); Layout->addWidget(Button, 7, 1, 1, 1); Line = new EddLineDisplay("Edd/Rate_kBSec"); Layout->addWidget(Line, 8, 0, 1, 1); } // Start DIM Browser void TP_Evidence::StartDIMBrowser() { QProcess::startDetached("did", QStringList(), QString(getenv("DIMDIR"))+"/linux/"); } //-------------------------------------------------------------------- //*************************** Main GUI ******************************* //-------------------------------------------------------------------- // // All widgets have ultimately Central as parent. // GUI::GUI() { Handler = new EddDim(); // Set features of main window Central = new QWidget(this); setCentralWidget(Central); setStatusBar(new QStatusBar(this)); setWindowTitle("Edd - Evidence Data Display"); // Arrangement in tabs TabWidget = new QTabWidget(Central); TabWidget->setTabsClosable(true); connect(TabWidget, SIGNAL(tabCloseRequested(int)), SLOT(DetachTab(int))); TabWidget->addTab(new TP_DAQ, "Event scope"); TabWidget->addTab(new TP_Bias, "Bias"); TabWidget->addTab(new TP_Feedback, "Feedback"); TabWidget->addTab(new TP_Environment, "Environment"); TabWidget->addTab(new TP_Evidence, "Evidence"); // Menu bar QMenu* Menu = menuBar()->addMenu("&Menu"); Menu->addAction("New history plot", this, SLOT(MenuNewHistory())); Menu->addSeparator(); Menu->addAction("About", this, SLOT(MenuAbout())); Menu->addSeparator(); QAction* QuitAction = Menu->addAction("Quit", qApp, SLOT(quit())); QuitAction->setShortcut(Qt::CTRL + Qt::Key_Q); // Show main window resize(TabWidget->sizeHint()*1.1); show(); } GUI::~GUI() { delete Central; } void GUI::MenuAbout() { QString Rev(SVN_REVISION); Rev.remove(0,1).chop(2); QMessageBox::about(this, "About Edd","Evidence Data Display\n\n" "Written by Oliver Grimm, IPP, ETH Zurich\n" "This version compiled "__DATE__" ("+Rev+")\n\n" "Graphical user interface implemented with Qt and Qwt.\n" "Evidence control system based on DIM (http://dim.web.cern.ch).\n\n" "Comments to oliver.grimm@phys.ethz.ch."); } // Open request for new history plot void GUI::MenuNewHistory() { QStringList List; char *Name, *Format; int Type; bool OK; // Find all services that are not history services and sort getServices("*"); while ((Type = getNextService(Name, Format)) != 0) { if (Type==DimSERVICE) List.append(Name); } List.sort(); // Open dialog and open history window QString Result = QInputDialog::getItem(this, "Edd Request", "Enter DIM service name", List, 0, true, &OK); if (OK && !Result.isEmpty()) { Result = Result.trimmed(); QWidget *Hist = OpenHistory(Result.toAscii().data(), 0); if (Hist != NULL) Hist->show(); } } // Open tab as separate window void GUI::DetachTab(int Tab) { QWidget *W = NULL; QMainWindow *M = new QMainWindow; M->setCentralWidget(new QWidget(M)); M->setStatusBar(new QStatusBar(M)); switch(Tab) { case 0: W = new TP_DAQ; break; case 1: W = new TP_Bias; break; case 2: W = new TP_Feedback; break; case 3: W = new TP_Environment; break; case 4: W = new TP_Evidence; break; default: break; } if (W == NULL) { delete M->centralWidget(); delete M; return; } W->setParent(M); M->resize(size()); M->setWindowTitle("Edd - " + TabWidget->tabText(Tab)); M->show(); } // Quit application when clicking close button on window void GUI::closeEvent(QCloseEvent *) { qApp->quit(); } //--------------------------------------------------------------------- //**************************** Main program *************************** //--------------------------------------------------------------------- int main(int argc, char *argv[]) { QApplication app(argc, argv); GUI MainWindow; return app.exec(); }