265 lines
8.9 KiB
C++
265 lines
8.9 KiB
C++
#include "PicSceneLoader.h"
|
|
#include "picscene.h"
|
|
#include "pictextitem.h"
|
|
#include "PicImageItem.h"
|
|
#include "PicDateItem.h"
|
|
#include "PicScheduleShiftItem.h"
|
|
#include "piccodeitem.h"
|
|
#include "PicCounterItem.h"
|
|
|
|
#include <QFile>
|
|
#include <QXmlStreamReader>
|
|
#include <QXmlStreamWriter>
|
|
#include <QXmlStreamAttributes>
|
|
#include <QDebug>
|
|
#include <QImage>
|
|
#include <QPainter>
|
|
#include <QScopedPointer>
|
|
#include <cmath>
|
|
#include <unistd.h>
|
|
|
|
#include "../Printsetting.h"
|
|
|
|
namespace {
|
|
|
|
PicTextItem* createSceneItem(SourceType sourceType)
|
|
{
|
|
switch (sourceType) {
|
|
case IMAGE:
|
|
return new PicImageItem();
|
|
case DATETIME:
|
|
return new PicDateItem();
|
|
case SCHEDULE_SHIFT:
|
|
return new PicScheduleShiftItem();
|
|
case CODE:
|
|
return new PicCodeItem();
|
|
case COUNTER:
|
|
return new PicCounterItem();
|
|
case TEXT:
|
|
case UNKNOWN:
|
|
default:
|
|
return new PicTextItem(nullptr, nullptr, sourceType);
|
|
}
|
|
}
|
|
|
|
SourceType sourceTypeFromAttrs(const QXmlStreamAttributes& attrs)
|
|
{
|
|
if (!attrs.hasAttribute("sourceType")) {
|
|
return SourceType::UNKNOWN;
|
|
}
|
|
|
|
bool ok = false;
|
|
int value = attrs.value("sourceType").toInt(&ok);
|
|
if (!ok) {
|
|
return SourceType::UNKNOWN;
|
|
}
|
|
|
|
if (value < SourceType::UNKNOWN || value > SourceType::COUNTER) {
|
|
return SourceType::UNKNOWN;
|
|
}
|
|
|
|
return static_cast<SourceType>(value);
|
|
}
|
|
|
|
bool parseVbFileElement(QXmlStreamReader& reader, PicScene* scene)
|
|
{
|
|
float posX = 0.0f;
|
|
float posY = 0.0f;
|
|
bool condition = false;
|
|
int catchStartValue = -1;
|
|
int catchValue = -1;
|
|
QXmlStreamAttributes itemAttrs;
|
|
QString htmlCData;
|
|
|
|
while (!reader.atEnd()) {
|
|
QXmlStreamReader::TokenType token = reader.readNext();
|
|
if (token == QXmlStreamReader::StartElement) {
|
|
const QString elementName = reader.name().toString();
|
|
if (elementName == "pointer_x") {
|
|
posX = reader.readElementText().toDouble();
|
|
} else if (elementName == "pointer_y") {
|
|
posY = reader.readElementText().toDouble();
|
|
} else if (elementName == "Condition") {
|
|
condition = reader.readElementText().toInt() != 0;
|
|
} else if (elementName == "CatchStartValue") {
|
|
catchStartValue = reader.readElementText().toInt();
|
|
} else if (elementName == "CatchValue") {
|
|
catchValue = reader.readElementText().toInt();
|
|
} else if (elementName == "itemAttrs") {
|
|
itemAttrs = reader.attributes();
|
|
reader.skipCurrentElement();
|
|
} else if (elementName == "html_src") {
|
|
reader.skipCurrentElement();
|
|
} else {
|
|
reader.skipCurrentElement();
|
|
}
|
|
} else if (reader.isCDATA()) {
|
|
htmlCData += reader.text().toString();
|
|
} else if (token == QXmlStreamReader::EndElement && reader.name() == "VBfile") {
|
|
break;
|
|
}
|
|
}
|
|
|
|
SourceType sourceType = sourceTypeFromAttrs(itemAttrs);
|
|
PicTextItem* item = createSceneItem(sourceType);
|
|
if (!item) {
|
|
// 单个图元损坏不应影响整个文件的其余内容加载。
|
|
qWarning() << "PicSceneLoader: skip invalid VBfile item";
|
|
return true;
|
|
}
|
|
|
|
item->setAttrs(itemAttrs);
|
|
item->setPos(posX, posY);
|
|
item->SpecialConditions = condition;
|
|
item->CatchCnt1StartValue = catchStartValue;
|
|
item->CatchCnt1CurrValue = catchValue;
|
|
|
|
if (!htmlCData.isEmpty()) {
|
|
item->setHtml(htmlCData);
|
|
}
|
|
|
|
scene->addItem(item);
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace PicSceneLoader {
|
|
|
|
bool loadSceneFromFile(const QString& xmlFilename, PicScene* targetScene, PrintSettingModel* outPrintSettings)
|
|
{
|
|
if (!targetScene) {
|
|
qWarning() << "PicSceneLoader: targetScene is null";
|
|
return false;
|
|
}
|
|
|
|
QFile file(xmlFilename);
|
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
qWarning() << "PicSceneLoader: failed to open" << xmlFilename << file.errorString();
|
|
return false;
|
|
}
|
|
|
|
QXmlStreamReader reader(&file);
|
|
PrintSettingModel printSettings;
|
|
|
|
// Widget 对 scene 持有长期信号和 view 绑定,因此加载时必须复用现有 scene。
|
|
targetScene->clear();
|
|
|
|
while (!reader.atEnd()) {
|
|
reader.readNext();
|
|
if (reader.isStartElement() && reader.name() == "VBfiles") {
|
|
while (reader.readNextStartElement()) {
|
|
if (reader.name() == "VBfile") {
|
|
if (!parseVbFileElement(reader, targetScene)) {
|
|
return false;
|
|
}
|
|
} else if (reader.name() == "PrintSettings") {
|
|
PrintSettingModel::loadFromXml(printSettings, reader.attributes());
|
|
reader.skipCurrentElement();
|
|
} else {
|
|
reader.skipCurrentElement();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (reader.hasError()) {
|
|
qWarning() << "PicSceneLoader: XML parse error" << reader.errorString()
|
|
<< "at" << reader.lineNumber() << ":" << reader.columnNumber();
|
|
return false;
|
|
}
|
|
|
|
if (outPrintSettings) {
|
|
*outPrintSettings = printSettings;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
QImage previewSceneFromFile(const QString &xmlFilename)
|
|
{
|
|
// 预览刻意复用与正常打开相同的加载路径,避免两套逻辑逐渐产生偏差。
|
|
QScopedPointer<PicScene> scene(new PicScene());
|
|
if (!loadSceneFromFile(xmlFilename, scene.data(), nullptr)) {
|
|
return QImage();
|
|
}
|
|
|
|
QRectF contentRect = scene->itemsBoundingRect();
|
|
if (!contentRect.isValid() || contentRect.isEmpty()) {
|
|
return QImage(1, 1, QImage::Format_ARGB32_Premultiplied);
|
|
}
|
|
|
|
QSize imageSize(qMax<int>(1, int(std::ceil(contentRect.width()))),
|
|
qMax<int>(1, int(std::ceil(contentRect.height()))));
|
|
QImage image(imageSize, QImage::Format_ARGB32_Premultiplied);
|
|
image.fill(Qt::transparent);
|
|
|
|
QPainter painter(&image);
|
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
|
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
|
|
|
// 以 itemsBoundingRect 作为渲染源区域,预览图会裁剪到实际内容,而不是整个 sceneRect。
|
|
scene->render(&painter, QRectF(QPointF(0, 0), imageSize), contentRect);
|
|
painter.end();
|
|
|
|
return image;
|
|
}
|
|
|
|
bool saveSceneToXmlFile(const QString &xmlFilename, PicScene *scene, const PrintSettingModel *printSettings)
|
|
{
|
|
if (!scene)
|
|
return false;
|
|
|
|
QFile file(xmlFilename);
|
|
if (!file.open(QFile::WriteOnly | QFile::Text)) {
|
|
qDebug() << QString("Cannot write file %1(%2).").arg(xmlFilename).arg(file.errorString());
|
|
return false;
|
|
}
|
|
QXmlStreamWriter writer(&file);
|
|
writer.setAutoFormatting(true);
|
|
writer.writeStartDocument("1.0", true);
|
|
writer.writeComment(QString::fromLocal8Bit("Vernon Bach graphic scene file"));
|
|
writer.writeDTD(QString::fromLocal8Bit("<!DOCTYPE VBfiles [ <!ELEMENT VBfiles (VBfile) > <!ELEMENT VBfile (pointer_x,pointer_y,html_src)> <!ELEMENT pointer_x (#PCDATA)><!ELEMENT pointer_y (#PCDATA)><!ELEMENT html_src (#PCDATA)> ]>"));
|
|
writer.writeStartElement("VBfiles");
|
|
writer.writeAttribute("Version", "1.0");
|
|
|
|
QList<QGraphicsItem *> items = scene->items();
|
|
foreach (QGraphicsItem *item, items) {
|
|
PicTextItem *picText = qgraphicsitem_cast<PicTextItem*>(item);
|
|
if (picText == nullptr)
|
|
continue;
|
|
|
|
writer.writeStartElement("VBfile");
|
|
writer.writeTextElement(QString::fromLocal8Bit("pointer_x"), QString::fromLocal8Bit("%1").arg(picText->pos().x()));
|
|
writer.writeTextElement(QString::fromLocal8Bit("pointer_y"), QString::fromLocal8Bit("%1").arg(picText->pos().y()));
|
|
writer.writeTextElement(QString::fromLocal8Bit("Condition"), QString::fromLocal8Bit("%1").arg(picText->SpecialConditions ? 1 : 0));
|
|
writer.writeTextElement(QString::fromLocal8Bit("CatchStartValue"), QString::fromLocal8Bit("%1").arg(picText->CatchCnt1StartValue));
|
|
writer.writeTextElement(QString::fromLocal8Bit("CatchValue"), QString::fromLocal8Bit("%1").arg(picText->CatchCnt1CurrValue));
|
|
|
|
QXmlStreamAttributes attrs;
|
|
attrs.append("sourceType", QString::number(picText->sourceType()));
|
|
if (picText->sourceType() != SourceType::UNKNOWN) {
|
|
picText->saveAttrs(attrs);
|
|
}
|
|
writer.writeStartElement("itemAttrs");
|
|
writer.writeAttributes(attrs);
|
|
writer.writeEndElement();
|
|
|
|
writer.writeTextElement(QString::fromLocal8Bit("html_src"), QString::fromLocal8Bit("my_src"));
|
|
writer.writeCDATA(picText->toHtml());
|
|
writer.writeEndElement();
|
|
}
|
|
|
|
if (printSettings) {
|
|
writer.writeEmptyElement("PrintSettings");
|
|
PrintSettingModel::saveToXml(*printSettings, writer);
|
|
}
|
|
|
|
writer.writeEndElement();
|
|
writer.writeEndDocument();
|
|
fsync(file.handle());
|
|
file.close();
|
|
return true;
|
|
}
|
|
|
|
} // namespace PicSceneLoader
|