File indexing completed on 2024-04-14 04:47:22
0001 /* 0002 SPDX-FileCopyrightText: 2008 Marco Gittler <g.marco@freenet.de> 0003 SPDX-FileCopyrightText: Rafał Lalik 0004 0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0006 */ 0007 0008 /* 0009 * * 0010 * Modifications by Rafał Lalik to implement Patterns mechanism * 0011 * * 0012 ***************************************************************************/ 0013 0014 #include "titledocument.h" 0015 #include "gradientwidget.h" 0016 0017 #include "graphicsscenerectmove.h" 0018 #include "kdenlivesettings.h" 0019 #include "utils/timecode.h" 0020 #include "xml/xml.hpp" 0021 0022 #include <KIO/FileCopyJob> 0023 #include <KLocalizedString> 0024 #include <KMessageBox> 0025 0026 #include "kdenlive_debug.h" 0027 #include <QApplication> 0028 #include <QCryptographicHash> 0029 #include <QDir> 0030 #include <QDomElement> 0031 #include <QFile> 0032 #include <QFontInfo> 0033 #include <QGraphicsItem> 0034 #include <QGraphicsRectItem> 0035 #include <QGraphicsScene> 0036 #include <QGraphicsSvgItem> 0037 #include <QGraphicsTextItem> 0038 #include <QSaveFile> 0039 #include <QSvgRenderer> 0040 #include <QTextCursor> 0041 #include <locale> 0042 #ifdef Q_OS_MAC 0043 #include <xlocale.h> 0044 #endif 0045 0046 #include <QGraphicsBlurEffect> 0047 #include <QGraphicsDropShadowEffect> 0048 #include <QGraphicsEffect> 0049 #include <QPainter> 0050 0051 QByteArray fileToByteArray(const QString &filename) 0052 { 0053 QByteArray ret; 0054 QFile file(filename); 0055 if (file.open(QIODevice::ReadOnly)) { 0056 while (!file.atEnd()) { 0057 ret.append(file.readLine()); 0058 } 0059 } 0060 return ret; 0061 } 0062 0063 TitleDocument::TitleDocument() 0064 { 0065 m_scene = nullptr; 0066 m_width = 0; 0067 m_height = 0; 0068 m_missingElements = 0; 0069 } 0070 0071 void TitleDocument::setScene(QGraphicsScene *_scene, int width, int height) 0072 { 0073 m_scene = _scene; 0074 m_width = width; 0075 m_height = height; 0076 } 0077 0078 int TitleDocument::base64ToUrl(QGraphicsItem *item, QDomElement &content, bool embed, const QString &projectPath) 0079 { 0080 if (embed) { 0081 if (!item->data(Qt::UserRole + 1).toString().isEmpty()) { 0082 content.setAttribute(QStringLiteral("base64"), item->data(Qt::UserRole + 1).toString()); 0083 } else if (!item->data(Qt::UserRole).toString().isEmpty()) { 0084 content.setAttribute(QStringLiteral("base64"), fileToByteArray(item->data(Qt::UserRole).toString()).toBase64().data()); 0085 } 0086 content.removeAttribute(QStringLiteral("url")); 0087 } else { 0088 // save for project files to disk 0089 QString base64 = item->data(Qt::UserRole + 1).toString(); 0090 if (!base64.isEmpty()) { 0091 QString titlePath; 0092 if (!projectPath.isEmpty()) { 0093 titlePath = projectPath; 0094 } else { 0095 titlePath = QStringLiteral("/tmp/titles/"); 0096 } 0097 QString filename = extractBase64Image(titlePath, base64); 0098 if (!filename.isEmpty()) { 0099 content.setAttribute(QStringLiteral("url"), filename); 0100 content.removeAttribute(QStringLiteral("base64")); 0101 } 0102 0103 } else { 0104 return 1; 0105 } 0106 } 0107 return 0; 0108 } 0109 0110 // static 0111 const QString TitleDocument::extractBase64Image(const QString &titlePath, const QString &data) 0112 { 0113 QDir dir(titlePath); 0114 dir.mkpath(titlePath); 0115 const QString filename = dir.absoluteFilePath(QString(QCryptographicHash::hash(data.toLatin1(), QCryptographicHash::Md5).toHex().append(".titlepart"))); 0116 QFile f(filename); 0117 if (f.open(QIODevice::WriteOnly)) { 0118 f.write(QByteArray::fromBase64(data.toLatin1())); 0119 f.close(); 0120 return filename; 0121 } 0122 return QString(); 0123 } 0124 0125 QDomDocument TitleDocument::xml(QGraphicsRectItem *startv, QGraphicsRectItem *endv, bool embed) 0126 { 0127 return xml(m_scene->items(), m_width, m_height, startv, endv, embed, m_projectPath); 0128 } 0129 0130 QDomDocument TitleDocument::xml(const QList<QGraphicsItem *> &items, int width, int height, QGraphicsRectItem *startv, QGraphicsRectItem *endv, bool embed, 0131 const QString &projectPath) 0132 { 0133 QDomDocument doc; 0134 0135 QDomElement main = doc.createElement(QStringLiteral("kdenlivetitle")); 0136 main.setAttribute(QStringLiteral("width"), width); 0137 main.setAttribute(QStringLiteral("height"), height); 0138 0139 // Save locale. Since 20.08, we always use the C locale for serialising. 0140 main.setAttribute(QStringLiteral("LC_NUMERIC"), "C"); 0141 doc.appendChild(main); 0142 QTextCursor cur; 0143 QTextBlockFormat format; 0144 0145 for (QGraphicsItem *item : items) { 0146 if (!(item->flags() & QGraphicsItem::ItemIsSelectable)) { 0147 continue; 0148 } 0149 QDomElement e = doc.createElement(QStringLiteral("item")); 0150 QDomElement content = doc.createElement(QStringLiteral("content")); 0151 QFont font; 0152 QString gradient; 0153 MyTextItem *t; 0154 double xPosition = item->pos().x(); 0155 0156 switch (item->type()) { 0157 case 7: 0158 e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsPixmapItem")); 0159 content.setAttribute(QStringLiteral("url"), item->data(Qt::UserRole).toString()); 0160 base64ToUrl(item, content, embed, projectPath); 0161 break; 0162 case 13: 0163 e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsSvgItem")); 0164 content.setAttribute(QStringLiteral("url"), item->data(Qt::UserRole).toString()); 0165 base64ToUrl(item, content, embed, projectPath); 0166 break; 0167 case 3: 0168 e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsRectItem")); 0169 content.setAttribute(QStringLiteral("rect"), rectFToString(static_cast<QGraphicsRectItem *>(item)->rect().normalized())); 0170 content.setAttribute(QStringLiteral("pencolor"), colorToString(static_cast<QGraphicsRectItem *>(item)->pen().color())); 0171 if (static_cast<QGraphicsRectItem *>(item)->pen() == Qt::NoPen) { 0172 content.setAttribute(QStringLiteral("penwidth"), 0); 0173 } else { 0174 content.setAttribute(QStringLiteral("penwidth"), static_cast<QGraphicsRectItem *>(item)->pen().width()); 0175 } 0176 content.setAttribute(QStringLiteral("brushcolor"), colorToString(static_cast<QGraphicsRectItem *>(item)->brush().color())); 0177 gradient = item->data(TitleDocument::Gradient).toString(); 0178 if (!gradient.isEmpty()) { 0179 content.setAttribute(QStringLiteral("gradient"), gradient); 0180 } 0181 break; 0182 case 4: 0183 e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsEllipseItem")); 0184 content.setAttribute(QStringLiteral("rect"), rectFToString(static_cast<QGraphicsEllipseItem *>(item)->rect().normalized())); 0185 content.setAttribute(QStringLiteral("pencolor"), colorToString(static_cast<QGraphicsEllipseItem *>(item)->pen().color())); 0186 if (static_cast<QGraphicsEllipseItem *>(item)->pen() == Qt::NoPen) { 0187 content.setAttribute(QStringLiteral("penwidth"), 0); 0188 } else { 0189 content.setAttribute(QStringLiteral("penwidth"), static_cast<QGraphicsEllipseItem *>(item)->pen().width()); 0190 } 0191 content.setAttribute(QStringLiteral("brushcolor"), colorToString(static_cast<QGraphicsEllipseItem *>(item)->brush().color())); 0192 gradient = item->data(TitleDocument::Gradient).toString(); 0193 if (!gradient.isEmpty()) { 0194 content.setAttribute(QStringLiteral("gradient"), gradient); 0195 } 0196 break; 0197 case 8: 0198 e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsTextItem")); 0199 t = static_cast<MyTextItem *>(item); 0200 // Don't save empty text nodes 0201 if (t->toPlainText().simplified().isEmpty()) { 0202 continue; 0203 } 0204 // content.appendChild(doc.createTextNode(((QGraphicsTextItem*)item)->toHtml())); 0205 content.appendChild(doc.createTextNode(t->toPlainText())); 0206 font = t->font(); 0207 content.setAttribute(QStringLiteral("font"), font.family()); 0208 content.setAttribute(QStringLiteral("font-weight"), font.weight()); 0209 content.setAttribute(QStringLiteral("font-pixel-size"), font.pixelSize()); 0210 content.setAttribute(QStringLiteral("font-italic"), static_cast<int>(font.italic())); 0211 content.setAttribute(QStringLiteral("font-underline"), static_cast<int>(font.underline())); 0212 content.setAttribute(QStringLiteral("letter-spacing"), QString::number(font.letterSpacing())); 0213 gradient = item->data(TitleDocument::Gradient).toString(); 0214 if (!gradient.isEmpty()) { 0215 content.setAttribute(QStringLiteral("gradient"), gradient); 0216 } 0217 cur = QTextCursor(t->document()); 0218 cur.select(QTextCursor::Document); 0219 format = cur.blockFormat(); 0220 if (t->toPlainText() == QLatin1String("%s")) { 0221 // template text box, adjust size for later replacement text 0222 if (t->alignment() == Qt::AlignHCenter) { 0223 // grow dimensions on both sides 0224 double xcenter = item->pos().x() + (t->baseBoundingRect().width()) / 2; 0225 double offset = qMin(xcenter, width - xcenter); 0226 xPosition = xcenter - offset; 0227 content.setAttribute(QStringLiteral("box-width"), QString::number(2 * offset)); 0228 } else if (t->alignment() == Qt::AlignRight) { 0229 // grow to the left 0230 double offset = item->pos().x() + (t->baseBoundingRect().width()); 0231 xPosition = 0; 0232 content.setAttribute(QStringLiteral("box-width"), QString::number(offset)); 0233 } else { 0234 // left align, grow on right side 0235 double offset = width - item->pos().x(); 0236 content.setAttribute(QStringLiteral("box-width"), QString::number(offset)); 0237 } 0238 } else { 0239 content.setAttribute(QStringLiteral("box-width"), QString::number(t->baseBoundingRect().width())); 0240 } 0241 content.setAttribute(QStringLiteral("box-height"), QString::number(t->baseBoundingRect().height())); 0242 if (!t->data(TitleDocument::LineSpacing).isNull()) { 0243 content.setAttribute(QStringLiteral("line-spacing"), QString::number(t->data(TitleDocument::LineSpacing).toInt())); 0244 } 0245 { 0246 QTextCursor cursor(t->document()); 0247 cursor.select(QTextCursor::Document); 0248 QColor fontcolor = cursor.charFormat().foreground().color(); 0249 content.setAttribute(QStringLiteral("font-color"), colorToString(fontcolor)); 0250 if (!t->data(TitleDocument::OutlineWidth).isNull()) { 0251 content.setAttribute(QStringLiteral("font-outline"), QString::number(t->data(TitleDocument::OutlineWidth).toDouble())); 0252 } 0253 if (!t->data(TitleDocument::OutlineColor).isNull()) { 0254 QVariant variant = t->data(TitleDocument::OutlineColor); 0255 QColor outlineColor = variant.value<QColor>(); 0256 content.setAttribute(QStringLiteral("font-outline-color"), colorToString(outlineColor)); 0257 } 0258 } 0259 if (!t->data(100).isNull()) { 0260 QStringList effectParams = t->data(100).toStringList(); 0261 QString effectName = effectParams.takeFirst(); 0262 content.setAttribute(QStringLiteral("textwidth"), QString::number(t->sceneBoundingRect().width())); 0263 content.setAttribute(effectName, effectParams.join(QLatin1Char(';'))); 0264 } 0265 0266 // Only save when necessary. 0267 if (t->data(OriginXLeft).toInt() == AxisInverted) { 0268 content.setAttribute(QStringLiteral("kdenlive-axis-x-inverted"), t->data(OriginXLeft).toInt()); 0269 } 0270 if (t->data(OriginYTop).toInt() == AxisInverted) { 0271 content.setAttribute(QStringLiteral("kdenlive-axis-y-inverted"), t->data(OriginYTop).toInt()); 0272 } 0273 if (t->textWidth() > 0) { 0274 content.setAttribute(QStringLiteral("alignment"), int(t->alignment())); 0275 } 0276 0277 content.setAttribute(QStringLiteral("shadow"), t->shadowInfo().join(QLatin1Char(';'))); 0278 content.setAttribute(QStringLiteral("typewriter"), t->twInfo().join(QLatin1Char(';'))); 0279 break; 0280 default: 0281 continue; 0282 } 0283 0284 // position 0285 QDomElement pos = doc.createElement(QStringLiteral("position")); 0286 pos.setAttribute(QStringLiteral("x"), QString::number(xPosition)); 0287 pos.setAttribute(QStringLiteral("y"), QString::number(item->pos().y())); 0288 QTransform transform = item->transform(); 0289 QDomElement tr = doc.createElement(QStringLiteral("transform")); 0290 if (!item->data(TitleDocument::ZoomFactor).isNull()) { 0291 tr.setAttribute(QStringLiteral("zoom"), QString::number(item->data(TitleDocument::ZoomFactor).toInt())); 0292 } 0293 if (!item->data(TitleDocument::RotateFactor).isNull()) { 0294 QList<QVariant> rotlist = item->data(TitleDocument::RotateFactor).toList(); 0295 tr.setAttribute(QStringLiteral("rotation"), 0296 QStringLiteral("%1,%2,%3").arg(rotlist[0].toDouble()).arg(rotlist[1].toDouble()).arg(rotlist[2].toDouble())); 0297 } 0298 tr.appendChild(doc.createTextNode(QStringLiteral("%1,%2,%3,%4,%5,%6,%7,%8,%9") 0299 .arg(transform.m11()) 0300 .arg(transform.m12()) 0301 .arg(transform.m13()) 0302 .arg(transform.m21()) 0303 .arg(transform.m22()) 0304 .arg(transform.m23()) 0305 .arg(transform.m31()) 0306 .arg(transform.m32()) 0307 .arg(transform.m33()))); 0308 e.setAttribute(QStringLiteral("z-index"), item->zValue()); 0309 pos.appendChild(tr); 0310 e.appendChild(pos); 0311 e.appendChild(content); 0312 if (item->zValue() > -1000) { 0313 main.appendChild(e); 0314 } 0315 } 0316 if ((startv != nullptr) && (endv != nullptr)) { 0317 QDomElement endport = doc.createElement(QStringLiteral("endviewport")); 0318 QDomElement startport = doc.createElement(QStringLiteral("startviewport")); 0319 QRectF r(endv->pos().x(), endv->pos().y(), endv->rect().width(), endv->rect().height()); 0320 endport.setAttribute(QStringLiteral("rect"), rectFToString(r)); 0321 QRectF r2(startv->pos().x(), startv->pos().y(), startv->rect().width(), startv->rect().height()); 0322 startport.setAttribute(QStringLiteral("rect"), rectFToString(r2)); 0323 0324 main.appendChild(startport); 0325 main.appendChild(endport); 0326 } 0327 QDomElement backgr = doc.createElement(QStringLiteral("background")); 0328 QColor color = getBackgroundColor(items); 0329 backgr.setAttribute(QStringLiteral("color"), colorToString(color)); 0330 main.appendChild(backgr); 0331 0332 return doc; 0333 } 0334 0335 /** \brief Get the background color (incl. alpha) from the document, if possibly 0336 * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */ 0337 QColor TitleDocument::getBackgroundColor() const 0338 { 0339 QColor color(0, 0, 0, 0); 0340 if (m_scene) { 0341 return getBackgroundColor(m_scene->items()); 0342 } 0343 return color; 0344 } 0345 0346 /** \brief Get the background color (incl. alpha) from list of items, if possibly 0347 * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */ 0348 QColor TitleDocument::getBackgroundColor(const QList<QGraphicsItem *> &items) 0349 { 0350 QColor color(0, 0, 0, 0); 0351 for (auto item : qAsConst(items)) { 0352 if (int(item->zValue()) == -1100) { 0353 color = static_cast<QGraphicsRectItem *>(item)->brush().color(); 0354 return color; 0355 } 0356 } 0357 return color; 0358 } 0359 0360 bool TitleDocument::saveDocument(const QUrl &url, QGraphicsRectItem *startv, QGraphicsRectItem *endv, int duration, bool embed) 0361 { 0362 if (!m_scene) { 0363 return false; 0364 } 0365 0366 QDomDocument doc = xml(startv, endv, embed); 0367 doc.documentElement().setAttribute(QStringLiteral("duration"), duration); 0368 // keep some time for backwards compatibility (opening projects with older versions) - 26/12/12 0369 doc.documentElement().setAttribute(QStringLiteral("out"), duration); 0370 return Xml::docContentToFile(doc, url.toLocalFile()); 0371 } 0372 0373 int TitleDocument::loadFromXml(const QDomDocument &doc, GraphicsSceneRectMove *scene, QGraphicsRectItem *startv, QGraphicsRectItem *endv, int *duration, 0374 const QString &projectpath) 0375 { 0376 m_projectPath = projectpath; 0377 0378 QList<QGraphicsItem *> items; 0379 0380 int width, height; 0381 int res = loadFromXml(doc, items, width, height, scene, startv, endv, duration, m_missingElements); 0382 0383 if (m_width != width || m_height != height) { 0384 KMessageBox::information(QApplication::activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile")); 0385 // TODO: convert using QTransform 0386 m_width = width; 0387 m_height = height; 0388 } 0389 0390 for (auto i : qAsConst(items)) { 0391 m_scene->addItem(i); 0392 } 0393 return res; 0394 } 0395 0396 int TitleDocument::loadFromXml(const QDomDocument &doc, QList<QGraphicsItem *> &gitems, int &width, int &height, GraphicsSceneRectMove *scene, 0397 QGraphicsRectItem *startv, QGraphicsRectItem *endv, int *duration, int &missingElements) 0398 { 0399 for (auto *i : gitems) { 0400 delete i; 0401 } 0402 gitems.clear(); 0403 0404 missingElements = 0; 0405 QDomNodeList titles = doc.elementsByTagName(QStringLiteral("kdenlivetitle")); 0406 // TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale 0407 if (doc.documentElement().hasAttribute(QStringLiteral("width")) && doc.documentElement().hasAttribute(QStringLiteral("height"))) { 0408 width = doc.documentElement().attribute(QStringLiteral("width")).toInt(); 0409 height = doc.documentElement().attribute(QStringLiteral("height")).toInt(); 0410 } else { 0411 // Document has no size info, it is likely an old version title, so ignore viewport data 0412 QDomNodeList viewportlist = doc.documentElement().elementsByTagName(QStringLiteral("startviewport")); 0413 if (!viewportlist.isEmpty()) { 0414 doc.documentElement().removeChild(viewportlist.at(0)); 0415 } 0416 viewportlist = doc.documentElement().elementsByTagName(QStringLiteral("endviewport")); 0417 if (!viewportlist.isEmpty()) { 0418 doc.documentElement().removeChild(viewportlist.at(0)); 0419 } 0420 } 0421 if (doc.documentElement().hasAttribute(QStringLiteral("duration"))) { 0422 *duration = doc.documentElement().attribute(QStringLiteral("duration")).toInt(); 0423 } else if (doc.documentElement().hasAttribute(QStringLiteral("out"))) { 0424 *duration = doc.documentElement().attribute(QStringLiteral("out")).toInt(); 0425 } else { 0426 *duration = Timecode().getFrameCount(KdenliveSettings::title_duration()); 0427 } 0428 0429 int maxZValue = 0; 0430 if (!titles.isEmpty()) { 0431 QDomNodeList items = titles.item(0).childNodes(); 0432 for (int i = 0; i < items.count(); ++i) { 0433 QGraphicsItem *gitem = nullptr; 0434 QDomNode itemNode = items.item(i); 0435 // qCDebug(KDENLIVE_LOG) << items.item(i).attributes().namedItem("type").nodeValue(); 0436 int zValue = itemNode.attributes().namedItem(QStringLiteral("z-index")).nodeValue().toInt(); 0437 double xPosition = itemNode.namedItem(QStringLiteral("position")).attributes().namedItem(QStringLiteral("x")).nodeValue().toDouble(); 0438 if (zValue > -1000) { 0439 if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsTextItem")) { 0440 QDomNamedNodeMap txtProperties = itemNode.namedItem(QStringLiteral("content")).attributes(); 0441 QFont font(txtProperties.namedItem(QStringLiteral("font")).nodeValue()); 0442 0443 QDomNode node = txtProperties.namedItem(QStringLiteral("font-bold")); 0444 if (!node.isNull()) { 0445 // Old: Bold/Not bold. 0446 font.setBold(node.nodeValue().toInt() != 0); 0447 } else { 0448 // New: Font weight (QFont::) 0449 font.setWeight(QFont::Weight(txtProperties.namedItem(QStringLiteral("font-weight")).nodeValue().toInt())); 0450 } 0451 // font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt()); 0452 font.setItalic(txtProperties.namedItem(QStringLiteral("font-italic")).nodeValue().toInt() != 0); 0453 font.setUnderline(txtProperties.namedItem(QStringLiteral("font-underline")).nodeValue().toInt() != 0); 0454 // Older Kdenlive version did not store pixel size but point size 0455 if (txtProperties.namedItem(QStringLiteral("font-pixel-size")).isNull()) { 0456 KMessageBox::information(QApplication::activeWindow(), 0457 i18n("Some of your text clips were saved with size in points, which means " 0458 "different sizes on different displays. They will be converted to pixel " 0459 "size, making them portable, but you could have to adjust their size."), 0460 i18n("Text Clips Updated")); 0461 QFont f2; 0462 f2.setPointSize(txtProperties.namedItem(QStringLiteral("font-size")).nodeValue().toInt()); 0463 font.setPixelSize(QFontInfo(f2).pixelSize()); 0464 } else { 0465 font.setPixelSize(txtProperties.namedItem(QStringLiteral("font-pixel-size")).nodeValue().toInt()); 0466 } 0467 font.setLetterSpacing(QFont::AbsoluteSpacing, txtProperties.namedItem(QStringLiteral("letter-spacing")).nodeValue().toInt()); 0468 QColor col(stringToColor(txtProperties.namedItem(QStringLiteral("font-color")).nodeValue())); 0469 MyTextItem *txt = new MyTextItem(itemNode.namedItem(QStringLiteral("content")).firstChild().nodeValue(), nullptr); 0470 gitems.append(txt); 0471 txt->setFont(font); 0472 txt->setTextInteractionFlags(Qt::NoTextInteraction); 0473 QTextCursor cursor(txt->document()); 0474 cursor.select(QTextCursor::Document); 0475 QTextCharFormat cformat = cursor.charFormat(); 0476 if (txtProperties.namedItem(QStringLiteral("font-outline")).nodeValue().toDouble() > 0.0) { 0477 txt->setData(TitleDocument::OutlineWidth, txtProperties.namedItem(QStringLiteral("font-outline")).nodeValue().toDouble()); 0478 txt->setData(TitleDocument::OutlineColor, stringToColor(txtProperties.namedItem(QStringLiteral("font-outline-color")).nodeValue())); 0479 cformat.setTextOutline(QPen(QColor(stringToColor(txtProperties.namedItem(QStringLiteral("font-outline-color")).nodeValue())), 0480 txtProperties.namedItem(QStringLiteral("font-outline")).nodeValue().toDouble(), Qt::SolidLine, Qt::RoundCap, 0481 Qt::RoundJoin)); 0482 } 0483 if (!txtProperties.namedItem(QStringLiteral("line-spacing")).isNull()) { 0484 int lineSpacing = txtProperties.namedItem(QStringLiteral("line-spacing")).nodeValue().toInt(); 0485 QTextBlockFormat format = cursor.blockFormat(); 0486 format.setLineHeight(lineSpacing, QTextBlockFormat::LineDistanceHeight); 0487 cursor.setBlockFormat(format); 0488 txt->setData(TitleDocument::LineSpacing, lineSpacing); 0489 } 0490 txt->setTextColor(col); 0491 cformat.setForeground(QBrush(col)); 0492 cursor.setCharFormat(cformat); 0493 if (!txtProperties.namedItem(QStringLiteral("gradient")).isNull()) { 0494 // Gradient color 0495 QString data = txtProperties.namedItem(QStringLiteral("gradient")).nodeValue(); 0496 txt->setData(TitleDocument::Gradient, data); 0497 QLinearGradient gr = GradientWidget::gradientFromString(data, int(txt->boundingRect().width()), int(txt->boundingRect().height())); 0498 cformat.setForeground(QBrush(gr)); 0499 cursor.setCharFormat(cformat); 0500 } 0501 0502 if (!txtProperties.namedItem(QStringLiteral("alignment")).isNull()) { 0503 txt->setAlignment(Qt::Alignment(txtProperties.namedItem(QStringLiteral("alignment")).nodeValue().toInt())); 0504 } 0505 0506 if (!txtProperties.namedItem(QStringLiteral("kdenlive-axis-x-inverted")).isNull()) { 0507 txt->setData(OriginXLeft, txtProperties.namedItem(QStringLiteral("kdenlive-axis-x-inverted")).nodeValue().toInt()); 0508 } 0509 if (!txtProperties.namedItem(QStringLiteral("kdenlive-axis-y-inverted")).isNull()) { 0510 txt->setData(OriginYTop, txtProperties.namedItem(QStringLiteral("kdenlive-axis-y-inverted")).nodeValue().toInt()); 0511 } 0512 0513 if (!txtProperties.namedItem(QStringLiteral("shadow")).isNull()) { 0514 QString info = txtProperties.namedItem(QStringLiteral("shadow")).nodeValue(); 0515 txt->loadShadow(info.split(QLatin1Char(';'))); 0516 } 0517 0518 // Effects 0519 if (!txtProperties.namedItem(QStringLiteral("typewriter")).isNull()) { 0520 QString info = txtProperties.namedItem(QStringLiteral("typewriter")).nodeValue(); 0521 txt->loadTW(info.split(QLatin1Char(';'))); 0522 } 0523 if (txt->toPlainText() == QLatin1String("%s")) { 0524 // template text box, adjust size for later replacement text 0525 if (txt->alignment() == Qt::AlignHCenter) { 0526 // grow dimensions on both sides 0527 double boxWidth = txtProperties.namedItem(QStringLiteral("box-width")).nodeValue().toDouble(); 0528 double xcenter = (boxWidth - xPosition) / 2.0; 0529 xPosition = xcenter - txt->boundingRect().width() / 2; 0530 } else if (txt->alignment() == Qt::AlignRight) { 0531 // grow to the left 0532 xPosition = xPosition + txtProperties.namedItem(QStringLiteral("box-width")).nodeValue().toDouble() - txt->boundingRect().width(); 0533 } else { 0534 // left align, grow on right side, nothing to do 0535 } 0536 } 0537 0538 gitem = txt; 0539 } else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsRectItem")) { 0540 QDomNamedNodeMap rectProperties = itemNode.namedItem(QStringLiteral("content")).attributes(); 0541 QString rect = rectProperties.namedItem(QStringLiteral("rect")).nodeValue(); 0542 QString br_str = rectProperties.namedItem(QStringLiteral("brushcolor")).nodeValue(); 0543 QString pen_str = rectProperties.namedItem(QStringLiteral("pencolor")).nodeValue(); 0544 double penwidth = rectProperties.namedItem(QStringLiteral("penwidth")).nodeValue().toDouble(); 0545 auto *rec = new MyRectItem(); 0546 rec->setRect(stringToRect(rect)); 0547 if (penwidth > 0) { 0548 rec->setPen(QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin)); 0549 } else { 0550 rec->setPen(Qt::NoPen); 0551 } 0552 if (!rectProperties.namedItem(QStringLiteral("gradient")).isNull()) { 0553 // Gradient color 0554 QString data = rectProperties.namedItem(QStringLiteral("gradient")).nodeValue(); 0555 rec->setData(TitleDocument::Gradient, data); 0556 QLinearGradient gr = GradientWidget::gradientFromString(data, int(rec->rect().width()), int(rec->rect().height())); 0557 rec->setBrush(QBrush(gr)); 0558 } else { 0559 rec->setBrush(QBrush(stringToColor(br_str))); 0560 } 0561 gitems.append(rec); 0562 0563 gitem = rec; 0564 } else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsEllipseItem")) { 0565 QDomNamedNodeMap ellipseProperties = itemNode.namedItem(QStringLiteral("content")).attributes(); 0566 QString rect = ellipseProperties.namedItem(QStringLiteral("rect")).nodeValue(); 0567 QString br_str = ellipseProperties.namedItem(QStringLiteral("brushcolor")).nodeValue(); 0568 QString pen_str = ellipseProperties.namedItem(QStringLiteral("pencolor")).nodeValue(); 0569 double penwidth = ellipseProperties.namedItem(QStringLiteral("penwidth")).nodeValue().toDouble(); 0570 auto *ellipse = new MyEllipseItem(); 0571 ellipse->setRect(stringToRect(rect)); 0572 if (penwidth > 0) { 0573 ellipse->setPen(QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin)); 0574 } else { 0575 ellipse->setPen(Qt::NoPen); 0576 } 0577 if (!ellipseProperties.namedItem(QStringLiteral("gradient")).isNull()) { 0578 // Gradient color 0579 QString data = ellipseProperties.namedItem(QStringLiteral("gradient")).nodeValue(); 0580 ellipse->setData(TitleDocument::Gradient, data); 0581 QLinearGradient gr = GradientWidget::gradientFromString(data, int(ellipse->rect().width()), int(ellipse->rect().height())); 0582 ellipse->setBrush(QBrush(gr)); 0583 } else { 0584 ellipse->setBrush(QBrush(stringToColor(br_str))); 0585 } 0586 gitems.append(ellipse); 0587 0588 gitem = ellipse; 0589 } else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsPixmapItem")) { 0590 QString url = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("url")).nodeValue(); 0591 QString base64 = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("base64")).nodeValue(); 0592 QPixmap pix; 0593 bool missing = false; 0594 if (base64.isEmpty()) { 0595 pix.load(url); 0596 if (pix.isNull()) { 0597 pix = createInvalidPixmap(url, height); 0598 missingElements++; 0599 missing = true; 0600 } 0601 } else { 0602 pix.loadFromData(QByteArray::fromBase64(base64.toLatin1())); 0603 } 0604 auto *rec = new MyPixmapItem(pix); 0605 if (missing) { 0606 rec->setData(Qt::UserRole + 2, 1); 0607 } 0608 gitems.append(rec); 0609 rec->setShapeMode(QGraphicsPixmapItem::BoundingRectShape); 0610 rec->setData(Qt::UserRole, url); 0611 if (!base64.isEmpty()) { 0612 rec->setData(Qt::UserRole + 1, base64); 0613 } 0614 gitem = rec; 0615 } else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsSvgItem")) { 0616 QString url = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("url")).nodeValue(); 0617 QString base64 = itemNode.namedItem(QStringLiteral("content")).attributes().namedItem(QStringLiteral("base64")).nodeValue(); 0618 QGraphicsSvgItem *rec = nullptr; 0619 if (base64.isEmpty()) { 0620 if (QFile::exists(url)) { 0621 rec = new MySvgItem(url); 0622 } 0623 } else { 0624 rec = new MySvgItem(); 0625 QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toLatin1()), rec); 0626 rec->setSharedRenderer(renderer); 0627 // QString elem=rec->elementId(); 0628 // QRectF bounds = renderer->boundsOnElement(elem); 0629 } 0630 if (rec) { 0631 gitems.append(rec); 0632 rec->setData(Qt::UserRole, url); 0633 if (!base64.isEmpty()) { 0634 rec->setData(Qt::UserRole + 1, base64); 0635 } 0636 gitem = rec; 0637 } else { 0638 QPixmap pix = createInvalidPixmap(url, height); 0639 missingElements++; 0640 auto *rec2 = new MyPixmapItem(pix); 0641 rec2->setData(Qt::UserRole + 2, 1); 0642 gitems.append(rec2); 0643 rec2->setShapeMode(QGraphicsPixmapItem::BoundingRectShape); 0644 rec2->setData(Qt::UserRole, url); 0645 gitem = rec2; 0646 } 0647 } 0648 } 0649 // pos and transform 0650 if (gitem) { 0651 QPointF p(xPosition, itemNode.namedItem(QStringLiteral("position")).attributes().namedItem(QStringLiteral("y")).nodeValue().toDouble()); 0652 gitem->setPos(p); 0653 QDomElement trans = itemNode.namedItem(QStringLiteral("position")).firstChild().toElement(); 0654 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue())); 0655 QString rotate = trans.attribute(QStringLiteral("rotation")); 0656 if (!rotate.isEmpty()) { 0657 gitem->setData(TitleDocument::RotateFactor, stringToList(rotate)); 0658 } 0659 QString zoom = trans.attribute(QStringLiteral("zoom")); 0660 if (!zoom.isEmpty()) { 0661 gitem->setData(TitleDocument::ZoomFactor, zoom.toInt()); 0662 } 0663 if (zValue >= maxZValue) { 0664 maxZValue = zValue + 1; 0665 } 0666 gitem->setZValue(zValue); 0667 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges); 0668 0669 // effects 0670 QDomNode eff = itemNode.namedItem(QStringLiteral("effect")); 0671 if (!eff.isNull()) { 0672 QDomElement e = eff.toElement(); 0673 if (e.attribute(QStringLiteral("type")) == QLatin1String("blur")) { 0674 auto *blur = new QGraphicsBlurEffect(); 0675 blur->setBlurRadius(e.attribute(QStringLiteral("blurradius")).toInt()); 0676 gitem->setGraphicsEffect(blur); 0677 } else if (e.attribute(QStringLiteral("type")) == QLatin1String("shadow")) { 0678 auto *shadow = new QGraphicsDropShadowEffect(); 0679 shadow->setBlurRadius(e.attribute(QStringLiteral("blurradius")).toInt()); 0680 shadow->setOffset(e.attribute(QStringLiteral("xoffset")).toInt(), e.attribute(QStringLiteral("yoffset")).toInt()); 0681 gitem->setGraphicsEffect(shadow); 0682 } 0683 } 0684 } 0685 0686 if (itemNode.nodeName() == QLatin1String("background")) { 0687 // qCDebug(KDENLIVE_LOG) << items.item(i).attributes().namedItem("color").nodeValue(); 0688 QColor color = QColor(stringToColor(itemNode.attributes().namedItem(QStringLiteral("color")).nodeValue())); 0689 // color.setAlpha(itemNode.attributes().namedItem("alpha").nodeValue().toInt()); 0690 if (scene) { 0691 QList<QGraphicsItem *> sceneItems = scene->items(); 0692 for (auto sceneItem : qAsConst(sceneItems)) { 0693 if (int(sceneItem->zValue()) == -1100) { 0694 static_cast<QGraphicsRectItem *>(sceneItem)->setBrush(QBrush(color)); 0695 break; 0696 } 0697 } 0698 scene->setBackgroundBrush(QBrush(color)); 0699 } 0700 } else if (itemNode.nodeName() == QLatin1String("startviewport") && (startv != nullptr)) { 0701 QString rect = itemNode.attributes().namedItem(QStringLiteral("rect")).nodeValue(); 0702 QRectF r = stringToRect(rect); 0703 startv->setRect(0, 0, r.width(), r.height()); 0704 startv->setPos(r.topLeft()); 0705 } else if (itemNode.nodeName() == QLatin1String("endviewport") && (endv != nullptr)) { 0706 QString rect = itemNode.attributes().namedItem(QStringLiteral("rect")).nodeValue(); 0707 QRectF r = stringToRect(rect); 0708 endv->setRect(0, 0, r.width(), r.height()); 0709 endv->setPos(r.topLeft()); 0710 } 0711 } 0712 } 0713 return maxZValue; 0714 } 0715 0716 int TitleDocument::invalidCount() const 0717 { 0718 return m_missingElements; 0719 } 0720 0721 QPixmap TitleDocument::createInvalidPixmap(const QString &url, int height) 0722 { 0723 int missingHeight = height / 10; 0724 QPixmap pix(missingHeight, missingHeight); 0725 QIcon icon = QIcon::fromTheme(QStringLiteral("messagebox_warning")); 0726 pix.fill(QColor(255, 0, 0, 50)); 0727 QPainter ptr(&pix); 0728 icon.paint(&ptr, 0, 0, missingHeight / 2, missingHeight / 2); 0729 QPen pen(Qt::red); 0730 pen.setWidth(3); 0731 ptr.setPen(pen); 0732 ptr.drawText(QRectF(2, 2, missingHeight - 4, missingHeight - 4), Qt::AlignHCenter | Qt::AlignBottom, QFileInfo(url).fileName()); 0733 ptr.drawRect(2, 1, missingHeight - 4, missingHeight - 4); 0734 ptr.end(); 0735 return pix; 0736 } 0737 0738 QString TitleDocument::colorToString(const QColor &c) 0739 { 0740 QString ret = QStringLiteral("%1,%2,%3,%4"); 0741 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha()); 0742 return ret; 0743 } 0744 0745 QString TitleDocument::rectFToString(const QRectF &c) 0746 { 0747 QString ret = QStringLiteral("%1,%2,%3,%4"); 0748 ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height()); 0749 return ret; 0750 } 0751 0752 QRectF TitleDocument::stringToRect(const QString &s) 0753 { 0754 0755 QStringList l = s.split(QLatin1Char(',')); 0756 if (l.size() < 4) { 0757 return {}; 0758 } 0759 return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized(); 0760 } 0761 0762 QColor TitleDocument::stringToColor(const QString &s) 0763 { 0764 QStringList l = s.split(QLatin1Char(',')); 0765 if (l.size() < 4) { 0766 return QColor(); 0767 } 0768 return {l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt()}; 0769 } 0770 0771 QTransform TitleDocument::stringToTransform(const QString &s) 0772 { 0773 QStringList l = s.split(QLatin1Char(',')); 0774 if (l.size() < 9) { 0775 return QTransform(); 0776 } 0777 return {l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble(), l.at(4).toDouble(), 0778 l.at(5).toDouble(), l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()}; 0779 } 0780 0781 QList<QVariant> TitleDocument::stringToList(const QString &s) 0782 { 0783 QStringList l = s.split(QLatin1Char(',')); 0784 if (l.size() < 3) { 0785 return QList<QVariant>(); 0786 } 0787 return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble()); 0788 } 0789 0790 int TitleDocument::frameWidth() const 0791 { 0792 return m_width; 0793 } 0794 0795 int TitleDocument::frameHeight() const 0796 { 0797 return m_height; 0798 }