/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <stdlib.h>
#include <o3tl/sprintf.hxx>
#include <tools/config.hxx>
#include <unotools/resmgr.hxx>
#include <vcl/bitmap.hxx>
#include <vcl/customweld.hxx>
#include <vcl/dibtools.hxx>
#include <vcl/lineinfo.hxx>
#include <vcl/weld.hxx>
#include <vcl/svapp.hxx>
#include <vcl/event.hxx>
#include "sanedlg.hxx"
#include "grid.hxx"
#include <math.h>
#include <sal/macros.h>
#include <sal/log.hxx>
#include <rtl/strbuf.hxx>
#include <memory>
#include <strings.hrc>

#define PREVIEW_WIDTH       113
#define PREVIEW_HEIGHT      160

#define RECT_SIZE_PIX 7

namespace {

void DrawRect(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect)
{
    tools::Rectangle aRect(rRect);
    rRenderContext.SetFillColor(COL_BLACK);
    rRenderContext.SetLineColor();
    rRenderContext.DrawRect(aRect);
    aRect.Move(1, 1);
    aRect.AdjustRight(-2);
    aRect.AdjustBottom(-2);
    rRenderContext.SetFillColor();
    rRenderContext.SetLineColor(COL_WHITE);
    rRenderContext.DrawRect(aRect);
}

void DrawRectangles(vcl::RenderContext& rRenderContext, Point const & rUL, Point const & rBR)
{
    Point aUR(rBR.X(), rUL.Y());
    Point aBL(rUL.X(), rBR.Y());
    int nMiddleX = (rBR.X() - rUL.X()) / 2 + rUL.X();
    int nMiddleY = (rBR.Y() - rUL.Y()) / 2 + rUL.Y();

    rRenderContext.SetLineColor(COL_WHITE);
    rRenderContext.DrawLine(rUL, aBL);
    rRenderContext.DrawLine(aBL, rBR);
    rRenderContext.DrawLine(rBR, aUR);
    rRenderContext.DrawLine(aUR, rUL);

    rRenderContext.SetLineColor(COL_BLACK);
    LineInfo aInfo(LineStyle::Dash, 1);
    aInfo.SetDistance(8);
    aInfo.SetDotLen(4);
    aInfo.SetDotCount(1);
    rRenderContext.DrawLine(rUL, aBL, aInfo);
    rRenderContext.DrawLine(aBL, rBR, aInfo);
    rRenderContext.DrawLine(rBR, aUR, aInfo);
    rRenderContext.DrawLine(aUR, rUL, aInfo);

    Size aSize(RECT_SIZE_PIX, RECT_SIZE_PIX);
    DrawRect(rRenderContext, tools::Rectangle(rUL, aSize));
    DrawRect(rRenderContext, tools::Rectangle(Point(aBL.X(), aBL.Y() - RECT_SIZE_PIX), aSize));
    DrawRect(rRenderContext, tools::Rectangle(Point(rBR.X() - RECT_SIZE_PIX, rBR.Y() - RECT_SIZE_PIX), aSize));
    DrawRect(rRenderContext, tools::Rectangle(Point(aUR.X() - RECT_SIZE_PIX, aUR.Y()), aSize));
    DrawRect(rRenderContext, tools::Rectangle(Point(nMiddleX - RECT_SIZE_PIX / 2, rUL.Y()), aSize));
    DrawRect(rRenderContext, tools::Rectangle(Point(nMiddleX - RECT_SIZE_PIX / 2, rBR.Y() - RECT_SIZE_PIX), aSize));
    DrawRect(rRenderContext, tools::Rectangle(Point(rUL.X(), nMiddleY - RECT_SIZE_PIX / 2), aSize));
    DrawRect(rRenderContext, tools::Rectangle(Point(rBR.X() - RECT_SIZE_PIX, nMiddleY - RECT_SIZE_PIX / 2), aSize));
}

}

class ScanPreview : public weld::CustomWidgetController
{
private:
    enum DragDirection { TopLeft, Top, TopRight, Right, BottomRight, Bottom,
                         BottomLeft, Left };

    Bitmap    maPreviewBitmap;
    tools::Rectangle maPreviewRect;
    Point     maTopLeft, maBottomRight;
    Point     maMinTopLeft, maMaxBottomRight;
    SaneDlg*  mpParentDialog;
    DragDirection meDragDirection;
    bool      mbDragEnable;
    bool      mbIsDragging;

public:
    ScanPreview()
        : maMaxBottomRight(PREVIEW_WIDTH,  PREVIEW_HEIGHT)
        , mpParentDialog(nullptr)
        , meDragDirection(TopLeft)
        , mbDragEnable(false)
        , mbIsDragging(false)
    {
    }

    void Init(SaneDlg *pParent)
    {
        mpParentDialog = pParent;
    }

    void ResetForNewScanner()
    {
        maTopLeft = Point();
        maBottomRight = Point();
        maMinTopLeft = Point();
        maMaxBottomRight = Point(PREVIEW_WIDTH,  PREVIEW_HEIGHT);
    }

    void EnableDrag()
    {
        mbDragEnable = true;
    }

    void DisableDrag()
    {
        mbDragEnable = false;
    }

    bool IsDragEnabled() const
    {
        return mbDragEnable;
    }

    virtual void Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) override;
    virtual bool MouseButtonDown(const MouseEvent& rMEvt) override;
    virtual bool MouseMove(const MouseEvent& rMEvt) override;
    virtual bool MouseButtonUp(const MouseEvent& rMEvt) override;
    Point GetPixelPos(const Point& rIn) const;
    Point GetLogicPos(const Point& rIn) const;

    void GetPreviewLogicRect(Point& rTopLeft, Point &rBottomRight) const
    {
        rTopLeft = GetLogicPos(maTopLeft);
        rBottomRight = GetLogicPos(maBottomRight);
    }
    void GetMaxLogicRect(Point& rTopLeft, Point &rBottomRight) const
    {
        rTopLeft = maMinTopLeft;
        rBottomRight = maMaxBottomRight;

    }
    void ChangePreviewLogicTopLeftY(tools::Long Y)
    {
        Point aPoint(0, Y);
        aPoint = GetPixelPos(aPoint);
        maTopLeft.setY( aPoint.Y() );
    }
    void ChangePreviewLogicTopLeftX(tools::Long X)
    {
        Point aPoint(X, 0);
        aPoint = GetPixelPos(aPoint);
        maTopLeft.setX( aPoint.X() );
    }
    void ChangePreviewLogicBottomRightY(tools::Long Y)
    {
        Point aPoint(0, Y);
        aPoint = GetPixelPos(aPoint);
        maBottomRight.setY( aPoint.Y() );
    }
    void ChangePreviewLogicBottomRightX(tools::Long X)
    {
        Point aPoint(X, 0);
        aPoint = GetPixelPos(aPoint);
        maBottomRight.setX( aPoint.X() );
    }
    void SetPreviewLogicRect(const Point& rTopLeft, const Point &rBottomRight)
    {
        maTopLeft = GetPixelPos(rTopLeft);
        maBottomRight = GetPixelPos(rBottomRight);
        maPreviewRect = tools::Rectangle(maTopLeft,
                                  Size(maBottomRight.X() - maTopLeft.X(),
                                       maBottomRight.Y() - maTopLeft.Y()));
    }
    void SetPreviewMaxRect(const Point& rTopLeft, const Point &rBottomRight)
    {
        maMinTopLeft = rTopLeft;
        maMaxBottomRight = rBottomRight;
    }
    void DrawDrag(vcl::RenderContext& rRenderContext);
    void UpdatePreviewBounds();
    void SetBitmap(SvStream &rStream)
    {
        ReadDIBBitmapEx(maPreviewBitmap, rStream, true);
    }
    virtual void SetDrawingArea(weld::DrawingArea* pDrawingArea) override
    {
        Size aSize(pDrawingArea->get_ref_device().LogicToPixel(Size(PREVIEW_WIDTH, PREVIEW_HEIGHT), MapMode(MapUnit::MapAppFont)));
        aSize.setWidth(aSize.getWidth()+1);
        aSize.setHeight(aSize.getHeight()+1);
        pDrawingArea->set_size_request(aSize.Width(), aSize.Height());
        CustomWidgetController::SetDrawingArea(pDrawingArea);
        SetOutputSizePixel(aSize);
    }
};

SaneDlg::SaneDlg(weld::Window* pParent, Sane& rSane, bool bScanEnabled)
    : GenericDialogController(pParent, u"modules/scanner/ui/sanedialog.ui"_ustr, u"SaneDialog"_ustr)
    , mpParent(pParent)
    , mrSane(rSane)
    , mbScanEnabled(bScanEnabled)
    , mnCurrentOption(0)
    , mnCurrentElement(0)
    , mfMin(0.0)
    , mfMax(0.0)
    , doScan(false)
    , mxCancelButton(m_xBuilder->weld_button(u"cancel"_ustr))
    , mxDeviceInfoButton(m_xBuilder->weld_button(u"deviceInfoButton"_ustr))
    , mxPreviewButton(m_xBuilder->weld_button(u"previewButton"_ustr))
    , mxScanButton(m_xBuilder->weld_button(u"ok"_ustr))
    , mxButtonOption(m_xBuilder->weld_button(u"optionsButton"_ustr))
    , mxOptionTitle(m_xBuilder->weld_label(u"optionTitleLabel"_ustr))
    , mxOptionDescTxt(m_xBuilder->weld_label(u"optionsDescLabel"_ustr))
    , mxVectorTxt(m_xBuilder->weld_label(u"vectorLabel"_ustr))
    , mxLeftField(m_xBuilder->weld_metric_spin_button(u"leftSpinbutton"_ustr, FieldUnit::PIXEL))
    , mxTopField(m_xBuilder->weld_metric_spin_button(u"topSpinbutton"_ustr, FieldUnit::PIXEL))
    , mxRightField(m_xBuilder->weld_metric_spin_button(u"rightSpinbutton"_ustr, FieldUnit::PIXEL))
    , mxBottomField(m_xBuilder->weld_metric_spin_button(u"bottomSpinbutton"_ustr, FieldUnit::PIXEL))
    , mxDeviceBox(m_xBuilder->weld_combo_box(u"deviceCombobox"_ustr))
    , mxReslBox(m_xBuilder->weld_combo_box(u"reslCombobox"_ustr))
    , mxAdvancedBox(m_xBuilder->weld_check_button(u"advancedCheckbutton"_ustr))
    , mxVectorBox(m_xBuilder->weld_spin_button(u"vectorSpinbutton"_ustr))
    , mxQuantumRangeBox(m_xBuilder->weld_combo_box(u"quantumRangeCombobox"_ustr))
    , mxStringRangeBox(m_xBuilder->weld_combo_box(u"stringRangeCombobox"_ustr))
    , mxBoolCheckBox(m_xBuilder->weld_check_button(u"boolCheckbutton"_ustr))
    , mxStringEdit(m_xBuilder->weld_entry(u"stringEntry"_ustr))
    , mxNumericEdit(m_xBuilder->weld_entry(u"numericEntry"_ustr))
    , mxOptionBox(m_xBuilder->weld_tree_view(u"optionSvTreeListBox"_ustr))
    , mxPreview(new ScanPreview)
    , mxPreviewWnd(new weld::CustomWeld(*m_xBuilder, u"preview"_ustr, *mxPreview))
{
    Size aSize(mxOptionBox->get_approximate_digit_width() * 32, mxOptionBox->get_height_rows(8));
    mxOptionTitle->set_size_request(aSize.Width(), aSize.Height() / 2);
    mxOptionBox->set_size_request(aSize.Width(), aSize.Height());
    mxPreview->Init(this);
    if( Sane::IsSane() )
    {
        InitDevices(); // opens first sane device
        DisableOption();
        InitFields();
    }

    mxDeviceInfoButton->connect_clicked( LINK( this, SaneDlg, ClickBtnHdl ) );
    mxPreviewButton->connect_clicked( LINK( this, SaneDlg, ClickBtnHdl ) );
    mxScanButton->connect_clicked( LINK( this, SaneDlg, ClickBtnHdl ) );
    mxButtonOption->connect_clicked( LINK( this, SaneDlg, ClickBtnHdl ) );
    mxDeviceBox->connect_changed( LINK( this, SaneDlg, SelectHdl ) );
    mxOptionBox->connect_selection_changed(LINK(this, SaneDlg, OptionsBoxSelectHdl));
    mxCancelButton->connect_clicked( LINK( this, SaneDlg, ClickBtnHdl ) );
    mxBoolCheckBox->connect_toggled( LINK( this, SaneDlg, ToggleBtnHdl ) );
    mxStringEdit->connect_changed( LINK( this, SaneDlg, ModifyHdl ) );
    mxNumericEdit->connect_changed( LINK( this, SaneDlg, ModifyHdl ) );
    mxVectorBox->connect_changed( LINK( this, SaneDlg, ModifyHdl ) );
    mxReslBox->connect_changed( LINK( this, SaneDlg, ValueModifyHdl ) );
    mxStringRangeBox->connect_changed( LINK( this, SaneDlg, SelectHdl ) );
    mxQuantumRangeBox->connect_changed( LINK( this, SaneDlg, SelectHdl ) );
    mxLeftField->connect_value_changed( LINK( this, SaneDlg, MetricValueModifyHdl ) );
    mxRightField->connect_value_changed( LINK( this, SaneDlg, MetricValueModifyHdl) );
    mxTopField->connect_value_changed( LINK( this, SaneDlg, MetricValueModifyHdl) );
    mxBottomField->connect_value_changed( LINK( this, SaneDlg, MetricValueModifyHdl) );
    mxAdvancedBox->connect_toggled( LINK( this, SaneDlg, ToggleBtnHdl ) );

    maOldLink = mrSane.SetReloadOptionsHdl( LINK( this, SaneDlg, ReloadSaneOptionsHdl ) );
}

SaneDlg::~SaneDlg()
{
    mrSane.SetReloadOptionsHdl(maOldLink);
}

namespace {

OUString SaneResId(TranslateId aID)
{
    return Translate::get(aID, Translate::Create("pcr"));
}

}

short SaneDlg::run()
{
    if (!Sane::IsSane())
    {
        std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(mpParent,
                                                       VclMessageType::Warning, VclButtonsType::Ok,
                                                       SaneResId(STR_COULD_NOT_BE_INIT)));
        xErrorBox->run();
        return RET_CANCEL;
    }
    LoadState();
    return GenericDialogController::run();
}

void SaneDlg::InitDevices()
{
    if( ! Sane::IsSane() )
        return;

    if( mrSane.IsOpen() )
        mrSane.Close();
    mrSane.ReloadDevices();
    mxDeviceBox->clear();
    for (int i = 0; i < Sane::CountDevices(); ++i)
        mxDeviceBox->append_text(Sane::GetName(i));
    if( Sane::CountDevices() )
    {
        mrSane.Open(0);
        mxDeviceBox->set_active(0);
    }
}

void SaneDlg::InitFields()
{
    if( ! Sane::IsSane() )
        return;

    int nOption, i, nValue;
    double fValue;
    const char *ppSpecialOptions[] = {
        "resolution",
        "tl-x",
        "tl-y",
        "br-x",
        "br-y",
        "preview"
    };

    mxPreview->EnableDrag();
    mxReslBox->clear();
    Point aTopLeft, aBottomRight;
    mxPreview->GetPreviewLogicRect(aTopLeft, aBottomRight);
    Point aMinTopLeft, aMaxBottomRight;
    mxPreview->GetMaxLogicRect(aMinTopLeft, aMaxBottomRight);
    mxScanButton->set_visible( mbScanEnabled );

    if( ! mrSane.IsOpen() )
        return;

    // set Resolution
    nOption = mrSane.GetOptionByName( "resolution" );
    if( nOption != -1 )
    {
        double fRes;

        if( mrSane.GetOptionValue( nOption, fRes ) )
        {
            mxReslBox->set_sensitive(true);

            mxReslBox->set_entry_text(OUString::number(static_cast<sal_uInt32>(fRes)));
            std::unique_ptr<double[]> pDouble;
            nValue = mrSane.GetRange( nOption, pDouble );
            if( nValue > -1 )
            {
                assert(pDouble);
                if( nValue )
                {
                    for( i=0; i<nValue; i++ )
                    {
                        if( i == 0 || i == nValue-1 || ! ( static_cast<int>(pDouble[i]) % 20) )
                            mxReslBox->append_text(OUString::number(static_cast<sal_uInt32>(pDouble[i])));
                    }
                }
                else
                {
                    mxReslBox->append_text(OUString::number(static_cast<sal_uInt32>(pDouble[0])));
                    // Can only select 75 and 2400 dpi in Scanner dialogue
                    // scanner allows random setting of dpi resolution, a slider might be useful
                    // support that
                    // workaround: offer at least some more standard dpi resolution between
                    // min and max value
                    int bGot300 = 0;
                    for (sal_uInt32 nRes = static_cast<sal_uInt32>(pDouble[0]) * 2; nRes < static_cast<sal_uInt32>(pDouble[1]); nRes = nRes * 2)
                    {
                        if ( !bGot300 && nRes > 300 ) {
                            nRes = 300; bGot300 = 1;
                        }
                        mxReslBox->append_text(OUString::number(nRes));
                    }
                    mxReslBox->append_text(OUString::number(static_cast<sal_uInt32>(pDouble[1])));
                }
            }
            else
                mxReslBox->set_sensitive( false );
        }
    }
    else
        mxReslBox->set_sensitive( false );

    // set scan area
    for( i = 0; i < 4; i++ )
    {
        char const *pOptionName = nullptr;
        weld::MetricSpinButton* pField = nullptr;
        switch( i )
        {
            case 0:
                pOptionName = "tl-x";
                pField = mxLeftField.get();
                break;
            case 1:
                pOptionName = "tl-y";
                pField = mxTopField.get();
                break;
            case 2:
                pOptionName = "br-x";
                pField = mxRightField.get();
                break;
            case 3:
                pOptionName = "br-y";
                pField = mxBottomField.get();
        }
        nOption = pOptionName ? mrSane.GetOptionByName( pOptionName ) : -1;
        if( nOption != -1 )
        {
            if( mrSane.GetOptionValue( nOption, fValue ) )
            {
                if( mrSane.GetOptionUnit( nOption ) == SANE_UNIT_MM )
                {
                    pField->set_unit( FieldUnit::MM );
                    pField->set_value( static_cast<int>(fValue), FieldUnit::MM );
                }
                else // SANE_UNIT_PIXEL
                {
                    pField->set_unit( FieldUnit::PIXEL );
                    pField->set_value( static_cast<int>(fValue), FieldUnit::PIXEL );
                }
                switch( i ) {
                    case 0: aTopLeft.setX( static_cast<int>(fValue) );break;
                    case 1: aTopLeft.setY( static_cast<int>(fValue) );break;
                    case 2: aBottomRight.setX( static_cast<int>(fValue) );break;
                    case 3: aBottomRight.setY( static_cast<int>(fValue) );break;
                }
            }
            std::unique_ptr<double[]> pDouble;
            nValue = mrSane.GetRange( nOption, pDouble );
            if( nValue > -1 )
            {
                if( pDouble )
                {
                    pField->set_min( static_cast<tools::Long>(pDouble[0]), FieldUnit::NONE );
                    if( nValue )
                        pField->set_max( static_cast<tools::Long>(pDouble[ nValue-1 ]), FieldUnit::NONE );
                    else
                        pField->set_max( static_cast<tools::Long>(pDouble[ 1 ]), FieldUnit::NONE );
                }
                switch( i ) {
                    case 0: aMinTopLeft.setX( pField->get_min(FieldUnit::NONE) );break;
                    case 1: aMinTopLeft.setY( pField->get_min(FieldUnit::NONE) );break;
                    case 2: aMaxBottomRight.setX( pField->get_max(FieldUnit::NONE) );break;
                    case 3: aMaxBottomRight.setY( pField->get_max(FieldUnit::NONE) );break;
                }
            }
            else
            {
                switch( i ) {
                    case 0: aMinTopLeft.setX( static_cast<int>(fValue) );break;
                    case 1: aMinTopLeft.setY( static_cast<int>(fValue) );break;
                    case 2: aMaxBottomRight.setX( static_cast<int>(fValue) );break;
                    case 3: aMaxBottomRight.setY( static_cast<int>(fValue) );break;
                }
            }
            pField->set_sensitive(true);
        }
        else
        {
            mxPreview->DisableDrag();
            pField->set_min( 0, FieldUnit::NONE );
            switch( i ) {
                case 0:
                    aMinTopLeft.setX( 0 );
                    aTopLeft.setX( 0 );
                    pField->set_max( PREVIEW_WIDTH, FieldUnit::NONE );
                    pField->set_value( 0, FieldUnit::NONE );
                    break;
                case 1:
                    aMinTopLeft.setY( 0 );
                    aTopLeft.setY( 0 );
                    pField->set_max( PREVIEW_HEIGHT, FieldUnit::NONE );
                    pField->set_value( 0, FieldUnit::NONE );
                    break;
                case 2:
                    aMaxBottomRight.setX( PREVIEW_WIDTH );
                    aBottomRight.setX( PREVIEW_WIDTH );
                    pField->set_max( PREVIEW_WIDTH, FieldUnit::NONE );
                    pField->set_value( PREVIEW_WIDTH, FieldUnit::NONE );
                    break;
                case 3:
                    aMaxBottomRight.setY( PREVIEW_HEIGHT );
                    aBottomRight.setY( PREVIEW_HEIGHT );
                    pField->set_max( PREVIEW_HEIGHT, FieldUnit::NONE );
                    pField->set_value( PREVIEW_HEIGHT, FieldUnit::NONE );
                    break;
            }
            pField->set_sensitive(false);
        }
    }

    mxPreview->SetPreviewMaxRect(aMinTopLeft, aMaxBottomRight);
    mxPreview->SetPreviewLogicRect(aTopLeft, aBottomRight);
    mxPreview->Invalidate();

    // fill OptionBox
    mxOptionBox->clear();
    std::unique_ptr<weld::TreeIter> xParentEntry(mxOptionBox->make_iterator());
    bool bGroupRejected = false;
    for( i = 1; i < mrSane.CountOptions(); i++ )
    {
        OUString aOption=mrSane.GetOptionName( i );
        bool bInsertAdvanced =
            (mrSane.GetOptionCap( i ) & SANE_CAP_ADVANCED) == 0 ||
            mxAdvancedBox->get_active();
        if( mrSane.GetOptionType( i ) == SANE_TYPE_GROUP )
        {
            if( bInsertAdvanced )
            {
                aOption = mrSane.GetOptionTitle( i );
                mxOptionBox->append(xParentEntry.get());
                mxOptionBox->set_text(*xParentEntry, aOption, 0);
                bGroupRejected = false;
            }
            else
                bGroupRejected = true;
        }
        else if( !aOption.isEmpty() &&
                 ! ( mrSane.GetOptionCap( i ) &
                     (
                         SANE_CAP_HARD_SELECT |
                         SANE_CAP_INACTIVE
                         ) ) &&
                 bInsertAdvanced && ! bGroupRejected )
        {
            bool bIsSpecial = false;
            for( size_t n = 0; !bIsSpecial &&
                     n < std::size(ppSpecialOptions); n++ )
            {
                if( aOption == OUString::createFromAscii(ppSpecialOptions[n]) )
                    bIsSpecial=true;
            }
            if( ! bIsSpecial )
            {
                if (xParentEntry)
                    mxOptionBox->append(xParentEntry.get(), aOption);
                else
                    mxOptionBox->append_text(aOption);
            }
        }
    }
}

IMPL_LINK( SaneDlg, ClickBtnHdl, weld::Button&, rButton, void )
{
    if( mrSane.IsOpen() )
    {
        if( &rButton == mxDeviceInfoButton.get() )
        {
            OUString aString(SaneResId(STR_DEVICE_DESC));
            aString = aString.replaceFirst( "%s", Sane::GetName( mrSane.GetDeviceNumber() ) );
            aString = aString.replaceFirst( "%s", Sane::GetVendor( mrSane.GetDeviceNumber() ) );
            aString = aString.replaceFirst( "%s", Sane::GetModel( mrSane.GetDeviceNumber() ) );
            aString = aString.replaceFirst( "%s", Sane::GetType( mrSane.GetDeviceNumber() ) );
            std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(m_xDialog.get(),
                                                          VclMessageType::Info, VclButtonsType::Ok,
                                                          aString));
            xInfoBox->run();
        }
        else if( &rButton == mxPreviewButton.get() )
            AcquirePreview();
        else if( &rButton == mxButtonOption.get() )
        {

            SANE_Value_Type nType = mrSane.GetOptionType( mnCurrentOption );
            switch( nType )
            {
                case SANE_TYPE_BUTTON:
                    mrSane.ActivateButtonOption( mnCurrentOption );
                    break;
                case SANE_TYPE_FIXED:
                case SANE_TYPE_INT:
                {
                    int nElements = mrSane.GetOptionElements( mnCurrentOption );
                    std::unique_ptr<double[]> x(new double[ nElements ]);
                    std::unique_ptr<double[]> y(new double[ nElements ]);
                    for( int i = 0; i < nElements; i++ )
                        x[ i ] = static_cast<double>(i);
                    mrSane.GetOptionValue( mnCurrentOption, y.get() );

                    GridDialog aGrid(m_xDialog.get(), x.get(), y.get(), nElements);
                    aGrid.set_title( mrSane.GetOptionName( mnCurrentOption ) );
                    aGrid.setBoundings( 0, mfMin, nElements, mfMax );
                    if (aGrid.run() && aGrid.getNewYValues())
                        mrSane.SetOptionValue( mnCurrentOption, aGrid.getNewYValues() );
                }
                break;
                case SANE_TYPE_BOOL:
                case SANE_TYPE_STRING:
                case SANE_TYPE_GROUP:
                    break;
            }
        }
    }
    if (&rButton == mxScanButton.get())
    {
        double fRes = static_cast<double>(mxReslBox->get_active_text().toUInt32());
        SetAdjustedNumericalValue( "resolution", fRes );
        UpdateScanArea(true);
        SaveState();
        m_xDialog->response(mrSane.IsOpen() ? RET_OK : RET_CANCEL);
        doScan = mrSane.IsOpen();
    }
    else if( &rButton == mxCancelButton.get() )
    {
        mrSane.Close();
        m_xDialog->response(RET_CANCEL);
    }
}

IMPL_LINK( SaneDlg, ToggleBtnHdl, weld::Toggleable&, rButton, void )
{
    if( mrSane.IsOpen() )
    {
        if( &rButton == mxBoolCheckBox.get() )
        {
            mrSane.SetOptionValue( mnCurrentOption,
                                   mxBoolCheckBox->get_active() );
        }
        else if( &rButton == mxAdvancedBox.get() )
        {
            ReloadSaneOptionsHdl( mrSane );
        }
    }
}

IMPL_LINK( SaneDlg, SelectHdl, weld::ComboBox&, rListBox, void )
{
    if( &rListBox == mxDeviceBox.get() && Sane::IsSane() && Sane::CountDevices() )
    {
        int nNewNumber = mxDeviceBox->get_active();
        int nOldNumber = mrSane.GetDeviceNumber();
        if (nNewNumber != nOldNumber)
        {
            mrSane.Close();
            mrSane.Open(nNewNumber);
            mxPreview->ResetForNewScanner();
            InitFields();
        }
    }
    if( mrSane.IsOpen() )
    {
        if( &rListBox == mxQuantumRangeBox.get() )
        {
            double fValue = mxQuantumRangeBox->get_active_text().toDouble();
            mrSane.SetOptionValue(mnCurrentOption, fValue, mnCurrentElement);
        }
        else if( &rListBox == mxStringRangeBox.get() )
        {
            mrSane.SetOptionValue(mnCurrentOption, mxStringRangeBox->get_active_text());
        }
    }
}

IMPL_LINK_NOARG(SaneDlg, OptionsBoxSelectHdl, weld::TreeView&, void)
{
    if (!Sane::IsSane())
        return;

    OUString aOption = mxOptionBox->get_selected_text();
    int nOption = mrSane.GetOptionByName(OUStringToOString(aOption,
        osl_getThreadTextEncoding()).getStr());
    if( nOption == -1 || nOption == mnCurrentOption )
        return;

    DisableOption();
    mnCurrentOption = nOption;
    mxOptionTitle->set_label(mrSane.GetOptionTitle(mnCurrentOption));
    SANE_Value_Type nType = mrSane.GetOptionType( mnCurrentOption );
    SANE_Constraint_Type nConstraint;
    switch( nType )
    {
        case SANE_TYPE_BOOL:    EstablishBoolOption();break;
        case SANE_TYPE_STRING:
            nConstraint = mrSane.GetOptionConstraintType( mnCurrentOption );
            if( nConstraint == SANE_CONSTRAINT_STRING_LIST )
                EstablishStringRange();
            else
                EstablishStringOption();
            break;
        case SANE_TYPE_FIXED:
        case SANE_TYPE_INT:
        {
            nConstraint = mrSane.GetOptionConstraintType( mnCurrentOption );
            int nElements = mrSane.GetOptionElements( mnCurrentOption );
            mnCurrentElement = 0;
            if( nConstraint == SANE_CONSTRAINT_RANGE ||
                nConstraint == SANE_CONSTRAINT_WORD_LIST )
                EstablishQuantumRange();
            else
            {
                mfMin = mfMax = 0.0;
                EstablishNumericOption();
            }
            if( nElements > 1 )
            {
                if( nElements <= 10 )
                {
                    mxVectorBox->set_range(1, mrSane.GetOptionElements(mnCurrentOption));
                    mxVectorBox->set_value(1);
                    mxVectorBox->show();
                    mxVectorTxt->show();
                }
                else
                {
                    DisableOption();
                    // bring up dialog only on button click
                    EstablishButtonOption();
                }
            }
        }
        break;
        case SANE_TYPE_BUTTON:
            EstablishButtonOption();
            break;
        default: break;
    }
}

IMPL_LINK(SaneDlg, ModifyHdl, weld::Entry&, rEdit, void)
{
    if( !mrSane.IsOpen() )
        return;

    if (&rEdit == mxStringEdit.get())
    {
        mrSane.SetOptionValue( mnCurrentOption, mxStringEdit->get_text() );
    }
    else if (&rEdit == mxNumericEdit.get())
    {
        double fValue = mxNumericEdit->get_text().toDouble();
        if( mfMin != mfMax && ( fValue < mfMin || fValue > mfMax ) )
        {
            char pBuf[256];
            if( fValue < mfMin )
                fValue = mfMin;
            else if( fValue > mfMax )
                fValue = mfMax;
            o3tl::sprintf( pBuf, "%g", fValue );
            mxNumericEdit->set_text( OUString( pBuf, strlen(pBuf), osl_getThreadTextEncoding() ) );
        }
        mrSane.SetOptionValue( mnCurrentOption, fValue, mnCurrentElement );
    }
    else if (&rEdit == mxVectorBox.get())
    {
        mnCurrentElement = mxVectorBox->get_value() - 1;
        double fValue;
        if( mrSane.GetOptionValue( mnCurrentOption, fValue, mnCurrentElement ))
        {
            char pBuf[256];
            o3tl::sprintf( pBuf, "%g", fValue );
            OUString aValue( pBuf, strlen(pBuf), osl_getThreadTextEncoding() );
            mxNumericEdit->set_text( aValue );
            mxQuantumRangeBox->set_active_text( aValue );
        }
    }
}

IMPL_LINK(SaneDlg, ValueModifyHdl, weld::ComboBox&, rEdit, void)
{
    if( !mrSane.IsOpen() )
        return;

    if (&rEdit != mxReslBox.get())
        return;

    double fRes = static_cast<double>(mxReslBox->get_active_text().toUInt32());
    int nOption = mrSane.GetOptionByName( "resolution" );
    if( nOption == -1 )
        return;

    std::unique_ptr<double[]> pDouble;
    int nValues = mrSane.GetRange( nOption, pDouble );
    if( nValues > 0 )
    {
        int i;
        for( i = 0; i < nValues; i++ )
        {
            if( fRes == pDouble[i] )
                break;
        }
        if( i >= nValues )
            fRes = pDouble[0];
    }
    else if( nValues == 0 )
    {
        if( fRes < pDouble[ 0 ] )
            fRes = pDouble[ 0 ];
        if( fRes > pDouble[ 1 ] )
            fRes = pDouble[ 1 ];
    }
    mxReslBox->set_entry_text(OUString::number(static_cast<sal_uInt32>(fRes)));
}

IMPL_LINK(SaneDlg, MetricValueModifyHdl, weld::MetricSpinButton&, rEdit, void)
{
    if( !mrSane.IsOpen() )
        return;

    if (&rEdit == mxTopField.get())
    {
        mxPreview->ChangePreviewLogicTopLeftY(mxTopField->get_value(FieldUnit::NONE));
        mxPreview->Invalidate();
    }
    else if (&rEdit == mxLeftField.get())
    {
        mxPreview->ChangePreviewLogicTopLeftX(mxLeftField->get_value(FieldUnit::NONE));
        mxPreview->Invalidate();
    }
    else if (&rEdit == mxBottomField.get())
    {
        mxPreview->ChangePreviewLogicBottomRightY(mxBottomField->get_value(FieldUnit::NONE));
        mxPreview->Invalidate();
    }
    else if (&rEdit == mxRightField.get())
    {
        mxPreview->ChangePreviewLogicBottomRightX(mxRightField->get_value(FieldUnit::NONE));
        mxPreview->Invalidate();
    }
}

IMPL_LINK_NOARG( SaneDlg, ReloadSaneOptionsHdl, Sane&, void )
{
    mnCurrentOption = -1;
    mnCurrentElement = 0;
    DisableOption();
    InitFields();
    mxPreview->Invalidate();
}

void SaneDlg::AcquirePreview()
{
    if( ! mrSane.IsOpen() )
        return;

    UpdateScanArea( true );
    // set small resolution for preview
    double fResl = static_cast<double>(mxReslBox->get_active_text().toUInt32());
    SetAdjustedNumericalValue( "resolution", 30.0 );

    int nOption = mrSane.GetOptionByName( "preview" );
    if( nOption == -1 )
    {
        OUString aString(SaneResId(STR_SLOW_PREVIEW));
        std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(m_xDialog.get(),
                                                  VclMessageType::Warning, VclButtonsType::OkCancel,
                                                  aString));
        if (xBox->run() == RET_CANCEL)
            return;
    }
    else
        mrSane.SetOptionValue( nOption, true );

    rtl::Reference<BitmapTransporter> xTransporter(new BitmapTransporter);
    if (!mrSane.Start(*xTransporter))
    {
        std::unique_ptr<weld::MessageDialog> xErrorBox(Application::CreateMessageDialog(m_xDialog.get(),
                                                       VclMessageType::Warning, VclButtonsType::Ok,
                                                       SaneResId(STR_ERROR_SCAN)));
        xErrorBox->run();
    }
    else
    {
#if OSL_DEBUG_LEVEL > 0
        SAL_INFO("extensions.scanner", "Previewbitmapstream contains " << xTransporter->getStream().TellEnd() << "bytes");
#endif
        xTransporter->getStream().Seek( STREAM_SEEK_TO_BEGIN );
        mxPreview->SetBitmap(xTransporter->getStream());
    }

    SetAdjustedNumericalValue( "resolution", fResl );
    mxReslBox->set_entry_text(OUString::number(static_cast<sal_uInt32>(fResl)));

    mxPreview->UpdatePreviewBounds();
    mxPreview->Invalidate();
}

void ScanPreview::UpdatePreviewBounds()
{
    if( mbDragEnable )
    {
        maPreviewRect = tools::Rectangle( maTopLeft,
                                   Size( maBottomRight.X() - maTopLeft.X(),
                                         maBottomRight.Y() - maTopLeft.Y() )
                                   );
    }
    else
    {
        Size aBMSize( maPreviewBitmap.GetSizePixel() );
        if( aBMSize.Width() > aBMSize.Height() && aBMSize.Width() )
        {
            int nVHeight = (maBottomRight.X() - maTopLeft.X()) * aBMSize.Height() / aBMSize.Width();
            maPreviewRect = tools::Rectangle( Point( maTopLeft.X(), ( maTopLeft.Y() + maBottomRight.Y() )/2 - nVHeight/2 ),
                                       Size( maBottomRight.X() - maTopLeft.X(),
                                             nVHeight ) );
        }
        else if (aBMSize.Height())
        {
            int nVWidth = (maBottomRight.Y() - maTopLeft.Y()) * aBMSize.Width() / aBMSize.Height();
            maPreviewRect = tools::Rectangle( Point( ( maTopLeft.X() + maBottomRight.X() )/2 - nVWidth/2, maTopLeft.Y() ),
                                       Size( nVWidth,
                                             maBottomRight.Y() - maTopLeft.Y() ) );
        }
    }
}

void ScanPreview::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
{
    rRenderContext.SetMapMode(MapMode(MapUnit::MapAppFont));
    rRenderContext.SetFillColor(COL_WHITE);
    rRenderContext.SetLineColor(COL_WHITE);
    rRenderContext.DrawRect(tools::Rectangle(Point(0, 0),
                                      Size(PREVIEW_WIDTH, PREVIEW_HEIGHT)));
    rRenderContext.SetMapMode(MapMode(MapUnit::MapPixel));
    // check for sane values
    rRenderContext.DrawBitmap(maPreviewRect.TopLeft(), maPreviewRect.GetSize(), maPreviewBitmap);

    DrawDrag(rRenderContext);
}

void SaneDlg::DisableOption()
{
    mxBoolCheckBox->hide();
    mxStringEdit->hide();
    mxNumericEdit->hide();
    mxQuantumRangeBox->hide();
    mxStringRangeBox->hide();
    mxButtonOption->hide();
    mxVectorBox->hide();
    mxVectorTxt->hide();
    mxOptionDescTxt->hide();
}

void SaneDlg::EstablishBoolOption()
{
    bool bSuccess, bValue;

    bSuccess = mrSane.GetOptionValue( mnCurrentOption, bValue );
    if( bSuccess )
    {
        mxBoolCheckBox->set_label( mrSane.GetOptionName( mnCurrentOption ) );
        mxBoolCheckBox->set_active( bValue );
        mxBoolCheckBox->show();
    }
}

void SaneDlg::EstablishStringOption()
{
    bool bSuccess;
    OString aValue;

    bSuccess = mrSane.GetOptionValue( mnCurrentOption, aValue );
    if( bSuccess )
    {
        mxOptionDescTxt->set_label( mrSane.GetOptionName( mnCurrentOption ) );
        mxOptionDescTxt->show();
        mxStringEdit->set_text(OStringToOUString(aValue, osl_getThreadTextEncoding()));
        mxStringEdit->show();
    }
}

void SaneDlg::EstablishStringRange()
{
    const char** ppStrings = mrSane.GetStringConstraint( mnCurrentOption );
    mxStringRangeBox->clear();
    for( int i = 0; ppStrings[i] != nullptr; i++ )
        mxStringRangeBox->append_text( OUString( ppStrings[i], strlen(ppStrings[i]), osl_getThreadTextEncoding() ) );
    OString aValue;
    mrSane.GetOptionValue( mnCurrentOption, aValue );
    mxStringRangeBox->set_active_text(OStringToOUString(aValue, osl_getThreadTextEncoding()));
    mxStringRangeBox->show();
    mxOptionDescTxt->set_label( mrSane.GetOptionName( mnCurrentOption ) );
    mxOptionDescTxt->show();
}

void SaneDlg::EstablishQuantumRange()
{
    mpRange.reset();
    int nValues = mrSane.GetRange( mnCurrentOption, mpRange );
    if( nValues == 0 )
    {
        mfMin = mpRange[ 0 ];
        mfMax = mpRange[ 1 ];
        mpRange.reset();
        EstablishNumericOption();
    }
    else if( nValues > 0 )
    {
        char pBuf[ 256 ];
        mxQuantumRangeBox->clear();
        mfMin = mpRange[ 0 ];
        mfMax = mpRange[ nValues-1 ];
        for( int i = 0; i < nValues; i++ )
        {
            o3tl::sprintf( pBuf, "%g", mpRange[ i ] );
            mxQuantumRangeBox->append_text( OUString( pBuf, strlen(pBuf), osl_getThreadTextEncoding() ) );
        }
        double fValue;
        if( mrSane.GetOptionValue( mnCurrentOption, fValue, mnCurrentElement ) )
        {
            o3tl::sprintf( pBuf, "%g", fValue );
            mxQuantumRangeBox->set_active_text( OUString( pBuf, strlen(pBuf), osl_getThreadTextEncoding() ) );
        }
        mxQuantumRangeBox->show();
        OUString aText = mrSane.GetOptionName( mnCurrentOption ) + " "
            + mrSane.GetOptionUnitName( mnCurrentOption );
        mxOptionDescTxt->set_label(aText);
        mxOptionDescTxt->show();
    }
}

void SaneDlg::EstablishNumericOption()
{
    bool bSuccess;
    double fValue;

    bSuccess = mrSane.GetOptionValue( mnCurrentOption, fValue );
    if( ! bSuccess )
        return;

    char pBuf[256];
    OUString aText = mrSane.GetOptionName( mnCurrentOption ) + " "
        + mrSane.GetOptionUnitName( mnCurrentOption );
    if( mfMin != mfMax )
    {
        o3tl::sprintf( pBuf, " < %g ; %g >", mfMin, mfMax );
        aText += OUString( pBuf, strlen(pBuf), osl_getThreadTextEncoding() );
    }
    mxOptionDescTxt->set_label( aText );
    mxOptionDescTxt->show();
    o3tl::sprintf( pBuf, "%g", fValue );
    mxNumericEdit->set_text( OUString( pBuf, strlen(pBuf), osl_getThreadTextEncoding() ) );
    mxNumericEdit->show();
}

void SaneDlg::EstablishButtonOption()
{
    mxOptionDescTxt->set_label(mrSane.GetOptionName(mnCurrentOption));
    mxOptionDescTxt->show();
    mxButtonOption->show();
}

bool ScanPreview::MouseMove(const MouseEvent& rMEvt)
{
    if( !mbIsDragging )
        return false;

    Point aMousePos = rMEvt.GetPosPixel();
    // move into valid area
    Point aLogicPos = GetLogicPos( aMousePos );
    aMousePos = GetPixelPos( aLogicPos );
    switch( meDragDirection )
    {
        case TopLeft:       maTopLeft = aMousePos; break;
        case Top:           maTopLeft.setY( aMousePos.Y() ); break;
        case TopRight:
            maTopLeft.setY( aMousePos.Y() );
            maBottomRight.setX( aMousePos.X() );
            break;
        case Right:         maBottomRight.setX( aMousePos.X() ); break;
        case BottomRight:   maBottomRight = aMousePos; break;
        case Bottom:        maBottomRight.setY( aMousePos.Y() ); break;
        case BottomLeft:
            maTopLeft.setX( aMousePos.X() );
            maBottomRight.setY( aMousePos.Y() );
            break;
        case Left:          maTopLeft.setX( aMousePos.X() ); break;
        default: break;
    }
    int nSwap;
    if( maTopLeft.X() > maBottomRight.X() )
    {
        nSwap = maTopLeft.X();
        maTopLeft.setX( maBottomRight.X() );
        maBottomRight.setX( nSwap );
    }
    if( maTopLeft.Y() > maBottomRight.Y() )
    {
        nSwap = maTopLeft.Y();
        maTopLeft.setY( maBottomRight.Y() );
        maBottomRight.setY( nSwap );
    }
    Invalidate();
    mpParentDialog->UpdateScanArea(false);
    return false;
}

bool ScanPreview::MouseButtonDown( const MouseEvent& rMEvt )
{
    if (!mbIsDragging && mbDragEnable)
    {
        Point aMousePixel = rMEvt.GetPosPixel();

        int nMiddleX = ( maBottomRight.X() - maTopLeft.X() ) / 2 - RECT_SIZE_PIX/2 + maTopLeft.X();
        int nMiddleY = ( maBottomRight.Y() - maTopLeft.Y() ) / 2 - RECT_SIZE_PIX/2 + maTopLeft.Y();
        if( aMousePixel.Y() >= maTopLeft.Y() &&
            aMousePixel.Y() < maTopLeft.Y() + RECT_SIZE_PIX )
        {
            if( aMousePixel.X() >= maTopLeft.X() &&
                aMousePixel.X() < maTopLeft.X() + RECT_SIZE_PIX )
            {
                meDragDirection = TopLeft;
                mbIsDragging = true;
            }
            else if( aMousePixel.X() >= nMiddleX &&
                     aMousePixel.X() < nMiddleX + RECT_SIZE_PIX )
            {
                meDragDirection = Top;
                mbIsDragging = true;
            }
            else if( aMousePixel.X() > maBottomRight.X() - RECT_SIZE_PIX &&
                     aMousePixel.X() <= maBottomRight.X() )
            {
                meDragDirection = TopRight;
                mbIsDragging = true;
            }
        }
        else if( aMousePixel.Y() >= nMiddleY &&
                 aMousePixel.Y() < nMiddleY + RECT_SIZE_PIX )
        {
            if( aMousePixel.X() >= maTopLeft.X() &&
                aMousePixel.X() < maTopLeft.X() + RECT_SIZE_PIX )
            {
                meDragDirection = Left;
                mbIsDragging = true;
            }
            else if( aMousePixel.X() > maBottomRight.X() - RECT_SIZE_PIX &&
                     aMousePixel.X() <= maBottomRight.X() )
            {
                meDragDirection = Right;
                mbIsDragging = true;
            }
        }
        else if( aMousePixel.Y() <= maBottomRight.Y() &&
                 aMousePixel.Y() > maBottomRight.Y() - RECT_SIZE_PIX )
        {
            if( aMousePixel.X() >= maTopLeft.X() &&
                aMousePixel.X() < maTopLeft.X() + RECT_SIZE_PIX )
            {
                meDragDirection = BottomLeft;
                mbIsDragging = true;
            }
            else if( aMousePixel.X() >= nMiddleX &&
                     aMousePixel.X() < nMiddleX + RECT_SIZE_PIX )
            {
                meDragDirection = Bottom;
                mbIsDragging = true;
            }
            else if( aMousePixel.X() > maBottomRight.X() - RECT_SIZE_PIX &&
                     aMousePixel.X() <= maBottomRight.X() )
            {
                meDragDirection = BottomRight;
                mbIsDragging = true;
            }
        }
    }

    if( mbIsDragging )
        Invalidate();

    return false;
}

bool ScanPreview::MouseButtonUp(const MouseEvent&)
{
    if( mbIsDragging )
        mpParentDialog->UpdateScanArea(true);
    mbIsDragging = false;

    return false;
}

void ScanPreview::DrawDrag(vcl::RenderContext& rRenderContext)
{
    if (!mbDragEnable)
        return;

    rRenderContext.SetMapMode(MapMode(MapUnit::MapPixel));

    DrawRectangles(rRenderContext, maTopLeft, maBottomRight);

    rRenderContext.SetMapMode(MapMode(MapUnit::MapAppFont));
}

Point ScanPreview::GetPixelPos( const Point& rIn) const
{
    Point aConvert(
        ( ( rIn.X() * PREVIEW_WIDTH ) /
          ( maMaxBottomRight.X() - maMinTopLeft.X() ) )
        ,
        ( ( rIn.Y() * PREVIEW_HEIGHT )
          / ( maMaxBottomRight.Y() - maMinTopLeft.Y() ) )
        );

    return GetDrawingArea()->get_ref_device().LogicToPixel(aConvert, MapMode(MapUnit::MapAppFont));
}

Point ScanPreview::GetLogicPos(const Point& rIn) const
{
    Point aConvert = GetDrawingArea()->get_ref_device().PixelToLogic(rIn, MapMode(MapUnit::MapAppFont));
    if( aConvert.X() < 0 )
        aConvert.setX( 0 );
    if( aConvert.X() >= PREVIEW_WIDTH )
        aConvert.setX( PREVIEW_WIDTH-1 );
    if( aConvert.Y() < 0 )
        aConvert.setY( 0 );
    if( aConvert.Y() >= PREVIEW_HEIGHT )
        aConvert.setY( PREVIEW_HEIGHT-1 );

    aConvert.setX( aConvert.X() * ( maMaxBottomRight.X() - maMinTopLeft.X() ) );
    aConvert.setX( aConvert.X() / ( PREVIEW_WIDTH) );
    aConvert.setY( aConvert.Y() * ( maMaxBottomRight.Y() - maMinTopLeft.Y() ) );
    aConvert.setY( aConvert.Y() / ( PREVIEW_HEIGHT) );
    return aConvert;
}

void SaneDlg::UpdateScanArea(bool bSend)
{
    if (!mxPreview->IsDragEnabled())
        return;

    Point aUL, aBR;
    mxPreview->GetPreviewLogicRect(aUL, aBR);

    mxLeftField->set_value(aUL.X(), FieldUnit::NONE);
    mxTopField->set_value(aUL.Y(), FieldUnit::NONE);
    mxRightField->set_value(aBR.X(), FieldUnit::NONE);
    mxBottomField->set_value(aBR.Y(), FieldUnit::NONE);

    if (!bSend)
        return;

    if( mrSane.IsOpen() )
    {
        SetAdjustedNumericalValue( "tl-x", static_cast<double>(aUL.X()) );
        SetAdjustedNumericalValue( "tl-y", static_cast<double>(aUL.Y()) );
        SetAdjustedNumericalValue( "br-x", static_cast<double>(aBR.X()) );
        SetAdjustedNumericalValue( "br-y", static_cast<double>(aBR.Y()) );
    }
}

bool SaneDlg::LoadState()
{
    int i;

    if( ! Sane::IsSane() )
        return false;

    const char* pEnv = getenv("HOME");
    OUString aFileName = (pEnv ? OUString(pEnv, strlen(pEnv), osl_getThreadTextEncoding() ) : OUString()) + "/.so_sane_state";
    Config aConfig( aFileName );
    if( ! aConfig.HasGroup( "SANE" ) )
        return false;

    aConfig.SetGroup( "SANE"_ostr );
    OString aString = aConfig.ReadKey( "SO_LastSaneDevice"_ostr );
    for( i = 0; i < Sane::CountDevices() && aString != OUStringToOString(Sane::GetName(i), osl_getThreadTextEncoding()); i++ ) ;
    if( i == Sane::CountDevices() )
        return false;

    mrSane.Close();
    mrSane.Open( aString.getStr() );

    DisableOption();
    InitFields();

    if( mrSane.IsOpen() )
    {
        int iMax = aConfig.GetKeyCount();
        for (i = 0; i < iMax; ++i)
        {
            aString = aConfig.GetKeyName( i );
            OString aValue = aConfig.ReadKey( i );
            int nOption = mrSane.GetOptionByName( aString.getStr() );
            if( nOption == -1 )
                continue;

            if (aValue.startsWith("BOOL="))
            {
                aValue = aValue.copy(RTL_CONSTASCII_LENGTH("BOOL="));
                bool aBOOL = aValue.toInt32() != 0;
                mrSane.SetOptionValue( nOption, aBOOL );
            }
            else if (aValue.startsWith("STRING="))
            {
                aValue = aValue.copy(RTL_CONSTASCII_LENGTH("STRING="));
                mrSane.SetOptionValue(nOption,OStringToOUString(aValue, osl_getThreadTextEncoding()) );
            }
            else if (aValue.startsWith("NUMERIC="))
            {
                aValue = aValue.copy(RTL_CONSTASCII_LENGTH("NUMERIC="));

                sal_Int32 nIndex = 0;
                int n = 0;
                do
                {
                    OString aSub = aValue.getToken(0, ':', nIndex);
                    double fValue=0.0;
                    sscanf(aSub.getStr(), "%lg", &fValue);
                    SetAdjustedNumericalValue(aString.getStr(), fValue, n++);
                }
                while ( nIndex >= 0 );
            }
        }
    }

    DisableOption();
    InitFields();

    return true;
}

void SaneDlg::SaveState()
{
    if( ! Sane::IsSane() )
        return;

    const char* pEnv = getenv( "HOME" );
    OUString aFileName;

    if( pEnv )
        aFileName = OUString::createFromAscii(pEnv) + "/.so_sane_state";
    else
        aFileName = OStringToOUString("", osl_getThreadTextEncoding()) + "/.so_sane_state";

    Config aConfig( aFileName );
    aConfig.DeleteGroup( "SANE" );
    aConfig.SetGroup( "SANE"_ostr );
    aConfig.WriteKey( "SO_LastSANEDevice"_ostr,
        OUStringToOString(mxDeviceBox->get_active_text(), RTL_TEXTENCODING_UTF8) );

    static char const* pSaveOptions[] = {
        "resolution",
        "tl-x",
        "tl-y",
        "br-x",
        "br-y"
    };
    for(const char * pSaveOption : pSaveOptions)
    {
        OString aOption = pSaveOption;
        int nOption = mrSane.GetOptionByName( pSaveOption );
        if( nOption > -1 )
        {
            SANE_Value_Type nType = mrSane.GetOptionType( nOption );
            switch( nType )
            {
                case SANE_TYPE_BOOL:
                {
                    bool bValue;
                    if( mrSane.GetOptionValue( nOption, bValue ) )
                    {
                        OString aString = "BOOL=" + OString::number(static_cast<sal_Int32>(bValue));
                        aConfig.WriteKey(aOption, aString);
                    }
                }
                break;
                case SANE_TYPE_STRING:
                {
                    OString aValue;
                    if( mrSane.GetOptionValue( nOption, aValue ) )
                    {
                        OString aString = "STRING=" + aValue;
                        aConfig.WriteKey( aOption, aString );
                    }
                }
                break;
                case SANE_TYPE_FIXED:
                case SANE_TYPE_INT:
                {
                    OStringBuffer aString("NUMERIC=");
                    double fValue;
                    char buf[256];
                    int n;

                    for( n = 0; n < mrSane.GetOptionElements( nOption ); n++ )
                    {
                        if( ! mrSane.GetOptionValue( nOption, fValue, n ) )
                            break;
                        if( n > 0 )
                            aString.append(':');
                        o3tl::sprintf( buf, "%lg", fValue );
                        aString.append(buf);
                    }
                    if( n >= mrSane.GetOptionElements( nOption ) )
                        aConfig.WriteKey( aOption, aString.makeStringAndClear() );
                }
                break;
                default:
                    break;
            }
        }
    }
}

bool SaneDlg::SetAdjustedNumericalValue(
    const char* pOption,
    double fValue,
    int nElement )
{
    if (! Sane::IsSane() || ! mrSane.IsOpen())
        return false;
    int const nOption(mrSane.GetOptionByName(pOption));
    if (nOption == -1)
        return false;

    if( nElement < 0 || nElement >= mrSane.GetOptionElements( nOption ) )
        return false;

    std::unique_ptr<double[]> pValues;
    int nValues;
    if( ( nValues = mrSane.GetRange( nOption, pValues ) ) < 0 )
    {
        return false;
    }

    SAL_INFO("extensions.scanner", "SaneDlg::SetAdjustedNumericalValue(\"" << pOption << "\", " << fValue << ") ");

    if( nValues )
    {
        int nNearest = 0;
        double fNearest = 1e6;
        for( int i = 0; i < nValues; i++ )
        {
            if( fabs( fValue - pValues[ i ] ) < fNearest )
            {
                fNearest = fabs( fValue - pValues[ i ] );
                nNearest = i;
            }
        }
        fValue = pValues[ nNearest ];
    }
    else
    {
        if( fValue < pValues[0] )
            fValue = pValues[0];
        if( fValue > pValues[1] )
            fValue = pValues[1];
    }
    mrSane.SetOptionValue( nOption, fValue, nElement );
    SAL_INFO("extensions.scanner", "yields " << fValue);


    return true;
}


/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
