/****************************************************************************
** $Id:  qt/qregion_x11.cpp   3.0.6   edited May 27 20:55 $
**
** Implementation of QRegion class for X11
**
** Created : 940729
**
** Copyright (C) 1992-2000 Trolltech AS.  All rights reserved.
**
** 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 for Unix/X11 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 "qregion.h"
#include "qpointarray.h"
#include "qbuffer.h"
#include "qimage.h"
#include "qbitmap.h"
#include "qt_x11.h"

// NOT REVISED

static QRegion *empty_region = 0;

static void cleanup_empty_region()
{
    delete empty_region;
    empty_region = 0;
}


/*!
    Constructs a null region.

    \sa isNull()
*/

QRegion::QRegion()
{
    if ( !empty_region ) {			// avoid too many allocs
	qAddPostRoutine( cleanup_empty_region );
	empty_region = new QRegion( TRUE );
	Q_CHECK_PTR( empty_region );
    }
    data = empty_region->data;
    data->ref();
}

/*! \internal
  Internal constructor that creates a null region.
*/

QRegion::QRegion( bool is_null )
{
    data = new QRegionData;
    Q_CHECK_PTR( data );
    data->rgn = XCreateRegion();
    data->is_null = is_null;
}

/*!
    \overload

    Create a region based on the rectange \a r with region type \a t.

    If the rectangle is invalid a null region will be created.

    \sa QRegion::RegionType
*/

QRegion::QRegion( const QRect &r, RegionType t )
{
    if ( r.isEmpty() ) {
	if ( !empty_region ) {			// avoid too many allocs
	    qAddPostRoutine( cleanup_empty_region );
	    empty_region = new QRegion( TRUE );
	    Q_CHECK_PTR( empty_region );
	}
	data = empty_region->data;
	data->ref();
    } else {
	data = new QRegionData;
	Q_CHECK_PTR( data );
	data->is_null = FALSE;
	if ( t == Rectangle ) {			// rectangular region
	    data->rect = r;
	    data->rgn = XCreateRegion();
	    XRectangle xr;
	    xr.x = r.x();
	    xr.y = r.y();
	    xr.width  = r.width();
	    xr.height = r.height();
	    XUnionRectWithRegion( &xr, data->rgn, data->rgn );
	} else if ( t == Ellipse ) {		// elliptic region
	    QPointArray a;
	    a.makeEllipse( r.x(), r.y(), r.width(), r.height() );
	    data->rgn = XPolygonRegion( (XPoint*)a.shortPoints(), a.size(),
					EvenOddRule );
	}
    }
}


/*!
    Constructs a polygon region from the point array \a a.

    If \a winding is TRUE, the polygon region is filled using the
    winding algorithm, otherwise the default even-odd fill algorithm
    is used.

    This constructor may create complex regions that will slow down
    painting when used.
*/

QRegion::QRegion( const QPointArray &a, bool winding )
{
    if (a.size() > 0) {
	data = new QRegionData;
	Q_CHECK_PTR( data );
	data->is_null = FALSE;
	data->rgn = XPolygonRegion( (XPoint*)a.shortPoints(), a.size(),
				    winding ? WindingRule : EvenOddRule );
    } else {
	if ( !empty_region ) {
	    qAddPostRoutine( cleanup_empty_region );
	    empty_region = new QRegion( TRUE );
	    Q_CHECK_PTR( empty_region );
	}
	data = empty_region->data;
	data->ref();
    }
}


/*!
    Constructs a new region which is equal to region \a r.
*/

QRegion::QRegion( const QRegion &r )
{
    data = r.data;
    data->ref();
}

Region qt_x11_bitmapToRegion(const QBitmap& bitmap)
{
    Region region = XCreateRegion();
    QImage image = bitmap.convertToImage();

    XRectangle xr;

#define AddSpan \
	{ \
	    xr.x = prev1; \
	    xr.y = y; \
	    xr.width = x-prev1-1; \
	    xr.height = 1; \
	    XUnionRectWithRegion( &xr, region, region ); \
	}

    // deal with 0<->1 problem (not on X11 anymore)
    int zero=0;//(qGray(image.color(0)) < qGray(image.color(1)) ? 0x00 : 0xff);
    bool little = image.bitOrder() == QImage::LittleEndian;

    int x, y;
    for (y=0; y<image.height(); y++) {
	uchar *line = image.scanLine(y);
	int w = image.width();
	uchar all=zero;
	int prev1 = -1;
	for (x=0; x<w; ) {
	    uchar byte = line[x/8];
	    if ( x>w-8 || byte!=all ) {
		if ( little ) {
		    for ( int b=8; b>0 && x<w; b-- ) {
			if ( !(byte&0x01) == !all ) {
			    // More of the same
			} else {
			    // A change.
			    if ( all!=zero ) {
				AddSpan;
				all = zero;
			    } else {
				prev1 = x;
				all = ~zero;
			    }
			}
			byte >>= 1;
			x++;
		    }
		} else {
		    for ( int b=8; b>0 && x<w; b-- ) {
			if ( !(byte&0x80) == !all ) {
			    // More of the same
			} else {
			    // A change.
			    if ( all!=zero ) {
				AddSpan;
				all = zero;
			    } else {
				prev1 = x;
				all = ~zero;
			    }
			}
			byte <<= 1;
			x++;
		    }
		}
	    } else {
		x+=8;
	    }
	}
	if ( all != zero ) {
	    AddSpan;
	}
    }

    return region;
}



/*!
    Constructs a region from the bitmap \a bm.

    The resulting region consists of the pixels in bitmap \a bm that
    are \c color1, as if each pixel was a 1 by 1 rectangle.

    This constructor may create complex regions that will slow down
    painting when used. Note that drawing masked pixmaps can be done
    much faster using QPixmap::setMask().
*/
QRegion::QRegion( const QBitmap & bm )
{
    data = new QRegionData;
    Q_CHECK_PTR( data );
    data->is_null = FALSE;
    data->rgn = qt_x11_bitmapToRegion(bm);
}

/*!
    Destroys the region.
*/

QRegion::~QRegion()
{
    if ( data->deref() ) {
	XDestroyRegion( data->rgn );
	delete data;
    }
}


/*!
    Assigns \a r to this region and returns a reference to the region.
*/

QRegion &QRegion::operator=( const QRegion &r )
{
    r.data->ref();				// beware of r = r
    if ( data->deref() ) {
	XDestroyRegion( data->rgn );
	delete data;
    }
    data = r.data;
    return *this;
}


/*!
    Returns a \link shclass.html deep copy\endlink of the region.

    \sa detach()
*/

QRegion QRegion::copy() const
{
    QRegion r( data->is_null );
    XUnionRegion( data->rgn, r.data->rgn, r.data->rgn );
    return r;
}

/*!
    Returns TRUE if the region is a null region; otherwise returns
    FALSE.

    A null region is a region that has not been initialized. A null
    region is always empty.

    \sa isEmpty()
*/

bool QRegion::isNull() const
{
    return data->is_null;
}


/*!
    Returns TRUE if the region is empty; otherwise returns FALSE. An
    empty region is a region that contains no points.

    Example:
    \code
	QRegion r1( 10, 10, 20, 20 );
	QRegion r2( 40, 40, 20, 20 );
	QRegion r3;
        r1.isNull();             // FALSE
        r1.isEmpty();            // FALSE
        r3.isNull();             // TRUE
        r3.isEmpty();            // TRUE
        r3 = r1.intersect( r2 ); // r3 = intersection of r1 and r2
        r3.isNull();             // FALSE
        r3.isEmpty();            // TRUE
        r3 = r1.unite( r2 );     // r3 = union of r1 and r2
        r3.isNull();             // FALSE
        r3.isEmpty();            // FALSE
    \endcode

    \sa isNull()
*/

bool QRegion::isEmpty() const
{
    return data->is_null || XEmptyRegion( data->rgn );
}


/*!
    Returns TRUE if the region contains the point \a p; otherwise
    returns FALSE.
*/

bool QRegion::contains( const QPoint &p ) const
{
    return XPointInRegion( data->rgn, p.x(), p.y() );
}

/*!
    \overload

    Returns TRUE if the region overlaps the rectangle \a r; otherwise
    returns FALSE.
*/

bool QRegion::contains( const QRect &r ) const
{
    return XRectInRegion( data->rgn, r.left(), r.top(),
			  r.width(), r.height() ) != RectangleOut;
}


/*!
    Translates (moves) the region \a dx along the X axis and \a dy
    along the Y axis.
*/

void QRegion::translate( int dx, int dy )
{
    if ( empty_region && data == empty_region->data )
	return;
    detach();
    if ( data->rect.isValid() ) {
	data->rect.moveBy( dx, dy );
	*this = QRegion( data->rect );
    } else {
	XOffsetRegion( data->rgn, dx, dy );
    }
}


/*!
    Returns a region which is the union of this region and \a r.

    \img runion.png Region Union

    The figure shows the union of two elliptical regions.
*/

QRegion QRegion::unite( const QRegion &r ) const
{
    QRegion result( FALSE );
    XUnionRegion( data->rgn, r.data->rgn, result.data->rgn );
    return result;
}

/*!
    Returns a region which is the intersection of this region and \a r.

    \img rintersect.png Region Intersection

    The figure shows the intersection of two elliptical regions.
*/

QRegion QRegion::intersect( const QRegion &r ) const
{
    QRegion result( FALSE );
    XIntersectRegion( data->rgn, r.data->rgn, result.data->rgn );
    return result;
}

/*!
    Returns a region which is \a r subtracted from this region.

    \img rsubtract.png Region Subtraction

    The figure shows the result when the ellipse on the right is
    subtracted from the ellipse on the left. (\c left-right )
*/

QRegion QRegion::subtract( const QRegion &r ) const
{
    QRegion result( FALSE );
    XSubtractRegion( data->rgn, r.data->rgn, result.data->rgn );
    return result;
}

/*!
    Returns a region which is the exclusive or (XOR) of this region
    and \a r.

    \img rxor.png Region XORed

    The figure shows the exclusive or of two elliptical regions.
*/

QRegion QRegion::eor( const QRegion &r ) const
{
    QRegion result( FALSE );
    XXorRegion( data->rgn, r.data->rgn, result.data->rgn );
    return result;
}


/*
  This is how X represents regions internally.
*/

struct BOX {
    short x1, x2, y1, y2;
};

struct _XRegion {
    long size;
    long numRects;
    BOX *rects;
    BOX  extents;
};


/*!
    Returns the bounding rectangle of this region. An empty region
    gives a rectangle that is QRect::isNull().
*/

QRect QRegion::boundingRect() const
{
    if ( data->rect.isValid() ) {
	Q_ASSERT( data->rgn->numRects == 1 );
	return data->rect;
    }
    XRectangle r;
    XClipBox( data->rgn, &r );
    return QRect( r.x, r.y, r.width, r.height );
}


/*!
    Returns an array of non-overlapping rectangles that make up the
    region.

    The union of all the rectangles is equal to the original region.
*/

QMemArray<QRect> QRegion::rects() const
{
    QMemArray<QRect> a( (int)data->rgn->numRects );
    if ( data->rect.isValid() ) {
	Q_ASSERT( data->rgn->numRects == 1 );
	a[0] = data->rect;
    } else {
	BOX *r = data->rgn->rects;
	for ( int i=0; i<(int)a.size(); i++ ) {
	    a[i].setCoords( r->x1, r->y1, r->x2-1, r->y2-1);
	    r++;
	}
    }
    return a;
}

/*!
  Sets the region to be the given set of rectangles.  The rectangles
  \e must be optimal Y-X sorted bands as follows:
   <ul>
    <li> The rectangles must not intersect
    <li> All rectangles with a given top coordinate must have the same height.
    <li> No two rectangles may abut horizontally (they should be combined
		into a single wider rectangle in that case).
    <li> The rectangles must be sorted ascendingly by Y as the major sort key
		and X as the minor sort key.
   </ul>
  \internal
  Only some platforms have that restriction (QWS).
*/
void QRegion::setRects( const QRect *rects, int num )
{
    // Could be optimized
    *this = QRegion();
    for (int i=0; i<num; i++)
	*this |= rects[i];
    data->rect.setCoords( 0, 0, -1, -1 );
}

/*!
    Returns TRUE if the region is equal to \a r; otherwise returns
    FALSE.
*/

bool QRegion::operator==( const QRegion &r ) const
{
    return data == r.data ?
	TRUE : XEqualRegion( data->rgn, r.data->rgn );
}

/*!
    \fn bool QRegion::operator!=( const QRegion &r ) const

    Returns TRUE if the region is different from \a r; otherwise
    returns FALSE.
*/
