Index: fact/Evidence/Doc/Evidence.tex
===================================================================
--- fact/Evidence/Doc/Evidence.tex	(revision 10279)
+++ fact/Evidence/Doc/Evidence.tex	(revision 10280)
@@ -11,5 +11,5 @@
 \usepackage{url}
 \usepackage{listings} 
-\usepackage[light]{draftcopy}
+%\usepackage[light]{draftcopy}
 \usepackage{longtable}
 
@@ -39,5 +39,5 @@
 \maketitle
 
-This report describes the design and basic functionality of the \E control system. This essentially is a C++ class and a set of programs running on Linux for controlling small scale experiments. It is based on CERN's DIM library for interprocess communication over TCP/IP connections. \lstinline{$Rev$}
+This report describes the design and basic functionality of the \E control system. This essentially is a C++ class and a set of programs running on Linux for controlling small scale experiments. It is based on CERN's DIM library for interprocess communication over TCP/IP connections.
 
 \tableofcontents
@@ -48,5 +48,5 @@
 \label{Overview}
 
-The \E control system has been developed for application in small-scale experiments, for which established and comprehensive control systems like EPICS, DOOCS or PVS-II are too large and impose too much overburden.\footnote{Information on these systems can be found at \url{http://www.aps.anl.gov/epics/}, \url{http://tesla.desy.de/doocs}, \url{http://www.etm.at/}.} The development of \E has been started within the FACT project (\emph{First G-APD Cherenkov Telescope} \cite{Bra09,And10}).  
+The \E control system has been developed for application in small-scale experiments, for which established and comprehensive control systems like EPICS, DOOCS or PVS-II are too large and impose too much overburden.\footnote{Information on these systems can be found at \url{http://www.aps.anl.gov/epics/}, \url{http://tesla.desy.de/doocs}, \url{http://www.etm.at/}.} The development of \E has been started within the FACT project (\emph{First G-APD Cherenkov Telescope} \cite{Bra09,And11}).  
 
 An experiment control system often comprises several individual programs that require configuration information, produce data that should be stored and easily visualized, and at least partly need to exchange information between each other. The intention of \E is to a) integrate this with a minimum of extra coding on part of the applications, b) to achieve this using as far as reasonable established tools, c) to be centralized\footnote{This is a design choice that partly defines the range of applicable experiments. For large systems decentralization can be a better approach.}, and d) to be lean. 
@@ -130,5 +130,5 @@
 \item Provides a method for configuration requests. If the configuration data is not available, the application terminates with a  message of FATAL severity unless default data is given.
 \item Provides a method for safely translating DIM service data into text.
-\item Implements the virtual DIM methods \lstinline{exitHandler()}. It can be called through a standard DIM command \lstinline{SvrName/EXIT}, taking a single integer as argument. Upon first invocation, the handler just sets the flag \lstinline{ExitRequest} which should be handled by the application. Upon second invocation, it will call \lstinline{exit()}. The user application can override this handler.
+\item Implements the virtual DIM methods \lstinline{exitHandler()}. It can be called through a standard DIM command \lstinline{SvrName/EXIT}, taking a single integer as argument. The handler will issue a message with severity \lstinline{INFO}, including the received integer, and then calls \lstinline{exit(EXIT_SUCCESS)}. The user application can override this handler.
 \item Provides a DIM command \lstinline{SvrName/ResetMessage}. It will set the message service to INFO severity with the information which client issued the command. This can be used to remove a WARN, ERROR or FATAL serverity once the problem has been fixed. The \lstinline{Alarm} server uses this command if it is instructed to reset an alarm level. The command takes no arguments.
 \item Implements the virtual DIM methods \lstinline{errorHandler()}. The error handler will issue a message with ERROR severity that contains the DIM error code. The user application can override this handler.
@@ -153,7 +153,7 @@
     ~EvidenceServer();
 
-    enum MessageType {INFO=0, WARN=1, ERROR=2, FATAL=3};
-
-    void Message(MessageType, const char *, ...);
+    enum MessageType {INFO=0, WARN=10, ERROR=20, FATAL=30};
+
+    void Message(int, const char *, ...);
     void SendToLog(const char *, ...);
     string GetConfig(string, string = string());
@@ -164,5 +164,5 @@
     static bool ServiceOK(DimInfo *);
     static bool ServiceOK(DimRpcInfo *);
-	static bool ServiceOK(DimCurrentInfo *);
+    static bool ServiceOK(DimCurrentInfo *);
     static vector<string> Tokenize(const string &, const string & = " ");
 
@@ -181,5 +181,5 @@
 \underline{\lstinline{GetConfig()}} issues, on first invocation, a DIM remote procedure call to the configuration server to retrieve the required data and returns it as a string. The second argument gives the data to be returned in case the server is unavailable or cannot provide the requested data. If in this case the second string is empty, the program terminates with a FATAL message. Using the service \lstinline{Config/ModifyTime}, the server keeps track of changes to the configuration file in the background. Upon subsequent requests for the same configuration data, it only issues a remote procedure call again if the file changed in the meantime. If not, the same data already retrieved is returned. This way, this function can be repeatedly called, even at high rate, without generating unnecessary load to the configuration server (as the configuration file does not change frequently).
 
-The virtual method \underline{\lstinline{ConfigChanged()}} is executed in a separate thread when the configuration file changes. It can be reimplemented by the application. Calls to \lstinline{GetConfig()} from this method will be blocking and thus result in updated configuration data.
+The virtual method \underline{\lstinline{ConfigChanged()}} is executed in a separate thread when the configuration file changes. It can be reimplemented by the application. Calls to \lstinline{GetConfig()} from this method will be blocking and thus result in immediately updated configuration data. See Sect.\,\ref{ConfigHandling} for more details.
 
 The methods \underline{\lstinline{Lock()}} and \underline{\lstinline{Unlock()}} work on an internal mutex.\footnote{Its type is \lstinline{PTHREAD_MUTEX_ERRORCHECK}. In case an already locked mutex is re-locked, the corresponding system call will therefore return a error and thus avoid dead-locking. Error messages from \lstinline{Lock()} and \lstinline{Unlock()} are written to the console and to the log file. They are not published using \lstinline{Message()} since this method itself uses locking and calling it would result in an infinite recursion.} They are used by \lstinline{GetConfig()} but are also available for the user application to serialize access from multiple threads. Calling functions in the locked state should be avoided as it might result in re-locking.
@@ -265,5 +265,5 @@
 \subsection{\lstinline{Alarm} --- Handling of error conditions}
 
-The alarm server maintains a list of \emph{alarm levels} for a given set of servers. The alarm levels are defined as \lstinline{OK} (0), \lstinline{WARN} (1), \lstinline{ERROR} (2), \lstinline{FATAL} (3), and \lstinline{UNAVAILABLE} (4). The first four result from the corresponding severities of the message services, to which the alarm server subscribes. The alarm level does not decrease if, for example, a server issues a message with severity \lstinline{WARN} after one with \lstinline{ERROR}. It is only reset by command or by restarting the alarm server.
+The alarm server maintains a list of \emph{alarm levels} for a given set of servers. The standard alarm levels are defined as \lstinline{OK} (0), \lstinline{WARN} (10), \lstinline{ERROR} (20), \lstinline{FATAL} (30), and \lstinline{UNAVAILABLE} (40). The first four result from the corresponding severities of the message services, to which the alarm server subscribes. The alarm level does not decrease if, for example, a server issues a message with severity \lstinline{WARN} after one with \lstinline{ERROR}. Only if a server changes from \lstinline{UNAVAILABLE} to \lstinline{OK} (by restarting) or through the command \lstinline|ResetAlarm| can the alarm level be lowered.
 
 A master alarm is generated from the highest server alarm level. The alarm server also periodically checks if all required servers are up (searching for them with the DIM browser). It can send an email in case a server is down or in error. One email will be send with each increase of alarm level for each server.
@@ -277,5 +277,6 @@
 \lstinline|period| & Interval in seconds to check for server availability.\\[1ex]
 \multicolumn{2}{l}{\textbf{Commands}} \\
-\lstinline|ResetAlarm xyz| & Reset alarm level of server \lstinline|xyz|.\\[1ex]
+\lstinline|ResetAlarm xyz| & Reset alarm level of server \lstinline|xyz|.\\
+\lstinline|Switch on/off| & Switch alarm server on or off.\\[1ex]
 \multicolumn{2}{l}{\textbf{Services}} \\
 \lstinline|Alarm/Summary| & Text listing all observed servers and their alarm level.\\
@@ -360,5 +361,5 @@
 As a first step in achieving this, the application should not store the obtained configuration data internally, but always re-request it using the method \lstinline{GetConfig()} described in Sect.\,\ref{EvidenceServer-Methods}. This method will only issue a remote procedure call to the \lstinline{Config} server if the configuration file has been modified since the last invocation. So calling this method even at high rate will not load the configuration server at all if the configuraton file is unchanged, but will yield up-to-date information if it did change.
 
-The remote procedure call is blocking when called from the main thread or from the method \lstinline{ConfigChanged()} (which runs in a separate thread). It is non-blocking, using an \lstinline{rpcInfoHandler()}, when called from any other thread, especially also from the DIM handler thread. Blocking execution means that the remote procedure call will wait until the data has arrived from the server before returning to the application, whereas non-blocking execution will return immediately and invoke a handler later when the data arrived. This procedure is necessary since a blocking remote procedure call from \lstinline{infoHandler()} will result in a dead-lock.
+The remote procedure call is non-blocking (using an \lstinline{rpcInfoHandler()}) when invoked from the DIM handler thread and blocking otherwise. Blocking execution means that the remote procedure call will wait until the data has arrived from the server before returning to the application, whereas non-blocking execution will return immediately and invoke an internal handler later when the data arrived. This procedure is necessary since a blocking remote procedure call from the DIM handler thread (e.g. from an \lstinline{infoHandler()}) will result in a dead-lock.
 
 In the non-blocking case, the call to \lstinline{GetConfig()} returns still the previous, non-updated data even if the configuration file changed. The result of the non-blocking remote procedure call can only be processed by DIM once the current and all queued handler invocations have finished. When this is done, updated data will be returned by subsequent calls to \lstinline{GetConfig()}.
@@ -368,5 +369,5 @@
 An alternative, albeit for the programmer more demanding, procedure for semi-automatic updates on configuration information is to reimplement the virtual method \lstinline{ConfigChanged()} in the user class. This method is invoked as a separate thread by the \lstinline{EvidenceServer} class whenever the service \lstinline{Config/ModifyTime} changes (and also at program start-up). As it is not running within the DIM handler thread, \lstinline{GetConfig()} will use blocking connections to get immediately up-to-date data when called from \lstinline{ConfigChanged()}.
 
-Running in a separate thread requires suitable protection by the programmer when accessing common data structures. To ease that, the \lstinline{EvidenceServer} class contains the pair of methods \lstinline{Lock()} and \lstinline{Unlock()} that work on an class internal mutex. The mutex type is \lstinline{PTHREAD_MUTEX_ERRORCHECK} and therefore includes error checking: no dead-lock will occur if double locking, but the program will terminate with a \lstinline{FATAL} message.   
+Running in a separate thread requires suitable protection by the programmer when accessing common data structures. To ease that, the \lstinline{EvidenceServer} class contains the methods \lstinline{Lock()} and \lstinline{Unlock()} that work on an class internal mutex. The mutex type is \lstinline{PTHREAD_MUTEX_ERRORCHECK} and therefore includes error checking: no dead-lock will occur if double locking, but instead the program will terminate with a \lstinline{FATAL} message.   
 
 
@@ -450,5 +451,5 @@
 A graphical user interface (GUI), implemented using the Qt and Qwt frameworks\footnote{Information on these frameworks is available at \url{http://qt.nokia.com/} and \url{http://qwt.sourceforge.net/}.}, is available. It derives from standard widget classes extended versions that can display the contents of DIM services and history buffers. A widget to send generic text commands is also available. Qwt is used to display graphs which is not supported by Qt.
 
-The GUI is called \emph{Evidence Data Display} (\lstinline{EDD}). It has a single point interface to the DIM system and distributes received service updates to its widgets using the Qt signal/slot mechanism. This is necessary since the DIM \lstinline{infoHandler()} receiving the updates runs in a separate thread, but manipulations of GUI elements within Qt may only be done by the main thread. This mechanism also guarantees that one GUI instance subscribes not more than once to a particular service, even if the same data is shown by multiple widgets.
+The GUI is called \emph{Evidence Data Display} (\lstinline{Edd}). It has a single point interface to the DIM system and distributes received service updates to its widgets using the Qt signal/slot mechanism. This is necessary since the DIM \lstinline{infoHandler()} receiving the updates runs in a separate thread, but manipulations of GUI elements within Qt may only be done by the main thread. This mechanism also guarantees that one GUI instance subscribes not more than once to a particular service, even if the same data is shown by multiple widgets.
 
 The GUI implementation is designed to be easily portable and does not use operating-system specifics. It sticks to standard C++ code, to Qt capabilities and to DIM. Qt is explicitely designed for cross-platform applications.  
@@ -466,5 +467,5 @@
 \label{DIMDetails}
 
-\subsection{Format of \lstinline{DIM_DNS/SERVER_LIST} and \lstinline{xyz/SERVICE_LIST}}
+\subsection{Format of \lstinline{DIS_DNS/SERVER_LIST} and \lstinline{xyz/SERVICE_LIST}}
 \label{ServiceFormats}
  
@@ -497,5 +498,5 @@
 If server and client use the same structure definition, a cast like \lstinline{struct A *Pnt = getData()} will guarantee correct access to elements. For example, \lstinline{Pnt->b[1]} will contain the second integer, even if client and server pad structures differently. Padding can also be disabled for a server if desired.
 
-In general, no blocking functions should be called from within a DIM handler. Specifically, making a blocking remote procedure call in an \lstinline{infoHandler()} will dead lock.\footnote{Non-blocking reception using an \lstinline{rpcInfoHandler()} is possible}.
+In general, no blocking functions should be called from within a DIM handler. Specifically, making a blocking remote procedure call in an \lstinline{infoHandler()} will dead lock.\footnote{Non-blocking reception using an \lstinline{rpcInfoHandler()} is possible}. This also applies to the \lstinline{exitHandler()}. Calling \lstinline{exit()} from this handler will run destructors of static objects within the DIM thread. This might result in undesirable behaviour if DIM services are deleted in those destructors.
 
 If DIM is compiled without threads, it uses internally the signals SIGIO and SIGALRM for communication.
@@ -538,8 +539,7 @@
 \begin{thebibliography}{xxxx00}
 
-\bibitem[Gas01]{Gas01} C. Gaspar, M. D\"{o}nszelmann and Ph. Charpentier, \emph{DIM, a portable, light weight package for information publishing, data transfer and inter-process communication}, Computer Physics Communications 140 1+2 102-9, 2001
-\bibitem[Bra09]{Bra09} I. Braun et al., Nucl. Inst. and Methods A 610, 400 (2009)
-\bibitem[And10]{And10} H. Anderhub et al., Nucl. Inst. and Methods, to be published (2010)
-
+\bibitem[Gas01]{Gas01} C. Gaspar, M. D\"{o}nszelmann and Ph. Charpentier, \emph{DIM, a portable, light weight package for information publishing, data transfer and inter-process communication}, Computer Physics Communications \textbf{140}, 102 (2001)
+\bibitem[Bra09]{Bra09} I. Braun et al., Nucl. Inst. and Methods \textbf{A 610}, 400 (2009)
+\bibitem[And11]{And11} H. Anderhub et al., Nucl. Inst. and Methods, \textbf{A 628}, 107 (2011)
 \end{thebibliography}
 
Index: fact/Evidence/Doc/INSTALL_Edd
===================================================================
--- fact/Evidence/Doc/INSTALL_Edd	(revision 10280)
+++ fact/Evidence/Doc/INSTALL_Edd	(revision 10280)
@@ -0,0 +1,41 @@
+Installation instructions for the Evidence Data Display Edd
+
+A) The packages Qt, Qwt and DIM are needed
+
+1. Qt can be downloaded from http://qt.nokia.com/. 
+
+   Test have been made with version 4.6.2 which can be downloaded aat
+   ftp://ftp.qt.nokia.com/qt/source/qt-everywhere-opensource-src-4.6.2.tar.gz.
+   It should well work with later version as well. 
+   
+   Installation instruction are given in the package. Usually, they amount on
+   Linux to executing './configure', then 'gmake all' followed by 
+   'gmake install', all done within the unpacked directory.
+ 
+   Before the following steps, it should be assured that the right version
+   of qmake is used, as many Linux installations include an older version
+   of Qt. Use 'qmake -version' to check and adjust your PATH variable if
+   necessary.
+    
+2. Qwt can be downloaded from http://sourceforge.net/projects/qwt/.
+   
+   Tests were made with version 5.2.1. Installation should simply be done
+   by executing 'qmake', then 'make', and finally 'make install'.
+
+3. DIM can be downloaded from http://dim.web.cern.ch/dim/.
+
+   The latest available version should be fine. Follow the installation
+   instruction on the web site given at the download link. Note that the
+   command line option in 'unzip -a' is mandatory. Compiling is done
+   with 'gmake all'.
+
+
+B) Compiling Edd
+
+An environment variable QWTDIR should point to the Qwt installation,
+and DIMDIR to the DIM installation.
+
+Issue 'qmake', then 'make' will build the executable Edd.
+
+To start, the environment variable LD_LIBRARY_PATH needs to include the
+location of the Qwt shared library. 
Index: fact/Evidence/GUI.cc
===================================================================
--- fact/Evidence/GUI.cc	(revision 10280)
+++ fact/Evidence/GUI.cc	(revision 10280)
@@ -0,0 +1,1042 @@
+
+/* ============================================================ 
+
+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 "GUI.h"
+
+class EddDim *Handler;
+
+//
+// History chooser function (opens plot for numeric data, TextHist for all other)
+//
+QWidget *OpenHistory(char *Service, int Index) {
+
+  QString Format;
+  DimBrowser Browser;
+  class EvidenceHistory *Hist = Handler->GetHistory(Service);
+
+  // Check if history service available
+  if (Hist == NULL || Hist->GetFormat() == NULL) {
+	QMessageBox::warning(NULL, "Edd Message", QString("Could not retrieve history for service ") + Service ,QMessageBox::Ok);
+
+	// If service currently available, take its format
+	char *Name, *Fmt;
+
+	Browser.getServices(Service);
+	if (Browser.getNextService(Name, Fmt) != 0) Format = QString(Fmt);
+	else {
+	  Handler->DropHistory(Service);
+	  return NULL;
+	}
+  }
+
+  if (Format.isEmpty()) Format = Hist->GetFormat();
+  Handler->DropHistory(Service);
+  
+  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);
+  
+  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;
+
+  // Check if service available
+  QPalette Pal = palette();  
+  if (!SetStatus(this, Name, 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 (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' && Format != "I:1;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 Service, int Index, QWidget *P): EddBasePlot(P) {
+
+  // Widget properties
+  setAcceptDrops(true);
+  setAxisScaleDraw(QwtPlot::xBottom, new EddTimeScale);
+	
+  // Update time range on plot when axis change	(update() results in paintEvent())
+  connect(axisWidget(QwtPlot::xBottom), SIGNAL(scaleDivChanged()), SLOT(update()));
+
+  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", Service.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 (!Service.isEmpty()) AddService(Service, 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; i<List.size(); i++) {
+    if (Name == List[i].Name && Index == List[i].Index) {
+      QMessageBox::warning(this, "Edd Message",Name+" ("+QString::number(Index)+") already present",QMessageBox::Ok);
+      return;
+    }
+  }  
+  
+  // Generate new curve and subscribe to service
+  struct ItemDetails N;
+
+  N.Name = Name;
+  N.Signal = NewCurve(Name+"("+QString::number(Index)+")");
+  N.Index = Index;
+  List.append(N);
+
+  Handler->Subscribe(Name);
+}
+
+// Update widget (must happen in GUI thread)
+void EddPlot::Update(QString Name, int Time, QByteArray, QString Format, QString Text) {
+
+  bool DoUpdate = false;
+
+  // Determine which plot item this call belongs to
+  int ItemNo;
+  for (ItemNo=0; ItemNo<List.size(); ItemNo++) if (List[ItemNo].Name == Name) {
+
+	DoUpdate = true;
+
+	// If size limit reached, clear buffer
+    if (List[ItemNo].Signal->dataSize() > 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);
+	}
+
+ 	// Append 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()));
+	}
+  }
+
+  if (DoUpdate) 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; i<List.size(); i++) if (List[i].Signal == Curve) {
+    Handler->Unsubscribe(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 &)));
+  connect(Zoomer, SIGNAL(zoomed(const QwtDoubleRect &)), this, SLOT(ReDoStats()));
+
+  Magnifier = new QwtPlotMagnifier(canvas());
+  Magnifier->setMouseButton(Qt::NoButton,Qt::NoButton);
+  Magnifier->setZoomInKey(Qt::Key_M, Qt::NoModifier);
+  Magnifier->setZoomOutKey(Qt::Key_M, Qt::ShiftModifier);
+
+  Panner = new QwtPlotPanner(canvas());
+  Panner->setMouseButton(Qt::LeftButton, Qt::ShiftModifier);
+  connect(Panner, SIGNAL(panned(int, int)), this, SLOT(ReDoStats()));
+
+  Picker = new QwtPicker(QwtPicker::CornerToCorner|QwtPicker::RectSelection, QwtPicker::RectRubberBand, QwtPicker::AlwaysOff, this);
+  connect(Picker, SIGNAL(selected(const QwtPolygon &)), SLOT(MouseSelection(const QwtPolygon &)));
+
+  // Grid and legend
+  Grid = new QwtPlotGrid;
+  Grid->setMajPen(QPen(Qt::gray, 0, Qt::DotLine));
+  Grid->attach(this);
+  
+  insertLegend(new QwtLegend(), QwtPlot::TopLegend, 0.3);
+
+  // Marker for statistics text
+  Stats = new QwtPlotMarker();
+  Stats->setLineStyle(QwtPlotMarker::NoLine);
+  Stats->setLabelAlignment(Qt::AlignLeft | Qt::AlignBottom);
+  Stats->hide();
+  Stats->attach(this);
+
+  // Context menu
+  Menu = new QMenu(this);
+  StripAction = Menu->addAction("Stripchart", this, SLOT(UpdatePlot()));
+  StripAction->setCheckable(true);
+  Menu->addSeparator();
+  YLogAction = Menu->addAction("y scale log", this, SLOT(UpdatePlot()));
+  YLogAction->setCheckable(true);
+  NormAction = Menu->addAction("Normalize", this, SLOT(UpdatePlot()));
+  NormAction->setCheckable(true);
+  StyleAction = Menu->addAction("Draw dots", this, SLOT(UpdatePlot()));
+  StyleAction->setCheckable(true);
+  StatisticsAction = Menu->addAction("Statistics", this, SLOT(ReDoStats()));
+  StatisticsAction->setCheckable(true);
+  Menu->addAction("Zoom out", this, SLOT(MenuZoomOut()));
+  Menu->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; i<Items.size(); i++) delete Items[i].Signal;
+  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; i<Items.size(); i++) {
+	Mean = 0;
+	Sigma = 0;
+	Count = 0;
+
+    // Calculate mean and sigma for data points currently visible	
+	for (int j=0; j<Items[i].y.size(); j++) {
+	  if (!axisScaleDiv(QwtPlot::xBottom)->contains(Items[i].x[j])) continue;
+	  Mean += Items[i].y[j];
+	  Sigma += Items[i].y[j]*Items[i].y[j];
+	  Count++;
+	}
+	
+	if (Count == 0) Mean = 0;
+	else Mean /= Count;
+	if (Count < 2) Sigma = 0;
+	else Sigma = sqrt(Count/(Count-1)*(Sigma/Count-Mean*Mean));
+  
+	// Prepare string
+	Text += Items[i].Signal->title().text() + ": m " + QString::number(Mean, 'f', 2) + ", s " + QString::number(Sigma, 'f', 2) + "\n";
+  }
+
+  // Replot once to get axis correct
+  replot();
+
+  // Print string to plot
+  QwtText text(Text);
+  text.setFont(QFont("Helvetica", 8));
+  Stats->setValue(axisScaleDiv(QwtPlot::xBottom)->upperBound(), axisScaleDiv(QwtPlot::yLeft)->upperBound());
+  Stats->setLabel(text);
+
+  // Replot again to update text
+  replot();
+}
+
+// Update all curves in plot
+void EddBasePlot::UpdatePlot() {
+
+  double Lower = axisScaleDiv(QwtPlot::xBottom)->lowerBound();
+  double Upper = axisScaleDiv(QwtPlot::xBottom)->upperBound();
+  double MaxTime = DBL_MIN;
+  static QwtSymbol Symbol, Sym1;
+  Symbol.setStyle(QwtSymbol::Ellipse);
+  Symbol.setSize(4);
+
+  // 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; ItemNo<Items.size(); ItemNo++) {
+	// Determine current maximum value for strip chart plotting
+	if (Items[ItemNo].Signal->boundingRect().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; i<DataPoints; i++) {
+      y[i] = Items[ItemNo].y[i];
+
+      if (NormAction->isChecked()) {
+	    if (Items[ItemNo].Smallest != Items[ItemNo].Largest) {
+		  y[i] = (y[i] - Items[ItemNo].Smallest)/(Items[ItemNo].Largest-Items[ItemNo].Smallest);
+		}
+	    else y[i] = 1;
+      }  
+    }
+ 
+    // Plot data
+    Items[ItemNo].Signal->setData(Items[ItemNo].x.data(), y, DataPoints);
+    Items[ItemNo].Signal->show();
+	delete[] y;
+
+	// Generate bounding box of all curves for zoomer
+	BBox = BBox.unite(Items[ItemNo].Signal->boundingRect());
+  }
+
+  // Reset zoom base to include all data
+  Zoomer->setZoomBase(BBox);
+
+  // If plot is strip char, only move axis but keep range
+  if (StripAction->isChecked()) {
+    setCanvasBackground(EddPlotBackgroundColor.lighter(90));
+    setAxisScale(QwtPlot::xBottom, Lower+ BBox.right() - MaxTime, Upper + BBox.right() - MaxTime);
+  }
+  else setCanvasBackground(EddPlotBackgroundColor);
+
+  ReDoStats();
+}
+
+// Append curve to plot
+QwtPlotCurve *EddBasePlot::NewCurve(QwtText Title) {
+
+  static Qt::GlobalColor LineColors[] = {Qt::black, Qt::blue, Qt::red, Qt::green, Qt::white,
+   	Qt::darkRed, Qt::darkGreen, Qt::darkBlue, Qt::cyan, Qt::darkCyan, Qt::magenta, Qt::darkMagenta,
+	Qt::gray, Qt::darkGray, Qt::lightGray};
+  struct PlotItem N;
+
+  N.Signal = new QwtPlotCurve;
+  N.Signal->attach(this);
+  N.Signal->setTitle(Title);
+  N.Signal->setPen(QColor(LineColors[Items.size() % (sizeof(LineColors)/sizeof(Qt::GlobalColor))]));
+  N.Largest = DBL_MIN;
+  N.Smallest = DBL_MAX;  
+  Items.append(N);
+  
+  return N.Signal;
+}
+
+// Clear curve data
+void EddBasePlot::ClearCurve(unsigned int Item) {
+
+  if (Item >= (unsigned int) Items.size()) return;
+  
+  Items[Item].x.clear();
+  Items[Item].y.clear();
+}
+
+// Append data point
+void EddBasePlot::AddPoint(unsigned int Item, double x, double y) {
+
+  if (Item >= (unsigned int) Items.size()) return;
+  
+  Items[Item].x.append(x);
+  Items[Item].y.append(y);
+
+  if (y > Items[Item].Largest) Items[Item].Largest = y;
+  if (y < Items[Item].Smallest) Items[Item].Smallest = y;    
+}
+
+// Rescale plot in case selection has been made outside the canvas
+void EddBasePlot::MouseSelection(const QPolygon &P) {
+ 
+  QwtDoubleInterval xPlot, xMouse, yPlot, yMouse; 
+
+  // Shift selected rectangle so that upper left corner is 0/0 on canvas
+  QRect R = P.boundingRect().translated(-plotLayout()->canvasRect().topLeft());
+
+  // Current axis intervals
+  xPlot = axisScaleDiv(QwtPlot::xBottom)->interval();
+  yPlot = axisScaleDiv(QwtPlot::yLeft)->interval();
+  
+  // Selected axis intervals
+  xMouse = QwtDoubleInterval(invTransform(QwtPlot::xBottom, R.left()),
+  		invTransform(QwtPlot::xBottom, R.right()));
+  yMouse = QwtDoubleInterval(invTransform(QwtPlot::yLeft, R.bottom()),
+		invTransform(QwtPlot::yLeft, R.top()));
+
+  // Selection region outside all axis?
+  if (R.right() < 0 && R.top() > plotLayout()->canvasRect().height()) return;
+  
+  // Rescale both axis if selected rectangle encompasses canvas completely
+  if (P.boundingRect().contains(plotLayout()->canvasRect())) {
+	yMouse.setMaxValue(yMouse.maxValue() + yPlot.width());
+	yMouse.setMinValue(yMouse.minValue() - yPlot.width());
+	xMouse.setMinValue(xMouse.minValue() - xPlot.width());
+	xMouse.setMaxValue(xMouse.maxValue() + xPlot.width());
+
+	setAxisScale(QwtPlot::xBottom, xMouse.minValue(), xMouse.maxValue());
+	setAxisScale(QwtPlot::yLeft, yMouse.minValue(), yMouse.maxValue());
+  }
+
+  // Rescale y axis (increase range if selected rectangle larger than axis area)
+  if (R.right() < 0) {
+	if (yMouse.maxValue() > axisScaleDiv(QwtPlot::yLeft)->upperBound()) {
+	  yMouse.setMaxValue(yMouse.maxValue() + yPlot.width());
+	}
+	if (yMouse.minValue() < axisScaleDiv(QwtPlot::yLeft)->lowerBound()) {
+	  yMouse.setMinValue(yMouse.minValue() - yPlot.width());
+	}
+	
+	setAxisScale(QwtPlot::yLeft, yMouse.minValue(), yMouse.maxValue());
+  }
+
+  // Rescale x axis (increase range if selected rectangle larger than axis area)
+  if (R.top() > plotLayout()->canvasRect().height()) {
+	if (xMouse.maxValue() > axisScaleDiv(QwtPlot::xBottom)->upperBound()) {
+	  xMouse.setMaxValue(xMouse.maxValue() + xPlot.width());
+	}
+	if (xMouse.minValue() < axisScaleDiv(QwtPlot::xBottom)->lowerBound()) {
+	  xMouse.setMinValue(xMouse.minValue() - xPlot.width());
+	}
+
+	setAxisScale(QwtPlot::xBottom, xMouse.minValue(), xMouse.maxValue());
+  }
+
+  // Draw new scales
+  replot();
+} 
+
+// Reset graph axes to autoscale when fully unzoomed
+void EddBasePlot::HandleZoom(const QwtDoubleRect &) {
+
+  if(Zoomer->zoomRectIndex() == 0) {
+    setAxisAutoScale(QwtPlot::xBottom);
+    setAxisAutoScale(QwtPlot::yLeft);
+  }
+  
+  UpdatePlot();
+}
+    
+// Opening context menu
+void EddBasePlot::contextMenuEvent(QContextMenuEvent *Event) {
+
+  Menu->exec(Event->globalPos());
+}
+
+// Zoom completely out
+void EddBasePlot::MenuZoomOut() {
+
+  Zoomer->zoom(0);
+  UpdatePlot();
+}
+
+// Remove 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; ItemNo<Items.size(); ItemNo++) {
+    Stream << QString("# ") + Items[ItemNo].Signal->title().text() + ".hist" << endl;
+    for (int i=0; i<Items[ItemNo].Signal->dataSize(); i++) {
+      Stream << Items[ItemNo].x.at(i) << " " << Items[ItemNo].Signal->y(i) << endl;
+    }
+  }
+}
+
+// Print plot
+void EddBasePlot::MenuPrint() {
+
+  QPrinter *Printer = new QPrinter;
+  QPrintDialog *PrintDialog = new QPrintDialog(Printer, this);
+  if (PrintDialog->exec() == QDialog::Accepted) {
+    QPainter Painter(Printer);
+    QPixmap Pixmap = QPixmap::grabWidget(this);
+    Painter.drawPixmap(0, 0, Pixmap);
+  }
+  delete Printer;	delete PrintDialog;
+}
+
+// Save plot as image
+void EddBasePlot::MenuSave() {
+
+  QString Filename = QFileDialog::getSaveFileName(this,
+     "Filename of image", "/home/ogrimm/ddd", "Image files (*.bmp *.jpg *.png *.ppm *.tiff *.xbm *.xpm);;All files (*)");
+  if (Filename.length()>0) {
+    QPixmap Pixmap = QPixmap::grabWidget(this);
+    if(!Pixmap.save(Filename)) {
+      QMessageBox::warning(this, "Edd Message","Could not write image file.",QMessageBox::Ok);
+      remove(Filename.toAscii().data());
+    }
+  }
+}
+
+// Help text
+void EddBasePlot::MenuPlotHelp() {
+
+  QMessageBox::about(this, "Edd - Plot 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\n\n"
+	"Statistics are calculated over the current x axis extend");
+}
+
+//////////////////////////////////////
+// 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()+") " +
+		  QString::fromStdString(EvidenceServer::ToString(Hist->GetFormat(), (void *) R->Data, R->Size)) + "\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(INT(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString))) == false) {
+    printf("Failed connection in EddDim()\n");
+  }
+}
+
+// Destructor
+EddDim::~EddDim() {
+
+  QList<QString> L = HistoryList.keys();
+  for(int i=0; i<L.size(); i++) delete HistoryList[L[i]].HistClass;
+
+  delete Mutex;
+}
+
+// Subscribe to DIM service
+void EddDim::Subscribe(QString Name) {
+
+  // Lock before accessing list
+  QMutexLocker Locker(Mutex);
+
+  // If already subscribed to service, increase usage count and reemit data for new subscriber
+  if (ServiceList.contains(Name)) {
+	ServiceList[Name].Count++;
+	YEP(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].Count = 1;
+  ServiceList[Name].DIMService = new DimStampedInfo(Name.toAscii().data(), INT_MAX, NO_LINK, this);
+  return;
+}
+
+// Unsubsribe from DIM service
+void EddDim::Unsubscribe(QString Name) {
+
+  // Lock before accessing list
+  QMutexLocker Locker(Mutex);
+
+  if (ServiceList.contains(Name)) ServiceList[Name].Count--;
+
+  if (ServiceList[Name].Count == 0) {
+	delete ServiceList[Name].DIMService;
+	ServiceList.remove(Name);
+	return;
+  }
+}
+
+// Get history buffer
+class EvidenceHistory *EddDim::GetHistory(QString Name) {
+
+  // History already available (only request again if too old)
+  if (HistoryList.contains(Name)) {
+	HistoryList[Name].Count++;
+
+	if (time(NULL)-HistoryList[Name].LastUpdate < 5) {
+	  HistoryList[Name].HistClass->Rewind();
+	  return HistoryList[Name].HistClass;
+	}
+	HistoryList[Name].LastUpdate = time(NULL);
+	if (HistoryList[Name].HistClass->GetHistory()) return HistoryList[Name].HistClass;
+	else return NULL;
+  }
+
+  // Create new history class
+  HistoryList[Name].HistClass = new EvidenceHistory(Name.toStdString());
+  HistoryList[Name].Count = 1;
+  HistoryList[Name].LastUpdate = time(NULL);
+
+  if (HistoryList[Name].HistClass->GetHistory()) return HistoryList[Name].HistClass;
+  else return NULL;
+}
+
+// Reduce history usage counter
+void EddDim::DropHistory(QString Name) {
+
+  if (HistoryList.contains(Name)) HistoryList[Name].Count--;
+}
+
+// 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<QString> L = HistoryList.keys();
+  for(int i=0; i<L.size(); i++) {
+	if ((HistoryList[L[i]].Count <= 0) && (time(NULL)-HistoryList[L[i]].LastUpdate) > 5) { 
+	  delete HistoryList[L[i]].HistClass;
+	  HistoryList.remove(L[i]);
+	}
+  }
+  
+  float Rate = Volume/1024.0/10;
+  Volume = 0;
+  YEP("Edd/Rate_kBSec", time(NULL), QByteArray::number(Rate), "F", QString::number(Rate));
+}
+
+// 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, QString Text) {
+
+  // Lock before accessing list
+  QMutexLocker Locker(Mutex);
+
+  // Store service data
+  if (ServiceList.contains(Name)) {
+	  ServiceList[Name].TimeStamp = Time;
+	  ServiceList[Name].ByteArray = Data;
+	  ServiceList[Name].Format = Format;
+	  ServiceList[Name].Text = Text;
+  }
+
+  // Update statistics only for actual Dim services
+  if (!Name.startsWith("Edd/")) Volume += Data.size();
+  
+  // Emit signal to all widgets
+  YEP(Name, Time, Data, Format, Text);
+}
+
+// Handling of DIM service update
+// No locking allowed. Signal triggers only EddDim::Update() when the main event loop is idle.
+void EddDim::infoHandler() {
+
+  if (!EvidenceServer::ServiceOK(getInfo())) INT(getInfo()->getName(), -1);
+  else {
+	INT(getInfo()->getName(), getInfo()->getTimestamp(), QByteArray((char *) getInfo()->getData(), 
+		getInfo()->getSize()), getInfo()->getFormat(), QString::fromStdString(EvidenceServer::ToString(getInfo()->getFormat(), getInfo()->getData(), getInfo()->getSize())));
+  }
+}
+
+
+/////////////////////
+// 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()), SLOT(Toggle()));
+}
+
+// Show or hide window
+void EddWindow::Toggle() {
+
+  if (M->isVisible()) M->hide();
+  else M->show();
+}
+
+// Return layout
+QGridLayout *EddWindow::Layout() {
+
+  return L;
+}
+
+// Return window
+QMainWindow *EddWindow::Window() {
+
+  return M;
+}
Index: fact/Evidence/GUI.h
===================================================================
--- fact/Evidence/GUI.h	(revision 10280)
+++ fact/Evidence/GUI.h	(revision 10280)
@@ -0,0 +1,271 @@
+#ifndef GUI_H_SEEN
+#define GUI_H_SEEN
+
+#include <QtGui>
+ 
+#include <qwt_plot.h>
+#include <qwt_plot_curve.h>
+#include <qwt_plot_grid.h>
+#include <qwt_plot_zoomer.h>
+#include <qwt_plot_magnifier.h>
+#include <qwt_plot_panner.h>
+#include <qwt_scale_engine.h>
+#include <qwt_analog_clock.h>
+#include <qwt_scale_widget.h>
+#include <qwt_plot_layout.h>
+#include <qwt_legend.h>
+#include <qwt_legend_item.h>
+#include <qwt_symbol.h>
+#include <qwt_plot_marker.h>
+#include <qwt_data.h>
+#include <qwt_color_map.h>
+
+#include <limits.h>
+#include <float.h>
+
+#include "dic.hxx"
+#include "Evidence.h"
+
+const QColor EddPlotBackgroundColor(Qt::yellow);
+
+QWidget *OpenHistory(char *, int);
+bool SetStatus(QWidget *, QString, int, QString, int = -1);
+
+
+// Base class for Edd plot
+// DeleteCurve() is pure virtual and needs to be implemented iin the application class
+class EddBasePlot: public QwtPlot {
+  Q_OBJECT
+
+  protected:    
+    QMenu *Menu;
+    QAction *StripAction;
+
+  private:
+    struct PlotItem {
+      QwtPlotCurve *Signal;
+	  QVector<double> x;
+	  QVector<double> y;
+      double Smallest;
+      double Largest;
+	  double Mean;
+	  double Sigma;
+    };
+    QList<struct PlotItem> Items;
+
+    QAction *YLogAction;
+    QAction *NormAction;
+    QAction *StyleAction;
+    QAction *StatisticsAction;
+
+    QwtPlotPanner *Panner;
+    QwtPlotGrid *Grid;
+    QwtPlotZoomer *Zoomer;
+	QwtPlotMagnifier *Magnifier;
+    QwtPicker *Picker;
+	QwtDoubleRect BBox;
+	QwtPlotMarker *Stats;
+	 	
+  public:
+    EddBasePlot(QWidget * = NULL);
+    ~EddBasePlot();
+
+	QwtPlotCurve *NewCurve(QwtText);
+	void ClearCurve(unsigned int);
+	void AddPoint(unsigned int, double, double);
+	virtual void DeleteCurve(QwtPlotCurve *) = 0;
+
+  protected slots:
+    void UpdatePlot();
+
+  private slots:
+	void ReDoStats();
+    void HandleZoom(const QwtDoubleRect &);
+	void MouseSelection(const QwtPolygon &);
+	void contextMenuEvent(QContextMenuEvent *);
+    void MenuSingleTrace();        
+    void MenuZoomOut();
+    void MenuSaveASCII();
+    void MenuSave();
+    void MenuPrint();
+	void MenuPlotHelp();
+};
+		  
+// General indicator for DIM service
+class EddLineDisplay: public QLineEdit {
+    Q_OBJECT
+
+    QMenu *Menu;
+    QPoint dragStart;
+    QWidget *LastHist;
+	
+	QString ServiceName;
+	int Index;
+	
+    void mousePressEvent(QMouseEvent *); 
+    void mouseReleaseEvent(QMouseEvent *); 
+    void mouseMoveEvent(QMouseEvent *); 
+	
+  public:
+    EddLineDisplay(QString, int=0, QWidget * = NULL);
+    ~EddLineDisplay();
+
+	bool ShowAsTime;
+	
+  private slots:
+	void Update(QString, int, QByteArray, QString, QString);
+    void contextMenuEvent(QContextMenuEvent *);    
+    void MenuOpenHistory();
+    void MenuCopyService();
+    void MenuCopyData();
+};
+
+// Sending command to DIM server
+class EddCommand: public QLineEdit {
+    Q_OBJECT
+
+	QString Name;
+	
+  public:
+    EddCommand(QString, QWidget * = NULL);
+
+  private slots:
+	void SendCommand();	
+};
+
+// Graph class for history display 
+class EddPlot: public EddBasePlot {
+    Q_OBJECT
+
+	// Time scale for axis
+	class EddTimeScale: public QwtScaleDraw {
+
+	  public:
+		EddTimeScale() {}
+
+		virtual QwtText label(double v) const {
+		  // Adapt text format to time span 
+		  QString Format;
+		  if (scaleDiv().range() < 60*60) Format = "hh' h\n'mm:ss";
+		  else if (scaleDiv().range() < 24*60*60) Format = "hh:mm";
+		  else if (scaleDiv().range() < 30*24*60*60) Format = "h' h\n'd-MMM";
+		  else Format = "d-MMM'\n'yyyy";
+
+		  // Generate text
+		  QwtText Text = QDateTime::fromTime_t((int) v).toString(Format);
+		  QFont Font = Text.font();
+		  Font.setPointSize(7);
+		  Text.setFont(Font);
+
+    	  return Text;
+		}
+	};
+
+    struct ItemDetails {
+      QString Name;
+	  int Index;
+      QwtPlotCurve *Signal;	  
+    };
+    QList<struct ItemDetails> List;
+
+  private:
+	QwtLegend *Legend;
+	int SizeLimit;
+
+    void dragEnterEvent(QDragEnterEvent *);
+    void dropEvent(QDropEvent *);
+	void paintEvent(QPaintEvent *);
+	 	
+  public:
+    EddPlot(QString = QString(), int = 0, QWidget * = NULL);
+    ~EddPlot();
+    void AddService(QString, int = 0);
+	void DeleteCurve(QwtPlotCurve *);
+
+  private slots:
+	void Update(QString, int, QByteArray, QString, QString);
+	void LegendClicked(QwtPlotItem *);
+    void MenuPasteService();
+};
+
+
+// Text history and output class
+class EddText: public QTextEdit {
+  Q_OBJECT
+
+  private:
+	QString Name;
+	bool Pure;
+	
+  public:
+    EddText(QString, bool = false, QWidget * = NULL);
+    ~EddText();
+
+	bool Accumulate;
+
+  private slots:
+	void Update(QString, int, QByteArray, QString, QString);
+};
+
+
+// Interface to DIM system
+class EddDim: public QObject, public DimInfo {
+  Q_OBJECT
+
+  private:
+	struct Item {
+	  DimStampedInfo *DIMService;
+	  int Count;
+	  int TimeStamp;
+	  QByteArray ByteArray;
+	  QString Format;
+	  QString Text;
+	};
+    QMap<QString, struct Item> ServiceList;
+    QMutex *Mutex;
+
+	struct HistItem {
+	  int Count;
+	  int LastUpdate;
+	  class EvidenceHistory *HistClass;
+	};
+    QMap<QString, struct HistItem> HistoryList;
+
+	long long Volume;
+
+	void infoHandler();
+
+  private slots:
+	void Update(QString, int, QByteArray, QString, QString);
+	void UpdateStatistics();
+
+  public:
+    EddDim();
+    ~EddDim();
+
+	void Subscribe(QString);
+	void Unsubscribe (QString);
+	class EvidenceHistory *GetHistory(QString);
+	void DropHistory(QString);
+
+  signals:
+    void YEP(QString, int, QByteArray = QByteArray(), QString = QString(), QString = QString());
+    void INT(QString, int, QByteArray = QByteArray(), QString = QString(), QString = QString());
+};
+
+// Open new window
+class EddWindow: public QPushButton {
+  Q_OBJECT
+
+  QMainWindow *M;
+  QGridLayout *L;
+
+  public:
+    EddWindow(QString, QString);
+	QGridLayout *Layout();
+	QMainWindow *Window();
+  private slots:
+	void Toggle();
+};
+
+#endif
Index: fact/tools/Edd/Edd.cc
===================================================================
--- fact/tools/Edd/Edd.cc	(revision 10280)
+++ fact/tools/Edd/Edd.cc	(revision 10280)
@@ -0,0 +1,969 @@
+/* ============================================================ 
+
+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
+
+============================================================ */
+
+#include "Edd.h"
+
+QString DRSBoard = "drsdaq";
+std::string PixelMapText;
+
+extern class EddDim *Handler;
+
+////////////////////////
+// Event oscilloscope //
+////////////////////////
+
+// Constructor
+EventScope::EventScope(QWidget *P): EddBasePlot(P), PixelMap(PixelMapText, false) {
+
+  Name = DRSBoard+"/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("Physical pipeline", this);
+  PhysPipeAction->setCheckable(true);
+  connect(PhysPipeAction, SIGNAL(triggered()), SLOT(PlotTraces()));
+  Menu->insertAction(StripAction, PhysPipeAction);
+
+  PersistanceAction = new QAction("Persistance", this);
+  PersistanceAction->setCheckable(true);
+  Menu->insertAction(StripAction, PersistanceAction);
+  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());
+  }
+  SetActive(true);
+}
+
+// Destructor (items with parent widget are automatically deleted)
+EventScope::~EventScope() {
+
+  SetActive(false);
+  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)+ " (" + ToPixel(0, Board, Chip, Channel) + ")");
+  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;
+  
+  // Clear in case persistance was activated
+  ClearCurve(0);
+
+  List.first().Signal->setTitle(QString::number(Board)+","+QString::number(Chip)+","+ QString::number(Channel) + " (" + ToPixel(0, Board, Chip, Channel) + ")");
+  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;
+  
+  // Disconnect while processing to avoid queing of events
+  disconnect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), this, SLOT(Update(QString, int, QByteArray, QString, QString)));
+  
+  // 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);
+
+  // Write event header text to file
+  if (RD->ReadEvent(0, Tmpfile) != CTX_OK) {
+    QMessageBox::warning(this, "Edd Warning","Could not read event.",QMessageBox::Ok);
+    return;
+  }
+
+  // Add trigger cells to file
+  fprintf(Tmpfile, "\nTrigger cells:");
+  int *TrigCells = (int *) RD->Data;
+  for (unsigned int i=0; i<RD->RHeader->NBoards; i++) {
+    fprintf(Tmpfile, "\n Board %d   ", i);
+	for (unsigned int j=0; j<RD->RHeader->NChips; j++) fprintf(Tmpfile, "%d ", *(TrigCells++));
+  }
+
+  // Emit signal containing run header
+  rewind(Tmpfile);
+  emit(EventHeaderChanged(Stream.readAll()));
+
+  // Update display
+  PlotTraces();
+  
+  // Reconnect after processing
+  connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), SLOT(Update(QString, int, QByteArray, QString, QString)));
+}
+
+// Update curves
+void EventScope::PlotTraces() {
+
+  double x,y;
+  unsigned int Cell, Trig;
+  static int Last = 0;
+  
+  // 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<List.size(); i++) {
+
+	if (PersistanceAction->isChecked()) List[i].Signal->setStyle(QwtPlotCurve::Dots);
+	else {
+	  ClearCurve(i);
+	  List[i].Signal->setStyle(QwtPlotCurve::Lines);
+	}
+
+ 	// Check if current event contains requested trace
+    if (List[i].Board>=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);
+
+	// Calulate point of curve
+	for (unsigned int j=0; j<R->Samples; 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) List[i].Trigger->setValue(x, y);	
+    }
+  }
+
+  // Limit update rate in persistance mode 
+  if (!PersistanceAction->isChecked() || time(NULL) > Last) {
+    UpdatePlot();
+    Last = time(NULL);
+  }
+
+
+  // Loop through event data for pixel display
+  QVector<double> Pixel(R->NBoards*R->NChips*R->NChannels);
+  int Count = 0;
+
+  for (unsigned int Board=0; Board<R->NBoards; Board++) {
+  for (unsigned int Chip=0; Chip<R->NChips; Chip++) {
+  for (unsigned int Channel=0; Channel<R->NChannels; Channel++) {
+    Pixel[Count] = DBL_MIN;
+
+	for (unsigned int i=0; i<R->Samples; 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; i<List.size(); i++) if (List[i].Signal == Curve) {
+	delete List[i].Trigger;
+    List.removeAt(i);
+  }
+}
+
+// Set display active (if inactive, disconnect from server)
+void EventScope::SetActive(bool State) {
+
+  static bool Active = false;
+
+  if (State && !Active) Handler->Subscribe(DRSBoard+"/EventData");
+  if (!State && Active) Handler->Unsubscribe(DRSBoard+"/EventData");
+  Active = State;
+}
+
+// Translate FPA ID to Pixel ID (use '-' instead of PM_ERROR_CODE)
+QString EventScope::ToPixel(unsigned int Crate, unsigned int Board, unsigned int Patch, unsigned int Pixel) {
+  
+  if (FPA_to_Pixel(Crate, Board, Patch, Pixel) == PM_ERROR_CODE) return "-";
+  else return QString::number(FPA_to_Pixel(Crate, Board, Patch, Pixel));
+}
+  
+//------------------------------------------------------------------
+//**************************** Tab pages ***************************
+//------------------------------------------------------------------
+
+//
+// Environment page
+//
+TP_Environment::TP_Environment() {
+
+  QGridLayout *Layout = new QGridLayout(this);
+  setAttribute(Qt::WA_DeleteOnClose);
+
+  // Status display
+  EddLineDisplay *Line = new EddLineDisplay("ARDUINO/Message");
+  Line->setMaximumWidth(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, 9, 7);      
+
+  // Night sky monitor
+  Line = new EddLineDisplay("SQM/Message");
+  Line->setMaximumWidth(200);
+  Layout->addWidget(Line, 6, 0, 1, 2);      
+
+  Line = new EddLineDisplay("SQM/NSB");
+  Layout->addWidget(Line, 7, 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+64);
+    Layout->addWidget(Line, i%9+1, 0+i/9, 1, 1);
+    Plot->AddService("Bias/VOLT/ID00", i+64);
+
+    Line = new EddLineDisplay("Bias/VOLT/ID00", i+96);
+    Layout->addWidget(Line, i%9+1, 2+i/9, 1, 1);
+    Plot->AddService("Bias/VOLT/ID00",i+96);
+  }
+
+  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/ConsoleOut", true);
+  Text->setFixedWidth(400);
+  Layout->addWidget(Text, 11, 0, 4, 4);
+
+  // Current page
+  EddWindow *Button = new EddWindow("Currents", "Edd - Edd - Bias currents");
+  Layout->addWidget(Button, 13, 4, 1, 1);      
+
+  Plot = new EddPlot();
+
+  for (int i=0; i<36; i++) {
+    Line = new EddLineDisplay("Bias/MICROAMP/ID00", i+64);
+	Line->setMaximumWidth(60);
+    Button->Layout()->addWidget(Line, i%9, 0+i/9, 1, 1);
+    Plot->AddService("Bias/MICROAMP/ID00", i+64);	
+  }
+  Button->Layout()->addWidget(Plot, 0, 4, 30, 12);
+}
+
+//
+// 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("Feedback/Average", i);
+	Line->setMaximumWidth(60);
+    Layout->addWidget(Line, i%9+2, 0+i/9, 1, 1);
+    Plot->AddService("Feedback/Average", i);
+  }
+  Layout->addWidget(Plot, 0, 4, 12, 10);
+
+  Line = new EddLineDisplay("Feedback/Message");
+  Line->setMaximumWidth(200);
+  Layout->addWidget(Line, 0, 0, 1, 2);      
+
+  Line = new EddLineDisplay("Feedback/State");
+  Line->setMaximumWidth(150);
+  Layout->addWidget(Line, 1, 0, 1, 2);      
+  Line = new EddLineDisplay("Feedback/Count");
+  Line->setMaximumWidth(60);
+  Layout->addWidget(Line, 1, 2);      
+
+  // Details page
+  EddWindow *Button = new EddWindow("Details", "Edd - Feedback Details");
+  Layout->addWidget(Button, 12, 0, 1, 1);      
+
+  Plot = new EddPlot();
+
+  for (int i=0; i<36; i++) {
+    Line = new EddLineDisplay("Feedback/Sigma", i);
+	Line->setMaximumWidth(60);
+    Button->Layout()->addWidget(Line, i%9, 0+i/9, 1, 1);
+    Plot->AddService("Feedback/Sigma", i);
+	
+    Line = new EddLineDisplay("Feedback/Target", i);
+	Line->setMaximumWidth(60);
+    Button->Layout()->addWidget(Line, i%9+10, 0+i/9, 1, 1);
+	
+	Line = new EddLineDisplay("Feedback/Response", i);
+	Line->setMaximumWidth(60);
+    Button->Layout()->addWidget(Line, i%9+20, 0+i/9, 1, 1);
+  }
+  Button->Layout()->addWidget(Plot, 0, 4, 30, 12);
+}
+
+
+//
+// FADctrl page
+//
+TP_FADctrl::TP_FADctrl() {
+
+  QString Board;
+
+  QScrollArea *scrollArea = new QScrollArea;
+  scrollArea->setBackgroundRole(QPalette::Dark);
+  scrollArea->setWidget(this);
+  setMinimumSize(QSize(0,0)); 
+  
+  QGridLayout *Layout = new QGridLayout(this);
+  setAttribute(Qt::WA_DeleteOnClose);
+  EddLineDisplay *Line;
+
+  Line = new EddLineDisplay("FADctrl/Message");
+  Line->setMaximumWidth(200);
+  Layout->addWidget(Line, 0, 0, 1, 3);      
+
+  EddCommand *Command = new EddCommand("FADctrl/Command");
+  Layout->addWidget(Command, 1, 0, 1, 3);    
+
+  EddText *Text = new EddText("FADctrl/ConsoleOut", true);
+  Text->setFixedWidth(400);
+  Layout->addWidget(Text, 2, 0, 4, 4);
+
+  EddPlot *Plot = new EddPlot();
+  Layout->addWidget(Plot, 2, 4, 4, 4);
+
+  // Details page
+  EddWindow *Button[2];
+  Button[0] = new EddWindow("Boards 0-19", "Edd - FADctrl - Board 0 to 19");
+  Button[1] = new EddWindow("Boards 20-39", "Edd - FADctrl - Board 20 to 39");
+  Layout->addWidget(Button[0], 7, 0, 1, 1);      
+  Layout->addWidget(Button[1], 7, 1, 1, 1);      
+
+  for (int i=0; i<40; i++) {
+    Board = Board.sprintf("FADctrl/Board%.2d/", i);
+    Line = new EddLineDisplay(Board+"Server");
+	Line->setMinimumWidth(90);
+    Button[i/20]->Layout()->addWidget(Line, i+7, 0, 1, 1);
+
+    Line = new EddLineDisplay(Board+"BoardID");
+	Line->setMaximumWidth(30);
+    Button[i/20]->Layout()->addWidget(Line, i+7, 1, 1, 1);
+
+    Line = new EddLineDisplay(Board+"Frequency");
+	Line->setMaximumWidth(40);
+    Button[i/20]->Layout()->addWidget(Line, i+7, 2, 1, 1);
+
+	for (int j=0; j<4; j++) {
+	  Plot->AddService(Board+"Temperature", j);
+
+      Line = new EddLineDisplay(Board+"Temperature", j);
+	  Line->setMinimumWidth(40);
+      Button[i/20]->Layout()->addWidget(Line, i+7, 3+j, 1, 1);
+	}
+
+	for (int j=0; j<8; j++) {
+      Line = new EddLineDisplay(Board+"DAC", j);
+	  Line->setMinimumWidth(40);
+      Button[i/20]->Layout()->addWidget(Line, i+7, 7+j, 1, 1);
+	}
+
+	Line = new EddLineDisplay(Board+"Status");
+	Line->setMaximumWidth(150);
+    Button[i/20]->Layout()->addWidget(Line, i+7, 15, 1, 1);
+  }
+}
+
+//
+// Event scope page
+//
+TP_DAQ::TP_DAQ() {
+
+  EddLineDisplay *Line;
+
+  setAttribute(Qt::WA_DeleteOnClose);
+  QGridLayout *Layout = new QGridLayout(this);
+
+  // Run-related information 
+  Line = new EddLineDisplay("drsdaq/RunNumber");
+  Line->setMaximumWidth(100);
+  Layout->addWidget(Line, 0, 1, 1, 1);      
+  Line = new EddLineDisplay(DRSBoard+"/EventNumber");
+  Line->setMaximumWidth(100);
+  Layout->addWidget(Line, 0, 2, 1, 1);      
+  Line = new EddLineDisplay("drsdaq/RunSizeMB");
+  Line->setMaximumWidth(100);
+  Layout->addWidget(Line, 0, 3, 1, 1);      
+  Line = new EddLineDisplay("drsdaq/FileSizeMB");
+  Line->setMaximumWidth(100);
+  Layout->addWidget(Line, 0, 4, 1, 1);      
+  Line = new EddLineDisplay("drsdaq/FileName");
+  Line->setMaximumWidth(200);
+  Layout->addWidget(Line, 0, 5, 1, 1);      
+
+  // Message service
+  Line = new EddLineDisplay(DRSBoard+"/Message");
+  Line->setMaximumWidth(200);
+  Layout->addWidget(Line, 1, 1, 1, 2);      
+
+  // 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, 2, 1, 6, 5);
+
+  // 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 QSpinBox;
+  PixelID->setMaximumWidth(60);
+  PixelID->setRange(-1, 9999);
+  PixelID->setSpecialValueText("n/a");
+  connect(PixelID, SIGNAL(valueChanged(int)), SLOT(TranslatePixelID(int)));
+  PixelID->setToolTip("Pixel identification");
+  
+  // Layout of pixel addressing widgets
+  QFormLayout *FormLayout = new QFormLayout();
+  FormLayout->setRowWrapPolicy(QFormLayout::WrapAllRows);
+  FormLayout->addRow("Board", Board);
+  FormLayout->addRow("Chip", Chip);
+  FormLayout->addRow("Channel", Channel);
+  FormLayout->addRow("Pixel ID", PixelID);
+  Layout->addLayout(FormLayout, 0, 0, 4, 1);
+  
+  // Add trace permanently
+  QPushButton *Button = new QPushButton("Keep trace");
+  Button->setToolTip("Keep trace in display");
+  Button->setMaximumWidth(80);
+  Layout->addWidget(Button, 4, 0);
+  connect(Button, SIGNAL(clicked()), SLOT(KeepCurrent()));
+
+  // Stop/start
+  StartStopButton = new QPushButton("Stop");
+  StartStopButton->setToolTip("Start/stop display");
+  StartStopButton->setMaximumWidth(80);
+  StartStopButton->setCheckable(true);
+  QPalette Palette = StartStopButton->palette();
+  Palette.setColor(QPalette::ButtonText, Qt::blue);
+  Palette.setColor(QPalette::Button, Qt::green);
+  StartStopButton->setPalette(Palette);
+  StartStopButton->setFont(QFont("Times", 10, QFont::Bold));
+  Layout->addWidget(StartStopButton, 6, 0);
+  connect(StartStopButton, SIGNAL(toggled(bool)), SLOT(StartStop(bool)));
+
+
+  // Event display page
+  EddWindow *New = new EddWindow("Pixel display", "Edd - Event display");
+  New->setFont(QFont("Times", 10, QFont::Bold));
+  New->setMaximumWidth(80);
+  New->setToolTip("Show event display window");
+  Layout->addWidget(New, 7, 0);      
+  
+  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] = new QPushButton(New->Window());
+	Pixel[Count]->setAutoFillBackground(true);
+	Pixel[Count]->setGeometry((int) (x*12.5 + 250), (int) (y*12.5 + 250), 10, 10);
+	Pixel[Count]->show();
+	Count++;
+  }
+
+  // Connect slots for updating displays
+  connect(Scope, SIGNAL(RunHeaderChanged(QString)), RunHeaderDisplay, SLOT(setPlainText(QString)));
+  connect(Scope, SIGNAL(EventHeaderChanged(QString)), EventHeaderDisplay, SLOT(setPlainText(QString)));
+
+  StartStop(false);
+  connect(Scope, SIGNAL(PixelData(QVector<double>)), SLOT(SetPixelData(QVector<double>)));
+
+  // Call to get initial pixel ID correct
+  UpdateScope(0);
+}
+
+TP_DAQ::~TP_DAQ() {
+
+  delete[] Pixel;
+}
+
+// Translate pixel ID to board, chip, channel
+void TP_DAQ::TranslatePixelID(int ID) {
+
+  // setValue() below will call UpdateScope() through signal, therefore need to store numbers here
+  unsigned int BoardNo = Scope->Pixel_to_FPAboard(ID);
+  unsigned int PatchNo = Scope->Pixel_to_FPApatch(ID);
+  unsigned int PixelNo = Scope->Pixel_to_FPApixel(ID);
+  
+  if (BoardNo == Scope->PM_ERROR_CODE) PixelID->setValue(-1);
+  else {
+    Board->setValue(BoardNo);
+    Chip->setValue(PatchNo);
+    Channel->setValue(PixelNo);
+  }
+}
+
+// Keep current trace
+void TP_DAQ::KeepCurrent() {
+
+  Scope->AddTrace(Board->value(), Chip->value(), Channel->value());
+}
+
+// Start/stop event acquisition
+void TP_DAQ::StartStop(bool State) {
+
+  Scope->SetActive(!State);
+  StartStopButton->setText(State ? "Start" : "Stop");
+  if (!State) connect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), Scope, SLOT(Update(QString, int, QByteArray, QString, QString)));
+  else disconnect(Handler, SIGNAL(YEP(QString, int, QByteArray, QString, QString)), Scope, SLOT(Update(QString, int, QByteArray, QString, QString)));
+}
+
+// Update event scope
+void TP_DAQ::UpdateScope(int) {
+
+  // Update pixel ID
+  PixelID->setValue(Scope->FPA_to_Pixel(0, Board->value(), Chip->value(), Channel->value()));
+  if (PixelID->value() == (int) Scope->PM_ERROR_CODE) PixelID->setValue(-1);
+  
+  // 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<double> Data) {
+
+  QwtLinearColorMap Map;
+
+  for (int i=0; i<Data.size(); i++) {
+	Pixel[i]->setPalette(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);      
+
+  QPushButton *Button = new QPushButton();
+  Button->setText("ON/OFF");
+  Button->setCheckable(true);
+  connect(Button, SIGNAL(toggled(bool)), SLOT(ToggleAlarm(bool)));
+  Layout->addWidget(Button, 0, 3, 1, 1);
+
+  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);
+
+  Button = new QPushButton();
+  Button->setText("eLogBook");
+  connect(Button, SIGNAL(released()), SLOT(StartELog()));
+  Layout->addWidget(Button, 6, 1, 1, 1);
+
+  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);
+}
+
+// Toggle state of Alarm server
+void TP_Evidence::ToggleAlarm(bool State) {
+
+  if (State) DimClient::sendCommandNB((char *) "Alarm/Switch", (char *) "off");
+  else DimClient::sendCommandNB((char *) "Alarm/Switch", (char *) "on");
+}
+ 
+// Start DIM Browser
+void TP_Evidence::StartDIMBrowser() {
+
+  QProcess::startDetached("did", QStringList(), QString(getenv("DIMDIR"))+"/linux/");
+}
+
+// Start eLogBook
+void TP_Evidence::StartELog() {
+
+  QProcess::startDetached("firefox http://fact.ethz.ch/FACTelog/index.jsp");
+}
+
+ 
+//--------------------------------------------------------------------
+//*************************** Main GUI *******************************
+//--------------------------------------------------------------------
+//
+// All widgets have ultimately Central as parent.
+//
+GUI::GUI() {
+
+  Handler = new EddDim;
+
+  // Arrangement in tabs
+  TabWidget = new QTabWidget(this);
+  TabWidget->setTabsClosable(true);
+  connect(TabWidget, SIGNAL(tabCloseRequested(int)), SLOT(DetachTab(int)));
+  TabWidget->addTab(new TP_DAQ, "Event scope " + DRSBoard);
+  TabWidget->addTab(new TP_FADctrl, "FADctrl");
+  TabWidget->addTab(new TP_Bias, "Bias");
+  TabWidget->addTab(new TP_Feedback, "Feedback");
+  TabWidget->addTab(new TP_Environment, "Environment");
+  TabWidget->addTab(new TP_Evidence, "Evidence");
+
+  // Set features of main window
+  setStatusBar(new QStatusBar(this));
+  setWindowTitle("Edd - Evidence Data Display");
+  setCentralWidget(TabWidget);
+
+  // 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);
+
+  // Size and show main window
+  QSize Size = TabWidget->sizeHint()*1.1;
+  Size = Size.boundedTo(QApplication::desktop()->screenGeometry(this).size()*0.6);
+  //setMaximumSize(QApplication::desktop()->screenGeometry(this).size()*0.9);
+  TabWidget->resize(Size);
+TabWidget->setMaximumSize(QApplication::desktop()->screenGeometry(this).size()*0.9);
+  show();
+
+  // Set timer to regularly check the master alarm
+  QTimer *Timer = new QTimer(this);
+  connect(Timer, SIGNAL(timeout()), this, SLOT(CheckAlarm()));
+  Timer->start(5000);
+}  
+    
+
+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 DIM 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();
+}
+
+// Check alarm level and if Alarm server is alive
+void GUI::CheckAlarm() {
+
+  static int WarnedLevel = 0;
+  static bool AlarmServerWarned = false;
+
+  // === Check service Alarm/MasterAlarm ===
+  DimCurrentInfo MasterAlarm("Alarm/MasterAlarm", -1);
+
+  if (MasterAlarm.getInt() > WarnedLevel) {
+	QSound::play(QApplication::applicationDirPath() + "/Error.wav");
+
+    // Construct warning message box
+	QMessageBox Box;
+	Box.setWindowTitle("Edd Alarm");
+	Box.setText("Service 'Alarm/MasterAlarm' is at " + QString::number(MasterAlarm.getInt()));
+	Box.setInformativeText("Warn again for the same alarm level?");
+	Box.setIcon(QMessageBox::Warning);
+	Box.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
+	Box.setDefaultButton(QMessageBox::No);
+
+	// Check if user wants to reset warned level
+	if (Box.exec() == QMessageBox::Yes) WarnedLevel = 0;
+	else WarnedLevel = MasterAlarm.getInt();
+  }
+
+  // If MasterAlam decreased, lower also warned level
+  if (MasterAlarm.getInt() < WarnedLevel && MasterAlarm.getInt() >=0 ) WarnedLevel = MasterAlarm.getInt();
+  
+  // === Check Alarm server ===
+  DimCurrentInfo ServerList("DIS_DNS/SERVER_LIST", NO_LINK);
+  std::string Result = EvidenceServer::ToString((char *) "C", ServerList.getData(), ServerList.getSize());
+
+  // Warn if SERVER_LIST does not contain alarm server
+  if (Result.find("Alarm@") == std::string::npos && !AlarmServerWarned) {
+	QMessageBox Box;
+	Box.setWindowTitle("Edd Alarm");
+	Box.setText("Alarm server is unavailable or in error");
+	Box.setIcon(QMessageBox::Critical);
+	Box.setStandardButtons(QMessageBox::Ok);
+	Box.setDefaultButton(QMessageBox::Ok);
+	Box.exec();
+
+	AlarmServerWarned = true;
+  }
+  
+  if (Result.find("Alarm@") != std::string::npos) AlarmServerWarned = false; 
+}
+
+// Quit application when clicking close button on window
+void GUI::closeEvent(QCloseEvent *) {
+
+  qApp->quit();
+}
+
+
+//
+//**************************** Main program ***************************
+//
+int main(int argc, char *argv[]) {
+
+  if (argc > 1) DRSBoard = "FADctrl";
+
+  // Make RPC to get pixelmap
+  DimRpcInfo RPC((char *) "ConfigRequest", (char *) "");
+  RPC.setData((char *) "Misc PixelMap");
+  PixelMapText = std::string(RPC.getString(), RPC.getSize());
+
+  QApplication app(argc, argv); 
+  GUI MainWindow;
+
+  return app.exec();
+}
Index: fact/tools/Edd/Edd.h
===================================================================
--- fact/tools/Edd/Edd.h	(revision 10280)
+++ fact/tools/Edd/Edd.h	(revision 10280)
@@ -0,0 +1,138 @@
+#ifndef EDD_H_SEEN
+#define EDD_H_SEEN
+
+#include "../../Evidence/GUI.h"
+#include "RawDataCTX.h"
+#include "PixelMap.h"
+
+#define SVN_REVISION "$Revision: 10143 $"
+
+// Event oscilloscope
+class EventScope: public EddBasePlot, public PixelMap {
+  Q_OBJECT
+
+  private:
+    struct ItemDetails {
+	  unsigned int Board, Chip, Channel;
+      QwtPlotCurve *Signal;
+	  QwtPlotMarker *Trigger;
+    };
+    QList<struct ItemDetails> List;
+
+    QString Name;
+	RawDataCTX *RD;
+	CTX_ErrCode ErrCode;
+	QAction *PhysPipeAction;
+	QAction *PersistanceAction;
+	FILE *Tmpfile;
+
+  public:
+    EventScope(QWidget * = NULL);
+    ~EventScope();
+	
+	void UpdateFirst(int, int, int);
+	void AddTrace(int, int, int);
+	void SetActive(bool);
+	QString ToPixel(unsigned int, unsigned int, unsigned int, unsigned int);
+
+  private slots:
+	void Update(QString, int, QByteArray, QString, QString);
+	void PlotTraces();
+	void DeleteCurve(QwtPlotCurve *);
+	
+  signals:
+	void RunHeaderChanged(QString);
+	void EventHeaderChanged(QString);
+	void PixelData(QVector<double>);
+};
+
+
+// Tab page classes
+class TP_Environment: public QWidget {
+  Q_OBJECT
+
+  public:
+    TP_Environment();
+};
+
+class TP_Bias: public QWidget {
+  Q_OBJECT
+
+  public:
+    TP_Bias();
+};
+
+class TP_FADctrl: public QWidget {
+  Q_OBJECT
+
+  public:
+    TP_FADctrl();
+};
+
+class TP_Feedback: public QWidget {
+  Q_OBJECT
+
+  public:
+    TP_Feedback();
+};
+
+class TP_DAQ: public QWidget {
+  Q_OBJECT
+  static const int MAXPIXEL = 1440;
+
+  private:
+	EventScope *Scope;
+    QPlainTextEdit *RunHeaderDisplay, *EventHeaderDisplay;
+
+ 	QSpinBox *Channel, *Chip, *Board, *PixelID;
+ 	QFormLayout *FormLayout;
+	QWidget *Display;
+	QPushButton **Pixel;
+	QPushButton *StartStopButton;
+
+  private slots:
+	void TranslatePixelID(int);
+	void UpdateScope(int);
+	void KeepCurrent();
+	void StartStop(bool);
+	void ShowPixelDisplay();
+	void SetPixelData(QVector<double>);
+
+  public:
+	TP_DAQ();	
+	~TP_DAQ();	
+};
+
+class TP_Evidence: public QWidget {
+  Q_OBJECT
+
+  private slots:
+	void ToggleAlarm(bool);
+	void StartDIMBrowser();
+	void StartELog();
+
+  public:
+	TP_Evidence();	
+};
+
+
+// Main window class
+class GUI: public QMainWindow, public DimBrowser {
+  Q_OBJECT
+
+  private:
+    QTabWidget *TabWidget;
+            
+    void closeEvent(QCloseEvent *);
+	
+  public:
+    GUI();
+    
+  private slots:
+    void MenuAbout();
+    void MenuNewHistory();
+	void DetachTab(int);
+	void CheckAlarm();
+};
+
+#endif
Index: fact/tools/Edd/Edd.pro
===================================================================
--- fact/tools/Edd/Edd.pro	(revision 10280)
+++ fact/tools/Edd/Edd.pro	(revision 10280)
@@ -0,0 +1,10 @@
+
+TEMPLATE = app
+TARGET = 
+DEPENDPATH += .
+INCLUDEPATH += . ../../Evidence $(QWTDIR)/include $(DIMDIR)/dim ../../drsdaq ../../pixelmap
+
+# Input
+HEADERS += Edd.h ../../Evidence/GUI.h ../../Evidence/Evidence.h
+SOURCES += Edd.cc ../../Evidence/GUI.cc ../../Evidence/Evidence.cc ../../drsdaq/RawDataCTX.cc ../../pixelmap/Pixel.cc ../../pixelmap/PixelMap.cc
+LIBS += -L$(QWTDIR)/lib -lqwt $(DIMDIR)/linux/libdim.a
