Index: /fact/Evidence/GUI.cc
===================================================================
--- /fact/Evidence/GUI.cc	(revision 11087)
+++ /fact/Evidence/GUI.cc	(revision 11088)
@@ -20,32 +20,38 @@
 // History chooser function (opens plot for numeric data, TextHist for all other)
 //
-QWidget *OpenHistory(char *Service, int Index) {
+void OpenHistory(char *Service, int Index) {
 
   QString Format;
   DimBrowser Browser;
   class EvidenceHistory *Hist = Handler->GetHistory(Service);
+  char *Name, *Fmt;
 
   // 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();
+  }
+  else Format = Hist->GetFormat();
+  
+  // If service currently available, take its format
+  Browser.getServices(Service);
+  if (Browser.getNextService(Name, Fmt) != 0) Format = QString(Fmt);
+
   Handler->DropHistory(Service);
   
-  if (Format.size() == 1 && Format[0] != 'C') return new EddPlot(Service, Index);
-  else return new EddText(Service);
-  
-  
+  // Open new window  	
+  QMainWindow *M = new QMainWindow;
+  M->setCentralWidget(new QWidget(M));
+  M->setStatusBar(new QStatusBar(M));
+  M->setAttribute(Qt::WA_DeleteOnClose);
+  
+  QWidget *W;
+  if (Format.size() == 1 && Format[0] != 'C') W = new EddPlot(Service, Index);
+  else W = new EddText(Service);
+
+  QGridLayout *Layout = new QGridLayout(M->centralWidget());
+  Layout->addWidget(W, 0, 0);
+  Layout->addWidget(new EddLineDisplay(Service, Index), 1, 0);
+  M->resize(300,350);
+  M->show();
 }
 
@@ -183,5 +189,5 @@
 //
 void EddLineDisplay::contextMenuEvent(QContextMenuEvent *Event) {
-
+ 
   Menu->exec(Event->globalPos());
 }
@@ -190,6 +196,5 @@
 void EddLineDisplay::MenuOpenHistory() {
   
-  LastHist = OpenHistory(ServiceName.toAscii().data(), Index);
-  if (LastHist != NULL) LastHist->show();
+  OpenHistory(ServiceName.toAscii().data(), Index);
 }
 
@@ -217,5 +222,19 @@
 
   setToolTip("Send command "+Name);  
+  setStatusTip(QString("Command %1").arg(Name));
+
   connect(this, SIGNAL(returnPressed()), SLOT(SendCommand()));
+}
+
+// Get command format and returns NULL (not empty) QString if not available
+QString EddCommand::GetFormat() {
+
+  char *ItsName, *Format;
+  DimBrowser Browser;
+
+  Browser.getServices(Name.toAscii().data());
+  
+  if (Browser.getNextService(ItsName, Format) != DimCOMMAND) return QString();	  
+  else return Format;
 }
 
@@ -223,8 +242,80 @@
 void EddCommand::SendCommand() {
 
-  DimClient::sendCommand(Name.toAscii().data(), text().toAscii().data());
+  QByteArray Data;
+
+  // Ignore empty commands  
+  if (text().isEmpty()) return;
+
+  // Check if command available and retrieve format
+  QString Format = GetFormat();
+
+  if (Format.isNull()) {
+	QMessageBox::warning(this, "Edd Message", "Command " + Name + " currently unavailable", QMessageBox::Ok);
+	return;	  
+  }
+
+  // Command has no argument
+  if (Format.isEmpty()) {
+	DimClient::sendCommand(Name.toAscii().data(), NULL, 0);
+	return;
+  }
+  
+  // Command has string as argument
+  if (Format == "C") {
+	DimClient::sendCommand(Name.toAscii().data(), text().toAscii().data());
+	return;
+  }
+
+  // Command has structure format, interpret data as hex
+  if (Format.size() > 1) {
+	Data = QByteArray::fromHex(text().toAscii());
+  }
+  else {
+	QDataStream Stream(&Data, QIODevice::WriteOnly);
+	std::vector<std::string> Values = EvidenceServer::Tokenize(text().toStdString());
+
+	for (unsigned int i=0; i<Values.size(); i++) {
+	  switch (Format[0].toAscii()) {
+      case 'I':
+      case 'L': Stream << (int) atoi(Values[i].c_str());			break;
+      case 'S': Stream << (short) atoi(Values[i].c_str());			break;
+      case 'F': Stream << (float) atof(Values[i].c_str());			break;
+      case 'D': Stream << (double) atof(Values[i].c_str());			break;
+      case 'X': Stream << (long long) (atof(Values[i].c_str()));	break;
+	  }	
+	}
+  }
+  
+  DimClient::sendCommand(Name.toAscii().data(), Data.data(), Data.size());
   clear();
 }
 
+// Opening context menu
+void EddCommand::contextMenuEvent(QContextMenuEvent *Event) {
+
+  QMenu *Menu = createStandardContextMenu();
+  Menu->addSeparator();
+  Menu->addAction("Command help", this, SLOT(MenuCommandHelp()));
+  Menu->exec(Event->globalPos());
+  delete Menu;
+}
+
+// Help text
+void EddCommand::MenuCommandHelp() {
+
+  QString Format = GetFormat();
+  QString Text = Name;
+  
+  if (Format.isNull()) {
+	Text += " is currently unavailable";
+  }
+  else 	Text += " has format '"+Format+"'";
+
+  Text +=	"\n\nCommand arguments will be transmitted as string for format 'C',\n"
+  			"interpreted as an array of numbers for all other single character formats\n"
+			"and as bytes in hexadecimal encoding for all more complex formats";
+
+  QMessageBox::about(this, "Edd - Command help", Text);
+}
 
 //////////////////////////////////
@@ -253,5 +344,10 @@
   Menu->removeAction(Action);
   Menu->insertAction(Menu->actions().value(1), Action);
-  
+
+  Action = Menu->addAction("Show last hour", this, SLOT(MenuShowLastHour())); 
+  Menu->insertAction(Menu->actions().value(8), Action);
+  Action = Menu->addAction("Show last day", this, SLOT(MenuShowLastDay())); 
+  Menu->insertAction(Menu->actions().value(8), Action);  
+   
   // Set timer to regularly update plot if new data available
   SingleShot = new QTimer(this);
@@ -378,4 +474,20 @@
   AddService(E.left(E.lastIndexOf(' ')), E.right(E.size()-E.lastIndexOf(' ')).toInt());
 }
+
+// Show last hour/last day
+void EddPlot::MenuShowLastHour() {
+
+  setAxisScale(QwtPlot::xBottom, time(NULL)-60*60, time(NULL)+60);
+  setAxisAutoScale(QwtPlot::yLeft);
+  replot();
+}
+
+void EddPlot::MenuShowLastDay() {
+
+  setAxisScale(QwtPlot::xBottom, time(NULL)-24*3600, time(NULL)+3600);
+  setAxisAutoScale(QwtPlot::yLeft);
+  replot();
+}
+
 
 // Remove list entry
@@ -783,5 +895,5 @@
 void EddBasePlot::MenuPlotHelp() {
 
-  QMessageBox::about(this, "Edd - Plot navigation",
+  QMessageBox::about(this, "Edd - Plot help",
     "Zoom\tMouse wheel\n"
 	"\tKeys m and shift-m\n"
@@ -793,5 +905,7 @@
 	"ESC cancels selection\n"
     "Cursor keys move mouse\n\n"
-	"Statistics are calculated over the current x axis extend");
+	"Statistics are calculated over the current x axis extend\n\n"
+	"Items can be added to history plot by drag&drop from\n"
+	"DIM service displays or from other plots legends");
 }
 
@@ -880,9 +994,10 @@
   Mutex = new QMutex(QMutex::Recursive);
   Volume = 0;
+  Period = 10;
 
   // Timer to calculate data rates
   QTimer *Timer = new QTimer(this);
   Timer->connect(Timer, SIGNAL(timeout()), this, SLOT(UpdateStatistics()));
-  Timer->start(10000);
+  Timer->start(Period*1000);
 
   // Connect to DIM handler
@@ -917,8 +1032,8 @@
 void EddDim::MakeSubscriptions() {
 
-  if (WaitingList.isEmpty()) return;
-
   // Lock before accessing list
   QMutexLocker Locker(Mutex);
+
+  if (WaitingList.isEmpty()) return;
 
   QString Name = WaitingList.first();
@@ -1001,5 +1116,5 @@
   }
   
-  float Rate = Volume/1024.0/10;
+  float Rate = Volume/1024.0/Period;
   Volume = 0;
   YEP("Edd/Rate_kBSec", time(NULL), QByteArray::number(Rate), "F", QString::number(Rate));
@@ -1034,7 +1149,4 @@
   }
 
-  // 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);
@@ -1047,6 +1159,9 @@
   if (!EvidenceServer::ServiceOK(getInfo())) INT(getInfo()->getName(), -1);
   else {
+    // Signal to EddDim::Update()
 	INT(getInfo()->getName(), getInfo()->getTimestamp(), QByteArray((char *) getInfo()->getData(), 
 		getInfo()->getSize()), getInfo()->getFormat(), QString::fromStdString(EvidenceServer::ToString(getInfo()->getFormat(), getInfo()->getData(), getInfo()->getSize())));
+	// No mutex protection for Volume (otherwise must be in Update())
+	Volume += getInfo()->getSize();
   }
 }
@@ -1064,18 +1179,8 @@
   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();
-	M->raise();
-  }
+  connect(this, SIGNAL(pressed()), M, SLOT(show()));
+  connect(this, SIGNAL(pressed()), M, SLOT(raise()));
 }
 
Index: /fact/Evidence/GUI.h
===================================================================
--- /fact/Evidence/GUI.h	(revision 11087)
+++ /fact/Evidence/GUI.h	(revision 11088)
@@ -31,7 +31,6 @@
 const QColor EddPlotBackgroundColor(Qt::yellow);
 
-QWidget *OpenHistory(char *, int);
+void OpenHistory(char *, int);
 bool SetStatus(QWidget *, QString, int, QString, int = -1);
-
 
 // Base class for Edd plot
@@ -131,5 +130,7 @@
 
 	QString Name;
-	
+
+	QString GetFormat();
+
   public:
     EddCommand(QString, QWidget * = NULL);
@@ -137,4 +138,6 @@
   private slots:
 	void SendCommand();	
+    void contextMenuEvent(QContextMenuEvent *);
+	void MenuCommandHelp();   
 };
 
@@ -192,4 +195,6 @@
 	void LegendClicked(QwtPlotItem *);
     void MenuPasteService();
+	void MenuShowLastHour();
+	void MenuShowLastDay();
 };
 
@@ -238,4 +243,5 @@
     QMap<QString, struct HistItem> HistoryList;
 
+	unsigned int Period;
 	long long Volume;
 
@@ -273,6 +279,4 @@
 	QGridLayout *Layout();
 	QMainWindow *Window();
-  private slots:
-	void Toggle();
 };
 
Index: /fact/Evidence/History.cc
===================================================================
--- /fact/Evidence/History.cc	(revision 11087)
+++ /fact/Evidence/History.cc	(revision 11088)
@@ -31,4 +31,5 @@
 const string DEFAULT_MAX_SIZE_KB = "2000";
 const string DEFAULT_NUM_ENTRIES = "1000";	// Number of entries in each history buffer
+const double MIN_SAVE_PERDIOD = 0.5;			// Minimum period between saving history buffers in hours
 
 //
@@ -44,4 +45,5 @@
 	  double MinAbsChange;
 	  string Format;
+	  time_t LastSave;
 	};
 	map<string, struct Item> Map;
@@ -55,4 +57,5 @@
 	void AddService(string, const char *);
 	void RemoveService(string);
+	void SaveHistory(string);
 	off_t FileSize(FILE *);
 	FILE *OpenFile(string, const char *);
@@ -73,4 +76,5 @@
   GetConfig("maxsize_kb", DEFAULT_MAX_SIZE_KB);
   GetConfig("numentries", DEFAULT_NUM_ENTRIES);
+  GetConfig("saveperiod", "1");
   GetConfig("exclude", "");
 
@@ -95,5 +99,5 @@
   // Check if service available
   if (!ServiceOK(I)) return;
-
+  
   // ====== Part A: Handle service subscriptions ===
   
@@ -143,4 +147,10 @@
   if (Map.count(Service) == 0 || I->getSize()==0 || I->getTimestamp()<=0) return;
 
+  // Save history buffers periodically (in case of program crash)
+  if (time(NULL)-Map[Service].LastSave > max(atof(GetConfig("saveperiod").c_str()),MIN_SAVE_PERDIOD)*3600) {
+    Map[Service].LastSave = time(NULL);
+	SaveHistory(Service);
+  }
+
   // Resize buffer if necessary
   int NEntries = atoi(GetConfig("numentries").c_str());
@@ -266,4 +276,5 @@
   Map[Name].Format = Format;
   Map[Name].MinAbsChange = 0.0;
+  Map[Name].LastSave = 0.0;
   
   // Set minimum required change if given in configuratrion
@@ -319,5 +330,17 @@
 
   // Save history buffer
+  SaveHistory(Name);
+
+  Map.erase(Name);
+}
+
+
+//
+// Save history buffer to file
+//
+void History::SaveHistory(string Name) {
+
   FILE *File = OpenFile(Name, "wb");
+
   if (File != NULL) {
     fwrite(&Map[Name].Next, sizeof(Map[Name].Next), 1, File);					// Next pointer
@@ -327,12 +350,12 @@
 	// If error, try to delete (possibly erroneous) file 
 	if (ferror(File) != 0) {
-	  if (remove(Name.c_str()) == -1) Message(WARN, "Error writing history file '%s' in RemoveService(), could also not delete file", Name.c_str());
-	  else Message(WARN, "Error writing history file '%s' in RemoveService(), deleted file", Name.c_str());
-	}
-    if (fclose(File) != 0) Message(WARN, "Error closing history file '%s' in RemoveService()", Name.c_str());;
-  }
-
-  Map.erase(Name);
-}
+	  if (remove(Name.c_str()) == -1) Message(WARN, "Error writing history file '%s', could also not delete file", Name.c_str());
+	  else Message(WARN, "Error writing history file '%s', deleted file", Name.c_str());
+	}
+    if (fclose(File) != 0) Message(WARN, "Error closing history file '%s'", Name.c_str());
+  }
+  else Message(WARN, "Could not open history file '%s' for writing", Name.c_str());;
+}
+
 
 //
Index: /fact/Evidence/readme.txt
===================================================================
--- /fact/Evidence/readme.txt	(revision 11087)
+++ /fact/Evidence/readme.txt	(revision 11088)
@@ -51,2 +51,3 @@
 			Signal handler calls abort() if invoked three times or more.
 10/6/2011	Services can be excluded from History buffer (mainly for large event data services)
+20/6/2011	History buffers periodically saved to file (in case program crash or computer reboot)
Index: /fact/tools/Edd/Edd.cc
===================================================================
--- /fact/tools/Edd/Edd.cc	(revision 11087)
+++ /fact/tools/Edd/Edd.cc	(revision 11088)
@@ -901,5 +901,6 @@
   // Menu bar
   QMenu* Menu = menuBar()->addMenu("&Menu");
-  Menu->addAction("New history plot", this, SLOT(MenuNewHistory()));
+  Menu->addAction("Open history plot", this, SLOT(MenuNewHistory()));
+  Menu->addAction("Send command", this, SLOT(MenuCommand()));
   Menu->addAction("Raw data browser", this, SLOT(MenuRawDataBrowser()));
   Menu->addSeparator();
@@ -939,5 +940,5 @@
 }
 
-// Open request for new history plot
+// Open new history plot
 void GUI::MenuNewHistory() {
 
@@ -956,9 +957,44 @@
   // Open dialog and open history window
   QString Result = QInputDialog::getItem(this, "Edd Request",
-    "Enter DIM service name", List, 0, true, &OK);
+    "Enter or choose DIM service name (add index separated by colon)", List, 0, true, &OK);
+
+  // Check if cancelled or empty data
+  List = Result.trimmed().split(":", QString::SkipEmptyParts);
+  if (!OK || List.isEmpty()) return;
+
+  if (List.size() == 1) OpenHistory(List[0].toAscii().data(), 0);
+  else OpenHistory(List[0].toAscii().data(), atoi(List[1].toAscii().data()));
+}
+
+// Send command selected from list
+void GUI::MenuCommand() {
+
+  QStringList List;
+  char *Name, *Format;
+  int Type;
+  bool OK;
+
+  // Find all DIM commands and sort
+  getServices("*");
+  while ((Type = getNextService(Name, Format)) != 0) {
+    if (Type==DimCOMMAND) List.append(Name);
+  }
+  List.sort();
+
+  // Open dialog and open window for entering command
+  QString Result = QInputDialog::getItem(this, "Edd Request",
+    "Enter or choose DIM command name", List, 0, true, &OK);
   if (OK && !Result.isEmpty()) {
     Result = Result.trimmed();
-    QWidget *Hist = OpenHistory(Result.toAscii().data(), 0);
-    if (Hist != NULL) Hist->show();
+	
+	QMainWindow *M = new QMainWindow;
+	M->setCentralWidget(new QWidget(M));
+	M->setStatusBar(new QStatusBar(M));
+	M->setAttribute(Qt::WA_DeleteOnClose);
+	QWidget *W = new EddCommand(Result.toAscii().data());
+	QFormLayout *FormLayout = new QFormLayout(M->centralWidget());
+	FormLayout->setRowWrapPolicy(QFormLayout::WrapAllRows);
+	FormLayout->addRow("Enter argument for " + Result, W);
+	M->show();
   }
 }
Index: /fact/tools/Edd/Edd.h
===================================================================
--- /fact/tools/Edd/Edd.h	(revision 11087)
+++ /fact/tools/Edd/Edd.h	(revision 11088)
@@ -142,4 +142,5 @@
     void MenuAbout();
     void MenuNewHistory();
+    void MenuCommand();
     void MenuRawDataBrowser();
 	void DetachTab(int, bool=false);
