#ifndef FACT_FactGui
#define FACT_FactGui

#include "MainWindow.h"

#include <iomanip>
#include <valarray>

#include <boost/bind.hpp>

#include <QTimer>
#include <QStandardItemModel>

#include "CheckBoxDelegate.h"

#include "src/Dim.h"
#include "src/Converter.h"
#include "src/HeadersFTM.h"
#include "src/HeadersFAD.h"
#include "src/DimNetwork.h"
#include "src/tools.h"

#include "TROOT.h"
#include "TSystem.h"
#include "TGraph.h"
#include "TH1.h"
#include "TStyle.h"
#include "TMarker.h"
#include "TColor.h"

using namespace std;

// #########################################################################

class Camera : public TObject
{
    typedef pair<double,double> Position;
    typedef vector<Position> Positions;

    Positions fGeom;

    void CreatePalette()
    {
        /*
         double ss[5] = {0., 0.10, 0.45, 0.75, 1.00};
         double rr[5] = {0., 0.35, 0.85, 1.00, 1.00};
         double gg[5] = {0., 0.10, 0.20, 0.73, 1.00};
         double bb[5] = {0., 0.03, 0.06, 0.00, 1.00};
         */
        double ss[5] = {0.00, 0.25, 0.50, 0.75, 1.00};
        double rr[5] = {0.15, 0.00, 0.00, 1.00, 0.85};
        double gg[5] = {0.15, 0.00, 1.00, 0.00, 0.85};
        double bb[5] = {0.15, 1.00, 0.00, 0.00, 0.85};

        const Int_t nn = 1440;

        Int_t idx = TColor::CreateGradientColorTable(5, ss, rr, gg, bb, nn);
        for (int i=0; i<nn; i++)
            fPalette.push_back(idx++);
    }

    void CreateGeometry()
    {
        const double gsSin60 = sqrt(3.)/2;

        const int rings = 23;

        //  add the first pixel to the list

        fGeom.push_back(make_pair(0, -0.5));

        for (int ring=1; ring<=rings; ring++)
        {
            for (int s=0; s<6; s++)
            {
                for (int i=1; i<=ring; i++)
                {
                    double xx, yy;
                    switch (s)
                    {
                    case 0: // Direction South East
                        xx = (ring+i)*0.5;
                        yy = (-ring+i)*gsSin60;
                        break;

                    case 1: // Direction North East
                        xx = ring-i*0.5;
                        yy = i*gsSin60;
                        break;

                    case 2: // Direction North
                        xx = ring*0.5-i;
                        yy = ring*gsSin60;
                        break;

                    case 3: // Direction North West
                        xx = -(ring+i)*0.5;
                        yy = (ring-i)*gsSin60;
                        break;

                    case 4: // Direction South West
                        xx = 0.5*i-ring;
                        yy = -i*gsSin60;
                        break;

                    case 5: // Direction South
                        xx = i-ring*0.5;
                        yy = -ring*gsSin60;
                        break;
                    }

                    if (xx*xx + yy*yy - xx > 395.75)
                        continue;

                    fGeom.push_back(make_pair(yy, xx-0.5));
                }
            }
        }
    }

    valarray<double> fData;
    vector<bool>   fBold;
    vector<bool>   fEnable;

    int fWhite;

public:
    Camera() : fData(1440), fBold(1440), fEnable(1440), fWhite(-1)
    {
        CreatePalette();
        CreateGeometry();

	for (int i=0; i<1440; i++)
	{
	    fData[i] = i;
	    fBold[i]=false;
	    fEnable[i]=true;
	}
    }

    void Reset() { fBold.assign(1440, false); }

    void SetBold(int idx) { fBold[idx]=true; }
    void SetWhite(int idx) { fWhite=idx; }
    void SetEnable(int idx, bool b) { fEnable[idx]=b; }
    void Toggle(int idx) { fEnable[idx]=!fEnable[idx]; }
    double GetData(int idx) const { return fData[idx]; }

    const char *GetName() const { return "Camera"; }

    vector<Int_t> fPalette;

    void Paint(const Position &p)
    {
        static const Double_t fgCos60 = 0.5;        // TMath::Cos(60/TMath::RadToDeg());
        static const Double_t fgSin60 = sqrt(3.)/2; // TMath::Sin(60/TMath::RadToDeg());

        static const Double_t fgDy[6] = { fgCos60,   0.,         -fgCos60,   -fgCos60,    0.,           fgCos60   };
        static const Double_t fgDx[6] = { fgSin60/3, fgSin60*2/3, fgSin60/3, -fgSin60/3, -fgSin60*2/3, -fgSin60/3 };

        //
        //  calculate the positions of the pixel corners
        //
        Double_t x[7], y[7];
        for (Int_t i=0; i<7; i++)
        {
            x[i] = p.first  + fgDx[i%6];
            y[i] = p.second + fgDy[i%6];
        }

        gPad->PaintFillArea(6, x, y);
        gPad->PaintPolyLine(7, x, y);

    }

    double align(double min, double val, double max) const
    {
        if (val<min)
            return min;
        if (val>max)
            return max;
        return val;
    }

    void Paint(Option_t *)
    {
        gStyle->SetPalette(fPalette.size(), fPalette.data());

        const double r   = double(gPad->GetWw())/gPad->GetWh();
        const double max = 20.5; // 20.5 rings in x and y

        if (r>1)
            gPad->Range(-r*max, -max, r*max, max);
        else
            gPad->Range(-max, -max/r, max, max/r);

        Double_t x1, x2, y1, y2;
        gPad->GetRange(x1, x2, y1, y2);

        double dmin = fData[0];
        double dmax = fData[0];

        for (unsigned int i=0; i<fData.size(); i++)
        {
            if (!fEnable[i])
                continue;

            if (fData[i]>dmax)
                dmax = fData[i];
            if (fData[i]<dmin)
                dmin = fData[i];
        }

        const double min   = dmin;
        const double scale = dmax==dmin ? 1 : dmax-dmin;

        TAttFill fill(0, 1001);
        TAttLine line;

        int cnt=0;
        for (Positions::iterator p=fGeom.begin(); p!=fGeom.end(); p++, cnt++)
        {
            if (fBold[cnt])
                continue;

            const double val = align(dmin, fData[cnt], dmax);

            const int col = (val-min)/scale*(fPalette.size()-1);

            if (fEnable[cnt])
                fill.SetFillColor(gStyle->GetColorPalette(col));
            else
                fill.SetFillColor(kWhite);

            fill.Modify();

            Paint(*p);
	}

        line.SetLineWidth(2);
        line.Modify();

        cnt = 0;
        for (Positions::iterator p=fGeom.begin(); p!=fGeom.end(); p++, cnt++)
        {
            if (!fBold[cnt])
                continue;

            const double val = align(dmin, fData[cnt], dmax);

            const int col = (val-min)/scale*(fPalette.size()-1);

            if (fEnable[cnt])
                fill.SetFillColor(gStyle->GetColorPalette(col));
            else
                fill.SetFillColor(kWhite);
            fill.Modify();

            Paint(*p);
        }

        TMarker m(0,0,kStar);
        m.DrawMarker(0, 0);

        if (fWhite<0)
            return;

        const Position &p = fGeom[fWhite];

        line.SetLineColor(kWhite);
        line.Modify();

        const double val = align(dmin, fData[fWhite], dmax);

        const int col = (val-min)/scale*(fPalette.size()-1);

        if (fEnable[fWhite])
            fill.SetFillColor(gStyle->GetColorPalette(col));
        else
            fill.SetFillColor(kWhite);
        fill.Modify();

        Paint(p);
    }

    int GetIdx(float px, float py) const
    {
        static const double sqrt3 = sqrt(3);

        int idx = 0;
        for (Positions::const_iterator p=fGeom.begin(); p!=fGeom.end(); p++, idx++)
        {
            const Double_t dy = py - p->second;
            if (fabs(dy)>0.5)
                continue;

            const Double_t dx = px - p->first;

            if  (TMath::Abs(dy + dx*sqrt3) > 1)
                continue;

            if  (TMath::Abs(dy - dx*sqrt3) > 1)
                continue;

            return idx;
        }
        return -1;
    }

    char *GetObjectInfo(Int_t px, Int_t py) const
    {
        static stringstream stream;
        static string       str;

        const float x = gPad->AbsPixeltoX(px);
        const float y = gPad->AbsPixeltoY(py);

        const int idx = GetIdx(x, y);

        stream.seekp(0);
        if (idx>=0)
        {
            stream << "Pixel=" << idx << "   Data=" << fData[idx] << '\0';
        }

        str = stream.str();
        return const_cast<char*>(str.c_str());
    }

    Int_t DistancetoPrimitive(Int_t px, Int_t py)
    {
        const float x = gPad->AbsPixeltoX(px);
        const float y = gPad->AbsPixeltoY(py);

        return GetIdx(x, y)>=0 ? 0 : 99999;
    }

    void SetData(const valarray<double> &data)
    {
        fData = data;
    }
};

// #########################################################################


class FactGui : public MainWindow, public DimNetwork
{
private:
    class FunctionEvent : public QEvent
    {
    public:
        boost::function<void(const QEvent &)> fFunction;

        FunctionEvent(const boost::function<void(const QEvent &)> &f)
            : QEvent((QEvent::Type)QEvent::registerEventType()),
            fFunction(f) { }

        bool Exec() { fFunction(*this); return true; }
    };

    valarray<int8_t> fFtuStatus;

    vector<int>  fPixelMapHW; // Software -> Hardware
    vector<int>  fPatchMapHW; // Software -> Hardware
    vector<int> fPatchHW; // Maps the software(!) pixel id to the hardware(!) patch id

    bool fInChoosePatch; // FIXME. Find a better solution

    DimStampedInfo fDimDNS;

    DimStampedInfo fDimLoggerStats;
    DimStampedInfo fDimLoggerFilenameNight;
    DimStampedInfo fDimLoggerFilenameRun;
    DimStampedInfo fDimLoggerNumSubs;

    DimStampedInfo fDimFtmPassport;
    DimStampedInfo fDimFtmTriggerCounter;
    DimStampedInfo fDimFtmError;
    DimStampedInfo fDimFtmFtuList;
    DimStampedInfo fDimFtmStaticData;
    DimStampedInfo fDimFtmDynamicData;
    DimStampedInfo fDimFtmCounter;

    DimStampedInfo fDimFadRuns;
    DimStampedInfo fDimFadEvents;
    DimStampedInfo fDimFadCurrentEvent;
    DimStampedInfo fDimFadEventData;
    DimStampedInfo fDimFadConnections;
    DimStampedInfo fDimFadFwVersion;
    DimStampedInfo fDimFadRunNumber;
    DimStampedInfo fDimFadDNA;
    DimStampedInfo fDimFadStatus;
    DimStampedInfo fDimFadStatistics;

    map<string, DimInfo*> fServices;

    // ========================== LED Colors ================================

    enum LedColor_t
    {
        kLedRed,
        kLedGreen,
        kLedYellow,
        kLedOrange,
        kLedGray
    };

    void SetLedColor(QPushButton *button, LedColor_t col, const Time &t)
    {
        switch (col)
        {
        case kLedRed:
            button->setIcon(QIcon(":/Resources/icons/red circle 1.png"));
            break;

        case kLedGreen:
            button->setIcon(QIcon(":/Resources/icons/green circle 1.png"));
            break;

        case kLedYellow:
            button->setIcon(QIcon(":/Resources/icons/yellow circle 1.png"));
            break;

        case kLedOrange:
            button->setIcon(QIcon(":/Resources/icons/orange circle 1.png"));
            break;

        case kLedGray:
            button->setIcon(QIcon(":/Resources/icons/gray circle 1.png"));
            break;
        }

        //button->setToolTip("Last change: "+QDateTime::currentDateTimeUtc().toString()+" UTC");
        button->setToolTip(("Last change: "+t.GetAsStr()+" (UTC)").c_str());
    }

    // ===================== Services and Commands ==========================

    QStandardItem *AddServiceItem(const std::string &server, const std::string &service, bool iscmd)
    {
        QListView *servers     = iscmd ? fDimCmdServers     : fDimSvcServers;
        QListView *services    = iscmd ? fDimCmdCommands    : fDimSvcServices;
        QListView *description = iscmd ? fDimCmdDescription : fDimSvcDescription;

        QStandardItemModel *m = dynamic_cast<QStandardItemModel*>(servers->model());
        if (!m)
        {
            m = new QStandardItemModel(this);
            servers->setModel(m);
            services->setModel(m);
            description->setModel(m);
        }

        QList<QStandardItem*> l = m->findItems(server.c_str());

        if (l.size()>1)
        {
            cout << "hae" << endl;
            return 0;
        }

        QStandardItem *col = l.size()==0 ? NULL : l[0];

        if (!col)
        {
            col = new QStandardItem(server.c_str());
            m->appendRow(col);

            if (!services->rootIndex().isValid())
            {
                services->setRootIndex(col->index());
                servers->setCurrentIndex(col->index());
            }
        }

        QStandardItem *item = 0;
        for (int i=0; i<col->rowCount(); i++)
        {
            QStandardItem *coli = col->child(i);
            if (coli->text().toStdString()==service)
                return coli;
        }

        item = new QStandardItem(service.c_str());
        col->appendRow(item);
        col->sortChildren(0);

        if (!description->rootIndex().isValid())
        {
            description->setRootIndex(item->index());
            services->setCurrentIndex(item->index());
        }

        if (!iscmd)
            item->setCheckable(true);

        return item;
    }

    void AddDescription(QStandardItem *item, const vector<Description> &vec)
    {
        if (!item)
            return;
        if (vec.size()==0)
            return;

        item->setToolTip(vec[0].comment.c_str());

        const string str = Description::GetHtmlDescription(vec);

        QStandardItem *desc = new QStandardItem(str.c_str());
        desc->setSelectable(false);
        item->setChild(0, 0, desc);
    }

    void AddServer(const std::string &s)
    {
        DimNetwork::AddServer(s);

        QApplication::postEvent(this,
           new FunctionEvent(boost::bind(&FactGui::handleAddServer, this, s)));
    }

    void RemoveServer(const std::string &s)
    {
        UnsubscribeServer(s);

        DimNetwork::RemoveServer(s);

        QApplication::postEvent(this,
           new FunctionEvent(boost::bind(&FactGui::handleRemoveServer, this, s)));
    }

    void RemoveAllServers()
    {
        UnsubscribeAllServers();

        vector<string> v = GetServerList();
        for (vector<string>::iterator i=v.begin(); i<v.end(); i++)
            QApplication::postEvent(this,
                                    new FunctionEvent(boost::bind(&FactGui::handleStateOffline, this, *i)));

        DimNetwork::RemoveAllServers();

        QApplication::postEvent(this,
           new FunctionEvent(boost::bind(&FactGui::handleRemoveAllServers, this)));
    }

    void AddService(const std::string &server, const std::string &service, const std::string &fmt, bool iscmd)
    {
        QApplication::postEvent(this,
           new FunctionEvent(boost::bind(&FactGui::handleAddService, this, server, service, fmt, iscmd)));
    }

    void RemoveService(const std::string &server, const std::string &service, bool iscmd)
    {
        if (fServices.find(server+'/'+service)!=fServices.end())
            UnsubscribeService(server+'/'+service);

        QApplication::postEvent(this,
           new FunctionEvent(boost::bind(&FactGui::handleRemoveService, this, server, service, iscmd)));
    }

    void RemoveAllServices(const std::string &server)
    {
        UnsubscribeServer(server);

        QApplication::postEvent(this,
           new FunctionEvent(boost::bind(&FactGui::handleRemoveAllServices, this, server)));
    }

    void AddDescription(const std::string &server, const std::string &service, const vector<Description> &vec)
    {
        QApplication::postEvent(this,
           new FunctionEvent(boost::bind(&FactGui::handleAddDescription, this, server, service, vec)));
    }

    // ======================================================================

    void handleAddServer(const std::string &server)
    {
        const State s = GetState(server, GetCurrentState(server));
        handleStateChanged(Time(), server, s);
    }

    void handleRemoveServer(const string &server)
    {
        handleStateOffline(server);
        handleRemoveAllServices(server);
    }

    void handleRemoveAllServers()
    {
        QStandardItemModel *m = 0;
        if ((m=dynamic_cast<QStandardItemModel*>(fDimCmdServers->model())))
            m->removeRows(0, m->rowCount());

        if ((m = dynamic_cast<QStandardItemModel*>(fDimSvcServers->model())))
            m->removeRows(0, m->rowCount());
    }

    void handleAddService(const std::string &server, const std::string &service, const std::string &/*fmt*/, bool iscmd)
    {
        QStandardItem *item = AddServiceItem(server, service, iscmd);
        const vector<Description> v = GetDescription(server, service);
        AddDescription(item, v);
    }

    void handleRemoveService(const std::string &server, const std::string &service, bool iscmd)
    {
        QListView *servers = iscmd ? fDimCmdServers : fDimSvcServers;

        QStandardItemModel *m = dynamic_cast<QStandardItemModel*>(servers->model());
        if (!m)
            return;

        QList<QStandardItem*> l = m->findItems(server.c_str());
        if (l.size()!=1)
            return;

        for (int i=0; i<l[0]->rowCount(); i++)
        {
            QStandardItem *row = l[0]->child(i);
            if (row->text().toStdString()==service)
            {
                l[0]->removeRow(row->index().row());
                return;
            }
        }
    }

    void handleRemoveAllServices(const std::string &server)
    {
        QStandardItemModel *m = 0;
        if ((m=dynamic_cast<QStandardItemModel*>(fDimCmdServers->model())))
        {
            QList<QStandardItem*> l = m->findItems(server.c_str());
            if (l.size()==1)
                m->removeRow(l[0]->index().row());
        }

        if ((m = dynamic_cast<QStandardItemModel*>(fDimSvcServers->model())))
        {
            QList<QStandardItem*> l = m->findItems(server.c_str());
            if (l.size()==1)
                m->removeRow(l[0]->index().row());
        }
    }

    void handleAddDescription(const std::string &server, const std::string &service, const vector<Description> &vec)
    {
        const bool iscmd = IsCommand(server, service)==true;

        QStandardItem *item = AddServiceItem(server, service, iscmd);
        AddDescription(item, vec);
    }

    // ======================================================================

    void SubscribeService(const string &service)
    {
        if (fServices.find(service)!=fServices.end())
        {
            cout << "ERROR - We are already subscribed to " << service << endl;
            return;
        }

        fServices[service] = new DimStampedInfo(service.c_str(), (void*)NULL, 0, this);
    }

    void UnsubscribeService(const string &service)
    {
        const map<string,DimInfo*>::iterator i=fServices.find(service);

        if (i==fServices.end())
        {
            cout << "ERROR - We are not subscribed to " << service << endl;
            return;
        }

        delete i->second;

        fServices.erase(i);
    }

    void UnsubscribeServer(const string &server)
    {
        for (map<string,DimInfo*>::iterator i=fServices.begin();
             i!=fServices.end(); i++)
            if (i->first.substr(0, server.length()+1)==server+'/')
            {
                delete i->second;
                fServices.erase(i);
            }
    }

    void UnsubscribeAllServers()
    {
        for (map<string,DimInfo*>::iterator i=fServices.begin();
             i!=fServices.end(); i++)
            delete i->second;

        fServices.clear();
    }

    // ======================================================================

    struct DimData
    {
        const int          qos;
        const string       name;
        const string       format;
        const vector<char> data;
        const Time         time;

        Time extract(DimInfo *inf) const
        {
            // Must be called in exactly this order!
            const int tsec = inf->getTimestamp();
            const int tms  = inf->getTimestampMillisecs();

            return Time(tsec, tms*1000);
        }

//        DimInfo *info;  // this is ONLY for a fast check of the type of the DimData!!

        DimData(DimInfo *inf) :
            qos(inf->getQuality()),
            name(inf->getName()),
            format(inf->getFormat()),
            data(inf->getString(), inf->getString()+inf->getSize()),
            time(extract(inf))/*,
            info(inf)*/
        {
        }

        template<typename T>
                T get(uint32_t offset=0) const { return *reinterpret_cast<const T*>(data.data()+offset); }

        template<typename T>
                const T *ptr(uint32_t offset=0) const { return reinterpret_cast<const T*>(data.data()+offset); }

        template<typename T>
                const T &ref(uint32_t offset=0) const { return *reinterpret_cast<const T*>(data.data()+offset); }

//        vector<char> vec(int b) const { return vector<char>(data.begin()+b, data.end()); }
//        string str(unsigned int b) const { return b>=data.size()?string():string(data.data()+b, data.size()-b); }
        const char *c_str() const { return (char*)data.data(); }
/*
        vector<boost::any> any() const
        {
            const Converter conv(format);
            conv.Print();
            return conv.GetAny(data.data(), data.size());
        }*/
        size_t size() const { return data.size(); }
    };

    // ======================= DNS ==========================================

    void handleDimDNS(const DimData &d)
    {
        const int version = d.size()!=4 ? 0 : d.get<uint32_t>();

        ostringstream str;
        str << "V" << version/100 << 'r' << version%100;

        SetLedColor(fStatusDNSLed, version==0 ? kLedRed : kLedGreen, Time());

        fStatusDNSLabel->setText(version==0?"Offline":str.str().c_str());
        fStatusDNSLabel->setToolTip(version==0?"No connection to DIM DNS.":"Connection to DIM DNS established.");
    }


    // ======================= Logger =======================================

    void handleLoggerStats(const DimData &d)
    {
        const bool connected = d.size()!=0;

        fLoggerET->setEnabled(connected);
        fLoggerRate->setEnabled(connected);
        fLoggerWritten->setEnabled(connected);
        fLoggerFreeSpace->setEnabled(connected);
        fLoggerSpaceLeft->setEnabled(connected);

        if (!connected)
            return;

        const uint64_t *vals = d.ptr<uint64_t>();

        const size_t written = vals[0];
        const size_t space   = vals[1];
        const size_t rate    = vals[2];

        fLoggerFreeSpace->setSuffix(" MB");
        fLoggerFreeSpace->setDecimals(0);
        fLoggerFreeSpace->setValue(space*1e-6);

        if (space>   1000000)  // > 1GB
        {
            fLoggerFreeSpace->setSuffix(" GB");
            fLoggerFreeSpace->setDecimals(2);
            fLoggerFreeSpace->setValue(space*1e-9);
        }
        if (space>=  3000000)  // >= 3GB
        {
            fLoggerFreeSpace->setSuffix(" GB");
            fLoggerFreeSpace->setDecimals(1);
            fLoggerFreeSpace->setValue(space*1e-9);
        }
        if (space>=100000000)  // >= 100GB
        {
            fLoggerFreeSpace->setSuffix(" GB");
            fLoggerFreeSpace->setDecimals(0);
            fLoggerFreeSpace->setValue(space*1e-9);
        }

        fLoggerET->setTime(QTime().addSecs(rate>0?space/rate:0));
        fLoggerRate->setValue(rate*1e-3); // kB/s
        fLoggerWritten->setValue(written*1e-6);

        fLoggerRate->setSuffix(" kB/s");
        fLoggerRate->setDecimals(2);
        fLoggerRate->setValue(rate*1e-3);
        if (rate>   2000)  // > 2kB/s
        {
            fLoggerRate->setSuffix(" kB/s");
            fLoggerRate->setDecimals(1);
            fLoggerRate->setValue(rate*1e-3);
        }
        if (rate>=100000)  // >100kB/s
        {
            fLoggerRate->setSuffix(" kB/s");
            fLoggerRate->setDecimals(0);
            fLoggerRate->setValue(rate*1e-3);
        }
        if (rate>=1000000)  // >100kB/s
        {
            fLoggerRate->setSuffix(" MB/s");
            fLoggerRate->setDecimals(2);
            fLoggerRate->setValue(rate*1e-6);
        }
        if (rate>=10000000)  // >1MB/s
        {
            fLoggerRate->setSuffix(" MB/s");
            fLoggerRate->setDecimals(1);
            fLoggerRate->setValue(rate*1e-6);
        }
        if (rate>=100000000)  // >10MB/s
        {
            fLoggerRate->setSuffix(" MB/s");
            fLoggerRate->setDecimals(0);
            fLoggerRate->setValue(rate*1e-6);
        }

        if (space/1000000>static_cast<size_t>(fLoggerSpaceLeft->maximum()))
            fLoggerSpaceLeft->setValue(fLoggerSpaceLeft->maximum());  // GB
        else
            fLoggerSpaceLeft->setValue(space/1000000);  // MB
    }

    void handleLoggerFilenameNight(const DimData &d)
    {
        const bool connected = d.size()!=0;

        fLoggerFilenameNight->setEnabled(connected);
        if (!connected)
            return;

        fLoggerFilenameNight->setText(d.c_str()+4);

        const uint32_t files = d.get<uint32_t>();

        SetLedColor(fLoggerLedLog,  files&1 ? kLedGreen : kLedGray, d.time);
        SetLedColor(fLoggerLedRep,  files&2 ? kLedGreen : kLedGray, d.time);
        SetLedColor(fLoggerLedFits, files&4 ? kLedGreen : kLedGray, d.time);
    }

    void handleLoggerFilenameRun(const DimData &d)
    {
        const bool connected = d.size()!=0;

        fLoggerFilenameRun->setEnabled(connected);
        if (!connected)
            return;

        fLoggerFilenameRun->setText(d.c_str()+4);

        const uint32_t files = d.get<uint32_t>();

        SetLedColor(fLoggerLedLog,  files&1 ? kLedGreen : kLedGray, d.time);
        SetLedColor(fLoggerLedRep,  files&2 ? kLedGreen : kLedGray, d.time);
        SetLedColor(fLoggerLedFits, files&4 ? kLedGreen : kLedGray, d.time);
    }

    void handleLoggerNumSubs(const DimData &d)
    {
        const bool connected = d.size()!=0;

        fLoggerSubscriptions->setEnabled(connected);
        fLoggerOpenFiles->setEnabled(connected);
        if (!connected)
            return;

        const uint32_t *vals = d.ptr<uint32_t>();

        fLoggerSubscriptions->setValue(vals[0]);
        fLoggerOpenFiles->setValue(vals[1]);
    }


    // ===================== All ============================================

    bool CheckSize(const DimData &d, size_t sz) const
    {
        if (d.size()==0)
            return false;

        if (d.size()!=sz)
        {
            cout << "Size mismatch in " << d.name << ": Found=" << d.size() << " Expected=" << sz << endl;
            return false;
        }

        return true;
    }

    // ===================== FAD ============================================

    void handleFadRuns(const DimData &d)
    {
        if (!CheckSize(d, 12))
            return;

        const uint32_t *ptr = d.ptr<uint32_t>();

        fEvtBldRunNumberMin->setValue(ptr[1]);
        fEvtBldRunNumberMax->setValue(ptr[2]);
        fEvtBldOpenFiles->setValue(ptr[0]);
    }

    void handleFadEvents(const DimData &d)
    {
        if (!CheckSize(d, 8))
            return;

        const uint32_t *ptr = d.ptr<uint32_t>();

        fEvtsSuccessCurRun->setValue(ptr[0]);
        fEvtsSuccessTotal->setValue(ptr[1]);
    }

    void handleFadCurrentEvent(const DimData &d)
    {
        if (!CheckSize(d, 4))
            return;

        fEvtsEventId->setValue(d.get<uint32_t>());
    }

    struct DimEventData
    {
	uint16_t Roi ;            // #slices per pixel (same for all pixels and tmarks)
	uint32_t EventNum ;       // EventNumber as from FTM
	uint16_t TriggerType ;    // Trigger Type from FTM

	uint32_t PCTime ;         // when did event start to arrive at PC
	uint32_t BoardTime;       //

	int16_t StartPix;         // First Channel per Pixel (Pixels sorted according Software ID)  ; -1 if not filled
	int16_t StartTM;          // First Channel for TimeMark (sorted Hardware ID) ; -1 if not filled

	uint16_t Adc_Data[];     // final length defined by malloc ....

    } __attribute__((__packed__));;

    void handleFadEventData(const DimData &d)
    {
	if (d.size()==0)
            return;

#ifdef HAVE_ROOT
	const DimEventData &dat = d.ref<DimEventData>();

        if (d.size()<sizeof(DimEventData))
        {
            cout << "Size mismatch in " << d.name << ": Found=" << d.size() << " Expected=" << sizeof(DimEventData) << endl;
            return;
        }

        if (d.size()!=dat.Roi*2+sizeof(DimEventData))
        {
            cout << "Size mismatch in " << d.name << ": Found=" << d.size() << " Expected=" << dat.Roi*2+sizeof(DimEventData) << endl;
            return;
        }

	TCanvas *c = fAdcDataCanv->GetCanvas();

	TH1 *h = dynamic_cast<TH1*>(c->FindObject("EventData"));
	if (h && h->GetNbinsX()!=dat.Roi)
	{
	    delete h;
	    h = 0;
	}

	if (!h)
	{
	    c->cd();

	    TH1D hist("EventData", "", dat.Roi, -0.5, dat.Roi-0.5);
	    hist.SetStats(kFALSE);
	    //hist->SetBit(TH1::kNoTitle);
	    hist.SetMarkerStyle(kFullDotMedium);
	    hist.SetYTitle("Voltage [mV]");
	    hist.GetXaxis()->CenterTitle();
	    hist.GetYaxis()->CenterTitle();
	    hist.SetMinimum(-1026);
	    hist.SetMaximum(1025);
	    h = hist.DrawCopy("P");
	    h->SetDirectory(0);
	}

	ostringstream str;
	str << "ADC Pipeline (start=" << dat.StartTM << ")";
	h->SetXTitle(str.str().c_str());

	//str.str("");
	//str << "Crate=" << crate << " Board=" << board << " Channel=" << channel << " [" << d.time() << "]" << endl;
	//hist->SetTitle(str.str().c_str());

	for (int i=0; i<dat.Roi; i++)
	    h->SetBinContent(i+1, dat.Adc_Data[i]*0.5-1024);

	c->Modified();
	c->Update();
#endif
    }

    vector<uint8_t> fFadConnections;

    void handleFadConnections(const DimData &d)
    {
        if (!CheckSize(d, 41))
            return;

        const uint8_t *ptr = d.ptr<uint8_t>();

        for (int i=0; i<40; i++)
        {
            const uint8_t stat1 = ptr[i]&3;
            const uint8_t stat2 = ptr[i]>>3;

            if (stat1==0 && stat2==0)
            {
                SetLedColor(fFadLED[i], kLedGray,   d.time);
                continue;
            }
            if (stat1==2 && stat2==8)
            {
                SetLedColor(fFadLED[i], kLedGreen,  d.time);
                continue;
            }

            if (stat1==1 && stat2==1)
                SetLedColor(fFadLED[i], kLedRed, d.time);
            else
                SetLedColor(fFadLED[i], kLedOrange, d.time);
        }

        SetLedColor(fFadLEDEventBuilder, ptr[40]==0?kLedRed:kLedGreen, d.time);

        fFadConnections.assign(ptr, ptr+40);
    }

    template<typename T, class S>
        void handleFadMinMax(const DimData &d, QPushButton *led, S *wmin, S *wmax=0)
    {
        if (!CheckSize(d, 42*sizeof(T)))
            return;

        const T *ptr = d.ptr<T>();
        const T  min = ptr[40];
        const T  max = ptr[41];

        if (max<min)
            SetLedColor(led, kLedGray, d.time);
        else
            SetLedColor(led, min==max?kLedGreen: kLedOrange, d.time);

        if (!wmax && max!=min)
            wmin->setValue(0);
        else
            wmin->setValue(min);

        if (wmax)
            wmax->setValue(max);

        ostringstream tip;
        tip << "<table border='1'><tr><th colspan='11'>" << Time().GetAsStr() << " (UTC)</th></tr><tr><th></th>";
        for (int b=0; b<10; b++)
            tip << "<th>" << b << "</th>";
        tip << "</tr>";

        for (int c=0; c<4; c++)
        {
            tip << "<tr><th>" << c << "</th>";
            for (int b=0; b<10; b++)
                tip << "<td>" << ptr[c*10+b] << "</td>";
            tip << "</tr>";
        }
        tip << "</table>";

        led->setToolTip(tip.str().c_str());
    }

    void handleFadFwVersion(const DimData &d)
    {
        handleFadMinMax<float, QDoubleSpinBox>(d, fFadLedFwVersion, fFadFwVersion);
    }

    void handleFadRunNumber(const DimData &d)
    {
        handleFadMinMax<uint32_t, QSpinBox>(d, fFadLedRunNumber, fFadRunNumber);
    }

    void handleFadDNA(const DimData &d)
    {
        if (!CheckSize(d, 40*sizeof(uint64_t)))
            return;

        const uint64_t *ptr = d.ptr<uint64_t>();

        ostringstream tip;
        tip << "<table width='100%'>";
        tip << "<tr><th>Crate</th><td></td><th>Board</th><td></td><th>DNA</th></tr>";

        for (int i=0; i<40; i++)
        {
            tip << dec;
            tip << "<tr>";
            tip << "<td align='center'>" << i/10 << "</td><td>:</td>";
            tip << "<td align='center'>" << i%10 << "</td><td>:</td>";
            tip << hex;
            tip << "<td>0x" << setfill('0') << setw(16) << ptr[i] << "</td>";
            tip << "</tr>";
        }
        tip << "</table>";

        fFadDNA->setText(tip.str().c_str());
    }

    void SetFadLed(QPushButton *led, const DimData &d, uint16_t bitmask)
    {
        const bool      quality = d.ptr<uint16_t>()[0]&bitmask;
        const bool      value   = d.ptr<uint16_t>()[1]&bitmask;
        const uint16_t *ptr     = d.ptr<uint16_t>()+2;

        SetLedColor(led, quality?kLedOrange:(value?kLedGreen:kLedRed), d.time);

        ostringstream tip;
        tip << "<table border='1'><tr><th colspan='11'>" << d.time.GetAsStr() << " (UTC)</th></tr><tr><th></th>";
        for (int b=0; b<10; b++)
            tip << "<th>" << b << "</th>";
        tip << "</tr>";

        for (int c=0; c<4; c++)
        {
            tip << "<tr><th>" << dec << c << "</th>" << hex;
            for (int b=0; b<10; b++)
            {
                tip << "<td>"
                    << (ptr[c*10+b]&bitmask)
                    << "</td>";
            }
            tip << "</tr>";
        }
        tip << "</table>";

        led->setToolTip(tip.str().c_str());
    }

    void handleFadStatus(const DimData &d)
    {
        if (!CheckSize(d, 42*sizeof(uint16_t)))
            return;

        SetFadLed(fFadLedDrsEnabled,     d, FAD::EventHeader::kDenable);
        SetFadLed(fFadLedDrsWrite,       d, FAD::EventHeader::kDwrite);
        SetFadLed(fFadLedDcmLocked,      d, FAD::EventHeader::kDcmLocked);
        SetFadLed(fFadLedDcmReady,       d, FAD::EventHeader::kDcmReady);
        SetFadLed(fFadLedSpiSclk,        d, FAD::EventHeader::kSpiSclk);
        SetFadLed(fFadLedRefClockTooLow, d, FAD::EventHeader::kRefClkTooLow);
        SetFadLed(fFadLedBusy,           d, FAD::EventHeader::kBusy);
        SetFadLed(fFadLedTriggerLine,    d, FAD::EventHeader::kTriggerLine);
        SetFadLed(fFadLedContTrigger,    d, FAD::EventHeader::kContTrigger);
        SetFadLed(fFadLedSocket,         d, FAD::EventHeader::kSock17);
        SetFadLed(fFadLedPllLock,        d, 0xf000);
    }

    void handleFadStatistics(const DimData &d)
    {
        if (!CheckSize(d, 8*sizeof(int64_t)))
            return;

        const int64_t *stat = d.ptr<int64_t>();

        fFadBufferMax->setValue(stat[0]/1000000);
        fFadBuffer->setMaximum(stat[0]);
        fFadBuffer->setValue(stat[5]);

        fFadEvtWait->setValue(stat[1]);
        fFadEvtSkip->setValue(stat[2]);
        fFadEvtDel->setValue(stat[3]);
        fFadEvtTot->setValue(stat[4]);
        fFadEvtRead->setValue(stat[6]/1024.);
        fFadEvtConn->setValue(stat[7]);
    }


    // ===================== FTM ============================================

    double fTimeStamp1;

    void handleFtmTriggerCounter(const DimData &d)
    {
        if (!CheckSize(d, sizeof(FTM::DimTriggerCounter)))
            return;

        const FTM::DimTriggerCounter &sdata = d.ref<FTM::DimTriggerCounter>();

        fFtmTime->setText(QString::number(sdata.fTimeStamp/1000000., 'f', 6)+ " s");
        fTriggerCounter->setText(QString::number(sdata.fTriggerCounter));

        if (sdata.fTimeStamp>0)
            fTriggerCounterRate->setValue(1000000.*sdata.fTriggerCounter/sdata.fTimeStamp);
        else
            fTriggerCounterRate->setValue(0);


        // ----------------------------------------------
#ifdef HAVE_ROOT

        if (fTriggerCounter0<0)
        {
            fTriggerCounter0 = sdata.fTriggerCounter;
            fTimeStamp1      = sdata.fTimeStamp;
            return;
        }

        TCanvas *c = fFtmRateCanv->GetCanvas();

        TH1 *h = (TH1*)c->FindObject("TimeFrame");

        const double rate  = sdata.fTriggerCounter-fTriggerCounter0;
        const double tdiff = sdata.fTimeStamp     -fTimeStamp1;

        fTriggerCounter0   = sdata.fTriggerCounter;
        fTimeStamp1        = sdata.fTimeStamp;

        if (rate<0 && tdiff<=0)
        {
            fGraphFtmRate.Set(0);

            const double tm = Time().RootTime();

            h->SetBins(1, tm, tm+60);
            h->GetXaxis()->SetTimeFormat("%M'%S\"");
            h->GetXaxis()->SetTitle("Time");

            c->Modified();
            c->Update();
            return;
        }

        if (rate<0)
            return;

//        const double avgrate = sdata.fTimeStamp>0 ? double(sdata.fTriggerCounter)/sdata.fTimeStamp*1000000 : 1;

        const double t1 = h->GetXaxis()->GetXmax();
        const double t0 = h->GetXaxis()->GetXmin();

        h->SetBins(h->GetNbinsX()+1, t0, t0+sdata.fTimeStamp/1000000.+1);
        fGraphFtmRate.SetPoint(fGraphFtmRate.GetN(),
                               t0+sdata.fTimeStamp/1000000., 1000000*rate/tdiff);

        if (t1-t0>60)
        {
            h->GetXaxis()->SetTimeFormat("%Hh%M'");
            h->GetXaxis()->SetTitle("Time");
        }

        h->SetMinimum(0);
//        h->SetMaximum(2*avgrate);

        c->Modified();
        c->Update();
#endif
        // ----------------------------------------------
    }

    void handleFtmCounter(const DimData &d)
    {
        if (!CheckSize(d, sizeof(uint32_t)*6))
            return;

        const uint32_t *sdata = d.ptr<uint32_t>();

        fFtmCounterH->setValue(sdata[0]);
        fFtmCounterS->setValue(sdata[1]);
        fFtmCounterD->setValue(sdata[2]);
        fFtmCounterF->setValue(sdata[3]);
        fFtmCounterE->setValue(sdata[4]);
        fFtmCounterR->setValue(sdata[5]);
    }

    int64_t fTriggerCounter0;
    int64_t fTimeStamp0;

    void handleFtmDynamicData(const DimData &d)
    {
        if (!CheckSize(d, sizeof(FTM::DimDynamicData)))
            return;

        const FTM::DimDynamicData &sdata = d.ref<FTM::DimDynamicData>();

        fOnTime->setText(QString::number(sdata.fOnTimeCounter/1000000., 'f', 6)+" s");

        if (sdata.fTimeStamp>0)
            fOnTimeRel->setValue(100.*sdata.fOnTimeCounter/sdata.fTimeStamp);
        else
            fOnTimeRel->setValue(0);

        fFtmTemp0->setValue(sdata.fTempSensor[0]*0.1);
        fFtmTemp1->setValue(sdata.fTempSensor[1]*0.1);
        fFtmTemp2->setValue(sdata.fTempSensor[2]*0.1);
        fFtmTemp3->setValue(sdata.fTempSensor[3]*0.1);


#ifdef HAVE_ROOT

        // ----------------------------------------------

        if (fTimeStamp0<0)
        {
            fTimeStamp0 = sdata.fTimeStamp;
            return;
        }

        TCanvas *c = fFtmRateCanv->GetCanvas();

        TH1 *h = (TH1*)c->FindObject("TimeFrame");

        const double tdiff = sdata.fTimeStamp-fTimeStamp0;
        fTimeStamp0 = sdata.fTimeStamp;

        if (tdiff<0)
        {
            for (int i=0; i<160; i++)
                fGraphPatchRate[i].Set(0);
            for (int i=0; i<40; i++)
                fGraphBoardRate[i].Set(0);

            return;
        }

        //const double t1 = h->GetXaxis()->GetXmax();
        const double t0 = h->GetXaxis()->GetXmin();

        for (int i=0; i<160; i++)
            fGraphPatchRate[i].SetPoint(fGraphPatchRate[i].GetN(),
                                       t0+sdata.fTimeStamp/1000000., float(sdata.fRatePatch[i])*2/fFtmStaticData.fPrescaling[i]);
        for (int i=0; i<40; i++)
            fGraphBoardRate[i].SetPoint(fGraphBoardRate[i].GetN(),
                                       t0+sdata.fTimeStamp/1000000., float(sdata.fRateBoard[i])*2/fFtmStaticData.fPrescaling[i]);

        c->Modified();
        c->Update();

        //fGraphFtmRate.ComputeRange(x[0], x[1], x[2], x[3]);

        // ----------------------------------------------

        if (fThresholdIdx->value()>=0)
        {
            const int isw = fThresholdIdx->value();
            const int ihw = fPatchMapHW[isw];
            fPatchRate->setValue(sdata.fRatePatch[ihw]);
        }

        valarray<double> dat(0., 1440);

        // fPatch converts from software id to software patch id
        for (int i=0; i<1440; i++)
        {
            const int ihw = fPatchHW[i];
//            const int isw = fPatch[i];
//            const int ihw = fPatchMapHW[isw];
            dat[i] = sdata.fRatePatch[ihw];
        }

        c = fRatesCanv->GetCanvas();
        Camera *cam = (Camera*)c->FindObject("Camera");

        cam->SetData(dat);

        c->Modified();
        c->Update();

        // ----------------------------------------------
#endif
    }

    void DisplayRates()
    {
#ifdef HAVE_ROOT
        TCanvas *c = fFtmRateCanv->GetCanvas();

        while (c->FindObject("PatchRate"))
            c->GetListOfPrimitives()->Remove(c->FindObject("PatchRate"));

        while (c->FindObject("BoardRate"))
            c->GetListOfPrimitives()->Remove(c->FindObject("BoardRate"));

        c->cd();

        if (fRatePatch1->value()>=0)
        {
            fGraphPatchRate[fRatePatch1->value()].SetLineColor(kRed);
            fGraphPatchRate[fRatePatch1->value()].SetMarkerColor(kRed);
            fGraphPatchRate[fRatePatch1->value()].Draw("PL");
        }
        if (fRatePatch2->value()>=0)
        {
            fGraphPatchRate[fRatePatch2->value()].SetLineColor(kGreen);
            fGraphPatchRate[fRatePatch2->value()].SetMarkerColor(kGreen);
            fGraphPatchRate[fRatePatch2->value()].Draw("PL");
        }
        if (fRateBoard1->value()>=0)
        {
            fGraphBoardRate[fRateBoard1->value()].SetLineColor(kMagenta);
            fGraphBoardRate[fRateBoard1->value()].SetMarkerColor(kMagenta);
            fGraphBoardRate[fRateBoard1->value()].Draw("PL");
        }
        if (fRateBoard2->value()>=0)
        {
            fGraphBoardRate[fRateBoard2->value()].SetLineColor(kCyan);
            fGraphBoardRate[fRateBoard2->value()].SetMarkerColor(kCyan);
            fGraphBoardRate[fRateBoard2->value()].Draw("PL");
        }
#endif
    }

    void on_fRatePatch1_valueChanged(int)
    {
        DisplayRates();
    }

    void on_fRatePatch2_valueChanged(int)
    {
        DisplayRates();
    }

    void on_fRateBoard1_valueChanged(int)
    {
        DisplayRates();
    }

    void on_fRateBoard2_valueChanged(int)
    {
        DisplayRates();
    }

    FTM::DimStaticData fFtmStaticData;

    void SetFtuLed(int idx, int counter, const Time &t)
    {
        if (counter==0 || counter>3)
            counter = 3;

        if (counter<0)
            counter = 0;

        const LedColor_t col[4] = { kLedGray, kLedGreen, kLedOrange, kLedRed };

        SetLedColor(fFtuLED[idx], col[counter], t);

        fFtuStatus[idx] = counter;
    }

    void SetFtuStatusLed(const Time &t)
    {
        const int max = fFtuStatus.max();

        switch (max)
        {
        case 0:
            SetLedColor(fStatusFTULed, kLedGray, t);
            fStatusFTULabel->setText("All disabled");
            fStatusFTULabel->setToolTip("All FTUs are disabled");
            break;

        case 1:
            SetLedColor(fStatusFTULed, kLedGreen, t);
            fStatusFTULabel->setToolTip("Communication with FTU is smooth.");
            fStatusFTULabel->setText("ok");
            break;

        case 2:
            SetLedColor(fStatusFTULed, kLedOrange, t);
            fStatusFTULabel->setText("Warning");
            fStatusFTULabel->setToolTip("At least one FTU didn't answer immediately");
            break;

        case 3:
            SetLedColor(fStatusFTULed, kLedRed, t);
            fStatusFTULabel->setToolTip("At least one FTU didn't answer!");
            fStatusFTULabel->setText("ERROR");
            break;
        }

        const int cnt = count(&fFtuStatus[0], &fFtuStatus[40], 0);
        fFtuAllOn->setEnabled(cnt!=0);
        fFtuAllOff->setEnabled(cnt!=40);
    }

    void handleFtmStaticData(const DimData &d)
    {
        if (!CheckSize(d, sizeof(FTM::DimStaticData)))
            return;

        const FTM::DimStaticData &sdata = d.ref<FTM::DimStaticData>();

        fTriggerInterval->setValue(sdata.fTriggerInterval);
        fPhysicsCoincidence->setValue(sdata.fMultiplicityPhysics);
        fCalibCoincidence->setValue(sdata.fMultiplicityCalib);
        fPhysicsWindow->setValue(sdata.fWindowPhysics);
        fCalibWindow->setValue(sdata.fWindowCalib);

        fTriggerDelay->setValue(sdata.fDelayTrigger);
        fTimeMarkerDelay->setValue(sdata.fDelayTimeMarker);
        fDeadTime->setValue(sdata.fDeadTime);

        fClockCondR0->setValue(sdata.fClockConditioner[0]);
        fClockCondR1->setValue(sdata.fClockConditioner[1]);
        fClockCondR8->setValue(sdata.fClockConditioner[2]);
        fClockCondR9->setValue(sdata.fClockConditioner[3]);
        fClockCondR11->setValue(sdata.fClockConditioner[4]);
        fClockCondR13->setValue(sdata.fClockConditioner[5]);
        fClockCondR14->setValue(sdata.fClockConditioner[6]);
        fClockCondR15->setValue(sdata.fClockConditioner[7]);

        const uint32_t R0  = sdata.fClockConditioner[0];
        const uint32_t R14 = sdata.fClockConditioner[6];
        const uint32_t R15 = sdata.fClockConditioner[7];

        const uint32_t Ndiv = (R15&0x1ffff00)<<2;
        const uint32_t Rdiv = (R14&0x007ff00)>>8;
        const uint32_t Cdiv = (R0 &0x000ff00)>>8;

        double freq = 40.*Ndiv/(Rdiv*Cdiv);

        fClockCondFreqRes->setValue(freq);

        //fClockCondFreq->setEditText("");
        fClockCondFreq->setCurrentIndex(0);

        fTriggerSeqPed->setValue(sdata.fTriggerSeqPed);
        fTriggerSeqLPint->setValue(sdata.fTriggerSeqLPint);
        fTriggerSeqLPext->setValue(sdata.fTriggerSeqLPext);

        fEnableTrigger->setChecked(sdata.HasTrigger());
        fEnableVeto->setChecked(sdata.HasVeto());
        fEnableExt1->setChecked(sdata.HasExt1());
        fEnableExt2->setChecked(sdata.HasExt2());
        fEnableClockCond->setChecked(sdata.HasClockConditioner());

        for (int i=0; i<40; i++)
        {
            if (!sdata.IsActive(i))
                SetFtuLed(i, -1, d.time);
            else
            {
                if (fFtuStatus[i]==0)
                    SetFtuLed(i, 1, d.time);
            }
            fFtuLED[i]->setChecked(false);
        }
        SetFtuStatusLed(d.time);

#ifdef HAVE_ROOT
        Camera *cam = (Camera*)fRatesCanv->GetCanvas()->FindObject("Camera");
        for (int isw=0; isw<1440; isw++)
        {
            const int ihw = fPixelMapHW[isw];
            cam->SetEnable(isw, sdata.IsEnabled(ihw));
        }

        fRatesCanv->GetCanvas()->Modified();
        fRatesCanv->GetCanvas()->Update();
#endif

        {
            const int  isw = fPixelIdx->value();
            const int  ihw = fPixelMapHW[isw];
            const bool on  = sdata.IsEnabled(ihw);
            fPixelEnable->setChecked(on);
        }

        if (fThresholdIdx->value()>=0)
        {
            const int isw = fThresholdIdx->value();
            const int ihw = fPatchMapHW[isw];
            fThresholdVal->setValue(sdata.fThreshold[ihw]);
        }

        fPrescalingVal->setValue(sdata.fPrescaling[0]);

        fFtmStaticData = sdata;
    }

    void handleFtmPassport(const DimData &d)
    {
        if (!CheckSize(d, sizeof(FTM::DimPassport)))
            return;

        const FTM::DimPassport &sdata = d.ref<FTM::DimPassport>();

        stringstream str1, str2;
        str1 << hex << "0x" << setfill('0') << setw(16) << sdata.fBoardId;
        str2 << sdata.fFirmwareId;

        fFtmBoardId->setText(str1.str().c_str());
        fFtmFirmwareId->setText(str2.str().c_str());
    }

    void handleFtmFtuList(const DimData &d)
    {
        if (!CheckSize(d, sizeof(FTM::DimFtuList)))
            return;

        fFtuPing->setChecked(false);

        const FTM::DimFtuList &sdata = d.ref<FTM::DimFtuList>();

        stringstream str;
        str << "<table width='100%'>" << setfill('0');
        str << "<tr><th>Num</th><th></th><th>Addr</th><th></th><th>DNA</th></tr>";
        for (int i=0; i<40; i++)
        {
            str << "<tr>";
            str << "<td align='center'>"   << dec << i << hex << "</td>";
            str << "<td align='center'>:</td>";
            str << "<td align='center'>0x" << setw(2)  << (int)sdata.fAddr[i] << "</td>";
            str << "<td align='center'>:</td>";
            str << "<td align='center'>0x" << setw(16) << sdata.fDNA[i] << "</td>";
            str << "</tr>";
        }
        str << "</table>";

        fFtuDNA->setText(str.str().c_str());

        fFtuAnswersTotal->setValue(sdata.fNumBoards);
        fFtuAnswersCrate0->setValue(sdata.fNumBoardsCrate[0]);
        fFtuAnswersCrate1->setValue(sdata.fNumBoardsCrate[1]);
        fFtuAnswersCrate2->setValue(sdata.fNumBoardsCrate[2]);
        fFtuAnswersCrate3->setValue(sdata.fNumBoardsCrate[3]);

        for (int i=0; i<40; i++)
            SetFtuLed(i, sdata.IsActive(i) ? sdata.fPing[i] : -1, d.time);

        SetFtuStatusLed(d.time);
    }

    void handleFtmError(const DimData &d)
    {
        if (!CheckSize(d, sizeof(FTM::DimError)))
            return;

        const FTM::DimError &sdata = d.ref<FTM::DimError>();

        SetFtuLed(sdata.fError.fDestAddress , sdata.fError.fNumCalls, d.time);
        SetFtuStatusLed(d.time);

        // FIXME: Write to special window!
        //Out() << "Error:" << endl;
        //Out() << sdata.fError << endl;
    }

    // ====================== MessageImp ====================================

    bool fChatOnline;

    void handleStateChanged(const Time &time, const std::string &server,
                            const State &s)
    {
        // FIXME: Prefix tooltip with time
        if (server=="FTM_CONTROL")
        {
            // FIXME: Enable FTU page!!!
            fStatusFTMLabel->setText(s.name.c_str());
            fStatusFTMLabel->setToolTip(s.comment.c_str());

            bool enable = false;

            if (s.index<FTM::kDisconnected) // No Dim connection
                SetLedColor(fStatusFTMLed, kLedGray, time);
            if (s.index==FTM::kDisconnected) // Dim connection / FTM disconnected
                SetLedColor(fStatusFTMLed, kLedYellow, time);
	    if (s.index==FTM::kConnected || s.index==FTM::kIdle || s.index==FTM::kTakingData) // Dim connection / FTM connected
                SetLedColor(fStatusFTMLed, kLedGreen, time);

            if (s.index==FTM::kConnected || s.index==FTM::kIdle) // Dim connection / FTM connected
                enable = true;

            fTriggerWidget->setEnabled(enable);
            fFtuWidget->setEnabled(enable);
            fRatesWidget->setEnabled(enable);

            if (!enable)
            {
                SetLedColor(fStatusFTULed, kLedGray, time);
                fStatusFTULabel->setText("Offline");
                fStatusFTULabel->setToolTip("FTM is not online.");
            }
    }

        if (server=="FAD_CONTROL")
        {
            fStatusFADLabel->setText(s.name.c_str());
            fStatusFADLabel->setToolTip(s.comment.c_str());

            bool enable = false;

            if (s.index<FAD::kDisconnected) // No Dim connection
                SetLedColor(fStatusFADLed, kLedGray, time);
            if (s.index==FAD::kOffline) // Dim connection / FTM disconnected
                SetLedColor(fStatusFADLed, kLedRed, time);
            if (s.index==FAD::kDisconnected) // Dim connection / FTM disconnected
                SetLedColor(fStatusFADLed, kLedOrange, time);
	    if (s.index==FAD::kConnecting) // Dim connection / FTM disconnected
            {
		SetLedColor(fStatusFADLed, kLedYellow, time);
		// FIXME FIXME FIXME: The LEDs are not displayed when disabled!
                enable = true;
            }
            if (s.index>=FAD::kConnected) // Dim connection / FTM connected
            {
                SetLedColor(fStatusFADLed, kLedGreen, time);
                enable = true;
            }

            fFadWidget->setEnabled(enable);
        }

	if (server=="FSC_CONTROL")
        {
            fStatusFSCLabel->setText(s.name.c_str());
            fStatusFSCLabel->setToolTip(s.comment.c_str());

            bool enable = false;

            if (s.index<1) // No Dim connection
                SetLedColor(fStatusFSCLed, kLedGray, time);
            if (s.index==1) // Dim connection / FTM disconnected
                SetLedColor(fStatusFSCLed, kLedRed, time);
            if (s.index>=2) // Dim connection / FTM disconnected
            {
                SetLedColor(fStatusFSCLed, kLedGreen, time);
                enable = true;
            }

            //fFscWidget->setEnabled(enable);
        }

        if (server=="DATA_LOGGER")
        {
            fStatusLoggerLabel->setText(s.name.c_str());
            fStatusLoggerLabel->setToolTip(s.comment.c_str());

            bool enable = true;

            if (s.index<=30)   // Ready/Waiting
                SetLedColor(fStatusLoggerLed, kLedYellow, time);
            if (s.index<-1)     // Offline
            {
                SetLedColor(fStatusLoggerLed, kLedGray, time);
                enable = false;
            }
            if (s.index>=0x100) // Error
                SetLedColor(fStatusLoggerLed, kLedRed, time);
            if (s.index==40)   // Logging
                SetLedColor(fStatusLoggerLed, kLedGreen, time);

            fLoggerWidget->setEnabled(enable);
        }

        if (server=="CHAT")
        {
            fStatusChatLabel->setText(s.name.c_str());

            fChatOnline = s.index==0;

            SetLedColor(fStatusChatLed, fChatOnline ? kLedGreen : kLedGray, time);

            fChatSend->setEnabled(fChatOnline);
            fChatMessage->setEnabled(fChatOnline);
        }

        if (server=="SCHEDULER")
        {
            fStatusSchedulerLabel->setText(s.name.c_str());

            SetLedColor(fStatusSchedulerLed, s.index>=0 ? kLedGreen : kLedRed, time);
        }
    }

    void handleStateOffline(const string &server)
    {
        handleStateChanged(Time(), server, State(-2, "Offline", "No connection via DIM."));
    }

    void on_fTabWidget_currentChanged(int which)
    {
        if (fTabWidget->tabText(which)=="Chat")
            fTabWidget->setTabIcon(which, QIcon());
    }

    void handleWrite(const Time &time, const string &text, int qos)
    {
        stringstream out;

        if (text.substr(0, 6)=="CHAT: ")
        {
            if (qos==MessageImp::kDebug)
                return;

            out << "<font size='-1' color='navy'>[<B>";
            out << Time::fmt("%H:%M:%S") << time << "</B>]</FONT>  ";
            out << text.substr(6);
            fChatText->append(out.str().c_str());

            if (fTabWidget->tabText(fTabWidget->currentIndex())=="Chat")
                return;

            static int num = 0;
            if (num++<2)
                return;

            for (int i=0; i<fTabWidget->count(); i++)
                if (fTabWidget->tabText(i)=="Chat")
                {
                    fTabWidget->setTabIcon(i, QIcon(":/Resources/icons/warning 3.png"));
                    break;
                }

            return;
        }


        out << "<font style='font-family:monospace' color='";

        switch (qos)
        {
        case kMessage: out << "black";   break;
        case kInfo:    out << "green";   break;
        case kWarn:    out << "#FF6600"; break;
        case kError:   out << "maroon";  break;
        case kFatal:   out << "maroon";  break;
        case kDebug:   out << "navy";    break;
        default:       out << "navy";    break;
        }
        out << "'>" << time.GetAsStr() << " - " << text << "</font>";

        fLogText->append(out.str().c_str());

        if (qos>=kWarn)
            fTextEdit->append(out.str().c_str());
    }

    void IndicateStateChange(const Time &time, const std::string &server)
    {
        const State s = GetState(server, GetCurrentState(server));

        QApplication::postEvent(this,
           new FunctionEvent(boost::bind(&FactGui::handleStateChanged, this, time, server, s)));
    }

    int Write(const Time &time, const string &txt, int qos)
    {
        QApplication::postEvent(this,
           new FunctionEvent(boost::bind(&FactGui::handleWrite, this, time, txt, qos)));

        return 0;
    }

    // ====================== Dim infoHandler================================

    void handleDimService(const string &txt)
    {
        fDimSvcText->append(txt.c_str());
    }

    void infoHandlerService(DimInfo &info)
    {
        const string fmt = string(info.getFormat()).empty() ? "C" : info.getFormat();

        stringstream dummy;
        const Converter conv(dummy, fmt, false);

        const Time tm(info.getTimestamp(), info.getTimestampMillisecs()*1000);

        stringstream out;
        out << "<font size'-1' color='navy'>[" << Time::fmt("%H:%M:%S.%f") << tm << "]</font>   <B>" << info.getName() << "</B> - ";

        bool iserr = true;
        if (!conv)
        {
            out << "Compilation of format string '" << fmt << "' failed!";
        }
        else
        {
            try
            {
                const string dat = conv.GetString(info.getData(), info.getSize());
                out << dat;
                iserr = false;
            }
            catch (const runtime_error &e)
            {
                out << "Conversion to string failed!<pre>" << e.what() << "</pre>";
            }
        }

        // srand(hash<string>()(string(info.getName())));
        // int bg = rand()&0xffffff;

        int bg = hash<string>()(string(info.getName()));

        // allow only light colors
        bg = ~(bg&0x1f1f1f)&0xffffff;

        if (iserr)
            bg = 0xffffff;

        stringstream bgcol;
        bgcol << hex << setfill('0') << setw(6) << bg;

        const string col = iserr ? "red" : "black";
        const string str = "<table width='100%' bgcolor=#"+bgcol.str()+"><tr><td><font color='"+col+"'>"+out.str()+"</font></td></tr></table>";

        QApplication::postEvent(this,
                                new FunctionEvent(boost::bind(&FactGui::handleDimService, this, str)));
    }

    void CallInfoHandler(void (FactGui::*handler)(const DimData&), const DimData &d)
    {
        fInHandler = true;
        (this->*handler)(d);
        fInHandler = false;
    }

    /*
    void CallInfoHandler(const boost::function<void()> &func)
    {
        // This ensures that newly received values are not sent back to the emitter
        // because changing the value emits the valueChanged signal (or similar)
        fInHandler = true;
        func();
        fInHandler = false;
    }*/

    void PostInfoHandler(void (FactGui::*handler)(const DimData&))
    {
        //const boost::function<void()> f = boost::bind(handler, this, DimData(getInfo()));

        FunctionEvent *evt = new FunctionEvent(boost::bind(&FactGui::CallInfoHandler, this, handler, DimData(getInfo())));
        // FunctionEvent *evt = new FunctionEvent(boost::bind(&FactGui::CallInfoHandler, this, f));
        // FunctionEvent *evt = new FunctionEvent(boost::bind(handler, this, DimData(getInfo()))));

        QApplication::postEvent(this, evt);
    }

    void infoHandler()
    {
        // Initialize the time-stamp (what a weird workaround...)
        if (getInfo())
            getInfo()->getTimestamp();

        if (getInfo()==&fDimDNS)
            return PostInfoHandler(&FactGui::handleDimDNS);
#ifdef DEBUG_DIM
        cout << "HandleDimInfo " << getInfo()->getName() << endl;
#endif
        if (getInfo()==&fDimLoggerStats)
            return PostInfoHandler(&FactGui::handleLoggerStats);

//        if (getInfo()==&fDimFadFiles)
//            return PostInfoHandler(&FactGui::handleFadFiles);

        if (getInfo()==&fDimFadConnections)
            return PostInfoHandler(&FactGui::handleFadConnections);

        if (getInfo()==&fDimFadFwVersion)
            return PostInfoHandler(&FactGui::handleFadFwVersion);

        if (getInfo()==&fDimFadRunNumber)
            return PostInfoHandler(&FactGui::handleFadRunNumber);

        if (getInfo()==&fDimFadDNA)
            return PostInfoHandler(&FactGui::handleFadDNA);

        if (getInfo()==&fDimFadStatus)
            return PostInfoHandler(&FactGui::handleFadStatus);

        if (getInfo()==&fDimFadStatistics)
            return PostInfoHandler(&FactGui::handleFadStatistics);

        if (getInfo()==&fDimFadEvents)
            return PostInfoHandler(&FactGui::handleFadEvents);

        if (getInfo()==&fDimFadRuns)
            return PostInfoHandler(&FactGui::handleFadRuns);

        if (getInfo()==&fDimFadCurrentEvent)
            return PostInfoHandler(&FactGui::handleFadCurrentEvent);

	if (getInfo()==&fDimFadEventData)
            return PostInfoHandler(&FactGui::handleFadEventData);

/*
        if (getInfo()==&fDimFadSetup)
            return PostInfoHandler(&FactGui::handleFadSetup);
*/
        if (getInfo()==&fDimLoggerFilenameNight)
            return PostInfoHandler(&FactGui::handleLoggerFilenameNight);

        if (getInfo()==&fDimLoggerNumSubs)
            return PostInfoHandler(&FactGui::handleLoggerNumSubs);

        if (getInfo()==&fDimLoggerFilenameRun)
            return PostInfoHandler(&FactGui::handleLoggerFilenameRun);

        if (getInfo()==&fDimFtmTriggerCounter)
            return PostInfoHandler(&FactGui::handleFtmTriggerCounter);

        if (getInfo()==&fDimFtmCounter)
            return PostInfoHandler(&FactGui::handleFtmCounter);

        if (getInfo()==&fDimFtmDynamicData)
            return PostInfoHandler(&FactGui::handleFtmDynamicData);

        if (getInfo()==&fDimFtmPassport)
            return PostInfoHandler(&FactGui::handleFtmPassport);

        if (getInfo()==&fDimFtmFtuList)
            return PostInfoHandler(&FactGui::handleFtmFtuList);

        if (getInfo()==&fDimFtmStaticData)
            return PostInfoHandler(&FactGui::handleFtmStaticData);

        if (getInfo()==&fDimFtmError)
            return PostInfoHandler(&FactGui::handleFtmError);

//        if (getInfo()==&fDimFadFiles)
//            return PostInfoHandler(&FactGui::handleFadFiles);

        for (map<string,DimInfo*>::iterator i=fServices.begin(); i!=fServices.end(); i++)
            if (i->second==getInfo())
            {
                infoHandlerService(*i->second);
                return;
            }

        DimNetwork::infoHandler();
    }


    // ======================================================================

    bool event(QEvent *evt)
    {
        if (dynamic_cast<FunctionEvent*>(evt))
            return static_cast<FunctionEvent*>(evt)->Exec();

        if (dynamic_cast<CheckBoxEvent*>(evt))
        {
            const QStandardItem &item = static_cast<CheckBoxEvent*>(evt)->item;
            const QStandardItem *par  = item.parent();
            if (par)
            {
                const QString server  = par->text();
                const QString service = item.text();

                const string s = (server+'/'+service).toStdString();

                if (item.checkState()==Qt::Checked)
                    SubscribeService(s);
                else
                    UnsubscribeService(s);
            }
        }

        return MainWindow::event(evt); // unrecognized
    }

    void on_fDimCmdSend_clicked()
    {
        const QString server    = fDimCmdServers->currentIndex().data().toString();
        const QString command   = fDimCmdCommands->currentIndex().data().toString();
        const QString arguments = fDimCmdLineEdit->displayText();

        // FIXME: Sending a command exactly when the info Handler changes
        //        the list it might lead to confusion.
        try
        {
            SendDimCommand(server.toStdString(), command.toStdString()+" "+arguments.toStdString());
            fTextEdit->append("<font color='green'>Command '"+server+'/'+command+"' successfully emitted.</font>");
            fDimCmdLineEdit->clear();
        }
        catch (const runtime_error &e)
        {
            stringstream txt;
            txt << e.what();

            string buffer;
            while (getline(txt, buffer, '\n'))
                fTextEdit->append(("<font color='red'><pre>"+buffer+"</pre></font>").c_str());
        }
    }

#ifdef HAVE_ROOT
    void slot_RootEventProcessed(TObject *obj, unsigned int evt, TCanvas *canv)
    {
        // kMousePressEvent       // TCanvas processed QEvent mousePressEvent
        // kMouseMoveEvent        // TCanvas processed QEvent mouseMoveEvent
        // kMouseReleaseEvent     // TCanvas processed QEvent mouseReleaseEvent
        // kMouseDoubleClickEvent // TCanvas processed QEvent mouseDoubleClickEvent
        // kKeyPressEvent         // TCanvas processed QEvent keyPressEvent
        // kEnterEvent            // TCanvas processed QEvent enterEvent
        // kLeaveEvent            // TCanvas processed QEvent leaveEvent
        if (dynamic_cast<TCanvas*>(obj))
            return;

        TQtWidget *tipped = static_cast<TQtWidget*>(sender());

        if (evt==11/*kMouseReleaseEvent*/)
        {
            if (dynamic_cast<Camera*>(obj))
            {
                const float xx = canv->AbsPixeltoX(tipped->GetEventX());
                const float yy = canv->AbsPixeltoY(tipped->GetEventY());

                Camera *cam = static_cast<Camera*>(obj);
                const int isw = cam->GetIdx(xx, yy);

                fPixelIdx->setValue(isw);
                ChoosePixel(*cam, isw);
            }
            return;
        }

        if (evt==61/*kMouseDoubleClickEvent*/)
        {
            if (dynamic_cast<Camera*>(obj))
            {
                const float xx = canv->AbsPixeltoX(tipped->GetEventX());
                const float yy = canv->AbsPixeltoY(tipped->GetEventY());

                Camera *cam = static_cast<Camera*>(obj);
                const int isw = cam->GetIdx(xx, yy);

                ChoosePixel(*cam, isw);

                fPixelIdx->setValue(isw);

                const uint16_t ihw = fPixelMapHW[isw];

                Dim::SendCommand("FTM_CONTROL/TOGGLE_PIXEL", ihw);
            }

            if (dynamic_cast<TAxis*>(obj))
                static_cast<TAxis*>(obj)->UnZoom();

            return;
        }

        // Find the object which will get picked by the GetObjectInfo
        // due to buffer overflows in many root-versions
        // in TH1 and TProfile we have to work around and implement
        // our own GetObjectInfo which make everything a bit more
        // complicated.
        canv->cd();
#if ROOT_VERSION_CODE > ROOT_VERSION(5,22,00)
        const char *objectInfo =
            obj->GetObjectInfo(tipped->GetEventX(),tipped->GetEventY());
#else
        const char *objectInfo = dynamic_cast<TH1*>(obj) ?
            "" : obj->GetObjectInfo(tipped->GetEventX(),tipped->GetEventY());
#endif

        QString tipText;
        tipText += obj->GetName();
        tipText += " [";
        tipText += obj->ClassName();
        tipText += "]: ";
        tipText += objectInfo;

        if (dynamic_cast<Camera*>(obj))
        {
            const float xx = canv->AbsPixeltoX(tipped->GetEventX());
            const float yy = canv->AbsPixeltoY(tipped->GetEventY());

            Camera *cam = static_cast<Camera*>(obj);

            const int isw = cam->GetIdx(xx, yy);
            const int ihw = fPixelMapHW[isw];

            const int idx = fPatchHW[isw];

            int ii = 0;
            for (; ii<160; ii++)
                if (idx==fPatchMapHW[ii])
                    break;


            const int patch =  ihw%4;
            const int board = (ihw/4)%10;
            const int crate = (ihw/4)/10;

            ostringstream str;
            str << " (hw=" << ihw << ")  Patch=" << ii << "  (hw=" << fPatchMapHW[idx] << "; Crate=" << crate << " Board=" << board << " Patch=" << patch << ")";

            tipText += str.str().c_str();
        }


        fStatusBar->showMessage(tipText, 3000);

        gSystem->ProcessEvents();
        //QWhatsThis::display(tipText)
    }

    void slot_RootUpdate()
    {
        gSystem->ProcessEvents();
        QTimer::singleShot(0, this, SLOT(slot_RootUpdate()));
    }

    void ChoosePatch(Camera &cam, int isw)
    {
        cam.Reset();

        fThresholdIdx->setValue(isw);

        const int ihw = isw<0 ? 0 : fPatchMapHW[isw];

        fPatchRate->setEnabled(isw>=0);
        fThresholdCrate->setEnabled(isw>=0);
        fThresholdBoard->setEnabled(isw>=0);
        fThresholdPatch->setEnabled(isw>=0);

        if (isw<0)
            return;

        const int patch = ihw%4;
        const int board = (ihw/4)%10;
        const int crate = (ihw/4)/10;

        fInChoosePatch = true;

        fThresholdCrate->setValue(crate);
        fThresholdBoard->setValue(board);
        fThresholdPatch->setValue(patch);

        fInChoosePatch = false;

        fThresholdVal->setValue(fFtmStaticData.fThreshold[ihw]);
        fPatchRate->setValue(cam.GetData(isw));

        // Loop over the software idx of all pixels
        for (unsigned int i=0; i<1440; i++)
            if (fPatchHW[i]==ihw)
                cam.SetBold(i);
    }

    void ChoosePixel(Camera &cam, int isw)
    {
        const int  ihw = fPixelMapHW[isw];

        int ii = 0;
        for (; ii<160; ii++)
            if (fPatchHW[isw]==fPatchMapHW[ii])
                break;

        cam.SetWhite(isw);
        ChoosePatch(cam, ii);

        const bool on  = fFtmStaticData.IsEnabled(ihw);
        fPixelEnable->setChecked(on);
    }

    void UpdatePatch(int isw)
    {
        Camera *cam = (Camera*)fRatesCanv->GetCanvas()->FindObject("Camera");
        ChoosePatch(*cam, isw);
    }

    void on_fThresholdIdx_valueChanged(int isw)
    {
        UpdatePatch(isw);

        fRatesCanv->GetCanvas()->Modified();
        fRatesCanv->GetCanvas()->Update();
    }

    void UpdateThresholdIdx()
    {
        if (fInChoosePatch)
            return;

        const int crate = fThresholdCrate->value();
        const int board = fThresholdBoard->value();
        const int patch = fThresholdPatch->value();

        const int ihw = patch + board*4 + crate*40;

        int isw = 0;
        for (; isw<160; isw++)
            if (ihw==fPatchMapHW[isw])
                break;

        UpdatePatch(isw);
    }

    void on_fThresholdPatch_valueChanged(int)
    {
        UpdateThresholdIdx();
    }
    void on_fThresholdBoard_valueChanged(int)
    {
        UpdateThresholdIdx();
    }
    void on_fThresholdCrate_valueChanged(int)
    {
        UpdateThresholdIdx();
    }

    void on_fPixelIdx_valueChanged(int isw)
    {
        Camera *cam = (Camera*)fRatesCanv->GetCanvas()->FindObject("Camera");
        ChoosePixel(*cam, isw);

        fRatesCanv->GetCanvas()->Modified();
        fRatesCanv->GetCanvas()->Update();
    }
#endif

    void on_fPixelEnable_stateChanged(int b)
    {
        if (fInHandler)
            return;

        const uint16_t isw = fPixelIdx->value();
        const uint16_t ihw = fPixelMapHW[isw];

        Dim::SendCommand(b==Qt::Unchecked ?
                         "FTM_CONTROL/DISABLE_PIXEL" : "FTM_CONTROL/ENABLE_PIXEL",
                         ihw);
    }

    void on_fPixelDisableOthers_clicked()
    {
        const uint16_t isw = fPixelIdx->value();
        const uint16_t ihw = fPixelMapHW[isw];

        Dim::SendCommand("FTM_CONTROL/DISABLE_ALL_PIXELS_EXCEPT", ihw);
    }

    void on_fThresholdDisableOthers_clicked()
    {
        const uint16_t isw = fThresholdIdx->value();
        const uint16_t ihw = fPatchMapHW[isw];

        Dim::SendCommand("FTM_CONTROL/DISABLE_ALL_PATCHES_EXCEPT", ihw);
    }

    void on_fThresholdVal_valueChanged(int v)
    {
        fThresholdVolt->setValue(2500./4095*v);

        const int32_t isw = fThresholdIdx->value();
        const int32_t ihw = fPatchMapHW[isw];

        const int32_t d[2] = { ihw, v };

        if (!fInHandler)
            Dim::SendCommand("FTM_CONTROL/SET_THRESHOLD", d);
    }

    TGraph fGraphFtmTemp[4];
    TGraph fGraphFtmRate;
    TGraph fGraphPatchRate[160];
    TGraph fGraphBoardRate[40];

#ifdef HAVE_ROOT
    void DrawTimeFrame(const char *ytitle)
    {
        const double tm = Time().RootTime();

        TH1F h("TimeFrame", "", 1, tm, tm+60);//Time().RootTime()-1./24/60/60, Time().RootTime());
        h.SetDirectory(0);
//        h.SetBit(TH1::kCanRebin);
        h.SetStats(kFALSE);
//        h.SetMinimum(0);
//        h.SetMaximum(1);
        h.SetXTitle("Time");
        h.SetYTitle(ytitle);
        h.GetXaxis()->CenterTitle();
	h.GetYaxis()->CenterTitle();
        h.GetXaxis()->SetTimeDisplay(true);
        h.GetXaxis()->SetTimeFormat("%Mh%S'");
	h.GetXaxis()->SetLabelSize(0.025);
	h.GetYaxis()->SetLabelSize(0.025);
        h.GetYaxis()->SetTitleOffset(1.2);
//        h.GetYaxis()->SetTitleSize(1.2);
        h.DrawCopy()->SetDirectory(0);
    }
#endif

public:
    FactGui() :
        fFtuStatus(40),
        fPixelMapHW(1440), fPatchMapHW(160), fPatchHW(1440),
        fInChoosePatch(false), 
        fDimDNS("DIS_DNS/VERSION_NUMBER", 1, int(0), this),

        fDimLoggerStats        ("DATA_LOGGER/STATS",            (void*)NULL, 0, this),
        fDimLoggerFilenameNight("DATA_LOGGER/FILENAME_NIGHTLY", (void*)NULL, 0, this),
        fDimLoggerFilenameRun  ("DATA_LOGGER/FILENAME_RUN",     (void*)NULL, 0, this),
        fDimLoggerNumSubs      ("DATA_LOGGER/NUM_SUBS",         (void*)NULL, 0, this),

        fDimFtmPassport        ("FTM_CONTROL/PASSPORT",         (void*)NULL, 0, this),
        fDimFtmTriggerCounter  ("FTM_CONTROL/TRIGGER_COUNTER",  (void*)NULL, 0, this),
        fDimFtmError           ("FTM_CONTROL/ERROR",            (void*)NULL, 0, this),
        fDimFtmFtuList         ("FTM_CONTROL/FTU_LIST",         (void*)NULL, 0, this),
        fDimFtmStaticData      ("FTM_CONTROL/STATIC_DATA",      (void*)NULL, 0, this),
        fDimFtmDynamicData     ("FTM_CONTROL/DYNAMIC_DATA",     (void*)NULL, 0, this),
        fDimFtmCounter         ("FTM_CONTROL/COUNTER",          (void*)NULL, 0, this),
        fDimFadRuns            ("FAD_CONTROL/RUNS",             (void*)NULL, 0, this),
        fDimFadEvents          ("FAD_CONTROL/EVENTS",           (void*)NULL, 0, this),
        fDimFadCurrentEvent    ("FAD_CONTROL/CURRENT_EVENT",    (void*)NULL, 0, this),
        fDimFadEventData       ("FAD_CONTROL/EVENT_DATA",       (void*)NULL, 0, this),
        fDimFadConnections     ("FAD_CONTROL/CONNECTIONS",      (void*)NULL, 0, this),
        fDimFadFwVersion       ("FAD_CONTROL/FIRMWARE_VERSION", (void*)NULL, 0, this),
        fDimFadRunNumber       ("FAD_CONTROL/RUN_NUMBER",       (void*)NULL, 0, this),
        fDimFadDNA             ("FAD_CONTROL/DNA",              (void*)NULL, 0, this),
        fDimFadStatus          ("FAD_CONTROL/STATUS",           (void*)NULL, 0, this),
        fDimFadStatistics      ("FAD_CONTROL/STATISTICS",       (void*)NULL, 0, this)
    {
        fClockCondFreq->addItem("--- Hz",  QVariant(-1));
        fClockCondFreq->addItem("800 MHz", QVariant(800));
        fClockCondFreq->addItem("1 GHz",   QVariant(1000));
        fClockCondFreq->addItem("2 GHz",   QVariant(2000));
        fClockCondFreq->addItem("3 GHz",   QVariant(3000));
        fClockCondFreq->addItem("4 GHz",   QVariant(4000));
        fClockCondFreq->addItem("5 GHz",   QVariant(5000));

        fTriggerWidget->setEnabled(false);
        fFtuWidget->setEnabled(false);
        fRatesWidget->setEnabled(false);
//        fFadWidget->setEnabled(false);
        fLoggerWidget->setEnabled(false);

        fChatSend->setEnabled(false);
        fChatMessage->setEnabled(false);

        DimClient::sendCommand("CHAT/MSG", "GUI online.");
        // + MessageDimRX

        // --------------------------------------------------------------------------

        ifstream fin1("Trigger-Patches.txt");

        int l = 0;

        string buf;
        while (getline(fin1, buf, '\n'))
        {
            buf = Tools::Trim(buf);
            if (buf[0]=='#')
                continue;

            stringstream str(buf);
            for (int i=0; i<9; i++)
            {
                unsigned int n;
                str >> n;

                if (n>=fPatchHW.size())
                    continue;

                fPatchHW[n] = l;
            }
            l++;
        }

        if (l!=160)
            cerr << "WARNING - Problems reading Trigger-Patches.txt" << endl;

        // --------------------------------------------------------------------------

        ifstream fin2("MasterList-v3.txt");

        l = 0;

        while (getline(fin2, buf, '\n'))
        {
            buf = Tools::Trim(buf);
            if (buf[0]=='#')
                continue;

            unsigned int softid, hardid, dummy;

            stringstream str(buf);

            str >> softid;
            str >> dummy;
            str >> hardid;

            if (softid>=fPixelMapHW.size())
                continue;

            fPixelMapHW[softid] = hardid;

            l++;
        }

        if (l!=1440)
            cerr << "WARNING - Problems reading MasterList-v3.txt" << endl;

        // --------------------------------------------------------------------------

        ifstream fin3("PatchList.txt");

        l = 0;

        while (getline(fin3, buf, '\n'))
        {
            buf = Tools::Trim(buf);
            if (buf[0]=='#')
                continue;

            unsigned int softid, hardid;

            stringstream str(buf);

            str >> softid;
            str >> hardid;

            if (softid>=fPatchMapHW.size())
                continue;

            fPatchMapHW[softid] = hardid-1;

            l++;
        }

        if (l!=160)
            cerr << "WARNING - Problems reading PatchList.txt" << endl;

        // --------------------------------------------------------------------------
#ifdef HAVE_ROOT

        fGraphFtmRate.SetLineColor(kBlue);
        fGraphFtmRate.SetMarkerColor(kBlue);
        fGraphFtmRate.SetMarkerStyle(kFullDotMedium);

        for (int i=0; i<160; i++)
        {
            fGraphPatchRate[i].SetName("PatchRate");
            //fGraphPatchRate[i].SetLineColor(kBlue);
            //fGraphPatchRate[i].SetMarkerColor(kBlue);
            fGraphPatchRate[i].SetMarkerStyle(kFullDotMedium);
        }
        for (int i=0; i<40; i++)
        {
            fGraphBoardRate[i].SetName("BoardRate");
            //fGraphBoardRate[i].SetLineColor(kBlue);
            //fGraphBoardRate[i].SetMarkerColor(kBlue);
            fGraphBoardRate[i].SetMarkerStyle(kFullDotMedium);
        }
        /*
        TCanvas *c = fFtmTempCanv->GetCanvas();
        c->SetBit(TCanvas::kNoContextMenu);
        c->SetBorderMode(0);
        c->SetFrameBorderMode(0);
        c->SetFillColor(kWhite);
        c->SetRightMargin(0.03);
        c->SetTopMargin(0.03);
        c->cd();
        */
        //CreateTimeFrame("Temperature / °C");

        fGraphFtmTemp[0].SetMarkerStyle(kFullDotSmall);
        fGraphFtmTemp[1].SetMarkerStyle(kFullDotSmall);
        fGraphFtmTemp[2].SetMarkerStyle(kFullDotSmall);
        fGraphFtmTemp[3].SetMarkerStyle(kFullDotSmall);

        fGraphFtmTemp[1].SetLineColor(kBlue);
        fGraphFtmTemp[2].SetLineColor(kRed);
        fGraphFtmTemp[3].SetLineColor(kGreen);

        fGraphFtmTemp[1].SetMarkerColor(kBlue);
        fGraphFtmTemp[2].SetMarkerColor(kRed);
        fGraphFtmTemp[3].SetMarkerColor(kGreen);

        //fGraphFtmTemp[0].Draw("LP");
        //fGraphFtmTemp[1].Draw("LP");
        //fGraphFtmTemp[2].Draw("LP");
        //fGraphFtmTemp[3].Draw("LP");

        // --------------------------------------------------------------------------

        TCanvas *c = fFtmRateCanv->GetCanvas();
        //c->SetBit(TCanvas::kNoContextMenu);
        c->SetBorderMode(0);
        c->SetFrameBorderMode(0);
        c->SetFillColor(kWhite);
        c->SetRightMargin(0.03);
        c->SetTopMargin(0.03);
        c->SetGrid();
        c->cd();

        DrawTimeFrame("Trigger rate [Hz]");

        fTriggerCounter0 = -1;

        fGraphFtmRate.SetMarkerStyle(kFullDotSmall);
        fGraphFtmRate.Draw("LP");

        // --------------------------------------------------------------------------

        c = fRatesCanv->GetCanvas();
        //c->SetBit(TCanvas::kNoContextMenu);
        c->SetBorderMode(0);
        c->SetFrameBorderMode(0);
        c->SetFillColor(kWhite);
        c->cd();

        Camera *cam = new Camera;
        cam->SetBit(kCanDelete);
        cam->Draw();

        ChoosePixel(*cam, 0);

        // --------------------------------------------------------------------------

        c = fAdcDataCanv->GetCanvas();
        c->SetBit(TCanvas::kNoContextMenu);
        c->SetBorderMode(0);
        c->SetFrameBorderMode(0);
        c->SetFillColor(kWhite);
        c->SetGrid();
        c->cd();

        // Create histogram?

        // --------------------------------------------------------------------------

//        QTimer::singleShot(0, this, SLOT(slot_RootUpdate()));

        //widget->setMouseTracking(true);
        //widget->EnableSignalEvents(kMouseMoveEvent);

        fFtmRateCanv->setMouseTracking(true);
        fFtmRateCanv->EnableSignalEvents(kMouseMoveEvent);

        fAdcDataCanv->setMouseTracking(true);
        fAdcDataCanv->EnableSignalEvents(kMouseMoveEvent);

        fRatesCanv->setMouseTracking(true);
        fRatesCanv->EnableSignalEvents(kMouseMoveEvent|kMouseReleaseEvent|kMouseDoubleClickEvent);

        connect(fRatesCanv,   SIGNAL(     RootEventProcessed(TObject*, unsigned int, TCanvas*)),
                this,         SLOT  (slot_RootEventProcessed(TObject*, unsigned int, TCanvas*)));
        connect(fFtmRateCanv, SIGNAL(     RootEventProcessed(TObject*, unsigned int, TCanvas*)),
                this,         SLOT  (slot_RootEventProcessed(TObject*, unsigned int, TCanvas*)));
        connect(fAdcDataCanv, SIGNAL(     RootEventProcessed(TObject*, unsigned int, TCanvas*)),
                this,         SLOT  (slot_RootEventProcessed(TObject*, unsigned int, TCanvas*)));
#endif
    }

    ~FactGui()
    {
        UnsubscribeAllServers();
    }
};

#endif
