/****************************************************************************
** $Id: qpaintdevice_pm.cpp 8 2005-11-16 19:36:46Z dmik $
**
** Implementation of QPaintDevice class for OS/2
**
** Copyright (C) 1992-2000 Trolltech AS.  All rights reserved.
** Copyright (C) 2004 Norman ASA.  Initial OS/2 Port.
** Copyright (C) 2005 netlabs.org.  Further OS/2 Development.
**
** This file is part of the kernel module of the Qt GUI Toolkit.
**
** This file may be distributed under the terms of the Q Public License
** as defined by Trolltech AS of Norway and appearing in the file
** LICENSE.QPL included in the packaging of this file.
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
** licenses may use this file in accordance with the Qt Commercial License
** Agreement provided with the Software.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
**   information about Qt Commercial License Agreements.
** See http://www.trolltech.com/qpl/ for QPL licensing information.
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
**********************************************************************/

#include "qpaintdevice.h"
#include "qpaintdevicemetrics.h"
#include "qpainter.h"
#include "qwidget.h"
#include "qbitmap.h"
#include "qapplication.h"
#include "qapplication_p.h"
#include "qt_os2.h"


QPaintDevice::QPaintDevice( uint devflags )
{
    if ( !qApp ) {				// global constructor
#if defined(QT_CHECK_STATE)
	qFatal( "QPaintDevice: Must construct a QApplication before a "
		"QPaintDevice" );
#endif
	return;
    }
    devFlags = devflags;
    hps = 0;
    painters = 0;
}

QPaintDevice::~QPaintDevice()
{
#if defined(QT_CHECK_STATE)
    if ( paintingActive() )
	qWarning( "QPaintDevice: Cannot destroy paint device that is being "
		  "painted.  Be sure to QPainter::end() painters!" );
#endif
}

HPS QPaintDevice::handle() const
{
    return hps;
}

bool QPaintDevice::cmd( int, QPainter *, QPDevCmdParam * )
{
#if defined(QT_CHECK_STATE)
    qWarning( "QPaintDevice::cmd: Device has no command interface" );
#endif
    return FALSE;
}

int QPaintDevice::metric( int ) const
{
#if defined(QT_CHECK_STATE)
    qWarning( "QPaintDevice::metrics: Device has no metric information" );
#endif
    return 0;
}

int QPaintDevice::fontMet( QFont *, int, const char*, int ) const
{
    return 0;
}

int QPaintDevice::fontInf( QFont *, int ) const
{
    return 0;
}

// these are defined in qpixmap_pm.cpp
extern HPS qt_alloc_mem_ps( int w, int h, HPS compat = 0 );
extern void qt_free_mem_ps( HPS hps );

// ROP translation table to convert Qt::RasterOp to ROP_ constants
extern const LONG qt_ropCodes_2ROP[] = {
    ROP_SRCCOPY, 		// CopyROP
    ROP_SRCPAINT,		// OrROP
    ROP_SRCINVERT,		// XorROP
    0x22,	                // NotAndROP
    ROP_NOTSRCCOPY,		// NotCopyROP
    ROP_MERGEPAINT,	        // NotOrROP
    0x99,	                // NotXorROP
    ROP_SRCAND,		        // AndROP
    ROP_DSTINVERT,		// NotROP
    ROP_ZERO,		        // ClearROP
    ROP_ONE,		        // SetROP
    0xAA,	                // NopROP
    ROP_SRCERASE,		// AndNotROP
    0xDD,	                // OrNotROP
    0x77,	                // NandROP
    ROP_NOTSRCERASE		// NorROP
};

// draws the pixmap with a mask using the black source method
void drawMaskedPixmap(
    HPS dst_ps, int dst_depth, HPS src_ps, int src_w, int src_h, HPS mask_ps,
    int dx, int dy, int sx, int sy, int w, int h, Qt::RasterOp rop
) {
    // all rectangles for different operations combined together
    POINTL ptls[] = {
        { dx, dy }, { dx + w, dy + h },         // 0: src/buf => dst
        { sx, sy }, { sx + w, sy + h },         // 2: src <=> buf
        { sx, sy }, { sx + w, sy + h },         // 4: dst => buf
        { dx, dy }
    };
    IMAGEBUNDLE oldIb;

    if ( !mask_ps ) {
        // self-masked pixmap
        IMAGEBUNDLE newIb;
        GpiQueryAttrs( dst_ps, PRIM_IMAGE, IBB_BACK_MIX_MODE, (PBUNDLE) &oldIb );
        newIb.usBackMixMode = BM_SRCTRANSPARENT;
        GpiSetAttrs( dst_ps, PRIM_IMAGE, IBB_BACK_MIX_MODE, 0, (PBUNDLE) &newIb );
        GpiBitBlt( dst_ps, src_ps, 3, ptls, qt_ropCodes_2ROP[rop], BBO_IGNORE );
        GpiSetAttrs( dst_ps, PRIM_IMAGE, IBB_BACK_MIX_MODE, 0, (PBUNDLE) &oldIb );
    } else {
        bool simple = (rop == Qt::CopyROP) || (rop == Qt::XorROP);
        // helper hps for dblbuf
        HPS hpsBuf = qt_alloc_mem_ps( src_w, simple ? src_h : src_h * 2, dst_ps );
        // helper bitmap for dblbuf
        BITMAPINFOHEADER2 bmh;
        memset( &bmh, 0, sizeof(BITMAPINFOHEADER2) );
        bmh.cbFix = sizeof(BITMAPINFOHEADER2);
        bmh.cx = src_w;
        bmh.cy = simple ? src_h : src_h * 2;
        bmh.cPlanes = 1;
        bmh.cBitCount = dst_depth;
        HBITMAP hbmBuf = GpiCreateBitmap( hpsBuf, &bmh, 0, NULL, NULL );
        GpiSetBitmap( hpsBuf, hbmBuf );

        // query the destination color for 1-bpp bitmaps
        GpiQueryAttrs( dst_ps, PRIM_IMAGE, IBB_COLOR, (PBUNDLE) &oldIb );
        // setup colors for the masking
        GpiSetColor( hpsBuf, CLR_TRUE );
        GpiSetBackColor( hpsBuf, CLR_FALSE );

        // grab the destination
        GpiBitBlt( hpsBuf, dst_ps, 3, &ptls[4], ROP_SRCCOPY, BBO_IGNORE );
        // draw the mask: make non-transparent pixels (corresponding to
        // ones in the mask) black. skip this step when rop = XorROP
        // to get the XOR effect.
        if ( rop != Qt::XorROP )
            GpiBitBlt( hpsBuf, mask_ps, 3, &ptls[2], 0x22, BBO_IGNORE );

        // compose a masked pixmap with a given rop into the second buffer
        if ( !simple ) {
            // grab the destination to the 2nd buffer
            ptls[4].y += h; ptls[5].y += h;
            GpiBitBlt( hpsBuf, dst_ps, 3, &ptls[4], ROP_SRCCOPY, BBO_IGNORE );
            ptls[4].y -= h; ptls[5].y -= h;
            // bitblt pixmap using rop
            ptls[2].y += h; ptls[3].y += h;
            GpiSetColor( hpsBuf, oldIb.lColor );
            GpiBitBlt( hpsBuf, src_ps, 3, &ptls[2], qt_ropCodes_2ROP[rop], BBO_IGNORE );
            // make transparent pixels black
            GpiSetColor( hpsBuf, CLR_TRUE );
            GpiBitBlt( hpsBuf, mask_ps, 3, &ptls[2], ROP_SRCAND, BBO_IGNORE );
            // draw masked pixmap from the 2nd buffer to the 1rd
            ptls[2].y -= h; ptls[3].y -= h;
            ptls[4].y += h; ptls[5].y += h;
            GpiBitBlt( hpsBuf, hpsBuf, 3, &ptls[2], ROP_SRCPAINT, BBO_IGNORE );
        } else {
            // draw masked pixmap; transparent pixels are zeroed there
            // by prepareForMasking( TRUE )
            GpiSetColor( hpsBuf, oldIb.lColor );
            GpiBitBlt( hpsBuf, src_ps, 3, &ptls[2], ROP_SRCINVERT, BBO_IGNORE );
        }

        // flush the buffer
        GpiBitBlt( dst_ps, hpsBuf, 3, &ptls[0], ROP_SRCCOPY, BBO_IGNORE );
        // free resources
        GpiSetBitmap( hpsBuf, 0 );
        GpiDeleteBitmap( hbmBuf );
        qt_free_mem_ps( hpsBuf );
    }
}

//@@TODO (dmik): later
//
//#ifndef Q_OS_TEMP
//// For alpha blending, we must load the AlphaBlend() function at run time.
//#if !defined(AC_SRC_ALPHA)
//#define AC_SRC_ALPHA 0x01
//#endif
//typedef BOOL (WINAPI *ALPHABLEND)( HDC, int, int, int, int, HDC, int, int, int, int, BLENDFUNCTION );
//static HINSTANCE msimg32Lib = 0;
//static ALPHABLEND alphaBlend = 0;
//static bool loadAlphaBlendFailed = FALSE;
//static void cleanup_msimg32Lib()
//{
//    if ( msimg32Lib != 0 ) {
//	FreeLibrary( msimg32Lib );
//	msimg32Lib = 0;
//	alphaBlend = 0;
//	loadAlphaBlendFailed = FALSE;
//    }
//}
//#endif
//
///*
//   Try to do an AlphaBlend(). If it fails for some reasons, use BitBlt()
//   instead. The arguments are like in the BitBlt() call.
//*/
//void qt_AlphaBlend( HDC dst_dc, int dx, int dy, int sw, int sh, HDC src_dc, int sx, int sy, DWORD rop )
//{
//#ifndef Q_OS_TEMP
//    BLENDFUNCTION blend = {
//	AC_SRC_OVER,
//	0,
//	255,
//	AC_SRC_ALPHA
//    };
//    if ( alphaBlend ) {
//	alphaBlend( dst_dc, dx, dy, sw, sh, src_dc, sx, sy, sw, sh, blend );
//    } else {
//	if ( !loadAlphaBlendFailed ) {
//	    // try to load msimg32.dll and get the function
//	    // AlphaBlend()
//	    loadAlphaBlendFailed = TRUE;
//	    msimg32Lib = LoadLibraryA( "msimg32" );
//	    if ( msimg32Lib != 0 ) {
//		qAddPostRoutine( cleanup_msimg32Lib );
//		alphaBlend = (ALPHABLEND) GetProcAddress( msimg32Lib, "AlphaBlend" );
//		loadAlphaBlendFailed = ( alphaBlend == 0 );
//	    }
//	}
//	if ( loadAlphaBlendFailed )
//	    BitBlt( dst_dc, dx, dy, sw, sh, src_dc, sx, sy, rop );
//	else
//	    alphaBlend( dst_dc, dx, dy, sw, sh, src_dc, sx, sy, sw, sh, blend );
//    }
//#else
//    BitBlt( dst_dc, dx, dy, sw, sh, src_dc, sx, sy, rop );
//#endif
//}

void bitBlt( QPaintDevice *dst, int dx, int dy,
	     const QPaintDevice *src, int sx, int sy, int sw, int sh,
	     Qt::RasterOp rop, bool ignoreMask  )
{
    if ( !src || !dst ) {
#if defined(QT_CHECK_NULL)
	Q_ASSERT( src != 0 );
	Q_ASSERT( dst != 0 );
#endif
	return;
    }
    if ( src->isExtDev() )
	return;

    QPaintDevice *pdev = QPainter::redirect( dst );
    if ( pdev )
	dst = pdev;

    int ts = src->devType();			// from device type
    int td = dst->devType();			// to device type

    if ( sw <= 0 ) {				// special width
	if ( sw < 0 )
	    sw = src->metric(QPaintDeviceMetrics::PdmWidth) - sx;
	else
	    return;
    }
    if ( sh <= 0 ) {				// special height
	if ( sh < 0 )
	    sh = src->metric(QPaintDeviceMetrics::PdmHeight) - sy;
	else
	    return;
    }

    if ( dst->paintingActive() && dst->isExtDev() ) {
	QPixmap *pm;				// output to picture/printer
	bool	 tmp_pm = TRUE;
	if ( ts == QInternal::Pixmap ) {
	    pm = (QPixmap*)src;
	    if ( sx != 0 || sy != 0 ||
		 sw != pm->width() || sh != pm->height() ) {
		QPixmap *pm_new = new QPixmap( sw, sh, pm->depth() );
		bitBlt( pm_new, 0, 0, pm, sx, sy, sw, sh );
		pm = pm_new;
	    } else {
		tmp_pm = FALSE;
	    }
	} else if ( ts == QInternal::Widget ) {	// bitBlt to temp pixmap
	    pm = new QPixmap( sw, sh );
	    Q_CHECK_PTR( pm );
	    bitBlt( pm, 0, 0, src, sx, sy, sw, sh );
	} else {
#if defined(QT_CHECK_RANGE)
	    qWarning( "bitBlt: Cannot bitBlt from device" );
#endif
	    return;
	}
	QPDevCmdParam param[3];
	QPoint p(dx,dy);
	param[0].point	= &p;
	param[1].pixmap = pm;
	dst->cmd( QPaintDevice::PdcDrawPixmap, 0, param );
	if ( tmp_pm )
	    delete pm;
	return;
    }

    switch ( ts ) {
	case QInternal::Widget:
	case QInternal::Pixmap:
	case QInternal::System:			// OK, can blt from these
	    break;
	default:
#if defined(QT_CHECK_RANGE)
	    qWarning( "bitBlt: Cannot bitBlt from device type %x", ts );
#endif
	    return;
    }
    switch ( td ) {
	case QInternal::Widget:
	case QInternal::Pixmap:
	case QInternal::System:			// OK, can blt to these
	    break;
	default:
#if defined(QT_CHECK_RANGE)
	    qWarning( "bitBlt: Cannot bitBlt to device type %x", td );
#endif
	    return;
    }

    if ( rop > Qt::LastROP ) {
#if defined(QT_CHECK_RANGE)
	qWarning( "bitBlt: Invalid ROP code" );
#endif
	return;
    }

    if ( dst->isExtDev() ) {
#if defined(QT_CHECK_NULL)
	qWarning( "bitBlt: Cannot bitBlt to device" );
#endif
	return;
    }

    if ( td == QInternal::Pixmap )
	((QPixmap*)dst)->detach();		// changes shared pixmap

    HPS	 src_ps = src->hps, dst_ps = dst->hps;
    bool src_tmp = FALSE, dst_tmp = FALSE;

    QPixmap *src_pm;
    QBitmap *mask;
    if ( ts == QInternal::Pixmap ) {
	src_pm = (QPixmap *)src;
	mask = ignoreMask ? 0 : (QBitmap *)src_pm->mask();
    } else {
	src_pm = 0;
	mask   = 0;
	if ( !src_ps && ts == QInternal::Widget ) {
	    src_ps = WinGetPS( ((QWidget*)src)->winId() );
	    src_tmp = TRUE;
	}
    }
    if ( td != QInternal::Pixmap ) {
	if ( !dst_ps && td == QInternal::Widget ) {
            QWidget *dst_w = (QWidget*)dst;
	    if ( dst_w->testWFlags(Qt::WPaintUnclipped) ) {
                dst_ps = WinGetClipPS( dst_w->winId(), 0, PSF_PARENTCLIP );
            } else {
                dst_ps = WinGetPS( dst_w->winId() );
            }
	    dst_tmp = TRUE;
	}
    }
#if defined(QT_CHECK_NULL)
    Q_ASSERT( src_ps && dst_ps );
#endif

    // flip y coordinates
    int fsy = sy;
    if ( ts == QInternal::Widget || ts == QInternal::Pixmap ) {
        int devh = (ts == QInternal::Widget) ?
                ((QWidget*)src)->height() : ((QPixmap*)src)->height();
        fsy = devh - (fsy + sh);
    }
    int fdy = dy;
    if ( td == QInternal::Widget || td == QInternal::Pixmap ) {
        int devh = (td == QInternal::Widget) ?
                ((QWidget*)dst)->height() : ((QPixmap*)dst)->height();
        fdy = devh - (fdy + sh);
    }
    POINTL ptls[] = {
        { dx, fdy }, { dx + sw, fdy + sh },
        { sx, fsy },
    };

    if ( src_pm && src_pm->data->realAlphaBits ) {
        qWarning( "::bitBlt() for pixmaps with alpha is not yet implemented on OS/2" );
//@@TODO (dmik): later
//	if ( td == QInternal::Pixmap && ((QPixmap *)dst)->data->realAlphaBits )
//	    QPixmap::bitBltAlphaPixmap( ((QPixmap *)dst), dx, dy, src_pm, sx, sy, sw, sh, TRUE );
//	else
//	    qt_AlphaBlend( dst_dc, dx, dy, sw, sh, src_dc, sx, sy, ropCodes[rop] );
    } else if ( mask ) {
	if ( /*src_pm &&*/ td==QInternal::Pixmap && ((QPixmap *)dst)->data->realAlphaBits ) {
            qWarning( "::bitBlt() for pixmaps with alpha is not yet implemented on OS/2" );
//@@TODO (dmik): later
//	    src_pm->convertToAlphaPixmap();
//	    QPixmap::bitBltAlphaPixmap( (QPixmap *)dst, dx, dy, src_pm, sx, sy, sw, sh, TRUE );
	} else {
#if 0
//@@TODO (dmik): unfortunately, this nice method of setting the mask as a
//  pattern and using the corresponding ROPs to do masking in one step
//  will not apways work on many video drivers (including SDD/SNAP) since
//  they can spontaneously simplify the bitmap set as a pattern (for example,
//  take only first 8 pixels of the mask width), which will produce wrong
//  results.
            const ULONG AllAreaAttrs =
                ABB_COLOR | ABB_BACK_COLOR |
                ABB_MIX_MODE | ABB_BACK_MIX_MODE |
                ABB_SET | ABB_SYMBOL | ABB_REF_POINT;
            AREABUNDLE oldAb;
            GpiQueryAttrs( dst_ps, PRIM_AREA, AllAreaAttrs, (PBUNDLE) &oldAb );
            AREABUNDLE newAb = {
                CLR_TRUE, CLR_FALSE, FM_OVERPAINT, BM_OVERPAINT,
                LCID_QTMaskBitmap, 0, ptls [0]
            };
            GpiSetBitmap( mask->hps, 0 );
            GpiSetBitmapId( dst_ps, mask->hbm(), LCID_QTMaskBitmap );
            GpiSetAttrs( dst_ps, PRIM_AREA, AllAreaAttrs, 0, (PBUNDLE) &newAb );
            // we use the same rop codes for masked bitblt after setting the
            // low half of the rop byte to 0xA, which means destination bits
            // should be preserved where the corresponding bits in the mask
            // are zeroes.
            GpiBitBlt( dst_ps, src_ps, 3, ptls, (qt_ropCodes_2ROP[rop] & 0xF0) | 0x0A, BBO_IGNORE );
            GpiSetAttrs( dst_ps, PRIM_AREA, AllAreaAttrs, 0, (PBUNDLE) &oldAb );
            GpiDeleteSetId( dst_ps, LCID_QTMaskBitmap );
            GpiSetBitmap( mask->hps, mask->hbm() );
#else
            src_pm->prepareForMasking( TRUE );
            drawMaskedPixmap(
                dst_ps, dst->metric( QPaintDeviceMetrics::PdmDepth ),
                src_ps, src_pm->data->w, src_pm->data->h,
                src_pm->data->selfmask ? 0 : mask->hps,
                dx, fdy, sx, fsy, sw, sh, rop
            );
            src_pm->prepareForMasking( FALSE );
#endif
	}
    } else {
	if ( td==QInternal::Pixmap && ((QPixmap *)dst)->isQBitmap() ) {
            GpiBitBlt( dst_ps, src_ps, 3, ptls, qt_ropCodes_2ROP[rop], BBO_IGNORE );
	} else if ( src_pm && td==QInternal::Pixmap && ((QPixmap *)dst)->data->realAlphaBits ) {
            qWarning( "::bitBlt() for pixmaps with alpha is not yet implemented on OS/2" );
//@@TODO (dmik): later
//	    QPixmap *dst_pm = (QPixmap *)dst;
//	    if ( rop == Qt::CopyROP ) {
//		src_pm->convertToAlphaPixmap();
//		QPixmap::bitBltAlphaPixmap( dst_pm, dx, dy, src_pm, sx, sy, sw, sh, TRUE );
//	    } else {
//		src_pm->convertToAlphaPixmap();
//		if ( dst_pm->mask() ) {
//		    int width = QMIN( dst_pm->mask()->width()-dx, sw );
//		    int height = QMIN( dst_pm->mask()->height()-dy, sh );
//		    MaskBlt( dst_dc, dx, dy, width, height, src_pm->hdc, sx, sy, dst_pm->mask()->hbm(),
//			    dx, dy, MAKEROP4(0x00aa0000,ropCodes[rop]) );
//		} else {
//		    BitBlt( dst_dc, dx, dy, sw, sh, src_pm->hdc, sx, sy, ropCodes[rop] );
//		}
//	    }
	} else {
            GpiBitBlt( dst_ps, src_ps, 3, ptls, qt_ropCodes_2ROP[rop], BBO_IGNORE );
	}
    }
    if ( src_tmp )
	WinReleasePS( src_ps );
    if ( dst_tmp )
	WinReleasePS( dst_ps );
}


void QPaintDevice::setResolution( int )
{
}

int QPaintDevice::resolution() const
{
    return metric( QPaintDeviceMetrics::PdmDpiY );
}
