//-----------------------------------------------------------------------------
//  Copyright (C) 2020-2021 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: Mar 3, 2021
//-----------------------------------------------------------------------------
#include <QGridLayout>
#include <QSplitter>
#include <QToolBox>
#include <QGraphicsScene>
#include <QColorDialog>
#include <QGraphicsView>
#include <QActionGroup>
#include <QDoubleSpinBox>
#include <QMenu>
#include <QMenuBar>
#include <QStatusBar>
#include <QLabel>
#include <QShortcut>
#include <QPushButton>
#include <QRadioButton>
#include <QLineEdit>
#include <QCheckBox>
#include <QComboBox>
#include <QDockWidget>
#include <QGraphicsLineItem>
#include <QGraphicsPixmapItem>
#include <QBarSeries>

#include "xyscanBaseWindow.h"
#include "xyscanGraphicsView.h"
#include "xyscanAbout.h"
#include "xyscanErrorBarScanModeToolBox.h"
#include "xyscanDataTable.h"
#include "xyscanMarkerMaps.h"
#include "xyscanHelpBrowser.h"

#include <cfloat>
#include <iostream>

using namespace std;

#define PR(x) cout << #x << " = " << (x) << endl;

xyscanBaseWindow::xyscanBaseWindow()
{
    //
    //  Create central widget and main elements:
    //  graphic view and tool box.
    //
    mCentralWidget = new QWidget(this);
    auto gridLayout = new QGridLayout(mCentralWidget);
    auto splitter = new QSplitter(mCentralWidget);
    splitter->setOrientation(Qt::Horizontal);
    mImageView = new xyscanGraphicsView(splitter);
    splitter->addWidget(mImageView);
    mToolBox = new QToolBox(splitter);
    QLocale locale;
    if (locale.language() == QLocale::Spanish)
        mToolBox->setMaximumWidth(325);
    else
        mToolBox->setMaximumWidth(285);
    splitter->addWidget(mToolBox);
    gridLayout->addWidget(splitter, 0, 0, 1, 1);
    setCentralWidget(mCentralWidget);

    //
    //  Default colors for crosshair and markers
    //
    //  By setting these as custom colors, the user can always
    //  go back to the default color.
    //
    mCrosshairColor = Qt::red;
    mMarkerColor = QColor("#00BB00");
    mPathColor = QColor(Qt::black);
    QColorDialog::setCustomColor(0, mCrosshairColor);
    QColorDialog::setCustomColor(1, mMarkerColor);
    QColorDialog::setCustomColor(2, mPathColor);

    //
    //  Members that need to be initialized
    //
    mImageAngle = 0;
    mImageScale = 1;
    mAngleSpinBox = nullptr;
    mScaleSpinBox = nullptr;
    mZoomSpinBox = nullptr;

    //
    //  Create widgets, docks, and dialogs.
    //  Order matters here a lot.
    //
    mImageScene = new QGraphicsScene(mImageView);
    mImageScene->setItemIndexMethod(QGraphicsScene::NoIndex); // if you don’t do frequent hit tests
    mImageView->setScene(mImageScene);
    mImageView->setCacheMode(QGraphicsView::CacheBackground);
    mImageView->setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
    
    mAboutDialog = new xyscanAbout(this);

    //
    //  Build most of the windows and pieces of xyscan
    //
    createMenuActions();
    createMenus();
    createStatusBar();
    createToolBox();
    createTableDockingWidget();
    createZoomDockingWidget();
    createHistogramDockingWidget();
    createMarker();
    createCrosshair();
    createHelpBrowser();
    createToolTips();

    //
    //  Set main window properties
    //
    setWindowTitle("xyscan");
    setWindowIcon(QIcon(QString::fromUtf8(":/images/xyscanIcon.png")));

    //
    //  Create line for measure tool
    //
    mMeasureLine = new QGraphicsLineItem();
    mMeasureLine->setPen(QPen(mMarkerColor));
    mMeasureLine->setZValue(1);
    mImageScene->addItem(mMeasureLine);

    //
    //  Create path for path measure tool
    //
    mMeasurePathLine = new QGraphicsPathItem();
    mMeasurePathLine->setPen(QPen(mPathColor));
    mMeasurePathLine->setZValue(1);
    mImageScene->addItem(mMeasurePathLine);
}

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

void xyscanBaseWindow::createAdjustmentToolBoxItem(QWidget *form)
{
    QGridLayout *gridLayout = new QGridLayout(form);
    int nrow = 0;

    //
    //  Info label on top
    //
    mPlotInfoLabel = new QLabel(form);
    mPlotInfoLabel->setText(tr("No image loaded"));
    gridLayout->addWidget(mPlotInfoLabel, nrow++, 0, 1, 1);

    //
    //  Separator line
    //
    QFrame *line = new QFrame(form);
    line->setFrameShape(QFrame::HLine);
    line->setFrameShadow(QFrame::Sunken);
    gridLayout->addWidget(line, nrow++, 0, 1, 1);

    //
    //  Angle/rotate spin box with label
    //
    QLabel* labelAngle = new QLabel(form);
    labelAngle->setText(tr("Rotate:"));
    mAngleSpinBox = new QDoubleSpinBox(form);
    mAngleSpinBox->setMinimum(-90);
    mAngleSpinBox->setMaximum(180);
    mAngleSpinBox->setSingleStep(0.05);
    mAngleSpinBox->setAccelerated(true);
    mAngleSpinBox->setValue(mImageAngle);
    QLabel* labelScale = new QLabel(form);
    labelScale->setText(tr("Scale:"));
    mScaleSpinBox = new QDoubleSpinBox(form);
    mScaleSpinBox->setMinimum(0.1);
    mScaleSpinBox->setMaximum(10);
    mScaleSpinBox->setSingleStep(0.05);
    mScaleSpinBox->setAccelerated(true);
    mScaleSpinBox->setValue(mImageScale);

    QFontMetrics fontMetrics(labelAngle->font());
    int labelWidth1 = fontMetrics.horizontalAdvance(labelAngle->text());
    int labelWidth2 = fontMetrics.horizontalAdvance(labelScale->text());
    labelAngle->setFixedWidth(max(labelWidth1, labelWidth2));
    labelScale->setFixedWidth(max(labelWidth1, labelWidth2));

    QHBoxLayout *horizontalLayoutAngle = new QHBoxLayout();
    horizontalLayoutAngle->addWidget(labelAngle);
    horizontalLayoutAngle->addWidget(mAngleSpinBox);
    horizontalLayoutAngle->addItem(new QSpacerItem(100, 20, QSizePolicy::Expanding, QSizePolicy::Minimum));
    QHBoxLayout *horizontalLayoutScale = new QHBoxLayout();
    horizontalLayoutScale->addWidget(labelScale);
    horizontalLayoutScale->addWidget(mScaleSpinBox);
    horizontalLayoutScale->addItem(new QSpacerItem(100, 20, QSizePolicy::Expanding, QSizePolicy::Minimum));

    gridLayout->addLayout(horizontalLayoutAngle, nrow++, 0, 1, 1);
    gridLayout->addLayout(horizontalLayoutScale, nrow++, 0, 1, 1);

    //
    //  Spacers
    //
    QSpacerItem *verticalSpacer = new QSpacerItem(20, 100, QSizePolicy::Minimum, QSizePolicy::Expanding);
    QSpacerItem *horizontalSpacer = new QSpacerItem(100, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);

    gridLayout->addItem(horizontalSpacer, 2, 1, 1, 1);
    gridLayout->addItem(verticalSpacer, nrow, 0, 1, 1);
}

void xyscanBaseWindow::createAxisSettingsToolBoxItem(QWidget *form)
{
    QGridLayout *gridLayout = new QGridLayout(form);
#if defined(Q_OS_MAC)
    gridLayout->setVerticalSpacing(0);  // On macOS too much room around 'set' buttons
#endif
    int nrow = 0;

    //
    //  Markers widgets
    //
    QLabel *labelLX = new QLabel(form);
    QLabel *labelUX = new QLabel(form);
    QLabel *labelLY = new QLabel(form);
    QLabel *labelUY = new QLabel(form);
    mSetLowerXButton = new QPushButton(form);
    mSetUpperXButton = new QPushButton(form);
    mSetLowerYButton = new QPushButton(form);
    mSetUpperYButton = new QPushButton(form);
    mLowerXValueField = new QLineEdit(form);
    mLowerXValueField->setReadOnly(true);
    mUpperXValueField = new QLineEdit(form);
    mUpperXValueField->setReadOnly(true);
    mLowerYValueField = new QLineEdit(form);
    mLowerYValueField->setReadOnly(true);
    mUpperYValueField = new QLineEdit(form);
    mUpperYValueField->setReadOnly(true);

    QHBoxLayout *horizontalLayoutLX = new QHBoxLayout();
    horizontalLayoutLX->addWidget(labelLX);
    horizontalLayoutLX->addWidget(mLowerXValueField);
    horizontalLayoutLX->addWidget(mSetLowerXButton);

    gridLayout->addLayout(horizontalLayoutLX, nrow++, 0, 1, 1);
    QHBoxLayout *horizontalLayoutUX = new QHBoxLayout();
    horizontalLayoutUX->addWidget(labelUX);
    horizontalLayoutUX->addWidget(mUpperXValueField);
    horizontalLayoutUX->addWidget(mSetUpperXButton);
    gridLayout->addLayout(horizontalLayoutUX, nrow++, 0, 1, 1);
    QHBoxLayout *horizontalLayoutLY = new QHBoxLayout();
    horizontalLayoutLY->addWidget(labelLY);
    horizontalLayoutLY->addWidget(mLowerYValueField);
    horizontalLayoutLY->addWidget(mSetLowerYButton);
    gridLayout->addLayout(horizontalLayoutLY, nrow++, 0, 1, 1);
    QHBoxLayout *horizontalLayoutUY = new QHBoxLayout();
    horizontalLayoutUY->addWidget(labelUY);
    horizontalLayoutUY->addWidget(mUpperYValueField);
    horizontalLayoutUY->addWidget(mSetUpperYButton);
    gridLayout->addLayout(horizontalLayoutUY, nrow++, 0, 1, 1);

    //
    //  Axis mode (lin/log) and rotate & scale of plot
    //
    QLabel *labelLinLog = new QLabel(form);
    mLogXRadioButton = new QRadioButton(form);
    mLogYRadioButton = new QRadioButton(form);
    mLogXRadioButton->setAutoExclusive(false);
    mLogYRadioButton->setAutoExclusive(false);  // not a toggle radio button

    QHBoxLayout *horizontalLayoutLinLog = new QHBoxLayout();
    horizontalLayoutLinLog->addWidget(labelLinLog);
    horizontalLayoutLinLog->addWidget(mLogXRadioButton);
    horizontalLayoutLinLog->addWidget(mLogYRadioButton);

    gridLayout->addWidget(new QLabel(form), nrow++, 0, 1, 1);
    gridLayout->addLayout(horizontalLayoutLinLog, nrow++, 0, 1, 1);

    //
    //  Spacers
    //
    QSpacerItem *verticalSpacer = new QSpacerItem(20, 100, QSizePolicy::Minimum, QSizePolicy::Expanding);
    QSpacerItem *horizontalSpacer = new QSpacerItem(50, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);

    gridLayout->addItem(horizontalSpacer, 4, 1, 1, 1);
    gridLayout->addItem(verticalSpacer, nrow, 0, 1, 1);

    //
    //  Set text of all labels
    //
    labelLX->setText(tr("Lower x:"));
    mSetLowerXButton->setText(tr("Set"));
    mLowerXValueField->setText(tr(""));
    labelUX->setText(tr("Upper x:"));
    mSetUpperXButton->setText(tr("Set"));
    mUpperXValueField->setText(tr(""));
    labelLY->setText(tr("Lower y:"));
    mSetLowerYButton->setText(tr("Set"));
    mLowerYValueField->setText(tr(""));
    labelUY->setText(tr("Upper y:"));
    mSetUpperYButton->setText(tr("Set"));
    mUpperYValueField->setText(tr(""));
    mLogXRadioButton->setText(tr("Log x"));
    mLogYRadioButton->setText(tr("Log y"));
    labelLinLog->setText(tr("Lin/Log Scale:"));

    //
    //  Given various languages the size of the text
    //  fields vary and screw up the layout. Hence
    //  we pick one label width making the layout
    //  look cleaner.
    //
    QFontMetrics fontMetrics1(labelLX->font());
    QFontMetrics fontMetrics2(labelUX->font());
    int labelWidth = max(fontMetrics1.horizontalAdvance(labelLX->text()), fontMetrics1.horizontalAdvance(labelUX->text()));
    labelLX->setFixedWidth(labelWidth);
    labelUX->setFixedWidth(labelWidth);
    labelLY->setFixedWidth(labelWidth);
    labelUY->setFixedWidth(labelWidth);
}

void xyscanBaseWindow::createCoordinateToolBoxItem(QWidget *form)
{
     const int lineEditWidth = 150;
     QGridLayout *gridLayout = new QGridLayout(form);
     int nrow = 0;

     //
     //  Pixel coordinates
     //
     QLabel *pixelLabel = new QLabel(form);
     QLabel *xpLabel = new QLabel(form);
     QLabel *ypLabel = new QLabel(form);
     mPixelXDisplay = new QLineEdit(form);
     mPixelXDisplay->setReadOnly(true);
     mPixelXDisplay->setText(tr("N/A"));
     mPixelXDisplay->setFixedWidth(lineEditWidth);
     mPixelYDisplay = new QLineEdit(form);
     mPixelYDisplay->setReadOnly(true);
     mPixelYDisplay->setText(tr("N/A"));
     mPixelYDisplay->setFixedWidth(lineEditWidth);

     QHBoxLayout *horizontalLayoutPX = new QHBoxLayout();
     horizontalLayoutPX->addWidget(xpLabel);
     horizontalLayoutPX->addWidget(mPixelXDisplay);
     QHBoxLayout *horizontalLayoutPY = new QHBoxLayout();
     horizontalLayoutPY->addWidget(ypLabel);
     horizontalLayoutPY->addWidget(mPixelYDisplay);

     gridLayout->addWidget(pixelLabel, nrow++, 0, 1, 1);
     gridLayout->addLayout(horizontalLayoutPX, nrow++, 0, 1, 1);
     gridLayout->addLayout(horizontalLayoutPY, nrow++, 0, 1, 1);
     gridLayout->addWidget(new QLabel(form), nrow++, 0, 1, 1);

     //
     //  Plot coordinates
     //
     QLabel *plotCoordinateLabel = new QLabel(form);
     QLabel *xcLabel = new QLabel(form);
     QLabel *ycLabel = new QLabel(form);
     mPlotXDisplay = new QLineEdit(form);
     mPlotXDisplay->setReadOnly(true);
     mPlotXDisplay->setText(tr("N/A"));
     mPlotXDisplay->setFixedWidth(lineEditWidth);
     mPlotYDisplay = new QLineEdit(form);
     mPlotYDisplay->setReadOnly(true);
     mPlotYDisplay->setText(tr("N/A"));
     mPlotYDisplay->setFixedWidth(lineEditWidth);

     QHBoxLayout *horizontalLayoutCX = new QHBoxLayout();
     horizontalLayoutCX->addWidget(xcLabel);
     horizontalLayoutCX->addWidget(mPlotXDisplay);
     QHBoxLayout *horizontalLayoutCY = new QHBoxLayout();
     horizontalLayoutCY->addWidget(ycLabel);
     horizontalLayoutCY->addWidget(mPlotYDisplay);

     gridLayout->addWidget(plotCoordinateLabel, nrow++, 0, 1, 1);
     gridLayout->addLayout(horizontalLayoutCX, nrow++, 0, 1, 1);
     gridLayout->addLayout(horizontalLayoutCY, nrow++, 0, 1, 1);

     //
     //  Spacers
     //
     QSpacerItem *verticalSpacer = new QSpacerItem(20, 100, QSizePolicy::Minimum, QSizePolicy::Expanding);
     QSpacerItem *horizontalSpacer = new QSpacerItem(100, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);

     gridLayout->addItem(horizontalSpacer, 2, 1, 1, 1);
     gridLayout->addItem(verticalSpacer, nrow, 0, 1, 1);

     //
     //  Set text of all labels
     //
     pixelLabel->setText(tr("Pixel:"));
     xpLabel->setText(tr("x:"));
     ypLabel->setText(tr("y:"));
     plotCoordinateLabel->setText(tr("Plot Coordinates:"));
     xcLabel->setText(tr("x:"));
     ycLabel->setText(tr("y:"));
}

void xyscanBaseWindow::createCrosshair()
{
    QRectF r = mImageScene->sceneRect();
    const int myInfinity = 5000; // big enough for all evantualities

    QPen pen(mCrosshairColor);
    pen.setWidthF(0);

    mCrosshairH = new QGraphicsLineItem(-myInfinity, 0, myInfinity, 0);
    mCrosshairH->setZValue(2);
    mCrosshairH->setPen(pen);
    mCrosshairH->setVisible(false);
    mImageScene->addItem(mCrosshairH);

    mCrosshairV = new QGraphicsLineItem(0, -myInfinity, 0, myInfinity);
    mCrosshairV->setZValue(2);
    mCrosshairV->setPen(pen);
    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 xyscanBaseWindow::createHelpBrowser()
{
    mHelpBrowser = 0;

    //
    //  The documentation is in the docs/ directory.
    //  On MacOS it is the app bundle under Resources,
    //  on Linux typically in /usr/local/share/xyscan.
    //  Here we go through the various options step by step.
    //

    QString path;

    //
    //  1. This is for the developer using xyscan/docs directly
    //  for easier testing and turn around times.
    //
    // path = "/Users/ullrich/Documents/Projects/xyscan/docs";
    // QDir dir(path);
    QDir dir("-");

    //
    //  2. MacOS
    //
    if (!dir.exists()) {
        path = qApp->applicationDirPath() + "/../Resources/docs";
        dir.setPath(path);
    }

    //
    //  3. Windows
    //
    if (!dir.exists()) {
        path = qApp->applicationDirPath() + "/docs";
        dir.setPath(path);
    }
    if (!dir.exists()) {
        path = qApp->applicationDirPath() + "/../../docs";
        dir.setPath(path);
    }

    //
    //  4. Linux
    //
    if (!dir.exists()) {
        path = "/usr/local/share/xyscan/docs";
        dir.setPath(path);
    }
    if (!dir.exists()) {
        path = "/usr/local/share/docs/xyscan";
        dir.setPath(path);
    }
    if (!dir.exists()) {
        path = "/opt/local/share/xyscan/docs";
        dir.setPath(path);
    }
    if (!dir.exists()) {
        path = "/usr/share/doc/xyscan/docs";
        dir.setPath(path);
    }

    //
    //  4. Just in case we check other locations if nothing
    //     was found so far. More an act of desperation.
    //
    if (!dir.exists()) {
        path = "/usr/local/xyscan/docs";
        dir.setPath(path);
    }
    if (!dir.exists()) {
        path = "/opt/local/xyscan/docs";
        dir.setPath(path);
    }
    if (!dir.exists()) {
       path = qApp->applicationDirPath() + "/docs";
       dir.setPath(path);
    }
    if (!dir.exists()) {
        path = qApp->applicationDirPath() + "/../docs";
        dir.setPath(path);
    }
    if (!dir.exists()) {
        path = qApp->applicationDirPath() + "../../docs";
        dir.setPath(path);
    }

    if (!dir.exists()) {
        QMessageBox::warning(0, "xyscan",
                             tr("Cannot find the directory holding the help files. "
                                "No help will be available. "
                                "Check your installation and reinstall if necessary."));
    
    }

    //
    //  Setup the help browser
    //
    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 help system. "
                                "No help will be available. "
                                "Check your installation and reinstall if necessary."));
    }
}

void xyscanBaseWindow::createHistogramDockingWidget()
{
    //
    //  Cursor zoom window.
    //  Made as a docking widget for convinience
    //  but make non-dockable.
    //
    mHistogramDock = new QDockWidget(tr("Density Histogram"), this);
    mHistogramDock->setVisible(false);
    mHistogramDock->setAllowedAreas(Qt::NoDockWidgetArea);

    //
    //  Chart
    //
    mBarSeries = new QBarSeries();

    QBarSet *barSetH = new QBarSet(tr("Horizontal"));
    for (int i=0; i<2*mHistogramRange+1; i++) barSetH->append(0);
    barSetH->setColor(QColor(200,0,0));
    mBarSeries->append(barSetH);

    QBarSet *barSetV = new QBarSet(tr("Vertical"));
    for (int i=0; i<2*mHistogramRange+1; i++) barSetV->append(0);
    barSetV->setColor(QColor(0,0,200));
    mBarSeries->append(barSetV);

    QChart *chart = new QChart();
    chart->addSeries(mBarSeries);
    chart->setTitle(tr("Grayscale Densities Around Cursor"));
    chart->setAnimationOptions(QChart::SeriesAnimations);

    QStringList categories;
    for (int i=0; i<2*mHistogramRange+1; i++) {
        categories.append(QString("%1").arg(i-mHistogramRange));
    }
    QBarCategoryAxis *axisX = new QBarCategoryAxis();
    axisX->append(categories);
    chart->addAxis(axisX, Qt::AlignBottom);
    mBarSeries->attachAxis(axisX);

    QValueAxis *axisY = new QValueAxis();
    axisY->setRange(0,260);
    chart->addAxis(axisY, Qt::AlignLeft);
    mBarSeries->attachAxis(axisY);
    chart->legend()->setVisible(true);
    chart->legend()->setAlignment(Qt::AlignBottom);
    QChartView *chartView = new QChartView(chart);
    chartView->setRenderHint(QPainter::Antialiasing);
    mHistogramDock->setWidget(chartView);

    //
    //   Pulldown menu
    //
    mHistogramModeComboBox = new QComboBox(chartView);
    mHistogramModeComboBox->insertItems(0, QStringList()
                                     << tr("Horizontal and Vertical ")  // leave blanks
                                     << tr("Horizontal Only ")
                                     << tr("Vertical Only ")
                                     );

    //
    //  Rest of docking window setup
    //
    addDockWidget(Qt::TopDockWidgetArea, mHistogramDock); // widget area is only a dummy here
    mHistogramDock->toggleViewAction()->setText(tr("&Density Histogram"));
    mHistogramDock->toggleViewAction()->setShortcut(Qt::CTRL | Qt::Key_G);

    mViewMenu->addAction(mHistogramDock->toggleViewAction());
    mHistogramDock->setFloating(true);
    mHistogramDock->resize(600,400);
}

void xyscanBaseWindow::createMarker()
{
    QColor color = Qt::gray;
    const int myInfinity = 5000; // enough for any possible screen

    for (int i=mXLower; i<= mYUpper; i++) {
        if (i == mXLower || i == mXUpper)
            mMarker[i] = new QGraphicsLineItem(0, -myInfinity, 0, myInfinity);
        else
            mMarker[i] = new QGraphicsLineItem(-myInfinity, 0, myInfinity, 0);
        mMarker[i]->setPen(QPen(color));
        mMarker[i]->setZValue(1);
        mImageScene->addItem(mMarker[i]);
        mMarker[i]->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 xyscanBaseWindow::createMeasureToolBoxItem(QWidget *form)
{
    QGridLayout *gridLayout = new QGridLayout(form);
    QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);

    gridLayout->setHorizontalSpacing(10);
    gridLayout->setVerticalSpacing(10);

    const int maxMeasureNumbersDisplayWidth = 150;
    int nrow = 0;

    //
    //   Start button and path check box
    //
    mStartMeasureButton = new QPushButton(form);
    mStartMeasureButton->setText(tr("Start Measuring"));
    mStartMeasureButton->setCheckable(true);
    mMeasurePathCheckBox = new QCheckBox(tr("Path"), form);
    gridLayout->addWidget(mStartMeasureButton, nrow, 0, 1, 1);
    gridLayout->addWidget(mMeasurePathCheckBox, nrow++, 1, 1, 1);

    //
    //   delta x
    //
    QHBoxLayout *horizontalLayoutDX = new QHBoxLayout();
    QLabel *labelDX = new QLabel(form);
    labelDX->setText(tr("dx:"));
    labelDX->setSizePolicy(sizePolicy);
    QFontMetrics fontMetrics(labelDX->font());
    int labelWidth = fontMetrics.horizontalAdvance(labelDX->text());   // use that width for all distance labels
    labelDX->setFixedWidth(labelWidth);

    mMeasureDXDisplay = new QLineEdit(form);
    mMeasureDXDisplay->setReadOnly(true);
    mMeasureDXDisplay->setText(tr("N/A"));
    mMeasureDXDisplay->setMaximumWidth(maxMeasureNumbersDisplayWidth);

    horizontalLayoutDX->addWidget(labelDX);
    horizontalLayoutDX->addWidget(mMeasureDXDisplay);
    gridLayout->addLayout(horizontalLayoutDX, nrow++, 0, 1, 1);

    //
    //   delta y
    //
    QHBoxLayout *horizontalLayoutDY = new QHBoxLayout();
    QLabel *labelDY = new QLabel(form);
    labelDY->setText(tr("dy:"));
    labelDY->setSizePolicy(sizePolicy);
    labelDY->setFixedWidth(labelWidth);

    mMeasureDYDisplay = new QLineEdit(form);
    mMeasureDYDisplay->setReadOnly(true);
    mMeasureDYDisplay->setText(tr("N/A"));
    mMeasureDYDisplay->setMaximumWidth(maxMeasureNumbersDisplayWidth);

    horizontalLayoutDY->addWidget(labelDY);
    horizontalLayoutDY->addWidget(mMeasureDYDisplay);
    gridLayout->addLayout(horizontalLayoutDY, nrow++, 0, 1, 1);

    //
    //    Radial/Total distance
    //
    QHBoxLayout *horizontalLayoutDR = new QHBoxLayout();
    QLabel *labelDR = new QLabel(form);
    labelDR->setText(tr("r:"));
    labelDR->setSizePolicy(sizePolicy);
    labelDR->setFixedWidth(labelWidth);

    mMeasureDRDisplay = new QLineEdit(form);
    mMeasureDRDisplay->setReadOnly(true);
    mMeasureDRDisplay->setText(tr("N/A"));
    mMeasureDRDisplay->setMaximumWidth(maxMeasureNumbersDisplayWidth);

    horizontalLayoutDR->addWidget(labelDR);
    horizontalLayoutDR->addWidget(mMeasureDRDisplay);
    gridLayout->addLayout(horizontalLayoutDR, nrow++, 0, 1, 1);

    //
    //    Angle
    //
    QHBoxLayout *horizontalLayoutDA = new QHBoxLayout();
    QLabel *labelDA = new QLabel(form);
#if defined(Q_OS_WIN)
    labelDA->setText(QString::fromWCharArray(L"\u2220:"));
#else
    labelDA->setText(QString::fromUtf8("\u2220:"));
#endif
    labelDA->setSizePolicy(sizePolicy);
    labelDA->setFixedWidth(labelWidth);

    mMeasureDADisplay = new QLineEdit(form);
    mMeasureDADisplay->setReadOnly(true);
    mMeasureDADisplay->setText(tr("N/A"));
    mMeasureDADisplay->setMaximumWidth(maxMeasureNumbersDisplayWidth);

    horizontalLayoutDA->addWidget(labelDA);
    horizontalLayoutDA->addWidget(mMeasureDADisplay);

    gridLayout->addLayout(horizontalLayoutDA, nrow, 0, 1, 1);

    //
    //  Check box to select radians instead of degrees
    //
    mMeasureAngleRadiansCheckBox = new QCheckBox(tr("Radians"), form);
    gridLayout->addWidget(mMeasureAngleRadiansCheckBox, nrow++, 1, 1, 1);

    //
    //  Paths (adding measures)
    //  Temporary layout only - will see what we need
    //  and streamline later.
    //
    QHBoxLayout *horizontalLayoutPath = new QHBoxLayout();
    QLabel *labelPath = new QLabel(form);
    labelPath->setText(tr("L:"));
    labelPath->setSizePolicy(sizePolicy);
    labelPath->setFixedWidth(labelWidth);

    mMeasurePathDisplay = new QLineEdit(form);
    mMeasurePathDisplay->setReadOnly(true);
    mMeasurePathDisplay->setText(tr("N/A"));
    mMeasurePathDisplay->setMaximumWidth(maxMeasureNumbersDisplayWidth);

    horizontalLayoutPath->addWidget(labelPath);
    horizontalLayoutPath->addWidget(mMeasurePathDisplay);

    gridLayout->addLayout(horizontalLayoutPath, nrow, 0, 1, 1);


    mClearMeasuredPathButton = new QPushButton(form);
    mClearMeasuredPathButton->setText(tr("Reset"));
    gridLayout->addWidget(mClearMeasuredPathButton, nrow++, 1, 1, 1);


    //
    //  Units Selector
    //  There's a slight problem with the combo box that
    //  items in the pop-up list get truncated depending
    //  on the language. This is fixed with the
    //  QComboBox::setMinimumContentsLength() line. Why
    //  this is not handled properly nrow by default is a bit
    //  unclear.
    //
    QLabel *labelUS = new QLabel(form);
    labelUS->setSizePolicy(sizePolicy);
    labelUS->setText(tr("Units:"));

    mMeasureScaleValueLabel = new QLabel(form);
    mMeasureScaleValueLabel->setSizePolicy(sizePolicy);
    mMeasureScaleValueLabel->setText(tr("Scale:", "short"));
    mMeasureScaleValueLabel->setVisible(false);

    mMeasureScaleComboBox = new QComboBox(form);
    mMeasureScaleComboBox->clear();
    QStringList items = { tr("Pixel"), tr("Plot Units"), tr("User Scale") };
    mMeasureScaleComboBox->insertItems(0, items);
    int maxChar = max(max(items.at(0).length(), items.at(1).length()), items.at(2).length());
    mMeasureScaleComboBox->setMinimumContentsLength(maxChar-1); // -1 seems long enough to see in full
    mMeasureScaleComboBox->setCurrentIndex(mPixelUnits);
    mMeasureScaleComboBox->setDisabled(true);
    mMeasureUserScaleField = new QLineEdit(form);
    mMeasureUserScaleField->setText(locale().toString(1.0));
    mMeasureUserScaleField->setVisible(false);
    mMeasureUserScaleField->setMaximumWidth(maxMeasureNumbersDisplayWidth);

    QHBoxLayout *horizontalLayoutUS = new QHBoxLayout();
    horizontalLayoutUS->addWidget(labelUS);
    horizontalLayoutUS->addWidget(mMeasureScaleComboBox);
    gridLayout->addLayout(horizontalLayoutUS, nrow++, 0, 1, 1);
    QHBoxLayout *horizontalLayoutSV = new QHBoxLayout();
    horizontalLayoutSV->addWidget(mMeasureScaleValueLabel);
    horizontalLayoutSV->addWidget(mMeasureUserScaleField);
    gridLayout->addLayout(horizontalLayoutSV, nrow++, 0, 1, 1);

    //
    //  Spacers
    //
    QSpacerItem *verticalSpacer = new QSpacerItem(20, 100, QSizePolicy::Minimum, QSizePolicy::Expanding);
    QSpacerItem *horizontalSpacer = new QSpacerItem(100, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);

    gridLayout->addItem(horizontalSpacer, 2, 1, 1, 1);
    gridLayout->addItem(verticalSpacer, nrow, 0, 1, 1);
}


void xyscanBaseWindow::createMenuActions()
{
    mOpenAction = new QAction(tr("&Open..."), this);
    mOpenAction->setShortcut(QKeySequence::Open);

    for (int i = 0; i < mMaxRecentFiles; ++i) {
        mRecentFileAction[i] = new QAction(this);
        mRecentFileAction[i]->setVisible(false);
    }

    mSignificantDigitsActionGroup = new QActionGroup(this);
    for (int i = 0; i < mMaxSignificantDigits; ++i) {
        mSignificantDigitsAction[i] = new QAction(this);
        mSignificantDigitsAction[i]->setCheckable(true);
        mSignificantDigitsActionGroup->addAction(mSignificantDigitsAction[i]);
    }
    mSignificantDigitsActionGroup->setExclusive(true);

    mClearHistoryAction = new QAction(tr("&Clear History"), this);

    mSaveAction = new QAction(tr("&Save..."), this);
    mSaveAction->setShortcut(QKeySequence::Save); // Qt::CTRL + Qt::Key_S);

    mPrintAction = new QAction(tr("&Print..."), this);
    mPrintAction->setShortcut(QKeySequence::Print);

    mFinishAction = new QAction(tr("&Quit xyscan"), this);
    mFinishAction->setShortcut(QKeySequence::Quit);

    mPasteImageAction = new QAction(tr("&Paste Image"), this);
    mPasteImageAction->setShortcut(QKeySequence::Paste);

    mEditCrosshairColorAction = new QAction(tr("Crosshair Color..."), this);

    mEditMarkerColorAction = new QAction(tr("Marker Color..."), this);

    mEditPathColorAction = new QAction(tr("Path Color..."), this);

    mDeleteLastAction = new QAction(tr("Delete &Last Point"), this);
    mDeleteLastAction->setShortcut(QKeySequence::Undo);

    mDeleteAllAction = new QAction(tr("Clear Table"), this);

    mEditCommentAction = new QAction(tr("&Comment..."), this);
    mEditCommentAction->setShortcut(QKeySequence::AddTab);

    mShowPrecisionAction = new QAction(tr("&Current Precision"), this);
    mShowPrecisionAction->setShortcut(Qt::CTRL | Qt::Key_I);

    mScreenScanAction = new QAction(tr("Scan Screen"), this);
    mScreenScanAction->setCheckable(true);
    mScreenScanAction->setChecked(false);

    mAboutAction = new QAction(tr("&About xyscan"), this);
#if defined(Q_OS_MAC)
    mAboutAction->setMenuRole(QAction::AboutRole);
#endif

    mHelpAction = new QAction(tr("xyscan &Help"), this);

    mShowTooltipsAction = new QAction(tr("&Tool Tips"), this);
    mShowTooltipsAction->setCheckable(true);
    mShowTooltipsAction->setChecked(false);

    mCheckForUpdatesAction = new QAction(tr("&Check For Updates ..."), this);
}

void xyscanBaseWindow::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(mScreenScanAction);
    mFileMenu->addSeparator();
    mFileMenu->addAction(mPrintAction);
#if !defined(Q_OS_MAC)
    mFileMenu->addSeparator();
    mFileMenu->addAction(mFinishAction);
#endif

    mEditMenu = menuBar()->addMenu(tr("&Edit"));
    mEditMenu->addAction(mEditCrosshairColorAction);
    mEditMenu->addAction(mEditMarkerColorAction);
    mEditMenu->addAction(mEditPathColorAction);
    QLocale local;
    mSignificantDigitsMenu = mEditMenu->addMenu(tr("Significant Digits")); // submenu
    for (unsigned int i = 0; i < mMaxSignificantDigits; ++i) {
        mSignificantDigitsAction[i]->setText(local.toString(i));
        if (i == mSignificantDigits) mSignificantDigitsAction[i]->setChecked(true);
        mSignificantDigitsMenu->addAction(mSignificantDigitsAction[i]);
    }
    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_OS_MAC)
    mHelpMenu->addSeparator();
#endif
    mHelpMenu->addAction(mAboutAction);  // In application menu on MacOS/X
}

void xyscanBaseWindow::createStatusBar()
{
    mMessageLabel = new QLabel(this);
    mMessageLabel->setAlignment(Qt::AlignLeft);
    mMessageLabel->setIndent(4);

    mXCoordinateLabel = new QLabel(" ", this);
    mXCoordinateLabel->setAlignment(Qt::AlignLeft);
    mXCoordinateLabel->setMinimumWidth(statusBar()->fontMetrics().horizontalAdvance("000.000e-00"));
    mXCoordinateLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken);

    mYCoordinateLabel = new QLabel(" ", this);
    mYCoordinateLabel->setAlignment(Qt::AlignLeft);
    mYCoordinateLabel->setMinimumWidth(statusBar()->fontMetrics().horizontalAdvance("000.000e-00"));
    mYCoordinateLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken);

    mMeasuredDistanceLabel = new QLabel(" ", this);
    mMeasuredDistanceLabel->setAlignment(Qt::AlignLeft);
    mMeasuredDistanceLabel->setMinimumWidth(statusBar()->fontMetrics().horizontalAdvance("000.000e-00"));
    mMeasuredDistanceLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken);
    mMeasuredDistanceLabel->setVisible(false);
    QPalette palette;
    palette.setColor(QPalette::WindowText, Qt::blue);
    mMeasuredDistanceLabel->setPalette(palette);

    statusBar()->addWidget(mMessageLabel, 1);  // not permanent
    statusBar()->addPermanentWidget(mXCoordinateLabel, 0);
    statusBar()->addPermanentWidget(mYCoordinateLabel, 0);
    statusBar()->addPermanentWidget(mMeasuredDistanceLabel, 0);
    statusBar()->setSizeGripEnabled(false);

    mMessageLabel->setText(tr("Ready"));
}

void xyscanBaseWindow::createTableDockingWidget()
{
    mTableDock = new QDockWidget(tr("Data Table"), this);
    mTableDock->setVisible(false);
    mTableDock->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea);
    QWidget *tableDockContents = new QWidget();

    mDataTable = new xyscanDataTable(tableDockContents);

    QGridLayout *gridLayout = new QGridLayout(tableDockContents);
    gridLayout->addWidget(mDataTable, 0, 0, 1, 1);

    mTableDock->setWidget(tableDockContents);
    addDockWidget(Qt::BottomDockWidgetArea, mTableDock);
    mTableDock->toggleViewAction()->setText(tr("&Data Table"));
    mTableDock->toggleViewAction()->setShortcut(Qt::CTRL | Qt::Key_D );

    mViewMenu->addAction(mTableDock->toggleViewAction());
    mTableDock->setFloating(true);
    mTableDock->resize(800,300);
}

void xyscanBaseWindow::createToolBox()
{
    mPlotAdjustmentItem = new QWidget();
    mAxisSettingsItem = new QWidget();
    mErrorScanModeItem = new QWidget();
    mCoordinatesItem = new QWidget();
    mMeasureItem = new QWidget();

    mToolBox->addItem(mPlotAdjustmentItem, QString(tr("Image Info and Adjustment")));
    mToolBox->addItem(mAxisSettingsItem, QString(tr("Marker and Axis Settings")));
    mToolBox->addItem(mErrorScanModeItem, QString(tr("Error Bar Scan Mode")));
    mToolBox->addItem(mCoordinatesItem, QString(tr("Coordinate Display")));
    mToolBox->addItem(mMeasureItem, QString(tr("Measure Tool")));

    mToolBox->setCurrentIndex(0);

    createAdjustmentToolBoxItem(mPlotAdjustmentItem);
    createAxisSettingsToolBoxItem(mAxisSettingsItem);
    createCoordinateToolBoxItem(mCoordinatesItem);
    createMeasureToolBoxItem(mMeasureItem);
    mErrorBarScanModeToolBox = new xyscanErrorBarScanModeToolBox(mErrorScanModeItem);
}

void xyscanBaseWindow::createToolTips()
{
#if defined(Q_OS_MAC)
    QString platformCtrl(QString::fromUtf8("\u2318"));
#else
    QString platformCtrl(tr("Ctrl"));
#endif

    //
    //    General
    //
    mImageView->setToolTip(tr("Scan area. The loaded graphics\n"
                              "is displayed here."));
    if (mHelpBrowser) mHelpBrowser->setToolTip(tr("Help and Documentation for xyscan."));
    mMessageLabel->setToolTip(tr("Status bar. Displays instruction during scanning."));
    mCrosshairH->setToolTip(tr("Horizontal bar of crosshairs cursor."));
    mCrosshairV->setToolTip(tr("Vertical bar of crosshairs cursor."));
    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."));

    //
    //   Measure tool
    //
    mMeasureItem->setToolTip(tr("Measure Tool Window:\n"
                                "Allows to measure distances and paths in plots and maps."));
    mStartMeasureButton->setToolTip(tr("Starts and stops measuring."));
    mMeasurePathCheckBox->setToolTip(tr("If checked, will measure along a path (polyline)\n"
                                        "instead measuring a point-to-point distance.\n"
                                        "The points on the path are set by double-clicking\n"
                                        "the mouse."));
    mMeasureDXDisplay->setToolTip(tr("While measuring, displays the horizontal distance (dx)."));
    mMeasureDYDisplay->setToolTip(tr("While measuring, displays the vertical distance (dy)."));
    mMeasureDRDisplay->setToolTip(tr("While measuring, displays the radial distance (dr)."));
    mMeasureDADisplay->setToolTip(tr("While measuring, displays the angle w.r.t. the horizontal axis."));
    mMeasureAngleRadiansCheckBox->setToolTip(tr("Select if angles should be displayed in radians\n"
                                                "instead of degrees."));
    mMeasureScaleComboBox->setToolTip(tr("Unit Selector:\n"
                                         "Select the units in which the distance is displayed."));
    mMeasureUserScaleField->setToolTip(tr("User Scale:\n"
                                          "Allows to enter a scale in which the measured\n"
                                          "distances will be expressed. The shown distances\n"
                                          "are calculated by multiplying this scale by the\n"
                                          "measured length in pixel. If you have an object in\n"
                                          "the plot of which you know the length in your desired\n"
                                          "units (e.g., cm, miles) you can measure it using the\n"
                                          "measure tool (let it sit at the right distance) and\n"
                                          "then enter the length of that object in its own units\n"
                                          "and press Shift+Enter. This will then automatically\n"
                                          "calculate, set, and display the correct scale."));
    mMeasurePathDisplay->setToolTip(tr("Shows the measured path length."));
    mClearMeasuredPathButton->setToolTip(tr("Resets the path length and deletes the path\n"
                                            "superimposed on the image."));
    //
    //   Zoom window
    //
    mZoomDock->setToolTip(tr("Cursor Zoom Window:\n"
                             "Displays a zoomed view of the area\n"
                             "surrounding the cross-hair allowing\n"
                             "for higher scan precision."));
    mZoomSpinBox->setToolTip(tr("Set the zoom scale."));

    //
    //   Histogram window
    //
    mHistogramDock->setToolTip(tr("Histogram Window:\n"
                             "Displays a histogram that indicates the\n"
                             "grayscale densities around the cursor\n"
                             "position."));
    mHistogramModeComboBox->setToolTip(tr("Selects which data to show in the histogram."));

    //
    //   Coordinates
    //
    mCoordinatesItem->setToolTip(tr("Coordinates Window:\n"
                                    "Displays cursor position in local\n"
                                    "(pixel) and plot coordinates.\n"
                                    "Shortcut is %1+C.").arg(platformCtrl));
    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."));

    //
    //    Axis settings
    //
    mAxisSettingsItem->setToolTip(tr("Axis Settings Window:\n"
                                     "Use to set axes marker\n"
                                     "and set log/lin scales.\n"
                                     "Shortcut is %1+A.").arg(platformCtrl));
    mSetLowerXButton->setToolTip(tr("Set value of marker for the lower x-position.\n"
                                    "Launches input dialog for the referring value in plot coordinates.\n"
                                    "Shortcut is %1+1.").arg(platformCtrl));
    mSetUpperXButton->setToolTip(tr("Set value of marker for the upper x-position.\n"
                                    "Launches input dialog for the referring value in plot coordinates.\n"
                                    "Shortcut is %1+2.").arg(platformCtrl));
    mSetLowerYButton->setToolTip(tr("Set value of marker for the lower y-position.\n"
                                    "Launches input dialog for the referring value in plot coordinates.\n"
                                    "Shortcut is %1+3.").arg(platformCtrl));
    mSetUpperYButton->setToolTip(tr("Set value of marker for the upper y-position.\n"
                                    "Launches input dialog for the referring value in plot coordinates.\n"
                                    "Shortcut is %1+4.").arg(platformCtrl));
    mLowerXValueField->setToolTip(tr("x-axis value in plot coordinates assigned to the low-x marker (read only)."));
    mUpperXValueField->setToolTip(tr("x-axis value in plot coordinates assigned to the upper-x marker (read only)."));
    mLowerYValueField->setToolTip(tr("y-axis value in plot coordinates assigned to the low-y marker (read only)."));
    mUpperYValueField->setToolTip(tr("y-axis value in plot coordinates assigned to the upper-y marker (read only)."));
    mLogXRadioButton->setToolTip(tr("Check when x-axis on plot has log scale."));
    mLogYRadioButton->setToolTip(tr("Check when y-axis on plot has log scale."));

    //
    //    Plot adjustments
    //
    mPlotAdjustmentItem->setToolTip(tr("Image Info and Adjustment:\n"
                                       "Window displaying info on the current image and controls\n"
                                       "that allows one to scale (zoom in/out) and rotate the plot."));
    mAngleSpinBox->setToolTip(tr("Rotate current image (degrees)."));
    mScaleSpinBox->setToolTip(tr("Scale (zoom in/out) the current image."));
    mPlotInfoLabel->setToolTip(tr("Show dimension and depth of current image."));

    //
    //   Errors
    //
    mErrorScanModeItem->setToolTip(tr("Error Bar Settings:\n"
                                      "Define how to scan error bars."));

    //
    //    Data table
    //
    mTableDock->setToolTip(tr("Data Table Window:\n"
                              "Window displaying the data table that holds \n "
                              "all points (and error bars) scanned so far."));
    mDataTable->setToolTip(tr("Data table holding all points scanned so far."));

    //
    //   Actions (don't show on the Mac)
    //
    mOpenAction->setToolTip(tr("Open file to read in image."));
    mScreenScanAction->setToolTip(tr("Scan screen directly without loading image.\n"
                                     "xyscan will turn semi-transparent."));
    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."));
    
    //
    // Mirror page tooltips onto the QToolBox tabs:
    //
    auto setTabTip = [this](QWidget* page) {
        int idx = mToolBox->indexOf(page);
        if (idx != -1)
            mToolBox->setItemToolTip(idx, page->toolTip());
    };

    setTabTip(mPlotAdjustmentItem);  // "Image Info and Adjustment"
    setTabTip(mAxisSettingsItem);    // "Marker and Axis Settings"
    setTabTip(mErrorScanModeItem);   // "Error Bar Scan Mode"
    setTabTip(mCoordinatesItem);     // "Coordinates"
    setTabTip(mMeasureItem);         // "Measure"
}

void xyscanBaseWindow::createZoomDockingWidget()
{
    //
    //  Cursor zoom window.
    //  Made as a docking widget for convinience
    //  but make non-dockable.
    //
    mZoomDock = new QDockWidget(tr("Cursor Zoom"), this);
    mZoomDock->setVisible(false);
    mZoomDock->setAllowedAreas(Qt::NoDockWidgetArea);

    mZoomView = new QGraphicsView(mImageScene, mZoomDock);
    mZoomDock->setWidget(mZoomView);

    addDockWidget(Qt::TopDockWidgetArea, mZoomDock); // widget area is only a dummy here
    mZoomDock->toggleViewAction()->setText(tr("&Cursor Zoom"));
    mZoomDock->toggleViewAction()->setShortcut(Qt::CTRL | Qt::Key_Y);

    mViewMenu->addAction(mZoomDock->toggleViewAction());
    mZoomDock->setFloating(true);
    mZoomDock->resize(200,200);

    mZoomScale = 8;
    mZoomView->scale(mZoomScale,mZoomScale);

    mZoomSpinBox = new QDoubleSpinBox(mZoomView);
    mZoomSpinBox->setDecimals(1);
    mZoomSpinBox->setMinimum(1);
    mZoomSpinBox->setSingleStep(0.5);
    mZoomSpinBox->setValue(mZoomScale);
}

