/* ============================================================ Edd - Evidence Data Display Qt-based graphical user interface for the Evidence contron system Edd_Indicator changes its background colour in case it display a DIM status service December 2009, 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 GUI *Handler; ////////////////////////////////////////// // Text display for arbitary DIM service// ////////////////////////////////////////// // Constructor Edd_Indicator::Edd_Indicator(QString DIMService, QWidget *P): QLineEdit(P) { // Widget properties setReadOnly(true); setMaximumWidth(100); ShowAsTime = false; // Connect to DIM handler if (connect(Handler, SIGNAL(YEP(DimInfo*, int, QString, QByteArray, QString)), SLOT(Update(DimInfo*, int, QString, QByteArray, QString))) == false) { printf("Failed connection for %s\n", DIMService.toAscii().data()); } // Context menu Menu = new QMenu(this); Menu->addAction("Open history", this, SLOT(MenuOpenHistory())); Menu->addAction("Copy service", this, SLOT(MenuCopyService())); // DIM client Data = new DimStampedInfo(DIMService.toAscii().data(), INT_MAX, (char *) NO_LINK, Handler); } // Destructor Edd_Indicator::~Edd_Indicator() { delete Data; } // Update widget void Edd_Indicator::Update(DimInfo *Info, int Time, QString Format, QByteArray Data, QString Text) { if (Info != this->Data) return; QPalette Pal = palette(); // Check if service available if (Time == -1) { setText("n/a"); setStatusTip(QString("%1: unavailable").arg(Info->getName())); Pal.setColor(QPalette::Base, Qt::lightGray); } else { // If this is a status indicator, adapt background colour if (Data.size() == Text.size()+2) { switch (Data[Text.size() + 2]) { case 0: Pal.setColor(QPalette::Base, Qt::white); break; case 1: Pal.setColor(QPalette::Base, Qt::cyan); break; case 2: Pal.setColor(QPalette::Base, Qt::red); break; case 3: Pal.setColor(QPalette::Base, Qt::red); break; default: break; } } else Pal.setColor(QPalette::Base, Qt::white); if (!ShowAsTime) setText(Text); else setText(QDateTime::fromTime_t(Text.toInt()).toString()); // Update status tip setStatusTip(QString("%1: Last update %2 Format '%3'").arg(Info->getName(), QDateTime::fromTime_t(Time).toString()).arg(Format)); } setPalette(Pal); } // Open plot if mouse release within widget void Edd_Indicator::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 == LastPlot) { Widget->activateWindow(); Widget->raise(); return; } } // If not, open new plot Edd_Indicator::MenuOpenHistory(); } // Handling of mouse press event: Register start position for drag void Edd_Indicator::mousePressEvent(QMouseEvent *Event) { if (Event->button() == Qt::LeftButton) dragStart = Event->pos(); } // Handling of dragging (Drag and MimeData will be deleted by Qt) void Edd_Indicator::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; MimeData->setText(QString(Data->getName())); Drag->setMimeData(MimeData); Drag->exec(); } // // Opening context menu // void Edd_Indicator::contextMenuEvent(QContextMenuEvent *Event) { Menu->exec(Event->globalPos()); } // Open history plot void Edd_Indicator::MenuOpenHistory() { LastPlot = new Edd_Plot(Data->getName()); LastPlot->show(); } // Copy service name void Edd_Indicator::MenuCopyService() { QApplication::clipboard()->setText(QString(Data->getName())); } ////////////////////////////////// // History plot for DIM service // ////////////////////////////////// // // Constructor // Edd_Plot::Edd_Plot(QString DIMService, QWidget *P): QwtPlot(P) { setAcceptDrops(true); setAttribute(Qt::WA_DeleteOnClose); // Graph properties setAutoReplot(false); QwtText XAxisTitle("Time (RJD-55200)"); XAxisTitle.setFont(QFont("Helvetica", 10)); setAxisTitle(QwtPlot::xBottom, XAxisTitle); setCanvasBackground(QColor(Qt::yellow)); Zoomer = new QwtPlotZoomer(QwtPlot::xBottom,QwtPlot::yLeft,canvas()); connect(Zoomer, SIGNAL(zoomed(const QwtDoubleRect &)), this, SLOT(HandleZoom(const QwtDoubleRect &))); Panner = new QwtPlotPanner(canvas()); Panner->setMouseButton(Qt::LeftButton, Qt::ShiftModifier); Grid = new QwtPlotGrid; Grid->setMajPen(QPen(Qt::gray, 0, Qt::DotLine)); Grid->attach(this); Legend = new QwtLegend(); insertLegend(Legend, QwtPlot::TopLegend); // Connect to DIM handler if (connect(Handler, SIGNAL(YEP(DimInfo *, int, QString, QByteArray, QString)), SLOT(Update(DimInfo *, int, QString, QByteArray, QString))) == false) { printf("Failed connection for %s\n", DIMService.toAscii().data()); } // Context menu Menu = new QMenu(this); 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->addSeparator(); Menu->addAction("Paste service", this, SLOT(MenuPasteService())); // DIM client if (!DIMService.isEmpty()) AddService(DIMService); } // // Destructor (items with parent widget are automatically deleted) // Edd_Plot::~Edd_Plot() { for (int i=0; igetName()) { QMessageBox::warning(this, "Edd Message",HistName+" already present",QMessageBox::Ok); return; } } // Generate new curve and subscribe to service struct PlotItem New; New.Signal = new QwtPlotCurve; New.Signal->attach(this); New.Signal->setTitle(HistName); New.Signal->setPen(QColor(LineColors[Items.size() % (sizeof(LineColors)/sizeof(Qt::GlobalColor))])); New.x = NULL; New.y = NULL; New.Count = 0; New.Data = new DimStampedInfo(HistName.toAscii().data(), (char *) NO_LINK, Handler); New.LiveData = new DimStampedInfo(Name.toAscii().data(), (char *) NO_LINK, Handler); Items.append(New); } // Update widget (must happen in GUI thread) void Edd_Plot::Update(DimInfo *Info, int Time, QString Format, QByteArray Data, QString Text) { // Check if service available if (Time == -1) { setStatusTip(QString("%1: unavailable").arg(Info->getName())); } // Lock before accessing Items list QMutexLocker Locker(&Mutex); // Determine which plot item this call belongs to int ItemNo; for (ItemNo=0; ItemNo Items[ItemNo].y[i]) Smallest = Items[ItemNo].y[i]; } Items[ItemNo].Smallest = Smallest; Items[ItemNo].Largest = Largest; Items[ItemNo].Count = Count; // Clear local history Items[ItemNo].Live.clear(); // Update status tip QDateTime Timex = QDateTime::fromTime_t(Time); StatusTip = QString("%1: Last update %2 Format '%3'").arg(Info->getName(), Timex.toString()).arg(Format); } else if (Info == Items[ItemNo].LiveData) { // This is a live service // Limit size of list if (Items[ItemNo].Live.size() > 1000) Items[ItemNo].Live.removeFirst(); // Append data struct EvidenceHistoryItem Data; Data.Seconds = Time; Data.Value = atof(Text.toAscii().data()); Items[ItemNo].Live.append(Data); // Update largest and smallest value if (Data.Value > Items[ItemNo].Largest) Items[ItemNo].Largest = Data.Value; if (Data.Value < Items[ItemNo].Smallest) Items[ItemNo].Smallest = Data.Value; } Locker.unlock(); UpdatePlot(); } // // Update all curves in plot // void Edd_Plot::UpdatePlot() { static QwtSymbol Symbol, Sym1; Symbol.setStyle(QwtSymbol::Ellipse); Symbol.setSize(4); if (!YLogAction->isChecked()) { setAxisScaleEngine(QwtPlot::yLeft, new QwtLinearScaleEngine); } else setAxisScaleEngine(QwtPlot::yLeft, new QwtLog10ScaleEngine); // Lock before accessing Items list QMutexLocker Locker(&Mutex); setStatusTip(StatusTip); for (int ItemNo=0; ItemNoisChecked()) Items[ItemNo].Signal->setSymbol(Symbol); else Items[ItemNo].Signal->setSymbol(Sym1); int DataPoints = Items[ItemNo].Count + Items[ItemNo].Live.size(); if (DataPoints == 0) continue; double *x = new double [DataPoints]; double *y = new double [DataPoints]; // Adapt time scale and normalize y scale if requested 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 datas Items[ItemNo].Signal->setData(x, y, DataPoints); Items[ItemNo].Signal->show(); Zoomer->setZoomBase(Items[ItemNo].Signal->boundingRect()); delete[] x; delete[] y; } replot(); } // // Reset graph axes to autoscale when fully unzoomed // void Edd_Plot::HandleZoom(const QwtDoubleRect &) { if(Zoomer->zoomRectIndex() == 0) { setAxisAutoScale(QwtPlot::xBottom); setAxisAutoScale(QwtPlot::yLeft); } } // // Drag and drop methods // void Edd_Plot::dragEnterEvent(QDragEnterEvent *Event) { if (Event->mimeData()->hasFormat("text/plain")) Event->acceptProposedAction(); } void Edd_Plot::dropEvent(QDropEvent *Event) { AddService(Event->mimeData()->text().toAscii().data()); } // // Opening context menu // void Edd_Plot::contextMenuEvent(QContextMenuEvent *Event) { Menu->exec(Event->globalPos()); } void Edd_Plot::MenuZoomOut() { Zoomer->zoom(0); UpdatePlot(); } // Remove all items except last void Edd_Plot::MenuSingleTrace() { // Lock before accessing Items list QMutexLocker Locker(&Mutex); while (Items.size() > 1) { delete Items.last().Data; delete Items.last().LiveData; delete Items.last().Signal; delete[] Items.last().x; delete[] Items.last().y; Items.takeLast(); } Locker.unlock(); UpdatePlot(); } // Save data of plot as test void Edd_Plot::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; } // Lock before accessing Items list QMutexLocker Locker(&Mutex); QTextStream Stream(&File); // Write x and y data for all signals to file for (int ItemNo=0; ItemNogetName() << endl; for (int i=0; idataSize(); i++) { Stream << (int) Items[ItemNo].x[i] << " " << Items[ItemNo].Signal->y(i) << endl; } } } // Print plot void Edd_Plot::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 Edd_Plot::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()); } } } // Add new service by pasting name void Edd_Plot::MenuPasteService() { AddService(QApplication::clipboard()->text().toAscii().data()); } ////////////////// // Text display // ////////////////// // Constructor Edd_Textout::Edd_Textout(QString DIMService, QWidget *P): QTextEdit(P) { // Widget properties setReadOnly(true); setAutoFillBackground(true); document()->setMaximumBlockCount(1000); Accumulate = true; // Connect to DIM handler if (connect(Handler, SIGNAL(YEP(DimInfo*, int, QString, QByteArray, QString)), SLOT(Update(DimInfo*, int, QString, QByteArray, QString))) == false) { printf("Failed connection for %s\n", DIMService.toAscii().data()); } // DIM client Data = new DimStampedInfo(DIMService.toAscii().data(), INT_MAX, (char *) NO_LINK, Handler); } // Destructor Edd_Textout::~Edd_Textout() { delete Data; } // Handling of DIM service update void Edd_Textout::Update(DimInfo *Info, int Time, QString Format, QByteArray, QString Text) { if (Info != this->Data) return; QPalette Pal = palette(); // Check if service available if (Time == -1) { setStatusTip(QString("%1: unavailable").arg(Info->getName())); Pal.setColor(QPalette::Base, Qt::lightGray); } else { Pal.setColor(QPalette::Base, Qt::white); // Clear display in case text should not accumulate if (Accumulate == false) clear(); // Add if service contains only a string if (Format == "C") insertPlainText(Text); // Update status tip setStatusTip(QString("%1: Last update %2 Format '%3'").arg(Info->getName(), QDateTime::fromTime_t(Time).toString()).arg(Format)); } setPalette(Pal); } // // Main GUI (all widgets have ultimately Central as parent) // GUI::GUI() { Handler = this; // Set features of main window Central = new QWidget(this); setCentralWidget(Central); setStatusBar(new QStatusBar(this)); setGeometry(100, 100, 800, 650); setWindowTitle("Edd - Evidence Data Display"); Edd_Indicator *Value; Edd_Plot *Graph; Edd_Textout *Textout; QString Text; // TextBox for value //Value = new Edd_Indicator((char *) "SQM/NSB", Central); //Graph = new Edd_Plot((char *) "SQM/NSB", Central); //Graph->AddService("BIAS/VOLT/ID00/00-000"); // Clock (updated every second) //Clock = new QwtAnalogClock(Central); //Clock->scaleDraw()->setPenWidth(3); //Clock->setLineWidth(6); //Clock->setFrameShadow(QwtDial::Sunken); //Clock->setGeometry(0,0,10,10); //Clock->setTime(); //QTimer *Timer = new QTimer(Clock); //Timer->connect(Timer, SIGNAL(timeout()), Clock, SLOT(setCurrentTime())); //Timer->start(1000); MainWidget = new QWidget(); MainLayout = new QGridLayout(MainWidget); Value = new Edd_Indicator("DColl/DataSizekB"); MainLayout->addWidget(Value, 3, 5, 1, 1); Value = new Edd_Indicator("DColl/LogSizekB"); MainLayout->addWidget(Value, 4, 5, 1, 1); Value = new Edd_Indicator("Config/ModifyTime"); Value->setMaximumWidth(200); Value->ShowAsTime = true; MainLayout->addWidget(Value, 5, 5, 1, 1); Value = new Edd_Indicator("Alarm/MasterAlarm"); MainLayout->addWidget(Value, 2, 0, 1, 1); Textout = new Edd_Textout("Alarm/Summary"); Textout->Accumulate = false; MainLayout->addWidget(Textout, 3, 0, 2, 1); // Layout of all widgets //MainLayout->addWidget(Value, 0, 0, 1, 1); //MainLayout->addWidget(Clock, 0, 1, 1, 1); //MainLayout->addWidget(Graph, 1, 0, 1, 2); //MainLayout->setColumnStretch(1, 10); //MainLayout->addWidget(Clock, 0, 1, 1, 1); //MainLayout->addWidget(Graph1, 2, 0, 1, 2); // Bias voltage page BiasWidget = new QWidget(); BiasLayout = new QGridLayout(BiasWidget); Graph = new Edd_Plot(); for (int i=0; i<18; i++) { Text = Text.sprintf("BIAS/VOLT/ID00/00-%.3d",i); Value = new Edd_Indicator(Text); BiasLayout->addWidget(Value, i%9+1, 0+i/9, 1, 1); Graph->AddService(Text); Text = Text.sprintf("BIAS/VOLT/ID00/01-%.3d",i); Value = new Edd_Indicator(Text); BiasLayout->addWidget(Value, i%9+1, 2+i/9, 1, 1); Graph->AddService(Text); } BiasLayout->addWidget(Graph, 0, 4, 12, 3); Value = new Edd_Indicator("BIAS/Status"); Value->setMaximumWidth(200); BiasLayout->addWidget(Value, 0, 0, 1, 3); Textout = new Edd_Textout("BIAS/Textout"); Textout->setFixedWidth(400); BiasLayout->addWidget(Textout, 10, 0, 4, 4); // Environment page EnvironmentWidget = new QWidget(); EnvironmentLayout = new QGridLayout(EnvironmentWidget); Value = new Edd_Indicator("ARDUINO/Status"); Value->setMaximumWidth(200); EnvironmentLayout->addWidget(Value, 0, 0, 1, 3); Graph = new Edd_Plot(); for (int i=0; i<10; i++) { Text = Text.sprintf("ARDUINO/VAL%.2d", i); Value = new Edd_Indicator(Text); EnvironmentLayout->addWidget(Value, i%5+1, i/5, 1, 1); Graph->AddService(Text); } EnvironmentLayout->addWidget(Graph, 0, 3, 7, 4); Value = new Edd_Indicator("SQM/NSB"); EnvironmentLayout->addWidget(Value, 6, 0, 1, 1); // Tab widget TabWidget = new QTabWidget(Central); TabWidget->addTab(MainWidget, "&Main"); TabWidget->addTab(BiasWidget, "&Bias"); TabWidget->addTab(EnvironmentWidget, "&Environment"); // 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 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 && strstr(Name, ".hist")==NULL) 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(); if (Result.endsWith(".hist")) Result.chop(5); Edd_Plot *Plot = new Edd_Plot(Result); Plot->show(); } } // Handling of DIM service update void GUI::infoHandler() { // Check if service available if (getInfo()->getSize() == strlen(NO_LINK)+1 && strcmp(getInfo()->getString(), NO_LINK) == 0) { YEP(getInfo(), -1); } else { char *Txt = EvidenceServer::ToString(getInfo()); YEP(getInfo(), getInfo()->getTimestamp(), getInfo()->getFormat(), QByteArray((char *) getInfo()->getData(), getInfo()->getSize()), QString(Txt)); free(Txt); } } //--------------------------------------------------------------------- //************************ All functions **************************** //------------------------------------------------------------------- // 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(); }