changeset 41:691e68637348

non-working deform example
author maxter
date Sun, 17 May 2009 12:41:14 +0000
parents a5cc4ada07f5
children eb3b5bbffc8f
files demos/deform/main.d demos/deform/pathdeform.d demos/shared/arthurstyle.d demos/shared/arthurwidgets.d demos/shared/hoverpoints.d
diffstat 5 files changed, 2107 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demos/deform/main.d	Sun May 17 12:41:14 2009 +0000
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the demonstration applications 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$
+**
+****************************************************************************/
+
+import
+    pathdeform,
+    arthurstyle,
+    qt.gui.QApplication;
+
+
+int main(string[] args)
+{
+    //Q_INIT_RESOURCE(deform);
+
+    scope app = new QApplication(args);
+
+    bool smallScreen = false;
+    foreach (arg; args)
+    {
+        if (arg == "-small-screen")
+            smallScreen = true;
+    }
+
+    scope deformWidget = new PathDeformWidget(null, smallScreen);
+
+    QStyle arthurStyle = new ArthurStyle();
+    deformWidget.setWidgetStyle(arthurStyle);
+    auto widgets = deformWidget.findChildren!(QWidget);
+    foreach (w; widgets)
+        w.setStyle(arthurStyle);
+
+    if (smallScreen)
+        deformWidget.showFullScreen();
+    else
+        deformWidget.show();
+
+    return app.exec();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demos/deform/pathdeform.d	Sun May 17 12:41:14 2009 +0000
@@ -0,0 +1,722 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the demonstration applications 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$
+**
+****************************************************************************/
+
+import arthurwidgets,
+    qt.gui.QPainterPath,
+
+    qt.gui.QApplication,
+    qt.gui.QMouseEvent,
+    qt.core.QDateTime,
+    qt.core.QTimerEvent,
+    qt.core.QBasicTimer,
+    qt.gui.QLayout,
+    qt.gui.QLineEdit,
+    qt.gui.QPainter,
+    qt.gui.QSlider,
+    qt.gui.QLabel,
+    qt.gui.QDesktopWidget,
+    qt.gui.QGroupBox,
+    qt.gui.QPushButton,
+    qt.gui.QVBoxLayout,
+    qt.gui.QGridLayout,
+    qt.gui.QHBoxLayout,
+    qt.gui.QRadialGradient,
+    qt.opengl.QGLFormat,
+    tango.math.Math;
+
+class PathDeformControls : QWidget
+{
+    private PathDeformRenderer m_renderer;
+
+    mixin Signal!("okPressed");
+    mixin Signal!("quitPressed");
+
+    this(QWidget parent, PathDeformRenderer renderer, bool smallScreen)
+    {
+        super(parent);
+        m_renderer = renderer;
+
+        if (smallScreen)
+            layoutForSmallScreen();
+        else
+            layoutForDesktop();
+    }
+
+    void layoutForDesktop()
+    {
+        QGroupBox mainGroup = new QGroupBox(this);
+        mainGroup.setTitle(tr("Controls"));
+
+        QGroupBox radiusGroup = new QGroupBox(mainGroup);
+        radiusGroup.setTitle(tr("Lens Radius"));
+        QSlider radiusSlider = new QSlider(Qt.Horizontal, radiusGroup);
+        radiusSlider.setRange(15, 150);
+        radiusSlider.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed);
+
+        QGroupBox deformGroup = new QGroupBox(mainGroup);
+        deformGroup.setTitle(tr("Deformation"));
+        QSlider deformSlider = new QSlider(Qt.Horizontal, deformGroup);
+        deformSlider.setRange(-100, 100);
+        deformSlider.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed);
+
+        QGroupBox fontSizeGroup = new QGroupBox(mainGroup);
+        fontSizeGroup.setTitle(tr("Font Size"));
+        QSlider fontSizeSlider = new QSlider(Qt.Horizontal, fontSizeGroup);
+        fontSizeSlider.setRange(16, 200);
+        fontSizeSlider.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed);
+
+        QGroupBox textGroup = new QGroupBox(mainGroup);
+        textGroup.setTitle(tr("Text"));
+        QLineEdit textInput = new QLineEdit(textGroup);
+
+        QPushButton animateButton = new QPushButton(mainGroup);
+        animateButton.setText(tr("Animated"));
+        animateButton.setCheckable(true);
+
+        QPushButton showSourceButton = new QPushButton(mainGroup);
+        showSourceButton.setText(tr("Show Source"));
+
+        version (QT_OPENGL_SUPPORT)
+        {
+            QPushButton enableOpenGLButton = new QPushButton(mainGroup);
+            enableOpenGLButton.setText(tr("Use OpenGL"));
+            enableOpenGLButton.setCheckable(true);
+            enableOpenGLButton.setChecked(m_renderer.usesOpenGL());
+            if (!QGLFormat.hasOpenGL())
+                enableOpenGLButton.hide();
+        }
+
+        QPushButton whatsThisButton = new QPushButton(mainGroup);
+        whatsThisButton.setText(tr("What's This?"));
+        whatsThisButton.setCheckable(true);
+
+        mainGroup.setFixedWidth(180);
+
+        QVBoxLayout mainGroupLayout = new QVBoxLayout(mainGroup);
+        mainGroupLayout.addWidget(radiusGroup);
+        mainGroupLayout.addWidget(deformGroup);
+        mainGroupLayout.addWidget(fontSizeGroup);
+        mainGroupLayout.addWidget(textGroup);
+        mainGroupLayout.addWidget(animateButton);
+        mainGroupLayout.addStretch(1);
+        version (QT_OPENGL_SUPPORT)
+        {
+            mainGroupLayout.addWidget(enableOpenGLButton);
+        }
+        mainGroupLayout.addWidget(showSourceButton);
+        mainGroupLayout.addWidget(whatsThisButton);
+
+        QVBoxLayout radiusGroupLayout = new QVBoxLayout(radiusGroup);
+        radiusGroupLayout.addWidget(radiusSlider);
+
+        QVBoxLayout deformGroupLayout = new QVBoxLayout(deformGroup);
+        deformGroupLayout.addWidget(deformSlider);
+
+        QVBoxLayout fontSizeGroupLayout = new QVBoxLayout(fontSizeGroup);
+        fontSizeGroupLayout.addWidget(fontSizeSlider);
+
+        QVBoxLayout textGroupLayout = new QVBoxLayout(textGroup);
+        textGroupLayout.addWidget(textInput);
+
+        QVBoxLayout mainLayout = new QVBoxLayout(this);
+        mainLayout.addWidget(mainGroup);
+        mainLayout.setMargin(0);
+
+        radiusSlider.valueChanged.connect(&m_renderer.setRadius);
+        deformSlider.valueChanged.connect(&m_renderer.setIntensity);
+        fontSizeSlider.valueChanged.connect(&m_renderer.setFontSize);
+        animateButton.clicked.connect(&m_renderer.setAnimated);
+        version (QT_OPENGL_SUPPORT)
+        {
+            enableOpenGLButton.clicked.connect(&m_renderer.enableOpenGL);
+        }
+
+        textInput.textChanged.connect(&m_renderer.setText);
+        m_renderer.descriptionEnabledChanged.connect(&whatsThisButton.setChecked);
+        whatsThisButton.clicked.connect(&m_renderer.setDescriptionEnabled);
+        showSourceButton.clicked.connect(&m_renderer.showSource);
+
+        animateButton.animateClick();
+        deformSlider.setValue(80);
+        fontSizeSlider.setValue(120);
+        radiusSlider.setValue(100);
+        textInput.setText(tr("Qt"));
+    }
+
+    void layoutForSmallScreen()
+    {
+        QGroupBox mainGroup = new QGroupBox(this);
+        mainGroup.setTitle(tr("Controls"));
+
+        QLabel radiusLabel = new QLabel(mainGroup);
+        radiusLabel.setText(tr("Lens Radius:"));
+        QSlider radiusSlider = new QSlider(Qt.Horizontal, mainGroup);
+        radiusSlider.setRange(15, 150);
+        radiusSlider.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed);
+
+        QLabel deformLabel = new QLabel(mainGroup);
+        deformLabel.setText(tr("Deformation:"));
+        QSlider deformSlider = new QSlider(Qt.Horizontal, mainGroup);
+        deformSlider.setRange(-100, 100);
+        deformSlider.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed);
+
+        QLabel fontSizeLabel = new QLabel(mainGroup);
+        fontSizeLabel.setText(tr("Font Size:"));
+        QSlider fontSizeSlider = new QSlider(Qt.Horizontal, mainGroup);
+        fontSizeSlider.setRange(16, 200);
+        fontSizeSlider.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed);
+
+        QPushButton animateButton = new QPushButton(tr("Animated"), mainGroup);
+        animateButton.setCheckable(true);
+
+        version (QT_OPENGL_SUPPORT)
+        {
+            QPushButton enableOpenGLButton = new QPushButton(mainGroup);
+            enableOpenGLButton.setText(tr("Use OpenGL"));
+            enableOpenGLButton.setCheckable(true);
+            enableOpenGLButton.setChecked(m_renderer.usesOpenGL());
+            if (!QGLFormat.hasOpenGL())
+                enableOpenGLButton.hide();
+        }
+
+        QPushButton quitButton = new QPushButton(tr("Quit"), mainGroup);
+        QPushButton okButton = new QPushButton(tr("OK"), mainGroup);
+
+
+        QGridLayout mainGroupLayout = new QGridLayout(mainGroup);
+        mainGroupLayout.setMargin(0);
+        mainGroupLayout.addWidget(radiusLabel, 0, 0, Qt.AlignRight);
+        mainGroupLayout.addWidget(radiusSlider, 0, 1);
+        mainGroupLayout.addWidget(deformLabel, 1, 0, Qt.AlignRight);
+        mainGroupLayout.addWidget(deformSlider, 1, 1);
+        mainGroupLayout.addWidget(fontSizeLabel, 2, 0, Qt.AlignRight);
+        mainGroupLayout.addWidget(fontSizeSlider, 2, 1);
+        mainGroupLayout.addWidget(animateButton, 3,0, 1,2);
+        version (QT_OPENGL_SUPPORT)
+        {
+            mainGroupLayout.addWidget(enableOpenGLButton, 4,0, 1,2);
+        }
+
+        QVBoxLayout mainLayout = new QVBoxLayout(this);
+        mainLayout.addWidget(mainGroup);
+        mainLayout.addStretch(1);
+        mainLayout.addWidget(okButton);
+        mainLayout.addWidget(quitButton);
+
+        quitButton.clicked.connect(&emitQuitSignal);
+        okButton.clicked.connect(&emitOkSignal);
+        radiusSlider.valueChanged.connect(&m_renderer.setRadius);
+        deformSlider.valueChanged.connect(&m_renderer.setIntensity);
+        fontSizeSlider.valueChanged.connect(&m_renderer.setFontSize);
+        animateButton.clicked.connect(&m_renderer.setAnimated);
+        version (QT_OPENGL_SUPPORT)
+        {
+            enableOpenGLButton.clicked.connect(&m_renderer.enableOpenGL);
+        }
+
+
+        animateButton.animateClick();
+        deformSlider.setValue(80);
+        fontSizeSlider.setValue(120);
+
+        QRect screen_size = QApplication.desktop().screenGeometry();
+        radiusSlider.setValue(qMin(screen_size.width(), screen_size.height())/5);
+        m_renderer.setText(tr("Qt"));
+    }
+
+
+    void emitQuitSignal()
+    {   quitPressed.emit;  }
+
+    void emitOkSignal()
+    {   okPressed.emit;   }
+}
+
+
+class PathDeformWidget : QWidget
+{
+private:
+    PathDeformRenderer m_renderer;
+    PathDeformControls m_controls;
+
+public:
+    this(QWidget parent, bool smallScreen)
+    {
+        super(parent);
+
+        setWindowTitle(tr("Vector Deformation"));
+
+        m_renderer = new PathDeformRenderer(this, smallScreen);
+        m_renderer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding);
+
+        // Layouts
+        QHBoxLayout mainLayout = new QHBoxLayout(this);
+        mainLayout.addWidget(m_renderer);
+
+        m_controls = new PathDeformControls(null, m_renderer, smallScreen);
+        m_controls.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Minimum);
+
+        if (!smallScreen)
+            mainLayout.addWidget(m_controls);
+
+        m_renderer.loadSourceFile(":res/deform/pathdeform.d");
+        m_renderer.loadDescription(":res/deform/pathdeform.html");
+        m_renderer.setDescriptionEnabled(false);
+
+        m_renderer.clicked.connect(&showControls);
+        m_controls.okPressed.connect(&hideControls);
+
+        m_controls.quitPressed.connect(&QApplication.quit);
+    }
+
+    void showControls()
+    {
+        m_controls.showFullScreen;
+    }
+
+    void hideControls()
+    {
+        m_controls.hide;
+    }
+
+    void setWidgetStyle(QStyle style) // TODO: QWidget.setStyle is not virtual
+    {
+        super.setStyle(style);
+        if (m_controls)
+        {
+            m_controls.setStyle(style);
+
+            auto widgets = m_controls.findChildren!(QWidget);
+            foreach (w; widgets)
+                w.setStyle(style);
+        }
+    }
+}
+
+private QRect circle_bounds(QPointF center, qreal radius, qreal compensation)
+{
+    return new QRect(qRound(center.x() - radius - compensation),
+                 qRound(center.y() - radius - compensation),
+                 qRound((radius + compensation) * 2),
+                 qRound((radius + compensation) * 2));
+}
+
+enum
+{
+    LENS_EXTENT = 10
+}
+
+class PathDeformRenderer : ArthurFrame
+{
+private:
+    QBasicTimer m_repaintTimer;
+//     QBasicTimer m_fpsTimer;
+//     int m_fpsCounter;
+    QTime m_repaintTracker;
+
+    QPainterPath[] m_paths;
+    QPointF[] m_advances;
+    QRectF m_pathBounds;
+    string m_text;
+
+    QPixmap m_lens_pixmap;
+    QImage m_lens_image;
+
+    int m_fontSize;
+    bool m_animated;
+
+    qreal m_intensity;
+    qreal m_radius;
+    QPointF m_pos;
+    QPointF m_offset;
+    QPointF m_direction;
+    QPointF m_mousePress;
+    bool    m_mouseDrag;
+    bool    m_smallScreen;
+
+public:
+    mixin Signal!("clicked");
+
+    this(QWidget widget, bool smallScreen)
+    {
+        super(widget);
+        m_radius = 100;
+        m_pos = QPointF(m_radius, m_radius);
+        m_direction = QPointF(1, 1);
+        m_fontSize = 24;
+        m_animated = true;
+        m_repaintTimer.start(25, this);
+        m_repaintTracker.start();
+        m_intensity = 100;
+        m_smallScreen = smallScreen;
+
+//     m_fpsTimer.start(1000, this);
+//     m_fpsCounter = 0;
+
+        generateLensPixmap();
+    }
+
+    void setFontSize(int fontSize) { m_fontSize = fontSize; setText(m_text); }
+
+    override QSize sizeHint() { return QSize(600, 500); }
+
+    bool animated() { return m_animated; }
+    int radius() { return cast(int)m_radius; }
+    int fontSize() { return m_fontSize; }
+    int intensity() { return cast(int)m_intensity; }
+    string text() { return m_text; }
+
+
+    void setText(string text)
+    {
+        m_text = text;
+
+        auto f = new QFont("times new roman,utopia");
+        f.setStyleStrategy(QFont.ForceOutline);
+        f.setPointSize(m_fontSize);
+        f.setStyleHint(QFont.Times);
+
+        m_paths = null;
+        m_pathBounds = new QRectF();
+
+        QPointF advance;
+
+        auto path = new QPainterPath;
+        path.addText(advance, f, text);
+        m_pathBounds = m_pathBounds.united(path.boundingRect);
+        m_paths ~= path;
+
+        foreach (ref p; m_paths)
+            p = (new QMatrix(1, 0, 0, 1, -m_pathBounds.x(), -m_pathBounds.y())).map(path);
+
+        update;
+    }
+
+
+    void generateLensPixmap()
+    {
+        qreal rad = m_radius + LENS_EXTENT;
+
+        QRect bounds = circle_bounds(QPointF(), rad, 0);
+
+        QPainter painter = new QPainter;
+
+        if (preferImage()) {
+            m_lens_image = new QImage(bounds.size(), QImage.Format_ARGB32_Premultiplied);
+            m_lens_image.fill(0);
+            painter.begin(m_lens_image);
+        } else {
+            m_lens_pixmap = new QPixmap(bounds.size());
+            m_lens_pixmap.fill(new QColor(Qt.transparent));
+            painter.begin(m_lens_pixmap);
+        }
+
+        auto gr = new QRadialGradient(rad, rad, rad, 3 * rad / 5, 3 * rad / 5);
+        gr.setColorAt(0.0, new QColor(255, 255, 255, 191));
+        gr.setColorAt(0.2, new QColor(255, 255, 127, 191));
+        gr.setColorAt(0.9, new QColor(150, 150, 200, 63));
+        gr.setColorAt(0.95, new QColor(0, 0, 0, 127));
+        gr.setColorAt(1, new QColor(0, 0, 0, 0));
+        painter.setRenderHint(QPainter.Antialiasing);
+        painter.setBrush(gr);
+        painter.setPen(Qt.NoPen);
+        painter.drawEllipse(0, 0, bounds.width(), bounds.height());
+    }
+
+    void setAnimated(bool animated)
+    {
+        m_animated = animated;
+
+        if (m_animated) {
+    //         m_fpsTimer.start(1000, this);
+    //         m_fpsCounter = 0;
+            m_repaintTimer.start(25, this);
+            m_repaintTracker.start();
+        } else {
+    //         m_fpsTimer.stop();
+            m_repaintTimer.stop();
+        }
+    }
+
+    override void timerEvent(QTimerEvent e)
+    {
+
+        if (e.timerId == m_repaintTimer.timerId) {
+
+            if ((new QLineF(QPointF(0,0), m_direction)).length > 1)
+                m_direction *= 0.995;
+            qreal time = m_repaintTracker.restart();
+
+            QRect rectBefore = circle_bounds(m_pos, m_radius, m_fontSize);
+
+            qreal dx = m_direction.x();
+            qreal dy = m_direction.y();
+            if (time > 0) {
+                dx = dx * time * .1;
+                dy = dy * time * .1;
+            }
+
+            m_pos += QPointF(dx, dy);
+
+            if (m_pos.x() - m_radius < 0) {
+                m_direction.x = -m_direction.x;
+                m_pos.x = m_radius;
+            } else if (m_pos.x + m_radius > width) {
+                m_direction.x = -m_direction.x;
+                m_pos.x = width - m_radius;
+            }
+
+            if (m_pos.y - m_radius < 0) {
+                m_direction.y = -m_direction.y;
+                m_pos.y = m_radius;
+            } else if (m_pos.y + m_radius > height) {
+                m_direction.y = -m_direction.y;
+                m_pos.y = height - m_radius;
+            }
+
+            void noGLUpdate()
+            {
+                QRect rectAfter = circle_bounds(m_pos, m_radius, m_fontSize);
+                update(rectAfter.united(rectBefore));
+                QApplication.syncX();
+            }
+
+            version (QT_OPENGL_SUPPORT)
+            {
+                if (usesOpenGL()) {
+                    update;
+                }
+                else
+                    noGLUpdate;
+            }
+            else
+                noGLUpdate;
+        }
+    //     else if (e.timerId() == m_fpsTimer.timerId()) {
+    //         printf("fps: %d\n", m_fpsCounter);
+    //         emit frameRate(m_fpsCounter);
+    //         m_fpsCounter = 0;
+
+    //     }
+    }
+
+    override void mousePressEvent(QMouseEvent e)
+    {
+        setDescriptionEnabled(false);
+
+        m_repaintTimer.stop();
+        m_offset = QPointF();
+        if ((new QLineF(m_pos, QPointF(e.pos))).length <= m_radius)
+            m_offset = m_pos - QPointF(e.pos);
+
+        m_mousePress = e.pos;
+
+        // If we're not running in small screen mode, always assume we're dragging
+        m_mouseDrag = !m_smallScreen;
+
+        mouseMoveEvent(e);
+    }
+
+    override void mouseReleaseEvent(QMouseEvent e)
+    {
+        if (e.buttons() == Qt.NoButton && m_animated) {
+            m_repaintTimer.start(10, this);
+            m_repaintTracker.start();
+        }
+
+        if (!m_mouseDrag && m_smallScreen)
+            clicked.emit;
+    }
+
+    override void mouseMoveEvent(QMouseEvent e)
+    {
+        auto epos = QPointF(e.pos);
+
+        if (!m_mouseDrag && (new QLineF(m_mousePress, QPointF(e.pos))).length() > 25.0)
+            m_mouseDrag = true;
+
+        if (m_mouseDrag) {
+            QRect rectBefore = circle_bounds(m_pos, m_radius, m_fontSize);
+            if (e.type() == QEvent.MouseMove) {
+                QLineF line = new QLineF(m_pos, epos + m_offset);
+                line.setLength(line.length() * .1);
+                auto dir = QPointF(line.dx(), line.dy());
+                m_direction = (m_direction + dir) / 2;
+            }
+            m_pos = epos + m_offset;
+
+            void noGLUpdate()
+            {
+                QRect rectAfter = circle_bounds(m_pos, m_radius, m_fontSize);
+                update(rectBefore.united(rectAfter));
+            }
+
+            version (QT_OPENGL_SUPPORT)
+            {
+                if (usesOpenGL()) {
+                    update;
+                } else
+                    noGLUpdate;
+            }
+            else
+                noGLUpdate;
+        }
+    }
+
+    QPainterPath lensDeform(QPainterPath source, QPointF offset)
+    {
+        auto path = new QPainterPath;
+        path.addPath(source);
+
+        qreal flip = m_intensity / 100.0;
+
+        for (int i=0; i<path.elementCount; ++i) {
+            auto e = path.elementAt(i);
+
+            qreal x = e.x + offset.x();
+            qreal y = e.y + offset.y();
+
+            qreal dx = x - m_pos.x();
+            qreal dy = y - m_pos.y();
+            qreal len = m_radius - sqrt(dx * dx + dy * dy);
+
+            if (len > 0) {
+                path.setElementPositionAt(i,
+                                          x + flip * dx * len / m_radius,
+                                          y + flip * dy * len / m_radius);
+            } else {
+                path.setElementPositionAt(i, x, y);
+            }
+
+        }
+
+        return path;
+    }
+
+
+    override void paint(QPainter painter)
+    {
+        int pad_x = 5;
+        int pad_y = 5;
+
+        int skip_x = qRound(m_pathBounds.width() + pad_x + m_fontSize/2);
+        int skip_y = qRound(m_pathBounds.height() + pad_y);
+
+        painter.setPen(Qt.NoPen);
+        painter.setBrush(new QColor(Qt.black));
+
+        auto clip = painter.clipPath().boundingRect();
+
+        int overlap = pad_x / 2;
+
+        for (int start_y=0; start_y < height(); start_y += skip_y) {
+
+            if (start_y > clip.bottom())
+                break;
+
+            int start_x = -overlap;
+            for (; start_x < width(); start_x += skip_x) {
+
+                if (start_y + skip_y >= clip.top() &&
+                    start_x + skip_x >= clip.left() &&
+                    start_x <= clip.right()) {
+                    for (int i=0; i<m_paths.length; ++i) {
+                        QPainterPath path = lensDeform(m_paths[i], QPointF(start_x, start_y));
+                        painter.drawPath(path);
+                    }
+                }
+            }
+            overlap = skip_x - (start_x - width());
+
+        }
+
+        if (preferImage) {
+            painter.drawImage(m_pos - QPointF(m_radius + LENS_EXTENT, m_radius + LENS_EXTENT),
+                               m_lens_image);
+        } else {
+            painter.drawPixmap(m_pos - QPointF(m_radius + LENS_EXTENT, m_radius + LENS_EXTENT),
+                                m_lens_pixmap);
+        }
+    }
+
+
+    void setRadius(int radius)
+    {
+        qreal max = max(m_radius, cast(qreal)radius);
+        m_radius = radius;
+        generateLensPixmap();
+        if (!m_animated || m_radius < max) {
+
+            auto noGLUpdate = (){ update(circle_bounds(m_pos, max, m_fontSize)); };
+
+            version (QT_OPENGL_SUPPORT)
+            {
+                if (usesOpenGL())
+                    update();
+                else
+                    noGLUpdate();
+            }
+            else
+                noGLUpdate();
+        }
+    }
+
+    void setIntensity(int intensity)
+    {
+        m_intensity = intensity;
+        if (!m_animated) {
+
+            auto noGLUpdate = (){ update(circle_bounds(m_pos, m_radius, m_fontSize)); };
+
+            version (QT_OPENGL_SUPPORT)
+            {
+
+
+                if (usesOpenGL()) {
+                    update();
+                } else
+                    noGLUpdate();
+            }
+            else
+                noGLUpdate();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demos/shared/arthurstyle.d	Sun May 17 12:41:14 2009 +0000
@@ -0,0 +1,457 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the demonstration applications 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$
+**
+****************************************************************************/
+
+
+//import arthurwidgets;
+import
+    qt.gui.QLayout,
+    qt.gui.QWindowsStyle,
+    qt.gui.QPainter,
+    qt.gui.QPainterPath,
+    qt.gui.QPixmapCache,
+    qt.gui.QRadioButton,
+    qt.gui.QPushButton,
+    qt.gui.QGroupBox,
+    qt.gui.QLinearGradient,
+    qt.gui.QstyleOptionFrameV2,
+    qt.gui.QStyleOption;
+
+class ArthurStyle : QWindowsStyle
+{
+    QPixmap cached(string img)
+    {
+        QPixmap pm = new QPixmap;
+        if (QPixmapCache.find(img, pm))
+            return pm;
+
+        pm = QPixmap.fromImage(new QImage(img), Qt.OrderedDither | Qt.OrderedAlphaDither);
+        if (pm.isNull)
+            return new QPixmap;
+
+        QPixmapCache.insert(img, pm);
+        return pm;
+    }
+
+    this()
+    {
+        //Q_INIT_RESOURCE(shared);
+    }
+
+
+    void drawHoverRect(QPainter painter, QRect r)
+    {
+        qreal h = r.height();
+        qreal h2 = r.height() / 2.0;
+        QPainterPath path;
+        path.addRect(r.x() + h2, r.y() + 0, r.width() - h2 * 2, r.height());
+        path.addEllipse(r.x(), r.y(), h, h);
+        path.addEllipse(r.x() + r.width() - h, r.y(), h, h);
+        path.setFillRule(Qt.WindingFill);
+        painter.setPen(Qt.NoPen);
+        painter.setBrush(new QColor(191, 215, 191));
+        painter.setRenderHint(QPainter.Antialiasing);
+        painter.drawPath(path);
+    }
+
+
+    override void drawPrimitive(PrimitiveElement element, QStyleOption option,
+                                QPainter painter, QWidget widget)
+    {
+        assert(option);
+        switch (element)
+        {
+        case PE_FrameFocusRect:
+            break;
+
+        case PE_IndicatorRadioButton:
+            if (QStyleOptionButton button = cast(QStyleOptionButton)(option)) {
+                bool hover = (button.state & State_Enabled) && (button.state & State_MouseOver);
+                painter.save;
+                QPixmap radio;
+                if (hover)
+                    drawHoverRect(painter, widget.rect);
+
+                if (button.state & State_Sunken)
+                    radio = cached(":res/images/radiobutton-on.png");
+                else if (button.state & State_On)
+                    radio = cached(":res/images/radiobutton_on.png");
+                else
+                    radio = cached(":res/images/radiobutton_off.png");
+                painter.drawPixmap(button.rect.topLeft, radio);
+
+                painter.restore();
+            }
+            break;
+
+        case PE_PanelButtonCommand:
+            if (QStyleOptionButton button = cast(QStyleOptionButton)(option)) {
+                bool hover = (button.state & State_Enabled) && (button.state & State_MouseOver);
+
+                painter.save();
+                QPushButton pushButton = cast(QPushButton)(widget);
+                assert(pushButton);
+                auto parent = pushButton.parentWidget;
+                if (parent && cast(QGroupBox)(parent)) {
+                    auto lg = new QLinearGradient(0, 0, 0, parent.height);
+                    lg.setColorAt(0, new QColor(224,224,224));
+                    lg.setColorAt(1, new QColor(255,255,255));
+                    painter.setPen(Qt.NoPen);
+                    painter.setBrush(lg);
+                    painter.setBrushOrigin(QPoint() - widget.mapToParent(QPoint(0,0)));
+                    painter.drawRect(button.rect);
+                    painter.setBrushOrigin(0, 0);
+                }
+
+                bool down = (button.state & State_Sunken) || (button.state & State_On);
+
+                QPixmap left, right, mid;
+                if (down) {
+                    left = cached(":res/images/button_pressed_cap_left.png");
+                    right = cached(":res/images/button_pressed_cap_right.png");
+                    mid = cached(":res/images/button_pressed_stretch.png");
+                } else {
+                    left = cached(":res/images/button_normal_cap_left.png");
+                    right = cached(":res/images/button_normal_cap_right.png");
+                    mid = cached(":res/images/button_normal_stretch.png");
+                }
+                painter.drawPixmap(button.rect.topLeft, left);
+                painter.drawTiledPixmap(new QRect(button.rect.x + left.width,
+                                               button.rect.y,
+                                               button.rect.width - left.width - right.width,
+                                               left.height),
+                                         mid);
+                painter.drawPixmap(button.rect.x + button.rect.width - right.width,
+                                    button.rect.y,
+                                    right);
+                if (hover)
+                    painter.fillRect(widget.rect.adjusted(3,5,-3,-5), new QColor(31,127,31,63));
+                painter.restore;
+            }
+            break;
+
+        case PE_FrameGroupBox:
+            if (QStyleOptionFrameV2 group
+                    = cast(QStyleOptionFrameV2)(option)) {
+                auto r = group.rect;
+
+                painter.save();
+                int radius = 14;
+                int radius2 = radius*2;
+                QPainterPath clipPath;
+                clipPath.moveTo(radius, 0);
+                clipPath.arcTo(r.right() - radius2, 0, radius2, radius2, 90, -90);
+                clipPath.arcTo(r.right() - radius2, r.bottom() - radius2, radius2, radius2, 0, -90);
+                clipPath.arcTo(r.left(), r.bottom() - radius2, radius2, radius2, 270, -90);
+                clipPath.arcTo(r.left(), r.top(), radius2, radius2, 180, -90);
+                painter.setClipPath(clipPath);
+                QPixmap titleStretch = cached(":res/images/title_stretch.png");
+                QPixmap topLeft = cached(":res/images/groupframe_topleft.png");
+                QPixmap topRight = cached(":res/images/groupframe_topright.png");
+                QPixmap bottomLeft = cached(":res/images/groupframe_bottom_left.png");
+                QPixmap bottomRight = cached(":res/images/groupframe_bottom_right.png");
+                QPixmap leftStretch = cached(":res/images/groupframe_left_stretch.png");
+                QPixmap topStretch = cached(":res/images/groupframe_top_stretch.png");
+                QPixmap rightStretch = cached(":res/images/groupframe_right_stretch.png");
+                QPixmap bottomStretch = cached(":res/images/groupframe_bottom_stretch.png");
+                auto lg = new QLinearGradient(0, 0, 0, r.height());
+                lg.setColorAt(0, new QColor(224,224,224));
+                lg.setColorAt(1, new QColor(255,255,255));
+                painter.setPen(Qt.NoPen);
+                painter.setBrush(lg);
+                painter.drawRect(r.adjusted(0, titleStretch.height()/2, 0, 0));
+                painter.setClipping(false);
+
+                int topFrameOffset = titleStretch.height()/2 - 2;
+                painter.drawPixmap(r.topLeft() + QPoint(0, topFrameOffset), topLeft);
+                painter.drawPixmap(r.topRight() - QPoint(topRight.width()-1, 0)
+                                    + QPoint(0, topFrameOffset), topRight);
+                painter.drawPixmap(r.bottomLeft() - QPoint(0, bottomLeft.height()-1), bottomLeft);
+                painter.drawPixmap(r.bottomRight() - QPoint(bottomRight.width()-1,
+                                    bottomRight.height()-1), bottomRight);
+
+                QRect left = r;
+                left.setY(r.y() + topLeft.height() + topFrameOffset);
+                left.setWidth(leftStretch.width());
+                left.setHeight(r.height() - topLeft.height() - bottomLeft.height() - topFrameOffset);
+                painter.drawTiledPixmap(left, leftStretch);
+
+                QRect top = r;
+                top.setX(r.x() + topLeft.width());
+                top.setY(r.y() + topFrameOffset);
+                top.setWidth(r.width() - topLeft.width() - topRight.width());
+                top.setHeight(topLeft.height());
+                painter.drawTiledPixmap(top, topStretch);
+
+                QRect right = r;
+                right.setX(r.right() - rightStretch.width()+1);
+                right.setY(r.y() + topRight.height() + topFrameOffset);
+                right.setWidth(rightStretch.width());
+                right.setHeight(r.height() - topRight.height()
+                                - bottomRight.height() - topFrameOffset);
+                painter.drawTiledPixmap(right, rightStretch);
+
+                QRect bottom = r;
+                bottom.setX(r.x() + bottomLeft.width());
+                bottom.setY(r.bottom() - bottomStretch.height()+1);
+                bottom.setWidth(r.width() - bottomLeft.width() - bottomRight.width());
+                bottom.setHeight(bottomLeft.height());
+                painter.drawTiledPixmap(bottom, bottomStretch);
+                painter.restore();
+            }
+            break;
+
+        default:
+            QWindowsStyle.drawPrimitive(element, option, painter, widget);
+            break;
+        }
+        return;
+    }
+
+    override void drawComplexControl(ComplexControl control, QStyleOptionComplex option,
+                                         QPainter painter, QWidget widget)
+    {
+        switch (control) {
+        case CC_Slider:
+            if (QStyleOptionSlider slider = cast(QStyleOptionSlider)(option)) {
+                QRect groove = subControlRect(CC_Slider, option, SC_SliderGroove, widget);
+                QRect handle = subControlRect(CC_Slider, option, SC_SliderHandle, widget);
+
+                painter.save;
+
+                bool hover = (slider.state & State_Enabled) && (slider.state & State_MouseOver);
+                if (hover) {
+                    QRect moderated = widget.rect().adjusted(0, 4, 0, -4);
+                    drawHoverRect(painter, moderated);
+                }
+
+                if ((option.subControls & SC_SliderGroove) && groove.isValid()) {
+                    QPixmap grv = cached(":res/images/slider_bar.png");
+                    painter.drawPixmap(new QRect(groove.x() + 5, groove.y(),
+                                              groove.width() - 10, grv.height()),
+                                        grv);
+                }
+                if ((option.subControls & SC_SliderHandle) && handle.isValid()) {
+                    QPixmap hndl = cached(":res/images/slider_thumb_on.png");
+                    painter.drawPixmap(handle.topLeft(), hndl);
+                }
+
+                painter.restore();
+            }
+            break;
+        case CC_GroupBox:
+            if (QStyleOptionGroupBox groupBox
+                    = cast(QStyleOptionGroupBox)(option)) {
+                auto groupBoxCopy = new QStyleOptionGroupBox(groupBox);
+                groupBoxCopy.setSubControls = groupBoxCopy.subControls & ~SC_GroupBoxLabel;
+                QWindowsStyle.drawComplexControl(control, groupBoxCopy, painter, widget);
+
+                if (groupBox.subControls & SC_GroupBoxLabel) {
+                    QRect r = groupBox.rect;
+                    QPixmap titleLeft = cached(":res/images/title_cap_left.png");
+                    QPixmap titleRight = cached(":res/images/title_cap_right.png");
+                    QPixmap titleStretch = cached(":res/images/title_stretch.png");
+                    int txt_width = groupBox.fontMetrics.width(groupBox.text) + 20;
+                    painter.drawPixmap(r.center().x() - txt_width/2, 0, titleLeft);
+                    QRect tileRect = subControlRect(control, groupBox, SC_GroupBoxLabel, widget);
+                    painter.drawTiledPixmap(tileRect, titleStretch);
+                    painter.drawPixmap(tileRect.x() + tileRect.width(), 0, titleRight);
+                    int opacity = 31;
+                    painter.setPen(new QColor(0, 0, 0, opacity));
+                    painter.drawText(tileRect.translated(0, 1),
+                                      cast(int)(Qt.AlignVCenter | Qt.AlignHCenter), groupBox.text, null);
+                    painter.drawText(tileRect.translated(2, 1),
+                                      cast(int)(Qt.AlignVCenter | Qt.AlignHCenter), groupBox.text, null);
+                    painter.setPen(new QColor(0, 0, 0, opacity * 2));
+                    painter.drawText(tileRect.translated(1, 1),
+                                      cast(int)(Qt.AlignVCenter | Qt.AlignHCenter), groupBox.text, null);
+                    painter.setPen(new QColor(Qt.white));
+                    painter.drawText(tileRect, cast(int)(Qt.AlignVCenter | Qt.AlignHCenter), groupBox.text, null);
+                }
+            }
+            break;
+        default:
+            QWindowsStyle.drawComplexControl(control, option, painter, widget);
+            break;
+        }
+        return;
+    }
+
+    override QRect subControlRect(QStyle_ComplexControl control, QStyleOptionComplex option,
+                                      int sc, QWidget widget = null)
+    {
+        QRect rect;
+
+        auto subControl = cast(SubControl)sc;
+
+        switch (control) {
+        default:
+            rect = QWindowsStyle.subControlRect(control, option, subControl, widget);
+            break;
+        case CC_GroupBox:
+            if (QStyleOptionGroupBox group
+                    = cast(QStyleOptionGroupBox)(option)) {
+                switch (subControl) {
+                default:
+                    rect = QWindowsStyle.subControlRect(control, option, subControl, widget);
+                    break;
+                case SC_GroupBoxContents:
+                    rect = QWindowsStyle.subControlRect(control, option, subControl, widget);
+                    rect.adjust(0, -8, 0, 0);
+                    break;
+                case SC_GroupBoxFrame:
+                    rect = group.rect;
+                    break;
+                case SC_GroupBoxLabel:
+                    QPixmap titleLeft = cached(":res/images/title_cap_left.png");
+                    QPixmap titleRight = cached(":res/images/title_cap_right.png");
+                    QPixmap titleStretch = cached(":res/images/title_stretch.png");
+                    int txt_width = group.fontMetrics.width(group.text) + 20;
+                    rect = new QRect(group.rect.center().x() - txt_width/2 + titleLeft.width(), 0,
+                                 txt_width - titleLeft.width() - titleRight.width(),
+                                 titleStretch.height());
+                    break;
+                }
+            }
+            break;
+        }
+
+        if (control == CC_Slider && subControl == SC_SliderHandle) {
+            rect.setWidth(13);
+            rect.setHeight(27);
+        } else if (control == CC_Slider && subControl == SC_SliderGroove) {
+            rect.setHeight(9);
+            rect.moveTop(27/2 - 9/2);
+        }
+        return rect;
+    }
+
+    override QSize sizeFromContents(ContentsType type, QStyleOption option,
+                                        QSize size, QWidget widget)
+    {
+        QSize newSize = QWindowsStyle.sizeFromContents(type, option, size, widget);
+
+        switch (type) {
+        case CT_RadioButton:
+            newSize += QSize(20, 0);
+            break;
+
+        case CT_PushButton:
+            newSize.setHeight(26);
+            break;
+
+        case CT_Slider:
+            newSize.setHeight(27);
+            break;
+
+        default:
+            break;
+        }
+
+        return newSize;
+    }
+
+    override int pixelMetric(PixelMetric pm, QStyleOption opt, QWidget widget)
+    {
+        if (pm == PM_SliderLength)
+            return 13;
+        return QWindowsStyle.pixelMetric(pm, opt, widget);
+    }
+
+    override void polish(QWidget widget)
+    {
+        if (widget.layout() && cast(QGroupBox)(widget)) {
+            if (widget.findChildren!(QGroupBox).length == 0) {
+                widget.layout().setWidgetSpacing(0); // Why setSpacing was renamed to setWidgetSpacing?
+                widget.layout().setMargin(12);
+            } else {
+                widget.layout().setMargin(13);
+            }
+        }
+
+        if (cast(QPushButton)(widget)
+            || cast(QRadioButton)(widget)
+            || cast(QSlider)(widget)) {
+            widget.setAttribute(Qt.WA_Hover);
+        }
+
+        QPalette pal = widget.palette();
+        if (widget.isWindow()) {
+            pal.setColor(QPalette.Window, new QColor(241, 241, 241));
+            widget.setPalette(pal);
+        }
+
+    }
+
+    override void unpolish(QWidget widget)
+    {
+        if (cast(QPushButton)(widget)
+            || cast(QRadioButton)(widget)
+            || cast(QSlider)(widget)) {
+            widget.setAttribute(Qt.WA_Hover, false);
+        }
+    }
+
+    override void polish(QPalette palette)
+    {
+        palette.setColor(QPalette.Window, new QColor(241, 241, 241));
+    }
+
+    override QRect subElementRect(SubElement element, QStyleOption option, QWidget widget)
+    {
+        QRect r;
+        switch(element) {
+        case SE_RadioButtonClickRect:
+            r = widget.rect();
+            break;
+        case SE_RadioButtonContents:
+            r = widget.rect().adjusted(20, 0, 0, 0);
+            break;
+        default:
+            r = QWindowsStyle.subElementRect(element, option, widget);
+            break;
+        }
+
+        if (cast(QRadioButton)(widget))
+            r = r.adjusted(5, 0, -5, 0);
+
+        return r;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demos/shared/arthurwidgets.d	Sun May 17 12:41:14 2009 +0000
@@ -0,0 +1,414 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the demonstration applications 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$
+**
+****************************************************************************/
+
+
+import qt.core.QFile,
+    qt.gui.QApplication,
+    qt.gui.QPainter,
+    qt.gui.QPainterPath,
+    qt.gui.QPixmapCache,
+    qt.gui.QTextDocument,
+    qt.gui.QAbstractTextDocumentLayout,
+    qt.gui.QLinearGradient,
+
+    qt.gui.QTextBrowser,
+    qt.gui.QBoxLayout,
+    qt.opengl.QGL,
+    qt.Signal,
+    tango.text.Util;
+
+import tango.text.Regex : Regex;
+
+//#include <private/qpixmapdata_p.h>
+
+//extern QPixmap cached(const QString &img);
+
+version (QT_OPENGL_SUPPORT)
+{
+    import qt.opengl.QGLWidget;
+    class GLWidget : QGLWidget
+    {
+        this(QWidget parent)
+        {
+            super(new QGLFormat(QGL.SampleBuffers), parent);
+        }
+
+        void disableAutoBufferSwap() { setAutoBufferSwap(false); }
+        override void paintEvent(QPaintEvent) { parentWidget().update(); }
+    }
+}
+
+class ArthurFrame : QWidget
+{
+protected:
+
+    version(QT_OPENGL_SUPPORT)
+    {
+        GLWidget glw;
+        bool m_use_opengl;
+    }
+
+    QPixmap m_tile;
+
+    bool m_show_doc;
+    bool m_prefer_image;
+    QTextDocument m_document;
+
+    string m_sourceFileName;
+
+public:
+    mixin Signal!("descriptionEnabledChanged", bool);
+
+    bool preferImage() { return m_prefer_image; }
+
+    void paint(QPainter) {}
+
+    this(QWidget parent)
+    {
+        super(parent);
+
+        version (QT_OPENGL_SUPPORT)
+        {
+            QGLFormat f = QGLFormat.defaultFormat();
+            f.setSampleBuffers(true);
+            f.setStencil(true);
+            f.setAlpha(true);
+            f.setAlphaBufferSize(8);
+            QGLFormat.setDefaultFormat(f);
+        }
+
+        m_tile = new QPixmap(128, 128);
+        m_tile.fill(new QColor(Qt.white));
+        scope pt = new QPainter(m_tile);
+        auto color = new QColor(230, 230, 230);
+        pt.fillRect(0, 0, 64, 64, color);
+        pt.fillRect(64, 64, 64, 64, color);
+        pt.end();
+
+    //     QPalette pal = palette();
+    //     pal.setBrush(backgroundRole(), m_tile);
+    //     setPalette(pal);
+
+        version (Q_WS_X11)
+        {
+            auto xRenderPixmap = new QPixmap(1, 1);
+            m_prefer_image = xRenderPixmap.pixmapData().classId() == QPixmapData.X11Class && !xRenderPixmap.x11PictureHandle();
+        }
+    }
+
+    version (QT_OPENGL_SUPPORT)
+    {
+        void enableOpenGL(bool use_opengl)
+        {
+            m_use_opengl = use_opengl;
+
+            if (!glw) {
+                glw = new GLWidget(this);
+                glw.setAutoFillBackground(false);
+                glw.disableAutoBufferSwap();
+                QApplication.postEvent(this, new QResizeEvent(size(), size()));
+            }
+
+            if (use_opengl) {
+                glw.show();
+            } else {
+                glw.hide();
+            }
+
+            update();
+        }
+
+        bool usesOpenGL() { return m_use_opengl; }
+        QGLWidget glWidget(){ return glw; }
+    }
+
+    override void paintEvent(QPaintEvent e)
+    {
+        version (Q_WS_QWS)
+            static QPixmap static_image;
+        else
+            static QImage static_image;
+
+        auto painter = new QPainter;
+
+        version (QT_OPENGL_SUPPORT)
+            auto prefImage = preferImage && !m_use_opengl;
+        else
+            auto prefImage = preferImage;
+
+        if (prefImage) {
+            if (!static_image || static_image.size() != size()) {
+                delete static_image;
+                version (Q_WS_QWS)
+                    static_image = new QPixmap(size());
+                else
+                    static_image = new QImage(size(), QImage.Format_RGB32);
+            }
+            painter.begin(static_image);
+
+            int o = 10;
+
+            QBrush bg = palette().brush(QPalette.Window);
+            painter.fillRect(0, 0, o, o, bg);
+            painter.fillRect(width() - o, 0, o, o, bg);
+            painter.fillRect(0, height() - o, o, o, bg);
+            painter.fillRect(width() - o, height() - o, o, o, bg);
+        } else {
+            version (QT_OPENGL_SUPPORT)
+            {
+                if (m_use_opengl) {
+                    painter.begin(glw);
+                    painter.fillRect(new QRectF(0, 0, glw.width(), glw.height()), palette().color(backgroundRole()));
+                } else {
+                    painter.begin(this);
+                }
+            }
+            else
+                painter.begin(this);
+        }
+
+        painter.setClipRect(e.rect());
+
+        painter.setRenderHint(QPainter.Antialiasing);
+
+        auto clipPath = new QPainterPath;
+
+        QRect r = rect();
+        qreal left = r.x() + 1;
+        qreal top = r.y() + 1;
+        qreal right = r.right();
+        qreal bottom = r.bottom();
+        qreal radius2 = 8 * 2;
+
+        clipPath.moveTo(right - radius2, top);
+        clipPath.arcTo(right - radius2, top, radius2, radius2, 90, -90);
+        clipPath.arcTo(right - radius2, bottom - radius2, radius2, radius2, 0, -90);
+        clipPath.arcTo(left, bottom - radius2, radius2, radius2, 270, -90);
+        clipPath.arcTo(left, top, radius2, radius2, 180, -90);
+        clipPath.closeSubpath();
+
+        painter.save();
+        painter.setClipPath(clipPath, Qt.IntersectClip);
+
+        painter.drawTiledPixmap(rect(), m_tile);
+
+        // client painting
+
+        paint(painter);
+        painter.restore();
+
+        painter.save();
+        if (m_show_doc)
+            paintDescription(painter);
+        painter.restore();
+
+        int level = 180;
+        painter.setPen(new QPen(new QBrush(new QColor(level, level, level)), 2));
+        painter.setBrush(Qt.NoBrush);
+        painter.drawPath(clipPath);
+
+        if (prefImage) {
+            painter.end();
+            painter.begin(this);
+            version (Q_WS_QWS)
+                painter.drawPixmap(e.rect(), static_image, e.rect());
+            else
+                painter.drawImage(e.rect(), static_image, e.rect());
+        }
+
+        // TODO: this sucks
+        version (QT_OPENGL_SUPPORT) {
+            if (m_use_opengl && (inherits("PathDeformRenderer") || inherits("PathStrokeRenderer") || inherits("CompositionRenderer") || m_show_doc))
+                glw.swapBuffers();
+        }
+    }
+
+    void resizeEvent(QResizeEvent e)
+    {
+        version (QT_OPENGL_SUPPORT)
+        {
+            if (glw)
+                glw.setGeometry(0, 0, e.size().width()-1, e.size().height()-1);
+        }
+        super.resizeEvent(e);
+    }
+
+    void setDescriptionEnabled(bool enabled)
+    {
+        if (m_show_doc != enabled) {
+            m_show_doc = enabled;
+            descriptionEnabledChanged.emit(m_show_doc);
+            update();
+        }
+    }
+
+    void loadDescription(string fileName)
+    {
+        auto textFile = new QFile(fileName);
+        string text;
+        if (!textFile.open(QFile.ReadOnly))
+            text = "Unable to load resource file: " ~ fileName;
+        else
+            text = textFile.readAll().toString; // TODO: excessive copying
+        setDescription(text);
+    }
+
+    void setDescription(string text)
+    {
+        m_document = new QTextDocument(this);
+        m_document.setHtml(text);
+    }
+
+    void paintDescription(QPainter painter)
+    {
+        if (!m_document)
+            return;
+
+        int pageWidth = qMax(width() - 100, 100);
+        int pageHeight = qMax(height() - 100, 100);
+        if (pageWidth != m_document.pageSize().width()) {
+            m_document.setPageSize(QSizeF(pageWidth, pageHeight));
+        }
+
+        auto textRect = new QRect(width() / 2 - pageWidth / 2,
+                       height() / 2 - pageHeight / 2,
+                       pageWidth,
+                       pageHeight);
+        int pad = 10;
+        QRect clearRect = textRect.adjusted(-pad, -pad, pad, pad);
+        painter.setPen(Qt.NoPen);
+        painter.setBrush(new QColor(0, 0, 0, 63));
+        int shade = 10;
+        painter.drawRect(clearRect.x() + clearRect.width() + 1,
+                          clearRect.y() + shade,
+                          shade,
+                          clearRect.height() + 1);
+        painter.drawRect(clearRect.x() + shade,
+                          clearRect.y() + clearRect.height() + 1,
+                          clearRect.width() - shade + 1,
+                          shade);
+
+        painter.setRenderHint(QPainter.Antialiasing, false);
+        painter.setBrush(new QColor(255, 255, 255, 220));
+        painter.setPen(new QColor(Qt.black));
+        painter.drawRect(clearRect);
+
+        painter.setClipRect(textRect, Qt.IntersectClip);
+        painter.translate(textRect.topLeft());
+
+        auto ctx = new QAbstractTextDocumentLayout_PaintContext;
+
+        auto g = new QLinearGradient(0, 0, 0, textRect.height());
+        g.setColorAt(0, new QColor(Qt.black));
+        g.setColorAt(0.9, new QColor(Qt.black));
+        g.setColorAt(1, new QColor(Qt.transparent));
+
+        QPalette pal = palette();
+        pal.setBrush(QPalette.Text, new QBrush(g));
+
+        ctx.setPalette(pal);
+        ctx.setClip(new QRectF(0, 0, textRect.width(), textRect.height()));
+        m_document.documentLayout().draw(painter, ctx);
+    }
+
+    void loadSourceFile(string sourceName)
+    {
+        m_sourceFileName = sourceName;
+    }
+
+    void showSource()
+    {
+        // Check for existing source
+        if (findChild!(QTextBrowser))
+            return;
+
+        string contents;
+        if (!m_sourceFileName.length) {
+            contents = "No source for widget: " ~ objectName();
+        } else {
+            auto f = new QFile(m_sourceFileName);
+            if (!f.open(QFile.ReadOnly))
+                contents = "Could not open file: " ~ m_sourceFileName;
+            else
+                contents = f.readAll.toString;
+        }
+
+        contents = contents.substitute("&", "&amp;");
+        contents = contents.substitute("<", "&lt;");
+        contents = contents.substitute(">", "&gt;");
+
+        static const string[] keywords =
+            ["for ", "if ", "switch ", " int ", "#include ", "const"
+                , "void ", "uint ", "case ", "double ", "#define ", "static"
+                , "new", "this"];
+
+        foreach (keyword; keywords)
+            contents = contents.substitute(keyword, "<font color=olive>" ~ keyword ~ "</font>");
+        contents = contents.substitute("(int ", "(<font color=olive><b>int </b></font>");
+
+        static const string[] ppKeywords =
+            ["#ifdef", "#ifndef", "#if", "#endif", "#else"];
+
+        foreach (keyword; ppKeywords)
+            contents = contents.substitute(keyword, "<font color=navy>" ~ keyword ~ "</font>");
+
+        auto ddRe = new Regex("(\\d\\d?)");
+        contents = ddRe.replaceAll(contents, "<font color=navy>\\1</font>");
+
+        auto commentRe = new Regex("(//.+?)\\n");
+        contents = commentRe.replaceAll(contents, "<font color=red>\\1</font>\n");
+
+        auto stringLiteralRe = new Regex("(\".+?\")");
+        contents = stringLiteralRe.replaceAll(contents, "<font color=green>\\1</font>");
+
+        auto html = contents.dup;
+        html = "<html><pre>" ~ html ~ "</pre></html>";
+
+        QTextBrowser sourceViewer = new QTextBrowser(null);
+        sourceViewer.setWindowTitle("Source: " ~ m_sourceFileName[5..$]);
+        sourceViewer.setParent(this, Qt.Dialog);
+        sourceViewer.setAttribute(Qt.WA_DeleteOnClose);
+        sourceViewer.setLineWrapMode(QTextEdit.NoWrap);
+        sourceViewer.setHtml(html);
+        sourceViewer.resize(600, 600);
+        sourceViewer.show();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/demos/shared/hoverpoints.d	Sun May 17 12:41:14 2009 +0000
@@ -0,0 +1,439 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the demonstration applications 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$
+**
+****************************************************************************/
+
+version (QT_OPENGL_SUPPORT)
+    import qt.opengl.QGLWidget;
+
+version (D_Version2) {}
+else
+    import tango.core.Array : sort;
+
+import
+    qt.gui.QWidget,
+    qt.qtd.Array,
+    arthurwidgets;
+
+final class HoverPoints : QObject
+{
+public:
+    enum PointShape {
+        CircleShape,
+        RectangleShape
+    }
+
+    enum LockType {
+        LockToLeft   = 0x01,
+        LockToRight  = 0x02,
+        LockToTop    = 0x04,
+        LockToBottom = 0x08
+    }
+
+    enum SortType {
+        NoSort,
+        XSort,
+        YSort
+    }
+
+    enum ConnectionType {
+        NoConnection,
+        LineConnection,
+        CurveConnection
+    }
+
+private:
+    QWidget m_widget;
+
+    QPolygonF m_points;
+    QRectF m_bounds;
+    PointShape m_shape;
+    SortType m_sortType;
+    ConnectionType m_connectionType;
+
+    uint[] m_locks;
+
+    QSizeF m_pointSize;
+    int m_currentIndex;
+    bool m_editable;
+    bool m_enabled;
+
+    QPen m_pointPen;
+    QBrush m_pointBrush;
+    QPen m_connectionPen;
+
+public:
+    mixin Signal!("pointsChanged", QPolygonF /*points*/);
+
+    this(QWidget widget, PointShape shape)
+    {
+        super(widget);
+
+        m_widget = widget;
+        widget.installEventFilter(this);
+
+        m_connectionType = ConnectionType.CurveConnection;
+        m_sortType = SortType.NoSort;
+        m_shape = shape;
+        m_pointPen = new QPen(new QBrush(new QColor(255, 255, 255, 191)), 1);
+        m_connectionPen = new QPen(new QBrush(new QColor(255, 255, 255, 127)), 2);
+        m_pointBrush = new QBrush(new QColor(191, 191, 191, 127));
+        m_pointSize = QSizeF(11, 11);
+        m_currentIndex = -1;
+        m_editable = true;
+        m_enabled = true;
+
+        pointsChanged.connect(&m_widget.update);
+    }
+
+    void setBoundingRect(QRectF boundingRect) { m_bounds = boundingRect; }
+
+
+    QRectF pointBoundingRect(int i)
+    {
+        QPointF p = m_points.at(i);
+        qreal w = m_pointSize.width();
+        qreal h = m_pointSize.height();
+        qreal x = p.x() - w / 2;
+        qreal y = p.y() - h / 2;
+        return new QRectF(x, y, w, h);
+    }
+
+    QRectF boundingRect()
+    {
+        if (m_bounds.isEmpty())
+            return new QRectF(m_widget.rect());
+        else
+            return m_bounds;
+    }
+
+    QPolygonF points() { return m_points; }
+
+    QSizeF pointSize() { return m_pointSize; }
+    void setPointSize(QSizeF size) { m_pointSize = size; }
+
+    SortType sortType() { return m_sortType; }
+    void setSortType(SortType sortType) { m_sortType = sortType; }
+
+    ConnectionType connectionType() { return m_connectionType; }
+    void setConnectionType(ConnectionType connectionType) { m_connectionType = connectionType; }
+
+    void setConnectionPen(QPen pen) { m_connectionPen = pen; }
+    void setShapePen(QPen pen) { m_pointPen = pen; }
+    void setShapeBrush(QBrush brush) { m_pointBrush = brush; }
+
+    void setPointLock(int pos, LockType lock) { m_locks[pos] = lock; }
+
+    void setEditable(bool editable) { m_editable = editable; }
+    bool editable() { return m_editable; }
+
+    void setEnabled(bool enabled)
+    {
+        if (m_enabled != enabled) {
+            m_enabled = enabled;
+            m_widget.update();
+        }
+    }
+
+
+    override bool eventFilter(QObject object, QEvent event)
+    {
+        if ((object == m_widget) && m_enabled) {
+        switch (event.type()) {
+
+            case QEvent.MouseButtonPress:
+            {
+                QMouseEvent me = cast(QMouseEvent) event;
+
+                QPointF clickPos = me.pos();
+                int index = -1;
+                for (int i=0; i<m_points.size(); ++i) {
+                    auto path = new QPainterPath;
+                    if (m_shape == PointShape.CircleShape)
+                        path.addEllipse(pointBoundingRect(i));
+                    else
+                        path.addRect(pointBoundingRect(i));
+
+                    if (path.contains(clickPos)) {
+                        index = i;
+                        break;
+                    }
+                }
+
+                if (me.button() == Qt.LeftButton) {
+                    if (index == -1) {
+                        if (!m_editable)
+                            return false;
+                        int pos = 0;
+                        // Insert sort for x or y
+                        if (m_sortType == SortType.XSort) {
+                            for (int i=0; i<m_points.size(); ++i)
+                                if (m_points.at(i).x() > clickPos.x()) {
+                                    pos = i;
+                                    break;
+                                }
+                        } else if (m_sortType == SortType.YSort) {
+                            for (int i=0; i<m_points.size(); ++i)
+                                if (m_points.at(i).y() > clickPos.y()) {
+                                    pos = i;
+                                    break;
+                                }
+                        }
+
+                        // TODO: implement QPoligon(F).insert
+                        auto tmpPoints = m_points.toList;
+                        tmpPoints.insert(pos, clickPos);
+                        m_points = new QPolygonF(tmpPoints);
+
+                        m_locks.insert(pos, 0u);
+                        m_currentIndex = pos;
+                        firePointChange();
+                    } else {
+                        m_currentIndex = index;
+                    }
+                    return true;
+
+                } else if (me.button() == Qt.RightButton) {
+                    if ((index >= 0) && m_editable) {
+                        if (m_locks[index] == 0) {
+                            m_locks.removeAt(index);
+                            m_points.remove(index);
+                        }
+                        firePointChange();
+                        return true;
+                    }
+                }
+
+            }
+            break;
+
+            case QEvent.MouseButtonRelease:
+                m_currentIndex = -1;
+                break;
+
+            case QEvent.MouseMove:
+                if (m_currentIndex >= 0)
+                    movePoint(m_currentIndex, QPointF((cast(QMouseEvent)event).pos));
+                break;
+
+            case QEvent.Resize:
+            {
+                QResizeEvent e = cast(QResizeEvent) event;
+                if (e.oldSize().width() == 0 || e.oldSize().height() == 0)
+                    break;
+                qreal stretch_x = e.size().width() / cast(qreal)e.oldSize.width;
+                qreal stretch_y = e.size().height() / cast(qreal)e.oldSize.height;
+                for (int i=0; i<m_points.size(); ++i) {
+                    QPointF p = m_points.at(i);
+                    movePoint(i, QPointF(p.x() * stretch_x, p.y() * stretch_y), false);
+                }
+
+                firePointChange();
+                break;
+            }
+
+            case QEvent.Paint:
+            {
+                QWidget that_widget = m_widget;
+                m_widget = null;
+                QApplication.sendEvent(object, event);
+                m_widget = that_widget;
+                paintPoints();
+                version (QT_OPENGL_SUPPORT)
+                {
+                    ArthurFrame af = cast(ArthurFrame)(that_widget);
+                    if (af && af.usesOpenGL())
+                        af.glWidget().swapBuffers();
+                }
+
+                return true;
+            }
+            default:
+                break;
+            }
+        }
+
+        return false;
+    }
+
+
+    void paintPoints()
+    {
+        scope p = new QPainter;
+        version (QT_OPENGL_SUPPORT)
+        {
+            ArthurFrame af = cast(ArthurFrame)(m_widget);
+            if (af && af.usesOpenGL())
+                p.begin(af.glWidget());
+            else
+                p.begin(m_widget);
+        }
+        else
+            p.begin(m_widget);
+
+        p.setRenderHint(QPainter.Antialiasing);
+
+        if (m_connectionPen.style() != Qt.NoPen && m_connectionType != ConnectionType.NoConnection) {
+            p.setPen(m_connectionPen);
+
+            if (m_connectionType == ConnectionType.CurveConnection) {
+                auto path = new QPainterPath;
+                path.moveTo(m_points.at(0));
+                for (int i=1; i<m_points.size(); ++i) {
+                    QPointF p1 = m_points.at(i-1);
+                    QPointF p2 = m_points.at(i);
+                    qreal distance = p2.x() - p1.x();
+
+                    path.cubicTo(p1.x() + distance / 2, p1.y(),
+                                 p1.x() + distance / 2, p2.y(),
+                                 p2.x(), p2.y());
+                }
+                p.drawPath(path);
+            } else {
+                p.drawPolyline(m_points);
+            }
+        }
+
+        p.setPen(m_pointPen);
+        p.setBrush(m_pointBrush);
+
+        for (int i=0; i<m_points.size(); ++i) {
+            QRectF bounds = pointBoundingRect(i);
+            if (m_shape == PointShape.CircleShape)
+                p.drawEllipse(bounds);
+            else
+                p.drawRect(bounds);
+        }
+    }
+
+
+    void setPoints(QPolygonF points)
+    {
+        delete m_points;
+        for (int i=0; i<points.size; ++i)
+            m_points.append(bound_point(points.at(i), boundingRect(), 0));
+
+        delete m_locks;
+        if (m_points.size > 0) {
+            m_locks.length = m_points.size;
+
+            m_locks[] = 0;
+        }
+    }
+
+    void movePoint(int index, QPointF point, bool emitUpdate = true)
+    {
+        m_points.replace(index, bound_point(point, boundingRect(), m_locks[index]));
+        if (emitUpdate)
+            firePointChange();
+    }
+
+    void firePointChange()
+    {
+    //    printf("HoverPoints.firePointChange(), current=%d\n", m_currentIndex);
+
+        if (m_sortType != SortType.NoSort) {
+
+            QPointF oldCurrent;
+            if (m_currentIndex != -1) {
+                oldCurrent = m_points.at(m_currentIndex);
+            }
+
+            if (m_sortType == SortType.XSort)
+            {
+                auto tmpPoints = m_points.toList;
+                sort(tmpPoints, &x_less_than);
+                m_points = new QPolygonF(tmpPoints);
+            }
+            else if (m_sortType == SortType.YSort)
+            {
+                auto tmpPoints = m_points.toList;
+                sort(tmpPoints, &y_less_than);
+                m_points = new QPolygonF(tmpPoints);
+            }
+
+            // Compensate for changed order...
+            if (m_currentIndex != -1) {
+                for (int i=0; i<m_points.size; ++i) {
+                    if (m_points.at(i) == oldCurrent) {
+                        m_currentIndex = i;
+                        break;
+                    }
+                }
+            }
+
+    //         printf(" - firePointChange(), current=%d\n", m_currentIndex);
+        }
+
+    //     for (int i=0; i<m_points.size(); ++i) {
+    //         printf(" - point(%2d)=[%.2f, %.2f], lock=%d\n",
+    //                i, m_points.at(i).x(), m_points.at(i).y(), m_locks.at(i));
+    //     }
+
+        pointsChanged.emit(m_points);
+    }
+}
+
+private QPointF bound_point(QPointF point, QRectF bounds, int lock)
+{
+    QPointF p = point;
+
+    qreal left = bounds.left();
+    qreal right = bounds.right();
+    qreal top = bounds.top();
+    qreal bottom = bounds.bottom();
+
+    if (p.x() < left || (lock & HoverPoints.LockType.LockToLeft)) p.x = left;
+    else if (p.x() > right || (lock & HoverPoints.LockType.LockToRight)) p.x = right;
+
+    if (p.y() < top || (lock & HoverPoints.LockType.LockToTop)) p.y = top;
+    else if (p.y() > bottom || (lock & HoverPoints.LockType.LockToBottom)) p.y = bottom;
+
+    return p;
+}
+
+private bool x_less_than(QPointF p1, QPointF p2)
+{
+    return p1.x() < p2.x();
+}
+
+private bool y_less_than(QPointF p1, QPointF p2)
+{
+    return p1.y() < p2.y();
+}