diff options
Diffstat (limited to 'extensions/source/scanner')
-rw-r--r-- | extensions/source/scanner/grid.cxx | 700 | ||||
-rw-r--r-- | extensions/source/scanner/grid.hxx | 51 | ||||
-rw-r--r-- | extensions/source/scanner/sane.cxx | 996 | ||||
-rw-r--r-- | extensions/source/scanner/sane.hxx | 190 | ||||
-rw-r--r-- | extensions/source/scanner/sanedlg.cxx | 1467 | ||||
-rw-r--r-- | extensions/source/scanner/sanedlg.hxx | 111 | ||||
-rw-r--r-- | extensions/source/scanner/scanner.cxx | 89 | ||||
-rw-r--r-- | extensions/source/scanner/scanner.hxx | 85 | ||||
-rw-r--r-- | extensions/source/scanner/scanunx.cxx | 341 | ||||
-rw-r--r-- | extensions/source/scanner/scanwin.cxx | 646 | ||||
-rw-r--r-- | extensions/source/scanner/scn.component | 26 | ||||
-rw-r--r-- | extensions/source/scanner/twain32shim.cxx | 604 | ||||
-rw-r--r-- | extensions/source/scanner/twain32shim.hxx | 68 |
13 files changed, 5374 insertions, 0 deletions
diff --git a/extensions/source/scanner/grid.cxx b/extensions/source/scanner/grid.cxx new file mode 100644 index 000000000..110779c55 --- /dev/null +++ b/extensions/source/scanner/grid.cxx @@ -0,0 +1,700 @@ +/* -*- 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 <sal/config.h> +#include <osl/thread.h> +#include <rtl/math.hxx> +#include <cstdio> + +#include <bitmaps.hlst> +#include <cmath> + +#include "grid.hxx" +#include <vcl/bitmapex.hxx> +#include <vcl/customweld.hxx> +#include <vcl/event.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> + +#include <algorithm> +#include <limits> +#include <memory> + +class GridWindow : public weld::CustomWidgetController +{ + // helper class for handles + struct impHandle + { + Point maPos; + sal_uInt16 mnOffX; + sal_uInt16 mnOffY; + + impHandle(const Point& rPos, sal_uInt16 nX, sal_uInt16 nY) + : maPos(rPos), mnOffX(nX), mnOffY(nY) + { + } + + bool operator<(const impHandle& rComp) const + { + return (maPos.X() < rComp.maPos.X()); + } + + void draw(vcl::RenderContext& rRenderContext, const BitmapEx& rBitmapEx) + { + const Point aOffset(rRenderContext.PixelToLogic(Point(mnOffX, mnOffY))); + rRenderContext.DrawBitmapEx(maPos - aOffset, rBitmapEx); + } + + bool isHit(OutputDevice const & rWin, const Point& rPos) + { + const Point aOffset(rWin.PixelToLogic(Point(mnOffX, mnOffY))); + const tools::Rectangle aTarget(maPos - aOffset, maPos + aOffset); + return aTarget.Contains(rPos); + } + }; + + tools::Rectangle m_aGridArea; + + double m_fMinX; + double m_fMinY; + double m_fMaxX; + double m_fMaxY; + + double m_fChunkX; + double m_fMinChunkX; + double m_fChunkY; + double m_fMinChunkY; + + double* m_pXValues; + double* m_pOrigYValues; + int m_nValues; + std::unique_ptr<double[]> m_pNewYValues; + + sal_uInt16 m_BmOffX; + sal_uInt16 m_BmOffY; + + bool m_bCutValues; + + // stuff for handles + using Handles = std::vector<impHandle>; + static constexpr auto npos = std::numeric_limits<Handles::size_type>::max(); + Handles m_aHandles; + Handles::size_type m_nDragIndex; + + BitmapEx m_aMarkerBitmap; + + Point transform( double x, double y ); + void transform( const Point& rOriginal, double& x, double& y ); + + double findMinX(); + double findMinY(); + double findMaxX(); + double findMaxY(); + + void drawGrid(vcl::RenderContext& rRenderContext); + void drawOriginal(vcl::RenderContext& rRenderContext); + void drawNew(vcl::RenderContext& rRenderContext); + void drawHandles(vcl::RenderContext& rRenderContext); + + void computeExtremes(); + static void computeChunk( double fMin, double fMax, double& fChunkOut, double& fMinChunkOut ); + void computeNew(); + static double interpolate( double x, double const * pNodeX, double const * pNodeY, int nNodes ); + + virtual bool MouseMove( const MouseEvent& ) override; + virtual bool MouseButtonDown( const MouseEvent& ) override; + virtual bool MouseButtonUp( const MouseEvent& ) override; + void onResize(); + virtual void Resize() override; + virtual void SetDrawingArea(weld::DrawingArea* pDrawingArea) override; + void drawLine(vcl::RenderContext& rRenderContext, double x1, double y1, double x2, double y2); +public: + GridWindow(); + void Init(double* pXValues, double* pYValues, int nValues, bool bCutValues, const BitmapEx &rMarkerBitmap); + virtual ~GridWindow() override; + + void setBoundings( double fMinX, double fMinY, double fMaxX, double fMaxY ); + + double* getNewYValues() { return m_pNewYValues.get(); } + + void ChangeMode(ResetType nType); + + virtual void Paint( vcl::RenderContext& /*rRenderContext*/, const tools::Rectangle& rRect ) override; +}; + +GridWindow::GridWindow() + : m_aGridArea(50, 15, 100, 100) + , m_fMinX(0.0) + , m_fMinY(0.0) + , m_fMaxX(0.0) + , m_fMaxY(0.0) + , m_fChunkX(0.0) + , m_fMinChunkX(0.0) + , m_fChunkY(0.0) + , m_fMinChunkY(0.0) + , m_pXValues(nullptr) + , m_pOrigYValues(nullptr) + , m_nValues(0) + , m_BmOffX(0) + , m_BmOffY(0) + , m_bCutValues(false) + , m_nDragIndex(npos) +{ +} + +void GridWindow::Init(double* pXValues, double* pYValues, int nValues, bool bCutValues, const BitmapEx &rMarkerBitmap) +{ + m_aMarkerBitmap = rMarkerBitmap; + m_pXValues = pXValues; + m_pOrigYValues = pYValues; + m_nValues = nValues; + m_bCutValues = bCutValues; + + onResize(); + + if (m_pOrigYValues && m_nValues) + { + m_pNewYValues.reset(new double[ m_nValues ]); + memcpy( m_pNewYValues.get(), m_pOrigYValues, sizeof( double ) * m_nValues ); + } + + setBoundings( 0, 0, 1023, 1023 ); + computeExtremes(); + + // create left and right marker as first and last entry + m_BmOffX = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Width() >> 1); + m_BmOffY = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Height() >> 1); + m_aHandles.push_back(impHandle(transform(findMinX(), findMinY()), m_BmOffX, m_BmOffY)); + m_aHandles.push_back(impHandle(transform(findMaxX(), findMaxY()), m_BmOffX, m_BmOffY)); +} + +void GridWindow::Resize() +{ + onResize(); +} + +void GridWindow::onResize() +{ + Size aSize = GetOutputSizePixel(); + m_aGridArea.setWidth( aSize.Width() - 80 ); + m_aGridArea.setHeight( aSize.Height() - 40 ); +} + +void GridWindow::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + Size aSize(pDrawingArea->get_ref_device().LogicToPixel(Size(240, 200), MapMode(MapUnit::MapAppFont))); + pDrawingArea->set_size_request(aSize.Width(), aSize.Height()); + CustomWidgetController::SetDrawingArea(pDrawingArea); + SetOutputSizePixel(aSize); +} + +GridDialog::GridDialog(weld::Window* pParent, double* pXValues, double* pYValues, int nValues) + : GenericDialogController(pParent, "modules/scanner/ui/griddialog.ui", "GridDialog") + , m_xOKButton(m_xBuilder->weld_button("ok")) + , m_xResetTypeBox(m_xBuilder->weld_combo_box("resetTypeCombobox")) + , m_xResetButton(m_xBuilder->weld_button("resetButton")) + , m_xGridWindow(new GridWindow) + , m_xGridWindowWND(new weld::CustomWeld(*m_xBuilder, "gridwindow", *m_xGridWindow)) +{ + m_xGridWindow->Init(pXValues, pYValues, nValues, true/*bCutValues*/, BitmapEx(RID_SCANNER_HANDLE)); + m_xResetTypeBox->set_active(0); + m_xResetButton->connect_clicked( LINK( this, GridDialog, ClickButtonHdl ) ); +} + +GridDialog::~GridDialog() +{ +} + +GridWindow::~GridWindow() +{ + m_pNewYValues.reset(); +} + +double GridWindow::findMinX() +{ + if( ! m_pXValues ) + return 0.0; + double fMin = m_pXValues[0]; + for( int i = 1; i < m_nValues; i++ ) + if( m_pXValues[ i ] < fMin ) + fMin = m_pXValues[ i ]; + return fMin; +} + +double GridWindow::findMinY() +{ + if( ! m_pNewYValues ) + return 0.0; + double fMin = m_pNewYValues[0]; + for( int i = 1; i < m_nValues; i++ ) + if( m_pNewYValues[ i ] < fMin ) + fMin = m_pNewYValues[ i ]; + return fMin; +} + + +double GridWindow::findMaxX() +{ + if( ! m_pXValues ) + return 0.0; + double fMax = m_pXValues[0]; + for( int i = 1; i < m_nValues; i++ ) + if( m_pXValues[ i ] > fMax ) + fMax = m_pXValues[ i ]; + return fMax; +} + + +double GridWindow::findMaxY() +{ + if( ! m_pNewYValues ) + return 0.0; + double fMax = m_pNewYValues[0]; + for( int i = 1; i < m_nValues; i++ ) + if( m_pNewYValues[ i ] > fMax ) + fMax = m_pNewYValues[ i ]; + return fMax; +} + + +void GridWindow::computeExtremes() +{ + if( !(m_nValues && m_pXValues && m_pOrigYValues) ) + return; + + m_fMaxX = m_fMinX = m_pXValues[0]; + m_fMaxY = m_fMinY = m_pOrigYValues[0]; + for( int i = 1; i < m_nValues; i++ ) + { + if( m_pXValues[ i ] > m_fMaxX ) + m_fMaxX = m_pXValues[ i ]; + else if( m_pXValues[ i ] < m_fMinX ) + m_fMinX = m_pXValues[ i ]; + if( m_pOrigYValues[ i ] > m_fMaxY ) + m_fMaxY = m_pOrigYValues[ i ]; + else if( m_pOrigYValues[ i ] < m_fMinY ) + m_fMinY = m_pOrigYValues[ i ]; + } + setBoundings( m_fMinX, m_fMinY, m_fMaxX, m_fMaxY ); +} + + +Point GridWindow::transform( double x, double y ) +{ + Point aRet; + + aRet.setX( static_cast<tools::Long>( ( x - m_fMinX ) * + static_cast<double>(m_aGridArea.GetWidth()) / ( m_fMaxX - m_fMinX ) + + m_aGridArea.Left() ) ); + aRet.setY( static_cast<tools::Long>( + m_aGridArea.Bottom() - + ( y - m_fMinY ) * + static_cast<double>(m_aGridArea.GetHeight()) / ( m_fMaxY - m_fMinY ) ) ); + return aRet; +} + +void GridWindow::transform( const Point& rOriginal, double& x, double& y ) +{ + const tools::Long nWidth = m_aGridArea.GetWidth(); + const tools::Long nHeight = m_aGridArea.GetHeight(); + if (!nWidth || !nHeight) + return; + x = ( rOriginal.X() - m_aGridArea.Left() ) * (m_fMaxX - m_fMinX) / static_cast<double>(nWidth) + m_fMinX; + y = ( m_aGridArea.Bottom() - rOriginal.Y() ) * (m_fMaxY - m_fMinY) / static_cast<double>(nHeight) + m_fMinY; +} + +void GridWindow::drawLine(vcl::RenderContext& rRenderContext, double x1, double y1, double x2, double y2 ) +{ + rRenderContext.DrawLine(transform(x1, y1), transform(x2, y2)); +} + +void GridWindow::computeChunk( double fMin, double fMax, double& fChunkOut, double& fMinChunkOut ) +{ + // get a nice chunk size like 10, 100, 25 or such + fChunkOut = ( fMax - fMin ) / 6.0; + int logchunk = static_cast<int>(std::log10( fChunkOut )); + int nChunk = static_cast<int>( fChunkOut / std::exp( static_cast<double>(logchunk-1) * M_LN10 ) ); + if( nChunk >= 75 ) + nChunk = 100; + else if( nChunk >= 35 ) + nChunk = 50; + else if ( nChunk > 20 ) + nChunk = 25; + else if ( nChunk >= 13 ) + nChunk = 20; + else if( nChunk > 5 ) + nChunk = 10; + else + nChunk = 5; + fChunkOut = static_cast<double>(nChunk) * exp( static_cast<double>(logchunk-1) * M_LN10 ); + // compute whole chunks fitting into fMin + nChunk = static_cast<int>( fMin / fChunkOut ); + fMinChunkOut = static_cast<double>(nChunk) * fChunkOut; + while( fMinChunkOut < fMin ) + fMinChunkOut += fChunkOut; +} + + +void GridWindow::computeNew() +{ + if(2 == m_aHandles.size()) + { + // special case: only left and right markers + double xleft, yleft; + double xright, yright; + transform(m_aHandles[0].maPos, xleft, yleft); + transform(m_aHandles[1].maPos, xright, yright ); + double factor = (yright-yleft)/(xright-xleft); + for( int i = 0; i < m_nValues; i++ ) + { + m_pNewYValues[ i ] = yleft + ( m_pXValues[ i ] - xleft )*factor; + } + } + else + { + // sort markers + std::sort(m_aHandles.begin(), m_aHandles.end()); + const int nSorted = m_aHandles.size(); + int i; + + // get node arrays + std::unique_ptr<double[]> nodex(new double[ nSorted ]); + std::unique_ptr<double[]> nodey(new double[ nSorted ]); + + for( i = 0; i < nSorted; i++ ) + transform( m_aHandles[i].maPos, nodex[ i ], nodey[ i ] ); + + for( i = 0; i < m_nValues; i++ ) + { + double x = m_pXValues[ i ]; + m_pNewYValues[ i ] = interpolate( x, nodex.get(), nodey.get(), nSorted ); + if( m_bCutValues ) + { + if( m_pNewYValues[ i ] > m_fMaxY ) + m_pNewYValues[ i ] = m_fMaxY; + else if( m_pNewYValues[ i ] < m_fMinY ) + m_pNewYValues[ i ] = m_fMinY; + } + } + } +} + + +double GridWindow::interpolate( + double x, + double const * pNodeX, + double const * pNodeY, + int nNodes ) +{ + // compute Lagrange interpolation + double ret = 0; + for( int i = 0; i < nNodes; i++ ) + { + double sum = pNodeY[ i ]; + for( int n = 0; n < nNodes; n++ ) + { + if( n != i ) + { + sum *= x - pNodeX[ n ]; + sum /= pNodeX[ i ] - pNodeX[ n ]; + } + } + ret += sum; + } + return ret; +} + +void GridDialog::setBoundings(double fMinX, double fMinY, double fMaxX, double fMaxY) +{ + m_xGridWindow->setBoundings(fMinX, fMinY, fMaxX, fMaxY); +} + +void GridWindow::setBoundings(double fMinX, double fMinY, double fMaxX, double fMaxY) +{ + m_fMinX = fMinX; + m_fMinY = fMinY; + m_fMaxX = fMaxX; + m_fMaxY = fMaxY; + + computeChunk( m_fMinX, m_fMaxX, m_fChunkX, m_fMinChunkX ); + computeChunk( m_fMinY, m_fMaxY, m_fChunkY, m_fMinChunkY ); +} + +void GridWindow::drawGrid(vcl::RenderContext& rRenderContext) +{ + char pBuf[256]; + rRenderContext.SetLineColor(COL_BLACK); + // draw vertical lines + for (double fX = m_fMinChunkX; fX < m_fMaxX; fX += m_fChunkX) + { + drawLine(rRenderContext, fX, m_fMinY, fX, m_fMaxY); + // draw tickmarks + Point aPt = transform(fX, m_fMinY); + std::sprintf(pBuf, "%g", fX); + OUString aMark(pBuf, strlen(pBuf), osl_getThreadTextEncoding()); + Size aTextSize(rRenderContext.GetTextWidth(aMark), rRenderContext.GetTextHeight()); + aPt.AdjustX( -(aTextSize.Width() / 2) ); + aPt.AdjustY(aTextSize.Height() / 2 ); + rRenderContext.DrawText(aPt, aMark); + } + // draw horizontal lines + for (double fY = m_fMinChunkY; fY < m_fMaxY; fY += m_fChunkY) + { + drawLine(rRenderContext, m_fMinX, fY, m_fMaxX, fY); + // draw tickmarks + Point aPt = transform(m_fMinX, fY); + std::sprintf(pBuf, "%g", fY); + OUString aMark(pBuf, strlen(pBuf), osl_getThreadTextEncoding()); + Size aTextSize(rRenderContext.GetTextWidth(aMark), rRenderContext.GetTextHeight()); + aPt.AdjustX( -(aTextSize.Width() + 2) ); + aPt.AdjustY( -(aTextSize.Height() / 2) ); + rRenderContext.DrawText(aPt, aMark); + } + + // draw boundings + drawLine(rRenderContext, m_fMinX, m_fMinY, m_fMaxX, m_fMinY); + drawLine(rRenderContext, m_fMinX, m_fMaxY, m_fMaxX, m_fMaxY); + drawLine(rRenderContext, m_fMinX, m_fMinY, m_fMinX, m_fMaxY); + drawLine(rRenderContext, m_fMaxX, m_fMinY, m_fMaxX, m_fMaxY); +} + +void GridWindow::drawOriginal(vcl::RenderContext& rRenderContext) +{ + if (m_nValues && m_pXValues && m_pOrigYValues) + { + rRenderContext.SetLineColor(COL_RED); + for (int i = 0; i < m_nValues - 1; i++) + { + drawLine(rRenderContext, + m_pXValues[i], m_pOrigYValues[i], + m_pXValues[i + 1], m_pOrigYValues[i + 1]); + } + } +} + +void GridWindow::drawNew(vcl::RenderContext& rRenderContext) +{ + if (m_nValues && m_pXValues && m_pNewYValues) + { + rRenderContext.SetClipRegion(vcl::Region(m_aGridArea)); + rRenderContext.SetLineColor(COL_YELLOW); + for (int i = 0; i < m_nValues - 1; i++) + { + drawLine(rRenderContext, + m_pXValues[i], m_pNewYValues[i], + m_pXValues[i + 1], m_pNewYValues[i + 1]); + } + rRenderContext.SetClipRegion(); + } +} + +void GridWindow::drawHandles(vcl::RenderContext& rRenderContext) +{ + for(impHandle & rHandle : m_aHandles) + { + rHandle.draw(rRenderContext, m_aMarkerBitmap); + } +} + +void GridWindow::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&) +{ + rRenderContext.SetBackground(Wallpaper(Application::GetSettings().GetStyleSettings().GetDialogColor())); + drawGrid(rRenderContext); + drawOriginal(rRenderContext); + drawNew(rRenderContext); + drawHandles(rRenderContext); +} + +bool GridWindow::MouseMove( const MouseEvent& rEvt ) +{ + if( rEvt.GetButtons() != MOUSE_LEFT || m_nDragIndex == npos ) + return false; + + Point aPoint( rEvt.GetPosPixel() ); + + if( m_nDragIndex == 0 || m_nDragIndex == m_aHandles.size() - 1) + { + aPoint.setX( m_aHandles[m_nDragIndex].maPos.X() ); + } + else + { + if(aPoint.X() < m_aGridArea.Left()) + aPoint.setX( m_aGridArea.Left() ); + else if(aPoint.X() > m_aGridArea.Right()) + aPoint.setX( m_aGridArea.Right() ); + } + + if( aPoint.Y() < m_aGridArea.Top() ) + aPoint.setY( m_aGridArea.Top() ); + else if( aPoint.Y() > m_aGridArea.Bottom() ) + aPoint.setY( m_aGridArea.Bottom() ); + + if( aPoint != m_aHandles[m_nDragIndex].maPos ) + { + m_aHandles[m_nDragIndex].maPos = aPoint; + Invalidate( m_aGridArea ); + } + + return false; +} + +bool GridWindow::MouseButtonUp( const MouseEvent& rEvt ) +{ + if( rEvt.GetButtons() == MOUSE_LEFT ) + { + if( m_nDragIndex != npos ) + { + m_nDragIndex = npos; + computeNew(); + Invalidate(m_aGridArea); + } + } + + return false; +} + +bool GridWindow::MouseButtonDown( const MouseEvent& rEvt ) +{ + Point aPoint( rEvt.GetPosPixel() ); + Handles::size_type nMarkerIndex = npos; + + for(Handles::size_type a(0); nMarkerIndex == npos && a < m_aHandles.size(); a++) + { + if(m_aHandles[a].isHit(GetDrawingArea()->get_ref_device(), aPoint)) + { + nMarkerIndex = a; + } + } + + if( rEvt.GetButtons() == MOUSE_LEFT ) + { + // user wants to drag a button + if( nMarkerIndex != npos ) + { + m_nDragIndex = nMarkerIndex; + } + } + else if( rEvt.GetButtons() == MOUSE_RIGHT ) + { + // user wants to add/delete a button + if( nMarkerIndex != npos ) + { + if( nMarkerIndex != 0 && nMarkerIndex != m_aHandles.size() - 1) + { + // delete marker under mouse + if( m_nDragIndex == nMarkerIndex ) + m_nDragIndex = npos; + + m_aHandles.erase(m_aHandles.begin() + nMarkerIndex); + } + } + else + { + m_BmOffX = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Width() >> 1); + m_BmOffY = sal_uInt16(m_aMarkerBitmap.GetSizePixel().Height() >> 1); + m_aHandles.push_back(impHandle(aPoint, m_BmOffX, m_BmOffY)); + } + + computeNew(); + Invalidate(m_aGridArea); + } + + return false; +} + +void GridWindow::ChangeMode(ResetType nType) +{ + switch( nType ) + { + case ResetType::LINEAR_ASCENDING: + { + for( int i = 0; i < m_nValues; i++ ) + { + m_pNewYValues[ i ] = m_fMinY + (m_fMaxY-m_fMinY)/(m_fMaxX-m_fMinX)*(m_pXValues[i]-m_fMinX); + } + } + break; + case ResetType::LINEAR_DESCENDING: + { + for( int i = 0; i < m_nValues; i++ ) + { + m_pNewYValues[ i ] = m_fMaxY - (m_fMaxY-m_fMinY)/(m_fMaxX-m_fMinX)*(m_pXValues[i]-m_fMinX); + } + } + break; + case ResetType::RESET: + { + if( m_pOrigYValues && m_pNewYValues && m_nValues ) + memcpy( m_pNewYValues.get(), m_pOrigYValues, m_nValues*sizeof(double) ); + } + break; + case ResetType::EXPONENTIAL: + { + for( int i = 0; i < m_nValues; i++ ) + { + m_pNewYValues[ i ] = m_fMinY + (m_fMaxY-m_fMinY)*(rtl::math::expm1((m_pXValues[i]-m_fMinX)/(m_fMaxX-m_fMinX)))/(M_E-1.0); + } + } + break; + + default: + break; + } + + if (m_pNewYValues) + { + for(size_t i(0); i < m_aHandles.size(); i++) + { + // find nearest xvalue + double x, y; + transform( m_aHandles[i].maPos, x, y ); + int nIndex = 0; + double delta = std::fabs( x-m_pXValues[0] ); + for( int n = 1; n < m_nValues; n++ ) + { + if( delta > std::fabs( x - m_pXValues[ n ] ) ) + { + delta = std::fabs( x - m_pXValues[ n ] ); + nIndex = n; + } + } + if( 0 == i ) + m_aHandles[i].maPos = transform( m_fMinX, m_pNewYValues[ nIndex ] ); + else if( m_aHandles.size() - 1 == i ) + m_aHandles[i].maPos = transform( m_fMaxX, m_pNewYValues[ nIndex ] ); + else + m_aHandles[i].maPos = transform( m_pXValues[ nIndex ], m_pNewYValues[ nIndex ] ); + } + } + + Invalidate(); +} + +IMPL_LINK_NOARG(GridDialog, ClickButtonHdl, weld::Button&, void) +{ + int nType = m_xResetTypeBox->get_active(); + m_xGridWindow->ChangeMode(static_cast<ResetType>(nType)); +} + +double* GridDialog::getNewYValues() +{ + return m_xGridWindow->getNewYValues(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/grid.hxx b/extensions/source/scanner/grid.hxx new file mode 100644 index 000000000..86f1a521c --- /dev/null +++ b/extensions/source/scanner/grid.hxx @@ -0,0 +1,51 @@ +/* -*- 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 . + */ +#pragma once + +#include <vcl/customweld.hxx> +#include <vcl/weld.hxx> + +class GridWindow; + +enum class ResetType +{ + LINEAR_ASCENDING = 0, + LINEAR_DESCENDING = 1, + RESET = 2, + EXPONENTIAL = 3 +}; + +class GridDialog : public weld::GenericDialogController +{ + std::unique_ptr<weld::Button> m_xOKButton; + std::unique_ptr<weld::ComboBox> m_xResetTypeBox; + std::unique_ptr<weld::Button> m_xResetButton; + std::unique_ptr<GridWindow> m_xGridWindow; + std::unique_ptr<weld::CustomWeld> m_xGridWindowWND; + + DECL_LINK(ClickButtonHdl, weld::Button&, void); + +public: + GridDialog(weld::Window* pParent, double* pXValues, double* pYValues, int nValues); + virtual ~GridDialog() override; + void setBoundings(double fMinX, double fMinY, double fMaxX, double fMaxY); + double* getNewYValues(); +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/sane.cxx b/extensions/source/scanner/sane.cxx new file mode 100644 index 000000000..de03e1158 --- /dev/null +++ b/extensions/source/scanner/sane.cxx @@ -0,0 +1,996 @@ +/* -*- 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 <type_traits> +#include <math.h> + +#include <o3tl/safeint.hxx> +#include <osl/file.h> +#include <sal/log.hxx> +#include <tools/stream.hxx> +#include <unotools/tempfile.hxx> +#include "sane.hxx" +#include <dlfcn.h> +#include <stdio.h> +#include <sys/time.h> +#include <sal/config.h> +#include <sal/macros.h> +#include <memory> + +#if (OSL_DEBUG_LEVEL > 0) || defined DBG_UTIL +#include <stdarg.h> +#define dump_state( a, b, c, d ) fprintf( stderr, a, b, c, d ); +#else +#define dump_state( a, b, c, d ) ; +#endif +static void dbg_msg( const char* pString, ... ) +{ +#if (OSL_DEBUG_LEVEL > 0) || defined DBG_UTIL + va_list ap; + va_start( ap, pString ); + vfprintf( stderr, pString, ap ); + va_end( ap ); +#else + (void)pString; +#endif +} + +#define FAIL_SHUTDOWN_STATE( x, y, z ) \ + if( x != SANE_STATUS_GOOD ) \ + { \ + dump_state( "%s returned error %d (%s)\n", \ + y, x, p_strstatus( x ) ); \ + DeInit(); \ + return z; \ + } + +#define FAIL_STATE( x, y, z ) \ + if( x != SANE_STATUS_GOOD ) \ + { \ + dump_state( "%s returned error %d (%s)\n", \ + y, x, p_strstatus( x ) ); \ + return z; \ + } + +#define DUMP_STATE( x, y ) \ + if( x != SANE_STATUS_GOOD ) \ + { \ + dump_state( "%s returned error %d (%s)\n", \ + y, x, p_strstatus( x ) ); \ + } + +int Sane::nRefCount = 0; +oslModule Sane::pSaneLib = nullptr; +SANE_Int Sane::nVersion = 0; +SANE_Device** Sane::ppDevices = nullptr; +int Sane::nDevices = 0; + +SANE_Status (*Sane::p_init)( SANE_Int*, + SANE_Auth_Callback ) = nullptr; +void (*Sane::p_exit)() = nullptr; +SANE_Status (*Sane::p_get_devices)( const SANE_Device***, + SANE_Bool ) = nullptr; +SANE_Status (*Sane::p_open)( SANE_String_Const, SANE_Handle ) = nullptr; +void (*Sane::p_close)( SANE_Handle ) = nullptr; +const SANE_Option_Descriptor* (*Sane::p_get_option_descriptor)( + SANE_Handle, SANE_Int ) = nullptr; +SANE_Status (*Sane::p_control_option)( SANE_Handle, SANE_Int, + SANE_Action, void*, + SANE_Int* ) = nullptr; +SANE_Status (*Sane::p_get_parameters)( SANE_Handle, + SANE_Parameters* ) = nullptr; +SANE_Status (*Sane::p_start)( SANE_Handle ) = nullptr; +SANE_Status (*Sane::p_read)( SANE_Handle, SANE_Byte*, SANE_Int, + SANE_Int* ) = nullptr; +void (*Sane::p_cancel)( SANE_Handle ) = nullptr; +SANE_Status (*Sane::p_set_io_mode)( SANE_Handle, SANE_Bool ) = nullptr; +SANE_Status (*Sane::p_get_select_fd)( SANE_Handle, SANE_Int* ) = nullptr; +SANE_String_Const (*Sane::p_strstatus)( SANE_Status ) = nullptr; + +static bool bSaneSymbolLoadFailed = false; + +inline oslGenericFunction Sane::LoadSymbol( const char* pSymbolname ) +{ + oslGenericFunction pFunction = osl_getAsciiFunctionSymbol( pSaneLib, pSymbolname ); + if( ! pFunction ) + { + fprintf( stderr, "Could not load symbol %s\n", + pSymbolname ); + bSaneSymbolLoadFailed = true; + } + return pFunction; +} + +SANE_Status Sane::ControlOption( int nOption, SANE_Action nAction, + void* pData ) +{ + SANE_Int nInfo = 0; + + SANE_Status nStatus = p_control_option( maHandle, static_cast<SANE_Int>(nOption), + nAction, pData, &nInfo ); + DUMP_STATE( nStatus, "sane_control_option" ); +#if OSL_DEBUG_LEVEL > 0 + if( nStatus != SANE_STATUS_GOOD ) + { + const char* pAction = "Unknown"; + switch( nAction ) + { + case SANE_ACTION_GET_VALUE: + pAction = "SANE_ACTION_GET_VALUE";break; + case SANE_ACTION_SET_VALUE: + pAction = "SANE_ACTION_SET_VALUE";break; + case SANE_ACTION_SET_AUTO: + pAction = "SANE_ACTION_SET_AUTO";break; + } + dbg_msg( "Option: \"%s\" action: %s\n", + OUStringToOString(GetOptionName(nOption), osl_getThreadTextEncoding()).getStr(), + pAction ); + } +#endif + if( nInfo & SANE_INFO_RELOAD_OPTIONS ) + ReloadOptions(); + return nStatus; +} + +Sane::Sane() : + mnOptions( 0 ), + mnDevice( -1 ), + maHandle( nullptr ) +{ + if( ! nRefCount || ! pSaneLib ) + Init(); + nRefCount++; +}; + +Sane::~Sane() +{ + if( IsOpen() ) + Close(); + nRefCount--; + if( ! nRefCount && pSaneLib ) + DeInit(); +} + +void Sane::Init() +{ +#ifndef DISABLE_DYNLOADING + OUString sSaneLibName( "libsane" SAL_DLLEXTENSION ); + pSaneLib = osl_loadModule( sSaneLibName.pData, SAL_LOADMODULE_LAZY ); + if( ! pSaneLib ) + { + sSaneLibName = "libsane" SAL_DLLEXTENSION ".1"; + pSaneLib = osl_loadModule( sSaneLibName.pData, SAL_LOADMODULE_LAZY ); + } + // try reasonable places that might not be in the library search path + if( ! pSaneLib ) + { + OUString sSaneLibSystemPath( "/usr/local/lib/libsane" SAL_DLLEXTENSION ); + osl_getFileURLFromSystemPath( sSaneLibSystemPath.pData, &sSaneLibName.pData ); + pSaneLib = osl_loadModule( sSaneLibName.pData, SAL_LOADMODULE_LAZY ); + } +#endif + if( pSaneLib ) + { + bSaneSymbolLoadFailed = false; + p_init = reinterpret_cast<SANE_Status(*)(SANE_Int*, SANE_Auth_Callback )>( + LoadSymbol( "sane_init" )); + p_exit = reinterpret_cast<void(*)()>( + LoadSymbol( "sane_exit" )); + p_get_devices = reinterpret_cast<SANE_Status(*)(const SANE_Device***, + SANE_Bool )>( + LoadSymbol( "sane_get_devices" )); + p_open = reinterpret_cast<SANE_Status(*)(SANE_String_Const, SANE_Handle )>( + LoadSymbol( "sane_open" )); + p_close = reinterpret_cast<void(*)(SANE_Handle)>( + LoadSymbol( "sane_close" )); + p_get_option_descriptor = reinterpret_cast<const SANE_Option_Descriptor*(*)(SANE_Handle, + SANE_Int)>( + LoadSymbol( "sane_get_option_descriptor" )); + p_control_option = reinterpret_cast<SANE_Status(*)(SANE_Handle, SANE_Int, + SANE_Action, void*, SANE_Int*)>( + LoadSymbol( "sane_control_option" )); + p_get_parameters = reinterpret_cast<SANE_Status(*)(SANE_Handle,SANE_Parameters*)>( + LoadSymbol( "sane_get_parameters" )); + p_start = reinterpret_cast<SANE_Status(*)(SANE_Handle)>( + LoadSymbol( "sane_start" )); + p_read = reinterpret_cast<SANE_Status(*)(SANE_Handle, SANE_Byte*, + SANE_Int, SANE_Int* )>( + LoadSymbol( "sane_read" )); + p_cancel = reinterpret_cast<void(*)(SANE_Handle)>( + LoadSymbol( "sane_cancel" )); + p_set_io_mode = reinterpret_cast<SANE_Status(*)(SANE_Handle, SANE_Bool)>( + LoadSymbol( "sane_set_io_mode" )); + p_get_select_fd = reinterpret_cast<SANE_Status(*)(SANE_Handle, SANE_Int*)>( + LoadSymbol( "sane_get_select_fd" )); + p_strstatus = reinterpret_cast<SANE_String_Const(*)(SANE_Status)>( + LoadSymbol( "sane_strstatus" )); + if( bSaneSymbolLoadFailed ) + DeInit(); + else + { + SANE_Status nStatus = p_init( &nVersion, nullptr ); + FAIL_SHUTDOWN_STATE( nStatus, "sane_init", ); + nStatus = p_get_devices( const_cast<const SANE_Device***>(&ppDevices), + SANE_FALSE ); + FAIL_SHUTDOWN_STATE( nStatus, "sane_get_devices", ); + for( nDevices = 0 ; ppDevices[ nDevices ]; nDevices++ ) ; + } + } +#if (OSL_DEBUG_LEVEL > 0) || defined DBG_UTIL + else + fprintf( stderr, "libsane%s could not be opened: %s\n", SAL_DLLEXTENSION, + dlerror() ); +#endif +} + +void Sane::DeInit() +{ + if( pSaneLib ) + { + p_exit(); +#ifndef DISABLE_DYNLOADING + osl_unloadModule( pSaneLib ); +#endif + pSaneLib = nullptr; + } +} + +void Sane::ReloadDevices() +{ + if( IsOpen() ) + Close(); + DeInit(); + Init(); +} + +void Sane::ReloadOptions() +{ + if( ! IsOpen() ) + return; + + const SANE_Option_Descriptor* pZero = p_get_option_descriptor( maHandle, 0 ); + SANE_Word pOptions[2]; + SANE_Status nStatus = p_control_option( maHandle, 0, SANE_ACTION_GET_VALUE, + static_cast<void*>(pOptions), nullptr ); + if( nStatus != SANE_STATUS_GOOD ) + fprintf( stderr, "Error: sane driver returned %s while reading number of options !\n", p_strstatus( nStatus ) ); + + mnOptions = pOptions[ 0 ]; + if( o3tl::make_unsigned(pZero->size) > sizeof( SANE_Word ) ) + fprintf( stderr, "driver returned number of options with larger size than SANE_Word!!!\n" ); + mppOptions.reset(new const SANE_Option_Descriptor*[ mnOptions ]); + mppOptions[ 0 ] = pZero; + for( int i = 1; i < mnOptions; i++ ) + mppOptions[ i ] = p_get_option_descriptor( maHandle, i ); + + CheckConsistency( nullptr, true ); + + maReloadOptionsLink.Call( *this ); +} + +bool Sane::Open( const char* name ) +{ + SANE_Status nStatus = p_open( reinterpret_cast<SANE_String_Const>(name), &maHandle ); + FAIL_STATE( nStatus, "sane_open", false ); + + ReloadOptions(); + + if( mnDevice == -1 ) + { + OString aDevice( name ); + for( int i = 0; i < nDevices; i++ ) + { + if( aDevice == ppDevices[i]->name ) + { + mnDevice = i; + break; + } + } + } + + return true; +} + +bool Sane::Open( int n ) +{ + if( n >= 0 && n < nDevices ) + { + mnDevice = n; + return Open( ppDevices[n]->name ); + } + return false; +} + +void Sane::Close() +{ + if( maHandle ) + { + p_close( maHandle ); + mppOptions.reset(); + maHandle = nullptr; + mnDevice = -1; + } +} + +int Sane::GetOptionByName( const char* rName ) +{ + int i; + OString aOption( rName ); + for( i = 0; i < mnOptions; i++ ) + { + if( mppOptions[i]->name && aOption == mppOptions[i]->name ) + return i; + } + return -1; +} + +bool Sane::GetOptionValue( int n, bool& rRet ) +{ + if( ! maHandle || mppOptions[n]->type != SANE_TYPE_BOOL ) + return false; + SANE_Word nRet; + SANE_Status nStatus = ControlOption( n, SANE_ACTION_GET_VALUE, &nRet ); + if( nStatus != SANE_STATUS_GOOD ) + return false; + + rRet = nRet; + return true; +} + +bool Sane::GetOptionValue( int n, OString& rRet ) +{ + bool bSuccess = false; + if( ! maHandle || mppOptions[n]->type != SANE_TYPE_STRING ) + return false; + std::unique_ptr<char[]> pRet(new char[mppOptions[n]->size+1]); + SANE_Status nStatus = ControlOption( n, SANE_ACTION_GET_VALUE, pRet.get() ); + if( nStatus == SANE_STATUS_GOOD ) + { + bSuccess = true; + rRet = pRet.get(); + } + return bSuccess; +} + +bool Sane::GetOptionValue( int n, double& rRet, int nElement ) +{ + bool bSuccess = false; + + if( ! maHandle || ( mppOptions[n]->type != SANE_TYPE_INT && + mppOptions[n]->type != SANE_TYPE_FIXED ) ) + return false; + + std::unique_ptr<SANE_Word[]> pRet(new SANE_Word[mppOptions[n]->size/sizeof(SANE_Word)]); + SANE_Status nStatus = ControlOption( n, SANE_ACTION_GET_VALUE, pRet.get() ); + if( nStatus == SANE_STATUS_GOOD ) + { + bSuccess = true; + if( mppOptions[n]->type == SANE_TYPE_INT ) + rRet = static_cast<double>(pRet[ nElement ]); + else + rRet = SANE_UNFIX( pRet[nElement] ); + } + return bSuccess; +} + +bool Sane::GetOptionValue( int n, double* pSet ) +{ + if( ! maHandle || ( mppOptions[n]->type != SANE_TYPE_FIXED && + mppOptions[n]->type != SANE_TYPE_INT ) ) + return false; + + std::unique_ptr<SANE_Word[]> pFixedSet(new SANE_Word[mppOptions[n]->size/sizeof(SANE_Word)]); + SANE_Status nStatus = ControlOption( n, SANE_ACTION_GET_VALUE, pFixedSet.get() ); + if( nStatus != SANE_STATUS_GOOD ) + return false; + for( size_t i = 0; i <mppOptions[n]->size/sizeof(SANE_Word); i++ ) + { + if( mppOptions[n]->type == SANE_TYPE_FIXED ) + pSet[i] = SANE_UNFIX( pFixedSet[i] ); + else + pSet[i] = static_cast<double>(pFixedSet[i]); + } + return true; +} + +void Sane::SetOptionValue( int n, bool bSet ) +{ + if( ! maHandle || mppOptions[n]->type != SANE_TYPE_BOOL ) + return; + SANE_Word nRet = bSet ? SANE_TRUE : SANE_FALSE; + ControlOption( n, SANE_ACTION_SET_VALUE, &nRet ); +} + +void Sane::SetOptionValue( int n, std::u16string_view rSet ) +{ + if( ! maHandle || mppOptions[n]->type != SANE_TYPE_STRING ) + return; + OString aSet(OUStringToOString(rSet, osl_getThreadTextEncoding())); + ControlOption( n, SANE_ACTION_SET_VALUE, const_cast<char *>(aSet.getStr()) ); +} + +void Sane::SetOptionValue( int n, double fSet, int nElement ) +{ + if( ! maHandle || ( mppOptions[n]->type != SANE_TYPE_INT && + mppOptions[n]->type != SANE_TYPE_FIXED ) ) + return; + + if( mppOptions[n]->size/sizeof(SANE_Word) > 1 ) + { + std::unique_ptr<SANE_Word[]> pSet(new SANE_Word[mppOptions[n]->size/sizeof(SANE_Word)]); + SANE_Status nStatus = ControlOption( n, SANE_ACTION_GET_VALUE, pSet.get() ); + if( nStatus == SANE_STATUS_GOOD ) + { + pSet[nElement] = mppOptions[n]->type == SANE_TYPE_INT ? + static_cast<SANE_Word>(fSet) : SANE_FIX( fSet ); + ControlOption( n, SANE_ACTION_SET_VALUE, pSet.get() ); + } + } + else + { + SANE_Word nSetTo = + mppOptions[n]->type == SANE_TYPE_INT ? + static_cast<SANE_Word>(fSet) : SANE_FIX( fSet ); + + ControlOption( n, SANE_ACTION_SET_VALUE, &nSetTo ); + } +} + +void Sane::SetOptionValue( int n, double const * pSet ) +{ + if( ! maHandle || ( mppOptions[n]->type != SANE_TYPE_INT && + mppOptions[n]->type != SANE_TYPE_FIXED ) ) + return; + std::unique_ptr<SANE_Word[]> pFixedSet(new SANE_Word[mppOptions[n]->size/sizeof(SANE_Word)]); + for( size_t i = 0; i < mppOptions[n]->size/sizeof(SANE_Word); i++ ) + { + if( mppOptions[n]->type == SANE_TYPE_FIXED ) + pFixedSet[i] = SANE_FIX( pSet[i] ); + else + pFixedSet[i] = static_cast<SANE_Word>(pSet[i]); + } + ControlOption( n, SANE_ACTION_SET_VALUE, pFixedSet.get() ); +} + +namespace { + +enum FrameStyleType { + FrameStyle_BW, FrameStyle_Gray, FrameStyle_RGB, FrameStyle_Separated +}; + +} + +#define BYTE_BUFFER_SIZE 32768 + +static sal_uInt8 ReadValue( FILE* fp, int depth ) +{ + if( depth == 16 ) + { + sal_uInt16 nWord; + // data always come in native byte order ! + // 16 bits is not really supported by backends as of now + // e.g. UMAX Astra 1200S delivers 16 bit but in BIGENDIAN + // against SANE documentation (xscanimage gets the same result + // as we do + size_t items_read = fread( &nWord, 1, 2, fp ); + + if (items_read != 2) + { + SAL_WARN( "extensions.scanner", "short read, abandoning" ); + return 0; + } + + return static_cast<sal_uInt8>( nWord / 256 ); + } + sal_uInt8 nByte; + size_t items_read = fread( &nByte, 1, 1, fp ); + if (items_read != 1) + { + SAL_WARN( "extensions.scanner", "short read, abandoning" ); + return 0; + } + return nByte; +} + +bool Sane::CheckConsistency( const char* pMes, bool bInit ) +{ + static const SANE_Option_Descriptor** pDescArray = nullptr; + static const SANE_Option_Descriptor* pZero = nullptr; + + if( bInit ) + { + pDescArray = mppOptions.get(); + if( mppOptions ) + pZero = mppOptions[0]; + return true; + } + + bool bConsistent = true; + + if( pDescArray != mppOptions.get() ) + bConsistent = false; + if( pZero != mppOptions[0] ) + bConsistent = false; + + if( ! bConsistent ) + dbg_msg( "Sane is not consistent. (%s)\n", pMes ); + + return bConsistent; +} + +bool Sane::Start( BitmapTransporter& rBitmap ) +{ + int nStream = 0, nLine = 0, i = 0; + SANE_Parameters aParams; + FrameStyleType eType = FrameStyle_Gray; + bool bSuccess = true; + bool bWidthSet = false; + + if( ! maHandle ) + return false; + + int nWidthMM = 0; + int nHeightMM = 0; + double fTLx, fTLy, fResl = 0.0; + int nOption; + nOption = GetOptionByName( "tl-x" ); + if( nOption != -1 && + GetOptionValue( nOption, fTLx ) && + GetOptionUnit( nOption ) == SANE_UNIT_MM ) + { + double fBRx; + nOption = GetOptionByName( "br-x" ); + if( nOption != -1 && + GetOptionValue( nOption, fBRx ) && + GetOptionUnit( nOption ) == SANE_UNIT_MM ) + { + nWidthMM = static_cast<int>(fabs(fBRx - fTLx)); + } + } + nOption = GetOptionByName( "tl-y" ); + if( nOption != -1 && + GetOptionValue( nOption, fTLy ) && + GetOptionUnit( nOption ) == SANE_UNIT_MM ) + { + double fBRy; + nOption = GetOptionByName( "br-y" ); + if( nOption != -1 && + GetOptionValue( nOption, fBRy ) && + GetOptionUnit( nOption ) == SANE_UNIT_MM ) + { + nHeightMM = static_cast<int>(fabs(fBRy - fTLy)); + } + } + if( ( nOption = GetOptionByName( "resolution" ) ) != -1 ) + (void)GetOptionValue( nOption, fResl ); + + std::unique_ptr<sal_uInt8[]> pBuffer; + + SANE_Status nStatus = SANE_STATUS_GOOD; + + rBitmap.lock(); + SvMemoryStream& aConverter = rBitmap.getStream(); + aConverter.Seek( 0 ); + aConverter.SetEndian( SvStreamEndian::LITTLE ); + + // write bitmap stream header + aConverter.WriteChar( 'B' ).WriteChar( 'M' ); + aConverter.WriteUInt32( 0 ); + aConverter.WriteUInt32( 0 ); + aConverter.WriteUInt32( 60 ); + + // write BITMAPINFOHEADER + aConverter.WriteUInt32( 40 ); + aConverter.WriteUInt32( 0 ); // fill in width later + aConverter.WriteUInt32( 0 ); // fill in height later + aConverter.WriteUInt16( 1 ); + // create header for 24 bits + // correct later if necessary + aConverter.WriteUInt16( 24 ); + aConverter.WriteUInt32( 0 ); + aConverter.WriteUInt32( 0 ); + aConverter.WriteUInt32( 0 ); + aConverter.WriteUInt32( 0 ); + aConverter.WriteUInt32( 0 ); + aConverter.WriteUInt32( 0 ); + + for( nStream=0; nStream < 3 && bSuccess ; nStream++ ) + { + nStatus = p_start( maHandle ); + DUMP_STATE( nStatus, "sane_start" ); + CheckConsistency( "sane_start" ); + if( nStatus == SANE_STATUS_GOOD ) + { + nStatus = p_get_parameters( maHandle, &aParams ); + DUMP_STATE( nStatus, "sane_get_parameters" ); + CheckConsistency( "sane_get_parameters" ); + if (nStatus != SANE_STATUS_GOOD || aParams.bytes_per_line == 0) + { + bSuccess = false; + break; + } +#if (OSL_DEBUG_LEVEL > 0) || defined DBG_UTIL + const char* const ppFormats[] = { "SANE_FRAME_GRAY", "SANE_FRAME_RGB", + "SANE_FRAME_RED", "SANE_FRAME_GREEN", + "SANE_FRAME_BLUE", "Unknown !!!" }; + fprintf( stderr, "Parameters for frame %d:\n", nStream ); + if( static_cast< + typename std::make_unsigned< + typename std::underlying_type<SANE_Frame>::type>::type>( + aParams.format) + > 4 ) + { + aParams.format = SANE_Frame(5); + } + fprintf( stderr, "format: %s\n", ppFormats[ static_cast<int>(aParams.format) ] ); + fprintf( stderr, "last_frame: %s\n", aParams.last_frame ? "TRUE" : "FALSE" ); + fprintf( stderr, "depth: %d\n", static_cast<int>(aParams.depth) ); + fprintf( stderr, "pixels_per_line: %d\n", static_cast<int>(aParams.pixels_per_line) ); + fprintf( stderr, "bytes_per_line: %d\n", static_cast<int>(aParams.bytes_per_line) ); +#endif + if( ! pBuffer ) + { + pBuffer.reset(new sal_uInt8[ BYTE_BUFFER_SIZE < 4*aParams.bytes_per_line ? 4*aParams.bytes_per_line : BYTE_BUFFER_SIZE ]); + } + + if( aParams.last_frame ) + nStream=3; + + switch( aParams.format ) + { + case SANE_FRAME_GRAY: + eType = FrameStyle_Gray; + if( aParams.depth == 1 ) + eType = FrameStyle_BW; + break; + case SANE_FRAME_RGB: + eType = FrameStyle_RGB; + break; + case SANE_FRAME_RED: + case SANE_FRAME_GREEN: + case SANE_FRAME_BLUE: + eType = FrameStyle_Separated; + break; + default: + fprintf( stderr, "Warning: unknown frame style !!!\n" ); + } + + bool bSynchronousRead = true; + + // should be fail safe, but ... ?? + nStatus = p_set_io_mode( maHandle, SANE_FALSE ); + CheckConsistency( "sane_set_io_mode" ); + if( nStatus != SANE_STATUS_GOOD ) + { + bSynchronousRead = false; + nStatus = p_set_io_mode(maHandle, SANE_TRUE); + CheckConsistency( "sane_set_io_mode" ); + if (nStatus != SANE_STATUS_GOOD) + { + SAL_WARN("extensions.scanner", "SANE driver status is: " << nStatus); + } + } + + SANE_Int nLen=0; + SANE_Int fd = 0; + + if( ! bSynchronousRead ) + { + nStatus = p_get_select_fd( maHandle, &fd ); + DUMP_STATE( nStatus, "sane_get_select_fd" ); + CheckConsistency( "sane_get_select_fd" ); + if( nStatus != SANE_STATUS_GOOD ) + bSynchronousRead = true; + } + utl::TempFile aFrame; + aFrame.EnableKillingFile(); + FILE* pFrame = fopen(OUStringToOString(aFrame.GetFileName(), osl_getThreadTextEncoding()).getStr(), "w+b"); + if( ! pFrame ) + { + bSuccess = false; + break; + } + do { + if( ! bSynchronousRead ) + { + fd_set fdset; + struct timeval tv; + + FD_ZERO( &fdset ); + FD_SET( static_cast<int>(fd), &fdset ); + tv.tv_sec = 5; + tv.tv_usec = 0; + if( select( fd+1, &fdset, nullptr, nullptr, &tv ) == 0 ) + fprintf( stderr, "Timeout on sane_read descriptor\n" ); + } + nLen = 0; + nStatus = p_read( maHandle, pBuffer.get(), BYTE_BUFFER_SIZE, &nLen ); + CheckConsistency( "sane_read" ); + if( nLen && ( nStatus == SANE_STATUS_GOOD || + nStatus == SANE_STATUS_EOF ) ) + { + bSuccess = (static_cast<size_t>(nLen) == fwrite( pBuffer.get(), 1, nLen, pFrame )); + if (!bSuccess) + break; + } + else + DUMP_STATE( nStatus, "sane_read" ); + } while( nStatus == SANE_STATUS_GOOD ); + if (nStatus != SANE_STATUS_EOF || !bSuccess) + { + fclose( pFrame ); + bSuccess = false; + break; + } + + int nFrameLength = ftell( pFrame ); + fseek( pFrame, 0, SEEK_SET ); + sal_uInt32 nWidth = static_cast<sal_uInt32>(aParams.pixels_per_line); + sal_uInt32 nHeight = static_cast<sal_uInt32>(nFrameLength / aParams.bytes_per_line); + if( ! bWidthSet ) + { + if( ! fResl ) + fResl = 300; // if all else fails that's a good guess + if( ! nWidthMM ) + nWidthMM = static_cast<int>((static_cast<double>(nWidth) / fResl) * 25.4); + if( ! nHeightMM ) + nHeightMM = static_cast<int>((static_cast<double>(nHeight) / fResl) * 25.4); + SAL_INFO("extensions.scanner", "set dimensions to(" << nWidth << ", " << nHeight << ") Pixel, (" << nWidthMM << ", " << nHeightMM << + ") mm, resolution is " << fResl); + + aConverter.Seek( 18 ); + aConverter.WriteUInt32( nWidth ); + aConverter.WriteUInt32( nHeight ); + aConverter.Seek( 38 ); + aConverter.WriteUInt32( 1000*nWidth/nWidthMM ); + aConverter.WriteUInt32( 1000*nHeight/nHeightMM ); + bWidthSet = true; + } + aConverter.Seek(60); + + if( eType == FrameStyle_BW ) + { + aConverter.Seek( 10 ); + aConverter.WriteUInt32( 64 ); + aConverter.Seek( 28 ); + aConverter.WriteUInt16( 1 ); + aConverter.Seek( 54 ); + // write color table + aConverter.WriteUInt16( 0xffff ); + aConverter.WriteUChar( 0xff ); + aConverter.WriteUChar( 0 ); + aConverter.WriteUInt32( 0 ); + aConverter.Seek( 64 ); + } + else if( eType == FrameStyle_Gray ) + { + aConverter.Seek( 10 ); + aConverter.WriteUInt32( 1084 ); + aConverter.Seek( 28 ); + aConverter.WriteUInt16( 8 ); + aConverter.Seek( 54 ); + // write color table + for( nLine = 0; nLine < 256; nLine++ ) + { + aConverter.WriteUChar( nLine ); + aConverter.WriteUChar( nLine ); + aConverter.WriteUChar( nLine ); + aConverter.WriteUChar( 0 ); + } + aConverter.Seek( 1084 ); + } + + for (nLine = nHeight-1; nLine >= 0; --nLine) + { + if (fseek(pFrame, nLine * aParams.bytes_per_line, SEEK_SET) == -1) + { + bSuccess = false; + break; + } + if( eType == FrameStyle_BW || + ( eType == FrameStyle_Gray && aParams.depth == 8 ) + ) + { + SANE_Int items_read = fread( pBuffer.get(), 1, aParams.bytes_per_line, pFrame ); + if (items_read != aParams.bytes_per_line) + { + SAL_WARN( "extensions.scanner", "short read, padding with zeros" ); + memset(pBuffer.get() + items_read, 0, aParams.bytes_per_line - items_read); + } + aConverter.WriteBytes(pBuffer.get(), aParams.bytes_per_line); + } + else if( eType == FrameStyle_Gray ) + { + for( i = 0; i < (aParams.pixels_per_line); i++ ) + { + sal_uInt8 nGray = ReadValue( pFrame, aParams.depth ); + aConverter.WriteUChar( nGray ); + } + } + else if( eType == FrameStyle_RGB ) + { + for( i = 0; i < (aParams.pixels_per_line); i++ ) + { + sal_uInt8 nRed, nGreen, nBlue; + nRed = ReadValue( pFrame, aParams.depth ); + nGreen = ReadValue( pFrame, aParams.depth ); + nBlue = ReadValue( pFrame, aParams.depth ); + aConverter.WriteUChar( nBlue ); + aConverter.WriteUChar( nGreen ); + aConverter.WriteUChar( nRed ); + } + } + else if( eType == FrameStyle_Separated ) + { + for( i = 0; i < (aParams.pixels_per_line); i++ ) + { + sal_uInt8 nValue = ReadValue( pFrame, aParams.depth ); + switch( aParams.format ) + { + case SANE_FRAME_RED: + aConverter.SeekRel( 2 ); + aConverter.WriteUChar( nValue ); + break; + case SANE_FRAME_GREEN: + aConverter.SeekRel( 1 ); + aConverter.WriteUChar( nValue ); + aConverter.SeekRel( 1 ); + break; + case SANE_FRAME_BLUE: + aConverter.WriteUChar( nValue ); + aConverter.SeekRel( 2 ); + break; + case SANE_FRAME_GRAY: + case SANE_FRAME_RGB: + break; + } + } + } + int nGap = aConverter.Tell() & 3; + if (nGap) + aConverter.SeekRel( 4-nGap ); + } + fclose( pFrame ); // deletes tmpfile + if( eType != FrameStyle_Separated ) + break; + } + else + bSuccess = false; + } + // get stream length + int nPos = aConverter.TellEnd(); + + aConverter.Seek( 2 ); + aConverter.WriteUInt32( nPos+1 ); + aConverter.Seek( 0 ); + + rBitmap.unlock(); + + if( bSuccess ) + { + // only cancel a successful operation + // sane disrupts memory else + p_cancel( maHandle ); + CheckConsistency( "sane_cancel" ); + } + pBuffer.reset(); + + ReloadOptions(); + + + dbg_msg( "Sane::Start returns with %s\n", bSuccess ? "TRUE" : "FALSE" ); + + return bSuccess; +} + +int Sane::GetRange( int n, std::unique_ptr<double[]>& rpDouble ) +{ + if( mppOptions[n]->constraint_type != SANE_CONSTRAINT_RANGE && + mppOptions[n]->constraint_type != SANE_CONSTRAINT_WORD_LIST ) + { + return -1; + } + + rpDouble = nullptr; + int nItems, i; + bool bIsFixed = mppOptions[n]->type == SANE_TYPE_FIXED; + + dbg_msg( "Sane::GetRange of option %s ", mppOptions[n]->name ); + if(mppOptions[n]->constraint_type == SANE_CONSTRAINT_RANGE ) + { + double fMin, fMax, fQuant; + if( bIsFixed ) + { + fMin = SANE_UNFIX( mppOptions[n]->constraint.range->min ); + fMax = SANE_UNFIX( mppOptions[n]->constraint.range->max ); + fQuant = SANE_UNFIX( mppOptions[n]->constraint.range->quant ); + } + else + { + fMin = static_cast<double>(mppOptions[n]->constraint.range->min); + fMax = static_cast<double>(mppOptions[n]->constraint.range->max); + fQuant = static_cast<double>(mppOptions[n]->constraint.range->quant); + } + if( fQuant != 0.0 ) + { + dbg_msg( "quantum range [ %lg ; %lg ; %lg ]\n", + fMin, fQuant, fMax ); + nItems = static_cast<int>((fMax - fMin)/fQuant)+1; + rpDouble.reset(new double[ nItems ]); + double fValue = fMin; + for( i = 0; i < nItems; i++, fValue += fQuant ) + rpDouble[i] = fValue; + rpDouble[ nItems-1 ] = fMax; + return nItems; + } + else + { + dbg_msg( "normal range [ %lg %lg ]\n", + fMin, fMax ); + rpDouble.reset(new double[2]); + rpDouble[0] = fMin; + rpDouble[1] = fMax; + return 0; + } + } + else + { + nItems = mppOptions[n]->constraint.word_list[0]; + rpDouble.reset(new double[nItems]); + for( i=0; i<nItems; i++ ) + { + rpDouble[i] = bIsFixed ? + SANE_UNFIX( mppOptions[n]->constraint.word_list[i+1] ) : + static_cast<double>(mppOptions[n]->constraint.word_list[i+1]); + } + dbg_msg( "wordlist [ %lg ... %lg ]\n", + rpDouble[ 0 ], rpDouble[ nItems-1 ] ); + return nItems; + } +} + +static const char *ppUnits[] = { + "", + "[Pixel]", + "[Bit]", + "[mm]", + "[DPI]", + "[%]", + "[usec]" +}; + +OUString Sane::GetOptionUnitName( int n ) +{ + OUString aText; + SANE_Unit nUnit = mppOptions[n]->unit; + size_t nUnitAsSize = static_cast<size_t>(nUnit); + if (nUnitAsSize >= SAL_N_ELEMENTS( ppUnits )) + aText = "[unknown units]"; + else + aText = OUString( ppUnits[ nUnit ], strlen(ppUnits[ nUnit ]), osl_getThreadTextEncoding() ); + return aText; +} + +bool Sane::ActivateButtonOption( int n ) +{ + SANE_Status nStatus = ControlOption( n, SANE_ACTION_SET_VALUE, nullptr ); + return nStatus == SANE_STATUS_GOOD; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/sane.hxx b/extensions/source/scanner/sane.hxx new file mode 100644 index 000000000..a0e631942 --- /dev/null +++ b/extensions/source/scanner/sane.hxx @@ -0,0 +1,190 @@ +/* -*- 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 . + */ +#pragma once + +#include <sal/config.h> + +#include <string_view> + +#include <cppuhelper/implbase.hxx> +#include <osl/thread.h> +#include <osl/module.h> +#include <tools/stream.hxx> +#include <tools/link.hxx> +#include <sane/sane.h> + +#include <com/sun/star/awt/XBitmap.hpp> +#include <com/sun/star/uno/Sequence.hxx> + +using namespace com::sun::star::uno; + + +class BitmapTransporter: public cppu::WeakImplHelper<css::awt::XBitmap> +{ + SvMemoryStream m_aStream; + osl::Mutex m_aProtector; + +public: + + BitmapTransporter(); + virtual ~BitmapTransporter() override; + + virtual css::awt::Size SAL_CALL getSize() override; + virtual Sequence< sal_Int8 > SAL_CALL getDIB() override; + virtual Sequence< sal_Int8 > SAL_CALL getMaskDIB() override { return Sequence< sal_Int8 >(); } + + // Misc + void lock() { m_aProtector.acquire(); } + void unlock() { m_aProtector.release(); } + SvMemoryStream& getStream() { return m_aStream; } +}; + + +class Sane +{ +private: + static int nRefCount; + static oslModule pSaneLib; + + static SANE_Status (*p_init)( SANE_Int*, + SANE_Auth_Callback ); + static void (*p_exit)(); + static SANE_Status (*p_get_devices)( const SANE_Device***, + SANE_Bool ); + static SANE_Status (*p_open)( SANE_String_Const, SANE_Handle ); + static void (*p_close)( SANE_Handle ); + static const SANE_Option_Descriptor* (*p_get_option_descriptor)( + SANE_Handle, SANE_Int ); + static SANE_Status (*p_control_option)( SANE_Handle, SANE_Int, + SANE_Action, void*, + SANE_Int* ); + static SANE_Status (*p_get_parameters)( SANE_Handle, + SANE_Parameters* ); + static SANE_Status (*p_start)( SANE_Handle ); + static SANE_Status (*p_read)( SANE_Handle, SANE_Byte*, SANE_Int, + SANE_Int* ); + static void (*p_cancel)( SANE_Handle ); + static SANE_Status (*p_set_io_mode)( SANE_Handle, SANE_Bool ); + static SANE_Status (*p_get_select_fd)( SANE_Handle, SANE_Int* ); + static SANE_String_Const (*p_strstatus)( SANE_Status ); + + static SANE_Int nVersion; + static SANE_Device** ppDevices; + static int nDevices; + + std::unique_ptr<const SANE_Option_Descriptor*[]> mppOptions; + int mnOptions; + int mnDevice; + SANE_Handle maHandle; + + Link<Sane&,void> maReloadOptionsLink; + + static inline oslGenericFunction + LoadSymbol( const char* ); + static void Init(); + static void DeInit(); + + SANE_Status ControlOption( int, SANE_Action, void* ); + + bool CheckConsistency( const char*, bool bInit = false ); + +public: + Sane(); + ~Sane(); + + static bool IsSane() + { return pSaneLib != nullptr; } + bool IsOpen() const + { return maHandle != nullptr; } + static int CountDevices() + { return nDevices; } + static OUString GetName( int n ) + { return ppDevices[n]->name ? OUString( ppDevices[n]->name, strlen(ppDevices[n]->name), osl_getThreadTextEncoding() ) : OUString(); } + static OUString GetVendor( int n ) + { return ppDevices[n]->vendor ? OUString( ppDevices[n]->vendor, strlen(ppDevices[n]->vendor), osl_getThreadTextEncoding() ) : OUString(); } + static OUString GetModel( int n ) + { return ppDevices[n]->model ? OUString( ppDevices[n]->model, strlen(ppDevices[n]->model), osl_getThreadTextEncoding() ) : OUString(); } + static OUString GetType( int n ) + { return ppDevices[n]->type ? OUString( ppDevices[n]->type, strlen(ppDevices[n]->type), osl_getThreadTextEncoding() ) : OUString(); } + + OUString GetOptionName( int n ) + { return mppOptions[n]->name ? OUString( mppOptions[n]->name, strlen(mppOptions[n]->name), osl_getThreadTextEncoding() ) : OUString(); } + OUString GetOptionTitle( int n ) + { return mppOptions[n]->title ? OUString( mppOptions[n]->title, strlen(mppOptions[n]->title), osl_getThreadTextEncoding() ) : OUString(); } + SANE_Value_Type GetOptionType( int n ) + { return mppOptions[n]->type; } + SANE_Unit GetOptionUnit( int n ) + { return mppOptions[n]->unit; } + OUString GetOptionUnitName( int n ); + SANE_Int GetOptionCap( int n ) + { return mppOptions[n]->cap; } + SANE_Constraint_Type GetOptionConstraintType( int n ) + { return mppOptions[n]->constraint_type; } + const char** GetStringConstraint( int n ) + { return const_cast<const char**>(mppOptions[n]->constraint.string_list); } + int GetRange( int, std::unique_ptr<double[]>& ); + + inline int GetOptionElements( int n ); + int GetOptionByName( const char* ); + bool GetOptionValue( int, bool& ); + bool GetOptionValue( int, OString& ); + bool GetOptionValue( int, double&, int nElement = 0 ); + bool GetOptionValue( int, double* ); + + void SetOptionValue( int, bool ); + void SetOptionValue( int, std::u16string_view ); + void SetOptionValue( int, double, int nElement = 0 ); + void SetOptionValue( int, double const * ); + + bool ActivateButtonOption( int ); + + int CountOptions() { return mnOptions; } + int GetDeviceNumber() const { return mnDevice; } + + bool Open( const char* ); + bool Open( int ); + void Close(); + void ReloadDevices(); + void ReloadOptions(); + + bool Start( BitmapTransporter& ); + + inline Link<Sane&,void> SetReloadOptionsHdl( const Link<Sane&,void>& rLink ); +}; + + +inline int Sane::GetOptionElements( int n ) +{ + if( mppOptions[n]->type == SANE_TYPE_FIXED || + mppOptions[n]->type == SANE_TYPE_INT ) + { + return mppOptions[n]->size/sizeof( SANE_Word ); + } + return 1; +} + + +inline Link<Sane&,void> Sane::SetReloadOptionsHdl( const Link<Sane&,void>& rLink ) +{ + Link<Sane&,void> aRet = maReloadOptionsLink; + maReloadOptionsLink = rLink; + return aRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/sanedlg.cxx b/extensions/source/scanner/sanedlg.cxx new file mode 100644 index 000000000..8486195c9 --- /dev/null +++ b/extensions/source/scanner/sanedlg.cxx @@ -0,0 +1,1467 @@ +/* -*- 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 <stdio.h> +#include <stdlib.h> +#include <tools/config.hxx> +#include <unotools/resmgr.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/customweld.hxx> +#include <vcl/dibtools.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 DrawRectangles(vcl::RenderContext& rRenderContext, Point const & rUL, Point const & rBR) +{ + int nMiddleX, nMiddleY; + Point aBL, aUR; + + aUR = Point(rBR.X(), rUL.Y()); + aBL = Point(rUL.X(), rBR.Y()); + nMiddleX = (rBR.X() - rUL.X()) / 2 + rUL.X(); + nMiddleY = (rBR.Y() - rUL.Y()) / 2 + rUL.Y(); + + rRenderContext.DrawLine(rUL, aBL); + rRenderContext.DrawLine(aBL, rBR); + rRenderContext.DrawLine(rBR, aUR); + rRenderContext.DrawLine(aUR, rUL); + rRenderContext.DrawRect(tools::Rectangle(rUL, Size(RECT_SIZE_PIX,RECT_SIZE_PIX))); + rRenderContext.DrawRect(tools::Rectangle(aBL, Size(RECT_SIZE_PIX, -RECT_SIZE_PIX))); + rRenderContext.DrawRect(tools::Rectangle(rBR, Size(-RECT_SIZE_PIX, -RECT_SIZE_PIX))); + rRenderContext.DrawRect(tools::Rectangle(aUR, Size(-RECT_SIZE_PIX, RECT_SIZE_PIX ))); + rRenderContext.DrawRect(tools::Rectangle(Point(nMiddleX - RECT_SIZE_PIX / 2, rUL.Y()), Size(RECT_SIZE_PIX, RECT_SIZE_PIX))); + rRenderContext.DrawRect(tools::Rectangle(Point(nMiddleX - RECT_SIZE_PIX / 2, rBR.Y()), Size(RECT_SIZE_PIX, -RECT_SIZE_PIX))); + rRenderContext.DrawRect(tools::Rectangle(Point(rUL.X(), nMiddleY - RECT_SIZE_PIX / 2), Size(RECT_SIZE_PIX, RECT_SIZE_PIX))); + rRenderContext.DrawRect(tools::Rectangle(Point(rBR.X(), nMiddleY - RECT_SIZE_PIX / 2), Size(-RECT_SIZE_PIX, RECT_SIZE_PIX))); +} + +} + +class ScanPreview : public weld::CustomWidgetController +{ +private: + enum DragDirection { TopLeft, Top, TopRight, Right, BottomRight, Bottom, + BottomLeft, Left }; + + BitmapEx maPreviewBitmapEx; + tools::Rectangle maPreviewRect; + Point maLastUL, maLastBR; + Point maTopLeft, maBottomRight; + Point maMinTopLeft, maMaxBottomRight; + SaneDlg* mpParentDialog; + DragDirection meDragDirection; + bool mbDragEnable; + bool mbDragDrawn; + bool mbIsDragging; + +public: + ScanPreview() + : maMaxBottomRight(PREVIEW_WIDTH, PREVIEW_HEIGHT) + , mpParentDialog(nullptr) + , meDragDirection(TopLeft) + , mbDragEnable(false) + , mbDragDrawn(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(maPreviewBitmapEx, 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, "modules/scanner/ui/sanedialog.ui", "SaneDialog") + , mpParent(pParent) + , mrSane(rSane) + , mbScanEnabled(bScanEnabled) + , mnCurrentOption(0) + , mnCurrentElement(0) + , mfMin(0.0) + , mfMax(0.0) + , doScan(false) + , mxCancelButton(m_xBuilder->weld_button("cancel")) + , mxDeviceInfoButton(m_xBuilder->weld_button("deviceInfoButton")) + , mxPreviewButton(m_xBuilder->weld_button("previewButton")) + , mxScanButton(m_xBuilder->weld_button("ok")) + , mxButtonOption(m_xBuilder->weld_button("optionsButton")) + , mxOptionTitle(m_xBuilder->weld_label("optionTitleLabel")) + , mxOptionDescTxt(m_xBuilder->weld_label("optionsDescLabel")) + , mxVectorTxt(m_xBuilder->weld_label("vectorLabel")) + , mxLeftField(m_xBuilder->weld_metric_spin_button("leftSpinbutton", FieldUnit::PIXEL)) + , mxTopField(m_xBuilder->weld_metric_spin_button("topSpinbutton", FieldUnit::PIXEL)) + , mxRightField(m_xBuilder->weld_metric_spin_button("rightSpinbutton", FieldUnit::PIXEL)) + , mxBottomField(m_xBuilder->weld_metric_spin_button("bottomSpinbutton", FieldUnit::PIXEL)) + , mxDeviceBox(m_xBuilder->weld_combo_box("deviceCombobox")) + , mxReslBox(m_xBuilder->weld_combo_box("reslCombobox")) + , mxAdvancedBox(m_xBuilder->weld_check_button("advancedCheckbutton")) + , mxVectorBox(m_xBuilder->weld_spin_button("vectorSpinbutton")) + , mxQuantumRangeBox(m_xBuilder->weld_combo_box("quantumRangeCombobox")) + , mxStringRangeBox(m_xBuilder->weld_combo_box("stringRangeCombobox")) + , mxBoolCheckBox(m_xBuilder->weld_check_button("boolCheckbutton")) + , mxStringEdit(m_xBuilder->weld_entry("stringEntry")) + , mxNumericEdit(m_xBuilder->weld_entry("numericEntry")) + , mxOptionBox(m_xBuilder->weld_tree_view("optionSvTreeListBox")) + , mxPreview(new ScanPreview) + , mxPreviewWnd(new weld::CustomWeld(*m_xBuilder, "preview", *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_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 < SAL_N_ELEMENTS(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; + 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]; + 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( maPreviewBitmapEx.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.DrawBitmapEx(maPreviewRect.TopLeft(), maPreviewRect.GetSize(), maPreviewBitmapEx); + + mbDragDrawn = false; + 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++ ) + { + sprintf( pBuf, "%g", mpRange[ i ] ); + mxQuantumRangeBox->append_text( OUString( pBuf, strlen(pBuf), osl_getThreadTextEncoding() ) ); + } + double fValue; + if( mrSane.GetOptionValue( mnCurrentOption, fValue, mnCurrentElement ) ) + { + sprintf( pBuf, "%g", fValue ); + mxQuantumRangeBox->set_active_text( OUString( pBuf, strlen(pBuf), osl_getThreadTextEncoding() ) ); + } + mxQuantumRangeBox->show(); + OUString aText( mrSane.GetOptionName( mnCurrentOption ) + " " ); + aText += 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 ) + " " ); + aText += mrSane.GetOptionUnitName( mnCurrentOption ); + if( mfMin != mfMax ) + { + sprintf( pBuf, " < %g ; %g >", mfMin, mfMax ); + aText += OUString( pBuf, strlen(pBuf), osl_getThreadTextEncoding() ); + } + mxOptionDescTxt->set_label( aText ); + mxOptionDescTxt->show(); + 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; + + RasterOp eROP = rRenderContext.GetRasterOp(); + rRenderContext.SetRasterOp(RasterOp::Invert); + rRenderContext.SetMapMode(MapMode(MapUnit::MapPixel)); + + if (mbDragDrawn) + DrawRectangles(rRenderContext, maLastUL, maLastBR); + + maLastUL = maTopLeft; + maLastBR = maBottomRight; + DrawRectangles(rRenderContext, maTopLeft, maBottomRight); + + mbDragDrawn = true; + rRenderContext.SetRasterOp(eROP); + 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" ); + OString aString = aConfig.ReadKey( "SO_LastSaneDevice" ); + 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" ); + aConfig.WriteKey( "SO_LastSANEDevice", + 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(':'); + 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: */ diff --git a/extensions/source/scanner/sanedlg.hxx b/extensions/source/scanner/sanedlg.hxx new file mode 100644 index 000000000..e9ba1540d --- /dev/null +++ b/extensions/source/scanner/sanedlg.hxx @@ -0,0 +1,111 @@ +/* -*- 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 . + */ +#pragma once + +#include <vcl/customweld.hxx> +#include <vcl/weld.hxx> + +#include "sane.hxx" + +class ScanPreview; + +class SaneDlg : public weld::GenericDialogController +{ +private: + weld::Window* mpParent; + Sane& mrSane; + bool mbScanEnabled; + + Link<Sane&,void> maOldLink; + + int mnCurrentOption; + int mnCurrentElement; + std::unique_ptr<double[]> mpRange; + double mfMin, mfMax; + + bool doScan; + + std::unique_ptr<weld::Button> mxCancelButton; + std::unique_ptr<weld::Button> mxDeviceInfoButton; + std::unique_ptr<weld::Button> mxPreviewButton; + std::unique_ptr<weld::Button> mxScanButton; + std::unique_ptr<weld::Button> mxButtonOption; + + std::unique_ptr<weld::Label> mxOptionTitle; + std::unique_ptr<weld::Label> mxOptionDescTxt; + std::unique_ptr<weld::Label> mxVectorTxt; + + std::unique_ptr<weld::MetricSpinButton> mxLeftField; + std::unique_ptr<weld::MetricSpinButton> mxTopField; + std::unique_ptr<weld::MetricSpinButton> mxRightField; + std::unique_ptr<weld::MetricSpinButton> mxBottomField; + + std::unique_ptr<weld::ComboBox> mxDeviceBox; + std::unique_ptr<weld::ComboBox> mxReslBox; + std::unique_ptr<weld::CheckButton> mxAdvancedBox; + + std::unique_ptr<weld::SpinButton> mxVectorBox; + std::unique_ptr<weld::ComboBox> mxQuantumRangeBox; + std::unique_ptr<weld::ComboBox> mxStringRangeBox; + + std::unique_ptr<weld::CheckButton> mxBoolCheckBox; + + std::unique_ptr<weld::Entry> mxStringEdit; + std::unique_ptr<weld::Entry> mxNumericEdit; + + std::unique_ptr<weld::TreeView> mxOptionBox; + + std::unique_ptr<ScanPreview> mxPreview; + std::unique_ptr<weld::CustomWeld> mxPreviewWnd; + + DECL_LINK( ClickBtnHdl, weld::Button&, void ); + DECL_LINK( ToggleBtnHdl, weld::Toggleable&, void ); + DECL_LINK( SelectHdl, weld::ComboBox&, void ); + DECL_LINK( ModifyHdl, weld::Entry&, void ); + DECL_LINK( MetricValueModifyHdl, weld::MetricSpinButton&, void ); + DECL_LINK( ValueModifyHdl, weld::ComboBox&, void ); + DECL_LINK( ReloadSaneOptionsHdl, Sane&, void ); + DECL_LINK( OptionsBoxSelectHdl, weld::TreeView&, void ); + + void SaveState(); + bool LoadState(); + + void InitDevices(); + void InitFields(); + void AcquirePreview(); + void DisableOption(); + void EstablishBoolOption(); + void EstablishStringOption(); + void EstablishStringRange(); + void EstablishQuantumRange(); + void EstablishNumericOption(); + void EstablishButtonOption(); + + // helper + bool SetAdjustedNumericalValue( const char* pOption, double fValue, int nElement = 0 ); +public: + SaneDlg(weld::Window*, Sane&, bool); + virtual ~SaneDlg() override; + + virtual short run() override; + void UpdateScanArea( bool ); + bool getDoScan() const { return doScan;} +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/scanner.cxx b/extensions/source/scanner/scanner.cxx new file mode 100644 index 000000000..b661a4f7e --- /dev/null +++ b/extensions/source/scanner/scanner.cxx @@ -0,0 +1,89 @@ +/* -*- 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 "scanner.hxx" + +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/namedvaluecollection.hxx> +#include <com/sun/star/uno/XComponentContext.hpp> + +Reference< XInterface > ScannerManager_CreateInstance( const Reference< css::lang::XMultiServiceFactory >& /*rxFactory*/ ) +{ + return *( new ScannerManager() ); +} + + +ScannerManager::ScannerManager() : + mpData( nullptr ) +{ + AcquireData(); +} + + +ScannerManager::~ScannerManager() +{ + ReleaseData(); +} + + +Sequence< sal_Int8 > SAL_CALL ScannerManager::getMaskDIB() +{ + return Sequence< sal_Int8 >(); +} + + +OUString ScannerManager::getImplementationName() +{ + return "com.sun.star.scanner.ScannerManager"; +} + + +sal_Bool ScannerManager::supportsService(OUString const & ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + + +css::uno::Sequence<OUString> ScannerManager::getSupportedServiceNames() +{ + return { "com.sun.star.scanner.ScannerManager" }; +} + + +sal_Bool SAL_CALL ScannerManager::configureScanner( ScannerContext& rContext ) +{ + return configureScannerAndScan( rContext, nullptr ); +} + +void SAL_CALL ScannerManager::initialize(const css::uno::Sequence<css::uno::Any>& rArguments) +{ + ::comphelper::NamedValueCollection aProperties(rArguments); + if (aProperties.has("ParentWindow")) + aProperties.get("ParentWindow") >>= mxDialogParent; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +extensions_ScannerManager_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new ScannerManager()); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/scanner.hxx b/extensions/source/scanner/scanner.hxx new file mode 100644 index 000000000..42e03a453 --- /dev/null +++ b/extensions/source/scanner/scanner.hxx @@ -0,0 +1,85 @@ +/* -*- 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 . + */ + +#pragma once + +#include <osl/mutex.hxx> +#include <rtl/ustring.hxx> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/uno/Reference.h> +#include <com/sun/star/uno/Sequence.h> +#include <com/sun/star/awt/XBitmap.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/lang/XEventListener.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/scanner/XScannerManager2.hpp> + +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::scanner; + +class ScannerManager final : + public cppu::WeakImplHelper< + XScannerManager2, css::awt::XBitmap, css::lang::XServiceInfo, css::lang::XInitialization> +{ + osl::Mutex maProtector; + css::uno::Reference<css::awt::XWindow> mxDialogParent; + void* mpData; + + static void AcquireData(); + void ReleaseData(); + +public: + + ScannerManager(); + virtual ~ScannerManager() override; + + // XScannerManager + virtual Sequence< ScannerContext > SAL_CALL getAvailableScanners() override; + virtual sal_Bool SAL_CALL configureScanner( ScannerContext& scanner_context ) override; + virtual sal_Bool SAL_CALL configureScannerAndScan( ScannerContext& scanner_context, const Reference< css::lang::XEventListener >& rxListener ) override; + virtual void SAL_CALL startScan( const ScannerContext& scanner_context, const Reference< css::lang::XEventListener >& rxListener ) override; + virtual ScanError SAL_CALL getError( const ScannerContext& scanner_context ) override; + virtual Reference< css::awt::XBitmap > SAL_CALL getBitmap( const ScannerContext& scanner_context ) override; + + // XBitmap + virtual css::awt::Size SAL_CALL getSize() override; + virtual Sequence< sal_Int8 > SAL_CALL getDIB() override; + virtual Sequence< sal_Int8 > SAL_CALL getMaskDIB() override; + + OUString SAL_CALL getImplementationName() override; + + sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override; + + css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + virtual void SAL_CALL initialize(const css::uno::Sequence<css::uno::Any>& rArguments) override; + +#ifdef _WIN32 + void* GetData() const { return mpData; } +#endif + void SetData( void* pData ) { ReleaseData(); mpData = pData; } + }; + +/// @throws Exception +Reference< XInterface > ScannerManager_CreateInstance( const Reference< css::lang::XMultiServiceFactory >& rxFactory ); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/scanunx.cxx b/extensions/source/scanner/scanunx.cxx new file mode 100644 index 000000000..b06e30a27 --- /dev/null +++ b/extensions/source/scanner/scanunx.cxx @@ -0,0 +1,341 @@ +/* -*- 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 "scanner.hxx" +#include "sanedlg.hxx" +#include <o3tl/safeint.hxx> +#include <osl/thread.hxx> +#include <sal/log.hxx> +#include <vcl/svapp.hxx> +#include <memory> + +#include <com/sun/star/scanner/ScannerException.hpp> + +BitmapTransporter::BitmapTransporter() +{ + SAL_INFO("extensions.scanner", "BitmapTransporter"); +} + + +BitmapTransporter::~BitmapTransporter() +{ + SAL_INFO("extensions.scanner", "~BitmapTransporter"); +} + + +css::awt::Size BitmapTransporter::getSize() +{ + osl::MutexGuard aGuard( m_aProtector ); + css::awt::Size aRet; + + // ensure that there is at least a header + int nLen = m_aStream.TellEnd(); + if( nLen > 15 ) + { + int nPreviousPos = m_aStream.Tell(); + m_aStream.Seek( 4 ); + m_aStream.ReadInt32( aRet.Width ).ReadInt32( aRet.Height ); + m_aStream.Seek( nPreviousPos ); + } + else + aRet.Width = aRet.Height = 0; + + + return aRet; +} + + +Sequence< sal_Int8 > BitmapTransporter::getDIB() +{ + osl::MutexGuard aGuard( m_aProtector ); + int nPreviousPos = m_aStream.Tell(); + + // create return value + int nBytes = m_aStream.TellEnd(); + m_aStream.Seek( 0 ); + + Sequence< sal_Int8 > aValue( nBytes ); + m_aStream.ReadBytes( aValue.getArray(), nBytes ); + m_aStream.Seek( nPreviousPos ); + + return aValue; +} + +namespace { + +struct SaneHolder +{ + Sane m_aSane; + Reference< css::awt::XBitmap > m_xBitmap; + osl::Mutex m_aProtector; + ScanError m_nError; + bool m_bBusy; + + SaneHolder() : m_nError(ScanError_ScanErrorNone), m_bBusy(false) {} +}; + + typedef std::vector< std::shared_ptr<SaneHolder> > sanevec; + class allSanes + { + private: + int mnRefCount; + public: + sanevec m_aSanes; + allSanes() : mnRefCount(0) {} + void acquire(); + void release(); + }; + + void allSanes::acquire() + { + ++mnRefCount; + } + + void allSanes::release() + { + // was unused, now because of i99835: "Scanning interface not SANE API + // compliant" destroy all SaneHolder to get Sane Dtor called + --mnRefCount; + if (!mnRefCount) + m_aSanes.clear(); + } + + struct theSaneProtector : public rtl::Static<osl::Mutex, theSaneProtector> {}; + struct theSanes : public rtl::Static<allSanes, theSanes> {}; + +class ScannerThread : public osl::Thread +{ + std::shared_ptr<SaneHolder> m_pHolder; + Reference< css::lang::XEventListener > m_xListener; + ScannerManager* m_pManager; // just for the disposing call + +public: + virtual void SAL_CALL run() override; + virtual void SAL_CALL onTerminated() override { delete this; } +public: + ScannerThread( const std::shared_ptr<SaneHolder>& pHolder, + const Reference< css::lang::XEventListener >& listener, + ScannerManager* pManager ); + virtual ~ScannerThread() override; +}; + +} + +ScannerThread::ScannerThread(const std::shared_ptr<SaneHolder>& pHolder, + const Reference< css::lang::XEventListener >& listener, + ScannerManager* pManager) + : m_pHolder( pHolder ), m_xListener( listener ), m_pManager( pManager ) +{ + SAL_INFO("extensions.scanner", "ScannerThread"); +} + + +ScannerThread::~ScannerThread() +{ + SAL_INFO("extensions.scanner", "~ScannerThread"); +} + + +void ScannerThread::run() +{ + osl_setThreadName("ScannerThread"); + + osl::MutexGuard aGuard( m_pHolder->m_aProtector ); + rtl::Reference<BitmapTransporter> pTransporter = new BitmapTransporter; + + m_pHolder->m_xBitmap = pTransporter; + + m_pHolder->m_bBusy = true; + if( m_pHolder->m_aSane.IsOpen() ) + { + int nOption = m_pHolder->m_aSane.GetOptionByName( "preview" ); + if( nOption != -1 ) + m_pHolder->m_aSane.SetOptionValue( nOption, false ); + + m_pHolder->m_nError = + m_pHolder->m_aSane.Start( *pTransporter ) ? + ScanError_ScanErrorNone : ScanError_ScanCanceled; + } + else + m_pHolder->m_nError = ScanError_ScannerNotAvailable; + + + Reference< XInterface > xXInterface( static_cast< OWeakObject* >( m_pManager ) ); + m_xListener->disposing( css::lang::EventObject(xXInterface) ); + m_pHolder->m_bBusy = false; +} + + +void ScannerManager::AcquireData() +{ + osl::MutexGuard aGuard( theSaneProtector::get() ); + theSanes::get().acquire(); +} + + +void ScannerManager::ReleaseData() +{ + osl::MutexGuard aGuard( theSaneProtector::get() ); + theSanes::get().release(); +} + + +css::awt::Size ScannerManager::getSize() +{ + css::awt::Size aRet; + aRet.Width = aRet.Height = 0; + return aRet; +} + + +Sequence< sal_Int8 > ScannerManager::getDIB() +{ + return Sequence< sal_Int8 >(); +} + + +Sequence< ScannerContext > ScannerManager::getAvailableScanners() +{ + osl::MutexGuard aGuard( theSaneProtector::get() ); + sanevec &rSanes = theSanes::get().m_aSanes; + + if( rSanes.empty() ) + { + auto pSaneHolder = std::make_shared<SaneHolder>(); + if( Sane::IsSane() ) + rSanes.push_back( pSaneHolder ); + } + + if( Sane::IsSane() ) + { + Sequence< ScannerContext > aRet{ { /* ScannerName */ "SANE", /* InternalData */ 0 } }; + return aRet; + } + + return Sequence< ScannerContext >(); +} + + +sal_Bool ScannerManager::configureScannerAndScan( ScannerContext& scanner_context, + const Reference< css::lang::XEventListener >& listener ) +{ + bool bRet; + bool bScan; + { + osl::MutexGuard aGuard( theSaneProtector::get() ); + sanevec &rSanes = theSanes::get().m_aSanes; + + SAL_INFO("extensions.scanner", "ScannerManager::configureScanner"); + + if( scanner_context.InternalData < 0 || o3tl::make_unsigned(scanner_context.InternalData) >= rSanes.size() ) + throw ScannerException( + "Scanner does not exist", + Reference< XScannerManager >( this ), + ScanError_InvalidContext + ); + + std::shared_ptr<SaneHolder> pHolder = rSanes[scanner_context.InternalData]; + if( pHolder->m_bBusy ) + throw ScannerException( + "Scanner is busy", + Reference< XScannerManager >( this ), + ScanError_ScanInProgress + ); + + pHolder->m_bBusy = true; + SaneDlg aDlg(Application::GetFrameWeld(mxDialogParent), pHolder->m_aSane, listener.is()); + bRet = aDlg.run(); + bScan = aDlg.getDoScan(); + pHolder->m_bBusy = false; + } + if ( bScan ) + startScan( scanner_context, listener ); + + return bRet; +} + + +void ScannerManager::startScan( const ScannerContext& scanner_context, + const Reference< css::lang::XEventListener >& listener ) +{ + osl::MutexGuard aGuard( theSaneProtector::get() ); + sanevec &rSanes = theSanes::get().m_aSanes; + + SAL_INFO("extensions.scanner", "ScannerManager::startScan"); + + if( scanner_context.InternalData < 0 || o3tl::make_unsigned(scanner_context.InternalData) >= rSanes.size() ) + throw ScannerException( + "Scanner does not exist", + Reference< XScannerManager >( this ), + ScanError_InvalidContext + ); + std::shared_ptr<SaneHolder> pHolder = rSanes[scanner_context.InternalData]; + if( pHolder->m_bBusy ) + throw ScannerException( + "Scanner is busy", + Reference< XScannerManager >( this ), + ScanError_ScanInProgress + ); + pHolder->m_bBusy = true; + + ScannerThread* pThread = new ScannerThread( pHolder, listener, this ); + pThread->create(); +} + + +ScanError ScannerManager::getError( const ScannerContext& scanner_context ) +{ + osl::MutexGuard aGuard( theSaneProtector::get() ); + sanevec &rSanes = theSanes::get().m_aSanes; + + if( scanner_context.InternalData < 0 || o3tl::make_unsigned(scanner_context.InternalData) >= rSanes.size() ) + throw ScannerException( + "Scanner does not exist", + Reference< XScannerManager >( this ), + ScanError_InvalidContext + ); + + std::shared_ptr<SaneHolder> pHolder = rSanes[scanner_context.InternalData]; + + return pHolder->m_nError; +} + + +Reference< css::awt::XBitmap > ScannerManager::getBitmap( const ScannerContext& scanner_context ) +{ + osl::MutexGuard aGuard( theSaneProtector::get() ); + sanevec &rSanes = theSanes::get().m_aSanes; + + if( scanner_context.InternalData < 0 || o3tl::make_unsigned(scanner_context.InternalData) >= rSanes.size() ) + throw ScannerException( + "Scanner does not exist", + Reference< XScannerManager >( this ), + ScanError_InvalidContext + ); + std::shared_ptr<SaneHolder> pHolder = rSanes[scanner_context.InternalData]; + + osl::MutexGuard aProtGuard( pHolder->m_aProtector ); + + Reference< css::awt::XBitmap > xRet( pHolder->m_xBitmap ); + pHolder->m_xBitmap.clear(); + + return xRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/scanwin.cxx b/extensions/source/scanner/scanwin.cxx new file mode 100644 index 000000000..109f2944a --- /dev/null +++ b/extensions/source/scanner/scanwin.cxx @@ -0,0 +1,646 @@ +/* -*- 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 <sal/config.h> + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/frame/XFrame.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/scanner/ScannerException.hpp> + +#include "twain32shim.hxx" + +#include <comphelper/processfactory.hxx> +#include <comphelper/scopeguard.hxx> +#include <config_folders.h> +#include <o3tl/char16_t2wchar_t.hxx> +#include <osl/conditn.hxx> +#include <osl/file.hxx> +#include <osl/mutex.hxx> +#include <rtl/bootstrap.hxx> +#include <salhelper/thread.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/stream.hxx> +#include <tools/helpers.hxx> +#include <vcl/svapp.hxx> +#include <vcl/window.hxx> +#include "scanner.hxx" + +namespace +{ +enum TwainState +{ + TWAIN_STATE_NONE = 0, + TWAIN_STATE_SCANNING = 1, + TWAIN_STATE_DONE = 2, + TWAIN_STATE_CANCELED = 3 +}; + +struct HANDLEDeleter +{ + using pointer = HANDLE; + void operator()(HANDLE h) { CloseHandle(h); } +}; + +using ScopedHANDLE = std::unique_ptr<HANDLE, HANDLEDeleter>; + +class Twain +{ +public: + Twain(); + ~Twain(); + + bool SelectSource(ScannerManager& rMgr, const VclPtr<vcl::Window>& xTopWindow); + bool PerformTransfer(ScannerManager& rMgr, + const css::uno::Reference<css::lang::XEventListener>& rxListener, + const VclPtr<vcl::Window>& xTopWindow); + void WaitReadyForNextTask(); + + TwainState GetState() const { return meState; } + +private: + friend class ShimListenerThread; + class ShimListenerThread : public salhelper::Thread + { + public: + ShimListenerThread(const VclPtr<vcl::Window>& xTopWindow); + ~ShimListenerThread() override; + void execute() override; + const OUString& getError() { return msErrorReported; } + + // These methods are executed outside of own thread + bool WaitInitialization(); + bool WaitRequestResult(); + void DontNotify() { mbDontNotify = true; } + void RequestDestroy(); + bool RequestSelectSource(); + bool RequestInitXfer(); + + private: + VclPtr<vcl::Window> mxTopWindow; // the window that we made modal + bool mbDontNotify = false; + HWND mhWndShim = nullptr; // shim main window handle + OUString msErrorReported; + osl::Condition mcInitCompleted; // initially not set + bool mbInitSucceeded = false; + osl::Condition mcGotRequestResult; + bool mbRequestResult = false; + + void SendShimRequest(WPARAM nRequest); + bool SendShimRequestWithResult(WPARAM nRequest); + void NotificationHdl(WPARAM nEvent, LPARAM lParam); + void NotifyOwner(WPARAM nEvent); + void NotifyXFerOwner(LPARAM nHandle); + }; + css::uno::Reference<css::lang::XEventListener> mxListener; + css::uno::Reference<css::scanner::XScannerManager> mxMgr; + ScannerManager* mpCurMgr = nullptr; + TwainState meState = TWAIN_STATE_NONE; + rtl::Reference<ShimListenerThread> mpThread; + osl::Mutex maMutex; + + DECL_LINK(ImpNotifyHdl, void*, void); + DECL_LINK(ImpNotifyXferHdl, void*, void); + void Notify(WPARAM nEvent); // called by shim communication thread to notify me + void NotifyXFer(LPARAM nHandle); // called by shim communication thread to notify me + + bool InitializeNewShim(ScannerManager& rMgr, const VclPtr<vcl::Window>& xTopWindow); + + void Reset(); // cleanup thread and manager +}; + +Twain aTwain; + +Twain::ShimListenerThread::ShimListenerThread(const VclPtr<vcl::Window>& xTopWindow) + : salhelper::Thread("TWAINShimListenerThread") + , mxTopWindow(xTopWindow) +{ + if (mxTopWindow) + { + mxTopWindow->IncModalCount(); // the operation is modal to the frame + } +} + +Twain::ShimListenerThread::~ShimListenerThread() +{ + if (mxTopWindow) + { + mxTopWindow->DecModalCount(); // unblock the frame + } +} + +bool Twain::ShimListenerThread::WaitInitialization() +{ + mcInitCompleted.wait(); + return mbInitSucceeded; +} + +bool Twain::ShimListenerThread::WaitRequestResult() +{ + mcGotRequestResult.wait(); + return mbRequestResult; +} + +void Twain::ShimListenerThread::SendShimRequest(WPARAM nRequest) +{ + if (mhWndShim) + PostMessageW(mhWndShim, WM_TWAIN_REQUEST, nRequest, 0); +} + +bool Twain::ShimListenerThread::SendShimRequestWithResult(WPARAM nRequest) +{ + mcGotRequestResult.reset(); + mbRequestResult = false; + SendShimRequest(nRequest); + return WaitRequestResult(); +} + +void Twain::ShimListenerThread::RequestDestroy() { SendShimRequest(TWAIN_REQUEST_QUIT); } + +bool Twain::ShimListenerThread::RequestSelectSource() +{ + assert(mbInitSucceeded); + return SendShimRequestWithResult(TWAIN_REQUEST_SELECTSOURCE); +} + +bool Twain::ShimListenerThread::RequestInitXfer() +{ + assert(mbInitSucceeded); + return SendShimRequestWithResult(TWAIN_REQUEST_INITXFER); +} + +void Twain::ShimListenerThread::NotifyOwner(WPARAM nEvent) +{ + if (!mbDontNotify) + aTwain.Notify(nEvent); +} + +void Twain::ShimListenerThread::NotifyXFerOwner(LPARAM nHandle) +{ + if (!mbDontNotify) + aTwain.NotifyXFer(nHandle); +} + +// May only be called from the own thread, so no threading issues modifying self +void Twain::ShimListenerThread::NotificationHdl(WPARAM nEvent, LPARAM lParam) +{ + switch (nEvent) + { + case TWAIN_EVENT_NOTIFYHWND: // shim reported its main HWND for communications + if (!mcInitCompleted.check()) // only if not yet initialized! + { + // Owner is still waiting mcInitCompleted in its Twain::InitializeNewShim, + // holding its access mutex + mhWndShim = reinterpret_cast<HWND>(lParam); + + mbInitSucceeded = lParam != 0; + mcInitCompleted.set(); + } + break; + case TWAIN_EVENT_SCANNING: + NotifyOwner(nEvent); + break; + case TWAIN_EVENT_XFER: + NotifyXFerOwner(lParam); + break; + case TWAIN_EVENT_REQUESTRESULT: + mbRequestResult = lParam; + mcGotRequestResult.set(); + break; + // We don't handle TWAIN_EVENT_QUIT notification from shim, because we send it ourselves + // in the end of execute() + } +} + +// Spawn a separate 32-bit process to use TWAIN on Windows, and listen for its notifications +void Twain::ShimListenerThread::execute() +{ + MSG msg; + // Initialize thread message queue before launching shim process + PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE); + + try + { + ScopedHANDLE hShimProcess; + { + // Determine twain32shim executable URL: + OUString shimURL("$BRAND_BASE_DIR/" LIBO_BIN_FOLDER "/twain32shim.exe"); + rtl::Bootstrap::expandMacros(shimURL); + + OUString sCmdLine; + if (osl::FileBase::getSystemPathFromFileURL(shimURL, sCmdLine) != osl::FileBase::E_None) + throw std::exception("getSystemPathFromFileURL failed!"); + + HANDLE hDup; + if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), + &hDup, SYNCHRONIZE | THREAD_QUERY_LIMITED_INFORMATION, TRUE, 0)) + ThrowLastError("DuplicateHandle"); + // we will not need our copy as soon as shim has its own inherited one + ScopedHANDLE hScopedDup(hDup); + DWORD nDup = static_cast<DWORD>(reinterpret_cast<sal_uIntPtr>(hDup)); + if (reinterpret_cast<HANDLE>(nDup) != hDup) + throw std::exception("HANDLE does not fit to 32 bit - cannot pass to shim!"); + + // Send this thread handle as the first parameter + sCmdLine = "\"" + sCmdLine + "\" " + OUString::number(nDup); + + // We need a WinAPI HANDLE of the process to be able to wait on it and detect the process + // termination; so use WinAPI to start the process, not osl_executeProcess. + + STARTUPINFOW si{}; + si.cb = sizeof(si); + PROCESS_INFORMATION pi; + + if (!CreateProcessW(nullptr, const_cast<LPWSTR>(o3tl::toW(sCmdLine.getStr())), nullptr, + nullptr, TRUE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi)) + ThrowLastError("CreateProcessW"); + + CloseHandle(pi.hThread); + hShimProcess.reset(pi.hProcess); + } + HANDLE h = hShimProcess.get(); + while (true) + { + DWORD nWaitResult = MsgWaitForMultipleObjects(1, &h, FALSE, INFINITE, QS_POSTMESSAGE); + // Process any messages in queue before checking if we need to break, to not loose + // possible pending notifications + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) + { + // process it here + if (msg.message == WM_TWAIN_EVENT) + { + NotificationHdl(msg.wParam, msg.lParam); + } + } + if (nWaitResult == WAIT_OBJECT_0) + { + // shim process exited - return + break; + } + if (nWaitResult == WAIT_FAILED) + { + // Some Win32 error - report and return + ThrowLastError("MsgWaitForMultipleObjects"); + } + } + } + catch (const std::exception& e) + { + msErrorReported = OUString(e.what(), strlen(e.what()), RTL_TEXTENCODING_UTF8); + // allow owner to resume (in case the condition isn't set yet) + mcInitCompleted.set(); // let mbInitSucceeded keep its (maybe false) value! + } + // allow owner to resume (in case the conditions isn't set yet) + mcGotRequestResult.set(); + NotifyOwner(TWAIN_EVENT_QUIT); +} + +Twain::Twain() {} + +Twain::~Twain() +{ + osl::MutexGuard aGuard(maMutex); + if (mpThread) + { + mpThread->DontNotify(); + mpThread->RequestDestroy(); + mpThread->join(); + mpThread.clear(); + } +} + +void Twain::Reset() +{ + mpThread->join(); + if (!mpThread->getError().isEmpty()) + SAL_WARN("extensions.scanner", mpThread->getError()); + mpThread.clear(); + mpCurMgr = nullptr; + mxMgr.clear(); +} + +bool Twain::InitializeNewShim(ScannerManager& rMgr, const VclPtr<vcl::Window>& xTopWindow) +{ + osl::MutexGuard aGuard(maMutex); + if (mpThread) + return false; // Have a shim for another task already! + + // hold reference to ScannerManager, to prevent premature death + mxMgr = mpCurMgr = &rMgr; + + mpThread.set(new ShimListenerThread(xTopWindow)); + mpThread->launch(); + const bool bSuccess = mpThread->WaitInitialization(); + if (!bSuccess) + Reset(); + + return bSuccess; +} + +void Twain::Notify(WPARAM nEvent) +{ + Application::PostUserEvent(LINK(this, Twain, ImpNotifyHdl), reinterpret_cast<void*>(nEvent)); +} + +void Twain::NotifyXFer(LPARAM nHandle) +{ + Application::PostUserEvent(LINK(this, Twain, ImpNotifyXferHdl), + reinterpret_cast<void*>(nHandle)); +} + +bool Twain::SelectSource(ScannerManager& rMgr, const VclPtr<vcl::Window>& xTopWindow) +{ + osl::MutexGuard aGuard(maMutex); + bool bRet = false; + + if (InitializeNewShim(rMgr, xTopWindow)) + { + meState = TWAIN_STATE_NONE; + bRet = mpThread->RequestSelectSource(); + } + + return bRet; +} + +bool Twain::PerformTransfer(ScannerManager& rMgr, + const css::uno::Reference<css::lang::XEventListener>& rxListener, + const VclPtr<vcl::Window>& xTopWindow) +{ + osl::MutexGuard aGuard(maMutex); + bool bRet = false; + + if (InitializeNewShim(rMgr, xTopWindow)) + { + mxListener = rxListener; + meState = TWAIN_STATE_NONE; + bRet = mpThread->RequestInitXfer(); + } + + return bRet; +} + +void Twain::WaitReadyForNextTask() +{ + while ([&]() { + osl::MutexGuard aGuard(maMutex); + return bool(mpThread); + }()) + { + Application::Reschedule(true); + } +} + +IMPL_LINK(Twain, ImpNotifyHdl, void*, pParam, void) +{ + osl::MutexGuard aGuard(maMutex); + WPARAM nEvent = reinterpret_cast<WPARAM>(pParam); + switch (nEvent) + { + case TWAIN_EVENT_SCANNING: + meState = TWAIN_STATE_SCANNING; + break; + + case TWAIN_EVENT_QUIT: + { + if (meState != TWAIN_STATE_DONE) + meState = TWAIN_STATE_CANCELED; + + css::lang::EventObject event(mxMgr); // mxMgr will be cleared below + + if (mpThread) + Reset(); + + if (mxListener.is()) + { + mxListener->disposing(event); + mxListener.clear(); + } + } + break; + + default: + break; + } +} + +IMPL_LINK(Twain, ImpNotifyXferHdl, void*, pParam, void) +{ + osl::MutexGuard aGuard(maMutex); + if (mpThread) + { + mpCurMgr->SetData(pParam); + meState = pParam ? TWAIN_STATE_DONE : TWAIN_STATE_CANCELED; + + css::lang::EventObject event(mxMgr); // mxMgr will be cleared below + + Reset(); + + if (mxListener.is()) + mxListener->disposing(css::lang::EventObject(mxMgr)); + } + + mxListener.clear(); +} + +VclPtr<vcl::Window> ImplGetActiveFrameWindow() +{ + try + { + // query desktop instance + css::uno::Reference<css::frame::XDesktop2> xDesktop + = css::frame::Desktop::create(comphelper::getProcessComponentContext()); + if (css::uno::Reference<css::frame::XFrame> xActiveFrame = xDesktop->getActiveFrame()) + return VCLUnoHelper::GetWindow(xActiveFrame->getComponentWindow()); + } + catch (const css::uno::Exception&) + { + } + SAL_WARN("extensions.scanner", "ImplGetActiveFrame: Could not determine active frame!"); + return nullptr; +} + +} // namespace + +void ScannerManager::AcquireData() {} + +void ScannerManager::ReleaseData() +{ + if (mpData) + { + CloseHandle(static_cast<HANDLE>(mpData)); + mpData = nullptr; + } +} + +css::awt::Size ScannerManager::getSize() +{ + css::awt::Size aRet; + + if (mpData) + { + HANDLE hMap = static_cast<HANDLE>(mpData); + // map full size + const sal_Int8* pMap = static_cast<sal_Int8*>(MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0)); + if (pMap) + { + const BITMAPINFOHEADER* pBIH = reinterpret_cast<const BITMAPINFOHEADER*>(pMap + 4); + aRet.Width = pBIH->biWidth; + aRet.Height = pBIH->biHeight; + + UnmapViewOfFile(pMap); + } + } + + return aRet; +} + +css::uno::Sequence<sal_Int8> ScannerManager::getDIB() +{ + css::uno::Sequence<sal_Int8> aRet; + + if (mpData) + { + HANDLE hMap = static_cast<HANDLE>(mpData); + // map full size + const sal_Int8* pMap = static_cast<sal_Int8*>(MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0)); + if (pMap) + { + DWORD nDIBSize; + memcpy(&nDIBSize, pMap, 4); // size of the following DIB + + const BITMAPINFOHEADER* pBIH = reinterpret_cast<const BITMAPINFOHEADER*>(pMap + 4); + + sal_uInt32 nColEntries = 0; + + switch (pBIH->biBitCount) + { + case 1: + case 4: + case 8: + nColEntries = pBIH->biClrUsed ? pBIH->biClrUsed : (1 << pBIH->biBitCount); + break; + + case 24: + nColEntries = pBIH->biClrUsed ? pBIH->biClrUsed : 0; + break; + + case 16: + case 32: + nColEntries = pBIH->biClrUsed; + if (pBIH->biCompression == BI_BITFIELDS) + nColEntries += 3; + break; + } + + aRet = css::uno::Sequence<sal_Int8>(sizeof(BITMAPFILEHEADER) + nDIBSize); + + sal_Int8* pBuf = aRet.getArray(); + SvMemoryStream* pMemStm + = new SvMemoryStream(pBuf, sizeof(BITMAPFILEHEADER), StreamMode::WRITE); + + pMemStm->WriteChar('B').WriteChar('M').WriteUInt32(0).WriteUInt32(0); + pMemStm->WriteUInt32(sizeof(BITMAPFILEHEADER) + pBIH->biSize + + (nColEntries * sizeof(RGBQUAD))); + + delete pMemStm; + memcpy(pBuf + sizeof(BITMAPFILEHEADER), pBIH, nDIBSize); + + UnmapViewOfFile(pMap); + } + + ReleaseData(); + } + + return aRet; +} + +css::uno::Sequence<ScannerContext> SAL_CALL ScannerManager::getAvailableScanners() +{ + osl::MutexGuard aGuard(maProtector); + css::uno::Sequence<ScannerContext> aRet(1); + + aRet.getArray()[0].ScannerName = "TWAIN"; + aRet.getArray()[0].InternalData = 0; + + return aRet; +} + +sal_Bool SAL_CALL ScannerManager::configureScannerAndScan( + ScannerContext& rContext, const css::uno::Reference<css::lang::XEventListener>& rxListener) +{ + osl::MutexGuard aGuard(maProtector); + css::uno::Reference<XScannerManager> xThis(this); + + if (rContext.InternalData != 0 || rContext.ScannerName != "TWAIN") + throw ScannerException("Scanner does not exist", xThis, ScanError_InvalidContext); + + ReleaseData(); + + VclPtr<vcl::Window> xTopWindow = ImplGetActiveFrameWindow(); + if (xTopWindow) + xTopWindow + ->IncModalCount(); // to avoid changes between the two operations that each block the window + comphelper::ScopeGuard aModalGuard([xTopWindow]() { + if (xTopWindow) + xTopWindow->DecModalCount(); + }); + + const bool bSelected = aTwain.SelectSource(*this, xTopWindow); + if (bSelected) + { + aTwain.WaitReadyForNextTask(); + aTwain.PerformTransfer(*this, rxListener, xTopWindow); + } + return bSelected; +} + +void SAL_CALL +ScannerManager::startScan(const ScannerContext& rContext, + const css::uno::Reference<css::lang::XEventListener>& rxListener) +{ + osl::MutexGuard aGuard(maProtector); + css::uno::Reference<XScannerManager> xThis(this); + + if (rContext.InternalData != 0 || rContext.ScannerName != "TWAIN") + throw ScannerException("Scanner does not exist", xThis, ScanError_InvalidContext); + + ReleaseData(); + aTwain.PerformTransfer(*this, rxListener, ImplGetActiveFrameWindow()); +} + +ScanError SAL_CALL ScannerManager::getError(const ScannerContext& rContext) +{ + osl::MutexGuard aGuard(maProtector); + css::uno::Reference<XScannerManager> xThis(this); + + if (rContext.InternalData != 0 || rContext.ScannerName != "TWAIN") + throw ScannerException("Scanner does not exist", xThis, ScanError_InvalidContext); + + return ((aTwain.GetState() == TWAIN_STATE_CANCELED) ? ScanError_ScanCanceled + : ScanError_ScanErrorNone); +} + +css::uno::Reference<css::awt::XBitmap> + SAL_CALL ScannerManager::getBitmap(const ScannerContext& /*rContext*/) +{ + osl::MutexGuard aGuard(maProtector); + return css::uno::Reference<css::awt::XBitmap>(this); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/scn.component b/extensions/source/scanner/scn.component new file mode 100644 index 000000000..19215a7c3 --- /dev/null +++ b/extensions/source/scanner/scn.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.scanner.ScannerManager" + constructor="extensions_ScannerManager_get_implementation"> + <service name="com.sun.star.scanner.ScannerManager"/> + </implementation> +</component> diff --git a/extensions/source/scanner/twain32shim.cxx b/extensions/source/scanner/twain32shim.cxx new file mode 100644 index 000000000..beca35f4f --- /dev/null +++ b/extensions/source/scanner/twain32shim.cxx @@ -0,0 +1,604 @@ +/* -*- 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 . + */ + +/* + * twain32shim.exe is a separate 32-bit executable that serves as a shim + * between LibreOffice and Windows' 32-bit TWAIN component. Without it, + * it's impossible for 64-bit program to use TWAIN on Windows. + * Using 64-bit TWAIN DSM library from twain.org to avoid using the shim + * is not an option, because scanner manufacturers only provide 32-bit + * drivers, and 64-bit drivers are only offered as 3rd-party commercial + * products. The shim is also used in 32-bit LibreOffice for uniformity. +*/ + +#include "twain32shim.hxx" +#include <tools/helpers.hxx> +#include <twain/twain.h> +#include <o3tl/unit_conversion.hxx> + +#define WM_TWAIN_FALLBACK (WM_SHIM_INTERNAL + 0) + +namespace +{ +double FixToDouble(const TW_FIX32& rFix) { return rFix.Whole + rFix.Frac / 65536.; } + +const wchar_t sTwainWndClass[] = L"TwainClass"; + +class ImpTwain +{ +public: + ImpTwain(HANDLE hParentThread); + ~ImpTwain(); + +private: + enum class TWAINState + { + DSMunloaded = 1, + DSMloaded = 2, + DSMopened = 3, + DSopened = 4, + DSenabled = 5, + DSreadyToXfer = 6, + Xferring = 7, + }; + + TW_IDENTITY m_aAppId; + TW_IDENTITY m_aSrcId; + DWORD m_nParentThreadId; + HANDLE m_hProc; + DSMENTRYPROC m_pDSM = nullptr; + HMODULE m_hMod = nullptr; + TWAINState m_nCurState = TWAINState::DSMunloaded; + HWND m_hTwainWnd = nullptr; + HHOOK m_hTwainHook = nullptr; + HANDLE m_hMap = nullptr; // the *duplicated* handle + + static bool IsTwainClassWnd(HWND hWnd); + static ImpTwain* GetImpFromWnd(HWND hWnd); + static void ImplCreateWnd(HWND hWnd, LPARAM lParam); + static LRESULT CALLBACK WndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam); + static LRESULT CALLBACK MsgHook(int nCode, WPARAM wParam, LPARAM lParam); + + void Destroy() { ImplFallback(TWAIN_EVENT_QUIT); } + bool SelectSource(); + bool InitXfer(); + + void NotifyParent(WPARAM nEvent, LPARAM lParam); + bool ImplHandleMsg(MSG* pMsg); + void ImplOpenSourceManager(); + void ImplOpenSource(); + bool ImplEnableSource(); + void ImplXfer(); + void ImplFallback(WPARAM nEvent); + + void ImplFallbackHdl(WPARAM nEvent); + void ImplRequestHdl(WPARAM nRequest); +}; + +//static +bool ImpTwain::IsTwainClassWnd(HWND hWnd) +{ + const int nBufSize = SAL_N_ELEMENTS(sTwainWndClass); + wchar_t sClassName[nBufSize]; + return (GetClassNameW(hWnd, sClassName, nBufSize) && wcscmp(sClassName, sTwainWndClass) == 0); +} + +//static +ImpTwain* ImpTwain::GetImpFromWnd(HWND hWnd) +{ + if (!IsTwainClassWnd(hWnd)) + return nullptr; + return reinterpret_cast<ImpTwain*>(GetWindowLongPtrW(hWnd, GWLP_USERDATA)); +} + +//static +void ImpTwain::ImplCreateWnd(HWND hWnd, LPARAM lParam) +{ + CREATESTRUCT* pCS = reinterpret_cast<CREATESTRUCT*>(lParam); + if (pCS && IsTwainClassWnd(hWnd)) + SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pCS->lpCreateParams)); +} + +// static +LRESULT CALLBACK ImpTwain::WndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam) +{ + ImpTwain* pImpTwain = GetImpFromWnd(hWnd); + switch (nMsg) + { + case WM_CREATE: + ImplCreateWnd(hWnd, lParam); + break; + case WM_TWAIN_FALLBACK: + if (pImpTwain) + pImpTwain->ImplFallbackHdl(wParam); + break; + case WM_TWAIN_REQUEST: + if (pImpTwain) + pImpTwain->ImplRequestHdl(wParam); + break; + } + return DefWindowProcW(hWnd, nMsg, wParam, lParam); +} + +// static +LRESULT CALLBACK ImpTwain::MsgHook(int nCode, WPARAM wParam, LPARAM lParam) +{ + MSG* pMsg = reinterpret_cast<MSG*>(lParam); + if (nCode >= 0 && pMsg) + { + ImpTwain* pImpTwain = GetImpFromWnd(pMsg->hwnd); + if (pImpTwain && pImpTwain->ImplHandleMsg(pMsg)) + { + pMsg->message = WM_USER; + pMsg->lParam = 0; + + return 0; + } + } + + return CallNextHookEx(nullptr, nCode, wParam, lParam); +} + +HANDLE GetProcOfThread(HANDLE hThread) +{ + DWORD nProcId = GetProcessIdOfThread(hThread); + if (!nProcId) + ThrowLastError("GetProcessIdOfThread"); + HANDLE hRet = OpenProcess(PROCESS_DUP_HANDLE, FALSE, nProcId); + if (!hRet) + ThrowLastError("OpenProcess"); + return hRet; +} + +ImpTwain::ImpTwain(HANDLE hParentThread) + : m_aAppId{ /* Id */ 0, + { /* Version.MajorNum */ 1, + /* Version.MinorNum */ 0, + /* Version.Language */ TWLG_USA, + /* Version.Country */ TWCY_USA, + /* Version.Info */ "8.0" }, + /* ProtocolMajor */ TWON_PROTOCOLMAJOR, + /* ProtocolMinor */ TWON_PROTOCOLMINOR, + /* SupportedGroups */ DG_IMAGE | DG_CONTROL, + /* Manufacturer */ "Sun Microsystems", + /* ProductFamily */ "Office", + /* ProductName */ "Office" } + , m_nParentThreadId(GetThreadId(hParentThread)) + , m_hProc(GetProcOfThread(hParentThread)) +{ + WNDCLASSW aWc = { 0, &WndProc, 0, sizeof(WNDCLASSW), GetModuleHandleW(nullptr), + nullptr, nullptr, nullptr, nullptr, sTwainWndClass }; + if (!RegisterClassW(&aWc)) + ThrowLastError("RegisterClassW"); + m_hTwainWnd = CreateWindowExW(WS_EX_TOPMOST, aWc.lpszClassName, L"TWAIN", 0, 0, 0, 0, 0, + HWND_DESKTOP, nullptr, aWc.hInstance, this); + if (!m_hTwainWnd) + ThrowLastError("CreateWindowExW"); + m_hTwainHook = SetWindowsHookExW(WH_GETMESSAGE, &MsgHook, nullptr, GetCurrentThreadId()); + if (!m_hTwainHook) + ThrowLastError("SetWindowsHookExW"); + + NotifyParent(TWAIN_EVENT_NOTIFYHWND, reinterpret_cast<LPARAM>(m_hTwainWnd)); +} + +ImpTwain::~ImpTwain() +{ + DestroyWindow(m_hTwainWnd); + UnhookWindowsHookEx(m_hTwainHook); +} + +bool ImpTwain::SelectSource() +{ + TW_UINT16 nRet = TWRC_FAILURE; + + ImplOpenSourceManager(); + + if (TWAINState::DSMopened == m_nCurState) + { + TW_IDENTITY aIdent; + + aIdent.Id = 0; + aIdent.ProductName[0] = '\0'; + NotifyParent(TWAIN_EVENT_SCANNING, 0); + nRet = m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_USERSELECT, &aIdent); + } + + Destroy(); + return (TWRC_SUCCESS == nRet); +} + +bool ImpTwain::InitXfer() +{ + bool bRet = false; + + ImplOpenSourceManager(); + + if (TWAINState::DSMopened == m_nCurState) + { + ImplOpenSource(); + + if (TWAINState::DSopened == m_nCurState) + bRet = ImplEnableSource(); + } + + if (!bRet) + Destroy(); + + return bRet; +} + +void ImpTwain::ImplOpenSourceManager() +{ + if (TWAINState::DSMunloaded == m_nCurState) + { + m_hMod = LoadLibraryW(L"TWAIN_32.DLL"); + if (!m_hMod) + { + // Windows directory might not be in DLL search path sometimes, so try the full path + PWSTR sPath; + if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Windows, 0, nullptr, &sPath))) + { + std::wstring sPathAndFile = sPath; + CoTaskMemFree(sPath); + sPathAndFile += L"\\TWAIN_32.DLL"; + m_hMod = LoadLibraryW(sPathAndFile.c_str()); + } + } + if (m_hMod) + { + m_nCurState = TWAINState::DSMloaded; + + m_pDSM = reinterpret_cast<DSMENTRYPROC>(GetProcAddress(m_hMod, "DSM_Entry")); + if (m_pDSM + && (m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_PARENT, MSG_OPENDSM, &m_hTwainWnd) + == TWRC_SUCCESS)) + { + m_nCurState = TWAINState::DSMopened; + } + } + } +} + +void ImpTwain::ImplOpenSource() +{ + if (TWAINState::DSMopened == m_nCurState) + { + if ((m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_GETDEFAULT, &m_aSrcId) + == TWRC_SUCCESS) + && (m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_OPENDS, &m_aSrcId) + == TWRC_SUCCESS)) + { + TW_CAPABILITY aCap + = { CAP_XFERCOUNT, TWON_ONEVALUE, GlobalAlloc(GHND, sizeof(TW_ONEVALUE)) }; + TW_ONEVALUE* pVal = static_cast<TW_ONEVALUE*>(GlobalLock(aCap.hContainer)); + + pVal->ItemType = TWTY_INT16; + pVal->Item = 1; + GlobalUnlock(aCap.hContainer); + m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_CAPABILITY, MSG_SET, &aCap); + GlobalFree(aCap.hContainer); + m_nCurState = TWAINState::DSopened; + } + } +} + +bool ImpTwain::ImplEnableSource() +{ + bool bRet = false; + + if (TWAINState::DSopened == m_nCurState) + { + TW_USERINTERFACE aUI = { true, true, m_hTwainWnd }; + + NotifyParent(TWAIN_EVENT_SCANNING, 0); + m_nCurState = TWAINState::DSenabled; + + if (m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_USERINTERFACE, MSG_ENABLEDS, &aUI) + == TWRC_SUCCESS) + { + bRet = true; + } + else + { + // dialog failed + m_nCurState = TWAINState::DSopened; + } + } + + return bRet; +} + +void ImpTwain::NotifyParent(WPARAM nEvent, LPARAM lParam) +{ + PostThreadMessageW(m_nParentThreadId, WM_TWAIN_EVENT, nEvent, lParam); +} + +bool ImpTwain::ImplHandleMsg(MSG* pMsg) +{ + if (!m_pDSM) + return false; + + TW_EVENT aEvt = { pMsg, MSG_NULL }; + TW_UINT16 nRet = m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_EVENT, MSG_PROCESSEVENT, &aEvt); + + switch (aEvt.TWMessage) + { + case MSG_XFERREADY: + { + WPARAM nEvent = TWAIN_EVENT_QUIT; + + if (TWAINState::DSenabled == m_nCurState) + { + m_nCurState = TWAINState::DSreadyToXfer; + ImplXfer(); + + if (m_hMap) + nEvent = TWAIN_EVENT_XFER; + } + else if (TWAINState::Xferring == m_nCurState && m_hMap) + { + // Already sent TWAIN_EVENT_XFER; not processed yet; + // duplicate event + nEvent = TWAIN_EVENT_NONE; + } + + ImplFallback(nEvent); + } + break; + + case MSG_CLOSEDSREQ: + Destroy(); + break; + + case MSG_NULL: + nRet = TWRC_NOTDSEVENT; + break; + } + + return (TWRC_DSEVENT == nRet); +} + +void ImpTwain::ImplXfer() +{ + if (m_nCurState == TWAINState::DSreadyToXfer) + { + TW_IMAGEINFO aInfo; + HANDLE hDIB = nullptr; + double nXRes, nYRes; + + if (m_pDSM(&m_aAppId, &m_aSrcId, DG_IMAGE, DAT_IMAGEINFO, MSG_GET, &aInfo) == TWRC_SUCCESS) + { + nXRes = FixToDouble(aInfo.XResolution); + nYRes = FixToDouble(aInfo.YResolution); + } + else + nXRes = nYRes = -1; + + switch (m_pDSM(&m_aAppId, &m_aSrcId, DG_IMAGE, DAT_IMAGENATIVEXFER, MSG_GET, &hDIB)) + { + case TWRC_CANCEL: + m_nCurState = TWAINState::Xferring; + break; + + case TWRC_XFERDONE: + { + if (hDIB) + { + m_hMap = nullptr; + const HGLOBAL hGlob = static_cast<HGLOBAL>(hDIB); + const SIZE_T nDIBSize = GlobalSize(hGlob); + const DWORD nMapSize = nDIBSize + 4; // leading 4 bytes for size + if (nMapSize > nDIBSize) // check for wrap + { + if (LPVOID pBmpMem = GlobalLock(hGlob)) + { + if ((nXRes > 0) && (nYRes > 0)) + { + // set resolution of bitmap + BITMAPINFOHEADER* pBIH = static_cast<BITMAPINFOHEADER*>(pBmpMem); + + const auto[m, d] + = getConversionMulDiv(o3tl::Length::in, o3tl::Length::m); + pBIH->biXPelsPerMeter = std::round(o3tl::convert(nXRes, d, m)); + pBIH->biYPelsPerMeter = std::round(o3tl::convert(nYRes, d, m)); + } + + HANDLE hMap = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, + PAGE_READWRITE, 0, nMapSize, nullptr); + if (hMap) + { + LPVOID pMap = MapViewOfFile(hMap, FILE_MAP_WRITE, 0, 0, nMapSize); + if (pMap) + { + memcpy(pMap, &nMapSize, 4); // size of the following DIB + memcpy(static_cast<char*>(pMap) + 4, pBmpMem, nDIBSize); + FlushViewOfFile(pMap, nDIBSize); + UnmapViewOfFile(pMap); + + DuplicateHandle(GetCurrentProcess(), hMap, m_hProc, &m_hMap, 0, + FALSE, DUPLICATE_SAME_ACCESS); + } + + CloseHandle(hMap); + } + + GlobalUnlock(hGlob); + } + } + } + + GlobalFree(static_cast<HGLOBAL>(hDIB)); + + m_nCurState = TWAINState::Xferring; + } + break; + + default: + break; + } + } +} + +void ImpTwain::ImplFallback(WPARAM nEvent) +{ + PostMessageW(m_hTwainWnd, WM_TWAIN_FALLBACK, nEvent, 0); +} + +void ImpTwain::ImplFallbackHdl(WPARAM nEvent) +{ + bool bFallback = true; + + switch (m_nCurState) + { + case TWAINState::Xferring: + case TWAINState::DSreadyToXfer: + { + TW_PENDINGXFERS aXfers; + + if (m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_PENDINGXFERS, MSG_ENDXFER, &aXfers) + == TWRC_SUCCESS) + { + if (aXfers.Count != 0) + m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_PENDINGXFERS, MSG_RESET, &aXfers); + } + + m_nCurState = TWAINState::DSenabled; + } + break; + + case TWAINState::DSenabled: + { + TW_USERINTERFACE aUI = { true, true, m_hTwainWnd }; + + m_pDSM(&m_aAppId, &m_aSrcId, DG_CONTROL, DAT_USERINTERFACE, MSG_DISABLEDS, &aUI); + m_nCurState = TWAINState::DSopened; + } + break; + + case TWAINState::DSopened: + { + m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_IDENTITY, MSG_CLOSEDS, &m_aSrcId); + m_nCurState = TWAINState::DSMopened; + } + break; + + case TWAINState::DSMopened: + { + m_pDSM(&m_aAppId, nullptr, DG_CONTROL, DAT_PARENT, MSG_CLOSEDSM, &m_hTwainWnd); + m_nCurState = TWAINState::DSMloaded; + } + break; + + case TWAINState::DSMloaded: + { + m_pDSM = nullptr; + FreeLibrary(m_hMod); + m_hMod = nullptr; + m_nCurState = TWAINState::DSMunloaded; + } + break; + + case TWAINState::DSMunloaded: + { + if (nEvent > TWAIN_EVENT_NONE) + NotifyParent(nEvent, reinterpret_cast<LPARAM>(m_hMap)); + PostQuitMessage(0); + + bFallback = false; + } + break; + } + + if (bFallback) + ImplFallback(nEvent); +} + +void ImpTwain::ImplRequestHdl(WPARAM nRequest) +{ + switch (nRequest) + { + case TWAIN_REQUEST_QUIT: + Destroy(); + break; + case TWAIN_REQUEST_SELECTSOURCE: + NotifyParent(TWAIN_EVENT_REQUESTRESULT, LPARAM(SelectSource())); + break; + case TWAIN_REQUEST_INITXFER: + NotifyParent(TWAIN_EVENT_REQUESTRESULT, LPARAM(InitXfer())); + break; + } +} +} // namespace + +int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) +{ + int argc = 0; + LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); + if (argc != 2) + return 1; // Wrong argument count + // 1st argument is parent thread handle; must be inherited. + // HANDLE is 32-bit in 32-bit applications, so wcstoul is OK. + HANDLE hParentThread = reinterpret_cast<HANDLE>(wcstoul(argv[1], nullptr, 10)); + LocalFree(argv); + if (!hParentThread) + return 2; // Invalid parent thread handle argument value + + int nRet = 0; + try + { + ImpTwain aImpTwain(hParentThread); // creates main window + + MSG msg; + while (true) + { + DWORD nWaitResult + = MsgWaitForMultipleObjects(1, &hParentThread, FALSE, INFINITE, QS_ALLINPUT); + if (nWaitResult == WAIT_OBJECT_0) + return 5; // Parent process' thread died before we exited + if (nWaitResult == WAIT_FAILED) + return 6; // Some Win32 error + // nWaitResult == WAIT_OBJECT_0 + nCount => an event is in queue + bool bQuit = false; + while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) + { + // process it here + if (msg.message == WM_QUIT) + { + bQuit = true; + nRet = msg.wParam; + } + else + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + if (bQuit) + break; + } + } + catch (const std::exception& e) + { + printf("Exception thrown: %s", e.what()); + nRet = 7; + } + return nRet; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/extensions/source/scanner/twain32shim.hxx b/extensions/source/scanner/twain32shim.hxx new file mode 100644 index 000000000..c9e87ee8b --- /dev/null +++ b/extensions/source/scanner/twain32shim.hxx @@ -0,0 +1,68 @@ +/* -*- 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/. + * + */ + +#pragma once + +#include <prewin.h> +#include <Shlobj.h> +#include <postwin.h> +#include <exception> +#include <string> +#include <sstream> +#include <iomanip> + +// Don't use WM_USER + +// Notifications from shim to parent; wParam = event id +#define WM_TWAIN_EVENT (WM_USER + 1) + +// lParam is HWND +#define TWAIN_EVENT_NOTIFYHWND 0 + +// lParam is result (bool indicating success) +#define TWAIN_EVENT_REQUESTRESULT 1 + +// lParam is ignored +#define TWAIN_EVENT_NONE 10 +#define TWAIN_EVENT_QUIT 11 +#define TWAIN_EVENT_SCANNING 12 + +// lParam is HANDLE to shared file mapping valid in context of parent process +#define TWAIN_EVENT_XFER 13 + +// Requests from parent to shim; wParam = request id +#define WM_TWAIN_REQUEST (WM_USER + 2) + +#define TWAIN_REQUEST_QUIT 0 // Destroy() +#define TWAIN_REQUEST_SELECTSOURCE 1 +#define TWAIN_REQUEST_INITXFER 2 + +// messages starting from this are not to be used for interprocess communications +#define WM_SHIM_INTERNAL (WM_USER + 200) + +template <typename IntType> std::string Num2Hex(IntType n) +{ + std::stringstream sMsg; + sMsg << "0x" << std::uppercase << std::setfill('0') << std::setw(sizeof(n) * 2) << std::hex + << n; + return sMsg.str(); +} + +void ThrowWin32Error(const char* sFunc, DWORD nWin32Error) +{ + std::stringstream sMsg; + sMsg << sFunc << " failed with Win32 error code " << Num2Hex(nWin32Error) << "!"; + + throw std::exception(sMsg.str().c_str()); +} + +void ThrowLastError(const char* sFunc) { ThrowWin32Error(sFunc, GetLastError()); } + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |