// $Id: qtlcmswidgets.cpp,v 1.19 2006/06/09 19:41:01 hvengel Exp $
//  Little cms - profiler construction set
//  Copyright (C) 1998-2001 Marti Maria
// Copyright (C) 2005-2006  Hal Engel
//
// THIS SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
// EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
// WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
//
// IN NO EVENT SHALL MARTI MARIA BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
// INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
// OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
// WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
// LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
// OF THIS SOFTWARE.
//
// This file is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
// As a special exception to the GNU General Public License, if you
// distribute this file as part of a program that contains a
// configuration script generated by Autoconf, you may include it under
// the same distribution terms that you use for the rest of that program.
//
// Version 1.08a


// Some shared widgets for trolltech Qt

#include "qtlcmswidgets.h"
#include <setjmp.h>

QString fName;

jmp_buf cmsJumpBuffer;

int cmsErrorHandler(int /*ErrorCode*/, const char *ErrorText)
{
    // After 1.11.4 add a message dialog here to let users
    // know that a profile did not load correctly
    qDebug((QString) "Littlecms : " + (QString) ErrorText +(QString) " in file " + fName);
    longjmp(cmsJumpBuffer, 1);
    return 1;
}

// A selector widget

BaseSelector::BaseSelector(QString ThePrompt, QString DefaultDir, 
                           QString Mask, BOOL lLineEdit, BOOL showFileDialog,
                           QWidget* parent, const char* name, WFlags f) :
    QHBox(parent, name, f),
    Dir(DefaultDir), 
    FileMask(Mask),
    lUseDescriptor(0),
    lUseLineEdit(lLineEdit)
{
    lForOutput = FALSE;
    Prompt = new QLabel(ThePrompt + " :", this);
    Prompt->setAlignment(AlignRight|AlignVCenter);
    Prompt->setSizePolicy(QSizePolicy (QSizePolicy::Preferred, QSizePolicy::Preferred));
        
    if (lUseLineEdit) 
    {
        LineEdit = new QLineEdit(this);
        Prompt->setBuddy(LineEdit);
        Combo = NULL;
    }
    else 
    {
        Combo = new QComboBox(FALSE, this), 
        Combo->setEditable(FALSE);
        Combo-> setSizePolicy(QSizePolicy (QSizePolicy::Expanding, QSizePolicy::Preferred));
        Prompt->setBuddy(Combo);
        LineEdit = NULL;
    }
        
    setMargin(2);
    setSpacing(10);
    if (showFileDialog)
    {
        PickButton = new QToolButton(this);
        PickButton -> setSizePolicy(QSizePolicy (QSizePolicy::Minimum, QSizePolicy::Preferred));
        PickButton->setText(" ... ");
        NewButtonPtr = NULL;
        connect(PickButton, SIGNAL( clicked() ), this, SLOT( slotButtonClicked() ) );
    } 
    
    if (lUseLineEdit)
        connect( LineEdit, SIGNAL( textChanged(const QString&) ), this, SLOT( slotValueChanged() ) );
    else
        connect( Combo, SIGNAL( activated(int) ), this, SLOT( slotValueChanged() ) );
}


QString BaseSelector::getFilename() const
{
    if (lUseLineEdit) return LineEdit -> text();

    int Key = Combo->currentItem();
    if (Key < 0) return QString::null;

    return FileNames[Key];
}


void BaseSelector::setFilename(const QString name)
{
    if (lUseLineEdit) 
    {
        LineEdit -> setText(name);
        return;
    }
    
    for (unsigned int i = 0; i < FileNames.count(); i++) 
    {
        if (FileNames[i] == name) Combo->setCurrentItem(i);
    }
}

void BaseSelector::slotButtonClicked()
{
    QString fn;
    QFileDialog fd;

    fd.setShowHiddenFiles(TRUE);

    if (lUseLineEdit) 
    {
        if (lForOutput)  
        {
            fn = fd.getSaveFileName(Dir, 
                                          FileMask, 
                                          this,
                                          /*QString::null */ "",
                                          Prompt->text());      
        }
        else 
        {
                fn = fd.getOpenFileName(Dir, 
                                          FileMask, 
                                          this,
                                          /* QString::null */ "",
                                          Prompt->text());      
        }
    }
    else 
    {
        fn = fd.getExistingDirectory(Dir, this, "", 
                                     QTranslator::tr("Select ") + getPrompt() + QTranslator::tr(" directory"), TRUE);    
    }
    

    if (fn != QString::null) 
    {
        Dir = fn;
        FillValues();
        slotValueChanged();
    }
    
}


void BaseSelector::slotValueChanged()
{
    emit valueChanged();
}


void IT8FileSelector::FillValues()
{

    if (lUseLineEdit) 
    {
            LineEdit -> setText(Dir);
    }
}



void IT8Selector::FillValues()
{
    // qDebug("In IT8Selector::FillValues()");
    if (lUseLineEdit)  { return; }

    Descriptions.clear();
    FileNames.clear();
    Combo->clear();

    QDir fd(Dir);

    if (!fd.isReadable()) { return; }

    // QApplication::setOverrideCursor(WaitCursor);
    
    fd.setFilter(QDir::Files | QDir::Hidden);
    fd.setNameFilter("*.txt *.TXT *.IT8 *.it8 *.q60 *.Q60 *.ITX *.itx");

    const QFileInfoList* files = fd.entryInfoList();
    QString FileName;

    if (files) 
    { 
        QFileInfoListIterator it(*files);
        QFileInfo* fi;
        if (it.current() == NULL) qDebug("file iterator is null");

        while ((fi=it.current()) != NULL) 
        {
            ++it;

            FileName = fi -> filePath();
            LCMSHANDLE hIT8 = cmsxIT8LoadFromFile((const char *) FileName.local8Bit());
            if (hIT8) 
            {
                if (lUseDescriptor) 
                {
                    const char *Descriptor = cmsxIT8GetProperty(hIT8, "DESCRIPTOR");

                    if (*Descriptor == '\0')
                        Descriptor = cmsxIT8GetProperty(hIT8, "ORIGINATOR");

                    Descriptions << Descriptor;
                    
                }
                else 
                {
                    QString Serial       = cmsxIT8GetProperty(hIT8, "SERIAL");
                    QString Manufacturer   = cmsxIT8GetProperty(hIT8, "MANUFACTURER");
					if (Manufacturer == QString::null)
							Manufacturer   = cmsxIT8GetProperty(hIT8, "MANUFACTURE");

                    if (Serial != QString::null && Manufacturer != QString::null) 
                            Descriptions << (Serial + " " + Manufacturer);
                    else
                    {
                        const char *Descriptor = cmsxIT8GetProperty(hIT8, "ORIGINATOR");
                        Descriptions << Descriptor;
                    }
                }

                FileNames << FileName;

                cmsxIT8Free(hIT8);
            }
            
        }
    }
    
    Combo->insertStringList(Descriptions);
    QApplication::restoreOverrideCursor();
}

void IT8SelectorWithFileDialog::FillValues()
{

    if (lUseLineEdit)  return;

    Descriptions.clear();
    FileNames.clear();
    Combo->clear();

    QDir fd(Dir);

    if (!fd.isReadable()) return;

    // QApplication::setOverrideCursor(WaitCursor);
    
    fd.setFilter(QDir::Files | QDir::Hidden);
    fd.setNameFilter("*.txt *.TXT *.IT8 *.it8 *.q60 *.Q60 *.ITX *.itx");

    const QFileInfoList* files = fd.entryInfoList();
    QString FileName;

    if (files) 
    {

        QFileInfoListIterator it(*files);
        QFileInfo* fi;

        while ((fi=it.current()) != NULL) 
        {
            ++it;

            FileName = fi -> filePath();
            LCMSHANDLE hIT8 = cmsxIT8LoadFromFile((const char *) FileName.local8Bit());
            if (hIT8) 
            {
                if (lUseDescriptor) 
                {
                    const char *Descriptor = cmsxIT8GetProperty(hIT8, "DESCRIPTOR");

                    if (*Descriptor == '\0')
                        Descriptor = cmsxIT8GetProperty(hIT8, "ORIGINATOR");

                    Descriptions << Descriptor;
                    
                }
                else 
                {
                    QString Serial       = cmsxIT8GetProperty(hIT8, "SERIAL");
                    QString Manufacturer   = cmsxIT8GetProperty(hIT8, "MANUFACTURER");
                    if (Manufacturer == QString::null)
                        Manufacturer   = cmsxIT8GetProperty(hIT8, "MANUFACTURE");

                    if (Serial != QString::null && Manufacturer != QString::null) 
                        Descriptions << (Serial + " " + Manufacturer);
                    else
                    {
                        const char *Descriptor = cmsxIT8GetProperty(hIT8, "ORIGINATOR");
                        Descriptions << Descriptor;
                    }
                }

                FileNames << FileName;

                cmsxIT8Free(hIT8);
            }
            
        }
    }
    
    Combo->insertStringList(Descriptions);
    QApplication::restoreOverrideCursor();
}

void ICCProfileSelector::FillValues()
{

    cmsHPROFILE hICM;
    if (lUseLineEdit) return;

    Descriptions.clear();
    FileNames.clear();
    Combo->clear();

    QDir fd(Dir);
    Combo->clear();


    if (!fd.isReadable()) return;

    // QApplication::setOverrideCursor(WaitCursor);
    
    fd.setFilter(QDir::Files | QDir::Hidden);
    fd.setNameFilter(FileMask);

    const QFileInfoList* files = fd.entryInfoList();
    QString FileName;
    
    if (files) 
    {

        QFileInfoListIterator it(*files);
        QFileInfo* fi;

        while ((fi=it.current()) != NULL) 
        {
            ++it;

            if (setjmp(cmsJumpBuffer))
            {
                cmsSetErrorHandler(NULL);
                if (hICM)
                {
                    cmsCloseProfile(hICM);
                    hICM = NULL;
                }
                continue;
            }
            
            cmsSetErrorHandler(&cmsErrorHandler);

            FileName = fi -> filePath();
            fName = FileName;
            
            hICM = cmsOpenProfileFromFile((const char *) FileName.local8Bit(), "r");
            if (hICM) 
            {
        
                if ((RestrictClass == -1 || 
                    ((int) cmsGetDeviceClass(hICM) == RestrictClass)) &&
                    (RestrictColorspace == -1 ||
                    ((int) cmsGetColorSpace(hICM) == RestrictColorspace)))
                {

                    const char *Descriptor = cmsTakeProductName(hICM);
                    if (strlen(Descriptor) > 0) // egnore bad profiles
                    {
                        Descriptions       << Descriptor;
                        FileNames          << FileName;
                    }
                }

                cmsCloseProfile(hICM);
            }
            
        }
    }
    
    Combo->insertStringList(Descriptions);

    QApplication::restoreOverrideCursor();

}


// ---------------------------------------------------------------- Results

// Relative colorfulness distance

static
double LabColorfulnessDist(LPcmsCIELab l1, LPcmsCIELab l2)
{

    if (l1 -> L == 0 && l2 ->L == 0) return 0;	// Pure black

       // L*a*b define relative colorfulness as
       //
       // Cab = sqrt(a^2+b^2)

    double adist, bdist;

    adist = fabs(l1->a - l2->a);
    bdist = fabs(l1->b - l2->b);

    return sqrt((adist*adist + bdist*bdist));

}

// Hue distance in degree, -1 if don't care

static
double LabHueDist(LPcmsCIELab l1, LPcmsCIELab l2)
{

    if ((fabs(l1->a) < 4.5) && (fabs(l1->b) < 4.5)) return -1;

    double l = fabs(atan2(l1->a, l1->b) - atan2(l2->a, l2->b));

    l *= (180.0/M_PI);

    while (l < 0)
            l += 360;

    return l;
}


static
double LabLDist(LPcmsCIELab l1, LPcmsCIELab l2)
{
    return fabs(l1-> L - l2 ->L);   
}


static
void SetGridItem(QTable* Grid, int row, int col, QString& s)
{
    QTableItem* Item = new QTableItem(Grid, QTableItem::Never, s);
    Grid -> setItem(row, col, Item);
}


static
int Scale(WORD i)
{
    return (int) ((double) i / 257. + .5);
}

static
QPixmap* CreateProofPixmap(WORD Inside[3], WORD Frame[3])
{
    QColor ColorFrame(Scale(Frame[0]), Scale(Frame[1]), Scale(Frame[2]));
    QColor ColorInside(Scale(Inside[0]), Scale(Inside[1]), Scale(Inside[2]));
    QPixmap* pix = new QPixmap(34, 32, -1, QPixmap::MemoryOptim);
        
    QPainter Painter(pix);


    Painter.fillRect(0, 0, 2,  32, Qt::white);
    Painter.fillRect(2, 0, 32, 32, ColorFrame);
    Painter.fillRect(8, 6, 20, 20, ColorInside);
                
    return pix;
}


static
void SetGridPatch(QTable* Grid, int row, int col, WORD RGB[3], WORD LabProof[3])
{
    QPixmap* Pixmap = CreateProofPixmap(LabProof, RGB);

    QTableItem* Item = new QTableItem(Grid, QTableItem::Never, QString::null, *Pixmap); 
    Grid -> setItem(row, col, Item);

}

static
void CreateFancyReport(QString& Report, Statistics& st)
{
    
    QString frm1 = 
            
            QTranslator::tr("<qt><font size=4><table cellspacing=0 cellpadding=0> "
            "  <tr> "
            "    <td align=right><b><font color=red>Average dE :</font></b></td>"
            "    <td><b><u>%1</b></u></td>"
            "    <td align=right><font color=blue>Standard deviation :</font></td>"
            "    <td>%2</td>"
            "  </tr>"
            "  <tr>" 
            "    <td align=right>Peak :</td> " 
            "    <td>%3</td> "
            "  </tr> "
            "  <tr> "
            "    <td align=right>Min : </td> "
            "    <td>%4</td> "
            "  </tr> ");


    QString frm2 = 

            QTranslator::tr("  <tr> "
            "    <td align=right>Average Target error: (99% confidence)</td> "
            "    <td>%1</td> "
            "    <td align=right><font color=blue>Maximum error induced by target itself:<font></i></td> "
            "    <td>%2</td> "
            "  </tr> ");


    QString Text;

    Text = frm1.arg( st.Mean(), 0, 'f', 2);
    Text = Text.arg( st.Std(), 0, 'f', 2);
    Text = Text.arg( st.Peak, 0, 'f', 2);
    Text = Text.arg( st.Min, 0, 'f', 2);

    Report = Text;

    if (st.TargetErrorMean() > 0.0) 
    {
        Text = frm2.arg(st.TargetErrorMean(), 0, 'f', 2);
        Text = Text.arg(st.TargetErrorMax(), 0, 'f', 2);
        Report += Text;
    }

    Report += "</table></font></qt>";
}



void CheckProfileResults(QTable* ResultsGrid, 
                     QString&  ResultsText, 
                     LPMEASUREMENT m,					 
                     const char* OutputProfileFile,
                     const char* ProofProfile)
{

    QString Text;
    
    QHeader* Vertical   = ResultsGrid -> verticalHeader();
    QHeader* Horizontal = ResultsGrid -> horizontalHeader();    

    Vertical -> setResizeEnabled(FALSE);

    ResultsGrid -> setLeftMargin(60);   
    ResultsGrid -> setNumCols(11);

    Horizontal -> setLabel(0, "");
    Horizontal -> setLabel(1, QTranslator::tr("CIE La*b* dE"));
    Horizontal -> setLabel(2, QTranslator::tr("Target error"));
    Horizontal -> setLabel(3, QTranslator::tr("Chroma error"));
    Horizontal -> setLabel(4, QTranslator::tr("Hue error"));
    Horizontal -> setLabel(5, QTranslator::tr("Luma error"));
    Horizontal -> setLabel(6, QTranslator::tr("Target Lab"));
    Horizontal -> setLabel(7, QTranslator::tr("Got"));

    Horizontal -> setLabel(8, QTranslator::tr("dE CIE94"));
    Horizontal -> setLabel(9, QTranslator::tr("dE CMC"));
    Horizontal -> setLabel(10, QTranslator::tr("dE BFL"));

    ResultsGrid -> setColumnWidth (0, 37);
            
    cmsHPROFILE hProfile  = cmsOpenProfileFromFile(OutputProfileFile, "r");
    
    cmsCIEXYZ Wt;
    cmsTakeMediaWhitePoint(&Wt, hProfile);

    cmsCIExyY xyY;
    cmsXYZ2xyY(&xyY, &Wt);

    /*
    int Medium;
    if (cmsGetDeviceClass(hProfile) == icSigDisplayClass)
        Medium = MEDIUM_TRANSMISSIVE;
    else
        Medium = MEDIUM_REFLECTIVE_D50;
    */

    // Assure we got D50 Lab
    cmsxCompleteLabOfPatches(m, m ->Allowed, MEDIUM_REFLECTIVE_D50);
    cmsHPROFILE hLab      = cmsCreateLabProfile(NULL);
    cmsHPROFILE hProofProfile, hCompute, hProof_Lab, hProof_RGB;
    if (ProofProfile == NULL) hProofProfile = cmsCreate_sRGBProfile();
    else hProofProfile = cmsOpenProfileFromFile(ProofProfile, "r");

    hCompute = cmsCreateTransform(hProfile, 
                                  TYPE_RGB_16, hLab, TYPE_Lab_16, 
                                  INTENT_ABSOLUTE_COLORIMETRIC, 
                                  cmsFLAGS_NOTPRECALC);

    hProof_Lab = cmsCreateTransform(hLab, 
                                    TYPE_Lab_16, 
                                    hProofProfile, TYPE_RGB_16,
                                    INTENT_ABSOLUTE_COLORIMETRIC, 
                                    cmsFLAGS_NOTPRECALC);

    hProof_RGB = cmsCreateTransform(hProfile, 
                                    TYPE_RGB_16, 
                                    hProofProfile, 
                                    TYPE_RGB_16,
                                    INTENT_ABSOLUTE_COLORIMETRIC, 
                                    cmsFLAGS_NOTPRECALC);
	

    Statistics st;
    st.ClearStatistics();
 
    ResultsGrid -> setNumRows(m->nPatches);  

    int i;
    for (i =0; i < m->nPatches; i++) 
    {
        LPPATCH p = m->Patches + i;
        if (m->Allowed[i]) 
        {
            cmsCIELab ProfileLab, RefLab; 
            WORD RGB[3], ProfileLabEncoded[3], RefLabEncoded[3];
            WORD RGBFromLabProof[3], RGBProof[3];

            ResultsGrid -> showRow(i);
            ResultsGrid -> setRowHeight(i, 37);

            // Take reference RGB
            RGB[0] = (WORD) floor(p -> Colorant.RGB[0] * 257. + .5);
            RGB[1] = (WORD) floor(p -> Colorant.RGB[1] * 257. + .5);
            RGB[2] = (WORD) floor(p -> Colorant.RGB[2] * 257. + .5);

            // Take reference Lab
            RefLab.L = p ->Lab.L;
            RefLab.a = p ->Lab.a;
            RefLab.b = p ->Lab.b;

            // Convert Lab to Encoded
            cmsFloat2LabEncoded(RefLabEncoded, &RefLab);

            // Check numbers
            cmsDoTransform(hCompute,   RGB,  ProfileLabEncoded, 1);

             // Check Visually
            cmsDoTransform(hProof_Lab, RefLabEncoded, RGBFromLabProof, 1);
            cmsDoTransform(hProof_RGB, RGB,           RGBProof, 1);
            
            SetGridPatch(ResultsGrid, i, 0, RGBProof, RGBFromLabProof);

            cmsLabEncoded2Float(&ProfileLab, ProfileLabEncoded);
            cmsLab2XYZ(cmsD50_XYZ(), &p->XYZProof, &ProfileLab);	
            p ->dwFlags |= PATCH_HAS_XYZ_PROOF;
            double dE   = cmsDeltaE(&RefLab, &ProfileLab);
            double dE94 = cmsCIE94DeltaE(&RefLab, &ProfileLab);
            double dECMC = cmsCMCdeltaE(&RefLab, &ProfileLab);
            double dEBFL = cmsBFDdeltaE(&RefLab, &ProfileLab);
            double dC = LabColorfulnessDist(&RefLab, &ProfileLab);
            double dH = LabHueDist(&RefLab, &ProfileLab);
            double dL = LabLDist(&RefLab, &ProfileLab);

            st.AddOnePatch(dE);
           Text.sprintf(" %2.2f ", dE);
            SetGridItem(ResultsGrid, i, 1, Text);

            Text.sprintf(" %2.2f ", dC);
            SetGridItem(ResultsGrid, i, 3, Text);

            // Compute % of deviation of hue

            if (dH > 180)
                dH = 360 - dH;

            if (dH < 0)
                Text.sprintf(QTranslator::tr("(achromatic)"));
            else
                Text.sprintf(" %3.0f° (%2.2f%%) ", dH, (dH * 100. / 180.));
            SetGridItem(ResultsGrid, i, 4, Text);

            Text.sprintf(" %2.2f ", dL);
            SetGridItem(ResultsGrid, i, 5, Text);


            Text.sprintf("%3.0f %3.0f %3.0f", RefLab.L, RefLab.a, RefLab.b);
            SetGridItem(ResultsGrid, i, 6, Text);

            Text.sprintf("%3.0f %3.0f %3.0f", ProfileLab.L, ProfileLab.a, ProfileLab.b);
            SetGridItem(ResultsGrid, i, 7, Text);


           Text.sprintf(" %2.2f ", dE94);
            SetGridItem(ResultsGrid, i, 8, Text);

            Text.sprintf(" %2.2f ", dECMC);
            SetGridItem(ResultsGrid, i, 9, Text);

            Text.sprintf(" %2.2f ", dEBFL);
            SetGridItem(ResultsGrid, i, 10, Text);

            if (p ->dwFlags & PATCH_HAS_STD_DE) 
            {
                    st.EstimateTargetError(p -> dEStd);
                    Text.sprintf(" %2.2f ", st.TargetError());
                    SetGridItem(ResultsGrid, i, 2, Text);
            }


            QString Title = p ->Name;

            if (dE > 10)
                    Title = "** " + Title;

                Vertical -> setLabel( i, Title);
            
             
        }
         else 
            ResultsGrid -> hideRow(i);
        
    }

    cmsDeleteTransform(hCompute);
    cmsDeleteTransform(hProof_Lab);
    cmsDeleteTransform(hProof_RGB);

    cmsCloseProfile(hProfile);
    cmsCloseProfile(hLab);
    cmsCloseProfile(hProofProfile);
    
    CreateFancyReport(ResultsText, st);        
    
}

