summaryrefslogtreecommitdiffstats
path: root/extensions/source/scanner
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/source/scanner')
-rw-r--r--extensions/source/scanner/grid.cxx700
-rw-r--r--extensions/source/scanner/grid.hxx51
-rw-r--r--extensions/source/scanner/sane.cxx996
-rw-r--r--extensions/source/scanner/sane.hxx190
-rw-r--r--extensions/source/scanner/sanedlg.cxx1467
-rw-r--r--extensions/source/scanner/sanedlg.hxx111
-rw-r--r--extensions/source/scanner/scanner.cxx89
-rw-r--r--extensions/source/scanner/scanner.hxx85
-rw-r--r--extensions/source/scanner/scanunx.cxx341
-rw-r--r--extensions/source/scanner/scanwin.cxx646
-rw-r--r--extensions/source/scanner/scn.component26
-rw-r--r--extensions/source/scanner/twain32shim.cxx604
-rw-r--r--extensions/source/scanner/twain32shim.hxx68
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: */