// xlsxdrawinganchor.cpp

#include "xlsxchart.h"
#include "xlsxdrawing_p.h"
#include "xlsxdrawinganchor_p.h"
#include "xlsxmediafile_p.h"
#include "xlsxutility_p.h"
#include "xlsxworkbook.h"

#include <QBuffer>
#include <QDebug>
#include <QDir>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
#include <QtGlobal>

QT_BEGIN_NAMESPACE_XLSX

/*
     The vertices that define the position of a graphical object
     within the worksheet in pixels.

             +------------+------------+
             |     A      |      B     |
       +-----+------------+------------+
       |     |(x1,y1)     |            |
       |  1  |(A1)._______|______      |
       |     |    |              |     |
       |     |    |              |     |
       +-----+----|    OBJECT    |-----+
       |     |    |              |     |
       |  2  |    |______________.     |
       |     |            |        (B2)|
       |     |            |     (x2,y2)|
       +---- +------------+------------+

     Example of an object that covers some of the area from cell A1 to  B2.

     Based on the width and height of the object we need to calculate 8 vars:

         col_start, row_start, col_end, row_end, x1, y1, x2, y2.

     We also calculate the absolute x and y position of the top left vertex of
     the object. This is required for images.

     The width and height of the cells that the object occupies can be
     variable and have to be taken into account.
*/

// anchor

DrawingAnchor::DrawingAnchor(Drawing *drawing, ObjectType objectType)
    : m_drawing(drawing)
    , m_objectType(objectType)
{
    m_drawing->anchors.append(this);
    m_id = m_drawing->anchors.size(); // must be unique in one drawing{x}.xml file.
}

DrawingAnchor::~DrawingAnchor()
{
}

void DrawingAnchor::setObjectPicture(const QImage &img)
{
    QByteArray ba;
    QBuffer buffer(&ba);
    buffer.open(QIODevice::WriteOnly);
    img.save(&buffer, "PNG");

    m_pictureFile =
        std::make_shared<MediaFile>(ba, QStringLiteral("png"), QStringLiteral("image/png"));
    m_drawing->workbook->addMediaFile(m_pictureFile);

    m_objectType = Picture;
}

bool DrawingAnchor::getObjectPicture(QImage &img)
{
    if (m_pictureFile == nullptr)
        return false;

    bool ret = img.loadFromData(m_pictureFile->contents());
    return ret;
}

//{{ liufeijin
void DrawingAnchor::setObjectShape(const QImage &img)
{
    QByteArray ba;
    QBuffer buffer(&ba);
    buffer.open(QIODevice::WriteOnly);
    img.save(&buffer, "PNG");

    m_pictureFile =
        std::make_shared<MediaFile>(ba, QStringLiteral("png"), QStringLiteral("image/png"));
    m_drawing->workbook->addMediaFile(m_pictureFile);

    m_objectType = Shape;
}
//}}

void DrawingAnchor::setObjectGraphicFrame(QSharedPointer<Chart> chart)
{
    m_chartFile = chart;
    m_drawing->workbook->addChartFile(chart);

    m_objectType = GraphicFrame;
}

int DrawingAnchor::row() const
{
    return -1;
}

int DrawingAnchor::col() const
{
    return -1;
}

QPoint DrawingAnchor::loadXmlPos(QXmlStreamReader &reader)
{
    Q_ASSERT(reader.name() == QLatin1String("pos"));

    QPoint pos;
    QXmlStreamAttributes attrs = reader.attributes();
    pos.setX(attrs.value(QLatin1String("x")).toInt());
    pos.setY(attrs.value(QLatin1String("y")).toInt());
    return pos;
}

QSize DrawingAnchor::loadXmlExt(QXmlStreamReader &reader)
{
    Q_ASSERT(reader.name() == QLatin1String("ext"));

    QSize size;
    QXmlStreamAttributes attrs = reader.attributes();
    size.setWidth(attrs.value(QLatin1String("cx")).toInt());
    size.setHeight(attrs.value(QLatin1String("cy")).toInt());
    return size;
}

XlsxMarker DrawingAnchor::loadXmlMarker(QXmlStreamReader &reader, const QString &node)
{
    Q_ASSERT(reader.name() == node);

    int col       = 0;
    int colOffset = 0;
    int row       = 0;
    int rowOffset = 0;
    while (!reader.atEnd()) {
        reader.readNextStartElement();
        if (reader.tokenType() == QXmlStreamReader::StartElement) {
            if (reader.name() == QLatin1String("col")) {
                col = reader.readElementText().toInt();
            } else if (reader.name() == QLatin1String("colOff")) {
                colOffset = reader.readElementText().toInt();
            } else if (reader.name() == QLatin1String("row")) {
                row = reader.readElementText().toInt();
            } else if (reader.name() == QLatin1String("rowOff")) {
                rowOffset = reader.readElementText().toInt();
            }
        } else if (reader.tokenType() == QXmlStreamReader::EndElement && reader.name() == node) {
            break;
        }
    }

    return XlsxMarker(row, col, rowOffset, colOffset);
}

void DrawingAnchor::loadXmlObject(QXmlStreamReader &reader)
{
    /*
    <xsd:group name="EG_ObjectChoices">
        <xsd:sequence>
            <xsd:choice minOccurs="1" maxOccurs="1">
                <xsd:element name="sp" type="CT_Shape"/>
                <xsd:element name="grpSp" type="CT_GroupShape"/>
                <xsd:element name="graphicFrame" type="CT_GraphicalObjectFrame"/>
                <xsd:element name="cxnSp" type="CT_Connector"/>
                <xsd:element name="pic" type="CT_Picture"/>
                <xsd:element name="contentPart" type="CT_Rel"/>
            </xsd:choice>
        </xsd:sequence>
    </xsd:group>
    */

    if (reader.name() == QLatin1String("sp")) // <xsd:element name="sp" type="CT_Shape"/>
    {
        // Shape
        m_objectType = Shape;

        //{{ liufeijin
        sp_textlink = reader.attributes().value(QLatin1String("textlink")).toString();
        sp_macro    = reader.attributes().value(QLatin1String("macro")).toString();
        //}}

        // <xsd:attribute name="macro" type="xsd:string" use="optional"/>
        // <xsd:attribute name="textlink" type="xsd:string" use="optional"/>
        // <xsd:attribute name="fLocksText" type="xsd:boolean" use="optional" default="true"/>
        // <xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>

        loadXmlObjectShape(reader); // CT_Shape
    } else if (reader.name() ==
               QLatin1String("grpSp")) // <xsd:element name="grpSp" type="CT_GroupShape"/>
    {
        // Group Shape
        m_objectType = GroupShape;
        loadXmlObjectGroupShape(reader);
    } else if (reader.name() == QLatin1String("graphicFrame")) // <xsd:element name="graphicFrame"
                                                               // type="CT_GraphicalObjectFrame"/>
    {
        // Graphic Frame
        m_objectType = GraphicFrame;
        loadXmlObjectGraphicFrame(reader);
    } else if (reader.name() ==
               QLatin1String("cxnSp")) // <xsd:element name="cxnSp" type="CT_Connector"/>
    {
        // Connection Shape
        m_objectType = ConnectionShape;

        // {{ liufeijin
        cxnSp_macro = reader.attributes().value(QLatin1String("macro")).toString();
        // }}

        loadXmlObjectConnectionShape(reader);
    } else if (reader.name() == QLatin1String("pic")) // <xsd:element name="pic" type="CT_Picture"/>
    {
        // Picture
        m_objectType = Picture;
        loadXmlObjectPicture(reader);
    } else if (reader.name() ==
               QLatin1String("contentPart")) // <xsd:element name="contentPart" type="CT_Rel"/>
    {
        // contentPart
        /// TODO:
    }
}

void DrawingAnchor::loadXmlObjectConnectionShape(QXmlStreamReader &reader)
{
    Q_ASSERT(reader.name() == QLatin1String("cxnSp"));
    bool hasoffext = false;
    while (!reader.atEnd()) {
        reader.readNextStartElement();
        if (reader.tokenType() == QXmlStreamReader::StartElement) {
            if (reader.name() == QLatin1String("cNvPr")) {
                xsp_cNvPR_name = reader.attributes().value(QLatin1String("name")).toString();
                xsp_cNvPR_id   = reader.attributes().value(QLatin1String("id")).toString();
            } else if (reader.name() == QLatin1String("spPr")) {
                xbwMode = reader.attributes().value(QLatin1String("bwMode")).toString();
            } else if (reader.name() == QLatin1String("xfrm")) {
                cxnSp_filpV = reader.attributes().value(QLatin1String("flipV")).toString();
            } else if (reader.name() == QLatin1String("off")) {
                posTA     = loadXmlPos(reader);
                hasoffext = true;
            } else if (reader.name() == QLatin1String("ext") && hasoffext) {
                extTA     = loadXmlExt(reader);
                hasoffext = false;
            } else if (reader.name() == QLatin1String("prstGeom")) {
                xprstGeom_prst =
                    reader.attributes().value(QLatin1String("prst")).toString().trimmed();
            } else if (reader.name() == QLatin1String("ln")) {
                xIn_algn = reader.attributes().value(QLatin1String("algn")).toString().trimmed();
                xIn_cmpd = reader.attributes().value(QLatin1String("cmpd")).toString().trimmed();
                xIn_cap  = reader.attributes().value(QLatin1String("cap")).toString().trimmed();
                xIn_w    = reader.attributes().value(QLatin1String("w")).toString().trimmed();
            } else if (reader.name() == QLatin1String("headEnd")) {
                x_headEnd_w = reader.attributes().value(QLatin1String("w")).toString().trimmed();
                x_headEnd_len =
                    reader.attributes().value(QLatin1String("len")).toString().trimmed();
                x_headEnd_tyep =
                    reader.attributes().value(QLatin1String("type")).toString().trimmed();
            } else if (reader.name() == QLatin1String("tailEnd")) {
                x_tailEnd_w = reader.attributes().value(QLatin1String("w")).toString().trimmed();
                x_tailEnd_len =
                    reader.attributes().value(QLatin1String("len")).toString().trimmed();
                x_tailEnd_tyep =
                    reader.attributes().value(QLatin1String("type")).toString().trimmed();
            } else if (reader.name() == QLatin1String("lnRef")) {
                Style_inref_idx =
                    reader.attributes().value(QLatin1String("idx")).toString().trimmed();
                reader.readNextStartElement();
                if (reader.tokenType() == QXmlStreamReader::StartElement) {
                    if (reader.name() == QLatin1String("schemeClr")) {
                        Style_inref_val =
                            reader.attributes().value(QLatin1String("val")).toString().trimmed();
                    }
                }
            } else if (reader.name() == QLatin1String("fillRef")) {
                style_fillref_idx =
                    reader.attributes().value(QLatin1String("idx")).toString().trimmed();
                reader.readNextStartElement();
                if (reader.tokenType() == QXmlStreamReader::StartElement) {
                    if (reader.name() == QLatin1String("schemeClr")) {
                        style_fillref_val =
                            reader.attributes().value(QLatin1String("val")).toString().trimmed();
                    }
                }
            } else if (reader.name() == QLatin1String("effectRef")) {
                style_effectref_idx =
                    reader.attributes().value(QLatin1String("idx")).toString().trimmed();
                reader.readNextStartElement();
                if (reader.tokenType() == QXmlStreamReader::StartElement) {
                    if (reader.name() == QLatin1String("schemeClr")) {
                        style_effectref_val =
                            reader.attributes().value(QLatin1String("val")).toString().trimmed();
                    }
                }
            } else if (reader.name() == QLatin1String("fontRef")) {
                style_forntref_idx =
                    reader.attributes().value(QLatin1String("idx")).toString().trimmed();
                reader.readNextStartElement();
                if (reader.tokenType() == QXmlStreamReader::StartElement) {
                    if (reader.name() == QLatin1String("schemeClr")) {
                        style_forntref_val =
                            reader.attributes().value(QLatin1String("val")).toString().trimmed();
                    }
                }
            }

        } else if (reader.tokenType() == QXmlStreamReader::EndElement &&
                   reader.name() == QLatin1String("cxnSp")) {
            break;
        }
    }

    return;
}

void DrawingAnchor::loadXmlObjectGraphicFrame(QXmlStreamReader &reader)
{
    Q_ASSERT(reader.name() == QLatin1String("graphicFrame"));

    while (!reader.atEnd()) {
        reader.readNextStartElement();
        if (reader.tokenType() == QXmlStreamReader::StartElement) {
            if (reader.name() == QLatin1String("chart")) {
                QString rId  = reader.attributes().value(QLatin1String("r:id")).toString();
                QString name = m_drawing->relationships()->getRelationshipById(rId).target;

                const auto parts = splitPath(m_drawing->filePath());
                QString path     = QDir::cleanPath(parts.first() + QLatin1String("/") + name);

                bool exist                       = false;
                QList<QSharedPointer<Chart>> cfs = m_drawing->workbook->chartFiles();
                for (int i = 0; i < cfs.size(); ++i) {
                    if (cfs[i]->filePath() == path) {
                        // already exist
                        exist       = true;
                        m_chartFile = cfs[i];
                    }
                }
                if (!exist) {
                    m_chartFile =
                        QSharedPointer<Chart>(new Chart(m_drawing->sheet, Chart::F_LoadFromExists));
                    m_chartFile->setFilePath(path);
                    m_drawing->workbook->addChartFile(m_chartFile);
                }
            }
        } else if (reader.tokenType() == QXmlStreamReader::EndElement &&
                   reader.name() == QLatin1String("graphicFrame")) {
            break;
        }
    }

    return;
}

void DrawingAnchor::loadXmlObjectGroupShape(QXmlStreamReader &reader)
{
    Q_UNUSED(reader)
}

void DrawingAnchor::loadXmlObjectPicture(QXmlStreamReader &reader)
{
    Q_ASSERT(reader.name() == QLatin1String("pic"));

    while (!reader.atEnd()) {
        reader.readNextStartElement();
        if (reader.tokenType() == QXmlStreamReader::StartElement) {
            if (reader.name() == QLatin1String("blip")) {
                QString rId  = reader.attributes().value(QLatin1String("r:embed")).toString();
                QString name = m_drawing->relationships()->getRelationshipById(rId).target;

                const auto parts = splitPath(m_drawing->filePath());
                QString path     = QDir::cleanPath(parts.first() + QLatin1String("/") + name);

                bool exist     = false;
                const auto mfs = m_drawing->workbook->mediaFiles();
                for (const auto &mf : mfs) {
                    if (mf->fileName() == path) {
                        // already exist
                        exist         = true;
                        m_pictureFile = mf;
                    }
                }
                if (!exist) {
                    m_pictureFile = std::make_shared<MediaFile>(path);
                    m_drawing->workbook->addMediaFile(m_pictureFile, true);
                }
            }
        } else if (reader.tokenType() == QXmlStreamReader::EndElement &&
                   reader.name() == QLatin1String("pic")) {
            break;
        }
    }

    return;
}

void DrawingAnchor::loadXmlObjectShape(QXmlStreamReader &reader)
{
    /*
    <xsd:complexType name="CT_Shape">
        <xsd:sequence>
            <xsd:element name="nvSpPr" type="CT_ShapeNonVisual" minOccurs="1" maxOccurs="1"/>
            <xsd:element name="spPr" type="a:CT_ShapeProperties" minOccurs="1" maxOccurs="1"/>
            <xsd:element name="style" type="a:CT_ShapeStyle" minOccurs="0" maxOccurs="1"/>
            <xsd:element name="txBody" type="a:CT_TextBody" minOccurs="0" maxOccurs="1"/>
        </xsd:sequence>
        <xsd:attribute name="macro" type="xsd:string" use="optional"/>
        <xsd:attribute name="textlink" type="xsd:string" use="optional"/>
        <xsd:attribute name="fLocksText" type="xsd:boolean" use="optional" default="true"/>
        <xsd:attribute name="fPublished" type="xsd:boolean" use="optional" default="false"/>
    </xsd:complexType>
    */
    /*
    <xsd:complexType name="CT_ShapeNonVisual">
        <xsd:sequence>
        <xsd:element name="cNvPr" type="a:CT_NonVisualDrawingProps" minOccurs="1" maxOccurs="1"/>
        <xsd:element name="cNvSpPr" type="a:CT_NonVisualDrawingShapeProps" minOccurs="1"
    maxOccurs="1"/>
        </xsd:sequence>
    </xsd:complexType>
    */

    Q_ASSERT(reader.name() == QLatin1String("sp"));

    while (!reader.atEnd()) {
        reader.readNextStartElement();

        // qDebug() << __FUNCTION__ << reader.name().toString();

        if (reader.tokenType() == QXmlStreamReader::StartElement) {
            if (reader.name() == QLatin1String("nvSpPr")) {

            } else if (reader.name() == QLatin1String("spPr")) {

            } else if (reader.name() == QLatin1String("style")) {

            } else if (reader.name() == QLatin1String("txBody")) {
            }
        } else if (reader.tokenType() == QXmlStreamReader::EndElement &&
                   reader.name() == QLatin1String("sp")) {
            break;
        }
    }

    /*

    bool hasoffext = false;
    while (!reader.atEnd())
    {
        reader.readNextStartElement();

        // qDebug() << __FUNCTION__ << reader.name().toString();

        if (reader.tokenType() == QXmlStreamReader::StartElement)
        {
            if (reader.name() == QLatin1String("blip"))
            {
                QString rId;
                sp_blip_rembed= reader.attributes().value(QLatin1String("r:embed")).toString();
                sp_blip_cstate=reader.attributes().value(QLatin1String("cstate")).toString();
                rId=sp_blip_rembed;
                QString name = m_drawing->relationships()->getRelationshipById(rId).target;
                QString path = QDir::cleanPath(splitPath(m_drawing->filePath())[0] +
    QLatin1String("/") + name); bool exist = false; QList<QSharedPointer<MediaFile> > mfs =
    m_drawing->workbook->mediaFiles(); for (int i=0; i<mfs.size(); ++i)
                   {
                       if (mfs[i]->fileName() == path)
                       {
                           //already exist
                           exist = true;
                           m_pictureFile = mfs[i];
                       }
                   }
                   if (!exist) {
                       m_pictureFile = QSharedPointer<MediaFile> (new MediaFile(path));
                       m_drawing->workbook->addMediaFile(m_pictureFile, true);
                   }
            }
            else if (reader.name() == QLatin1String("off"))
            {
               posTA = loadXmlPos(reader);
               hasoffext=true;
            }
            else if (reader.name() == QLatin1String("ext")&&hasoffext)
            {
               extTA = loadXmlExt(reader);
               hasoffext=false;
            }
            else if(reader.name() == QLatin1String("blipFill"))
            {
                // dev24 : fixed for old Qt 5

                rotWithShapeTA =
    reader.attributes().value(QLatin1String("rotWithShape")).toString().toInt(); dpiTA =
    reader.attributes().value(QLatin1String("dpi")).toString().toInt();

                // rotWithShapeTA =
    reader.attributes().value(QLatin1String("rotWithShape")).toInt();
                // dpiTA = reader.attributes().value(QLatin1String("dpi")).toInt();

            }else if(reader.name() == QLatin1String("cNvPr"))
            {
               xsp_cNvPR_name= reader.attributes().value(QLatin1String("name")).toString();
               xsp_cNvPR_id= reader.attributes().value(QLatin1String("id")).toString();
            }
            else if(reader.name() == QLatin1String("spPr"))
            {
                xbwMode= reader.attributes().value(QLatin1String("bwMode")).toString();
            }
            else if(reader.name() == QLatin1String("prstGeom"))
            {
                xprstGeom_prst= reader.attributes().value(QLatin1String("prst")).toString();
            }
            else if(reader.name() == QLatin1String("ln"))
            {
                xIn_algn= reader.attributes().value(QLatin1String("algn")).toString();
                xIn_cmpd= reader.attributes().value(QLatin1String("cmpd")).toString();
                xIn_cap= reader.attributes().value(QLatin1String("cap")).toString();
                xIn_w= reader.attributes().value(QLatin1String("w")).toString();
            }
            else if(reader.name() == QLatin1String("headEnd"))
            {
                x_headEnd_w= reader.attributes().value(QLatin1String("w")).toString();
                x_headEnd_len= reader.attributes().value(QLatin1String("len")).toString();
                x_headEnd_tyep= reader.attributes().value(QLatin1String("type")).toString();
            }
            else if(reader.name() == QLatin1String("tailEnd"))
            {
                x_tailEnd_w= reader.attributes().value(QLatin1String("w")).toString();
                x_tailEnd_len= reader.attributes().value(QLatin1String("len")).toString();
                x_tailEnd_tyep= reader.attributes().value(QLatin1String("type")).toString();
            }
            else if(reader.name() == QLatin1String("lnRef"))
            {
                Style_inref_idx=
    reader.attributes().value(QLatin1String("idx")).toString().trimmed();
                reader.readNextStartElement();
                if (reader.tokenType() == QXmlStreamReader::StartElement) {
                    if(reader.name() == QLatin1String("schemeClr")){
                        Style_inref_val=reader.attributes().value(QLatin1String("val")).toString().trimmed();
                    }
                }
            }
            else if(reader.name() == QLatin1String("fillRef"))
            {
                style_fillref_idx=
    reader.attributes().value(QLatin1String("idx")).toString().trimmed();
                reader.readNextStartElement();
                if (reader.tokenType() == QXmlStreamReader::StartElement)
                {
                    if(reader.name() == QLatin1String("schemeClr"))
                    {
                       style_fillref_val=reader.attributes().value(QLatin1String("val")).toString().trimmed();
                    }
                }
            }
            else if(reader.name() == QLatin1String("effectRef"))
            {
                style_effectref_idx=
    reader.attributes().value(QLatin1String("idx")).toString().trimmed();
                reader.readNextStartElement();
                if (reader.tokenType() == QXmlStreamReader::StartElement) {
                    if(reader.name() == QLatin1String("schemeClr")){
                      style_effectref_val=reader.attributes().value(QLatin1String("val")).toString().trimmed();
                    }
                }
            }
            else if(reader.name() == QLatin1String("fontRef"))
            {
                style_forntref_idx=
    reader.attributes().value(QLatin1String("idx")).toString().trimmed();
                reader.readNextStartElement();
                if (reader.tokenType() == QXmlStreamReader::StartElement) {
                    if(reader.name() == QLatin1String("schemeClr")){
                        style_forntref_val=reader.attributes().value(QLatin1String("val")).toString().trimmed();
                    }
                }
            }

        }
        else if (reader.tokenType() == QXmlStreamReader::EndElement
                   && reader.name() == QLatin1String("sp"))
        {
            break;
        }
    }

    //*/

    return;
}

void DrawingAnchor::saveXmlPos(QXmlStreamWriter &writer, const QPoint &pos) const
{
    writer.writeEmptyElement(QStringLiteral("xdr:pos"));
    writer.writeAttribute(QStringLiteral("x"), QString::number(pos.x()));
    writer.writeAttribute(QStringLiteral("y"), QString::number(pos.y()));
}

void DrawingAnchor::saveXmlExt(QXmlStreamWriter &writer, const QSize &ext) const
{
    writer.writeStartElement(QStringLiteral("xdr:ext"));
    writer.writeAttribute(QStringLiteral("cx"), QString::number(ext.width()));
    writer.writeAttribute(QStringLiteral("cy"), QString::number(ext.height()));
    writer.writeEndElement(); // xdr:ext
}

void DrawingAnchor::saveXmlMarker(QXmlStreamWriter &writer,
                                  const XlsxMarker &marker,
                                  const QString &node) const
{
    writer.writeStartElement(node); // xdr:from or xdr:to
    writer.writeTextElement(QStringLiteral("xdr:col"), QString::number(marker.col()));
    writer.writeTextElement(QStringLiteral("xdr:colOff"), QString::number(marker.colOff()));
    writer.writeTextElement(QStringLiteral("xdr:row"), QString::number(marker.row()));
    writer.writeTextElement(QStringLiteral("xdr:rowOff"), QString::number(marker.rowOff()));
    writer.writeEndElement();
}

void DrawingAnchor::saveXmlObject(QXmlStreamWriter &writer) const
{
    if (m_objectType == Picture)
        saveXmlObjectPicture(writer);
    else if (m_objectType == ConnectionShape)
        saveXmlObjectConnectionShape(writer);
    else if (m_objectType == GraphicFrame)
        saveXmlObjectGraphicFrame(writer);
    else if (m_objectType == GroupShape)
        saveXmlObjectGroupShape(writer);
    else if (m_objectType == Shape)
        saveXmlObjectShape(writer);
}

void DrawingAnchor::saveXmlObjectConnectionShape(QXmlStreamWriter &writer) const
{
    writer.writeStartElement(QStringLiteral("xdr:cxnSp")); ///?
    writer.writeAttribute(QStringLiteral("macro"), cxnSp_macro);

    writer.writeStartElement(QStringLiteral("xdr:nvCxnSpPr"));
    writer.writeEmptyElement(QStringLiteral("xdr:cNvPr"));
    writer.writeAttribute(QStringLiteral("id"), xsp_cNvPR_id);
    writer.writeAttribute(QStringLiteral("name"), xsp_cNvPR_name);
    writer.writeEmptyElement(QStringLiteral("xdr:cNvCxnSpPr"));
    writer.writeEndElement(); // xdr:nvCxnSpPr

    writer.writeStartElement(QStringLiteral("xdr:spPr"));
    if (!xbwMode.isNull()) {
        writer.writeAttribute(QStringLiteral("bwMode"), xbwMode);
    }

    writer.writeStartElement(QStringLiteral("a:xfrm"));
    if (!cxnSp_filpV.isEmpty()) {
        writer.writeAttribute(QStringLiteral("flipV"), cxnSp_filpV);
    }
    writer.writeEmptyElement(QStringLiteral("a:off"));
    writer.writeAttribute(QStringLiteral("x"), QString::number(posTA.x()));
    writer.writeAttribute(QStringLiteral("y"), QString::number(posTA.y()));
    writer.writeEmptyElement(QStringLiteral("a:ext"));
    writer.writeAttribute(QStringLiteral("cx"), QString::number(extTA.width()));
    writer.writeAttribute(QStringLiteral("cy"), QString::number(extTA.height()));
    writer.writeEndElement(); // a:xfrm

    writer.writeStartElement(QStringLiteral("a:prstGeom"));
    writer.writeAttribute(QStringLiteral("prst"), xprstGeom_prst);
    writer.writeEmptyElement(QStringLiteral("a:avLst"));
    writer.writeEndElement(); // a:prstGeom

    writer.writeStartElement(QStringLiteral("a:ln"));
    if (!xIn_w.isEmpty() && !xIn_cap.isEmpty()) {
        if (!xIn_w.isEmpty()) {
            writer.writeAttribute(QStringLiteral("w"), xIn_w);
        }
        if (!xIn_cap.isEmpty()) {
            writer.writeAttribute(QStringLiteral("cap"), xIn_cap);
        }
        if (!xIn_cmpd.isEmpty()) {
            writer.writeAttribute(QStringLiteral("cmpd"), xIn_cmpd);
        }
        if (!xIn_algn.isEmpty()) {
            writer.writeAttribute(QStringLiteral("algn"), xIn_algn);
        }
    }
    if ((!x_headEnd_tyep.isEmpty()) || (!x_headEnd_w.isEmpty()) || (!x_headEnd_len.isEmpty())) {
        writer.writeEmptyElement(QStringLiteral("a:headEnd"));
        if (!x_headEnd_tyep.isEmpty()) {
            writer.writeAttribute(QStringLiteral("type"), x_headEnd_tyep);
        }
        if (!x_headEnd_w.isEmpty()) {
            writer.writeAttribute(QStringLiteral("w"), x_headEnd_w);
        }
        if (!x_headEnd_len.isEmpty()) {
            writer.writeAttribute(QStringLiteral("len"), x_headEnd_len);
        }
    }
    if ((!x_tailEnd_tyep.isEmpty()) || (!x_tailEnd_w.isEmpty()) || (!x_tailEnd_len.isEmpty())) {
        writer.writeEmptyElement(QStringLiteral("a:tailEnd"));
        if (!x_tailEnd_tyep.isEmpty()) {
            writer.writeAttribute(QStringLiteral("type"), x_tailEnd_tyep);
        }
        if (!x_tailEnd_w.isEmpty()) {
            writer.writeAttribute(QStringLiteral("w"), x_tailEnd_w);
        }
        if (!x_tailEnd_len.isEmpty()) {
            writer.writeAttribute(QStringLiteral("len"), x_tailEnd_len);
        }
    }

    writer.writeEndElement(); // a:ln

    writer.writeEndElement(); // xdr:spPr
    // writer style

    writer.writeStartElement(QStringLiteral("xdr:style")); // style
    writer.writeStartElement(QStringLiteral("a:lnRef"));   // lnRef
    writer.writeAttribute(QStringLiteral("idx"), Style_inref_idx);
    writer.writeStartElement(QStringLiteral("a:schemeClr")); // val
    writer.writeAttribute(QStringLiteral("val"), Style_inref_val);
    writer.writeEndElement();                              // val
    writer.writeEndElement();                              // lnRef
    writer.writeStartElement(QStringLiteral("a:fillRef")); // fillRef
    writer.writeAttribute(QStringLiteral("idx"), style_fillref_idx);
    writer.writeStartElement(QStringLiteral("a:schemeClr")); // val
    writer.writeAttribute(QStringLiteral("val"), style_fillref_val);
    writer.writeEndElement();                                // val
    writer.writeEndElement();                                // fillRef
    writer.writeStartElement(QStringLiteral("a:effectRef")); // effectRef
    writer.writeAttribute(QStringLiteral("idx"), style_effectref_idx);
    writer.writeStartElement(QStringLiteral("a:schemeClr")); // val
    writer.writeAttribute(QStringLiteral("val"), style_effectref_val);
    writer.writeEndElement();                              // val
    writer.writeEndElement();                              // effectRef
    writer.writeStartElement(QStringLiteral("a:fontRef")); // fontRef
    writer.writeAttribute(QStringLiteral("idx"), style_forntref_idx);
    writer.writeStartElement(QStringLiteral("a:schemeClr")); // val
    writer.writeAttribute(QStringLiteral("val"), style_forntref_val);
    writer.writeEndElement(); // val
    writer.writeEndElement(); // fontRef
    writer.writeEndElement(); // style

    writer.writeEndElement(); // xdr:sp
}

void DrawingAnchor::saveXmlObjectGraphicFrame(QXmlStreamWriter &writer) const
{
    writer.writeStartElement(QStringLiteral("xdr:graphicFrame"));
    writer.writeAttribute(QStringLiteral("macro"), QString());

    writer.writeStartElement(QStringLiteral("xdr:nvGraphicFramePr"));
    writer.writeEmptyElement(QStringLiteral("xdr:cNvPr"));
    writer.writeAttribute(QStringLiteral("id"), QString::number(m_id));
    writer.writeAttribute(QStringLiteral("name"), QStringLiteral("Chart %1").arg(m_id));
    writer.writeEmptyElement(QStringLiteral("xdr:cNvGraphicFramePr"));
    writer.writeEndElement(); // xdr:nvGraphicFramePr

    writer.writeStartElement(QStringLiteral("xdr:xfrm"));
    writer.writeEndElement(); // xdr:xfrm

    writer.writeStartElement(QStringLiteral("a:graphic"));
    writer.writeStartElement(QStringLiteral("a:graphicData"));
    writer.writeAttribute(QStringLiteral("uri"),
                          QStringLiteral("http://schemas.openxmlformats.org/drawingml/2006/chart"));

    int idx = m_drawing->workbook->chartFiles().indexOf(m_chartFile);
    m_drawing->relationships()->addDocumentRelationship(
        QStringLiteral("/chart"), QStringLiteral("../charts/chart%1.xml").arg(idx + 1));

    writer.writeEmptyElement(QStringLiteral("c:chart"));
    writer.writeAttribute(QStringLiteral("xmlns:c"),
                          QStringLiteral("http://schemas.openxmlformats.org/drawingml/2006/chart"));
    writer.writeAttribute(
        QStringLiteral("xmlns:r"),
        QStringLiteral("http://schemas.openxmlformats.org/officeDocument/2006/relationships"));
    writer.writeAttribute(QStringLiteral("r:id"),
                          QStringLiteral("rId%1").arg(m_drawing->relationships()->count()));

    writer.writeEndElement(); // a:graphicData
    writer.writeEndElement(); // a:graphic
    writer.writeEndElement(); // xdr:graphicFrame
}

void DrawingAnchor::saveXmlObjectGroupShape(QXmlStreamWriter &writer) const
{
    Q_UNUSED(writer)
}

void DrawingAnchor::saveXmlObjectPicture(QXmlStreamWriter &writer) const
{
    Q_ASSERT(m_objectType == Picture && m_pictureFile);

    writer.writeStartElement(QStringLiteral("xdr:pic"));

    writer.writeStartElement(QStringLiteral("xdr:nvPicPr"));
    writer.writeEmptyElement(QStringLiteral("xdr:cNvPr"));
    writer.writeAttribute(QStringLiteral("id"), QString::number(m_id + 1)); // liufeijin
    writer.writeAttribute(QStringLiteral("name"), QStringLiteral("Picture %1").arg(m_id));

    writer.writeStartElement(QStringLiteral("xdr:cNvPicPr"));
    writer.writeEmptyElement(QStringLiteral("a:picLocks"));
    writer.writeAttribute(QStringLiteral("noChangeAspect"), QStringLiteral("1"));
    writer.writeEndElement(); // xdr:cNvPicPr

    writer.writeEndElement(); // xdr:nvPicPr

    m_drawing->relationships()->addDocumentRelationship(QStringLiteral("/image"),
                                                        QStringLiteral("../media/image%1.%2")
                                                            .arg(m_pictureFile->index() + 1)
                                                            .arg(m_pictureFile->suffix()));

    writer.writeStartElement(QStringLiteral("xdr:blipFill"));
    writer.writeEmptyElement(QStringLiteral("a:blip"));
    writer.writeAttribute(
        QStringLiteral("xmlns:r"),
        QStringLiteral("http://schemas.openxmlformats.org/officeDocument/2006/relationships"));
    writer.writeAttribute(QStringLiteral("r:embed"),
                          QStringLiteral("rId%1").arg(m_drawing->relationships()->count()));
    writer.writeStartElement(QStringLiteral("a:stretch"));
    writer.writeEmptyElement(QStringLiteral("a:fillRect"));
    writer.writeEndElement(); // a:stretch
    writer.writeEndElement(); // xdr:blipFill

    writer.writeStartElement(QStringLiteral("xdr:spPr"));

    writer.writeStartElement(QStringLiteral("a:prstGeom"));
    writer.writeAttribute(QStringLiteral("prst"), QStringLiteral("rect"));
    writer.writeEmptyElement(QStringLiteral("a:avLst"));
    writer.writeEndElement(); // a:prstGeom

    writer.writeEndElement(); // xdr:spPr

    writer.writeEndElement(); // xdr:pic
}

int DrawingAnchor::getm_id()
{
    return (this->m_id);
}

void DrawingAnchor::saveXmlObjectShape(QXmlStreamWriter &writer) const
{
    //{{ liufeijin
    writer.writeStartElement(QStringLiteral("xdr:sp")); // xdr:sp
    writer.writeAttribute(QStringLiteral("macro"), sp_macro);
    writer.writeAttribute(QStringLiteral("textlink"), sp_textlink);

    writer.writeStartElement(QStringLiteral("xdr:nvSpPr")); // xdr:nvSpPr

    writer.writeStartElement(QStringLiteral("xdr:cNvPr"));
    writer.writeAttribute(QStringLiteral("id"), xsp_cNvPR_id);
    writer.writeAttribute(QStringLiteral("name"), xsp_cNvPR_name);
    writer.writeStartElement(QStringLiteral("a:extLst"));
    writer.writeEndElement();
    writer.writeEndElement(); // xdr:cNvPr

    writer.writeEmptyElement(QStringLiteral("xdr:cNvSpPr"));

    writer.writeEndElement(); // xdr:nvSpPr

    writer.writeStartElement(QStringLiteral("xdr:spPr"));
    if (!xbwMode.isNull()) {
        writer.writeAttribute(QStringLiteral("bwMode"), xbwMode);
    }
    writer.writeStartElement(QStringLiteral("a:xfrm"));
    writer.writeEmptyElement(QStringLiteral("a:off"));
    writer.writeAttribute(QStringLiteral("x"), QString::number(posTA.x()));
    writer.writeAttribute(QStringLiteral("y"), QString::number(posTA.y()));
    writer.writeEmptyElement(QStringLiteral("a:ext"));
    writer.writeAttribute(QStringLiteral("cx"), QString::number(extTA.width()));
    writer.writeAttribute(QStringLiteral("cy"), QString::number(extTA.height()));
    writer.writeEndElement(); // a:xfrm

    writer.writeStartElement(QStringLiteral("a:prstGeom"));
    writer.writeAttribute(QStringLiteral("prst"), xprstGeom_prst);
    writer.writeEmptyElement(QStringLiteral("a:avLst"));
    writer.writeEndElement(); // a:prstGeom

    if (m_pictureFile) {
        m_drawing->relationships()->addDocumentRelationship(QStringLiteral("/image"),
                                                            QStringLiteral("../media/image%1.%2")
                                                                .arg(m_pictureFile->index() + 1)
                                                                .arg(m_pictureFile->suffix()));
        writer.writeStartElement(QStringLiteral("a:blipFill"));
        writer.writeAttribute(QStringLiteral("dpi"), QString::number(dpiTA));
        writer.writeAttribute(QStringLiteral("rotWithShape"), QString::number(rotWithShapeTA));

        writer.writeStartElement(QStringLiteral("a:blip"));
        writer.writeAttribute(
            QStringLiteral("r:embed"),
            QStringLiteral("rId%1").arg(
                m_drawing->relationships()
                    ->count())); // sp_blip_rembed
                                 // QStringLiteral("rId%1").arg(m_drawing->relationships()->count())
                                 // can't made new pic
        writer.writeAttribute(
            QStringLiteral("xmlns:r"),
            QStringLiteral("http://schemas.openxmlformats.org/officeDocument/2006/relationships"));
        if (!sp_blip_cstate.isNull()) {
            writer.writeAttribute(QStringLiteral("cstate"), sp_blip_cstate);
        }
        writer.writeEndElement(); // a:blip
        writer.writeEmptyElement(QStringLiteral("a:srcRect"));
        writer.writeStartElement(QStringLiteral("a:stretch"));
        writer.writeEmptyElement(QStringLiteral("a:fillRect"));
        writer.writeEndElement(); // a:stretch
        writer.writeEndElement(); // a:blipFill
    }
    writer.writeStartElement(QStringLiteral("a:ln"));
    if (!xIn_w.isEmpty() && !xIn_cap.isEmpty()) {
        if (!xIn_w.isEmpty()) {
            writer.writeAttribute(QStringLiteral("w"), xIn_w);
        }
        if (!xIn_cap.isEmpty()) {
            writer.writeAttribute(QStringLiteral("cap"), xIn_cap);
        }
        if (!xIn_cmpd.isEmpty()) {
            writer.writeAttribute(QStringLiteral("cmpd"), xIn_cmpd);
        }
        if (!xIn_algn.isEmpty()) {
            writer.writeAttribute(QStringLiteral("algn"), xIn_algn);
        }
    }
    if ((!x_headEnd_tyep.isEmpty()) || (!x_headEnd_w.isEmpty()) || (!x_headEnd_len.isEmpty())) {
        writer.writeEmptyElement(QStringLiteral("a:headEnd"));
        if (!x_headEnd_tyep.isEmpty()) {
            writer.writeAttribute(QStringLiteral("type"), x_headEnd_tyep);
        }
        if (!x_headEnd_w.isEmpty()) {
            writer.writeAttribute(QStringLiteral("w"), x_headEnd_w);
        }
        if (!x_headEnd_len.isEmpty()) {
            writer.writeAttribute(QStringLiteral("len"), x_headEnd_len);
        }
    }
    if ((!x_tailEnd_tyep.isEmpty()) || (!x_tailEnd_w.isEmpty()) || (!x_tailEnd_len.isEmpty())) {
        writer.writeEmptyElement(QStringLiteral("a:tailEnd"));
        if (!x_tailEnd_tyep.isEmpty()) {
            writer.writeAttribute(QStringLiteral("type"), x_tailEnd_tyep);
        }
        if (!x_tailEnd_w.isEmpty()) {
            writer.writeAttribute(QStringLiteral("w"), x_tailEnd_w);
        }
        if (!x_tailEnd_len.isEmpty()) {
            writer.writeAttribute(QStringLiteral("len"), x_tailEnd_len);
        }
    }

    writer.writeEndElement(); // a:ln

    writer.writeEndElement(); // xdr:spPr
    // writer style

    writer.writeStartElement(QStringLiteral("xdr:style")); // style
    writer.writeStartElement(QStringLiteral("a:lnRef"));   // lnRef
    writer.writeAttribute(QStringLiteral("idx"), Style_inref_idx);
    writer.writeStartElement(QStringLiteral("a:schemeClr")); // val
    writer.writeAttribute(QStringLiteral("val"), Style_inref_val);
    writer.writeEndElement();                              // val
    writer.writeEndElement();                              // lnRef
    writer.writeStartElement(QStringLiteral("a:fillRef")); // fillRef
    writer.writeAttribute(QStringLiteral("idx"), style_fillref_idx);
    writer.writeStartElement(QStringLiteral("a:schemeClr")); // val
    writer.writeAttribute(QStringLiteral("val"), style_fillref_val);
    writer.writeEndElement();                                // val
    writer.writeEndElement();                                // fillRef
    writer.writeStartElement(QStringLiteral("a:effectRef")); // effectRef
    writer.writeAttribute(QStringLiteral("idx"), style_effectref_idx);
    writer.writeStartElement(QStringLiteral("a:schemeClr")); // val
    writer.writeAttribute(QStringLiteral("val"), style_effectref_val);
    writer.writeEndElement();                              // val
    writer.writeEndElement();                              // effectRef
    writer.writeStartElement(QStringLiteral("a:fontRef")); // fontRef
    writer.writeAttribute(QStringLiteral("idx"), style_forntref_idx);
    writer.writeStartElement(QStringLiteral("a:schemeClr")); // val
    writer.writeAttribute(QStringLiteral("val"), style_forntref_val);
    writer.writeEndElement(); // val
    writer.writeEndElement(); // fontRef
    writer.writeEndElement(); // style

    writer.writeEndElement(); // xdr:sp

    //}} liufeijin
}

// absolute anchor

DrawingAbsoluteAnchor::DrawingAbsoluteAnchor(Drawing *drawing, ObjectType objectType)
    : DrawingAnchor(drawing, objectType)
{
}

// check point
bool DrawingAbsoluteAnchor::loadFromXml(QXmlStreamReader &reader)
{
    Q_ASSERT(reader.name() == QLatin1String("absoluteAnchor"));

    while (!reader.atEnd()) {
        reader.readNextStartElement();
        if (reader.tokenType() == QXmlStreamReader::StartElement) {
            if (reader.name() == QLatin1String("pos")) {
                pos = loadXmlPos(reader);
            } else if (reader.name() == QLatin1String("ext")) {
                ext = loadXmlExt(reader);
            } else {
                loadXmlObject(reader);
            }
        } else if (reader.tokenType() == QXmlStreamReader::EndElement &&
                   reader.name() == QLatin1String("absoluteAnchor")) {
            break;
        }
    }
    return true;
}

void DrawingAbsoluteAnchor::saveToXml(QXmlStreamWriter &writer) const
{
    writer.writeStartElement(QStringLiteral("xdr:absoluteAnchor"));
    saveXmlPos(writer, pos);
    saveXmlExt(writer, ext);

    saveXmlObject(writer);

    writer.writeEmptyElement(QStringLiteral("xdr:clientData"));
    writer.writeEndElement(); // xdr:absoluteAnchor
}

// one cell anchor

DrawingOneCellAnchor::DrawingOneCellAnchor(Drawing *drawing, ObjectType objectType)
    : DrawingAnchor(drawing, objectType)
{
}

int DrawingOneCellAnchor::row() const
{
    return from.row();
}

int DrawingOneCellAnchor::col() const
{
    return from.col();
}

// check point
bool DrawingOneCellAnchor::loadFromXml(QXmlStreamReader &reader)
{
    Q_ASSERT(reader.name() == QLatin1String("oneCellAnchor"));

    while (!reader.atEnd()) {
        reader.readNextStartElement();
        if (reader.tokenType() == QXmlStreamReader::StartElement) {
            if (reader.name() == QLatin1String("from")) {
                from = loadXmlMarker(reader, QLatin1String("from"));
            } else if (reader.name() == QLatin1String("ext")) {
                ext = loadXmlExt(reader);
            } else {
                loadXmlObject(reader);
            }
        } else if (reader.tokenType() == QXmlStreamReader::EndElement &&
                   reader.name() == QLatin1String("oneCellAnchor")) {
            break;
        }
    }
    return true;
}

void DrawingOneCellAnchor::saveToXml(QXmlStreamWriter &writer) const
{
    writer.writeStartElement(QStringLiteral("xdr:oneCellAnchor"));

    saveXmlMarker(writer, from, QStringLiteral("xdr:from"));
    saveXmlExt(writer, ext);

    saveXmlObject(writer);

    writer.writeEmptyElement(QStringLiteral("xdr:clientData"));
    writer.writeEndElement(); // xdr:oneCellAnchor
}

/*
   Two cell anchor

   This class specifies a two cell anchor placeholder for a group
   , a shape, or a drawing element. It moves with
   cells and its extents are in EMU units.
*/
DrawingTwoCellAnchor::DrawingTwoCellAnchor(Drawing *drawing, ObjectType objectType)
    : DrawingAnchor(drawing, objectType)
{
}

int DrawingTwoCellAnchor::row() const
{
    return from.row();
}

int DrawingTwoCellAnchor::col() const
{
    return from.col();
}

// check point
bool DrawingTwoCellAnchor::loadFromXml(QXmlStreamReader &reader)
{
    Q_ASSERT(reader.name() == QLatin1String("twoCellAnchor"));

    /*
    <xsd:complexType name="CT_TwoCellAnchor">
        <xsd:sequence>
            <xsd:element name="from" type="CT_Marker"/>
            <xsd:element name="to" type="CT_Marker"/>
            <xsd:group ref="EG_ObjectChoices"/>
            <xsd:element name="clientData" type="CT_AnchorClientData" minOccurs="1" maxOccurs="1"/>
        </xsd:sequence>
        <xsd:attribute name="editAs" type="ST_EditAs" use="optional" default="twoCell"/>
    </xsd:complexType>
    */

    //{{ liufeijin
    QXmlStreamAttributes attrs =
        reader.attributes(); // for absolute twocell added by liufeijin 20181024
    editASName = attrs.value(QLatin1String("editAs")).toString();
    //}}

    while (!reader.atEnd()) {
        reader.readNextStartElement();

        if (reader.tokenType() == QXmlStreamReader::StartElement) {
            if (reader.name() == QLatin1String("from")) {
                from = loadXmlMarker(reader, QLatin1String("from"));
            } else if (reader.name() == QLatin1String("to")) {
                to = loadXmlMarker(reader, QLatin1String("to"));
            } else if (reader.name() == QLatin1String("clientData")) {
                // clientData
            } else {
                /*
                <xsd:group name="EG_ObjectChoices">
                    <xsd:sequence>
                        <xsd:choice minOccurs="1" maxOccurs="1">
                            <xsd:element name="sp" type="CT_Shape"/>
                            <xsd:element name="grpSp" type="CT_GroupShape"/>
                            <xsd:element name="graphicFrame" type="CT_GraphicalObjectFrame"/>
                            <xsd:element name="cxnSp" type="CT_Connector"/>
                            <xsd:element name="pic" type="CT_Picture"/>
                            <xsd:element name="contentPart" type="CT_Rel"/>
                        </xsd:choice>
                    </xsd:sequence>
                </xsd:group>
                */

                loadXmlObject(reader);
            }
        } else if (reader.tokenType() == QXmlStreamReader::EndElement &&
                   reader.name() == QLatin1String("twoCellAnchor")) {
            break;
        }
    }
    return true;
}

void DrawingTwoCellAnchor::saveToXml(QXmlStreamWriter &writer) const
{
    writer.writeStartElement(QStringLiteral("xdr:twoCellAnchor"));

    //{{ liufeijin
    // writer.writeAttribute(QStringLiteral("editAs"), QStringLiteral("oneCell"));
    if (!editASName.isNull()) {
        writer.writeAttribute(QStringLiteral("editAs"), editASName); // QStringLiteral("oneCell")
    }
    // }}

    saveXmlMarker(writer, from, QStringLiteral("xdr:from"));
    saveXmlMarker(writer, to, QStringLiteral("xdr:to"));

    saveXmlObject(writer);

    writer.writeEmptyElement(QStringLiteral("xdr:clientData"));
    writer.writeEndElement(); // xdr:twoCellAnchor
}

QT_END_NAMESPACE_XLSX
