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
