Trolltech Home | Qt-interest Home | Recent Threads | All Threads | Author | Date
All threads index page 3

Qt-interest Archive, January 2007
QScrollArea scrolling blanks viewport at 2^15 pixels on Mac OS X


Message 1 in thread

Hi

We've come across a problem on Mac OS X (but not on Linux or Windows)
where scrolling through a QWidget that is a child of a QScrollArea
causes blanking of the widget when the coordinate crosses 2^15 pixels.
Scrolling may recover eventually as you continue moving to the right.

Installing an event filter shows that paint events stop being sent to
the widget during the blank phase, although move events still get
through. It looks like 2^15 and bigger coordinates are being seen as
negative coordinates (until the top bit is reset) and get clipped by the
viewport?

Anyway, a workaround is to drop back to subclassing the widget from
Q3ScrollView.

So, has anyone else seen this and does it look like a real bug, or am I
missing something obvious!

I've attached demo programs for the bug and also the workaround. These
draw a repeating colour gradient with vertical bars 1 pixel wide. The
first and last bars are coloured red to distinguish them. The banded
pattern thus created can be scrolled horizontally. See the code comments
for more.

System: Mac OS X 10.4.8  (i386, ppc)
Qt versions: 4.1.4, 4.2.1, 4.2.2  (open source)

Thanks!
Nige Brown

EMBL - European Molecular Biology Laboratory, Heidelberg, Germany


/**
/**
 * Demonstrates an apparent bug in Qt4 QScrollArea on Mac OS X, which
 * causes a contained widget (AlignmentWidget) with dimension near
 * 2^15 pixels to be clipped when scrolling past this boundary,
 * suggestive of a problem with 16 bit signed coordinates in the
 * scrolling implementation on OS X. Much larger dimensions do not
 * seem to show the problem.
 *
 * The AlignmentWidget draws a repeating colour gradient with vertical
 * bars 1 pixel wide. The first and last bars are coloured red to
 * distinguish them. The banded pattern thus created can be scrolled
 * horizontally.
 *
 * If the length of the scroll region (int main::length) is set to
 * about 40000 pixels, the scroll blanks part-way through and recovers
 * again later. An event filter shows that the child widget stops
 * receiving paint events during the blank phase, although move events
 * are still received.
 *
 * Setting the length to 2^15-1 scrolls perfectly.
 *
 * Setting to 2^15-1 causes blanking right until the end of the
 * rightward scroll and recovery only on scrolling to the leftward
 * end.
 *
 * system: Mac OS X 10.4.8  (i386, ppc)
 * Qt versions: 4.1.4, 4.2.1, 4.2.2  (open source)
 *
 * build: qmake -project; qmake; make
 *
 * reported by: nigel.brown@xxxxxxx  (Thu Jan 18 12:21:32 CET 2007)
 */

#include <iostream>
#include <vector>
using namespace std;

#include <QApplication>
#include <QMetaObject>
#include <QObject>
#include <QWidget>
#include <QScrollArea>
#include <QScrollBar>
#include <QPainter>
#include <QPixmap>
#include <QEvent>
#include <QPaintEvent>
#include <QSize>

QColor palette[] = {
    QColor(0x001122), QColor(0x223344), QColor(0x334455), QColor(0x445566),
    QColor(0x556677), QColor(0x667788), QColor(0x778899), QColor(0x8899AA),
    QColor(0x99AABB), QColor(0xAABBCC), QColor(0xFF0000),
};
    
class FilterObject : public QObject
{
//    Q_OBJECT

public:
    FilterObject(QObject *parent = 0) : QObject(parent) {}
    
protected:
    bool eventFilter(QObject *object, QEvent *event)
    {
        enum QEvent::Type e = event->type();
        
        const QMetaObject *qm = object->metaObject();
        const char *type = qm->className();
        const char *name = qPrintable(object->objectName());
        
        QWidget *qw = dynamic_cast<QWidget *>(object);
        if (qw == 0)
            return false;
        
        if (e == QEvent::Paint) {
            cout << type << " " << name << " " << "[paint] " << e << endl;
            return false;
        } else if (e == QEvent::Move) {
            cout << type << " " << name << " " << "[move]  " << e << endl;
            return false;
        }
        cout << type << " " << name << " " << "[?]  " << e << endl;
        return false;
    }
};

class AlignmentWidget : public QWidget
{
//    Q_OBJECT

public:
    AlignmentWidget(int length, int boxWidth, int boxHeight,
                    QWidget *parent = 0)
	: QWidget(parent), length(length), boxWidth(boxWidth),
	  boxHeight(boxHeight)
    {
        cout << "AlignmentWidget constructor called\n";
        for (int i=0; i<length; ++i) {
            sequence.push_back(48+((i+1) % 10));
        }
        setObjectName(QString("AlignmentWidget"));
        update();
    }

    QSize sizeHint() const
    {
        int hsize = length * boxWidth;
        int vsize = 10 * boxHeight; 
        cout << "SIZEHINT " << hsize << " x " << vsize << endl;
        return QSize(hsize, vsize);
    }


protected:
    void paintEvent(QPaintEvent *event)
    {
        QRect rect = event->rect();

        int beginColumn = rect.left() / boxWidth;
        int endColumn = rect.right() / boxWidth;

        (void) printf("paintEvent(%d,%d)\n", beginColumn, endColumn);

        QPainter painter(this);
        painter.fillRect(rect.left(), rect.top(), rect.width(),
                         rect.height(), QBrush(Qt::green));
        painter.setBackgroundMode(Qt::OpaqueMode);
        
        QPixmap pixmap(boxWidth, boxHeight);

        for (int i=beginColumn; i<=endColumn; ++i) {
            pixmap.fill(::palette[i%10]);
            painter.drawPixmap(i*boxWidth, boxHeight, pixmap);
        }
        if (beginColumn == 0) {
            pixmap.fill(::palette[10]);
            painter.drawPixmap(0, boxHeight, pixmap);
        }
        if (endColumn == length-1) {
            pixmap.fill(::palette[10]);
            painter.drawPixmap((length-1)*boxWidth, boxHeight, pixmap);
        }
    }

private:
    int length;
    int boxWidth;
    int boxHeight;
    vector<int> sequence;
};

int main(int argc, char *argv[])
{
    //A large number produces a blank region in mid-scroll, which
    //recovers as scrolling continues to the right. Similar behaviour
    //upon scrolling back to the left.
    int length = 40000;

    //This blanks part way through and remains blank until the end of
    //the scroll. Upon scrolling back the canvas never recovers until
    //the exact end of the scroll limit.
    //int length = 0x8000;  // 2^15
  
    //This works
    //int length   = 0x8000 - 1; // 2^15 - 1

    cout << "Length: " << length << endl;

    int boxWidth  = 1;
    int boxHeight = 10;

    QApplication app(argc, argv);

    QScrollArea     *alignScroll = new QScrollArea();
    AlignmentWidget *alignWidget = new AlignmentWidget(length, boxWidth,
						       boxHeight);

    alignScroll->setWidget(alignWidget);
    alignScroll->setBackgroundRole(QPalette::Dark);
    alignScroll->horizontalScrollBar()->setRange(0, length*boxWidth);
    alignScroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);

    alignScroll->show();

    FilterObject *ef = new FilterObject();
    qApp->installEventFilter(ef);

    return app.exec();
}

/**
/**
 *
 * system: Mac OS X 10.4.8  (i386, ppc)
 * Qt versions: 4.1.4, 4.2.1, 4.2.2  (open source)
 *
 * build: qmake -project 'QT += qt3support'; qmake; make
 *
 * reported by: nigel.brown@xxxxxxx  (Thu Jan 18 12:21:32 CET 2007)
 */

#include <iostream>
#include <vector>
using namespace std;

#include <QApplication>
#include <QMetaObject>
#include <QObject>
#include <QWidget>
#include <Q3ScrollView>  /* Qt3 backwards compatibility class */
#include <QScrollBar>
#include <QPainter>
#include <QPixmap>
#include <QEvent>
#include <QPaintEvent>
#include <QSize>

QColor palette[] = {
    QColor(0x001122), QColor(0x223344), QColor(0x334455), QColor(0x445566),
    QColor(0x556677), QColor(0x667788), QColor(0x778899), QColor(0x8899AA),
    QColor(0x99AABB), QColor(0xAABBCC), QColor(0xFF0000),
};
    
class FilterObject : public QObject
{
//    Q_OBJECT

public:
    FilterObject(QObject *parent = 0) : QObject(parent) {}
    
protected:
    bool eventFilter(QObject *object, QEvent *event)
    {
        enum QEvent::Type e = event->type();
        
        const QMetaObject *qm = object->metaObject();
        const char *type = qm->className();
        const char *name = qPrintable(object->objectName());
        
        QWidget *qw = dynamic_cast<QWidget *>(object);
        if (qw == 0)
            return false;
        
        if (e == QEvent::Paint) {
            cout << type << " " << name << " " << "[paint] " << e << endl;
            return false;
        } else if (e == QEvent::Move) {
            cout << type << " " << name << " " << "[move]  " << e << endl;
            return false;
        }
        cout << type << " " << name << " " << "[?]  " << e << endl;
        return false;
    }
};

class AlignmentWidget : public Q3ScrollView
{
//    Q_OBJECT

public:
    AlignmentWidget(int length, int boxWidth, int boxHeight)
	: Q3ScrollView(), length(length), boxWidth(boxWidth),
	  boxHeight(boxHeight)
    {
        cout << "AlignmentWidget constructor called\n";
        for (int i=0; i<length; ++i) {
            sequence.push_back(48+((i+1) % 10));
        }
        setObjectName(QString("AlignmentWidget"));
        resizeContents(length*boxWidth, boxHeight);
    }

protected:
    void drawContents(QPainter *painter, int cx, int cy, int cw, int ch)
    {
        painter->fillRect(cx, cy, cw, ch, QBrush(Qt::green));
        painter->setBackgroundMode(Qt::OpaqueMode);
    
        // Set up the begin and end columns and rows
        int beginColumn = cx        / boxWidth;
        int endColumn   = (cx+cw-1) / boxWidth;

        (void) printf("drawContents(%d,%d)\n", beginColumn, endColumn);

        QPixmap pixmap(boxWidth, boxHeight);

        for (int i=beginColumn; i<=endColumn; ++i) {
            pixmap.fill(::palette[i%10]);
            painter->drawPixmap(i*boxWidth, boxHeight, pixmap);
        }
        if (beginColumn == 0) {
            pixmap.fill(::palette[10]);
            painter->drawPixmap(0, boxHeight, pixmap);
        }
        if (endColumn == length-1) {
            pixmap.fill(::palette[10]);
            painter->drawPixmap((length-1)*boxWidth, boxHeight, pixmap);
        }
    }

private:
    int length;
    int boxWidth;
    int boxHeight;
    vector<int> sequence;
};

int main(int argc, char *argv[])
{
    // any length works
    int length = 40000;
    //int length = 0x8000;  // 2^15
    //int length   = 0x8000 - 1; // 2^15 - 1

    cout << "Length: " << length << endl;

    int boxWidth  = 1;
    int boxHeight = 10;

    QApplication app(argc, argv);

    AlignmentWidget *alignScroll = new AlignmentWidget(length, boxWidth,
						       boxHeight);

    alignScroll->setBackgroundRole(QPalette::Dark);
    alignScroll->horizontalScrollBar()->setRange(0, length*boxWidth);
    alignScroll->setHScrollBarMode(Q3ScrollView::AlwaysOn);
    alignScroll->show();

    FilterObject *ef = new FilterObject();
    qApp->installEventFilter(ef);

    return app.exec();
}


Message 2 in thread

On 1/19/07, Nigel Brown <brown@xxxxxxx> wrote:
> missing something obvious!
>
> I've attached demo programs for the bug and also the workaround. These
> draw a repeating colour gradient with vertical bars 1 pixel wide. The
> first and last bars are coloured red to distinguish them. The banded
> pattern thus created can be scrolled horizontally. See the code comments
> for more.

You can always send this to TT, qt-bugs at trolltech.com, they not
always following the forums.

-- 
 [ signature omitted ] 

Message 3 in thread

Hi,

> We've come across a problem on Mac OS X (but not on Linux or Windows)
> where scrolling through a QWidget that is a child of a QScrollArea
> causes blanking of the widget when the coordinate crosses 2^15 pixels.
> Scrolling may recover eventually as you continue moving to the right.

You mean you have a widget more than 2^15 pixels wide? This was a known 
limitation in Qt 3 (a system limitation, coordinates are encoded using 
16-bit integers) and I'm not certain this limitationt has been entirely 
worked around in Qt 4.

Load a large image in examples/widgets/imageviewer, for example this 
4096 x 4096 image:
http://www.cfht.hawaii.edu/News/WIRCam1stLight/orion_pressrel.jpg
Does this example work for you?

--
 [ signature omitted ] 

Message 4 in thread

Hi,

Well, it's not a problem with large drawables per se, rather that when 
the QScrollArea's child widget length is at or just above 2^15 pixels, 
the scroll area blanks. Further experiments with increasing lengths show 
the blank region decreasing in size until it becomes just a flicker, 
then disappears by about 64k pixels. Thereafter, as far as I can tell, 
scrolling is perfectly behaved.

It looks to me like the algorithm that works around the 16 bit system 
limitation has a pathological behaviour just near 2^15 bits.

The legacy Q3ScrollView works fine on the same system from the same Qt4 
build, so something seems to have broken in Qt4 QScrollArea, which 
replaces Qt3 QScrollView.

I will submit this as a bug to Trolltech.

nige

--
 [ signature omitted ] 

Message 5 in thread

Hi,

> Well, it's not a problem with large drawables per se, rather that when 
> the QScrollArea's child widget length is at or just above 2^15 pixels, 
> the scroll area blanks. Further experiments with increasing lengths show 
> the blank region decreasing in size until it becomes just a flicker, 
> then disappears by about 64k pixels. Thereafter, as far as I can tell, 
> scrolling is perfectly behaved.

Unfortunately I don't work on Mac OS so I can't test that. Are you able 
to reproduce this problem with examples/widgets/imageviewer?

> [...]
> I will submit this as a bug to Trolltech.

Good, at least this won't get unnoticed.

--
 [ signature omitted ]