/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** Contact: Qt Software Information (qt-info@nokia.com) ** ** Copyright (C) 2009 netlabs.org. OS/2 parts. ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain ** additional rights. These rights are described in the Nokia Qt LGPL ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at qt-sales@nokia.com. ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qbitmap.h" #include "qbuffer.h" #include "qimage.h" #include "qpolygon.h" #include "qregion.h" #include "qt_os2.h" QT_BEGIN_NAMESPACE // To compensate the difference between Qt (where y axis goes downwards) and // GPI (where y axis goes upwards) coordinate spaces when dealing with regions // we use the following technique: when a GPI resource is allocated for a Qt // region, we simply change the sign of all y coordinates to quickly flip it // top to bottom in a manner that doesn't depend on the target device height. // All we have to do to apply the created GPI region to a particular GPI device // is to align its y axis to the top of the device (i.e. offset the region // up by the height of the device), and unalign it afterwards to bring it back // to the coordinate space of other device-independent (unaligned) regions. // To optimize this, we remember (in data->hgt) the last height value used to // align the region, and align it again only if the target device height // changes. Zero height indicates a device-independent target (such as other // unaligned Qt region). // // The handle() function, used for external access to the region, takes an // argument that must be always set to the height of the target device to // guarantee the correct coordinate space alignment. #if defined(__GNUC__) && defined(__INNOTEK_LIBC__) // Innotek GCC lacks some API functions in its version of OS/2 Toolkit headers extern "C" HRGN APIENTRY GpiCreateEllipticRegion(HPS hps, PRECTL prclRect); extern "C" HRGN APIENTRY GpiCreatePolygonRegion(HPS hps, ULONG ulCount, PPOLYGON paplgn, ULONG flOptions); #endif QRegion::QRegionData QRegion::shared_empty = { Q_BASIC_ATOMIC_INITIALIZER(1), NULLHANDLE, 0 }; QRegion::QRegion() : d(&shared_empty) { d->ref.ref(); } QRegion::QRegion(const QRect &r, RegionType t) { if (r.isEmpty()) { d = &shared_empty; d->ref.ref(); } else { d = new QRegionData; d->ref = 1; d->height = 0; HPS hps = qt_display_ps(); if (t == Rectangle) { RECTL rcl = { r.left(), -(r.bottom()+1), r.right()+1, -r.top() }; d->rgn = GpiCreateRegion(hps, 1, &rcl); } else if (t == Ellipse) { // if the width or height of the ellipse is odd, GPI always // converts it to a nearest even value, which is obviously stupid // So, we don't use GpiCreateEllipticRegion(), but create an array // of points to call GpiCreatePolygonRegion() instead. QPainterPath p(QPointF(r.x(), r.y())); p.arcTo(r.x(), r.y(), r.width(), r.height(), 0, 360); QPolygon a = p.toFillPolygon().toPolygon(); for (int i = 0; i < a.size(); ++ i) a[i].ry() = -(a[i].y() + 1); // GpiCreatePolygonRegion() is bogus and always starts a poligon from // the current position. Make the last point the current one and reduce // the number of points by one. GpiMove(hps, reinterpret_cast(&a[a.size() - 1])); POLYGON poly = { a.size() - 1, reinterpret_cast(a.data()) }; d->rgn = GpiCreatePolygonRegion(hps, 1, &poly, POLYGON_ALTERNATE); } } } QRegion::QRegion(const QPolygon &a, Qt::FillRule fillRule) { if (a.size() < 3) { d = &shared_empty; d->ref.ref(); } else { d = new QRegionData; d->ref = 1; d->height = 0; HPS hps = qt_display_ps(); POINTL *pts = new POINTL[a.size()]; for (int i = 0; i < a.size(); ++ i) { pts[i].x = a[i].x(); pts[i].y = - (a[i].y() + 1); } // GpiCreatePolygonRegion() is bogus and always starts a poligon from // the current position. Make the last point the current one and reduce // the number of points by one. GpiMove(hps, &pts[a.size() - 1]); POLYGON poly = { a.size() - 1, pts }; ULONG opts = Qt::OddEvenFill ? POLYGON_ALTERNATE : POLYGON_WINDING; d->rgn = GpiCreatePolygonRegion(hps, 1, &poly, opts); delete[] pts; } } QRegion::QRegion(const QRegion &r) { d = r.d; d->ref.ref(); } static HRGN bitmapToRegion(const QBitmap& bitmap) { HRGN region = 0; QImage image = bitmap.toImage(); const int maxrect = 256; RECTL rects[maxrect]; HPS hps = qt_display_ps(); #define FlushSpans \ { \ HRGN r = GpiCreateRegion(hps, n, rects); \ if (region) { \ GpiCombineRegion(hps, region, region, r, CRGN_OR); \ GpiDestroyRegion(hps, r); \ } else { \ region = r; \ } \ } #define AddSpan \ { \ rects[n].xLeft = prev1; \ rects[n].yBottom = -(y+1); \ rects[n].xRight = x-1+1; \ rects[n].yTop = -y; \ n++; \ if (n == maxrect) { \ FlushSpans \ n = 0; \ } \ } int n = 0; int zero = 0x00; 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) { 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; } } if (n) { FlushSpans; } if (!region) region = GpiCreateRegion(hps, 0, NULL); return region; } QRegion::QRegion(const QBitmap &bm) { if (bm.isNull()) { d = &shared_empty; d->ref.ref(); } else { d = new QRegionData; d->ref = 1; d->height = 0; d->rgn = bitmapToRegion(bm); } } void QRegion::cleanUp(QRegion::QRegionData *x) { if (x->rgn != NULLHANDLE) GpiDestroyRegion(qt_display_ps(), x->rgn); delete x; } QRegion::~QRegion() { if (!d->ref.deref()) cleanUp(d); } QRegion &QRegion::operator=(const QRegion &r) { r.d->ref.ref(); if (!d->ref.deref()) cleanUp(d); d = r.d; return *this; } QRegion QRegion::copy() const { QRegion r; QRegionData *x = new QRegionData; x->ref = 1; if (d->rgn != NULLHANDLE) { x->height = d->height; HPS hps = qt_display_ps(); x->rgn = GpiCreateRegion(hps, 0, NULL); GpiCombineRegion(hps, x->rgn, d->rgn, NULL, CRGN_COPY); } else { x->height = 0; x->rgn = NULLHANDLE; } if (!r.d->ref.deref()) cleanUp(r.d); r.d = x; return r; } bool QRegion::isEmpty() const { return (d == &shared_empty || boundingRect().isEmpty()); } bool QRegion::contains(const QPoint &p) const { LONG rc = PRGN_OUTSIDE; if (d->rgn != NULLHANDLE) { POINTL ptl = { p.x(), d->height - (p.y() + 1) }; rc = GpiPtInRegion(qt_display_ps(), d->rgn, &ptl); } return rc == PRGN_INSIDE; } bool QRegion::contains(const QRect &r) const { LONG rc = PRGN_OUTSIDE; if (d->rgn != NULLHANDLE) { RECTL rcl = { r.left(), d->height - (r.bottom() + 1), r.right() + 1, d->height - r.top() }; rc = GpiRectInRegion(qt_display_ps(), d->rgn, &rcl); } return rc == RRGN_INSIDE || rc == RRGN_PARTIAL; } void QRegion::translate(int dx, int dy) { if (d->rgn == NULLHANDLE || (dx == 0 && dy == 0)) return; detach(); POINTL ptl = { dx, -dy }; GpiOffsetRegion(qt_display_ps(), d->rgn, &ptl); } #define CRGN_NOP -1 // Duplicates of those in qregion.cpp #define QRGN_OR 6 #define QRGN_AND 7 #define QRGN_SUB 8 #define QRGN_XOR 9 /* Performs the actual OR, AND, SUB and XOR operation between regions. Sets the resulting region handle to 0 to indicate an empty region. */ QRegion QRegion::pmCombine(const QRegion &r, int op) const { LONG both = CRGN_NOP, left = CRGN_NOP, right = CRGN_NOP; switch (op) { case QRGN_OR: both = CRGN_OR; left = right = CRGN_COPY; break; case QRGN_AND: both = CRGN_AND; break; case QRGN_SUB: both = CRGN_DIFF; left = CRGN_COPY; break; case QRGN_XOR: both = CRGN_XOR; left = right = CRGN_COPY; break; default: qWarning( "QRegion: Internal error in pmCombine" ); } QRegion result; if (d->rgn == NULLHANDLE && r.d->rgn == NULLHANDLE) return result; HPS hps = qt_display_ps(); result.detach(); result.d->rgn = GpiCreateRegion(hps, 0, NULL); LONG rc = RGN_NULL; if (d->rgn != NULLHANDLE && r.d->rgn != NULLHANDLE) { updateHandle(r.d->height); // bring to the same coordinate space rc = GpiCombineRegion(hps, result.d->rgn, d->rgn, r.d->rgn, both); result.d->height = r.d->height; } else if (d->rgn && left != CRGN_NOP) { rc = GpiCombineRegion(hps, result.d->rgn, d->rgn, 0, left); result.d->height = d->height; } else if (r.d->rgn != NULLHANDLE && right != CRGN_NOP) { rc = GpiCombineRegion(hps, result.d->rgn, r.d->rgn, 0, right); result.d->height = r.d->height; } if (rc == RGN_NULL || rc == RGN_ERROR) { result = QRegion(); // shared_empty } return result; } QRegion QRegion::unite(const QRegion &r) const { if (d->rgn == NULLHANDLE) return r; if (r.d->rgn == NULLHANDLE) return *this; return pmCombine(r, QRGN_OR); } QRegion QRegion::unite(const QRect &r) const { return unite(QRegion(r)); } QRegion QRegion::intersect(const QRegion &r) const { if (r.d->rgn == NULLHANDLE || d->rgn == NULLHANDLE) return QRegion(); return pmCombine(r, QRGN_AND); } QRegion QRegion::subtract(const QRegion &r) const { if (r.d->rgn == NULLHANDLE || d->rgn == NULLHANDLE) return *this; return pmCombine(r, QRGN_SUB); } QRegion QRegion::eor(const QRegion &r) const { if (d->rgn == NULLHANDLE) return r; if (r.d->rgn == NULLHANDLE) return *this; return pmCombine(r, QRGN_XOR); } QRect QRegion::boundingRect() const { if (!d->rgn) return QRect(); RECTL rcl; LONG rc = RGN_NULL; if (d->rgn != NULLHANDLE) rc = GpiQueryRegionBox(qt_display_ps(), d->rgn, &rcl); if (rc == RGN_NULL || rc == RGN_ERROR) return QRect(); else return QRect(rcl.xLeft, d->height - rcl.yTop, rcl.xRight - rcl.xLeft, rcl.yTop - rcl.yBottom); } QVector QRegion::rects() const { QVector a; if (d->rgn == NULLHANDLE) return a; HPS hps = qt_display_ps(); RGNRECT ctl = {1, 0, 0, RECTDIR_LFRT_TOPBOT}; if (!GpiQueryRegionRects(hps, d->rgn, NULL, &ctl, NULL)) return a; ctl.crc = ctl.crcReturned; PRECTL rcls = new RECTL[ctl.crcReturned]; if (rcls == 0) return a; if (!GpiQueryRegionRects(hps, d->rgn, NULL, &ctl, rcls)) { delete [] rcls; return a; } a = QVector(ctl.crcReturned); PRECTL r = rcls; for (int i = 0; i < a.size(); ++i) { a[i].setRect(r->xLeft, d->height - r->yTop, r->xRight - r->xLeft, r->yTop - r->yBottom); ++r; } delete [] rcls; return a; } void QRegion::setRects(const QRect *rects, int num) { *this = QRegion(); if (!rects || num == 0 || (num == 1 && rects->isEmpty())) return; for (int i = 0; i < num; ++i) *this |= rects[i]; } int QRegion::numRects() const { if (d->rgn == NULLHANDLE) return 0; RGNRECT ctl = {1, 0, 0, RECTDIR_LFRT_TOPBOT}; if (!GpiQueryRegionRects(qt_display_ps(), d->rgn, NULL, &ctl, NULL)) return 0; return ctl.crcReturned; } bool QRegion::operator==(const QRegion &r) const { if (d == r.d) return true; if ((d->rgn == NULLHANDLE) ^ (r.d->rgn == NULLHANDLE)) // one is empty, not both return false; if (d->rgn == NULLHANDLE) // both empty return true; updateHandle(r.d->height); // bring to the same coordinate space return // both not empty GpiEqualRegion(qt_display_ps(), d->rgn, r.d->rgn) == EQRGN_EQUAL; } QRegion& QRegion::operator+=(const QRegion &r) { if (r.d->rgn == NULLHANDLE) return *this; if (d->rgn == NULLHANDLE) { *this = r; return *this; } *this = unite(r); return *this; } QRegion& QRegion::operator-=(const QRegion &r) { if (r.d->rgn == NULLHANDLE || d->rgn == NULLHANDLE) return *this; *this = subtract(r); return *this; } QRegion& QRegion::operator&=(const QRegion &r) { if (d->rgn == NULLHANDLE) return *this; if (r.d->rgn == NULLHANDLE) { *this = QRegion(); return *this; } *this = intersect(r); return *this; } bool qt_region_strictContains(const QRegion ®ion, const QRect &rect) { Q_UNUSED(region); Q_UNUSED(rect); return false; } /*! \internal Updates the region handle so that it is suitable for selection to a device with the given \a height. */ void QRegion::updateHandle(int targetHeight) const { QRegion *that = const_cast(this); // we're const here if (d->rgn == NULLHANDLE) { // a handle of a null region is requested, allocate an empty region that->detach(); that->d->rgn = GpiCreateRegion(qt_display_ps(), 0, NULL); that->d->height = targetHeight; } else if (d->height != targetHeight) { // align region y axis to the top of the device that->detach(); POINTL ptl = { 0, targetHeight - d->height }; GpiOffsetRegion(qt_display_ps(), d->rgn, &ptl); that->d->height = targetHeight; } } QT_END_NAMESPACE