/*************************************************************************
 *
 *  $RCSfile: scanwin.cxx,v $
 *
 *  $Revision: 1.5 $
 *
 *  last change: $Author: rt $ $Date: 2003-04-08 15:40:31 $
 *
 *  The Contents of this file are made available subject to the terms of
 *  either of the following licenses
 *
 *         - GNU Lesser General Public License Version 2.1
 *         - Sun Industry Standards Source License Version 1.1
 *
 *  Sun Microsystems Inc., October, 2000
 *
 *  GNU Lesser General Public License Version 2.1
 *  =============================================
 *  Copyright 2000 by Sun Microsystems, Inc.
 *  901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA  02111-1307  USA
 *
 *
 *  Sun Industry Standards Source License Version 1.1
 *  =================================================
 *  The contents of this file are subject to the Sun Industry Standards
 *  Source License Version 1.1 (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.openoffice.org/license.html.
 *
 *  Software provided under this License is provided on an "AS IS" basis,
 *  WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
 *  WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
 *  MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
 *  See the License for the specific provisions governing your rights and
 *  obligations concerning the Software.
 *
 *  The Initial Developer of the Original Code is: Sun Microsystems, Inc.
 *
 *  Copyright: 2000 by Sun Microsystems, Inc.
 *
 *  All Rights Reserved.
 *
 *  Contributor(s): _______________________________________
 *
 *
 ************************************************************************/

#ifndef _COM_SUN_STAR_UNO_ANY_HXX_
#include <com/sun/star/uno/Any.hxx>
#endif

#ifndef _COM_SUN_STAR_UNO_REFERENCE_HXX_
#include <com/sun/star/uno/Reference.hxx>
#endif

#ifndef _COM_SUN_STAR_UTIL_XCLOSEABLE_HPP_
#include <com/sun/star/util/XCloseable.hpp>
#endif

#ifndef _COM_SUN_STAR_UTIL_XCLOSEBROADCASTER_HPP_
#include <com/sun/star/util/XCloseBroadcaster.hpp>
#endif

#ifndef _COM_SUN_STAR_UTIL_XCLOSELISTENER_HPP_
#include <com/sun/star/util/XCloseListener.hpp>
#endif

#ifndef _COM_SUN_STAR_FRAME_XFRAME_HPP_
#include <com/sun/star/frame/XFrame.hpp>
#endif

#ifndef _COM_SUN_STAR_FRAME_XDESKTOP_HPP_
#include <com/sun/star/frame/XDesktop.hpp>
#endif

#ifndef _COM_SUN_STAR_BEANS_XPROPERTYSET_HPP_
#include <com/sun/star/beans/XPropertySet.hpp>
#endif

#ifndef _CPPUHELPER_IMPLBASE1_HXX_
#include <cppuhelper/implbase1.hxx>
#endif

#ifndef _COMPHELPER_PROCESSFACTORY_HXX_
#include <comphelper/processfactory.hxx>
#endif

#include <math.h>
#include <tools/svwin.h>
#include <tools/stream.hxx>
#include <vos/mutex.hxx>
#include <vos/module.hxx>
#include <vcl/svapp.hxx>
#include <vcl/wrkwin.hxx>
#include <vcl/sysdata.hxx>
#include <vcl/salbtype.hxx>
#include "scanner.hxx"
#include "twain/twain.h"

using namespace ::com::sun::star;

// -----------
// - Defines -
// -----------

#define TWAIN_SELECT            0x00000001UL
#define TWAIN_ACQUIRE           0x00000002UL
#define TWAIN_TERMINATE         0xFFFFFFFFUL

#define TWAIN_EVENT_NONE        0x00000000UL
#define TWAIN_EVENT_QUIT        0x00000001UL
#define TWAIN_EVENT_SCANNING    0x00000002UL
#define TWAIN_EVENT_XFER        0x00000004UL

#define PFUNC                   (*pDSM)
#define PTWAINMSG               MSG*
#define FIXTODOUBLE( nFix )     ((double)nFix.Whole+(double)nFix.Frac/65536.)
#define FIXTOLONG( nFix )       ((long)floor(FIXTODOUBLE(nFix)+0.5))

#if defined WIN
#define TWAIN_LIBNAME           "TWAIN.DLL"
#define TWAIN_FUNCNAME          "DSM_Entry"
#elif defined WNT
#define TWAIN_LIBNAME           "TWAIN_32.DLL"
#define TWAIN_FUNCNAME          "DSM_Entry"
#endif

// --------------
// - TwainState -
// --------------

enum TwainState
{
    TWAIN_STATE_NONE = 0,
    TWAIN_STATE_SCANNING = 1,
    TWAIN_STATE_DONE = 2,
    TWAIN_STATE_CANCELED = 3
};

// ------------
// - ImpTwain -
// ------------

typedef ::cppu::WeakImplHelper1< ::com::sun::star::util::XCloseListener > CloseListenerBase;

class ImpTwain : public CloseListenerBase
{
    REF( com::sun::star::scanner::XScannerManager ) mxMgr;
    ScannerManager*                                 mpMgr;
    TW_IDENTITY                                     aAppIdent;
    TW_IDENTITY                                     aSrcIdent;
    Link                                            aNotifyLink;
    DSMENTRYPROC                                    pDSM;
    NAMESPACE_VOS( OModule )*                       pMod;
    ULONG                                           nCurState;
    bool                                            mbCloseFrameOnExit;
    uno::Reference< uno::XInterface >               mxSelfRef;

    void                                            ImplCreate();
    void                                            ImplOpenSourceManager();
    void                                            ImplOpenSource();
    BOOL                                            ImplEnableSource();
    void                                            ImplXfer();
    void                                            ImplFallback( ULONG nEvent );
    void                                            ImplSendCloseEvent();
    void                                            ImplDeregisterCloseListener();
    void                                            ImplRegisterCloseListener();
    uno::Reference< frame::XFrame >                 ImplGetActiveFrame();
    uno::Reference< util::XCloseBroadcaster >       ImplGetActiveFrameCloseBroadcaster();

                                                    DECL_LINK( ImplFallbackHdl, void* );
                                                    DECL_LINK( ImplDestroyHdl, void* );

public:

    HWND                        hTwainWnd;
    HHOOK                       hTwainHook;

    BOOL                        ImplHandleMsg( void* pMsg );

public:

                                ImpTwain( ScannerManager* pMgr, const Link& rNotifyLink );
                                ~ImpTwain();

    void                        Destroy();

    BOOL                        SelectSource();
    BOOL                        InitXfer();

    // from util::XCloseListener
    virtual void SAL_CALL queryClosing( const ::com::sun::star::lang::EventObject& Source, sal_Bool GetsOwnership ) throw (::com::sun::star::util::CloseVetoException, ::com::sun::star::uno::RuntimeException);
    virtual void SAL_CALL notifyClosing( const ::com::sun::star::lang::EventObject& Source ) throw (::com::sun::star::uno::RuntimeException);

    // from lang::XEventListener
    virtual void SAL_CALL disposing( const ::com::sun::star::lang::EventObject& Source ) throw (::com::sun::star::uno::RuntimeException);
};

// ---------
// - Procs -
// ---------

static ImpTwain* pImpTwainInstance = NULL;

// -------------------------------------------------------------------------

LRESULT CALLBACK TwainWndProc( HWND hWnd,UINT nMsg, WPARAM nPar1, LPARAM nPar2 )
{
    return DefWindowProc( hWnd, nMsg, nPar1, nPar2 );
}

// -------------------------------------------------------------------------

LRESULT CALLBACK TwainMsgProc( int nCode, WPARAM wParam, LPARAM lParam )
{
    MSG* pMsg = (MSG*) lParam;

    if( ( nCode < 0 ) ||
        ( pImpTwainInstance->hTwainWnd != pMsg->hwnd ) ||
        !pImpTwainInstance->ImplHandleMsg( (void*) lParam ) )
    {
        return CallNextHookEx( pImpTwainInstance->hTwainHook, nCode, wParam, lParam );
    }
    else
    {
        pMsg->message = WM_USER;
        pMsg->lParam = 0;

        return 0;
    }
}

// -----------------------------------------------------------------------------

ImpTwain::ImpTwain( ScannerManager* pMgr, const Link& rNotifyLink ) :
            // #107835# hold reference to ScannerManager, to prevent premature death
            mxMgr       ( REF(com::sun::star::scanner::XScannerManager)(
                              static_cast< OWeakObject* >(pMgr),
                              ::com::sun::star::uno::UNO_QUERY) ),
            mpMgr               ( pMgr ),
            aNotifyLink         ( rNotifyLink ),
            pDSM                ( NULL ),
            pMod                ( NULL ),
            hTwainWnd           ( 0 ),
            hTwainHook          ( 0 ),
            nCurState           ( 1 ),
            mbCloseFrameOnExit  ( false )
{
    // setup TWAIN window
    pImpTwainInstance = this;

    aAppIdent.Id = 0;
    aAppIdent.Version.MajorNum = 1;
    aAppIdent.Version.MinorNum = 0;
    aAppIdent.Version.Language = TWLG_USA;
    aAppIdent.Version.Country = TWCY_USA;
    aAppIdent.ProtocolMajor = TWON_PROTOCOLMAJOR;
    aAppIdent.ProtocolMinor = TWON_PROTOCOLMINOR;
    aAppIdent.SupportedGroups = DG_IMAGE | DG_CONTROL;
    strncpy( aAppIdent.Version.Info, "6.1", 32 );
    aAppIdent.Version.Info[32] = aAppIdent.Version.Info[33] = 0;
    strncpy( aAppIdent.Manufacturer, "Sun Microsystems", 32 );
    aAppIdent.Manufacturer[32] = aAppIdent.Manufacturer[33] = 0;
    strncpy( aAppIdent.ProductFamily,"Office", 32 );
    aAppIdent.ProductFamily[32] = aAppIdent.ProductFamily[33] = 0;
    strncpy( aAppIdent.ProductName, "Office", 32 );
    aAppIdent.ProductName[32] = aAppIdent.ProductName[33] = 0;

    HWND        hParentWnd = HWND_DESKTOP;

    WNDCLASS    aWc = { 0, &TwainWndProc, 0, sizeof( WNDCLASS ), GetModuleHandle( NULL ),
                        NULL, NULL, NULL, NULL, "TwainClass" };

    RegisterClass( &aWc );
    hTwainWnd = CreateWindowEx( WS_EX_TOPMOST, aWc.lpszClassName, "TWAIN", 0, 0, 0, 0, 0, hParentWnd, NULL, aWc.hInstance, 0 );
    hTwainHook = SetWindowsHookEx( WH_GETMESSAGE, &TwainMsgProc, NULL, GetCurrentThreadId() );

    // #107835# block destruction until ImplDestroyHdl is called
    mxSelfRef = static_cast< ::cppu::OWeakObject* >(this);
}

// -----------------------------------------------------------------------------

ImpTwain::~ImpTwain()
{
    // are we responsible for application shutdown?
    if( mbCloseFrameOnExit )
        ImplSendCloseEvent();
}

// -----------------------------------------------------------------------------

void ImpTwain::Destroy()
{
    ImplFallback( TWAIN_EVENT_NONE );
    Application::PostUserEvent( LINK( this, ImpTwain, ImplDestroyHdl ), NULL );
}

// -----------------------------------------------------------------------------

BOOL ImpTwain::SelectSource()
{
    TW_UINT16 nRet = TWRC_FAILURE;

    ImplOpenSourceManager();

    if( 3 == nCurState )
    {
        TW_IDENTITY aIdent;

        aIdent.Id = 0, aIdent.ProductName[ 0 ] = '\0';
        aNotifyLink.Call( (void*) TWAIN_EVENT_SCANNING );
        nRet = PFUNC( &aAppIdent, NULL, DG_CONTROL, DAT_IDENTITY, MSG_USERSELECT, &aIdent );
    }

    ImplFallback( TWAIN_EVENT_QUIT );

    return( TWRC_SUCCESS == nRet );
}

// -----------------------------------------------------------------------------

BOOL ImpTwain::InitXfer()
{
    BOOL bRet = FALSE;

    ImplOpenSourceManager();

    if( 3 == nCurState )
    {
        ImplOpenSource();

        if( 4 == nCurState )
            bRet = ImplEnableSource();
    }

    if( !bRet )
        ImplFallback( TWAIN_EVENT_QUIT );

    return bRet;
}

// -----------------------------------------------------------------------------

void ImpTwain::ImplOpenSourceManager()
{
    if( 1 == nCurState )
    {
        pMod = new ::vos::OModule( ::rtl::OUString() );

        if( pMod->load( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( TWAIN_LIBNAME ) ) ) )
        {
            nCurState = 2;

            if( ( ( pDSM = (DSMENTRYPROC) pMod->getSymbol( String( RTL_CONSTASCII_USTRINGPARAM( TWAIN_FUNCNAME ) ) ) ) != NULL ) &&
                ( PFUNC( &aAppIdent, NULL, DG_CONTROL, DAT_PARENT, MSG_OPENDSM, &hTwainWnd ) == TWRC_SUCCESS ) )
            {
                nCurState = 3;
            }
        }
        else
        {
            delete pMod;
            pMod = NULL;
        }
    }
}

// -----------------------------------------------------------------------------

void ImpTwain::ImplOpenSource()
{
    if( 3 == nCurState )
    {
        if( ( PFUNC( &aAppIdent, NULL, DG_CONTROL, DAT_IDENTITY, MSG_GETDEFAULT, &aSrcIdent ) == TWRC_SUCCESS ) &&
            ( PFUNC( &aAppIdent, NULL, DG_CONTROL, DAT_IDENTITY, MSG_OPENDS, &aSrcIdent ) == TWRC_SUCCESS ) )
        {
            TW_CAPABILITY   aCap = { CAP_XFERCOUNT, TWON_ONEVALUE, GlobalAlloc( GHND, sizeof( TW_ONEVALUE ) ) };
            TW_ONEVALUE*    pVal = (TW_ONEVALUE*) GlobalLock( aCap.hContainer );

            pVal->ItemType = TWTY_INT16, pVal->Item = 1;
            GlobalUnlock( aCap.hContainer );
            PFUNC( &aAppIdent, &aSrcIdent, DG_CONTROL, DAT_CAPABILITY, MSG_SET, &aCap );
            GlobalFree( aCap.hContainer );
            nCurState = 4;
        }
    }
}

// -----------------------------------------------------------------------------

BOOL ImpTwain::ImplEnableSource()
{
    BOOL bRet = FALSE;

    if( 4 == nCurState )
    {
        TW_USERINTERFACE aUI = { TRUE, TRUE, hTwainWnd };

        aNotifyLink.Call( (void*) TWAIN_EVENT_SCANNING );
        nCurState = 5;

        // #107835# register as vetoable close listener, to prevent
        // application to die under us
        ImplRegisterCloseListener();

        if( PFUNC( &aAppIdent, &aSrcIdent, DG_CONTROL, DAT_USERINTERFACE, MSG_ENABLEDS, &aUI ) == TWRC_SUCCESS )
        {
            bRet = TRUE;
        }
        else
        {
            nCurState = 4;

            // #107835# deregister as vetoable close listener, dialog failed
            ImplDeregisterCloseListener();
        }
    }

    return bRet;
}

// -----------------------------------------------------------------------------

BOOL ImpTwain::ImplHandleMsg( void* pMsg )
{
    TW_UINT16   nRet;
    PTWAINMSG   pMess = (PTWAINMSG) pMsg;
    TW_EVENT    aEvt = { pMess, MSG_NULL };

    nRet = PFUNC( &aAppIdent, &aSrcIdent, DG_CONTROL, DAT_EVENT, MSG_PROCESSEVENT, &aEvt );

    if( aEvt.TWMessage != MSG_NULL )
    {
        switch( aEvt.TWMessage )
        {
            case MSG_XFERREADY:
            {
                ULONG nEvent = TWAIN_EVENT_QUIT;

                if( 5 == nCurState )
                {
                    nCurState = 6;
                    ImplXfer();

                    if( mxMgr.is() && mpMgr && mpMgr->GetData() )
                        nEvent = TWAIN_EVENT_XFER;
                }

                ImplFallback( nEvent );
            }
            break;

            case MSG_CLOSEDSREQ:
                ImplFallback( TWAIN_EVENT_QUIT );
            break;

            default:
            break;
        }
    }
    else
        nRet = TWRC_NOTDSEVENT;

    return( TWRC_DSEVENT == nRet );
}

// -----------------------------------------------------------------------------

void ImpTwain::ImplXfer()
{
    if( nCurState == 6 )
    {
        TW_IMAGEINFO    aInfo;
        TW_UINT32       hDIB = 0;
        long            nWidth, nHeight, nXRes, nYRes;

        if( PFUNC( &aAppIdent, &aSrcIdent, DG_IMAGE, DAT_IMAGEINFO, MSG_GET, &aInfo ) == TWRC_SUCCESS )
        {
            nWidth = aInfo.ImageWidth;
            nHeight = aInfo.ImageLength;
            nXRes = FIXTOLONG( aInfo.XResolution );
            nYRes = FIXTOLONG( aInfo.YResolution );
        }
        else
            nWidth = nHeight = nXRes = nYRes = -1L;

        switch( PFUNC( &aAppIdent, &aSrcIdent, DG_IMAGE, DAT_IMAGENATIVEXFER, MSG_GET, &hDIB ) )
        {
            case( TWRC_CANCEL ):
                nCurState = 7;
            break;

            case( TWRC_XFERDONE ):
            {
                if( mxMgr.is() && mpMgr && hDIB )
                {
                    if( ( nXRes != -1 ) && ( nYRes != - 1 ) && ( nWidth != - 1 ) && ( nHeight != - 1 ) )
                    {
                        // set resolution of bitmap
                        BITMAPINFOHEADER*   pBIH = (BITMAPINFOHEADER*) GlobalLock( (HGLOBAL) hDIB );
                        static const double fFactor = 100.0 / 2.54;

                        pBIH->biXPelsPerMeter = FRound( fFactor * nXRes );
                        pBIH->biYPelsPerMeter = FRound( fFactor * nYRes );

                        GlobalUnlock( (HGLOBAL) hDIB );
                    }

                    mpMgr->SetData( (void*)(long) hDIB );
                }
                else
                    GlobalFree( (HGLOBAL) hDIB );

                nCurState = 7;
            }
            break;

            default:
            break;
        }
    }
}

// -----------------------------------------------------------------------------

void ImpTwain::ImplFallback( ULONG nEvent )
{
    Application::PostUserEvent( LINK( this, ImpTwain, ImplFallbackHdl ), (void*) nEvent );
}

// -----------------------------------------------------------------------------

IMPL_LINK( ImpTwain, ImplFallbackHdl, void*, pData )
{
    const ULONG nEvent = (ULONG) pData;
    BOOL        bFallback = TRUE;

    switch( nCurState )
    {
        case( 7 ):
        case( 6 ):
        {
            TW_PENDINGXFERS aXfers;

            if( PFUNC( &aAppIdent, &aSrcIdent, DG_CONTROL, DAT_PENDINGXFERS, MSG_ENDXFER, &aXfers ) == TWRC_SUCCESS )
            {
                if( aXfers.Count != 0 )
                    PFUNC( &aAppIdent, &aSrcIdent, DG_CONTROL, DAT_PENDINGXFERS, MSG_RESET, &aXfers );
            }

            nCurState = 5;
        }
        break;

        case( 5 ):
        {
            TW_USERINTERFACE aUI = { TRUE, TRUE, hTwainWnd };

            PFUNC( &aAppIdent, &aSrcIdent, DG_CONTROL, DAT_USERINTERFACE, MSG_DISABLEDS, &aUI );
            nCurState = 4;

            // #107835# deregister as vetoable close listener
            ImplDeregisterCloseListener();
        }
        break;

        case( 4 ):
        {
            PFUNC( &aAppIdent, NULL, DG_CONTROL, DAT_IDENTITY, MSG_CLOSEDS, &aSrcIdent );
            nCurState = 3;
        }
        break;

        case( 3 ):
        {
            PFUNC( &aAppIdent, NULL, DG_CONTROL, DAT_PARENT, MSG_CLOSEDSM, &hTwainWnd );
            nCurState = 2;
        }
        break;

        case( 2 ):
        {
            delete pMod;
            pMod = NULL;
            nCurState = 1;
        }
        break;

        default:
        {
            if( nEvent != TWAIN_EVENT_NONE )
                aNotifyLink.Call( (void*) nEvent );

            bFallback = FALSE;
        }
        break;
    }

    if( bFallback )
        ImplFallback( nEvent );

    return 0L;
}

// -----------------------------------------------------------------------------

IMPL_LINK( ImpTwain, ImplDestroyHdl, void*, p )
{
    if( hTwainWnd )
        DestroyWindow( hTwainWnd );

    if( hTwainHook )
        UnhookWindowsHookEx( hTwainHook );

    // #107835# permit destruction of ourselves (normally, refcount
    // should drop to zero exactly here)
    mxSelfRef = NULL;
    pImpTwainInstance = NULL;

    return 0L;
}

// -----------------------------------------------------------------------------

uno::Reference< frame::XFrame > ImpTwain::ImplGetActiveFrame()
{
    try
    {
        uno::Reference< lang::XMultiServiceFactory >  xMgr( ::comphelper::getProcessServiceFactory() );

        if( xMgr.is() )
        {
            // query desktop instance
            uno::Reference< frame::XDesktop > xDesktop( xMgr->createInstance(
                                                            OUString::createFromAscii( "com.sun.star.frame.Desktop" ) ), uno::UNO_QUERY );

            if( xDesktop.is() )
            {
                // query property set from desktop, which contains the currently active frame
                uno::Reference< beans::XPropertySet > xDesktopProps( xDesktop, uno::UNO_QUERY );

                if( xDesktopProps.is() )
                {
                    uno::Any aActiveFrame;

                    try
                    {
                        aActiveFrame = xDesktopProps->getPropertyValue(
                            OUString::createFromAscii( "ActiveFrame" ) );
                    }
                    catch( const beans::UnknownPropertyException& )
                    {
                        // property unknown.
                        DBG_ERROR("ImpTwain::ImplGetActiveFrame: ActiveFrame property unknown, cannot determine active frame!");
                        return uno::Reference< frame::XFrame >();
                    }

                    uno::Reference< frame::XFrame > xActiveFrame;

                    if( (aActiveFrame >>= xActiveFrame) &&
                        xActiveFrame.is() )
                    {
                        return xActiveFrame;
                    }
                }
            }
        }
    }
    catch( const uno::Exception& )
    {
    }

    DBG_ERROR("ImpTwain::ImplGetActiveFrame: Could not determine active frame!");
    return uno::Reference< frame::XFrame >();
}

// -----------------------------------------------------------------------------

uno::Reference< util::XCloseBroadcaster > ImpTwain::ImplGetActiveFrameCloseBroadcaster()
{
    try
    {
        return uno::Reference< util::XCloseBroadcaster >( ImplGetActiveFrame(), uno::UNO_QUERY );
    }
    catch( const uno::Exception& )
    {
    }

    DBG_ERROR("ImpTwain::ImplGetActiveFrameCloseBroadcaster: Could determine close broadcaster on active frame!");
    return uno::Reference< util::XCloseBroadcaster >();
}

// -----------------------------------------------------------------------------

void ImpTwain::ImplRegisterCloseListener()
{
    try
    {
        uno::Reference< util::XCloseBroadcaster > xCloseBroadcaster(
            ImplGetActiveFrameCloseBroadcaster() );

        if( xCloseBroadcaster.is() )
        {
            xCloseBroadcaster->addCloseListener(this);
            return; // successfully registered as a close listener
        }
        else
        {
            // interface unknown. don't register, then
            DBG_ERROR("ImpTwain::ImplRegisterCloseListener: XFrame has no XCloseBroadcaster!");
            return;
        }
    }
    catch( const uno::Exception& )
    {
    }

    DBG_ERROR("ImpTwain::ImplRegisterCloseListener: Could not register as close listener!");
}

// -----------------------------------------------------------------------------

void ImpTwain::ImplDeregisterCloseListener()
{
    try
    {
        uno::Reference< util::XCloseBroadcaster > xCloseBroadcaster(
            ImplGetActiveFrameCloseBroadcaster() );

        if( xCloseBroadcaster.is() )
        {
            xCloseBroadcaster->removeCloseListener(this);
            return; // successfully deregistered as a close listener
        }
        else
        {
            // interface unknown. don't deregister, then
            DBG_ERROR("ImpTwain::ImplDeregisterCloseListener: XFrame has no XCloseBroadcaster!");
            return;
        }
    }
    catch( const uno::Exception& )
    {
    }

    DBG_ERROR("ImpTwain::ImplDeregisterCloseListener: Could not deregister as close listener!");
}

// -----------------------------------------------------------------------------

void SAL_CALL ImpTwain::queryClosing( const lang::EventObject& Source, sal_Bool GetsOwnership ) throw (util::CloseVetoException, uno::RuntimeException)
{
    // shall we re-send the close query later on?
    mbCloseFrameOnExit = GetsOwnership;

    // the sole purpose of this listener is to forbid closing of the listened-at frame
    throw util::CloseVetoException();
}

// -----------------------------------------------------------------------------

void SAL_CALL ImpTwain::notifyClosing( const lang::EventObject& Source ) throw (uno::RuntimeException)
{
    // should not happen
    DBG_ERROR("ImpTwain::notifyClosing called, but we vetoed the closing before!");
}

// -----------------------------------------------------------------------------

void SAL_CALL ImpTwain::disposing( const lang::EventObject& Source ) throw (uno::RuntimeException)
{
    // we're not holding any references to the frame, thus noop
}

// -----------------------------------------------------------------------------

void ImpTwain::ImplSendCloseEvent()
{
    try
    {
        uno::Reference< util::XCloseable > xCloseable( ImplGetActiveFrame(), uno::UNO_QUERY );

        if( xCloseable.is() )
            xCloseable->close( true );
    }
    catch( const uno::Exception& )
    {
    }

    DBG_ERROR("ImpTwain::ImplSendCloseEvent: Could not send required close broadcast!");
}


// ---------
// - Twain -
// ---------

class Twain
{
    REF( com::sun::star::scanner::XScannerManager ) mxMgr;
    ScannerManager*                                 mpMgr;
    REF( com::sun::star::lang::XEventListener )     mxListener;
    ImpTwain*                                       mpImpTwain;
    TwainState                                      meState;

                                                    DECL_LINK( ImpNotifyHdl, ImpTwain* );

public:

                                                Twain();
                                                ~Twain();

    BOOL                                        SelectSource();
    BOOL                                        PerformTransfer( ScannerManager* pMgr,
                                                                 const REF( com::sun::star::lang::XEventListener )& rxListener );

    TwainState                                  GetState() const { return meState; }
};

// ------------------------------------------------------------------------

Twain::Twain() :
        mxMgr       (),
        mpMgr       ( NULL ),
        mpImpTwain  ( NULL ),
        meState     ( TWAIN_STATE_NONE )
{
}

// ------------------------------------------------------------------------

Twain::~Twain()
{
    if( mpImpTwain )
        mpImpTwain->Destroy();
}

// ------------------------------------------------------------------------

BOOL Twain::SelectSource()
{
    BOOL bRet;

    if( !mpImpTwain && mxMgr.is() )
    {
        meState = TWAIN_STATE_NONE;
        mpImpTwain = new ImpTwain( mpMgr, LINK( this, Twain, ImpNotifyHdl ) );
        bRet = mpImpTwain->SelectSource();
    }
    else
        bRet = FALSE;

    return bRet;
}

// ------------------------------------------------------------------------

BOOL Twain::PerformTransfer( ScannerManager* pMgr, const REF( com::sun::star::lang::XEventListener )& rxListener )
{
    BOOL bRet;

    if( pMgr && !mpImpTwain )
    {
        // #107835# hold reference to ScannerManager, to prevent premature death
        mxMgr = REF(com::sun::star::scanner::XScannerManager)(static_cast< OWeakObject* >(pMgr),
                                                              ::com::sun::star::uno::UNO_QUERY),
        mpMgr = pMgr;
        mxListener = rxListener;
        meState = TWAIN_STATE_NONE;
        mpImpTwain = new ImpTwain( mpMgr, LINK( this, Twain, ImpNotifyHdl ) );
        bRet = mpImpTwain->InitXfer();
    }
    else
        bRet = FALSE;

    return bRet;
}

// ------------------------------------------------------------------------

IMPL_LINK( Twain, ImpNotifyHdl, ImpTwain*, nEvent )
{
    switch( (ULONG)(void*) nEvent )
    {
        case( TWAIN_EVENT_SCANNING ):
            meState = TWAIN_STATE_SCANNING;
        break;

        case( TWAIN_EVENT_QUIT ):
        {
            if( meState != TWAIN_STATE_DONE )
                meState = TWAIN_STATE_CANCELED;

            if( mpImpTwain )
            {
                mpImpTwain->Destroy();
                mpImpTwain = NULL;
            }

            if( mxMgr.is() && mpMgr && mxListener.is() )
                mxListener->disposing( com::sun::star::lang::EventObject( REF( XInterface )( static_cast< OWeakObject* >( mpMgr ) ) ) );

            mxListener = REF( com::sun::star::lang::XEventListener )();
        }
        break;

        case( TWAIN_EVENT_XFER ):
        {
            if( mxMgr.is() && mpMgr && mpImpTwain )
            {
                meState = ( ( mpMgr && mpMgr->GetData() ) ? TWAIN_STATE_DONE : TWAIN_STATE_CANCELED );

                mpImpTwain->Destroy();
                mpImpTwain = NULL;

                if( mpMgr && mxListener.is() )
                    mxListener->disposing( com::sun::star::lang::EventObject( REF( XInterface )( static_cast< OWeakObject* >( mpMgr ) ) ) );
            }

            mxListener = REF( com::sun::star::lang::XEventListener )();
        }
        break;

        default:
        break;
    }

    return 0L;
}

// -----------
// - statics -
// -----------

static Twain aTwain;

// ------------------
// - ScannerManager -
// ------------------

void ScannerManager::DestroyData()
{
    if( mpData )
    {
        GlobalFree( (HGLOBAL)(long) mpData );
        mpData = NULL;
    }
}

// -----------------------------------------------------------------------------

AWT::Size ScannerManager::getSize()
{
    AWT::Size   aRet;
    HGLOBAL     hDIB = (HGLOBAL)(long) mpData;

    if( hDIB )
    {
        BITMAPINFOHEADER* pBIH = (BITMAPINFOHEADER*) GlobalLock( hDIB );

        if( pBIH )
        {
            aRet.Width = pBIH->biWidth;
            aRet.Height = pBIH->biHeight;
        }
        else
            aRet.Width = aRet.Height = 0;

        GlobalUnlock( hDIB );
    }
    else
        aRet.Width = aRet.Height = 0;

    return aRet;
}

// -----------------------------------------------------------------------------

SEQ( sal_Int8 ) ScannerManager::getDIB()
{
    SEQ( sal_Int8 ) aRet;

    if( mpData )
    {
        HGLOBAL             hDIB = (HGLOBAL)(long) mpData;
        const sal_uInt32    nDIBSize = GlobalSize( hDIB );
        sal_uInt32          nSize = sizeof( BITMAPFILEHEADER) + nDIBSize;
        BITMAPINFOHEADER*   pBIH = (BITMAPINFOHEADER*) GlobalLock( hDIB );

        if( pBIH )
        {
            sal_uInt32  nColEntries;

            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;

                default:
                    nColEntries = 0;
                break;
            }

            aRet = SEQ( sal_Int8 )( sizeof( BITMAPFILEHEADER ) + nDIBSize );

            sal_Int8*       pBuf = aRet.getArray();
            SvMemoryStream* pMemStm = new SvMemoryStream( (char*) pBuf, sizeof( BITMAPFILEHEADER ), STREAM_WRITE );

            *pMemStm << 'B' << 'M' << (sal_uInt32) 0 << (sal_uInt32) 0;
            *pMemStm << (sal_uInt32) ( sizeof( BITMAPFILEHEADER ) + pBIH->biSize + ( nColEntries * sizeof( RGBQUAD ) ) );

            delete pMemStm;
            HMEMCPY( pBuf + sizeof( BITMAPFILEHEADER ), pBIH, nDIBSize );
        }

        GlobalUnlock( hDIB );
        DestroyData();
    }

    return aRet;
}

// -----------------------------------------------------------------------------

SEQ( ScannerContext ) SAL_CALL ScannerManager::getAvailableScanners()
{
    vos::OGuard             aGuard( maProtector );
    SEQ( ScannerContext )   aRet( 1 );

    aRet.getArray()[0].ScannerName = ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "TWAIN" ) );
    aRet.getArray()[0].InternalData = 0;

    return aRet;
}

// -----------------------------------------------------------------------------

BOOL SAL_CALL ScannerManager::configureScanner( ScannerContext& rContext ) throw( ScannerException )
{
    vos::OGuard             aGuard( maProtector );
    REF( XScannerManager )  xThis( this );

    if( rContext.InternalData != 0 || rContext.ScannerName != ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "TWAIN" ) ) )
        throw ScannerException( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Scanner does not exist" ) ), xThis, ScanError_InvalidContext );

    DestroyData();

    return aTwain.SelectSource();
}

// -----------------------------------------------------------------------------

void SAL_CALL ScannerManager::startScan( const ScannerContext& rContext, const REF( com::sun::star::lang::XEventListener )& rxListener ) throw( ScannerException )
{
    vos::OGuard             aGuard( maProtector );
    REF( XScannerManager )  xThis( this );

    if( rContext.InternalData != 0 || rContext.ScannerName != ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "TWAIN" ) ) )
        throw ScannerException( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Scanner does not exist" ) ), xThis, ScanError_InvalidContext );

    DestroyData();

    aTwain.PerformTransfer( this, rxListener );
}

// -----------------------------------------------------------------------------

ScanError SAL_CALL ScannerManager::getError( const ScannerContext& rContext ) throw( ScannerException )
{
    vos::OGuard             aGuard( maProtector );
    ScanError               eError;
    REF( XScannerManager )  xThis( this );

    if( rContext.InternalData != 0 || rContext.ScannerName != ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "TWAIN" ) ) )
        throw ScannerException( ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "Scanner does not exist" ) ), xThis, ScanError_InvalidContext );

    if( aTwain.GetState() == TWAIN_STATE_CANCELED )
        eError = ScanError_ScanCanceled;
    else
        eError = ScanError_ScanErrorNone;

    return eError;
}

// -----------------------------------------------------------------------------

REF( AWT::XBitmap ) SAL_CALL ScannerManager::getBitmap( const ScannerContext& rContext ) throw( ScannerException )
{
    vos::OGuard aGuard( maProtector );
    return REF( AWT::XBitmap )( this );
}
