//-----------------------------------------------------------------------------
//  Copyright (C) 2002-2011 Thomas S. Ullrich 
//
//  This file is part of "xyscan".
//
//  This file may be used under the terms of the GNU General Public License.
//  This project is free software; you can redistribute it and/or modify it
//  under the terms of the GNU General Public License.
//  
//  Author: Thomas S. Ullrich
//  Last update: May 15, 2011
//-----------------------------------------------------------------------------
#include <QtGui>
#include <QtDebug>
#include <cmath>
#include <ctime>
#include <cfloat>
#include <iostream>
#include "xyscanWindow.h"
#include "xyscanVersion.h"
#include "xyscanAbout.h"
#include "xyscanHelpBrowser.h"
#include "xyscanMarkerMaps.h"
#include "xyscanUpdater.h"

using namespace std;

xyscanWindow::xyscanWindow() 
{
    //
    //  Handle command line argument (filename).
    //  Since this is a GUI application this is
    //  only here for convinience.
    //
    bool openFileafterInit = false;
    QString fileToOpen;
    
    QStringList arguments = QCoreApplication::arguments();
    QString usage(tr("Usage: xyscan -v\n       xyscan filename"));
    
    arguments.takeFirst();      // remove the first argument, which is the program's name
    
    if (arguments.size() > 1) {
        cout << qPrintable(usage) << endl;
        exit(0);
    }
    else if (arguments.size() == 1) {
        QString arg = arguments.at(0);
        if (arg[0] == '-') {
            if (arg == "-v")
                cout << "xyscan " << VERSION << qPrintable(tr(" (build ")) 
                << __DATE__ << ")" << endl;
            else
                cout << qPrintable(usage) << endl;
            exit(0);
        }
        else {
            if (QFile::exists(arg)) {
	      openFileafterInit = true; // defer until all initialization is done
                fileToOpen = arg;
	  }
            else {
                cerr << qPrintable(tr("File '")) << qPrintable(arg) 
                << qPrintable(tr("' does not exist.")) << endl;
                exit(1);
            }
        }
    }
    
    //
    //  Platform specifics
    //
#if defined(Q_WS_MAC)
    setAttribute(Qt::WA_MacSmallSize);
#endif
    
    //
    //  Create widgets, docks, and dialogs
    //
    mImageView = new QGraphicsView;
    mImageScene = new QGraphicsScene(mImageView);
    mImageView->setScene(mImageScene);
    mImageView->setCacheMode(QGraphicsView::CacheBackground);
    mImageView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
    setCentralWidget(mImageView);
    mImageScene->installEventFilter(this);
    mAboutDialog = new xyscanAbout(this);    
        
    mImageAngle = 0;
    mImageScale = 1;
    
    mCrosshairColor = Qt::red;

    createMenuActions();
    createMenus();
    createStatusBar();
    createDockWindows();
    createMarker();
    createCrosshair();
    createMarkerActions();  
    createHelpBrowser();

    // 
    // Connect remaining signals and slots 
    //
    connect(mLogXRadioButton, SIGNAL(toggled(bool)), this, SLOT(updateWhenAxisScaleChanged()));
    connect(mLogYRadioButton, SIGNAL(toggled(bool)), this, SLOT(updateWhenAxisScaleChanged()));
    connect(mAngleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(rotateImage(double)));
    connect(mScaleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(scaleImage(double)));

    //
    //  Set main window properties
    //
    setWindowTitle("xyscan");
    setWindowIcon(QIcon(QString::fromUtf8(":/images/xyscanIcon.png")));
    QSize s = minimumSizeHint();
    resize(QSize(900, s.height()));
    
    //
    //  Initialize remaining data member
    //
    mCurrentPixmap = 0;
    for (int i=0; i<4; i++)
        mMarkerPixel[i] = mMarkerPlotCoordinate[i] = 0;
    mDataSaved = true;
    mClearHistoryAction->setDisabled(true);

    //
    //  Read the references from file
    //
    loadSettings();
    
    //
    //  Disable all buttons that are useless 
    //  when no image is loaded ...
    //
    mSetLowerXButton->setDisabled(true);
    mSetUpperXButton->setDisabled(true);
    mSetLowerYButton->setDisabled(true);
    mSetUpperYButton->setDisabled(true);
    mLogXRadioButton->setDisabled(true);
    mLogYRadioButton->setDisabled(true);    
    mAngleSpinBox->setDisabled(true);    
    mScaleSpinBox->setDisabled(true);    
    mErrorXModeComboBox->setDisabled(true);
    mErrorYModeComboBox->setDisabled(true);
    mSaveAction->setDisabled(true);
    mPrintAction->setDisabled(true);
    mShowPrecisionAction->setDisabled(true);
    mDeleteLastAction->setDisabled(true);
    mDeleteAllAction->setDisabled(true);
    mEditCommentAction->setDisabled(true);
    mEditCrosshairColorAction->setDisabled(true);

    //
    //  If valid file was given at command line open it now
    //
    if (openFileafterInit) openFromFile(fileToOpen);
}

xyscanWindow::~xyscanWindow() { /* no op */ }

void xyscanWindow::about() 
{
    mAboutDialog->show();
}

void xyscanWindow::addToTable(double x, double y, double dxl, double dxu, double dyl, double dyu)
{
    //
    //  Store data into new table row
    //
    QString str;
    int nrows = mTableWidget->rowCount();
    mTableWidget->insertRow(nrows);
    str.setNum(x);
    mTableWidget->setItem(nrows, 0, new QTableWidgetItem(str));
    mTableWidget->item(nrows, 0)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter); 
    str.setNum(y);
    mTableWidget->setItem(nrows, 1, new QTableWidgetItem(str));
    mTableWidget->item(nrows, 1)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter); 
    str.setNum(dxl);
    mTableWidget->setItem(nrows, 2, new QTableWidgetItem(str));
    mTableWidget->item(nrows, 2)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter); 
    str.setNum(dxu);
    mTableWidget->setItem(nrows, 3, new QTableWidgetItem(str));
    mTableWidget->item(nrows, 3)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter); 
    str.setNum(dyl);
    mTableWidget->setItem(nrows, 4, new QTableWidgetItem(str));
    mTableWidget->item(nrows, 4)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter); 
    str.setNum(dyu);
    mTableWidget->setItem(nrows, 5, new QTableWidgetItem(str));
    mTableWidget->item(nrows, 5)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter); 
    mTableWidget->scrollToBottom();
    mDataSaved = false;    

    //
    //  Enable all buttons that make sense once 
    //  at least one data point is scanned ...
    //
    mSaveAction->setDisabled(false);
    mPrintAction->setDisabled(false);
    mDeleteLastAction->setDisabled(false);
    mDeleteAllAction->setDisabled(false);
}

void xyscanWindow::checkForUpdates()
{
    mUpdater.checkForNewVersion(QUrl("http://rhig.physics.yale.edu/~ullrich/xyscanDistributionPage/xyscanLatestVersion.xml"));
}

void xyscanWindow::clearHistory() 
{
    //
    //  Remove all stored recent files from submenu (File/Open Recent)
    //
    for (int i = 0; i < mRecentFiles.size(); ++i) {
        mRecentFileAction[i]->setVisible(false);
    }
    mRecentFiles.clear();
    mClearHistoryAction->setDisabled(true);
}

void xyscanWindow::closeEvent(QCloseEvent* e)
{
    //
    //  Reimplement, otherwise pressing the x button on the
    //  window frame closes the applications and we have
    //  no chance for storing the settings and checking
    //  for unsaved data.
    //
    finish(); 
    e->ignore();   // exit initiated in finish() or not at all
}

void xyscanWindow::createCoordinateWidget(QWidget *form)
{
    QGridLayout *gridLayout = new QGridLayout(form);
    
    QVBoxLayout *vboxLayout = new QVBoxLayout();
    vboxLayout->setContentsMargins(0, 0, 0, 0);
    QLabel *label_5 = new QLabel(form);    
    vboxLayout->addWidget(label_5);
    QHBoxLayout *hboxLayout = new QHBoxLayout();
    QLabel *label_2 = new QLabel(form);
    hboxLayout->addWidget(label_2);
    
    mPixelXDisplay = new QLineEdit(form);
    mPixelXDisplay->setReadOnly(true);
    mPixelXDisplay->setText(tr("N/A"));
    hboxLayout->addWidget(mPixelXDisplay);
    QLabel *label_1 = new QLabel(form);
    hboxLayout->addWidget(label_1);
    
    mPixelYDisplay = new QLineEdit(form);
    mPixelYDisplay->setReadOnly(true);
    mPixelYDisplay->setText(tr("N/A"));
    hboxLayout->addWidget(mPixelYDisplay);
    vboxLayout->addLayout(hboxLayout);
    QLabel *label_6 = new QLabel(form);
    vboxLayout->addWidget(label_6);
    QHBoxLayout *hboxLayout1 = new QHBoxLayout();
    QLabel *label_3 = new QLabel(form);
    hboxLayout1->addWidget(label_3);
    
    mPlotXDisplay = new QLineEdit(form);
    mPlotXDisplay->setReadOnly(true);
    mPlotXDisplay->setText(tr("N/A"));
    hboxLayout1->addWidget(mPlotXDisplay);
    QLabel *label_4 = new QLabel(form);
    hboxLayout1->addWidget(label_4);
    
    mPlotYDisplay = new QLineEdit(form);
    mPlotYDisplay->setReadOnly(true);
    mPlotYDisplay->setText(tr("N/A"));
    hboxLayout1->addWidget(mPlotYDisplay);
    vboxLayout->addLayout(hboxLayout1);
    
    gridLayout->addLayout(vboxLayout, 0, 0, 1, 1);    
    QSpacerItem *spacerItem = new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding);
    gridLayout->addItem(spacerItem, 1, 0, 1, 1);
    
    //
    //  Set text of all labels
    //
    label_5->setText(tr("Pixel:"));
    label_2->setText(tr("x:"));
    label_1->setText(tr("y:"));
    label_6->setText(tr("Plot Coordinates:"));
    label_3->setText(tr("x:"));
    label_4->setText(tr("y:"));
}

void xyscanWindow::createCrosshair()
{
    QRectF r = mImageScene->sceneRect();
    
    mCrosshairH = new QGraphicsLineItem(-5000, 0, 5000, 0); // big enough for all evantualities
    mCrosshairH->setZValue(2);
    mCrosshairH->setPen(QPen(mCrosshairColor));
    mCrosshairH->setVisible(false);
    mImageScene->addItem(mCrosshairH);
    
    mCrosshairV = new QGraphicsLineItem(0, -5000, 0, 5000);
    mCrosshairV->setZValue(2);
    mCrosshairV->setPen(QPen(mCrosshairColor));
    mCrosshairV->setVisible(false);
    mImageScene->addItem(mCrosshairV);
    
    mImageScene->setSceneRect(r); // don't alter the size of the scene yet,
                                  // otherwise scrollbars appear for an empty canvas
}

void xyscanWindow::createDockWindows()
{
    mTransformDock = new QDockWidget(tr("Plot Adjustments"), this);
    mTransformDock->setVisible(false);
    mTransformDock->setFloating(true);
    mTransformDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
    QWidget *transformDockContents = new QWidget();
    createTransformWidget(transformDockContents);
    mTransformDock->setWidget(transformDockContents);
    addDockWidget(Qt::RightDockWidgetArea, mTransformDock);
    mTransformDock->toggleViewAction()->setText(tr("Plot &Adjustments"));
    mViewMenu->addAction(mTransformDock->toggleViewAction());
    
    mCoordinateDock = new QDockWidget(tr("Coordinates"), this);
    mCoordinateDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
    QWidget *coordinateDockContents = new QWidget();
    createCoordinateWidget(coordinateDockContents);
    mCoordinateDock->setWidget(coordinateDockContents);
    addDockWidget(Qt::RightDockWidgetArea, mCoordinateDock);
    mCoordinateDock->toggleViewAction()->setText(tr("&Coordinate Display"));
    mViewMenu->addAction(mCoordinateDock->toggleViewAction());
    mCoordinateDock->setVisible(true);
    
    mSettingsDock = new QDockWidget(tr("Settings"), this);
    mSettingsDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
    QWidget *settingsDockContents = new QWidget();
    createSettingsWidget(settingsDockContents);
    mSettingsDock->setWidget(settingsDockContents);        
    addDockWidget(Qt::RightDockWidgetArea, mSettingsDock);
    mSettingsDock->toggleViewAction()->setMenuRole(QAction::NoRole); // Mac puts "seetings" otherwise in application menu
    mSettingsDock->toggleViewAction()->setText(tr("&Settings"));
    mViewMenu->addAction(mSettingsDock->toggleViewAction());
    mSettingsDock->setVisible(true);

    mTableDock = new QDockWidget(tr("Data Table"), this);
    mTableDock->setVisible(false);
    mTableDock->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea);
    QWidget *tableDockContents = new QWidget();
    createTableWidget(tableDockContents);
    mTableDock->setWidget(tableDockContents);        
    addDockWidget(Qt::BottomDockWidgetArea, mTableDock);
    mTableDock->toggleViewAction()->setText(tr("&Data Table"));
    mViewMenu->addAction(mTableDock->toggleViewAction());
}

void xyscanWindow::createMarker()
{
    QColor color = Qt::gray;
    mMarker[mXLower] = new QGraphicsLineItem(0, -5000, 0, 5000); 
    mMarker[mXLower]->setPen(QPen(color));
    mMarker[mXLower]->setZValue(1);
    mImageScene->addItem(mMarker[mXLower]);
    mMarker[mXLower]->setVisible(false);

    mMarker[mXUpper] = new QGraphicsLineItem(0, -5000, 0, 5000); 
    mMarker[mXUpper]->setPen(QPen(color));
    mMarker[mXUpper]->setZValue(1);
    mImageScene->addItem(mMarker[mXUpper]);
    mMarker[mXUpper]->setVisible(false);

    mMarker[mYLower] = new QGraphicsLineItem(-5000, 0, 5000, 0); 
    mMarker[mYLower]->setPen(QPen(color));
    mMarker[mYLower]->setZValue(1);
    mImageScene->addItem(mMarker[mYLower]);
    mMarker[mYLower]->setVisible(false);
    
    mMarker[mYUpper] = new QGraphicsLineItem(-5000, 0, 5000, 0); 
    mMarker[mYUpper]->setPen(QPen(color));
    mMarker[mYUpper]->setZValue(1);
    mImageScene->addItem(mMarker[mYUpper]);
    mMarker[mYUpper]->setVisible(false);
          
    QPixmap imgpm(marker_cross_xpm);
    mPointMarker = new QGraphicsPixmapItem(imgpm, 0);
    mImageScene->addItem(mPointMarker);
    mPointMarker->setZValue(1);
    mPointMarker->setVisible(false);
    
    mImageScene->setSceneRect(0, 0, 200, 200); // avoid sliders at beginning
}

void xyscanWindow::createHelpBrowser()
{
    mHelpBrowser = 0;

    QString path = qApp->applicationDirPath() + "/docs";
    QDir dir(path);
    if (!dir.exists()) {
        path = qApp->applicationDirPath() + "/../docs";
        dir.setPath(path);
    }
#if defined(Q_OS_MAC)   
    if (!dir.exists()) {
        path = qApp->applicationDirPath() + "/../Resources/docs";
        dir.setPath(path);
    }
#endif
    //
    //  For developing on Mac/Xcode only: avoids having to copy
    //  the docs into the build substructure.
    //
    if (!dir.exists()) { 
        path = "/Users/ullrich/Documents/Projects/xyscan/docs";
        dir.setPath(path);
    }
    if (!dir.exists()) {
        QMessageBox::warning( 0, "xyscan",
                             tr("Cannot find the directory holding the documentation (docs). "
                                "No online help will be available. "
                                "Check your installation and reinstall if necessary."));
        return;
    }
    
    //
    //  Now add the subdirectory for the appropriate
    //  language (so far English and French only).
    //
    QLocale local = QLocale::system();
    switch (local.language()) {
        case QLocale::French: 
            path += "/fr";
            break;
        case QLocale::English:
        default:
            path += "/en";
            break;
    }
    
    QString descFile = path + "/doc.index";
    
    QFileInfo file(descFile);
    if (file.exists())
        mHelpBrowser = new xyscanHelpBrowser(descFile, path);
    else {
        QMessageBox::warning( 0, "xyscan",
                              tr("Cannot find the index descriptor to setup the documentation. "
                              "No online help will be available. "
                              "Check your installation and reinstall if necessary."));
    }
}

void xyscanWindow::createMenus()
{
    mFileMenu = menuBar()->addMenu(tr("&File"));
    mFileMenu->addAction(mOpenAction);
    mRecentFilesMenu = mFileMenu->addMenu(tr("Open &Recent")); // submenu
    for (int i = 0; i < mMaxRecentFiles; ++i)
        mRecentFilesMenu->addAction(mRecentFileAction[i]);
    mRecentFilesMenu->addSeparator();
    mRecentFilesMenu->addAction(mClearHistoryAction);
    mFileMenu->addAction(mSaveAction);
    mFileMenu->addSeparator();
    mFileMenu->addAction(mPrintAction);
#if !defined(Q_WS_MAC)
    mFileMenu->addSeparator();
#endif
    mFileMenu->addAction(mFinishAction);

    
    mEditMenu = menuBar()->addMenu(tr("&Edit"));
    mEditMenu->addAction(mEditCrosshairColorAction); 
    mEditMenu->addSeparator();
    mEditMenu->addAction(mPasteImageAction); 
    mEditMenu->addSeparator();
    mEditMenu->addAction(mDeleteLastAction);
    mEditMenu->addAction(mDeleteAllAction);
    mEditMenu->addSeparator();
    mEditMenu->addAction(mEditCommentAction);
    
    mViewMenu = menuBar()->addMenu(tr("&View"));
    mViewMenu->addAction(mShowPrecisionAction); 
    mViewMenu->addSeparator();
    // dock windows toggle menues added later in createDockWindows()
    
    mHelpMenu = menuBar()->addMenu(tr("&Help"));
    mHelpMenu->addAction(mCheckForUpdatesAction);
    mHelpMenu->addSeparator();
    mHelpMenu->addAction(mShowTooltipsAction);
    mHelpMenu->addAction(mHelpAction);
#if !defined(Q_WS_MAC)
    mHelpMenu->addSeparator();
#endif    
    mHelpMenu->addAction(mAboutAction);  // In application menu on MacOS/X
}

void xyscanWindow::createMenuActions()
{
    mOpenAction = new QAction(tr("&Open..."), this);
    mOpenAction->setShortcut(QKeySequence::Open);
    connect(mOpenAction, SIGNAL(triggered()), this, SLOT(open()));
    
    for (int i = 0; i < mMaxRecentFiles; ++i) {
        mRecentFileAction[i] = new QAction(this);
        mRecentFileAction[i]->setVisible(false);
        connect(mRecentFileAction[i], SIGNAL(triggered()), this, SLOT(openRecent()));
    }
    
    mClearHistoryAction = new QAction(tr("&Clear History"), this);
    connect(mClearHistoryAction, SIGNAL(triggered()), this, SLOT(clearHistory()));
    
    mSaveAction = new QAction(tr("&Save..."), this);
    mSaveAction->setShortcut(QKeySequence::Save); // Qt::CTRL + Qt::Key_S);
    connect(mSaveAction, SIGNAL(triggered()), this, SLOT(save()));
    
    mPrintAction = new QAction(tr("&Print..."), this);
    mPrintAction->setShortcut(QKeySequence::Print);
    connect(mPrintAction, SIGNAL(triggered()), this, SLOT(print()));
    
    mFinishAction = new QAction(tr("&Quit xyscan"), this);
    mFinishAction->setShortcut(QKeySequence::Quit);
    connect(mFinishAction, SIGNAL(triggered()), this, SLOT(finish()));
    
    mPasteImageAction = new QAction(tr("&Paste Image"), this);
    mPasteImageAction->setShortcut(QKeySequence::Paste);
    connect(mPasteImageAction, SIGNAL(triggered()), this, SLOT(pasteImage()));
    
    mEditCrosshairColorAction = new QAction(tr("Crosshairs Color..."), this);
    connect(mEditCrosshairColorAction, SIGNAL(triggered()), this, SLOT(editCrosshairColor()));
    
    mDeleteLastAction = new QAction(tr("Delete &Last"), this);
    mDeleteLastAction->setShortcut(QKeySequence::Undo);
    connect(mDeleteLastAction, SIGNAL(triggered()), this, SLOT(deleteLast()));
    
    mDeleteAllAction = new QAction(tr("Delete &All"), this);
    connect(mDeleteAllAction, SIGNAL(triggered()), this, SLOT(deleteAll()));
    
    mEditCommentAction = new QAction(tr("&Comment..."), this);
    mEditCommentAction->setShortcut(QKeySequence::AddTab);
    connect(mEditCommentAction, SIGNAL(triggered()), this, SLOT(editComment()));

    mShowPrecisionAction = new QAction(tr("&Current Precision"), this);
    mShowPrecisionAction->setShortcut(Qt::CTRL + Qt::Key_I);
    connect(mShowPrecisionAction, SIGNAL(triggered()), this, SLOT(showPrecision()));
    
    mAboutAction = new QAction(tr("&About xyscan"), this);
    connect(mAboutAction, SIGNAL(triggered()), this, SLOT(about()));
#if defined(Q_WS_MAC)
    mAboutAction->setMenuRole(QAction::AboutRole);
#endif
    
    mHelpAction = new QAction(tr("&Documentation"), this);
    connect(mHelpAction, SIGNAL(triggered()), this, SLOT(help()));

    mShowTooltipsAction = new QAction(tr("&Tool Tips"), this);
    mShowTooltipsAction->setCheckable(true);
    mShowTooltipsAction->setChecked(false);
    connect(mShowTooltipsAction, SIGNAL(toggled(bool)), this, SLOT(showTooltips()));
    
    mCheckForUpdatesAction = new QAction(tr("&Check For Updates ..."), this);
    connect(mCheckForUpdatesAction, SIGNAL(triggered()), this, SLOT(checkForUpdates()));
}

void xyscanWindow::createMarkerActions()
{
    mSetLowerXAction = new QAction(this);
    mSetUpperXAction = new QAction(this);
    mSetLowerYAction = new QAction(this);
    mSetUpperYAction = new QAction(this);
    connect(mSetLowerXButton, SIGNAL(clicked()), this, SLOT(setLowerXButton()));
    connect(mSetUpperXButton, SIGNAL(clicked()), this, SLOT(setUpperXButton()));
    connect(mSetLowerYButton, SIGNAL(clicked()), this, SLOT(setLowerYButton()));
    connect(mSetUpperYButton, SIGNAL(clicked()), this, SLOT(setUpperYButton()));
    
    (void) new QShortcut(Qt::CTRL + Qt::Key_1, this, SLOT(setLowerXButton()));    
    (void) new QShortcut(Qt::CTRL + Qt::Key_2, this, SLOT(setUpperXButton()));    
    (void) new QShortcut(Qt::CTRL + Qt::Key_3, this, SLOT(setLowerYButton()));    
    (void) new QShortcut(Qt::CTRL + Qt::Key_4, this, SLOT(setUpperYButton()));    
}

void xyscanWindow::createStatusBar()
{
    statusBar()->showMessage(tr("Ready"));
}

void xyscanWindow::createSettingsWidget(QWidget *form)
{        
    QVBoxLayout *vboxLayout = new QVBoxLayout(form);
    
    //
    //  Markers Group
    //
    QGroupBox *markersGroupBox = new QGroupBox(form);
    QGridLayout *gridLayout = new QGridLayout(markersGroupBox);
    QHBoxLayout *hboxLayout = new QHBoxLayout();
    QLabel *label_1 = new QLabel(markersGroupBox);
    hboxLayout->addWidget(label_1);
    mSetLowerXButton = new QPushButton(markersGroupBox);
    hboxLayout->addWidget(mSetLowerXButton);    
    mLowerXValueField = new QLineEdit(markersGroupBox);
    mLowerXValueField->setReadOnly(true);
    hboxLayout->addWidget(mLowerXValueField);
    gridLayout->addLayout(hboxLayout, 0, 0, 1, 1);
    QHBoxLayout *hboxLayout1 = new QHBoxLayout();
    QLabel *label_2 = new QLabel(markersGroupBox);
    hboxLayout1->addWidget(label_2);
    mSetUpperXButton = new QPushButton(markersGroupBox);
    hboxLayout1->addWidget(mSetUpperXButton);
    mUpperXValueField = new QLineEdit(markersGroupBox);
    mUpperXValueField->setReadOnly(true);
    hboxLayout1->addWidget(mUpperXValueField);
    gridLayout->addLayout(hboxLayout1, 1, 0, 1, 1);
    QHBoxLayout *hboxLayout2 = new QHBoxLayout();
    QLabel *label_3 = new QLabel(markersGroupBox);
    hboxLayout2->addWidget(label_3);
    mSetLowerYButton = new QPushButton(markersGroupBox);
    hboxLayout2->addWidget(mSetLowerYButton);
    mLowerYValueField = new QLineEdit(markersGroupBox);
    mLowerYValueField->setReadOnly(true);
    hboxLayout2->addWidget(mLowerYValueField);
    gridLayout->addLayout(hboxLayout2, 2, 0, 1, 1);
    QHBoxLayout *hboxLayout3 = new QHBoxLayout();
    QLabel *label_4 = new QLabel(markersGroupBox);
    hboxLayout3->addWidget(label_4);
    mSetUpperYButton = new QPushButton(markersGroupBox);
    hboxLayout3->addWidget(mSetUpperYButton);
    mUpperYValueField = new QLineEdit(markersGroupBox);
    mUpperYValueField->setReadOnly(true);
    hboxLayout3->addWidget(mUpperYValueField);
    gridLayout->addLayout(hboxLayout3, 3, 0, 1, 1);
    vboxLayout->addWidget(markersGroupBox);
    
    //
    //  Axis mode (lin/log) and rotate & scale of plot
    //
    QGroupBox *axisGroupBox = new QGroupBox(form);
    
    mLogXRadioButton = new QRadioButton(axisGroupBox);
    mLogXRadioButton->setAutoExclusive(false);
    mLogYRadioButton = new QRadioButton(axisGroupBox);
    mLogYRadioButton->setAutoExclusive(false);  // not a toggle radio button
    
    QGridLayout *gridLayout1 = new QGridLayout(axisGroupBox);
    gridLayout1->addWidget(mLogXRadioButton, 1, 1);
    gridLayout1->addWidget(mLogYRadioButton, 1, 2);

    vboxLayout->addWidget(axisGroupBox);

    //
    //  Errors Group
    //
    QGroupBox *errorsGroupBox = new QGroupBox(form);
    QGridLayout *gridLayout2 = new QGridLayout(errorsGroupBox);
    QHBoxLayout *hboxLayout5 = new QHBoxLayout();
    QLabel *label_5 = new QLabel(errorsGroupBox);
    hboxLayout5->addWidget(label_5);
    mErrorXModeComboBox = new QComboBox(errorsGroupBox);
    hboxLayout5->addWidget(mErrorXModeComboBox);
    QSpacerItem *spacerItem1 = new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum);
    hboxLayout5->addItem(spacerItem1);
    gridLayout2->addLayout(hboxLayout5, 0, 0, 1, 1);
    QHBoxLayout *hboxLayout6 = new QHBoxLayout();
    QLabel *label_7 = new QLabel(errorsGroupBox);
    hboxLayout6->addWidget(label_7);
    mErrorYModeComboBox = new QComboBox(errorsGroupBox);
    hboxLayout6->addWidget(mErrorYModeComboBox);
    QSpacerItem *spacerItem2 = new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum);
    hboxLayout6->addItem(spacerItem2);
    gridLayout2->addLayout(hboxLayout6, 1, 0, 1, 1);
    vboxLayout->addWidget(errorsGroupBox);
    QSpacerItem *spacerItem3 = new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding);
    vboxLayout->addItem(spacerItem3);
    
    //
    //  Set text of all labels
    //
    int strWidth = label_1->fontMetrics().width(tr("Upper X:"));
    label_1->setText(tr("Lower X:"));
    label_1->setFixedWidth(strWidth);
    mSetLowerXButton->setText(tr("Set"));
    mLowerXValueField->setText(tr("undefined"));
    label_2->setText(tr("Upper X:"));
    label_2->setFixedWidth(strWidth);
    mSetUpperXButton->setText(tr("Set"));
    mUpperXValueField->setText(tr("undefined"));
    label_3->setText(tr("Lower Y:"));
    label_3->setFixedWidth(strWidth);
    mSetLowerYButton->setText(tr("Set"));
    mLowerYValueField->setText(tr("undefined"));
    label_4->setText(tr("Upper Y:"));
    label_4->setFixedWidth(strWidth);
    mSetUpperYButton->setText(tr("Set"));
    mUpperYValueField->setText(tr("undefined"));
    axisGroupBox->setTitle(tr("Axis:"));
    mLogXRadioButton->setText(tr("Log X"));
    mLogYRadioButton->setText(tr("Log Y"));
    errorsGroupBox->setTitle(tr("Error Scan Mode:"));
    label_5->setText(tr("X-Error:"));
    mErrorXModeComboBox->clear();
    mErrorXModeComboBox->insertItems(0, QStringList()
                                     << tr("No Scan")
                                     << tr("Asymmetric")
                                     << tr("Symmetric (mean)")
                                     << tr("Symmetric (max)")
                                     );
    label_7->setText(tr("Y-Error:"));
    mErrorYModeComboBox->clear();
    mErrorYModeComboBox->insertItems(0, QStringList()
                                     << tr("No Scan")
                                     << tr("Asymmetric")
                                     << tr("Symmetric (mean)")
                                     << tr("Symmetric (max)")
                                     );
}

void xyscanWindow::createTableWidget(QWidget *form) 
{
    QGridLayout *gridLayout = new QGridLayout(form);
    mTableWidget = new QTableWidget(form);
    mTableWidget->setAutoScroll(true);
    mTableWidget->setTextElideMode(Qt::ElideMiddle);
    mTableWidget->setShowGrid(true);
    gridLayout->addWidget(mTableWidget, 0, 0, 1, 1);        
    mTableWidget->setColumnCount(6);
    mTableWidget->setSortingEnabled(false); // no sorting do it manually
    
    QTableWidgetItem *colItem = new QTableWidgetItem();
    colItem->setText(tr("x"));
    mTableWidget->setHorizontalHeaderItem(0, colItem);        
    QTableWidgetItem *colItem1 = new QTableWidgetItem();
    colItem1->setText(tr("y"));
    mTableWidget->setHorizontalHeaderItem(1, colItem1);
    QTableWidgetItem *colItem2 = new QTableWidgetItem();
    colItem2->setText(tr("-dx"));
    mTableWidget->setHorizontalHeaderItem(2, colItem2);
    QTableWidgetItem *colItem3 = new QTableWidgetItem();
    colItem3->setText(tr("+dx"));
    mTableWidget->setHorizontalHeaderItem(3, colItem3);
    QTableWidgetItem *colItem4 = new QTableWidgetItem();
    colItem4->setText(tr("-dy"));
    mTableWidget->setHorizontalHeaderItem(4, colItem4);
    QTableWidgetItem *colItem5 = new QTableWidgetItem();
    colItem5->setText(tr("+dy"));
    mTableWidget->setHorizontalHeaderItem(5, colItem5);
    
    mTableWidget->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
    mTableWidget->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
    
    mTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); // needed ?       
}

void xyscanWindow::createTransformWidget(QWidget *form) 
{
    QGridLayout *gridLayout = new QGridLayout(form);
   
    // info label on top
    mPlotInfoLabel = new QLabel(form);
    mPlotInfoLabel->setText(tr("No plot loaded"));
    gridLayout->addWidget(mPlotInfoLabel, 0, 0, 1, -1);
    QFrame *line = new QFrame(form);
    line->setFrameShape(QFrame::HLine);
    line->setFrameShadow(QFrame::Sunken);
    gridLayout->addWidget(line, 1, 0, 1, -1);
        
    // angle/rotate spin box with label
    QHBoxLayout *hboxLayoutAngle = new QHBoxLayout();
    QLabel* labelAngle = new QLabel(form);
    labelAngle->setText(tr("Rotate:"));
    hboxLayoutAngle->addWidget(labelAngle);

    mAngleSpinBox = new QDoubleSpinBox(form);
    mAngleSpinBox->setMinimum(-90);
    mAngleSpinBox->setMaximum(180);
    mAngleSpinBox->setSingleStep(0.05);
    mAngleSpinBox->setAccelerated(true);
    mAngleSpinBox->setValue(mImageAngle);
    hboxLayoutAngle->addWidget(mAngleSpinBox);    

    QSpacerItem *horizontalSpacer = new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum);
    hboxLayoutAngle->addItem(horizontalSpacer);
    gridLayout->addLayout(hboxLayoutAngle, 2, 0, 1, 1);
    
    // scale spin box with label
    QHBoxLayout *hboxLayoutScale = new QHBoxLayout();
    QLabel* labelScale = new QLabel(form);
    labelScale->setText(tr("Scale:"));
    hboxLayoutScale->addWidget(labelScale);

    mScaleSpinBox = new QDoubleSpinBox(form);
    mScaleSpinBox->setMinimum(0.10);
    mScaleSpinBox->setMaximum(10);
    mScaleSpinBox->setSingleStep(0.05);
    mScaleSpinBox->setAccelerated(true);
    mScaleSpinBox->setValue(mImageScale);
    hboxLayoutScale->addWidget(mScaleSpinBox);        
  
    QSpacerItem *horizontalSpacer2 = new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum);    
    hboxLayoutScale->addItem(horizontalSpacer2);
    gridLayout->addLayout(hboxLayoutScale, 2, 1, 1, 1);
    
    QSpacerItem *horizontalSpacer3 = new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum);    
    gridLayout->addItem(horizontalSpacer3, 1, 2, 1, 1);
    QSpacerItem *verticalSpacer = new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding);
    gridLayout->addItem(verticalSpacer, 3, 0, 1, 1);
}

void xyscanWindow::deleteLast() 
{
    int nrows = mTableWidget->rowCount();
    if (nrows) {
        mTableWidget->removeRow(nrows);
        mTableWidget->setRowCount(nrows-1);
    }
}

void xyscanWindow::deleteAll() 
{
    mTableWidget->clearContents();
    mTableWidget->setRowCount(0);
}

void xyscanWindow::editComment()
{
    bool ok;
    QString txt = QInputDialog::getText(this, tr("xyscan - Comment"),
                                        tr("The following comment will be written "
                                           "together with the data when saved to file or when printed:\t"), 
                                        QLineEdit::Normal, mUserComment, &ok);
    if (ok) mUserComment = txt;
}

void xyscanWindow::editCrosshairColor()
{
    QColor newColor = QColorDialog::getColor (mCrosshairColor, this);
    if (newColor.isValid()) 
        mCrosshairColor = newColor;
    mCrosshairV->setPen(QPen(mCrosshairColor));
    mCrosshairH->setPen(QPen(mCrosshairColor));
}

void xyscanWindow::ensureCursorVisible()
{
    QRectF rect(mCrosshairH->pos().x()-25, mCrosshairV->pos().y()-25, 50, 50);
    mImageView->ensureVisible(rect);
}

bool xyscanWindow::eventFilter(QObject *obj, QEvent *event)
{
    //
    // If method retuns true  the event stops here otherwise its
    // passed down. Here we catch all dispatched events from the
    // image scene (canvas). 
    // 
    static bool leftMousePressedNearCrosshair = false;
    QGraphicsSceneMouseEvent *mevt;
    
    if (obj == mImageScene) {
        if (event->type() == QEvent::GraphicsSceneDrop) {
            handleDropEvent(dynamic_cast<QGraphicsSceneDragDropEvent*>(event));  // to complex to handle here
            return true;
        }
        else if (event->type() == QEvent::KeyPress) {
            handleKeyEvent(dynamic_cast<QKeyEvent*>(event));  // to complex to handle here
            return true;
        } 
        else if (event->type() == QEvent::GraphicsSceneMousePress) {
            mevt = dynamic_cast<QGraphicsSceneMouseEvent*>(event);  
            update();
            QPointF mpos = mevt->scenePos();
            QPointF cpos(mCrosshairH->pos().x(), mCrosshairV->pos().y());
            double d = (mpos.x()-cpos.x())*(mpos.x()-cpos.x())
                + (mpos.y()-cpos.y())*(mpos.y()-cpos.y());
            if (d < 50) leftMousePressedNearCrosshair = true;
            return true;
        }
        else if (event->type() == QEvent::GraphicsSceneMouseRelease) {
            leftMousePressedNearCrosshair = false;
            return true;
        }        
        // else if (event->type() == QEvent::GraphicsSceneMouseMove && leftMousePressedNearCrosshair) {
        // improvement from Valerie Fine (Oct 28, 2008)
        else if ( (event->type() == QEvent::GraphicsSceneMouseMove && leftMousePressedNearCrosshair ) || event->type() == QEvent::GraphicsSceneMouseDoubleClick) {
            mevt = dynamic_cast<QGraphicsSceneMouseEvent*>(event);    
            //
            //  In principle straighforward except for the case
            //  where we scan for errors (x, y or both) and one
            //  line is hidden. The idea is that for an x-error scan
            //  mCrosshairH is hidden and stays at const y and
            //  for an y error scan mCrosshairV is hidden and stays
            //  at const x. So when lines are hidden (which is
            //  only the case during the error scan) we have to keep
            //  one coordinate const.
            //
            if (mCrosshairH->isVisible() && mCrosshairV->isVisible()) {
                mCrosshairH->setPos(mevt->scenePos());
                mCrosshairV->setPos(mevt->scenePos());
            }
            else if (mCrosshairH->isVisible() && !mCrosshairV->isVisible()) {
                QPointF pos = QPointF(mCrosshairV->pos().x(), mevt->scenePos().y());
                mCrosshairV->setPos(pos);
                mCrosshairH->setPos(pos);
            }
            else if (!mCrosshairH->isVisible() && mCrosshairV->isVisible()) {
                QPointF pos(mevt->scenePos().x(), mCrosshairH->pos().y());
                mCrosshairH->setPos(pos);
                mCrosshairV->setPos(pos);
            }
            updatePixelDisplay();
            updatePlotCoordinateDisplay();
            ensureCursorVisible();
            return true;
        }
        else
            return false;
    }
    return QObject::eventFilter(obj, event);  // standard event processing
}

void xyscanWindow::finish() 
{
    //
    //  Gracefully end xyscan. 
    //  Check for unsaved data and write settings/preferences.
    //
    while (mCurrentPixmap && mTableWidget->rowCount() && !mDataSaved) {
        int ret = QMessageBox::warning(this, "xyscan",
                                       tr("You have unsaved data. Quitting now "
                                          "will cause loss of scanned data.\n"
                                          "Do you want to save the data?"),
                                       QMessageBox::Save | QMessageBox::Discard |
                                       QMessageBox::Cancel, QMessageBox::Save);
        if (ret == QMessageBox::Save)
            save();
        else if (ret == QMessageBox::Cancel)
            return;
        else 
            break;
    }
    
    writeSettings();
    QApplication::exit(0);
}

void xyscanWindow::handleDropEvent(QGraphicsSceneDragDropEvent* event)
{
    //
    //  This deals with the dropping of files/desktop-icons onto
    //  the xyscan canvas. Easier than I thought.
    //
    if (!event->mimeData()->hasUrls()) return;
    
    QList<QUrl> url = event->mimeData()->urls();
    QString filename = url[0].toLocalFile();
    openFromFile(filename);
}

void xyscanWindow::handleKeyEvent(QKeyEvent* k)
{
    //
    //  This is where most of the action happens.
    //  Since the logic is really mind-blowing,
    //  especially for the x and y-error scan
    //  a state machine is used which determines
    //  what is allowed at which point.
    //
    static QPointF originalHPosition;
    static QPointF originalVPosition;
    static QPointF xy(0,0);
    static QPointF errorX(0,0);
    static QPointF errorY(0,0);
    QPointF point;
    
    static bool previousShiftPressed = false;
    static int  previousKey  = 0;
    bool shiftPressed = false;
    int  key = 0;
    
    //
    //  There's a slight problem in Qt when Shift+arrow
    //  is pressed continuously: the Shift key gets lossed.
    //  We repair that by checking for 'AutoRepeat' and
    //  remembering the Shift key state before 'AutoRepeat'
    //  was signaled.
    //
    if (k->isAutoRepeat() && !(k->key() == Qt::Key_Space)) {
        shiftPressed = previousShiftPressed;
        key = previousKey;
    }
    else {
        shiftPressed = k->modifiers() & Qt::ShiftModifier;
        key = k->key();
    }
    
    previousShiftPressed = shiftPressed;
    previousKey = key;
    
    double dx_step = 1;        // simple move (arrow)
    double dy_step = 1;
    double dx_jump = 10;       // fast (shift+arrow)
    double dy_jump = 10;
    
    double dx = shiftPressed ? dx_jump : dx_step;
    double dy = shiftPressed ? dy_jump : dy_step;
    
    //  Pass settings (x-, y-error scan) to the state machine.
    mStateMachine.setScanErrorXSelected(mErrorXModeComboBox->currentIndex());
    mStateMachine.setScanErrorYSelected(mErrorYModeComboBox->currentIndex());
    
    switch(key) {
        case Qt::Key_Left:
            if (mCrosshairV && mStateMachine.allowKeyLeft()) {
                mCrosshairV->setPos(mCrosshairV->pos() + QPointF(-dx, 0));
                mCrosshairH->setPos(mCrosshairH->pos() + QPointF(-dx, 0));
            }
            break;
        case Qt::Key_Right:
            if (mCrosshairV && mStateMachine.allowKeyRight()) {
                mCrosshairV->setPos(mCrosshairV->pos() + QPointF(dx, 0));
                mCrosshairH->setPos(mCrosshairH->pos() + QPointF(dx, 0));
            }
            break;
        case Qt::Key_Up:
            if (mCrosshairH && mStateMachine.allowKeyUp()) {
                mCrosshairV->setPos(mCrosshairV->pos() + QPointF(0, -dy));
                mCrosshairH->setPos(mCrosshairH->pos() + QPointF(0, -dy));
            }
            break;
        case Qt::Key_Down:
            if (mCrosshairH && mStateMachine.allowKeyDown()) {
                mCrosshairV->setPos(mCrosshairV->pos() + QPointF(0, dy));
                mCrosshairH->setPos(mCrosshairH->pos() + QPointF(0, dy));
            }
            break;
        case Qt::Key_Space:
            if (readyForScan()) {
                if (mStateMachine.allowXYScan()) {
                    xy = scan();
                    void updatePixelDisplay();
                    void updatePlotCoordinateDisplay();
                    originalHPosition  = mCrosshairH->pos(); // later we return to this point after errors are scanned
                    originalVPosition  = mCrosshairV->pos();
                    mStateMachine.setXYScanDone(true);
                }
                
                if (mStateMachine.disableSettingsMenu()) {
                    mSetLowerXButton->setDisabled(true);
                    mSetUpperXButton->setDisabled(true);
                    mSetLowerYButton->setDisabled(true);
                    mSetUpperYButton->setDisabled(true);                
                    mErrorXModeComboBox->setDisabled(true);
                    mErrorYModeComboBox->setDisabled(true);
                    mLogXRadioButton->setDisabled(true);
                    mLogYRadioButton->setDisabled(true);
                }
                
                if (mStateMachine.doPrepareErrorXScan()) {
                    mCrosshairH->hide();
                    //
                    //  Set a marker (cross) where the original point was.
                    //  This helps remembering the point in case there are
                    //  many close by points. Removed once the errors are
                    //  all scanned.
                    //
                    QPointF pos(originalVPosition.x(), originalHPosition.y());
                    pos -= QPointF(mPointMarker->pixmap().width()/2, mPointMarker->pixmap().height()/2);
                    mPointMarker->setPos(pos);
                    mPointMarker->show();
                    
                    statusBar()->showMessage(tr("Scan x-error (-dx): move crosshair to end of left error bar and press [space]"));
                    mStateMachine.setErrorXScanPrepared(true);
                    break;
                }
                
                if (mStateMachine.allowScanXLeft()) {
                    point = scan();
                    void updatePixelDisplay();
                    void updatePlotCoordinateDisplay();
                    errorX.setX(point.x() - xy.x());
                    statusBar()->showMessage(tr("Scan x-error (+dx): move crosshair to end of right error bar and press [space]"));
                    mStateMachine.setScanXLeftDone(true);
                    break;
                }
                
                if (mStateMachine.allowScanXRight()) {
                    point = scan();
                    void updatePixelDisplay();
                    void updatePlotCoordinateDisplay();
                    errorX.setY(point.x() - xy.x());
                    point = errorX;
                    if (point.x() < 0) {
                        errorX.setX(fabs(point.x()));
                        errorX.setY(point.y());
                    }
                    else {
                        errorX.setX(fabs(point.y()));
                        errorX.setY(point.x());
                    }
                    mStateMachine.setScanXRightDone(true);
                }
                
                if (mStateMachine.removeErrorXScanSetup()) {
                    mPointMarker->hide();
                    mCrosshairH->show();
                    mCrosshairH->setPos(originalHPosition);
                    mCrosshairV->setPos(originalVPosition);
                    point = scan();
                    void updatePixelDisplay();
                    void updatePlotCoordinateDisplay();
                    mStateMachine.setErrorXScanSetupRemoved(true);
                }                
                
                if (mStateMachine.allowScanYLower()) {
                    point = scan();
                    void updatePixelDisplay();
                    void updatePlotCoordinateDisplay();
                    errorY.setX(point.y() - xy.y());
                    statusBar()->showMessage(tr("Scan y-error (+dy): move crosshair to end of upper error bar and press [space]"));
                    mStateMachine.setScanYLowerDone(true);
                    break;
                }
                
                if (mStateMachine.allowScanYUpper()) {
                    point = scan();
                    void updatePixelDisplay();
                    void updatePlotCoordinateDisplay();
                    errorY.setY(point.y() - xy.y());
                    point = errorY;
                    if (point.x() < 0) {
                        errorY.setX(fabs(point.x()));
                        errorY.setY(point.y());
                    }
                    else {
                        errorY.setX(fabs(point.y()));
                        errorY.setY(point.x());
                    }
                    mStateMachine.setScanYUpperDone(true);
                }
                
                if (mStateMachine.doPrepareErrorYScan()) {
                    mCrosshairV->hide();
                    //
                    //  Set a marker (cross) where the original point was.
                    //  This helps remembering the point in case there are
                    //  many close by points. Removed once the errors are
                    //  all scanned.
                    //
                    QPointF pos(originalVPosition.x(), originalHPosition.y());
                    pos -= QPointF(mPointMarker->pixmap().width()/2, 
                                   mPointMarker->pixmap().height()/2);
                    mPointMarker->setPos(pos);
                    mPointMarker->show();
                    
                    statusBar()->showMessage(tr("Scan y-error (-dy): "
                                                "move crosshair to end of lower "
                                                "error bar and press [space]"));
                    mStateMachine.setErrorYScanPrepared(true);
                    break;
                }
                
                if (mStateMachine.removeErrorYScanSetup()) {
                    mPointMarker->hide();
                    mCrosshairV->show();
                    mCrosshairV->setPos(originalVPosition);
                    mCrosshairH->setPos(originalHPosition);
                    point = scan();
                    void updatePixelDisplay();
                    void updatePlotCoordinateDisplay();
                    mStateMachine.setErrorYScanSetupRemoved(true);
                }
                
                if (mStateMachine.enableSettingsMenu()) {
                    mSetLowerXButton->setDisabled(false);
                    mSetUpperXButton->setDisabled(false);
                    mSetLowerYButton->setDisabled(false);
                    mSetUpperYButton->setDisabled(false);                
                    mErrorXModeComboBox->setDisabled(false);
                    mErrorYModeComboBox->setDisabled(false);
                    mLogXRadioButton->setDisabled(false);
                    mLogYRadioButton->setDisabled(false);
                }
                
                //
                //  Calculate the x,y errors according to the selected mode
                //  and fill data into table.
                //
                double firstY = fabs(errorY.x());
                double secondY = fabs(errorY.y());
                double firstX = fabs(errorX.x());
                double secondX = fabs(errorX.y());
                if (mErrorXModeComboBox->currentIndex() == mMax) {    // max(lower, upper)
                    firstX = firstX > secondX ? firstX : secondX;
                    secondX = firstX;
                }
                else if (mErrorXModeComboBox->currentIndex() == mAverage) {  // avg(lower, upper)
                    firstX = (firstX+secondX)/2;
                    secondX = firstX;
                }
                if (mErrorYModeComboBox->currentIndex() == mMax) {
                    firstY = firstY > secondY ? firstY : secondY;
                    secondY = firstY;
                }
                else if (mErrorYModeComboBox->currentIndex() == mAverage) {
                    firstY = (firstY+secondY)/2;
                    secondY = firstY;
                }
                
                if (mStateMachine.allowWriteXY()) {
                    addToTable(xy.x(), xy.y(), 0, 0, 0, 0);
                    statusBar()->showMessage(tr("Data point stored"));
                    mStateMachine.setDataWritten(true);
                }
                else if (mStateMachine.allowWriteXYErrorY()) {
                    addToTable(xy.x(), xy.y(), 0, 0, firstY, secondY);
                    statusBar()->showMessage(tr("Data point and y-error stored"));
                    mStateMachine.setDataWritten(true);
                }
                else if (mStateMachine.allowWriteXYErrorX()) {
                    addToTable(xy.x(), xy.y(), firstX, secondX, 0, 0);
                    statusBar()->showMessage(tr("Data point and x-error stored"));
                    mStateMachine.setDataWritten(true);
                }
                else if (mStateMachine.allowWriteXYErrorXErrorY()) {
                    addToTable(xy.x(), xy.y(), firstX, secondX, firstY, secondY);
                    statusBar()->showMessage(tr("Data point, x- and y-errors stored"));
                    mStateMachine.setDataWritten(true);
                }
            }
            else {
                QMessageBox::information(0, "xyscan",
                                         tr("Cannot scan yet. Not sufficient information available to perform "
                                            "the coordinate transformation. You need to define 2 points on the "
                                            "x-axis (x1 & x1) and 2 on the y-axis (y1 & y2)."));
            }
            break;
        default:
            k->ignore();
            break;
    }
    
    //
    //  Update coordinate display if an arrow key was pressed 
    // 
    if (key == Qt::Key_Left || key == Qt::Key_Right || key == Qt::Key_Up || key == Qt::Key_Down) {
        updatePixelDisplay();
        updatePlotCoordinateDisplay();
        ensureCursorVisible();
    }
}

void xyscanWindow::help() 
{
    if (mHelpBrowser) 
        mHelpBrowser->show(); 
    else
        QMessageBox::warning( 0, "xyscan",
                              tr("Sorry no help available.\n"
                              "Documentation files are missing in this installation. "
                              "Check your installation and reinstall if necessary."));    
}

void xyscanWindow::loadPixmap(QPixmap* pixmap)
{
    //
    //  All pixmaps/images displayed in xyscan go through here
    //
    QApplication::setOverrideCursor(Qt::WaitCursor);
    if (mCurrentPixmap) delete mCurrentPixmap;
    
    mImageAngle = 0;
    mImageScale = 1;
    mCurrentPixmap = mImageScene->addPixmap(*pixmap);
    mCurrentPixmap->setZValue(0);
    mImageScene->setSceneRect(0, 0, pixmap->width(), pixmap->height());
    update();
    
    //
    //  Not bigger than the desktop
    //
    if (QApplication::desktop()->width() > width() + 50 &&
        QApplication::desktop()->height() > height() + 50)
        show();
    else
        showMaximized();
    
    //
    //  Show crosshair and position
    //
    mCrosshairV->setVisible(true);
    mCrosshairH->setVisible(true);
    mCrosshairH->setPos(pixmap->width()/2,pixmap->height()/2);
    mCrosshairV->setPos(pixmap->width()/2,pixmap->height()/2);
    updatePixelDisplay();
    updatePlotCoordinateDisplay(); // sets display to N/A if not ready
   
    //
    //  No markers shown yet
    //
    resetMarker();
    
    //
    //  Remove all traces of the previous scan
    //
    mUserComment.clear();
    mDataSaved = true; 
    mAngleSpinBox->setValue(mImageAngle);
    mScaleSpinBox->setValue(mImageScale);
    deleteAll();        // clear table 
   
    //
    //  Enable all buttons that make sense to use
    //  once the image is loaded (or not).
    //
    mSetLowerXButton->setDisabled(false);
    mSetUpperXButton->setDisabled(false);
    mSetLowerYButton->setDisabled(false);
    mSetUpperYButton->setDisabled(false);
    mLogXRadioButton->setDisabled(false);
    mLogYRadioButton->setDisabled(false);    
    mErrorXModeComboBox->setDisabled(false);
    mErrorYModeComboBox->setDisabled(false);
    mEditCommentAction->setDisabled(false);
    mEditCrosshairColorAction->setDisabled(false);
    mShowPrecisionAction->setDisabled(false);
    mAngleSpinBox->setDisabled(false);    
    mScaleSpinBox->setDisabled(false);   
    mPrintAction->setDisabled(true);

    
    statusBar()->showMessage(tr("Image loaded, no markers set"));

    //
    //  Add info to Plot Adjustement window
    //
    QString txt = tr("<html><table border=\"0\">" 
                     "<tr><td>Info:"
                     "<tr><td align=\"right\">&nbsp;Dimensions:&nbsp;<td>%1x%2"
                     "<tr><td align=\"right\">&nbsp;Depth:&nbsp;<td>%3 bit"
                     "<tr><td align=\"right\">&nbsp;Alpha channel:&nbsp;<td>%4</table></html>")
    .arg(pixmap->width()).arg(pixmap->height()).arg(pixmap->depth()).arg(pixmap->hasAlphaChannel() ? tr("Yes"):tr("No"));
    mPlotInfoLabel->setText(txt);
    
    QApplication::restoreOverrideCursor();
}

void xyscanWindow::loadSettings()
{
    QSettings settings(QSettings::NativeFormat, QSettings::UserScope, "tu", "xyscan");
    move(settings.value("xyscan/position", QPoint(75, 45)).toPoint());
    bool toolTips = settings.value("xyscan/showToolTips", true).toBool();
    mShowTooltipsAction->setChecked(toolTips);
    showTooltips(); // defines them if checked otherwise no effect
    mOpenFileDirectory = settings.value("xyscan/lastOpenFileDirectory", QDir::homePath()).toString();
    mSaveFileDirectory = settings.value("xyscan/lastSaveFileDirectory", QDir::homePath()).toString();
    mRecentFiles = settings.value("xyscan/recentFiles").toStringList();
    
    while (mRecentFiles.size() > mMaxRecentFiles)
        mRecentFiles.removeLast();
    for (int i = 0; i < mRecentFiles.size(); ++i) {
        QString text = tr("&%1  %2").arg(i + 1).arg(QFileInfo(mRecentFiles[i]).fileName());
        mRecentFileAction[i]->setText(text);
        mRecentFileAction[i]->setData(mRecentFiles[i]);
        mRecentFileAction[i]->setVisible(true);
    }
    for (int j = mRecentFiles.size(); j < mMaxRecentFiles; ++j)
        mRecentFileAction[j]->setVisible(false);

    if (mRecentFiles.size()) mClearHistoryAction->setDisabled(false);
    
    QVariant var = settings.value("xyscan/crosshairColor", mCrosshairColor);
    QColor theColor = var.value<QColor>();
    if (theColor.isValid()) mCrosshairColor = theColor;
    mCrosshairV->setPen(QPen(mCrosshairColor));
    mCrosshairH->setPen(QPen(mCrosshairColor));
}

int  xyscanWindow::numberOfMarkersSet()
{
    int n = 0;
    for (int i=0; i<4; i++)
        if (mMarker[i]->isVisible()) n++;
    return n;
}

void xyscanWindow::open() 
{
    //
    //  We get here when the user uses the open action
    //  in the file menu.
    //  Check which image formats are supported and use
    //  them as filters for the file dialog.
    //
    QString formats(tr("Images ("));
    QList<QByteArray> imgFormats = QImageReader::supportedImageFormats();
    for (int i=0; i< imgFormats.size() ; i++) {
        formats += tr("*.%1").arg(QString(imgFormats[i]));
        if (i < imgFormats.size()-1) formats += tr(" ");
    }
    formats += tr(")");
    
    QString filename = QFileDialog::getOpenFileName(this, tr("Open File"),
                                                    mOpenFileDirectory,
                                                    formats);   
    if (filename.isEmpty()) return;
    
    openFromFile(filename);
}

void xyscanWindow::openFromFile(const QString& filename)
{
    //
    //  All pixmap/images to be loaded from file
    //  are handled here.
    //

    mOpenFileDirectory = filename;
    mOpenFileDirectory.truncate(mOpenFileDirectory.lastIndexOf('/'));
        
    if (!QFile::exists(filename)) {
        QMessageBox::warning(0, "xyscan", tr("File '%1' does not exist.").arg(filename));
        return;
    }
    
    QPixmap *pixmap = new QPixmap(filename);
   
    if (pixmap->isNull()) {
        QMessageBox::warning(0, "xyscan", tr("Cannot load pixmap/image from file '%1'. "
                                                 "Either the file content is damaged or the " 
                                                 "image file format is not supported.").arg(filename));
        return;
    }
    
    //
    //  Store the file in the recent file list
    //  and enable the referring actions in the
    //  File submenu (Open Recent).
    //
    mClearHistoryAction->setDisabled(false);
    mRecentFiles.removeAll(filename);
    mRecentFiles.prepend(filename);
    while (mRecentFiles.size() > mMaxRecentFiles)
        mRecentFiles.removeLast();

    for (int i = 0; i < mRecentFiles.size(); ++i) {
        QString text = tr("&%1  %2").arg(i + 1).arg(QFileInfo(mRecentFiles[i]).fileName());
        mRecentFileAction[i]->setText(text);
        mRecentFileAction[i]->setData(mRecentFiles[i]);
        mRecentFileAction[i]->setVisible(true);
    }
    for (int j = mRecentFiles.size(); j < mMaxRecentFiles; ++j)
        mRecentFileAction[j]->setVisible(false);

    mCurrentSource = filename;

    loadPixmap(pixmap);
}


void xyscanWindow::openRecent() 
{
    QAction *action = qobject_cast<QAction *>(sender());
    if (action) {
        QString filename = action->data().toString();
        openFromFile(filename);
    }
}

void xyscanWindow::pasteImage()
{
    QPixmap *pixmap = 0;
    
    QClipboard *clipboard = QApplication::clipboard();
    QImage img = clipboard->image();
    if (!img.isNull())
        pixmap = new QPixmap(QPixmap::fromImage(img));
    
    if (!pixmap || pixmap->isNull()) {
        QMessageBox::warning(0, "xyscan", tr("Cannot load image from clipboard.\n"
                                             "Either the clipboard does not contain an "
                                             "image or it contains an image in an "
                                             "unsupported image format."));
        return;
    }
    
    mCurrentSource = "Clipboard";
    
    loadPixmap(pixmap);
}

void xyscanWindow::print()
{
    //
    //  Create document for printing
    //
    QTextDocument document;
    document.setUseDesignMetrics(true);
    QTextCursor cursor(&document);

    QTextCharFormat textFormat;
    QFont fontVar("Helvetica",10);
    textFormat.setFont(fontVar);

    QFont fontFix("Courier",9);
    QTextCharFormat boldTableTextFormat;
    boldTableTextFormat.setFont(fontFix);
    boldTableTextFormat.setFontWeight(QFont::Bold);
    QTextCharFormat tableTextFormat;
    tableTextFormat.setFont(fontFix);
    
    cursor.movePosition(QTextCursor::Start);
    QTextFrame *topFrame = cursor.currentFrame();
    
    //
    //  Header
    //
    time_t now = time(0);
    cursor.insertText(tr("xyscan Version %1\n").arg(VERSION), textFormat);
    cursor.insertText(tr("Date: %1").arg(ctime(&now)), textFormat);
    cursor.insertText(tr("Scanned by: %1\n").arg(QDir::home().dirName()), textFormat);    
    cursor.insertText(tr("Source: %1\n").arg(mCurrentSource), textFormat);    
    cursor.insertText(tr("Comment: %1\n").arg(mUserComment), textFormat);    
    cursor.insertBlock();   
    cursor.insertBlock();   
    
    //
    //  Insert Plot
    //  (don't scale image first - looks terrible even with Qt::SmoothTransformation)
    //
    cursor.setPosition(topFrame->lastPosition());
    QImage original = mCurrentPixmap->pixmap().toImage();
    document.addResource(QTextDocument::ImageResource, QUrl("image"), QVariant(original));  
    int maxSize = 200; // width or height
    double scale = 1;
    if (original.size().width() > maxSize || original.size().height() > maxSize) {
        if (original.size().width() > original.size().height()) 
            scale = maxSize/static_cast<double>(original.size().width());
        else 
            scale = maxSize/static_cast<double>(original.size().height());
    }
    
    QTextImageFormat imageFormat;
    imageFormat.setWidth(static_cast<int>(scale*original.size().width()));
    imageFormat.setHeight(static_cast<int>(scale*original.size().height()));
    imageFormat.setName("image");
    cursor.insertImage(imageFormat);   
    cursor.insertBlock();
    cursor.insertBlock();
    
    //
    //  Table
    //
    cursor.setPosition(topFrame->lastPosition());
    QTextTableFormat tableFormat;
    tableFormat.setBorder(1);
    tableFormat.setCellPadding(3);
    tableFormat.setCellSpacing(0);
    tableFormat.setAlignment(Qt::AlignLeft);
    tableFormat.setHeaderRowCount(1);
    QTextTable *table = cursor.insertTable (mTableWidget->rowCount()+1, 
                                            mTableWidget->columnCount()+1, tableFormat);
    table->cellAt(0, 1).firstCursorPosition().insertText("x", boldTableTextFormat);
    table->cellAt(0, 2).firstCursorPosition().insertText("y", boldTableTextFormat);
    table->cellAt(0, 3).firstCursorPosition().insertText("-dx", boldTableTextFormat);
    table->cellAt(0, 4).firstCursorPosition().insertText("+dx", boldTableTextFormat);
    table->cellAt(0, 5).firstCursorPosition().insertText("-dy", boldTableTextFormat);
    table->cellAt(0, 6).firstCursorPosition().insertText("+dy", boldTableTextFormat);
    for (int irow = 0; irow < mTableWidget->rowCount(); irow++) {
        table->cellAt(irow+1, 0).firstCursorPosition().insertText(tr("%1").arg(irow+1), tableTextFormat);
        for (int icol = 0; icol < mTableWidget->columnCount(); icol++) {
            table->cellAt(irow+1, icol+1).firstCursorPosition().insertText(mTableWidget->item(irow,icol)->text(), tableTextFormat);
        }
    }
    
    //
    //  Start printer dialog and print the document we just created
    //
    QPrinter printer(QPrinter::HighResolution);
    QPrintDialog *dlg = new QPrintDialog(&printer, this);
    dlg->setWindowTitle(tr("xyscan - Print"));
    if (dlg->exec() != QDialog::Accepted) return;
    document.print(&printer);

    statusBar()->showMessage(tr("Ready"));
}

bool xyscanWindow::readyForScan()
{
    bool OK;
    bool allMarkers = (numberOfMarkersSet() == 4);
    bool allAxisValues = true;
    mLowerXValueField->text().toDouble(&OK); if (!OK) allAxisValues = false;
    mUpperXValueField->text().toDouble(&OK); if (!OK) allAxisValues = false;
    mLowerYValueField->text().toDouble(&OK); if (!OK) allAxisValues = false;
    mUpperYValueField->text().toDouble(&OK); if (!OK) allAxisValues = false;
    if (allMarkers && allAxisValues)
        return true;
    else
        return false;
}

void xyscanWindow::resetMarker()
{
    mMarker[mXLower]->setVisible(false);    
    mMarker[mXUpper]->setVisible(false);    
    mMarker[mYLower]->setVisible(false);    
    mMarker[mYUpper]->setVisible(false);    
    mLowerXValueField->setText(tr("undefined"));
    mUpperXValueField->setText(tr("undefined"));
    mLowerYValueField->setText(tr("undefined"));
    mUpperYValueField->setText(tr("undefined"));

    mAngleSpinBox->setDisabled(false);    
    mScaleSpinBox->setDisabled(false);    
}

void xyscanWindow::rotateImage(double d)
{
    QSize s = mCurrentPixmap->pixmap().size();
    double x = s.width()/2;
    double y = s.height()/2;
    if (mCurrentPixmap) {
        // rotate around center
        mCurrentPixmap->setTransform(QTransform().translate(x, y).scale(mImageScale, mImageScale).rotate(-d).translate(-x, -y));
        mImageAngle = -d;
        QRectF rectf = mCurrentPixmap->sceneBoundingRect();
        mImageScene->setSceneRect(rectf);
        update();
    }
}

void xyscanWindow::save()
{
    QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"),
                                                    mSaveFileDirectory+QString("/xyscan.txt"));
    if (fileName.isEmpty()) return;
    
    mSaveFileDirectory = fileName;
    mSaveFileDirectory.truncate(mSaveFileDirectory.lastIndexOf('/'));
    
    QFile file(fileName);
    if (!file.open(QFile::WriteOnly | QFile::Text)) {
        QMessageBox::warning(this, "xyscan",
                             tr("Cannot write file %1:\n%2.")
                             .arg(fileName)
                             .arg(file.errorString()));
        return;
    }
    
    bool writeAsRootMacro = fileName.endsWith(".C");     
    QTextStream out(&file);
    QApplication::setOverrideCursor(Qt::WaitCursor);
    int irow, icol;
    time_t now = time(0);
    
    if (writeAsRootMacro) {
        out << "//" << endl;
        out << "// ROOT macro: " << QFileInfo(file).fileName().toAscii() << " (autogenerated by xyscan)" << endl;
        out << "// xyscan Version " << VERSION << endl;
        out << "// Date: " << ctime(&now);
        out << "// Scanned by: " << QDir::home().dirName() << endl;
        out << "// Source: " << mCurrentSource << endl;
        out << "// Comment: " << mUserComment << endl;
        out << "//" << endl;
        
        out << "#include \"TROOT.h\"" << endl;
        out << "#include \"TH1D.h\"" << endl;
        out << "#include \"TCanvas.h\"" << endl;
        out << "#include \"TStyle.h\"" << endl;
        out << "#include \"TGraphAsymmErrors.h\"" << endl;
        out << endl;
        
        out << "void " << QFileInfo(file).baseName().toAscii() << "()" << endl;
        out << "{" << endl;
        out << "    gROOT->SetStyle(\"Plain\");" << endl;
        out << "    gStyle->SetOptFit(0);" << endl;
        out << "    gStyle->SetOptStat(0);" << endl;
        out << endl;
        out << "    TCanvas *c1 = new TCanvas(\"c1\",\"xyscan Data Display\",720,540);" << endl;
        out << "    c1->SetTickx(1);" << endl;
        out << "    c1->SetTicky(1);" << endl;
        out << "    c1->SetBorderSize(1);" << endl;
        out << "    c1->SetFillColor(0);" << endl;
        out << endl;
        
        double xmin = DBL_MAX;
        double xmax = DBL_MIN;
        double ymin = DBL_MAX;
        double ymax = DBL_MIN;
        for (irow=0; irow < mTableWidget->rowCount(); irow++) {
            double xx = mTableWidget->item(irow, 0)->text().toDouble();
            double yy = mTableWidget->item(irow, 1)->text().toDouble();
            double dxxlow = mTableWidget->item(irow, 2)->text().toDouble();
            double dxxup = mTableWidget->item(irow, 3)->text().toDouble();
            double dyylow = mTableWidget->item(irow, 4)->text().toDouble();
            double dyyup = mTableWidget->item(irow, 5)->text().toDouble();            
            if (xx-dxxlow < xmin) xmin = xx-dxxlow;
            if (xx+dxxup > xmax) xmax = xx+dxxup;
            if (yy-dyylow < ymin) ymin = yy-dyylow;
            if (yy+dyyup > ymax) ymax = yy+dyyup;
        }   
        if (mLogYRadioButton->isChecked()) {
            ymax *= 2;
            ymin /= 2;
        }
        else {
            ymax += (ymax-ymin)/10;
            ymin -= (ymax-ymin)/10;
            if (ymin > 0) ymin = 0;
        }
        if (mLogXRadioButton->isChecked()) {
            xmax *= 2;
            xmin /= 2;
        }
        else {
            xmax += (xmax-xmin)/10;
            xmin -= (xmax-xmin)/10;
        }
        
        out << "    TH1D *histo = new TH1D(\"histo\",\"xyscan\", 100, " << xmin << ", " << xmax << ");" << endl;
        out << "    histo->SetMinimum(" << ymin << ");" << endl;
        out << "    histo->SetMaximum(" << ymax << ");" << endl;
        out << "    histo->SetStats(false);" << endl;
        out << "    histo->GetXaxis()->SetTitle(\"x\");" << endl;
        out << "    histo->GetYaxis()->SetTitle(\"y\");" << endl;
        out << "    gPad->SetLogy(" << (mLogYRadioButton->isChecked() ? 1 : 0) << ");" << endl;
        out << "    gPad->SetLogx(" << (mLogXRadioButton->isChecked() ? 1 : 0) << ");" << endl;
        
        out << "    histo->Draw();" << endl;
        out << endl;
        out << "    double x[" << mTableWidget->rowCount() << "];" << endl;
        out << "    double y[" << mTableWidget->rowCount() << "];" << endl;
        out << "    double dxlow[" << mTableWidget->rowCount() << "];" << endl;
        out << "    double dxup[" << mTableWidget->rowCount() << "];" << endl;
        out << "    double dylow[" << mTableWidget->rowCount() << "];" << endl;
        out << "    double dyup[" << mTableWidget->rowCount() << "];" << endl;
        out << "    int n = 0;" << endl;
        for (irow=0; irow < mTableWidget->rowCount(); irow++) {
            out << "    x[n] = " << mTableWidget->item(irow, 0)->text().toDouble() << ";\t";
            out << "y[n] = " << mTableWidget->item(irow, 1)->text().toDouble() << ";\t";
            out << "dxlow[n] = " << mTableWidget->item(irow, 2)->text().toDouble() << ";\t";
            out << "dxup[n] = " << mTableWidget->item(irow, 3)->text().toDouble() << ";\t";
            out << "dylow[n] = " << mTableWidget->item(irow, 4)->text().toDouble() << ";\t";
            out << "dyup[n] = " << mTableWidget->item(irow, 5)->text().toDouble() << ";\t";
            out << "n++;" << endl;
        }   
        out << endl;
        out << "    TGraphAsymmErrors *xyscan = new TGraphAsymmErrors(n, x, y, dxlow, dxup, dylow, dyup);" << endl;
        out << "    xyscan->SetMarkerStyle(20);" << endl;
        out << "    xyscan->SetMarkerColor(1);" << endl;
        out << "    xyscan->Draw(\"PE same\");" << endl;
        out << "}" << endl;
    }
    else {
        out << "# xyscan Version " << VERSION << endl;
        out << "# Date: " << ctime(&now);
        out << "# Scanned by: " << QDir::home().dirName() << endl;
        out << "# Source: " << mCurrentSource << endl;
        out << "# Comment: " << mUserComment << endl;
        out << "# Format: x y -dx +dx -dy +dy" << endl;
        for (irow=0; irow < mTableWidget->rowCount(); irow++) {
            for (icol=0; icol < mTableWidget->columnCount(); icol++) {
                out << mTableWidget->item(irow, icol)->text() << "\t";
            }
            out << endl;
        }
        out << "# EoF" << endl;
    }
    
    QApplication::restoreOverrideCursor();
    
    if (writeAsRootMacro) 
        statusBar()->showMessage(tr("Data saved in ROOT macro '%1'").arg(fileName));
    else
        statusBar()->showMessage(tr("Data saved in '%1'").arg(fileName));
    mDataSaved = true;    
}

void xyscanWindow::scaleImage(double z)
{
    QSize s = mCurrentPixmap->pixmap().size();
    double x = s.width()/2;
    double y = s.height()/2;
    if (mCurrentPixmap) {
        // scale around center
        mCurrentPixmap->setTransform(QTransform().translate(x, y).scale(z, z).rotate(mImageAngle).translate(-x, -y));
        mImageScale = z;        
        QRectF rectf = mCurrentPixmap->sceneBoundingRect();
        mImageScene->setSceneRect(rectf);
        update();
    }
}

QPointF xyscanWindow::scan()
{
    //
    //  This is where the transformation from pixel
    //  to graph coordinates is performed. 
    //
    
    QPointF xy(0,0);
 
    bool logx = mLogXRadioButton->isChecked();
    bool logy = mLogYRadioButton->isChecked();
        
    //
    //  Consistency and numerics checks
    //
    if (!readyForScan()) {
        QMessageBox::information(0, "xyscan",
                                 tr("Cannot scan yet. Not sufficient information available to perform "
                                    "the coordinate transformation. You need to define 2 points on the "
                                    "x-axis (x1 & x1) and 2 on the y-axis (y1 & y2)."));
        return xy;
    }    
    
    if (fabs(mMarkerPixel[mXUpper]-mMarkerPixel[mXLower]) < 2) {
        QMessageBox::critical(0, "xyscan",
                              tr("Error in calculating transformation.\n"
                                 "Markers on x-axis are less than two pixels apart. "
                                 "Cannot continue with current settings."));
        return xy;
    }
    if (fabs(mMarkerPixel[mYUpper]-mMarkerPixel[mYLower]) < 2) {
        QMessageBox::critical(0, "xyscan",
                              tr("Error in calculating transformation.\n"
                                 "Markers on y-axis are less than two pixels apart. "
                                 "Cannot continue with current settings."));
        return xy;
    }
    
    if (logx) {
        if (mMarkerPlotCoordinate[mXUpper] <= 0 || mMarkerPlotCoordinate[mXLower] <= 0) {
            QMessageBox::critical(0, "xyscan",
                                  tr("Error in calculating transformation.\n"
                                     "Logarithmic x-axis selected but negative (or zero) values assigned to markers. "
                                     "Cannot continue with current settings."));
            return xy;
        }
    }
    
    if (logy) {
        if (mMarkerPlotCoordinate[mYUpper] <= 0 || mMarkerPlotCoordinate[mYLower] <= 0) {
            QMessageBox::critical(0, "xyscan",
                                  tr("Error in calculating transformation.\n"
                                     "Logarithmic y-axis selected but negative (or zero) values assigned to markers. "
                                     "Cannot continue with current settings."));
            return xy;
        }
    }
    
    mShowPrecisionAction->setDisabled(false); // OK from here on

    //
    //  Coordinate transformation
    //
    
    double m11, m22, dx, dy;
    if (logx) {
        m11 = (log10(mMarkerPlotCoordinate[mXUpper])-log10(mMarkerPlotCoordinate[mXLower]))/(mMarkerPixel[mXUpper]-mMarkerPixel[mXLower]);
        dx = log10(mMarkerPlotCoordinate[mXUpper])-m11*mMarkerPixel[mXUpper];
    }
    else {
        m11 = (mMarkerPlotCoordinate[mXUpper]-mMarkerPlotCoordinate[mXLower])/(mMarkerPixel[mXUpper]-mMarkerPixel[mXLower]);
        dx = mMarkerPlotCoordinate[mXUpper]-m11*mMarkerPixel[mXUpper];
    }
    if (logy) {
        m22 = (log10(mMarkerPlotCoordinate[mYUpper])-log10(mMarkerPlotCoordinate[mYLower]))/(mMarkerPixel[mYUpper]-mMarkerPixel[mYLower]);
        dy = log10(mMarkerPlotCoordinate[mYUpper])-m22*mMarkerPixel[mYUpper];
    }
    else {
        m22 = (mMarkerPlotCoordinate[mYUpper]-mMarkerPlotCoordinate[mYLower])/(mMarkerPixel[mYUpper]-mMarkerPixel[mYLower]);
        dy = mMarkerPlotCoordinate[mYUpper]-m22*mMarkerPixel[mYUpper];
    }
    QMatrix M(m11, 0, 0, m22, dx, dy);
    QPointF cross(mCrosshairV->pos().x(), mCrosshairH->pos().y());
    xy = M.map(cross);  
    if (logx) xy.setX(pow(10.,xy.x()));
    if (logy) xy.setY(pow(10.,xy.y()));
    return xy;
}

void xyscanWindow::setLowerXButton() 
{
    bool ok;
    double r = mMarkerPlotCoordinate[mXLower];
    QString ret = QInputDialog::getText(this, "xyscan", tr("Enter the x-value at marker position:"),
                                        QLineEdit::Normal, QString("%1").arg(r), &ok);
    if (!ok) return;
    r = ret.toDouble(&ok);
    if (!ok) {
        QMessageBox::warning( 0, "xyscan",
                              tr("%1 is not a valid floating point number.").arg(ret));
        return;
    }
    mMarkerPlotCoordinate[mXLower] = r;
    QString str;
    str.setNum(r);
    mLowerXValueField->setText(str);
    
    QPointF pos(mCrosshairH->pos().x(), mCrosshairV->pos().y());
    mMarkerPixel[mXLower] = mCrosshairH->pos().x();
    mMarker[mXLower]->setPos(pos);
    mMarker[mXLower]->setVisible(true);  
    
    if (numberOfMarkersSet() == 4)
        statusBar()->showMessage(tr("Ready to scan. Press space bar to record current cursor position."));
    else
        statusBar()->showMessage(QString("%1 of 4 markers set").arg(numberOfMarkersSet()));

    mAngleSpinBox->setDisabled(true);  // rotating would void marker position  
    mScaleSpinBox->setDisabled(true);  // same for scaling (zoomig in/out)
}

void xyscanWindow::setUpperXButton() 
{
    bool ok;
    double r = mMarkerPlotCoordinate[mXUpper];
    QString ret = QInputDialog::getText(this, "xyscan", tr("Enter the x-value at marker position:"),
                                        QLineEdit::Normal, QString("%1").arg(r), &ok);
    if (!ok) return;
    r = ret.toDouble(&ok);
    if (!ok) {
        QMessageBox::warning( 0, "xyscan",
                              tr("%1 is not a valid floating point number.").arg(ret));
        return;
    }
    mMarkerPlotCoordinate[mXUpper] = r;
    QString str;
    str.setNum(r);
    mUpperXValueField->setText(str);
    QPointF pos(mCrosshairH->pos().x(), mCrosshairV->pos().y());
    mMarkerPixel[mXUpper] = mCrosshairH->pos().x();
    mMarker[mXUpper]->setPos(pos);
    mMarker[mXUpper]->setVisible(true);  

    if (numberOfMarkersSet() == 4)
        statusBar()->showMessage(tr("Ready to scan. Press space bar to record current cursor position."));
    else
        statusBar()->showMessage(QString("%1 of 4 markers set").arg(numberOfMarkersSet()));

    mAngleSpinBox->setDisabled(true);  // rotating would void marker position  
    mScaleSpinBox->setDisabled(true);  // same for scaling (zoomig in/out)
}

void xyscanWindow::setLowerYButton()  
{
    bool ok;
    double r = mMarkerPlotCoordinate[mYLower];
    QString ret = QInputDialog::getText(this, "xyscan", tr("Enter the y-value at marker position:"),
                                        QLineEdit::Normal, QString("%1").arg(r), &ok);
    if (!ok) return;
    r = ret.toDouble(&ok);
    if (!ok) {
        QMessageBox::warning( 0, "xyscan",
                              tr("%1 is not a valid floating point number.").arg(ret));
        return;
    }
    mMarkerPlotCoordinate[mYLower] = r;
    QString str;
    str.setNum(r);
    mLowerYValueField->setText(str);
    QPointF pos(mCrosshairH->pos().x(), mCrosshairV->pos().y());
    mMarkerPixel[mYLower] = mCrosshairV->pos().y();
    mMarker[mYLower]->setPos(pos);
    mMarker[mYLower]->setVisible(true);    

    if (numberOfMarkersSet() == 4)
        statusBar()->showMessage(tr("Ready to scan. Press space bar to record current cursor position."));
    else
        statusBar()->showMessage(QString("%1 of 4 markers set").arg(numberOfMarkersSet()));
    
    mAngleSpinBox->setDisabled(true);  // rotating would void marker position  
    mScaleSpinBox->setDisabled(true);  // same for scaling (zoomig in/out)
}

void xyscanWindow::setUpperYButton() 
{
    bool ok;
    double r = mMarkerPlotCoordinate[mYUpper];
    QString ret = QInputDialog::getText(this, "xyscan", tr("Enter the y-value at marker position:"),
                                        QLineEdit::Normal, QString("%1").arg(r), &ok);
    if (!ok) return;
    r = ret.toDouble(&ok);
    if (!ok) {
        QMessageBox::warning( 0, "xyscan",
                              tr("%1 is not a valid floating point number.").arg(ret));
        return;
    }
    mMarkerPlotCoordinate[mYUpper] = r;
    QString str;
    str.setNum(r);
    mUpperYValueField->setText(str);
    QPointF pos(mCrosshairH->pos().x(), mCrosshairV->pos().y());
    mMarkerPixel[mYUpper] = mCrosshairV->pos().y();
    mMarker[mYUpper]->setPos(pos);
    mMarker[mYUpper]->setVisible(true);    

    if (numberOfMarkersSet() == 4)
        statusBar()->showMessage(tr("Ready to scan. Press space bar to record current cursor position."));
    else
        statusBar()->showMessage(QString("%1 of 4 markers set").arg(numberOfMarkersSet()));
    
    mAngleSpinBox->setDisabled(true);  // rotating would void marker position  
    mScaleSpinBox->setDisabled(true);  // same for scaling (zoomig in/out)
}

void xyscanWindow::showPrecision() 
{
    QPointF point, currentV, currentH;
    QPointF left, right, up, down;
    QString str;
    double mdx, pdx, mdy, pdy;
    if (readyForScan()) {
        currentV = mCrosshairV->pos();
        currentH = mCrosshairH->pos();
        point = scan();
        
        mCrosshairV->moveBy(-1,0);
        mCrosshairH->moveBy(-1,0);
        left = scan();
        mCrosshairV->setPos(currentV);
        mCrosshairH->setPos(currentH);
        
        mCrosshairV->moveBy(1,0);
        mCrosshairH->moveBy(1,0);
        right = scan();
        mCrosshairV->setPos(currentV);
        mCrosshairH->setPos(currentH);

        mCrosshairV->moveBy(0,1);
        mCrosshairH->moveBy(0,1);
        down = scan();
        mCrosshairV->setPos(currentV);
        mCrosshairH->setPos(currentH);
        
        mCrosshairV->moveBy(0,-1);
        mCrosshairH->moveBy(0,-1);
        up = scan();
        mCrosshairV->setPos(currentV);
        mCrosshairH->setPos(currentH);
        
        mdx = fabs(point.x()-left.x());
        pdx = fabs(point.x()-right.x());
        mdy = fabs(point.y()-down.y());
        pdy = fabs(point.y()-up.y());
        str = tr("Estimated precision at current point:\ndx = +%1  -%2\ndy = +%3  -%4").arg(pdx).arg(mdx).arg(pdy).arg(mdy);
    }
    else {
        str = tr("Cannot determin precision yet.\nPlace all 4 markers first.");
    }
    
    QMessageBox::information(0, "xyscan", str);
    
}

void xyscanWindow::showTooltips() 
{    
    //
    //  I do not see any other way than to do it like
    //  this. Many users do not like the tool tips and
    //  I want them to be able to switch that off and
    //  on as needed. I did not find a general method
    //  that switches all application tool tips on/off.
    //  It only works on the widget basis so we go 
    //  through each important widget/action one by one.
    //
    if (mShowTooltipsAction->isChecked()) {
        
        mImageView->setToolTip(tr("Scan area"));
        
        mCoordinateDock->setToolTip(tr("Coordinates Display Window:\n"
                                    "Displays cursor position in local \n"
                                    "(pixel) and plot coordinates."));
        mSettingsDock->setToolTip(tr("Settings Window:\n"
                                  "Use to set axes marker, define axes,\n" 
                                  "set log/lin scales, and set the error scan mode."));
        mTableDock->setToolTip(tr("Data Table Window:\n"
                               "Window displaying the data table that holds \n "
                               "all points (and errors) scanned so far."));
        mTransformDock->setToolTip(tr("Plot Adjustments:\n"
                                   "Window displaying info on the current plot and controls\n"
                                   "that allows to scale (zoom in/out) and rotate the plot."));
        
        mPixelXDisplay->setToolTip(tr("Displays the x coordinate of the cursor in pixel (screen) units"));
        mPixelYDisplay->setToolTip(tr("Displays the y coordinate of the cursor in pixel (screen) units"));
        mPlotXDisplay->setToolTip(tr("Displays the x coordinate of the cursor in plot units.\n"
                                  "When the point is recorded (space key) this coordinate\n"
                                  "gets stored in the data table."));
        mPlotYDisplay->setToolTip(tr("Displays the y coordinate of the cursor in plot units.\n"
                                  "When the point is recorded (space key) this coordinate\n"
                                  "gets stored in the data table."));
        
        // Actions (don't show on the Mac)
        mOpenAction->setToolTip(tr("Open file to read in image"));
        mSaveAction->setToolTip(tr("Save the scanned data in text file"));
        mPrintAction->setToolTip(tr("Print the plot together with the scanned data"));
        mFinishAction->setToolTip(tr("Quit xyscan"));
        mDeleteLastAction->setToolTip(tr("Delete last scanned point from data table"));
        mDeleteAllAction->setToolTip(tr("Delete all scanned point from data table."));
        mEditCommentAction->setToolTip(tr("Write comment that will be added to the\n"
                                       "scanned data when saved to file."));
        mPasteImageAction->setToolTip(tr("Paste image from clipboard"));
        mClearHistoryAction->setToolTip(tr("Clear list of recently opened files"));
        mShowPrecisionAction->setToolTip(tr("Shows scan precision at current cursor point"));
        mShowTooltipsAction->setToolTip(tr("Switch on/off tool tips"));
        
        mSetLowerXButton->setToolTip(tr("Set marker to define the first (lower) position on the x-axis.\n"
                                     "Launches input dialog for the referring value in plot coordinates."));
        mSetUpperXButton->setToolTip(tr("Set marker to define the second (upper) position on the x-axis.\n"
                                     "Launches input dialog for the referring value in plot coordinates."));
        mSetLowerYButton->setToolTip(tr("Set marker to define the first (lower) position on the y-axis.\n"
                                     "Launches input dialog for the referring value in plot coordinates."));
        mSetUpperYButton->setToolTip(tr("Set marker to define the second (upper) position on the y-axis.\n"
                                     "Launches input dialog for the referring value in plot coordinates."));
        
        mUpperYValueField->setToolTip(tr("x-axis value in plot coordinates assigned to the low-x marker (read only)"));
        mLowerYValueField->setToolTip(tr("x-axis value in plot coordinates assigned to the upper-x marker (read only)"));
        mUpperXValueField->setToolTip(tr("y-axis value in plot coordinates assigned to the low-y marker (read only)"));
        mLowerXValueField->setToolTip(tr("y-axis value in plot coordinates assigned to the upper-y marker (read only)"));
        
        mLogXRadioButton->setToolTip(tr("Check if x-axis on plot has log scale"));
        mLogYRadioButton->setToolTip(tr("Check if y-axis on plot has log scale"));
        mAngleSpinBox->setToolTip(tr("Allows to rotate the plot if needed to align it for\nthe scan. To be done before setting markers."));
        mScaleSpinBox->setToolTip(tr("Allows to zoom in and out the plot.\nTo be done before setting markers."));
        
        mErrorXModeComboBox->setToolTip(tr("Defines error scan mode"));
        mErrorYModeComboBox->setToolTip(tr("Defines error scan mode"));
        
        mTableWidget->setToolTip(tr("Data table holding all poinst scanned so far"));
        
        mMarker[mXLower]->setToolTip(tr("Marker for lower x-axis position"));
        mMarker[mXUpper]->setToolTip(tr("Marker for upper x-axis position"));
        mMarker[mYLower]->setToolTip(tr("Marker for lower y-axis position"));
        mMarker[mYUpper]->setToolTip(tr("Marker for upper y-axis position"));
        
        mCrosshairH->setToolTip(tr("Horizontal bar of crosshairs cursor"));
        mCrosshairV->setToolTip(tr("Vertical bar of crosshairs cursor"));    
        
        mAngleSpinBox->setToolTip(tr("Rotate current plot (degrees)"));
        mScaleSpinBox->setToolTip(tr("Scale (zoom in/out) the current plot"));
        mPlotInfoLabel->setToolTip(tr("Show dimension and depth of current plot"));
        
        if (mHelpBrowser) mHelpBrowser->setToolTip(tr("Help browser for xyscan documenation"));
    }
    else { 
        mImageView->setToolTip("");
        
        mCoordinateDock->setToolTip("");
        mSettingsDock->setToolTip("");
        mTableDock->setToolTip("");
        mTransformDock->setToolTip("");
        
        mPixelXDisplay->setToolTip("");
        mPixelYDisplay->setToolTip("");
        mPlotXDisplay->setToolTip("");
        mPlotYDisplay->setToolTip("");
        
        mOpenAction->setToolTip("");
        mSaveAction->setToolTip("");
        mPrintAction->setToolTip("");
        mFinishAction->setToolTip("");
        mDeleteLastAction->setToolTip("");
        mDeleteAllAction->setToolTip("");
        mEditCommentAction->setToolTip("");

        mPasteImageAction->setToolTip("");
        mClearHistoryAction->setToolTip("");
        mShowPrecisionAction->setToolTip("");
        mShowTooltipsAction->setToolTip("");
        
        mSetLowerXButton->setToolTip("");
        mSetUpperXButton->setToolTip("");
        mSetLowerYButton->setToolTip("");
        mSetUpperYButton->setToolTip("");
        
        mUpperYValueField->setToolTip("");
        mLowerYValueField->setToolTip("");
        mUpperXValueField->setToolTip("");
        mLowerXValueField->setToolTip("");
        
        mLogXRadioButton->setToolTip("");
        mLogYRadioButton->setToolTip("");
        
        mErrorXModeComboBox->setToolTip("");
        mErrorYModeComboBox->setToolTip("");
        
        mTableWidget->setToolTip("");
        
        mMarker[mXLower]->setToolTip("");
        mMarker[mXUpper]->setToolTip("");
        mMarker[mYLower]->setToolTip("");
        mMarker[mYUpper]->setToolTip("");
        
        mCrosshairH->setToolTip("");
        mCrosshairV->setToolTip("");

        mAngleSpinBox->setToolTip("");
        mScaleSpinBox->setToolTip("");
        mPlotInfoLabel->setToolTip("");
        
        if (mHelpBrowser) mHelpBrowser->setToolTip("");
    }    
}

void xyscanWindow::updatePixelDisplay()
{
    QString str;
    str.setNum(mCrosshairH->pos().x());
    mPixelXDisplay->setText(str);
    str.setNum(mCrosshairV->pos().y());
    mPixelYDisplay->setText(str);
}

void xyscanWindow::updatePlotCoordinateDisplay()
{
    if (readyForScan()) {
        QPointF xy = scan();
        QString str;
        str.setNum(xy.x());
        mPlotXDisplay->setText(str);
        str.setNum(xy.y());
        mPlotYDisplay->setText(str);
    }
    else {
        mPlotXDisplay->setText(tr("N/A"));
        mPlotYDisplay->setText(tr("N/A"));
    }
}

void xyscanWindow::updateWhenAxisScaleChanged()
{
    updatePixelDisplay();
    updatePlotCoordinateDisplay(); 
}

void xyscanWindow::writeSettings()
{
    QSettings settings(QSettings::NativeFormat, QSettings::UserScope, "tu", "xyscan");
    settings.setValue("xyscan/Version", VERSION_NUMBER);  // not used but good to have
    settings.setValue("xyscan/position", pos());
    settings.setValue("xyscan/showToolTips", mShowTooltipsAction->isChecked());
    settings.setValue("xyscan/lastOpenFileDirectory", mOpenFileDirectory);
    settings.setValue("xyscan/lastSaveFileDirectory", mSaveFileDirectory);
    settings.setValue("xyscan/recentFiles", mRecentFiles);
    settings.setValue("xyscan/crosshairColor", mCrosshairColor);
}


