QTfrontend/drawmapscene.cpp
author unc0rr
Tue, 13 Mar 2012 22:46:55 +0400
changeset 6781 23f627ba8ee9
parent 6700 e04da46ee43c
child 6784 2c02ccb8f4ec
permissions -rw-r--r--
Variable pen width

/*
 * Hedgewars, a free turn based strategy game
 * Copyright (c) 2012 Andrey Korotaev <unC0Rr@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include <QGraphicsSceneMouseEvent>
#include <QGraphicsPathItem>
#include <QtEndian>
#include <QDebug>

#include "drawmapscene.h"

template <class T> T sqr(const T & x)
{
    return x*x;
}

DrawMapScene::DrawMapScene(QObject *parent) :
    QGraphicsScene(parent),
    m_pen(Qt::yellow),
    m_brush(Qt::yellow)
{
    setSceneRect(0, 0, 4096, 2048);

    QLinearGradient gradient(0, 0, 0, 2048);
    gradient.setColorAt(0, QColor(60, 60, 155));
    gradient.setColorAt(1, QColor(155, 155, 60));
    setBackgroundBrush(QBrush(gradient));

    m_pen.setWidth(72);
    m_pen.setJoinStyle(Qt::RoundJoin);
    m_pen.setCapStyle(Qt::RoundCap);
    m_currPath = 0;
}

void DrawMapScene::mouseMoveEvent(QGraphicsSceneMouseEvent * mouseEvent)
{
    if(m_currPath && (mouseEvent->buttons() & Qt::LeftButton))
    {
        QPainterPath path = m_currPath->path();

        if(mouseEvent->modifiers() & Qt::ControlModifier)
        {
            int c = path.elementCount();
            QPointF pos = mouseEvent->scenePos();
            path.setElementPositionAt(c - 1, pos.x(), pos.y());

        }
        else
        {
            path.lineTo(mouseEvent->scenePos());
            paths.first().second.append(mouseEvent->scenePos().toPoint());
        }
        m_currPath->setPath(path);

        emit pathChanged();
    }
}

void DrawMapScene::mousePressEvent(QGraphicsSceneMouseEvent * mouseEvent)
{
    m_currPath = addPath(QPainterPath(), m_pen);

    QPainterPath path = m_currPath->path();
    QPointF p = mouseEvent->scenePos();
    p += QPointF(0.01, 0.01);
    path.moveTo(p);
    path.lineTo(mouseEvent->scenePos());
    paths.prepend(qMakePair(serializePenWidth(m_pen.width()), QList<QPoint>() << mouseEvent->scenePos().toPoint()));
    m_currPath->setPath(path);

    emit pathChanged();
}

void DrawMapScene::mouseReleaseEvent(QGraphicsSceneMouseEvent * mouseEvent)
{
    if (m_currPath)
    {
        QPainterPath path = m_currPath->path();
        path.lineTo(mouseEvent->scenePos());
        paths.first().second.append(mouseEvent->scenePos().toPoint());
        m_currPath->setPath(path);

        simplifyLast();

        m_currPath = 0;
    }
}

void DrawMapScene::wheelEvent(QGraphicsSceneWheelEvent * wheelEvent)
{
    if(wheelEvent->delta() > 0 && m_pen.width() < 512)
        m_pen.setWidth(m_pen.width() + 10);
    else if(wheelEvent->delta() < 0 && m_pen.width() >= 12)
        m_pen.setWidth(m_pen.width() - 10);

    if(m_currPath)
    {
        m_currPath->setPen(m_pen);
        paths.first().first = serializePenWidth(m_pen.width());
    }
}

void DrawMapScene::undo()
{
    if(items().size())
    {
        removeItem(items().first());
        paths.removeFirst();

        emit pathChanged();
    }
    else if(oldItems.size())
    {
        while(oldItems.size())
            addItem(oldItems.takeFirst());
        paths = oldPaths;

        emit pathChanged();
    }
}

void DrawMapScene::clearMap()
{
    // don't clear if already cleared
    if(!items().size())
        return;

    oldItems.clear();

    // do this since clear() would _destroy_ all items
    while(items().size())
    {
        oldItems.push_front(items().first());
        removeItem(items().first());
    }

    oldPaths = paths;

    paths.clear();

    emit pathChanged();
}

QByteArray DrawMapScene::encode()
{
    QByteArray b;

    for(int i = paths.size() - 1; i >= 0; --i)
    {
        int cnt = 0;
        QPair<quint8, QList<QPoint> > points = paths.at(i);
        foreach(QPoint point, points.second)
        {
            qint16 px = qToBigEndian((qint16)point.x());
            qint16 py = qToBigEndian((qint16)point.y());
            quint8 flags = 0;
            if(!cnt) flags = 0x80 + points.first;
            b.append((const char *)&px, 2);
            b.append((const char *)&py, 2);
            b.append((const char *)&flags, 1);

            ++cnt;
        }

    }

    return b;
}

void DrawMapScene::decode(QByteArray data)
{
    oldItems.clear();
    oldPaths.clear();
    clear();
    paths.clear();

    QPair<quint8, QList<QPoint> > points;

    while(data.size() >= 5)
    {
        qint16 px = qFromBigEndian(*(qint16 *)data.data());
        data.remove(0, 2);
        qint16 py = qFromBigEndian(*(qint16 *)data.data());
        data.remove(0, 2);
        quint8 flags = *(quint8 *)data.data();
        data.remove(0, 1);

        if(flags & 0x80)
        {
            if(points.second.size())
            {
                addPath(pointsToPath(points.second), m_pen);

                paths.prepend(points);

                points.second.clear();
            }

            quint8 penWidth = flags & 0x7f;
            m_pen.setWidth(deserializePenWidth(penWidth));
            points.first = penWidth;
        }

        points.second.append(QPoint(px, py));
    }

    if(points.second.size())
    {
        addPath(pointsToPath(points.second), m_pen);
        paths.prepend(points);
    }

    emit pathChanged();
}

void DrawMapScene::simplifyLast()
{
    if(!paths.size()) return;

    QList<QPoint> points = paths.at(0).second;

    QPoint prevPoint = points.first();
    int i = 1;
    while(i < points.size())
    {
        if( (i != points.size() - 1)
                && (sqr(prevPoint.x() - points[i].x()) + sqr(prevPoint.y() - points[i].y()) < 1000)
          )
            points.removeAt(i);
        else
        {
            prevPoint = points[i];
            ++i;
        }
    }

    paths[0].second = points;


    // redraw path
    {
        QGraphicsPathItem * pathItem = static_cast<QGraphicsPathItem *>(items()[0]);
        pathItem->setPath(pointsToPath(paths[0].second));
    }

    emit pathChanged();
}

QPainterPath DrawMapScene::pointsToPath(const QList<QPoint> points)
{
    QPainterPath path;

    if(points.size())
    {
        QPointF p = points[0] + QPointF(0.01, 0.01);
        path.moveTo(p);

        foreach(QPoint p, points)
        path.lineTo(p);
    }

    return path;
}

quint8 DrawMapScene::serializePenWidth(int width)
{
    return (width - 6) / 10;
}

int DrawMapScene::deserializePenWidth(quint8 width)
{
    return width * 10 + 6;
}