Index: /Evidence/Edd/Edd.cc
===================================================================
--- /Evidence/Edd/Edd.cc	(revision 137)
+++ /Evidence/Edd/Edd.cc	(revision 138)
@@ -10,4 +10,9 @@
 
 #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};
 
 //////////////////////////////////////////
@@ -16,5 +21,5 @@
 
 // Constructor
-FCS_Indicator::FCS_Indicator(char *DIMService, QWidget *P): QLineEdit(P) {
+Edd_Indicator::Edd_Indicator(char *DIMService, QWidget *P): QLineEdit(P) {
 
   ServiceName = new char [strlen(DIMService)+1];
@@ -31,5 +36,5 @@
 
 // Destructor
-FCS_Indicator::~FCS_Indicator() {
+Edd_Indicator::~Edd_Indicator() {
   delete Data;
   delete[] ServiceName;
@@ -37,5 +42,5 @@
 
 // Handling of DIM service update
-void FCS_Indicator::infoHandler() {
+void Edd_Indicator::infoHandler() {
 
   // Check if service available
@@ -74,8 +79,8 @@
 
 // Open plot if mouse release within widget
-void FCS_Indicator::mouseReleaseEvent(QMouseEvent *Event) {
+void Edd_Indicator::mouseReleaseEvent(QMouseEvent *Event) {
 
   if (Event->button()==Qt::LeftButton && contentsRect().contains(Event->pos())) {
-    FCS_Plot *Graph = new FCS_Plot(ServiceName, NULL);
+    Edd_Plot *Graph = new Edd_Plot(ServiceName, NULL);
     Graph->show();
   }
@@ -83,5 +88,5 @@
 
 // Handling of mouse press event: Register start position for drag
-void FCS_Indicator::mousePressEvent(QMouseEvent *Event) {
+void Edd_Indicator::mousePressEvent(QMouseEvent *Event) {
 
   if (Event->button() == Qt::LeftButton) dragStart = Event->pos();
@@ -89,5 +94,5 @@
 
 // Handling of dragging (Drag and MimeData will be deleted by Qt)
-void FCS_Indicator::mouseMoveEvent(QMouseEvent *Event) {
+void Edd_Indicator::mouseMoveEvent(QMouseEvent *Event) {
 
   if ((Event->buttons() & Qt::LeftButton) == 0) return;
@@ -109,19 +114,12 @@
 // Constructor
 //
-FCS_Plot::FCS_Plot(char *DIMService, QWidget *P): QwtPlot(P) {
-
-  // Generate name of DIM history service
-  if (asprintf(&Name, "%s.hist", DIMService) == -1) {
-    QMessageBox::warning(this, "Error","Could not generate service name with asprintf()",QMessageBox::Ok);
-    Name = NULL;
-    return;
-  }
-
-  // Enable drag&drop
+Edd_Plot::Edd_Plot(char *DIMService, QWidget *P): QwtPlot(P) {
+
   setAcceptDrops(true);
 
   // Graph properties
-  setAxisTitle(QwtPlot::xBottom, "Time");
-  setAxisTitle(QwtPlot::yLeft, Name);
+  QwtText XAxisTitle("Time (RJD-55000)");
+  XAxisTitle.setFont(QFont("Helvetica", 10));
+  setAxisTitle(QwtPlot::xBottom, XAxisTitle);
   setAutoReplot(false);
   setCanvasBackground(QColor(Qt::yellow));
@@ -134,17 +132,18 @@
   Grid->setMajPen(QPen(Qt::gray, 0, Qt::DotLine));
   Grid->attach(this);
-  Signal = new QwtPlotCurve;
-  Signal->attach(this);
-
+  Legend = new QwtLegend();
+  insertLegend(Legend, QwtPlot::TopLegend);
+    
   // Threads may not call replot directly, but only through this signal
-  connect(this, SIGNAL(YEP()), this, SLOT(replot()));
+  connect(this, SIGNAL(YEP()), this, SLOT(UpdatePlot()));
 
   // Context menu
   Menu = new QMenu(this);
-  YLogAction = Menu->addAction("y scale log", this, SLOT(MenuYScale()));
+  YLogAction = Menu->addAction("y scale log", this, SLOT(UpdatePlot()));
   YLogAction->setCheckable(true);
+  NormAction = Menu->addAction("Normalize", this, SLOT(UpdatePlot()));
+  NormAction->setCheckable(true);
   Menu->addAction("Zoom out", this, SLOT(MenuZoomOut()));
-  NormAction = Menu->addAction("Normalize");
-  NormAction->setCheckable(true);
+  Menu->addAction("Single trace", this, SLOT(MenuSingleTrace()));
   Menu->addSeparator();
   Menu->addAction("Save plot", this, SLOT(MenuSave()));
@@ -152,5 +151,5 @@
 
   // DIM client
-  Data = new DimStampedInfo(Name, NO_LINK, this); 
+  AddService(DIMService);
 }
 
@@ -158,8 +157,38 @@
 // Destructor (items with parent widget are automatically deleted)
 //
-FCS_Plot::~FCS_Plot() {
-  free(Name);
-
-  delete Data;	delete Signal;  delete Grid;
+Edd_Plot::~Edd_Plot() {
+
+  for (int i=0; i<Items.size(); i++) {
+    delete Items[i].Data;
+    delete Items[i].Signal;
+    delete[] Items[i].x;
+    delete[] Items[i].y;
+  }  
+  delete Grid;
+}
+
+//
+// Add history service to plot
+//
+void Edd_Plot::AddService(char *DIMService) {
+
+  // Generate name of DIM service
+  QString Name(DIMService);
+  Name.append(".hist");
+
+  // Lock this thread (because infoHandler() might be called before list insertion)
+  QMutexLocker Locker(&Mutex);
+
+  // Generate new curve and subscribe to service
+  struct PlotItem New;
+  New.Signal = new QwtPlotCurve;
+  New.Signal->attach(this);
+  New.Signal->setTitle(Name);
+  New.Signal->setPen(QColor(LineColors[Items.size()%(sizeof(LineColors)/sizeof(Qt::GlobalColor))]));
+  New.x = NULL;
+  New.y = NULL;
+  New.Data = new DimStampedInfo(Name.toAscii(), NO_LINK, this); 
+
+  Items.append(New);
 }
 
@@ -167,29 +196,39 @@
 // Handle update of DIM service
 //
-void FCS_Plot::infoHandler() {
+void Edd_Plot::infoHandler() {
 
   // Check if service available
   if (getInfo()->getSize() == strlen(NO_LINK)+1 && strcmp(getInfo()->getString(), NO_LINK) == 0) {
-    setStatusTip(QString("%1:  unavailable").arg(Name));
+    setStatusTip(QString("%1:  unavailable").arg(getInfo()->getName()));
     return;
   }
 
+  // Lock this thread (see AddService())
+  QMutexLocker Locker(&Mutex);
+
+  // Determine which plot item this call belongs to
+  int ItemNo;
+  for (ItemNo=0; ItemNo<Items.size(); ItemNo++) if (Items[ItemNo].Data == getInfo()) break;
+  if (ItemNo == Items.size()) return;  // safety check
+  
   EvidenceHistoryItem *Curr = (EvidenceHistoryItem *) getInfo()->getData();
-  int Count=0, Items = getInfo()->getSize()/sizeof(struct EvidenceHistoryItem);
-
-  double *x = new double [Items];
-  double *y = new double [Items];
+  int Count=0, DataPoints = getInfo()->getSize()/sizeof(struct EvidenceHistoryItem);
+
+  delete[] Items[ItemNo].x;
+  delete[] Items[ItemNo].y;
+  Items[ItemNo].x = new double [DataPoints];
+  Items[ItemNo].y = new double [DataPoints];
 
   // Find oldest item
   int Oldest;
-  for (Oldest=1; Oldest<Items; Oldest++) {
+  for (Oldest=1; Oldest<DataPoints; Oldest++) {
     if (Curr[Oldest].Seconds < Curr[Oldest-1].Seconds) break;
   }
 
   // Plot data starting with oldest value
-  for (int i=0; i<Items; i++) {
-    x[Count] = (double) Curr[(i+Oldest)%Items].Seconds;
-    y[Count] = Curr[(i+Oldest)%Items].Value;
-    if (x[Count] != 0) Count++;
+  for (int i=0; i<DataPoints; i++) {
+    Items[ItemNo].x[Count] = (double) Curr[(i+Oldest)%DataPoints].Seconds;
+    Items[ItemNo].y[Count] = Curr[(i+Oldest)%DataPoints].Value;
+    if (Items[ItemNo].x[Count] != 0) Count++;
   }
   
@@ -197,29 +236,55 @@
   double Smallest = DBL_MAX, Largest = DBL_MIN;
   for (int i=0; i<Count; i++) {
-    if (Largest < y[i]) Largest = y[i];
-    if (Smallest > y[i]) Smallest = y[i];
-  }
-  
-  // Adapt time scale and normalize y scale if requested
-  for (int i=Count-1; i>=0; i--) {
-    x[i] = x[i]-x[0];
-    if (NormAction->isChecked()) {
-      if (Smallest != Largest) y[i] = (y[i] - Smallest)/(Largest-Smallest);
-      else y[i] = 1;
-    }  
-  }
-  
-  // Plot datas
-  Signal->setData(x, y, Count);
-  Signal->show();
-  Zoomer->setZoomBase(Signal->boundingRect());
-  emit YEP(); // Direct drawing within thread illegal!
-
-  delete[] x;
-  delete[] y;
-
+    if (Largest < Items[ItemNo].y[i]) Largest = Items[ItemNo].y[i];
+    if (Smallest > Items[ItemNo].y[i]) Smallest = Items[ItemNo].y[i];
+  }
+  Items[ItemNo].Smallest = Smallest;
+  Items[ItemNo].Largest = Largest;
+  Items[ItemNo].Count = Count;
+  
   // Update status tip
   QDateTime Time = QDateTime::fromTime_t(getInfo()->getTimestamp()); 
-  setStatusTip(QString("%1:  Last update %2  Format '%3'").arg(Name, Time.toString()).arg(getInfo()->getFormat()));
+  setStatusTip(QString("%1:  Last update %2  Format '%3'").arg(getInfo()->getName(), Time.toString()).arg(getInfo()->getFormat()));
+
+  emit(YEP());
+}
+
+
+//
+// Update all curves in plot
+//
+void Edd_Plot::UpdatePlot() {
+
+  QMutexLocker Locker(&Mutex);
+
+  if (!YLogAction->isChecked()) {
+    setAxisScaleEngine(QwtPlot::yLeft, new QwtLinearScaleEngine);
+  }
+  else setAxisScaleEngine(QwtPlot::yLeft, new QwtLog10ScaleEngine);
+
+  for (int ItemNo=0; ItemNo<Items.size(); ItemNo++) {
+  
+    double *x = new double [Items[ItemNo].Count];
+    double *y = new double [Items[ItemNo].Count];
+
+    // Adapt time scale and normalize y scale if requested
+    for (int i=0; i<Items[ItemNo].Count; i++) {
+      x[i] = Items[ItemNo].x[i]/86400 + 40587.5 - 55000;
+      y[i] = Items[ItemNo].y[i];
+      if (NormAction->isChecked()) {
+	if (Items[ItemNo].Smallest != Items[ItemNo].Largest) y[i] = (Items[ItemNo].y[i] - Items[ItemNo].Smallest)/(Items[ItemNo].Largest-Items[ItemNo].Smallest);
+	else y[i] = 1;
+      }  
+    }
+
+    // Plot datas
+    Items[ItemNo].Signal->setData(x, y, Items[ItemNo].Count);
+    Items[ItemNo].Signal->show();
+    Zoomer->setZoomBase(Items[ItemNo].Signal->boundingRect());
+
+    delete[] x;
+    delete[] y;
+  }
+  replot();
 }
 
@@ -227,5 +292,5 @@
 // Reset graph axes to autoscale when fully unzoomed
 //
-void FCS_Plot::HandleZoom(const QwtDoubleRect &) {
+void Edd_Plot::HandleZoom(const QwtDoubleRect &) {
 
   if(Zoomer->zoomRectIndex() == 0) {
@@ -236,16 +301,15 @@
 
 //
-// Drag and drop methode
-//
-
-void FCS_Plot::dragEnterEvent(QDragEnterEvent *Event) {
+// Drag and drop methods
+//
+
+void Edd_Plot::dragEnterEvent(QDragEnterEvent *Event) {
     
   if (Event->mimeData()->hasFormat("text/plain")) Event->acceptProposedAction();
 }
 
-void FCS_Plot::dropEvent(QDropEvent *Event) {
-
- FCS_Plot *Graph = new FCS_Plot(Event->mimeData()->text().toAscii().data(), NULL);
- Graph->show();
+void Edd_Plot::dropEvent(QDropEvent *Event) {
+
+  AddService(Event->mimeData()->text().toAscii().data());
 }
     
@@ -253,19 +317,10 @@
 // Context menu and its functions
 //
-void FCS_Plot::contextMenuEvent(QContextMenuEvent *Event) {
+void Edd_Plot::contextMenuEvent(QContextMenuEvent *Event) {
 
   Menu->exec(Event->globalPos());
 }
 
-void FCS_Plot::MenuYScale() {
-
-  if (!YLogAction->isChecked()) {
-    setAxisScaleEngine(QwtPlot::yLeft, new QwtLinearScaleEngine);
-  }
-  else setAxisScaleEngine(QwtPlot::yLeft, new QwtLog10ScaleEngine);
-  emit YEP();
-}
-
-void FCS_Plot::MenuZoomOut() {
+void Edd_Plot::MenuZoomOut() {
 
   Zoomer->zoom(0);
@@ -273,5 +328,19 @@
 }
 
-void FCS_Plot::MenuPrint() {
+void Edd_Plot::MenuSingleTrace() {
+
+  QMutexLocker Locker(&Mutex);
+
+  while (Items.size() > 1) {  
+    delete Items.last().Data;
+    delete Items.last().Signal;
+    delete[] Items.last().x;
+    delete[] Items.last().y;
+    Items.takeLast();
+  }
+  emit(YEP());
+}
+
+void Edd_Plot::MenuPrint() {
 
   QPrinter *Printer = new QPrinter;
@@ -285,5 +354,5 @@
 }
 
-void FCS_Plot::MenuSave() {
+void Edd_Plot::MenuSave() {
 
   QString Filename = QFileDialog::getSaveFileName(this,
@@ -309,9 +378,9 @@
   
    // TextBox for value
-  Value = new FCS_Indicator((char *) "SQM/NSB", Central);
-  Graph = new FCS_Plot((char *) "SQM/NSB", Central);
-
-  Value1 = new FCS_Indicator((char *) "BIAS/VOLT/ID00/00-000", Central);
-  //Graph1 = new FCS_Plot((char *) "BIAS/VOLT/ID00/00-000", Central);
+  Value = new Edd_Indicator((char *) "SQM/NSB", Central);
+  Graph = new Edd_Plot((char *) "SQM/NSB", Central);
+
+  Value1 = new Edd_Indicator((char *) "BIAS/VOLT/ID00/00-000", Central);
+  //Graph1 = new Edd_Plot((char *) "BIAS/VOLT/ID00/00-000", Central);
 
   // Clock (updated every second)
@@ -345,11 +414,22 @@
     char *Item;
     if (asprintf(&Item, "BIAS/VOLT/ID00/00-%.3d", i) != -1) {
-      Value = new FCS_Indicator(Item, NULL);
+      Value = new Edd_Indicator(Item, NULL);
       BiasLayout->addWidget(Value, i%10, 0+i/10, 1, 1);      
       free(Item);
     }
     if (asprintf(&Item, "BIAS/VOLT/ID00/01-%.3d", i) != -1) {
-      Value = new FCS_Indicator(Item, NULL);
+      Value = new Edd_Indicator(Item, NULL);
       BiasLayout->addWidget(Value, i%10, 2+i/10, 1, 1);      
+      free(Item);
+    }
+  }
+
+  EnvironmentWidget = new QWidget();
+  EnvironmentLayout = new QGridLayout(EnvironmentWidget);
+  for (int i=0; i<10; i++) {
+    char *Item;
+    if (asprintf(&Item, "ARDUINO/VAL%.2d", i) != -1) {
+      Value = new Edd_Indicator(Item, NULL);
+      EnvironmentLayout->addWidget(Value, i%5, i/5, 1, 1);      
       free(Item);
     }
@@ -360,12 +440,28 @@
   TabWidget->addTab(MainWidget, "&Main");
   TabWidget->addTab(BiasWidget, "&Bias");
-  //TabWidget->addTab(EventHeaderDisplay, "&Environment");
+  TabWidget->addTab(EnvironmentWidget, "&Environment");
+
+  // Menu bar
+  QMenu* Menu = menuBar()->addMenu("&Menu");
+  Menu->addAction("About", this, SLOT(MenuAbout()));
+  Menu->addSeparator();
+  QAction* QuitAction = Menu->addAction("Quit", qApp, SLOT(quit()));
+  QuitAction->setShortcut(Qt::CTRL + Qt::Key_Q);
 }  
     
-
 GUI::~GUI() {
   delete Central;
 }
 
+void GUI::MenuAbout() {
+  QMessageBox::about(this, "About Edd","Evidence Data Display\n\n"
+    "Written by Oliver Grimm, IPP, ETH Zurich\n"
+    "EMail: oliver.grimm@phys.ethz.ch\n"
+    "This version compiled "__DATE__".\n"
+    "subversion revision "SVN_REVISION".\n\n"
+    "Graphical user interface implemented with Qt.\n"
+    "Evidence control system based on DIM (http://dim.web.cern.ch).");
+     printf("Revision: $Revision$\n");
+}
 
 //---------------------------------------------------------------------
Index: /Evidence/Edd/Edd.h
===================================================================
--- /Evidence/Edd/Edd.h	(revision 137)
+++ /Evidence/Edd/Edd.h	(revision 138)
@@ -1,4 +1,7 @@
+#ifndef EDD_H_SEEN
+#define EDD_H_SEEN
+
 #include <QtGui>
-
+ 
 #include <qwt_plot.h>
 #include <qwt_plot_curve.h>
@@ -10,4 +13,6 @@
 #include <qwt_scale_widget.h>
 #include <qwt_plot_layout.h>
+#include <qwt_legend.h>
+#include <qwt_legend_item.h>
 
 #include <limits.h>
@@ -18,7 +23,8 @@
 
 #define NO_LINK "__&DIM&NOLINK&__" // for checking if DIMserver is alive
-
+#define SVN_REVISION "$Revision$"
+		  
 // General indicator for DIM service
-class FCS_Indicator: public QLineEdit, public DimClient, public DimBrowser {
+class Edd_Indicator: public QLineEdit, public DimClient, public DimBrowser {
     Q_OBJECT
 
@@ -34,6 +40,6 @@
 	
   public:
-    FCS_Indicator(char*, QWidget* = NULL);
-    ~FCS_Indicator();
+    Edd_Indicator(char*, QWidget* = NULL);
+    ~Edd_Indicator();
 
   signals:
@@ -42,11 +48,20 @@
 
 // Graph class for history display
-class FCS_Plot: public QwtPlot, public DimClient {
+class Edd_Plot: public QwtPlot, public DimClient {
     Q_OBJECT
 
-    char *Name;
-    DimInfo *Data;
-    void infoHandler();
+    struct PlotItem {
+      DimInfo *Data;
+      QwtPlotCurve *Signal;
+      double *x;
+      double *y;
+      int Count;
+      double Smallest;
+      double Largest;
+    };
 
+    QList<struct PlotItem> Items;
+    QMutex Mutex;
+    
     QMenu *Menu;
     QAction *YLogAction;
@@ -55,19 +70,22 @@
     QwtPlotPanner *Panner;
     QwtPlotGrid *Grid;
-    QwtPlotCurve *Signal;
     QwtPlotZoomer *Zoomer;
-
+    QwtLegend *Legend;
+    
+    void AddService(char *);
+    void infoHandler();    
     void dragEnterEvent(QDragEnterEvent *);
     void dropEvent(QDropEvent *);
 
   public:
-    FCS_Plot(char*, QWidget* = NULL);
-    ~FCS_Plot();
+    Edd_Plot(char *, QWidget * = NULL);
+    ~Edd_Plot();
 
-  private slots:    
+  private slots:
+    void UpdatePlot();
     void HandleZoom(const QwtDoubleRect &);
     void contextMenuEvent(QContextMenuEvent *);    
-    void MenuYScale();
     void MenuZoomOut();
+    void MenuSingleTrace();        
     void MenuSave();
     void MenuPrint();
@@ -82,17 +100,22 @@
     Q_OBJECT
 
-    FCS_Indicator *Value, *Value1;
-    FCS_Plot *Graph, *Graph1;
+    Edd_Indicator *Value, *Value1;
+    Edd_Plot *Graph, *Graph1;
     QwtAnalogClock *Clock;
 	
-    QWidget *Central, *MainWidget, *BiasWidget;
-    QGridLayout *MainLayout, *BiasLayout;
+    QWidget *Central, *MainWidget, *BiasWidget, *EnvironmentWidget;
+    QGridLayout *MainLayout, *BiasLayout, *EnvironmentLayout;
 
     QTabWidget *TabWidget;
             
     void closeEvent(QCloseEvent *); 
-         
+
   public:
     GUI();
     ~GUI();
+    
+  private slots:
+    void MenuAbout();
 };
+
+#endif
