/****************************************************************************
**
** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved.
**
** This file is part of Qt Jambi.
**
** ** This file may be used under the terms of the GNU General Public
** License version 2.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 GNU
** General Public Licensing requirements will be met:
** http://www.trolltech.com/products/qt/opensource.html
**
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://www.trolltech.com/products/qt/licensing.html or contact the
** sales department at sales@trolltech.com.

**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
****************************************************************************/

package com.trolltech.demos;

import com.trolltech.examples.QtJambiExample;
import com.trolltech.qt.core.*;
import com.trolltech.qt.gui.*;

import java.util.*;

class PainterPathElements
{
    public QPainterPath path;
    public QPainterPath_Element elements[];
}

class PathDeformRenderer extends ArthurFrame
{
    QBasicTimer m_repaintTimer = new QBasicTimer();
    QTime m_repaintTracker = new QTime();
    List<PainterPathElements> m_paths = new LinkedList<PainterPathElements>();
    List<QPointF> m_advances = new LinkedList<QPointF>();
    QRectF m_pathBounds = new QRectF();
    String m_text = "";
    QPixmap m_lens_pixmap = new QPixmap();
    QImage m_lens_image = new QImage();
    int m_fontSize = 0;
    boolean m_animated = false;
    double m_intensity = 0.0;
    double m_radius = 0.0;
    QPointF m_pos = new QPointF();
    QPointF m_offset = new QPointF();
    QPointF m_direction = new QPointF();
    boolean m_textDirty = true;

    public PathDeformRenderer(QWidget parent)
    {
        super(parent);

        m_radius = 100;
        m_pos = new QPointF(m_radius, m_radius);
        m_direction = new QPointF(1, 1);
        m_fontSize = 24;
        m_animated = true;
        m_repaintTimer.start(25, this);
        m_repaintTracker.start();
        m_intensity = 1;

        generateLensPixmap();
    }

    public void setFontSize(int fontSize) { m_fontSize = fontSize; setText(m_text); }
    @Override
    public QSize sizeHint() { return new QSize(600, 500); }
    public boolean animated() { return m_animated; }
    public int radius() { return (int)m_radius; }
    public int fontSize() { return m_fontSize; }
    public int intensity() { return (int)m_intensity; }
    public String text() { return m_text; }

    public void setText(String text)
    {
        m_text = text;
        m_textDirty = true;
        update();
    }

    private void makeTextPaths()
    {
        QFont f = new QFont("times new roman,utopia");
        f.setStyleStrategy(QFont.StyleStrategy.ForceOutline);
        f.setPointSize(m_fontSize);
        f.setStyleHint(QFont.StyleHint.Times);

        QFontMetrics fm = new QFontMetrics(f);
        m_paths.clear();
        m_pathBounds = new QRectF();

        QPointF advance = new QPointF(0, 0);

        List<QPainterPath> paths = new LinkedList<QPainterPath>();

        boolean do_quick = true;
        for (int i=0; i<m_text.length(); ++i) {
            if (m_text.charAt(i) >= 0x4ff && m_text.charAt(i) <= 0x1e00) {
                do_quick = false;
                break;
            }
        }

        if (do_quick) {
            for (int i=0; i<m_text.length(); ++i) {
                QPainterPath path = new QPainterPath();
                path.addText(advance, f, m_text.substring(i, i + 1));
                m_pathBounds = m_pathBounds.united(path.boundingRect());
                paths.add(path);
                advance.add(new QPointF(fm.width(m_text.substring(i, i + 1)), 0));
            }
        } else {
            QPainterPath path = new QPainterPath();
            path.addText(advance, f, m_text);
            m_pathBounds = m_pathBounds.united(path.boundingRect());
            paths.add(path);
        }

        QMatrix m = new QMatrix(1, 0, 0, 1, -m_pathBounds.x(), -m_pathBounds.y());
        for (int i=0; i<paths.size(); ++i)
            addPath(m.map(paths.get(i)));

        m_textDirty = false;
    }

    private void addPath(QPainterPath path)
    {
        PainterPathElements p = new PainterPathElements();
        p.path = path;

        p.elements = new QPainterPath_Element[path.elementCount()];
        for (int i=0; i<path.elementCount(); ++i) {
            p.elements[i] = path.elementAt(i);
        }

        m_paths.add(p);
    }

    static QRect circle_bounds(QPointF center, double radius, double compensation)
    {
        return new QRect((int)Math.round(center.x() - radius - compensation),
                         (int)Math.round(center.y() - radius - compensation),
                         (int)Math.round((radius + compensation) * 2),
                         (int)Math.round((radius + compensation) * 2));
    }


    final int LENS_EXTENT = 10;
    void generateLensPixmap()
    {
        double rad = m_radius + LENS_EXTENT;

        QRect bounds = circle_bounds(new QPointF(), rad, 0);

        QPainter painter = new QPainter();
        if (preferImage()) {
            m_lens_image = new QImage(bounds.size(), QImage.Format.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(0, 0, 0, 0));
            painter.begin(m_lens_pixmap);
        }

        QRadialGradient 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.RenderHint.Antialiasing);
        painter.setBrush(new QBrush(gr));
        painter.setPen(QPen.NoPen);
        painter.drawEllipse(0, 0, bounds.width(), bounds.height());
        painter.end();
    }

    public void setAnimated(boolean animated)
    {
        m_animated = animated;

        if (m_animated) {
            m_repaintTimer.start(25, this);
            m_repaintTracker.start();
        } else {
            m_repaintTimer.stop();
        }
    }

    @Override
    protected void paintEvent(QPaintEvent e)
    {
        if (m_textDirty)
            makeTextPaths();
        super.paintEvent(e);
    }

    @Override
    protected void timerEvent(QTimerEvent e)
    {
        if (e.timerId() == m_repaintTimer.timerId()) {
            if ((new QLineF(new QPointF(0,0), m_direction)).length() > 1)
                m_direction.multiply(0.995);

            double time = m_repaintTracker.restart();

            QRect rectBefore = circle_bounds(m_pos, m_radius, m_fontSize);

            double dx = m_direction.x();
            double dy = m_direction.y();

            if (time > 0) {
                dx = dx * time * 0.1;
                dy = dy * time * 0.1;
            }

            m_pos.add(new QPointF(dx, dy));

            if (m_pos.x() - m_radius < 0) {
                m_direction.setX(-m_direction.x());
                m_pos.setX(m_radius);
            } else if (m_pos.x() + m_radius > width()) {
                m_direction.setX(-m_direction.x());
                m_pos.setX(width() - m_radius);
            }

            if (m_pos.y() - m_radius < 0) {
                m_direction.setY(-m_direction.y());
                m_pos.setY(m_radius);
            } else if (m_pos.y() + m_radius > height()) {
                m_direction.setY(-m_direction.y());
                m_pos.setY(height() - m_radius);
            }

            QRect rectAfter = circle_bounds(m_pos, m_radius, m_fontSize);
            update(rectBefore.united(rectAfter));
            QApplication.syncX();
        }
    }

    @Override
    protected void mousePressEvent(QMouseEvent e)
    {
        setDescriptionEnabled(false);

        m_repaintTimer.stop();
        m_offset = new QPointF();

        if ((new QLineF(m_pos, new QPointF(e.pos()))).length() <= m_radius) {
            m_offset = new QPointF(m_pos.x(), m_pos.y());
            m_offset.subtract(new QPointF(e.pos()));
        }

        mouseMoveEvent(e);
    }

    @Override
    protected void mouseReleaseEvent(QMouseEvent e)
    {
        if (e.buttons().isSet(Qt.MouseButton.NoButton) && m_animated) {
            m_repaintTimer.start(25, this);
            m_repaintTracker.start();
        }
    }

    @Override
    protected void mouseMoveEvent(QMouseEvent e)
    {
        QRect rectBefore = circle_bounds(m_pos, m_radius, m_fontSize);
        if (e.type() == QEvent.Type.MouseMove) {
            QPointF epos = new QPointF(e.pos());
            epos.add(m_offset);
            QLineF line = new QLineF(m_pos, epos);
            line.setLength(line.length() * .1);
            QPointF dir = new QPointF(line.dx(), line.dy());
            m_direction.add(dir);
            m_direction.multiply(0.5);
        }

        m_pos = new QPointF(e.pos());
        m_pos.add(m_offset);
        QRect rectAfter = circle_bounds(m_pos, m_radius, m_fontSize);

        update(rectBefore.united(rectAfter));
    }

    private void deformElement(QPainterPath_Element e, QPointF offset, double pts[])
    {
        double flip = m_intensity;

        double x = e.x() + offset.x();
        double y = e.y() + offset.y();

        double dx = x - m_pos.x();
        double dy = y - m_pos.y();
        double len = m_radius - Math.sqrt(dx * dx + dy * dy);

        if (len > 0) {
            x = x + flip * dx * len / m_radius;
            y = y + flip * dy * len / m_radius;
        }

        pts[0] = x;
        pts[1] = y;
    }

    protected QPainterPath lensDeform(PainterPathElements source, QPointF offset)
    {
        QPainterPath path = new QPainterPath();
        double pts[] = new double[2];

        for (int i=0; i<source.elements.length;) {
            QPainterPath_Element e = source.elements[i++];

            if (e.isLineTo()) {
                deformElement(e, offset, pts);
                path.lineTo(pts[0], pts[1]);
            } else if (e.isMoveTo()) {
                deformElement(e, offset, pts);
                path.moveTo(pts[0], pts[1]);
            } else if (e.isCurveTo()) {
                deformElement(e, offset, pts);
                double c1x = pts[0], c1y = pts[1];

                e = source.elements[i++];
                deformElement(e, offset, pts);
                double c2x = pts[0], c2y = pts[1];

                e = source.elements[i++];
                deformElement(e, offset, pts);
                double ex = pts[0], ey = pts[1];

                path.cubicTo(c1x, c1y, c2x, c2y, ex, ey);
            }
        }

        return path;
    }

    @Override
    public void paint(QPainter painter)
    {
        int pad_x = 5;
        int pad_y = 5;

        int skip_x = (int)Math.round(m_pathBounds.width() + pad_x + m_fontSize / 2);
        int skip_y = (int)Math.round(m_pathBounds.height() + pad_y);

        painter.setPen(QPen.NoPen);
        painter.setBrush(new QBrush(QColor.black));

        QRectF 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.size(); ++i) {
                        QPainterPath path = lensDeform(m_paths.get(i), new QPointF(start_x, start_y));
                        painter.drawPath(path);
                    }
                }

            }
            overlap = skip_x - (start_x - width());
        }
        if (preferImage()) {
            painter.drawImage((int)(m_pos.x() - m_radius - LENS_EXTENT), (int)(m_pos.y() - m_radius - LENS_EXTENT), m_lens_image);
        } else {
            painter.drawPixmap((int)(m_pos.x() - m_radius - LENS_EXTENT), (int)(m_pos.y() - m_radius - LENS_EXTENT), m_lens_pixmap);
        }
    }

    public void setRadius(int radius)
    {
        double max = Math.max(m_radius, radius);
        m_radius = radius;

        generateLensPixmap();
        if (!m_animated || m_radius < max)
            update(circle_bounds(m_pos, max, m_fontSize));
    }

    public void setIntensity(int intensity)
    {
        m_intensity = intensity / 100.0;
        if (!m_animated)
            update(circle_bounds(m_pos, m_radius, m_fontSize));
    }
}

@QtJambiExample(name = "Deform")
public class Deform extends QWidget
{
    private PathDeformRenderer m_renderer;

    public Deform(QWidget parent)
    {
        super(parent);

        setWindowTitle("Vector deformation");
        setWindowIcon(new QIcon("classpath:com/trolltech/images/qt-logo.png"));

        m_renderer = new PathDeformRenderer(this);
        m_renderer.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding);

        QGroupBox mainGroup = new QGroupBox(this);
        mainGroup.setTitle("Vector Deformation");

        QGroupBox radiusGroup = new QGroupBox(mainGroup);
        radiusGroup.setTitle("Lens radius");
        QSlider radiusSlider = new QSlider(Qt.Orientation.Horizontal, radiusGroup);
        radiusSlider.setRange(50, 150);
        radiusSlider.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed);

        QGroupBox deformGroup = new QGroupBox(mainGroup);
        deformGroup.setTitle("Deformation");
        QSlider deformSlider = new QSlider(Qt.Orientation.Horizontal, deformGroup);
        deformSlider.setRange(-100, 100);
        deformSlider.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed);

        QGroupBox fontSizeGroup = new QGroupBox(mainGroup);
        fontSizeGroup.setTitle("Font Size");
        QSlider fontSizeSlider = new QSlider(Qt.Orientation.Horizontal, fontSizeGroup);
        fontSizeSlider.setRange(16, 200);
        fontSizeSlider.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed);

        QGroupBox textGroup = new QGroupBox(mainGroup);
        textGroup.setTitle("Text");

        QLineEdit textInput = new QLineEdit(textGroup);

        QPushButton animateButton = new QPushButton(mainGroup);
        animateButton.setText("Animated");
        animateButton.setCheckable(true);

        QPushButton showSourceButton = new QPushButton(mainGroup);
        showSourceButton.setText("Show Source");

        QPushButton whatsThisButton = new QPushButton(mainGroup);
        whatsThisButton.setText("What's This?");
        whatsThisButton.setCheckable(true);

        QHBoxLayout mainLayout = new QHBoxLayout(this);
        mainLayout.addWidget(m_renderer);
        mainLayout.addWidget(mainGroup);
        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);
        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);

        textInput.textChanged.connect(m_renderer, "setText(String)");
        radiusSlider.valueChanged.connect(m_renderer, "setRadius(int)");
        deformSlider.valueChanged.connect(m_renderer, "setIntensity(int)");
        fontSizeSlider.valueChanged.connect(m_renderer, "setFontSize(int)");
        animateButton.clicked.connect(m_renderer, "setAnimated(boolean)");
        whatsThisButton.clicked.connect(m_renderer, "setDescriptionEnabled(boolean)");
        showSourceButton.clicked.connect(m_renderer, "showSource()");
        m_renderer.descriptionEnabledChanged.connect(whatsThisButton, "setChecked(boolean)");

        animateButton.animateClick();
        deformSlider.setValue(80);
        radiusSlider.setValue(100);
        fontSizeSlider.setValue(100);
        textInput.setText("Qt Jambi");

        m_renderer.loadSourceFile("classpath:com/trolltech/demos/Deform.java");
        m_renderer.loadDescription("classpath:com/trolltech/demos/Deform.html");
        m_renderer.setDescriptionEnabled(false);
    }

    public static void main(String args[])
    {
        QApplication.initialize(args);

        Deform w = new Deform(null);
        w.show();

        QApplication.exec();
    }
}

