#include "gui.hh"

#include "functions.hh"
#include "pdgmap.hh"

#include <iostream>
#include <algorithm>

#include <QTextStream>
#include <QFile>
#include <QTimer>
#include <QFileDialog>
#include <QProcess>
#include <QMessageBox>
#include <QRegExp>
#include <QStyle>
#include <QIcon>
#include <QCursor>
#include <QApplication>
#include <QDesktopServices>
#include <QUrl>
#include <QSqlQuery>
#include <QHeaderView>
#include <QShortcut>
#include <QNetworkAccessManager>

#include <cstdlib>
#include <cmath>
#include <fstream>
#include <sstream>

#include <sys/stat.h>
#include <sys/time.h>
#include <zlib.h>

//macro orders qt main loop to update display and respond to any other events
//needed to make progress bars display changes made in a loop and other such things
#define RUNEVENTS(a) qApp->processEvents(QEventLoop::AllEvents, a)

bool operator<(const FullHit& h1, const FullHit& h2)
{
  return h2.value<h1.value;
}
bool operator<(const Vertex& v1, const Vertex& v2)
{
  if (v1.id==v2.id)
    return v2.value<v1.value;
  else
    return v2.id<v1.id;
}


//constructor
Gui::Gui(void):
  QWidget(),
  settings_("lbne", "landed"),
  //menu widgets
  menu_(this),
  layout_(this),
  bar_(this),
  barLayout_(&bar_),
  barLabel_(&bar_),
  //event selection
  boxRecord_(),
  lblRecord_("Record", this),
  spinRecord_(this),
  lblNRecords_(this),
  btnJump_("Find", this),
  btnPrev_(QChar(0x25c0), this),
  btnNext_(QChar(0x25b6), this),
  lblID_(this),
  led_(this),
  //list vs World slider
  sides_(Qt::Horizontal, this),
  //tabber for viewers
  tabView_(&sides_),
  //parent for widgets on left of slider
  left_(),
  //layout widgets around World
  boxGL_(&left_),
  //OpenGl world itself
  gl_(&left_),
  //2d projections
  xy_(&left_, Projection::XY),
  xz_(&left_, Projection::XZ),
  yz_(&left_, Projection::YZ),
  //buttons for mouse action in 3d mode
  box_(),
  grp3d_(&left_),
  btnMove_("Move", &left_),
  btnZoom_("Zoom/Twist", &left_),
  btnPan_("Pan", &left_),
  btnOrigin_("Centre", &left_),
  //tabber
  tabLists_(&sides_),
  //tracks
  tracks_(),
  boxTracks_(&tracks_),
  boxTrackScale_(),
  lblTrackScale_("Track Scaling", this),
  txtTrackScale_(this),
  barTrackScale_(Qt::Horizontal, &tracks_),
  lstTracks_(&tracks_),
  //hits
  hits_(),
  boxHits_(&hits_),
  boxHitScale_(),
  lblHitScale_("Hit Scaling", this),
  txtHitScale_(this),
  //  lblHitFilter_("Filter", this),
  //  cbxHitFilter_(this),
  barHitScale_(Qt::Horizontal, &hits_),
  lstHits_(&hits_),
  //detector
  detector_(),
  boxDetector_(&detector_),
  //filter for following list
  txtFilter_(&detector_),
  //detector component list
  lstDetector_(&detector_),
  //recording thread for movie recording
  movie_(&gl_),
  server_(this),
  socket_(NULL)
{

  numRecords_=-1;
  record_=0;

  //settings
  //filenames
  int nfiles=settings_.beginReadArray("recent");
  for (int i=0; i<nfiles; i++)
    {
      settings_.setArrayIndex(i);
      filenames_.push_front(settings_.value("filename").toString());
    }
  settings_.endArray();
  if (filenames_.size()>9)
    filenames_.resize(9);

  //tabs
  int viewtab=settings_.value("viewerTab", 0).toInt();
  int listtab=settings_.value("listTab", 0).toInt();
  //gl colouring
  gl_.setOpacity(settings_.value("opacity", QString::number(gl_.opacity())).toString().toDouble());
  gl_.setBackground(settings_.value("backgroundRed", QString::number(gl_.backgroundRed())).toString().toDouble(),
								     settings_.value("backgroundGreen", QString::number(gl_.backgroundGreen())).toString().toDouble(),
								     settings_.value("backgroundBlue", QString::number(gl_.backgroundBlue())).toString().toDouble());
  //gl options
  gl_.setShowFullDet(settings_.value("showFullDet", gl_.showFullDet()).toBool());
  gl_.setShowWireFrame(settings_.value("showWireFrame", gl_.showWireFrame()).toBool());
  bool st=settings_.value("showTracks", gl_.showTracks()).toBool();
  bool sh=settings_.value("showHits", gl_.showHits()).toBool();
  gl_.setShowTracks(st);
  xy_.setShowTracks(st);
  xz_.setShowTracks(st);
  yz_.setShowTracks(st);
  gl_.setShowHits(sh);
  xy_.setShowHits(sh);
  xz_.setShowHits(sh);
  yz_.setShowHits(sh);
  //hit scale
  double hitscale=settings_.value("hitScale", QString::number(gl_.hitScale())).toString().toDouble();
  gl_.setHitScale(hitscale);
  xy_.setHitScale(hitscale);
  xz_.setHitScale(hitscale);
  yz_.setHitScale(hitscale);
  //track scale
  double trackscale=settings_.value("trackScale", QString::number(gl_.trackScale())).toString().toDouble();
  gl_.setTrackScale(trackscale);
  xy_.setTrackScale(trackscale);
  xz_.setTrackScale(trackscale);
  yz_.setTrackScale(trackscale);
  //hit filter
  //  cbxHitFilter_.addItems(QStringList(">10% Emax") << "Top 100" << "All");
  //  QString filtertext=settings_.value("hitFilter", cbxHitFilter_.currentText()).toString();
  //  cbxHitFilter_.setCurrentIndex(cbxHitFilter_.findText(filtertext));
  //3d action
  int iaction=settings_.value("3DAction", 0).toInt();
  //join 3d navigation buttons into cooperative
  grp3d_.setExclusive(true);
  grp3d_.addButton(&btnMove_, MOVE);
  btnMove_.setChecked(iaction==MOVE);
  grp3d_.addButton(&btnZoom_, ZOOM);
  btnZoom_.setChecked(iaction==ZOOM);
  grp3d_.addButton(&btnPan_, PAN);
  btnPan_.setChecked(iaction==PAN);
  grp3d_.addButton(&btnOrigin_, ORIGIN);
  btnOrigin_.setChecked(iaction==ORIGIN);
  //set menu buttons lower down
  //
  gl_.setAction((Action)iaction);

  //menu
  //file menu
  mnuFile_=menu_.addMenu("File");
  //open a new window (mac only)
#ifdef __APPLE__
  actNew_=mnuFile_->addAction("New Window", this, SLOT(cbNew()), Qt::CTRL+Qt::Key_N);
#endif
  //open a file in this window
  actOpen_=mnuFile_->addAction("Open", this, SLOT(cbOpen()), Qt::CTRL+Qt::Key_O);
  mnuRecent_=mnuFile_->addMenu("Recent");
  updateRecent();
  connect(mnuRecent_, SIGNAL(triggered(QAction*)), this, SLOT(cbRecent(QAction*)));
  actServer_=mnuFile_->addAction("Server", this, SLOT(cbServer()), Qt::CTRL+Qt::Key_S);
  actServer_->setCheckable(true);
  actQuit_=mnuFile_->addAction("Quit", this, SLOT(close()), Qt::CTRL+Qt::Key_Q);

  mnuRecord_=menu_.addMenu("Record");
  actPrev_=mnuRecord_->addAction("Previous Record", this, SLOT(cbPrev()), Qt::CTRL+Qt::Key_Left);
  actPrev_->setEnabled(false);
  actNext_=mnuRecord_->addAction("Next Record", this, SLOT(cbNext()), Qt::CTRL+Qt::Key_Right);

  //  otheractnext->setVisible(false);
  //, Qt::CTRL+Qt::Key_Period));
  actNext_->setEnabled(false);

  //mavigation menu
  mnuNav_=menu_.addMenu("Navigation");
  //go back to initial viewpoint
  actReset_=mnuNav_->addAction("Reset Viewpoint", this, SLOT(cbReset()), Qt::CTRL+Qt::Key_0);
  //swicth navigation mode to 'Move'
  actMove_=mnuNav_->addAction("Move", this, SLOT(cbMenuMove()), Qt::CTRL+Qt::Key_M);
  mnuNav_->insertSeparator(actMove_);
  //Move is the default, so tick it
  actMove_->setCheckable(true);
  actMove_->setChecked(iaction==MOVE);
  //switch navigation mode to Pan
  actPan_=mnuNav_->addAction("Pan", this, SLOT(cbMenuPan()), Qt::CTRL+Qt::Key_P);
  actPan_->setCheckable(true);
  actPan_->setChecked(iaction==PAN);
  //switch to zoom
  actZoom_=mnuNav_->addAction("Zoom/Twist", this, SLOT(cbMenuZoom()), Qt::CTRL+Qt::Key_Z);
  actZoom_->setCheckable(true);
  actZoom_->setChecked(iaction==ZOOM);
  //switch to move origin
  actCentre_=mnuNav_->addAction("Centre", this, SLOT(cbMenuOrigin()), Qt::CTRL+Qt::Key_C);
  actCentre_->setChecked(iaction==ORIGIN);
  //projection menu
  mnuProj_=menu_.addMenu("Projection");
  connect(mnuProj_, SIGNAL(triggered(QAction*)), this, SLOT(cbProjMenu(QAction*)));
  //autoscale
  actAuto_=mnuProj_->addAction("Auto-range");
  actAuto_->setShortcuts(QList<QKeySequence>() <<QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_A) << QKeySequence(Qt::CTRL+Qt::Key_A));
  //zoom in
  actZoomIn_=mnuProj_->addAction("Zoom In");
  actZoomIn_->setShortcuts(QList<QKeySequence>()<< QKeySequence(Qt::CTRL+Qt::Key_Plus) << QKeySequence(Qt::CTRL+Qt::Key_Equal));
  //zoom out
  actZoomOut_=mnuProj_->addAction("Zoom Out");
  actZoomOut_->setShortcut(Qt::CTRL+Qt::Key_Minus);
  //move
  actLeft_=mnuProj_->addAction("Move Left");
  actLeft_->setShortcut(Qt::CTRL+Qt::SHIFT+Qt::Key_Left);
  actRight_=mnuProj_->addAction("Move Right");
  actRight_->setShortcut(Qt::CTRL+Qt::SHIFT+Qt::Key_Right);
  actUp_=mnuProj_->addAction("Move Up");
  actUp_->setShortcut(Qt::CTRL+Qt::SHIFT+Qt::Key_Up);
  actDown_=mnuProj_->addAction("Move Down");
  actDown_->setShortcut(Qt::CTRL+Qt::SHIFT+Qt::Key_Down);

  //recording menu
  mnuCapture_=menu_.addMenu("Capture");
  //single png snapshot
  actSnapshot_=mnuCapture_->addAction("SnapShot", this, SLOT(cbSnap()), Qt::CTRL+Qt::Key_P);
//   //many snapshots at 40 ms intervals to go into movie
  actMovie_=mnuCapture_->addAction("Record Movie", this, SLOT(cbMovie()), Qt::CTRL+Qt::Key_R);
  //view menu
  mnuView_=menu_.addMenu("View");
  //switch to/from full screen
  actFullScreen_=mnuView_->addAction("Full Screen", this, SLOT(cbFullScreen()), Qt::CTRL+Qt::Key_F);
  //show detector wire-frame or not
  actWire_=mnuView_->addAction("Wire-Frame Detector", this, SLOT(cbWireFrame()), Qt::CTRL+Qt::SHIFT+Qt::Key_W);
  actWire_->setCheckable(true);
  mnuView_->insertSeparator(actWire_);
  //show detector surfaces or not
  actFullDet_=mnuView_->addAction("Full Detector", this, SLOT(cbFullDet()), Qt::CTRL+Qt::SHIFT+Qt::Key_F);
  actFullDet_->setCheckable(true);
  //show tracks or not
  actTracks_=mnuView_->addAction("Tracks", this, SLOT(cbTracks()), Qt::CTRL+Qt::SHIFT+Qt::Key_T);
  actTracks_->setCheckable(true);
  //show hits or not
  actHits_=mnuView_->addAction("Hits", this, SLOT(cbHits()), Qt::CTRL+Qt::SHIFT+Qt::Key_H);
  actHits_->setCheckable(true);
  //change opacity of surfaces
  actOpacity_=mnuView_->addAction("Opacity", this, SLOT(cbOpacity()), Qt::CTRL+Qt::SHIFT+Qt::Key_O);
  mnuView_->insertSeparator(actOpacity_);
  actBackground_=mnuView_->addAction("Background Colour", this, SLOT(cbBackground()), Qt::CTRL+Qt::SHIFT+Qt::Key_B);
  //set ticks on various "show" items above to indicate what is currently selected
  viewchecks();

  //help menu
  mnuHelp_=menu_.addMenu("Help");
  actManual_=mnuHelp_->addAction("Manual", this, SLOT(cbManual()));
  actAbout_=mnuHelp_->addAction("About", this, SLOT(cbAbout()));

#ifndef __APPLE__
  //on windows, the menu has to be packed into the window.  not on mac or linux
  layout_.setMenuBar(&menu_);
#else
  //on windows, a border around the window edge looks bad.  One mac/linux I like it
  layout_.setMargin(5);
#endif

  //special list font so that unicode looks clear
  listFont_.setFamily("Lucida Sans Unicode");
  lstTracks_.setFont(listFont_);
  listFontBig_=QFont(lstTracks_.font());
  listFontBig_.setPointSize((listFontBig_.pointSize()*5)/4);
  lstHits_.setFont(listFont_);
  lstDetector_.setFont(listFont_);
  gl_.setFont(listFont_);

  //add progress bar at top of window, for things which take more than a couple of seconds to do
  //    layout_.addWidget(&bar_, 1);
  bar_.setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
  bar_.hide();
  bar_.setTextVisible(false);
  barLayout_.addWidget(&barLabel_);
  barLabel_.setAlignment(Qt::AlignCenter);
  barLayout_.setContentsMargins(0,0,0,0);
  bar_.setWindowModality(Qt::WindowModal);
  bar_.setWindowFlags(bar_.windowFlags() | Qt::Dialog | Qt::Window);
  bar_.setMinimumWidth(800);

  //event selection
  layout_.addLayout(&boxRecord_);
  boxRecord_.addSpacing(10);
  boxRecord_.addWidget(&lblRecord_);
  boxRecord_.addWidget(&spinRecord_);
  boxRecord_.addWidget(&lblNRecords_);
  boxRecord_.addWidget(&btnJump_);
  boxRecord_.addSpacing(20);
  boxRecord_.addWidget(&btnPrev_);
  boxRecord_.addWidget(&btnNext_);
  boxRecord_.addSpacing(20);
  boxRecord_.addWidget(&lblID_);
  boxRecord_.addStretch();
  boxRecord_.addWidget(&led_);
  btnJump_.setEnabled(false);
  spinRecord_.setMinimum(0);
  spinRecord_.setMaximum(0);
  spinRecord_.setKeyboardTracking(false);
  connect(&spinRecord_, SIGNAL(valueChanged(int)), this, SLOT(cbSetRecord(int)));
  connect(&btnJump_, SIGNAL(clicked()), this, SLOT(cbJump()));

  //  btnPrev_.setStyleSheet("* { font-size: 30px; }");
  //  btnNext_.setStyleSheet("* { font-size: 30px; }");
  btnPrev_.setEnabled(false);
  btnNext_.setEnabled(false);
  connect(&btnPrev_, SIGNAL(clicked()), this, SLOT(cbPrev()));
  connect(&btnNext_, SIGNAL(clicked()), this, SLOT(cbNext()));

  spinRecord_.installEventFilter(new ShortcutEater(this));

  //create system to distribure main window between viewer and lists
  //a slider appears between them which can be adjusted with the mouse
  layout_.addWidget(&sides_, 100);

  tabLists_.addTab(&tracks_, "Track Segments");
  tabLists_.addTab(&hits_, "Hits");
  tabLists_.addTab(&detector_, "Detector");
  tabLists_.setCurrentIndex(listtab);

  tabView_.addTab(&left_, "3D");
  tabView_.addTab(&xy_, "X-Y Projection");
  tabView_.addTab(&xz_, "X-Z Projection");
  tabView_.addTab(&yz_, "Y-Z Projection");
  tabView_.setCurrentIndex(viewtab);
  connect(&tabView_, SIGNAL(currentChanged(int)), this, SLOT(cbRefresh()));

  //the 3d display area should not be tiny
  gl_.setMinimumSize(600,400);
  //but it should be there...
  gl_.show();
  //we would like to know if a user right-clicks to select a volums
  connect(&gl_, SIGNAL(select(int, THD)), this, SLOT(cbSelect(int, THD)));
  //we would like to know if a user double-clicks to hide a volume
  connect(&gl_, SIGNAL(hide(int)), this, SLOT(cbHide(int)));
  //and if the navigation mode changes
  connect(&gl_, SIGNAL(modeChange(Action)), this, SLOT(cbModeChange(Action)));
  //full screen switch
  connect(&gl_, SIGNAL(fullScreen()), this, SLOT(cbFullScreen()));

  //window caption, will add filename later if we get one
  setWindowTitle(QString(APPNAME));

  //insert 3d display area into left side
  boxGL_.addWidget(&gl_);
  //below that, a box for the navigation buttons
  boxGL_.addLayout(&box_);

  //lay out 3d navigation buttons below 3d display
  box_.addStretch();
  box_.addWidget(&btnMove_);
  box_.addWidget(&btnPan_);
  box_.addWidget(&btnZoom_);
  box_.addWidget(&btnOrigin_);
  box_.addStretch();

  //tracks
  boxTracks_.addLayout(&boxTrackScale_);
  boxTrackScale_.addStretch();
  boxTrackScale_.addWidget(&lblTrackScale_);
  boxTrackScale_.addWidget(&txtTrackScale_);
  boxTrackScale_.addStretch();  
  boxTracks_.addWidget(&barTrackScale_);
  boxTracks_.addWidget(&lstTracks_);
  lstTracks_.setSelectionMode(QAbstractItemView::ExtendedSelection);
  lstTracks_.setSelectionBehavior(QAbstractItemView::SelectRows);
  lstTracks_.setMinimumWidth(320);
  txtTrackScale_.setReadOnly(true);

  //hits
  boxHits_.addLayout(&boxHitScale_);
  boxHitScale_.addStretch();
  boxHitScale_.addWidget(&lblHitScale_);
  boxHitScale_.addWidget(&txtHitScale_);
  boxHitScale_.addStretch();
  //  boxHitScale_.addWidget(&lblHitFilter_);
  //  boxHitScale_.addWidget(&cbxHitFilter_);
  //  boxHitScale_.addStretch();
  boxHits_.addWidget(&barHitScale_);
  boxHits_.addWidget(&lstHits_);
  //  lstHits_.setMinimumWidth(300);
  lstHits_.setSelectionMode(QAbstractItemView::ExtendedSelection);
  lstHits_.setSelectionBehavior(QAbstractItemView::SelectRows);
  txtHitScale_.setReadOnly(true);

  double val=gl_.trackScale()/1000.;
  QString disp;
  if (val<1000)
    disp=QString::number(val, 'g', 3);
  else
    disp=QString::number((int)val);
  txtTrackScale_.setText(disp+" cm"+QChar(0x00b3)+"/MeV");
    
  val=gl_.hitScale()/1000.;
  if (val<1000)
    disp=QString::number(val, 'g', 3);
  else
    disp=QString::number((int)val);
  txtHitScale_.setText(disp+" cm"+QChar(0x00b3)+"/MeV");

  txtTrackScale_.setFixedWidth(140);
  barTrackScale_.setMinimum(10);
  barTrackScale_.setMaximum(50000);
  barTrackScale_.setValue(rint(pow(gl_.trackScale(), 1./3.))*100);
  barTrackScale_.setSingleStep(1);
  barTrackScale_.setPageStep(500);

  txtHitScale_.setFixedWidth(140);
  barHitScale_.setMinimum(1);
  barHitScale_.setMaximum(5000);
  barHitScale_.setValue(rint(pow(gl_.hitScale(), 1./3.))*100);
  barHitScale_.setSingleStep(1);
  barHitScale_.setPageStep(50);

  connect(&barTrackScale_, SIGNAL(valueChanged(int)), this, SLOT(cbTrackScale(int)));
  connect(&lstTracks_, SIGNAL(itemSelectionChanged()), this, SLOT(cbTrackSelect()));
  connect(&lstTracks_, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(cbTrackJump(int, int)));

  connect(&barHitScale_, SIGNAL(valueChanged(int)), this, SLOT(cbHitScale(int)));
  connect(&lstHits_, SIGNAL(itemSelectionChanged()), this, SLOT(cbHitSelect()));
  connect(&lstHits_, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(cbHitJump(int, int)));
  //  connect(&cbxHitFilter_, SIGNAL(currentIndexChanged(int)), this, SLOT(cbHitFilter()));

  //right hand side of window
  //detector component filter
  boxDetector_.addWidget(&txtFilter_);
  //detector component list
  boxDetector_.addWidget(&lstDetector_);
  //set minimum size of list
  //  lstDetector_.setMinimumWidth(230);
  //set flexible selection mode
  lstDetector_.setSelectionMode(QAbstractItemView::ExtendedSelection);

  //detector filter change
  connect(&txtFilter_, SIGNAL(textChanged(const QString&)), this, SLOT(cbDetFilter(const QString&)));
  //detector right click
  lstDetector_.setContextMenuPolicy(Qt::CustomContextMenu);
  connect(&lstDetector_, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(cbDetConf(QPoint)));
  //detector item check
  connect(&lstDetector_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbDetector(QTableWidgetItem*)));

  //track list
  //set up columns
  lstTracks_.setColumnCount(11);
  lstTracks_.setHorizontalHeaderLabels(QStringList("vis") << "ID" << "E (MeV)" << "dE" << "Part" << "X (mm)" << "dX" << "Y (mm)" << "dY" << "Z (mm)" << "dZ");
  lstTracks_.setColumnWidth(0, 30);
  lstTracks_.setColumnWidth(1, 60);
  lstTracks_.setColumnWidth(2, 75);
  lstTracks_.setColumnWidth(3, 65);
  lstTracks_.setColumnWidth(4, 60);
  lstTracks_.setColumnWidth(5, 65);
  lstTracks_.setColumnWidth(6, 55);
  lstTracks_.setColumnWidth(7, 65);
  lstTracks_.setColumnWidth(8, 55);
  lstTracks_.setColumnWidth(9, 65);
  lstTracks_.setColumnWidth(10, 55);
  //respond to request for context menu (right click)
  lstTracks_.setContextMenuPolicy(Qt::CustomContextMenu);
  connect(&lstTracks_, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(cbTrackConf(QPoint)));
  //item check
  connect(&lstTracks_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbTrackVis(QTableWidgetItem*)));

  lstTracks_.setItemDelegateForColumn(1, new DoubleItemDelegate(0));
  lstTracks_.setItemDelegateForColumn(2, new DoubleItemDelegate(2));
  lstTracks_.setItemDelegateForColumn(3, new DoubleItemDelegate(3));
  //  lstTracks_.setItemDelegateForColumn(4, new RichTextDelegate);
  lstTracks_.setItemDelegateForColumn(5, new DoubleItemDelegate(0));
  lstTracks_.setItemDelegateForColumn(6, new DoubleItemDelegate(1));
  lstTracks_.setItemDelegateForColumn(7, new DoubleItemDelegate(0));
  lstTracks_.setItemDelegateForColumn(8, new DoubleItemDelegate(1));
  lstTracks_.setItemDelegateForColumn(9, new DoubleItemDelegate(0));
  lstTracks_.setItemDelegateForColumn(10, new DoubleItemDelegate(1));

  lstTracks_.verticalHeader()->hide();
  
  //hit list
  //set up columns
  lstHits_.setColumnCount(8);
  lstHits_.setHorizontalHeaderLabels(QStringList("vis") << "E (MeV)" << "#e-" <<"Track" << "Part" << "X (mm)" << "Y (mm)" << "Z (mm)" );
  lstHits_.setColumnWidth(0, 30);
  lstHits_.setColumnWidth(1, 75);
  lstHits_.setColumnWidth(2, 65);
  lstHits_.setColumnWidth(3, 65);
  lstHits_.setColumnWidth(4, 65);
  lstHits_.setColumnWidth(5, 65);
  lstHits_.setColumnWidth(6, 65);
  lstHits_.setColumnWidth(7, 65);
  //format double columns to specific decimal places
  //this is a ball ache for some reason.  come on qt!
  lstHits_.setItemDelegateForColumn(1, new DoubleItemDelegate(3));
  lstHits_.setItemDelegateForColumn(2, new DoubleItemDelegate(0));
  lstHits_.setItemDelegateForColumn(3, new DoubleItemDelegate(0));
  lstHits_.setItemDelegateForColumn(5, new DoubleItemDelegate(1));
  lstHits_.setItemDelegateForColumn(6, new DoubleItemDelegate(1));
  lstHits_.setItemDelegateForColumn(7, new DoubleItemDelegate(1));
  //  lstHits_.setItemDelegateForColumn(7, new RichTextDelegate(0));
  //hide vertical header
  lstHits_.verticalHeader()->hide();
  //check
  connect(&lstHits_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbHitVis(QTableWidgetItem*)));
  //respond to request for context menu (right click)
  lstHits_.setContextMenuPolicy(Qt::CustomContextMenu);
  connect(&lstHits_, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(cbHitConf(QPoint)));

  //detector list
  //set up columns
  lstDetector_.setColumnCount(1);
  lstDetector_.setHorizontalHeaderLabels(QStringList("Detector Components"));
  lstDetector_.setColumnWidth(0, 340);
  //empty out the list
  lstDetector_.clearContents();
  //disable sorting
  lstDetector_.setSortingEnabled(false);
  //hide vertical header
  lstDetector_.verticalHeader()->hide();

  //callback for 3d navigation mode
  connect(&grp3d_, SIGNAL(buttonClicked(int)), this, SLOT(cbAction(int)));

  //we have not yet been give a file to load
  //  havefile=false;
//   //we are not recording a movie
//   m_frame=-1;

  db_=QSqlDatabase::addDatabase("QSQLITE");
  db_.setConnectOptions("QSQLITE_OPEN_READONLY");

  QNetworkAccessManager* manager=new QNetworkAccessManager(this);
  connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(cbVersion(QNetworkReply*)));
  manager->get(QNetworkRequest(QUrl("http://www.hep.shef.ac.uk/research/dune/landed/version.php")));
}

void Gui::
cbVersion(QNetworkReply* reply)
{
  if (reply->error()==QNetworkReply::NoError)
    {
      QString latest=reply->readAll();
      QString current=VERSION;
      if (vercmp(qPrintable(latest), qPrintable(current))>0)
	{
	  QMessageBox msgBox(this);
	  msgBox.setWindowTitle("LANDED");
	  msgBox.setTextFormat(Qt::RichText);
	  msgBox.setText("You are running LANDED version "+current+".\nVersion "+latest+" is available from <a href='http://www.hep.shef.ac.uk/research/dune/landed/'>http://www.hep.shef.ac.uk/research/dune/landed</a>");
	  msgBox.setStandardButtons(QMessageBox::Ok);
	  msgBox.exec();

	}
    }
}

void Gui::
cbRefresh(void)
{
  int index=tabView_.currentIndex();
  if (index==0)
    gl_.refresh();
  else if (index==1)
    xy_.refresh();
  else if (index==2)
    xz_.refresh();
  else if (index==3)
    yz_.refresh();
}


//open supplied vrml file
void Gui::
loadFile(QString filename)
{
  //not using file and socket server simultaneously
  server_.close();
  actServer_->setChecked(Qt::Unchecked);

  //stoe filename for later
  //filename_=filename;
  //check file exists
  QFile file(filename);
  if (!file.exists())
    {
      QMessageBox::critical(this, "LANDED", QString("File does not exist\n")+filename);
      return;
    }
  file.close();

  db_.setDatabaseName(filename);
  db_.open();
  if (!db_.isOpen())
    {
      QMessageBox::critical(this, "LANDED", QString("Failed to open file\n")+filename+QString("\nPermissions problem?"));
      return ;
    }
  //okay so this is our file
  //  havefile=true;
  //change window title to include filename
  QFileInfo fi(filename);
  setWindowTitle(QString(APPNAME)+" - "+fi.baseName());
  std::deque<QString>::iterator it=std::find(filenames_.begin(), filenames_.end(), filename);
  if (it!=filenames_.end())
    filenames_.erase(it);
  filenames_.push_front(filename);
  if (filenames_.size()>9)
    filenames_.resize(9);
  updateRecent();

  QSqlQuery query=db_.exec("SELECT vrml, size from Geometry;");
  query.next();
  QByteArray compvrml=query.value(0).toByteArray();
  uLongf size=query.value(1).toUInt();
  query.clear();
  Bytef* vrmldata=new Bytef[size+1];
  int zerr;
  if ((zerr=uncompress(vrmldata, &size, (const Bytef*)compvrml.constData(), compvrml.length()))!=Z_OK)
    {
      QMessageBox::critical(this, "LANDED", "Failed to decompress vrml, error "+QString::number(zerr)+" detector geometry will not appear");
      return;
    }
  else
    {
      vrmldata[size]=0;
      QString allvrml((const char*)vrmldata);
      
      readVRML(allvrml);
    }
  //load event
  query=db_.exec("SELECT COUNT(*) FROM Events;");
  query.next();
  numRecords_=query.value(0).toInt();
  spinRecord_.setMaximum(numRecords_);
  loadRecord(0);
  btnJump_.setEnabled(true);
}
void Gui::
readVRML(QString vrml)
{
  //clear out existing shapes
  xy_.clearShapes();
  yz_.clearShapes();
  xz_.clearShapes();
  gl_.clearShapes();
  gl_.reset();

  //open file
  QTextStream ts(&vrml);
  //check first line
  QString header=ts.readLine();
  if (!header.contains("#VRML"))
    QMessageBox::critical(this, "LANDED", "Bad VRML");
  else
    {
      //size of file in bytes
      int filesize=vrml.length();
      //keep track of our depth inside the object brackets in the file
      int depth=0;
      //the depth at which we found the beginning of a shape
      int shape=0;
      //the depth at which we found the beginning of an indexed face set
      int ifs=0;
      //similarly for an indexed line set
      int ils=0;
      //the depth at which we found the point array, the vertices in the shape
      int point=0;
      //the depth at which we found the coordinate index, indicating which of the points to join together
      int index=0;
      //the depth at which we found the material description from where we get the colour
      int mat=0;
      glShape* s=NULL;
      //the name and replica number of the shape
      QString name;
      int rep=-1;
      //progress through file
      int progress=0;
      int lastprogress=0;
      bar_.show();
      bar_.setMaximum(filesize);
      barLabel_.setText("Loading Geometry");
      //continue until the end of the file
      while (!ts.atEnd())
	{
	  //read in 1 line
	  QString vrml=ts.readLine();
	  progress+=vrml.length();
	  if (progress-lastprogress>filesize/20)
	    {
	      bar_.setValue(progress);
	      RUNEVENTS(10);
	      lastprogress=progress;
	      cbRefresh();
	    }
	  //the "SOLID" comment gives the name of the following volume
	  if (vrml.contains("SOLID:"))
	    {
	      QStringList parts=vrml.split(QRegExp("[:.]"), QString::SkipEmptyParts);
	      if (parts.size()>=3)
		{
		  rep=parts.back().trimmed().toInt();
		  parts.pop_back();
		  parts.pop_front();
		  name=parts.join(".").trimmed();
		}
	    }
	  //find { and [ brackets in the line and adjust our bracket depth accordingly
	  //this allow us to keep track of what object we're inside
	  //qt4 implementation of "count number of times this character appears
	  depth+=vrml.count("{")+vrml.count("[");
	  depth-=vrml.count("}")+vrml.count("]");
	  //if we thought we were inside a shape
	  //but closing of brackets indicates that we're not
	  if (shape && depth<shape)
	    {
	      //not that we're not in a shape
	      shape=0;
	      //the shape we have left is complete, so add it to the collection
	      if (s->name.contains("TPCActive"))
		{
		  s->r=s->g=0;
		  s->b=1;
		  xy_.addShape(*s);
		  yz_.addShape(*s);
		  xz_.addShape(*s);
		  xy_.autoScale();
		  yz_.autoScale();
		  xz_.autoScale();
		}
	      gl_.addShape(*s);
	    }
	  //similarly if we though we were inside an indexed face set 
	  if (ifs && depth<ifs)
	    {
	      ifs=0;
	    }
	  //or an indexed line set
	  if (ils && depth<ils)
	    {
	      ils=0;
	    }
	  //or a point array
	  if (point && depth<point)
	    {
	      point=0;
	    }
	  //or a coordinate index array
	  if (index && depth<index)
	    {
	      index=0;
	    }
	  //or a material description
	  if (mat && depth<mat)
	    {
	      mat=0;
	    }
	  //okay that's the matter of leaving objects handled
	  //now onto the object information
	  //if a new shape starts here
	  if (vrml.contains(QRegExp("Shape\\s+\\{")))
	    {
	      //create a shape class instance to hold it's description
	      name.remove(0, 3);
	      name.remove(-3, 3);
	      s=new glShape(name, rep, !name.contains("TPCWire"));
	      name="";
	      rep=-1;
	      //note the bracket depth at which we found the shape
	      shape=depth;
	    }
	  //if we're inside a shape, we should look for things describing it
	  if (shape)
	    {
	      //furthermore, if in material description
	      if (mat)
		{
		  //if this line gives the colour of this shape
		  if (vrml.contains("diffuseColor"))
		    {
		      //break it up into r, g, b
		      QStringList parts=vrml.split(QRegExp("\\s"), QString::SkipEmptyParts);
		      //and store those numbers
		      if (parts.size()>=4)
			{
			  s->r=parts.at(1).toDouble();
			  s->g=parts.at(2).toDouble();
			  s->b=parts.at(3).toDouble();
			}
		    }
		}
	      //if in point array within shape
	      else if (point)
		{
		  //this is layed out as 3 space-separated numbers (x y z) then a comma, then the next 3.  It will probably also be spread over many lines
		  //break this section of the point array on commas
		  QStringList parts=vrml.split(QRegExp(","), QString::SkipEmptyParts);
		  //take each comma separated number group
		  for (int i=0; i<parts.size(); i++)
		    //if it is not empty (a parsing error)
		    if (parts.at(i).length())
		      {
			//break it again on spaces (or commas for good measure)
			QStringList parts2=parts.at(i).split(QRegExp("[\\s,]"), QString::SkipEmptyParts);
			//that should have split it into 3 numbers
			//if it did
			if (parts2.size()>=3)
			  {
			    //read those numbers as x, y and z, and add them to the collection of coordinates for this shape
			    ThreeVector v;
			    v.x=parts2.at(0).toDouble();
			    v.y=parts2.at(1).toDouble();
			    v.z=parts2.at(2).toDouble();
			    s->points.push_back(v);
			  }
		      }
		}
	      //if in coordinate index array
	      else if (index)
		{
		  //this is just a list of comma separated numbers
		  //a -1 appears to indicate the end of a face/polygon and then another follows
		  //so just break it up on commas
		  QStringList parts=vrml.split(QRegExp("[,\\n]"), QString::SkipEmptyParts);
		  //and add each comma separated number we find to the collection in the shape object
		  for (int i=0; i<parts.size(); i++)
		    {
		      parts.replace(i, parts.at(i).trimmed());
		      if (parts.at(i).length())
			{
			  s->connections.push_back(parts.at(i).toInt());
			}
		    }
		}
	      //we are just in the shape, but not inside any of it's sub-components
	      //look for the start of such a sub-component
	      else
		{
		  //an indexed face set, describing a 3d object
		  if (vrml.contains(QRegExp("geometry\\s+IndexedFaceSet\\s+\\{")))
		    {
		      //note that this shape is a 3d object and not a squiggly line
		      //remember at what bracket depth we found the indexed face set 
		      ifs=depth;
		    }
// 		  //an indexed line set describes a squiggly line
// 		  else if (vrml.contains(QRegExp("geometry\\s+IndexedLineSet\\s+\\{")))
// 		    {
// 		      //note that this is a squiggly line and not a 3d object
// 		      s->faces=false;
// 		      //remember at what bracket depth we found the indexed line set
// 		      ils=depth;
// 		    }
		  //indexed face sets and indexed line sets both contain points arrays in the same format
		  //if we're in either of those things and we see the start of a points array
		  if ((ifs || ils) && vrml.contains(QRegExp("point\\s+\\[")))
		    {
		      //remember the depth at which we found the point array
		      point=depth;
		    }
		  //similarly for a coordinate index list
		  if (vrml.contains(QRegExp("coordIndex\\s+\\[")))
		    {
		      index=depth;
		    }
		  //we've found the material description for the shape, which includes the colour
		  if (vrml.contains(QRegExp("material.+\\{")))
		    {
		      //note the bracket depth at which we found the material description
		      mat=depth;
		    }
		}
	}
	}
    }

  //reset progress bar
  barLabel_.setText("");
  bar_.reset();
  cbRefresh();
  RUNEVENTS(10);
  bar_.hide();

  //update detector components list
  detList();

  
}
void Gui::
updateRecent(void)
{
  mnuRecent_->clear();
  mnuRecent_->setEnabled(filenames_.size()>0);
  for (int i=0; i<filenames_.size(); i++)
    {
      QAction* action=mnuRecent_->addAction(QFileInfo(filenames_.at(i)).baseName());
      action->setToolTip(filenames_.at(i));
      action->setShortcut(Qt::CTRL+Qt::Key_1+i);
    }
}

//indicates whether we have a file loaded
// bool Gui::
// hasfile(void)
// {
//   return havefile;
// }

void Gui::
detList(void)
{
  //updates off, speeds things up, will turn them back on at the end
  disconnect(&lstDetector_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbDetector(QTableWidgetItem*)));
  lstDetector_.setUpdatesEnabled(false);
  //clear
  lstDetector_.clearContents();
  //
  lstDetector_.setRowCount(gl_.numShapes());
  //add each detector "Shape" to the list
  for (int i=gl_.numShapes()-1; i>=0; i--)
    {
      const glShape& shape=gl_.shape(i);
      //      std::cout << qPrintable(shape.name) << std::endl;
      //add this shape to the list
      DetectorListWidgetItem* item=new DetectorListWidgetItem(shape.name, i, shape.show);
      lstDetector_.setItem(i, 0, item);
    }
  //updates back on
  lstDetector_.setUpdatesEnabled(true);

  connect(&lstDetector_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbDetector(QTableWidgetItem*)));

}

//set event number
void Gui::
cbSetRecord(int v)
{
  if (db_.isOpen() || socket_)
    {
      loadRecord(v-1);
    }
//   timeval tv1, tv2;
//   gettimeofday(&tv1, NULL);
//  world.refresh();
//   gettimeofday(&tv2, NULL);
//   std::cout << "updating 3d took " << (tv2.tv_sec+tv2.tv_usec/1e6-tv1.tv_sec-tv1.tv_usec/1e6) << " s\n";
//   gettimeofday(&tv1, NULL);
//  xy_.refresh();
//  xz_.refresh();
//  yz_.refresh();
//   gettimeofday(&tv2, NULL);
//   std::cout << "updating 2d took " << (tv2.tv_sec+tv2.tv_usec/1e6-tv1.tv_sec-tv1.tv_usec/1e6) << " s\n";
  
}

void Gui::
loadRecord(int e)
{
  if (socket_)
    {
      //send request for event, confirm, then allow cbSocket to take care of the rest
      //      disconnect(socket_, SIGNAL(readyRead()), this, SLOT(cbSocket()));
      socket_->write(qPrintable(QString::number(e)+"\n"));
    }
  else
    {
      if (e>=0)
	record_=e;
      
      QSqlQuery query;
      
      int run, subrun, event;
      query=db_.exec("SELECT RunNumber, SubRunNumber, EventNumber FROM Events ORDER BY RunNumber, SubRunNumber, EventNumber LIMIT 1 OFFSET "+QString::number(record_)+";");
      query.next();
      run=query.value(0).toInt();
      subrun=query.value(1).toInt();
      event=query.value(2).toInt();
      
      lblID_.setText("Run: "+QString::number(run)+"  SubRun: "+QString::number(subrun)+"  Event: "+QString::number(event));

      query=db_.exec("SELECT COUNT(ID) FROM Tracks WHERE RunNumber="+QString::number(run)+" AND SubRunNumber="+QString::number(subrun)+" AND EventNumber="+QString::number(event)+";");
      query.next();
      int count=query.value(0).toInt();
      query=db_.exec("SELECT ID, PDGCode, Energy, X, Y, Z FROM Tracks WHERE RunNumber="+QString::number(run)+" AND SubRunNumber="+QString::number(subrun)+" AND EventNumber="+QString::number(event)+" ORDER BY ID ASC, Segment ASC;");
      gl_.clearTracks();
      xy_.clearTracks();
      xz_.clearTracks();
      yz_.clearTracks();
      //updates off, speeds things up, will turn them back on at the end
      lstTracks_.setUpdatesEnabled(false);
      //clear
      disconnect(&lstTracks_, SIGNAL(itemSelectionChanged()), this, SLOT(cbTrackSelect()));
      disconnect(&lstTracks_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbTrackVis(QTableWidgetItem*)));
      lstTracks_.clearContents();
      //
      lstTracks_.setSortingEnabled(false);
      lstTracks_.setRowCount(count);
      bar_.show();
      bar_.setMaximum(count);
      barLabel_.setText("Loading Tracks");
      int i=0;
      Vertex v1, v2;
      int tc=0;
      //keep map of vertex if number to pdg code
      std::map<int, int> pdgs;
      while (query.next())
	{
	  v1=v2;
	  v2.id=query.value(0).toInt();
	  v2.pdgcode=query.value(1).toInt();
	  v2.value=query.value(2).toDouble();
	  v2.x=query.value(3).toDouble()*10;
	  v2.y=query.value(4).toDouble()*10;
	  v2.z=query.value(5).toDouble()*10;
	  pdgs[v2.id]=v2.pdgcode;
	  if (i && v1.id==v2.id)
	    {
	      addTrack(v1, v2, tc); 
	      tc++;
	    }
	  i++;
	  bar_.setValue(i);
	  RUNEVENTS(1);
	}
      lstTracks_.setRowCount(tc);
      //updates back on
      lstTracks_.setSortingEnabled(true);
      lstTracks_.setUpdatesEnabled(true);
      connect(&lstTracks_, SIGNAL(itemSelectionChanged()), this, SLOT(cbTrackSelect()));
      connect(&lstTracks_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbTrackVis(QTableWidgetItem*)));
      barLabel_.setText("");
      bar_.reset();

      QString filter;
//             if (cbxHitFilter_.currentIndex()==0)
// 	{
// 	  query=db_.exec("SELECT MAX(Energy) FROM Hits WHERE RunNumber="+QString::number(run)+" AND SubRunNumber="+QString::number(subrun)+" AND EventNumber="+QString::number(event)+";");
// 	  query.next();
// 	  double max=query.value(0).toDouble();
// 	  filter=" AND Energy>"+QString::number(max/10.);
// 	}
//       else if (cbxHitFilter_.currentIndex()==1)
// 	filter=" ORDER BY Energy DESC LIMIT 100";
      
      query=db_.exec("SELECT COUNT(Energy) FROM Hits WHERE RunNumber="+QString::number(run)+" AND SubRunNumber="+QString::number(subrun)+" AND EventNumber="+QString::number(event)+" "+filter+";");
      query.next();
      count=query.value(0).toInt();
//       if (cbxHitFilter_.currentIndex()==1 && count>100)
// 	count=100;
      
      query=db_.exec("SELECT NumElectrons, Energy, X, Y, Z, Track FROM Hits WHERE RunNumber="+QString::number(run)+" AND SubRunNumber="+QString::number(subrun)+" AND EventNumber="+QString::number(event)+" "+filter+";");
      gl_.clearHits();
      xy_.clearHits();
      xz_.clearHits();
      yz_.clearHits();
      //updates off, speeds things up, will turn them back on at the end
      lstHits_.setUpdatesEnabled(false);
      //clear
      disconnect(&lstHits_, SIGNAL(itemSelectionChanged()), this, SLOT(cbHitSelect()));
      disconnect(&lstHits_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbHitVis(QTableWidgetItem*)));
      lstHits_.clearContents();
      //
      lstHits_.setSortingEnabled(false);
      lstHits_.setRowCount(count);
      bar_.show();
      bar_.setMaximum(count);
      barLabel_.setText("Loading Hits");
      i=0;
      while (query.next())
	{
	  //configure hit
	  Hit hit;
	  hit.value=query.value(1).toDouble();
	  hit.x=query.value(2).toDouble()*10;
	  hit.y=query.value(3).toDouble()*10;
	  hit.z=query.value(4).toDouble()*10;
	  addHit(hit, query.value(0).toDouble(), query.value(5).toInt(), pdgs[abs(query.value(5).toInt())], i);
	  
	  i++;
	  bar_.setValue(i);
	  RUNEVENTS(1);
	}
      //updates back on
      lstHits_.setSortingEnabled(true);
      lstHits_.setUpdatesEnabled(true);
      connect(&lstHits_, SIGNAL(itemSelectionChanged()), this, SLOT(cbHitSelect()));
      connect(&lstHits_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbHitVis(QTableWidgetItem*)));
      barLabel_.setText("");
      bar_.reset();
      bar_.hide();
      
      disconnect(&spinRecord_, SIGNAL(valueChanged(int)), this, SLOT(cbSetRecord(int)));
      spinRecord_.setValue(record_+1);
      connect(&spinRecord_, SIGNAL(valueChanged(int)), this, SLOT(cbSetRecord(int)));
      
      btnPrev_.setEnabled(record_>0);
      btnNext_.setEnabled(numRecords_<0 || record_<numRecords_-1);
    }
      
  cbRefresh();
}

void Gui::
addHit(const Hit& hit, double nume, int track, int pdgcode, int i)
{
  gl_.addHit(hit);
  xy_.addHit(hit);
  xz_.addHit(hit);
  yz_.addHit(hit);
  //add this hit to the list
  QTableWidgetItem* item=new QTableWidgetItem;
  item->setData(Qt::UserRole, i);
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
  item->setCheckState(hit.show?Qt::Checked:Qt::Unchecked);
  lstHits_.setItem(i, 0, item);
  item=new QTableWidgetItem;
  item->setData(Qt::DisplayRole, hit.value);
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  lstHits_.setItem(i, 1, item);
  item=new QTableWidgetItem;
  item->setData(Qt::DisplayRole, nume);
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  lstHits_.setItem(i, 2, item);
  item=new QTableWidgetItem;
  if (!track)
    std::cout << track << std::endl;
  item->setData(Qt::DisplayRole, track);
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  lstHits_.setItem(i, 3, item);
  item=new QTableWidgetItem;
  std::pair<QString,QString> pdg=pdgstring(pdgcode);
  //  item->setData(Qt::InitialSortOrderRole, pdg.first);
  if (pdg.second[0].isLower() && pdg.second.length()<6)
    item->setFont(listFontBig_);
  QString pdgstr=pdg.second;
  if (track<0)
    pdgstr=QString::fromUtf8("\u21d8")+pdgstr;
  item->setData(Qt::DisplayRole, pdgstr);
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  lstHits_.setItem(i, 4, item);
  item=new QTableWidgetItem;
  item->setData(Qt::DisplayRole, hit.x);
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  lstHits_.setItem(i, 5, item);
  item=new QTableWidgetItem;
  item->setData(Qt::DisplayRole, hit.y);
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  lstHits_.setItem(i, 6, item);
  item=new QTableWidgetItem;
  item->setData(Qt::DisplayRole, hit.z);
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  lstHits_.setItem(i, 7, item);
}
void Gui::
addTrack(const Vertex& v1, const Vertex& v2, int i)
{
  Track track;
  track.start=v1;
  track.end=v2;
  track.value=v1.value+(v2.value-v1.value)/2;
  track.delta=v1.value-v2.value;
  track.pdgcode=v1.pdgcode;
  gl_.addTrack(track);
  xy_.addTrack(track);
  xz_.addTrack(track);
  yz_.addTrack(track);
  QTableWidgetItem* item=new QTableWidgetItem;
  item->setData(Qt::UserRole, i);
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
  item->setCheckState(track.show?Qt::Checked:Qt::Unchecked);
  lstTracks_.setItem(i, 0, item);
  item=new QTableWidgetItem;
  item->setData(Qt::DisplayRole, v1.id);
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  lstTracks_.setItem(i, 1, item);
  item=new QTableWidgetItem;
  item->setData(Qt::DisplayRole, track.value);
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  lstTracks_.setItem(i, 2, item);
  item=new QTableWidgetItem;
  item->setData(Qt::DisplayRole, track.delta);
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  lstTracks_.setItem(i, 3, item);
  item=new QTableWidgetItem;
  std::pair<QString,QString> pdg=pdgstring(v1.pdgcode);
  //  item->setData(Qt::InitialSortOrderRole, pdg.first);
  if (pdg.second[0].isLower() && pdg.second.length()<6)
    item->setFont(listFontBig_);
  item->setData(Qt::DisplayRole, pdg.second);
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  lstTracks_.setItem(i, 4, item);
  item=new QTableWidgetItem;
  item->setData(Qt::DisplayRole, v1.x+(v2.x-v1.x)/2);
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  lstTracks_.setItem(i, 5, item);
  item=new QTableWidgetItem;
  item->setData(Qt::DisplayRole, fabs(v2.x-v1.x));
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  lstTracks_.setItem(i, 6, item);
  item=new QTableWidgetItem;
  item->setData(Qt::DisplayRole, v1.y+(v2.y-v1.y)/2);
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  lstTracks_.setItem(i, 7, item);
  item=new QTableWidgetItem;
  item->setData(Qt::DisplayRole, fabs(v2.y-v1.y));
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  lstTracks_.setItem(i, 8, item);
  item=new QTableWidgetItem;
  item->setData(Qt::DisplayRole, v1.z+(v2.z-v1.z)/2);
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  lstTracks_.setItem(i, 9, item);
  item=new QTableWidgetItem;
  item->setData(Qt::DisplayRole, fabs(v2.z-v1.z));
  item->setFlags(item->flags() &  ~Qt::ItemIsEditable);
  lstTracks_.setItem(i, 10, item);

}

//override close event
void Gui::
closeEvent(QCloseEvent* event)
{
  settings_.setValue("3DAction", (int)gl_.action());
  settings_.setValue("trackScale", QString::number(gl_.trackScale()));
  settings_.setValue("hitScale", QString::number(gl_.hitScale()));
  //  settings_.setValue("hitFilter", cbxHitFilter_.currentText());
  settings_.setValue("viewerTab", tabView_.currentIndex());
  settings_.setValue("listTab", tabLists_.currentIndex());
  settings_.setValue("opacity", QString::number(gl_.opacity()));
  settings_.setValue("backgroundRed", QString::number(gl_.backgroundRed()));
  settings_.setValue("backgroundGreen", QString::number(gl_.backgroundGreen()));
  settings_.setValue("backgroundBlue", QString::number(gl_.backgroundBlue()));
  settings_.setValue("showFullDet", gl_.showFullDet());
  settings_.setValue("showWireFrame", gl_.showWireFrame());
  settings_.setValue("showTracks", gl_.showTracks());
  settings_.setValue("showHits", gl_.showHits());
  settings_.beginWriteArray("recent");
  for (int i=0; i<filenames_.size(); i++)
    {
      settings_.setArrayIndex(i);
      settings_.setValue("filename", filenames_.at(i));
    } 
  settings_.endArray();
  event->accept();
}


//snapshot
void Gui::
cbSnap(void)
{
  //grab the current display image
  QImage img;
  if (tabView_.currentIndex()==0)
    {
      gl_.lock();
      img=gl_.grabFrameBuffer();
      gl_.unlock();
    }
  else
    {
      QPixmap pixmap;
      if (tabView_.currentIndex()==1)
	pixmap=QPixmap::grabWidget(&xy_);
      else if (tabView_.currentIndex()==2)
	pixmap=QPixmap::grabWidget(&xz_);
      else if (tabView_.currentIndex()==3)
	pixmap=QPixmap::grabWidget(&yz_);
      img=pixmap.toImage();
    }
  //ask what filename to use
  QString filename=QFileDialog::getSaveFileName(this,  "Save Snapshot", "landed.png", "PNG Image Files (*.png)");
  //if a filename was given, save the image as a png
  if (filename!=QString::null)
    {
      img.save(filename, "PNG");
    }
}
void Gui::
cbMovie(void)
{
  if (movie_.isRunning())
    {
      movie_.stop();
      while (movie_.isRunning())
	usleep(100);
      actMovie_->setText("Record Movie");
      led_.setOff();
    }
  else
    {
      //ask what filename to use
      QString filename=QFileDialog::getSaveFileName(this,  "Save Movie", "landed.mp4", "MP4 Movie Files (*.mp4)");
      //if a filename was given
      if (filename!=QString::null) 
	{
	  if (movie_.setup(filename))
	    {
	      movie_.start(QThread::TimeCriticalPriority);
	      actMovie_->setText("Stop Recording");
	      led_.setOn();
	    }
	  else
	    QMessageBox::critical(this, "LANDED", "Failed to start ffmpeg.  Is it installed and in \"PATH\"");	    
	}
    }


}

//reset viewpoint
void Gui::
cbReset(void)
{
  //pass order on to 3d display
  gl_.reset();
}

//menus
//file - open
void Gui::
cbNew(void)
{
#ifdef __APPLE__
  QString path=QApplication::applicationDirPath();
  path.remove(path.lastIndexOf(".app")+4, 1000);
  QProcess::execute("/usr/bin/open", QStringList("-n") << "-a" << path);
#endif
}

//file - open
void Gui::
cbOpen(void)
{
  //ask for filename
  QString filename=QFileDialog::getOpenFileName(this, "Open Event Display File", QString(), "Event Display Database Files (*.eddb);;All Files (*.*)");
  //if filename given, load file
  if (filename!=QString::null)  loadFile(filename);
}

//switch mouse action in 3d mode
void Gui::
cbAction(int iaction)
{
  //set selection 3d mouse action to 3d plot widget
  Action a=(Action)iaction;
  gl_.setAction(a);
  //synchronise ticks on navigation menu
  actMove_->setChecked(iaction==MOVE);
  actPan_->setChecked(iaction==PAN);
  actZoom_->setChecked(iaction==ZOOM);
  actCentre_->setChecked(iaction==ORIGIN);
  //update
  cbRefresh();
}

//view menu
//go to/from full screen
void Gui::
cbFullScreen(void)
{
  //if we're fill screen now
  if (isFullScreen())
    {
      //bring back navigation buttons
      btnMove_.show();
      btnPan_.show();
      btnZoom_.show();
      btnOrigin_.show();
      //bring back track list etc
      tabLists_.show();
      //bring back menu (only affects windows)
      menu_.show();
      //drop out of full screen
      showNormal();
    }
  //if we're no full screen now
  else
    {
      //navigation buttons
      btnMove_.hide();
      btnPan_.hide();
      btnZoom_.hide();
      btnOrigin_.hide();
      //track list etc from right side
      tabLists_.hide();
      //menu bar (only affects windows)
      menu_.hide();
      //jump into full screen mode
      showFullScreen();
    }
}

//show detector wire-frame
void Gui::
cbWireFrame(void)
{
  //reverse setting in 3d display
  gl_.setShowWireFrame(!gl_.showWireFrame());
  //update ticks on view menu
  viewchecks();
  //update
  cbRefresh();
}
//show detector surfaces 
void Gui::
cbFullDet(void)
{
  //reverse setting in 3d display
  gl_.setShowFullDet(!gl_.showFullDet());
  //update ticks on view menu
  viewchecks();
  //update
  cbRefresh();
}
void Gui::
cbTracks(void)
{
  bool st=!gl_.showTracks();
  gl_.setShowTracks(st);
  xy_.setShowTracks(st);
  xz_.setShowTracks(st);
  yz_.setShowTracks(st);
  viewchecks();
  //update
  cbRefresh();
}
void Gui::
cbHits(void)
{
  bool sh=!gl_.showHits();
  gl_.setShowHits(sh);
  xy_.setShowHits(sh);
  xz_.setShowHits(sh);
  yz_.setShowHits(sh);
  viewchecks();
  //update
  cbRefresh();
}
//update View menu ticks for current settings of what to show or not
void Gui::
viewchecks(void)
{
  //detector
  actWire_->setChecked(gl_.showWireFrame());
  actFullDet_->setChecked(gl_.showFullDet());
  actTracks_->setChecked(gl_.showTracks());
  actHits_->setChecked(gl_.showHits());
}

//help
//open manual
void Gui::
cbManual(void)
{
  QDesktopServices::openUrl(QUrl("http://www.hep.shef.ac.uk/research/dune/landed/"));
}
//about
void Gui::
cbAbout(void)
{
  HelpDialog* dlg=new HelpDialog(this);
  dlg->exec();
}
//change in detector filter
void Gui::
cbDetFilter(const QString& text)
{
  //split text on white space
  QStringList parts=text.split(QRegExp("\\s"), QString::SkipEmptyParts);
  //go through list
  for (int i=0; i<lstDetector_.rowCount(); i++ )
    {
      //set visible if matches filter, or no filter; invisible otherwise
      DetectorListWidgetItem* item=dynamic_cast<DetectorListWidgetItem*>(lstDetector_.item(i, 0));
      bool vis=true;
      for (int j=0; j<parts.size(); j++)
	if (parts.at(j).length())
	  vis = vis && item->text().contains(parts.at(j), Qt::CaseInsensitive);
      lstDetector_.setRowHidden(i, !vis);
    }
}
//change in detector list
void Gui::
cbDetector(QTableWidgetItem* qitem)
{
  //  std::cout << "visibility change on " << qPrintable(qitem->text()) << std::endl;
  DetectorListWidgetItem* item=dynamic_cast<DetectorListWidgetItem*>(qitem);
  gl_.setShapeVis(item->index, item->checkState()==Qt::Checked);
  cbRefresh();

}
void Gui::
cbTrackVis(QTableWidgetItem* item)
{
  gl_.setTrackVis(item->data(Qt::UserRole).toInt(), item->checkState()==Qt::Checked);
  xy_.setTrackVis(item->data(Qt::UserRole).toInt(), item->checkState()==Qt::Checked);
  xz_.setTrackVis(item->data(Qt::UserRole).toInt(), item->checkState()==Qt::Checked);
  yz_.setTrackVis(item->data(Qt::UserRole).toInt(), item->checkState()==Qt::Checked);
  cbRefresh();
}
void Gui::
cbHitVis(QTableWidgetItem* item)
{
  gl_.setHitVis(item->data(Qt::UserRole).toInt(), item->checkState()==Qt::Checked);
  xy_.setHitVis(item->data(Qt::UserRole).toInt(), item->checkState()==Qt::Checked);
  xz_.setHitVis(item->data(Qt::UserRole).toInt(), item->checkState()==Qt::Checked);
  yz_.setHitVis(item->data(Qt::UserRole).toInt(), item->checkState()==Qt::Checked);
  cbRefresh();
}
//navigation mode changed from World
void Gui::
cbModeChange(Action a)
{
  if (a==MOVE)
    btnMove_.setChecked(true);
  else if (a==PAN)
    btnPan_.setChecked(true);
  else if (a==ZOOM)
    btnZoom_.setChecked(true);
  else if (a==ORIGIN)
    btnOrigin_.setChecked(true);
}
//right click on detector list
void Gui::
cbDetConf(QPoint p)
{
  //create a pop-up menu at the click point
  QMenu* mnu=new QMenu(this);
  //assign an event handler to user clicking an item from the pop-up menu
  connect(mnu, SIGNAL(triggered(QAction*)), this, SLOT(cbDetMenu(QAction*)));
  //2 options, check selected, uncheck selected
  mnu->addAction("Check Selection");
  mnu->addAction("Uncheck Selection");
  //Display the pop-up menu at the right-click position
  mnu->exec(lstDetector_.mapToGlobal(p));

}
//right click on track list
void Gui::
cbTrackConf(QPoint p)
{
  //create a pop-up menu at the click point
  QMenu* mnu=new QMenu(this);
  //assign an event handler to user clicking an item from the pop-up menu
  connect(mnu, SIGNAL(triggered(QAction*)), this, SLOT(cbTrackMenu(QAction*)));
  //2 options, check selected, uncheck selected
  mnu->addAction("Check Selection");
  mnu->addAction("Uncheck Selection");
  //Display the pop-up menu at the right-click position
  mnu->exec(lstTracks_.mapToGlobal(p));

}
//right click on hit list
void Gui::
cbHitConf(QPoint p)
{
  //create a pop-up menu at the click point
  QMenu* mnu=new QMenu(this);
  //assign an event handler to user clicking an item from the pop-up menu
  connect(mnu, SIGNAL(triggered(QAction*)), this, SLOT(cbHitMenu(QAction*)));
  //2 options, check selected, uncheck selected
  mnu->addAction("Check Selection");
  mnu->addAction("Uncheck Selection");
  //Display the pop-up menu at the right-click position
  mnu->exec(lstHits_.mapToGlobal(p));

}
//click on item from pop-up menu above
void Gui::
cbDetMenu(QAction* action)
{
  bool check=true;
  if (action->text()=="Uncheck Selection")
    check=false;
  disconnect(&lstDetector_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbDetector(QTableWidgetItem*)));
  //check-uncheck selected items
  for (int i=0; i<lstDetector_.rowCount(); i++)
    {
      DetectorListWidgetItem* item=dynamic_cast<DetectorListWidgetItem*>(lstDetector_.item(i, 0));
      if (item->isSelected() && !lstDetector_.isRowHidden(i))
      {
	//set on or off and to update or not depending on choice
        item->setCheckState(check?Qt::Checked:Qt::Unchecked);
	gl_.setShapeVis(item->index, check);
      }
    }
  connect(&lstDetector_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbDetector(QTableWidgetItem*)));

  //we have done the changes without re-painting the screen so we need to repaint it once at the end
  cbRefresh();
}
//click on item from pop-up menu above
void Gui::
cbTrackMenu(QAction* action)
{
  bool check=true;
  if (action->text()=="Uncheck Selection")
    check=false;
  disconnect(&lstTracks_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbTrackVis(QTableWidgetItem*)));
  disconnect(&lstTracks_, SIGNAL(itemSelectionChanged()), this, SLOT(cbTrackSelect()));
  //check-uncheck selected items
  for (int i=0; i<lstTracks_.rowCount(); i++)
    {
      QTableWidgetItem* item=lstTracks_.item(i, 0);
      if (item->isSelected())
	{
	  //set on or off and to update or not depending on choice
	  item->setCheckState(check?Qt::Checked:Qt::Unchecked);
	  int index=item->data(Qt::UserRole).toInt();
	  gl_.setTrackVis(index, check);
	  xy_.setTrackVis(index, check);
	  xz_.setTrackVis(index, check);
	  yz_.setTrackVis(index, check);
	}
    }
  connect(&lstTracks_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbTrackVis(QTableWidgetItem*)));
  connect(&lstTracks_, SIGNAL(itemSelectionChanged()), this, SLOT(cbTrackSelect()));

  //we have done the changes without re-painting the screen so we need to repaint it once at the end
  cbRefresh();
}
//click on item from pop-up menu above
void Gui::
cbHitMenu(QAction* action)
{
  bool check=true;
  if (action->text()=="Uncheck Selection")
    check=false;
  disconnect(&lstHits_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbHitVis(QTableWidgetItem*)));
  disconnect(&lstHits_, SIGNAL(itemSelectionChanged()), this, SLOT(cbHitSelect()));
  //check-uncheck selected items
  for (int i=0; i<lstHits_.rowCount(); i++)
    {
      QTableWidgetItem* item=lstHits_.item(i, 0);
      if (item->isSelected())
	{
	  //set on or off and to update or not depending on choice
	  item->setCheckState(check?Qt::Checked:Qt::Unchecked);
	  int index=item->data(Qt::UserRole).toInt();
	  gl_.setHitVis(index, check);
	  xy_.setHitVis(index, check);
	  xy_.setHitVis(index, check);
	  yz_.setHitVis(index, check);
	}
    }
  connect(&lstHits_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbHitVis(QTableWidgetItem*)));
  connect(&lstHits_, SIGNAL(itemSelectionChanged()), this, SLOT(cbHitSelect()));

  //we have done the changes without re-painting the screen so we need to repaint it once at the end
  cbRefresh();
}

//user has right clicked on a volume
void Gui::
cbSelect(int id, THD thd)
{
  if (thd==THD_TRACK)
    {
      tabLists_.setCurrentIndex(0);
      for (int i=0; i<lstTracks_.rowCount(); i++)
	{
	  QTableWidgetItem* item=lstTracks_.item(i, 0);
	  if (item->isSelected() && id!=item->data(Qt::UserRole).toInt())
	    item->setSelected(false);
	  else if (id==item->data(Qt::UserRole).toInt())
	    {
	      lstTracks_.selectRow(i);
	      lstTracks_.scrollToItem(item);
	    }

	}
    }
  else if (thd==THD_HIT)
    {
      tabLists_.setCurrentIndex(1);
      for (int i=0; i<lstHits_.rowCount(); i++)
	{
	  QTableWidgetItem* item=lstHits_.item(i, 0);
	  if (item->isSelected() && id!=item->data(Qt::UserRole).toInt())
	    item->setSelected(false);
	  else if (id==item->data(Qt::UserRole).toInt())
	    {
	      lstHits_.selectRow(i);
	      lstHits_.scrollToItem(item);
	    }
	}
    }
  else
    {
      tabLists_.setCurrentIndex(2);
      for (int i=0; i<lstDetector_.rowCount(); i++)
	{
	  DetectorListWidgetItem* item=dynamic_cast<DetectorListWidgetItem*>(lstDetector_.item(i, 0));
	  if (item->isSelected() && id!=item->index)
	    item->setSelected(false);
	  else if (id==item->index)
	    {
	      item->setSelected(true);
	      lstDetector_.setRowHidden(i, false);
	      lstDetector_.scrollToItem(item);
	    }
	}
    }
}
//user has double right clicked on a volume
void Gui::
cbHide(int)
{
  for (int i=0; i<lstDetector_.rowCount(); i++)
    {
      DetectorListWidgetItem* item=dynamic_cast<DetectorListWidgetItem*>(lstDetector_.item(i, 0));
      if (item->isSelected())
	{
	  item->setCheckState(Qt::Unchecked);
	  gl_.setShapeVis(item->index, false);
	}
    }
  QPoint p=gl_.mapFromGlobal(QCursor::pos());
  gl_.pointerInfo(p.x(), p.y());
  cbRefresh();
}

//user wants to change opacity, from menu
void Gui::
cbOpacity(void)
{
  OpacityDialog* dlg=new OpacityDialog(this, gl_.opacity());
  connect(dlg, SIGNAL(changed(float)), this, SLOT(cbOpacitySet(float)));
  dlg->show();
}
void Gui::
cbOpacitySet(float v)
{
  gl_.setOpacity(v);
  cbRefresh();
}
//user wants to change background colour
void Gui::
cbBackground(void)
{
  ColourDialog* dlg=new ColourDialog(this, "Background Colour", gl_.backgroundRed(), gl_.backgroundGreen(), gl_.backgroundBlue());
  connect(dlg, SIGNAL(changed(float, float, float)), this, SLOT(cbBackgroundSet(float, float, float)));
  dlg->show();
}
void Gui::
cbBackgroundSet(float r, float g, float b)
{
  gl_.setBackground(r, g, b);
  cbRefresh();
}
//track scaling
void Gui::
cbTrackScale(int v)
{
  gl_.setTrackScale(pow(v/100., 3));
  xy_.setTrackScale(pow(v/100., 3));
  xz_.setTrackScale(pow(v/100., 3));
  yz_.setTrackScale(pow(v/100., 3));

  cbRefresh();
  double val=gl_.trackScale()/1000;
  QString disp;
  if (val<1000)
    disp=QString::number(val, 'g', 3);
  else
    disp=QString::number((int)val);
  txtTrackScale_.setText(disp+" cm"+QChar(0x00b3)+"/MeV");
  
}
//track selection
void Gui::
cbTrackSelect(void)
{
  QList<QTableWidgetItem*> sels=lstTracks_.selectedItems();
  std::vector<int> selected;
  for (int i=0; i<sels.size(); i++)
    {
      if (sels.at(i)->column()==1)
	{
	  selected.push_back(lstTracks_.item(sels.at(i)->row(), 0)->data(Qt::UserRole).toInt());
	  if (!gl_.track(selected.back()).selected)
	    {
	      gl_.highlightTrack(selected.back(), true);
	      xy_.highlightTrack(selected.back(), true);
	      yz_.highlightTrack(selected.back(), true);
	      xz_.highlightTrack(selected.back(), true);
	    }
	}
    }
  for (int i=0; i<gl_.numTracks(); i++)
    {
      const Track& track=gl_.track(i);
      if (track.selected &&
	  std::find(selected.begin(), selected.end(), i)==selected.end())
	{
	  gl_.highlightTrack(i, false);
	  xy_.highlightTrack(i, false);
	  xz_.highlightTrack(i, false);
	  yz_.highlightTrack(i, false);
	}
    }
  cbRefresh();

}
//jump to track
void Gui::
cbTrackJump(int row, int)
{
  QTableWidgetItem* item=lstTracks_.item(row, 0);
  int id=item->data(Qt::UserRole).toInt();
  const Track& track=gl_.track(id);
  gl_.seek(track.start.x+(track.end.x-track.start.x)/2, 
	   track.start.y+(track.end.y-track.start.y)/2, 
	   track.start.z+(track.end.z-track.start.z)/2,
	   500.);
  cbRefresh();

}

//hit scaling
void Gui::
cbHitScale(int v)
{
  gl_.setHitScale(pow(v/100., 3));
  xy_.setHitScale(pow(v/100., 3));
  xz_.setHitScale(pow(v/100., 3));
  yz_.setHitScale(pow(v/100., 3));


  cbRefresh();
  double val=gl_.hitScale()/1000.;
  QString disp;
  if (val<1000)
    disp=QString::number(val, 'g', 3);
  else
    disp=QString::number((int)val);
  txtHitScale_.setText(disp+" cm"+QChar(0x00b3)+"/MeV");
}
// void Gui::
// cbHitFilter(void)
// {
//   if (db_.isOpen())
//     {
//       loadRecord();
//       cbRefresh();
//     }

// }
void Gui::
cbHitSelect(void)
{
  QList<QTableWidgetItem*> sels=lstHits_.selectedItems();
  std::vector<int> selected;
  for (int i=0; i<sels.size(); i++)
    {
      if (sels.at(i)->column()==1)
	{
	  selected.push_back(lstHits_.item(sels.at(i)->row(), 0)->data(Qt::UserRole).toInt());
	  if (!gl_.hit(selected.back()).selected)
	    {
	      gl_.highlightHit(selected.back(), true);
	      xy_.highlightHit(selected.back(), true);
	      yz_.highlightHit(selected.back(), true);
	      xz_.highlightHit(selected.back(), true);
	    }
	}
    }
  for (int i=0; i<gl_.numHits(); i++)
    {
      const Hit& hit=gl_.hit(i);
      if (hit.selected &&
	  std::find(selected.begin(), selected.end(), i)==selected.end())
	{
	  gl_.highlightHit(i, false);
	  xy_.highlightHit(i, false);
	  xz_.highlightHit(i, false);
	  yz_.highlightHit(i, false);
	}
    }
  cbRefresh();
}
void Gui::
cbHitJump(int row, int)
{
  QTableWidgetItem* item=lstHits_.item(row, 0);
  int id=item->data(Qt::UserRole).toInt();
  const Hit& hit=gl_.hit(id);
  gl_.seek(hit.x, hit.y, hit.z);
  cbRefresh();
}
//jump to event
void Gui::
cbJump(void)
{
  JumpDialog* dlg=new JumpDialog(db_, record_);
  int res=dlg->exec();
  if (res==QDialog::Accepted)
    {
      loadRecord(dlg->record());
    }
}
//previous event
void Gui::
cbPrev(void)
{
  if (record_>0)
    loadRecord(record_-1);
}
//next event
void Gui::
cbNext(void)
{
  if (record_<numRecords_ || numRecords_<0)
    loadRecord(record_+1);
}
//file clicked on recent menu
void Gui::
cbRecent(QAction* action)
{
  loadFile(qPrintable(action->toolTip()));
}

//projection
void Gui::
cbProjMenu(QAction* action)
{
  int index=tabView_.currentIndex();
  Projection* proj=NULL;
   if (index>0)
    {
      if (index==1)
	proj=&xy_;
      else if (index==2)
	proj=&xz_;
      else
	proj=&yz_;
      if (action==actZoomIn_)
	proj->zoomIn();
      else if (action==actZoomOut_)
	proj->zoomOut();
      else if (action==actAuto_)
	proj->autoScale();
      else if (action==actLeft_)
	proj->move(-1, 0);
      else if (action==actRight_)
	proj->move(1, 0);
      else if (action==actUp_)
	proj->move(0, 1);
      else if (action==actDown_)
	proj->move(0, -1);
      cbRefresh();
    }
}

void Gui::
cbServer(void)
{
  if (server_.isListening() || socket_)
    {
      if (socket_)
	{
	  delete socket_;
	  socket_=NULL;
	}
      server_.close();
      setWindowTitle(QString(APPNAME));
    }
  else
    {
      ::unlink(qPrintable(QString(qgetenv("HOME")+"/.landed.sock")));
      if (server_.listen(QString(qgetenv("HOME")+"/.landed.sock")))
	{
	  setWindowTitle(QString(APPNAME)+" - Socket Server Running");
	  connect(&server_, SIGNAL(newConnection()), this, SLOT(cbConnect()));
	}
      else
	{
	  QMessageBox::critical(this, APPNAME, QString("Failed to start socket server on "+qgetenv("HOME")+"/.landed.sock: %1").arg(server_.errorString()));
	}
    }

  //clear out existing shapes
  xy_.clearShapes();
  yz_.clearShapes();
  xz_.clearShapes();
  gl_.clearShapes();
  gl_.reset();
  //shut down sqlite link
  db_.close();
  //
  spinRecord_.setMinimum(0);
  spinRecord_.setMaximum(0);
  btnJump_.setEnabled(false);
  //update detector list
  detList();
  //updates off, speeds things up, will turn them back on at the end
  lstTracks_.setUpdatesEnabled(false);
  //clear out hits
  disconnect(&lstTracks_, SIGNAL(itemSelectionChanged()), this, SLOT(cbTrackSelect()));
  disconnect(&lstTracks_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbTrackVis(QTableWidgetItem*)));
  lstTracks_.clearContents();
  //
  lstTracks_.setUpdatesEnabled(true);
  lstTracks_.setRowCount(0);
  connect(&lstTracks_, SIGNAL(itemSelectionChanged()), this, SLOT(cbTrackSelect()));
  connect(&lstTracks_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbTrackVis(QTableWidgetItem*)));

  gl_.clearTracks();
  xy_.clearTracks();
  xz_.clearTracks();
  yz_.clearTracks();
  //updates off, speeds things up, will turn them back on at the end
  lstHits_.setUpdatesEnabled(false);
  //clear out hits
  disconnect(&lstHits_, SIGNAL(itemSelectionChanged()), this, SLOT(cbHitSelect()));
  disconnect(&lstHits_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbTrackVis(QTableWidgetItem*)));
  lstHits_.clearContents();
  //
  lstHits_.setUpdatesEnabled(true);
  lstHits_.setRowCount(0);
  connect(&lstHits_, SIGNAL(itemSelectionChanged()), this, SLOT(cbHitSelect()));
  connect(&lstHits_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbHitVis(QTableWidgetItem*)));
  gl_.clearHits();
  xy_.clearHits();
  xz_.clearHits();
  yz_.clearHits();

  actServer_->setChecked((server_.isListening() || socket_)?Qt::Checked:Qt::Unchecked);
  lblID_.setText("");

  cbRefresh();
}

void Gui::
cbConnect(void)
{
  if (!socket_)
    {
      socket_=server_.nextPendingConnection();
      connect(socket_, SIGNAL(disconnected()), this, SLOT(cbDisconnect()));
      connect(socket_, SIGNAL(readyRead()), this, SLOT(cbSocket()));
      server_.close();
      setWindowTitle(QString(APPNAME)+" - Socket Server Connected");
      spinRecord_.setMaximum(INT_MAX);
      btnJump_.setEnabled(false);
      lblID_.setText("");
    }
  
}

void Gui::
cbDisconnect(void)
{
  setWindowTitle(APPNAME);
  socket_=NULL;
  cbServer();
}

void Gui::
cbSocket(void)
{
  QString instr=QString(socket_->readLine());
  //    std::cout << "data from client: " << qPrintable(instr) << std::endl;
  QStringList parts=instr.split(":");
  if (parts[0]=="GEO")
    {
      int geosize=parts[1].toInt();
      QByteArray vrml;
      while (vrml.size()<geosize)
	{
	  vrml+=socket_->read(geosize-vrml.size());
	  if (vrml.size()<geosize)
	    socket_->waitForReadyRead(-1);
	}
      readVRML(vrml);
      socket_->write("\n");
      spinRecord_.setMaximum(INT_MAX);
      btnJump_.setEnabled(false);
      numRecords_=-1;
    }
  else if (parts[0]=="EVT")
    {
      QStringList info=parts[1].split(",");
      record_=info[0].toInt();
      disconnect(&spinRecord_, SIGNAL(valueChanged(int)), this, SLOT(cbSetRecord(int)));
      spinRecord_.setValue(record_+1);
      connect(&spinRecord_, SIGNAL(valueChanged(int)), this, SLOT(cbSetRecord(int)));
      int run=info[3].toInt();
      int subrun=info[4].toInt();
      int event=info[5].toInt();
      lblID_.setText("Run: "+QString::number(run)+"  SubRun: "+QString::number(subrun)+"  Event: "+QString::number(event));
      int count=info[1].toInt();
      int tcount=info[2].toInt();
      socket_->write("\n");
      gl_.clearTracks();
      xy_.clearTracks();
      xz_.clearTracks();
      yz_.clearTracks();
      gl_.clearHits();
      xy_.clearHits();
      xz_.clearHits();
      yz_.clearHits();
      //updates off, speeds things up, will turn them back on at the end
      lstHits_.setUpdatesEnabled(false);
      lstTracks_.setUpdatesEnabled(false);
      //clear
      disconnect(&lstHits_, SIGNAL(itemSelectionChanged()), this, SLOT(cbHitSelect()));
      disconnect(&lstTracks_, SIGNAL(itemSelectionChanged()), this, SLOT(cbTrackSelect()));
      disconnect(&lstHits_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbHitVis(QTableWidgetItem*)));
      disconnect(&lstTracks_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbTrackVis(QTableWidgetItem*)));

      lstHits_.clearContents();
      lstTracks_.clearContents();
      //
      lstHits_.setSortingEnabled(false);
      lstTracks_.setSortingEnabled(false);
      bar_.show();
      bar_.setMaximum(count);
      barLabel_.setText("Receiving Hits");
      std::vector<FullHit> hits;

      int i=0;
      while (i<count)
	{
	  socket_->waitForReadyRead(-1);
	  instr=QString(socket_->readLine());
	  parts=instr.split(":");
	  if (parts[0]=="HIT")
	    {
	      QStringList paras=parts[1].split(",");
	      FullHit hit;
	      hit.x=paras[0].toDouble()*10;
	      hit.y=paras[1].toDouble()*10;
	      hit.z=paras[2].toDouble()*10;
	      hit.value=paras[3].toDouble();
	      hit.ne=paras[4].toDouble();
	      hit.track=paras[5].toInt();
	      hits.push_back(hit);
		//	      addHit(hit, tdc, ne, i);
	      i++;
	      bar_.setValue(i);
	      RUNEVENTS(1);
	      socket_->write("\n");
	    }
	}

      //get vertices
      bar_.setMaximum(tcount);
      barLabel_.setText("Receiving Tracks");

      std::vector<Vertex> vertices;      
      i=0;
      while (i<tcount)
	{
	  socket_->waitForReadyRead(-1);
	  instr=QString(socket_->readLine());
	  parts=instr.split(":");
	  if (parts[0]=="VTX")
	    {
	      QStringList paras=parts[1].split(",");
	      Vertex v;
	      v.x=paras[0].toDouble()*10;
	      v.y=paras[1].toDouble()*10;
	      v.z=paras[2].toDouble()*10;
	      v.value=paras[3].toDouble();
	      v.id=paras[4].toInt();
	      v.pdgcode=paras[5].toInt();
	      vertices.push_back(v);
		//	      addHit(hit, tdc, ne, i);
	      i++;
	      bar_.setValue(i);
	      RUNEVENTS(1);
	      socket_->write("\n");
	    }
	}

      //keep map of vertex if number to pdg code
      std::map<int, int> pdgs;
      //display tracks
      bar_.setValue(0);
      bar_.setMaximum(vertices.size());
      lstTracks_.setRowCount(vertices.size());
      barLabel_.setText("Processing Tracks");
      int tc=0;
      for (int i=1; i<vertices.size(); i++)
	{
	  pdgs[vertices[i].id]=vertices[i].pdgcode;
	  if (vertices[i].id==vertices[i-1].id)
	    {
	      addTrack(vertices[i-1], vertices[i], tc);
	      tc++;
	    }
	  bar_.setValue(i);
	  RUNEVENTS(1);
	}
      lstTracks_.setRowCount(tc);

      //display hits
      bar_.setValue(0);
      bar_.setMaximum(hits.size());
      barLabel_.setText("Processing Hits");
 //      std::sort(hits.begin(), hits.end());
//       if (cbxHitFilter_.currentIndex()==0)
// 	{
// 	  std::vector<FullHit>::iterator it=hits.end();
// 	  for (int i=1; i<hits.size() && it==hits.end(); i++)
// 	    if (hits[i].value<hits[0].value/10.)
// 	      it=hits.begin()+i;
// 	  if (it!=hits.end())
// 	    hits.erase(it, hits.end());
// 	}
//       else if (cbxHitFilter_.currentIndex()==1)
// 	{
// 	  if (hits.size()>100)
// 	    hits.erase(hits.begin()+100, hits.end());
// 	}
      lstHits_.setRowCount(hits.size());
      for (int i=0; i<hits.size(); i++)
	{
	  addHit(hits[i], hits[i].ne, hits[i].track, pdgs[abs(hits[i].track)], i);
	  bar_.setValue(i);
	  RUNEVENTS(1);
	}

      //updates back on
      lstHits_.setSortingEnabled(true);
      lstHits_.setUpdatesEnabled(true);
      lstTracks_.setSortingEnabled(true);
      lstTracks_.setUpdatesEnabled(true);
      connect(&lstHits_, SIGNAL(itemSelectionChanged()), this, SLOT(cbHitSelect()));
      connect(&lstTracks_, SIGNAL(itemSelectionChanged()), this, SLOT(cbTrackSelect()));
      connect(&lstHits_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbHitVis(QTableWidgetItem*)));
      connect(&lstTracks_, SIGNAL(itemChanged(QTableWidgetItem*)), this, SLOT(cbTrackVis(QTableWidgetItem*)));
      barLabel_.setText("");
      bar_.reset();
      bar_.hide();

      cbRefresh();

      btnPrev_.setEnabled(record_>0);
      btnNext_.setEnabled(numRecords_<0 || record_<numRecords_-1);
    }
  else if (instr.trimmed()=="OK")
    {
      //ignore.  just means that an event request was accepted
      socket_->write("\n");
    }
  else if (instr.trimmed()=="NO")
    {
      QMessageBox::critical(this, "LANDED", "art reports no such event, the current event will be re-displayed");
      disconnect(&spinRecord_, SIGNAL(valueChanged(int)), this, SLOT(cbSetRecord(int)));
      spinRecord_.setValue(record_+1);
      connect(&spinRecord_, SIGNAL(valueChanged(int)), this, SLOT(cbSetRecord(int)));
      socket_->write("\n");
    }
  else
    std::cout << "data from client: " << qPrintable(instr) << std::endl;
  //  std::cout << "data from client: " << qPrintable) << std::endl;
}
