#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 #include #include #include #include #include #include #include #include #include #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(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 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(1, int(std::ceil(contentRect.width()))), qMax(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(" ]>")); writer.writeStartElement("VBfiles"); writer.writeAttribute("Version", "1.0"); QList items = scene->items(); foreach (QGraphicsItem *item, items) { PicTextItem *picText = qgraphicsitem_cast(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