1 /***************************************************************************
2 titledocument.h - description
5 copyright : (C) 2008 by Marco Gittler
6 email : g.marco@freenet.de
7 ***************************************************************************/
9 /***************************************************************************
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
16 ***************************************************************************/
18 #include "titledocument.h"
21 #include <KTemporaryFile>
22 #include <kio/netaccess.h>
23 #include <KApplication>
24 #include <KStandardDirs>
26 #include <KMessageBox>
28 #include <QGraphicsScene>
29 #include <QDomElement>
30 #include <QGraphicsItem>
31 #include <QGraphicsRectItem>
32 #include <QGraphicsTextItem>
33 #include <QGraphicsSvgItem>
34 #include <QCryptographicHash>
35 #include <QSvgRenderer>
38 #include <QTextCursor>
42 #if QT_VERSION >= 0x040600
43 #include <QGraphicsEffect>
44 #include <QGraphicsBlurEffect>
45 #include <QGraphicsDropShadowEffect>
48 QByteArray fileToByteArray(const QString& filename)
52 if (file.open(QIODevice::ReadOnly)) {
53 while (!file.atEnd()) {
54 ret.append(file.readLine());
60 TitleDocument::TitleDocument()
67 void TitleDocument::setScene(QGraphicsScene* _scene, int width, int height)
74 int TitleDocument::base64ToUrl(QGraphicsItem* item, QDomElement& content, bool embed)
77 if (!item->data(Qt::UserRole + 1).toString().isEmpty()) {
78 content.setAttribute("base64", item->data(Qt::UserRole + 1).toString());
79 } else if (!item->data(Qt::UserRole).toString().isEmpty()) {
80 content.setAttribute("base64", fileToByteArray(item->data(Qt::UserRole).toString()).toBase64().data());
82 content.removeAttribute("url");
84 // save for project files to disk
85 QString base64 = item->data(Qt::UserRole + 1).toString();
86 if (!base64.isEmpty()) {
88 if (!m_projectPath.isEmpty()) {
89 titlePath = m_projectPath;
91 titlePath = "/tmp/titles";
93 QString filename = extractBase64Image(titlePath, base64);
94 if (!filename.isEmpty()) {
95 content.setAttribute("url", filename);
96 content.removeAttribute("base64");
108 const QString TitleDocument::extractBase64Image(const QString &titlePath, const QString &data)
110 QString filename = titlePath + QString(QCryptographicHash::hash(data.toAscii(), QCryptographicHash::Md5).toHex().append(".titlepart"));
111 KStandardDirs::makeDir(titlePath);
113 if (f.open(QIODevice::WriteOnly)) {
114 f.write(QByteArray::fromBase64(data.toAscii())) ;
121 QDomDocument TitleDocument::xml(QGraphicsRectItem* startv, QGraphicsRectItem* endv, bool embed)
125 QDomElement main = doc.createElement("kdenlivetitle");
126 main.setAttribute("width", m_width);
127 main.setAttribute("height", m_height);
129 const char *locale = setlocale(LC_NUMERIC, NULL);
130 main.setAttribute("LC_NUMERIC", locale);
131 doc.appendChild(main);
133 foreach(QGraphicsItem * item, m_scene->items()) {
134 QDomElement e = doc.createElement("item");
135 QDomElement content = doc.createElement("content");
137 QGraphicsTextItem *t;
139 switch (item->type()) {
141 e.setAttribute("type", "QGraphicsPixmapItem");
142 content.setAttribute("url", item->data(Qt::UserRole).toString());
143 base64ToUrl(item, content, embed);
146 e.setAttribute("type", "QGraphicsSvgItem");
147 content.setAttribute("url", item->data(Qt::UserRole).toString());
148 base64ToUrl(item, content, embed);
151 e.setAttribute("type", "QGraphicsRectItem");
152 content.setAttribute("rect", rectFToString(((QGraphicsRectItem*)item)->rect().normalized()));
153 content.setAttribute("pencolor", colorToString(((QGraphicsRectItem*)item)->pen().color()));
154 content.setAttribute("penwidth", ((QGraphicsRectItem*)item)->pen().width());
155 content.setAttribute("brushcolor", colorToString(((QGraphicsRectItem*)item)->brush().color()));
158 e.setAttribute("type", "QGraphicsTextItem");
159 t = static_cast<QGraphicsTextItem *>(item);
160 // Don't save empty text nodes
161 if (t->toPlainText().simplified().isEmpty()) continue;
162 //content.appendChild(doc.createTextNode(((QGraphicsTextItem*)item)->toHtml()));
163 content.appendChild(doc.createTextNode(t->toPlainText()));
165 content.setAttribute("font", font.family());
166 content.setAttribute("font-weight", font.weight());
167 content.setAttribute("font-pixel-size", font.pixelSize());
168 content.setAttribute("font-italic", font.italic());
169 content.setAttribute("font-underline", font.underline());
171 QTextCursor cursor(t->document());
172 cursor.select(QTextCursor::Document);
173 QColor fontcolor = cursor.charFormat().foreground().color();
174 content.setAttribute("font-color", colorToString(fontcolor));
175 if (!t->data(101).isNull()) content.setAttribute("font-outline", t->data(101).toDouble());
176 if (!t->data(102).isNull()) {
177 QVariant variant = t->data(102);
178 QColor outlineColor = variant.value<QColor>();
179 content.setAttribute("font-outline-color", colorToString(outlineColor));
182 if (!t->data(100).isNull()) {
183 QStringList effectParams = t->data(100).toStringList();
184 QString effectName = effectParams.takeFirst();
185 content.setAttribute(effectName, effectParams.join(";"));
188 // Only save when necessary.
189 if (t->data(OriginXLeft).toInt() == AxisInverted) {
190 content.setAttribute("kdenlive-axis-x-inverted", t->data(OriginXLeft).toInt());
192 if (t->data(OriginYTop).toInt() == AxisInverted) {
193 content.setAttribute("kdenlive-axis-y-inverted", t->data(OriginYTop).toInt());
195 if (t->textWidth() != -1) {
196 content.setAttribute("alignment", t->textCursor().blockFormat().alignment());
204 QDomElement pos = doc.createElement("position");
205 pos.setAttribute("x", item->pos().x());
206 pos.setAttribute("y", item->pos().y());
207 QTransform transform = item->transform();
208 QDomElement tr = doc.createElement("transform");
209 if (!item->data(ZOOMFACTOR).isNull()) {
210 tr.setAttribute("zoom", QString::number(item->data(ZOOMFACTOR).toInt()));
212 if (!item->data(ROTATEFACTOR).isNull()) {
213 QList<QVariant> rotlist = item->data(ROTATEFACTOR).toList();
214 tr.setAttribute("rotation", QString("%1,%2,%3").arg(rotlist[0].toDouble()).arg(rotlist[1].toDouble()).arg(rotlist[2].toDouble()));
216 tr.appendChild(doc.createTextNode(
217 QString("%1,%2,%3,%4,%5,%6,%7,%8,%9").arg(
218 transform.m11()).arg(transform.m12()).arg(transform.m13()).arg(transform.m21()).arg(transform.m22()).arg(transform.m23()).arg(transform.m31()).arg(transform.m32()).arg(transform.m33())
221 e.setAttribute("z-index", item->zValue());
224 #if QT_VERSION >= 0x040600
226 QGraphicsEffect *eff = item->graphicsEffect();
228 QGraphicsBlurEffect *blur = static_cast <QGraphicsBlurEffect *>(eff);
229 QDomElement effect = doc.createElement("effect");
231 effect.setAttribute("type", "blur");
232 effect.setAttribute("blurradius", blur->blurRadius());
234 QGraphicsDropShadowEffect *shadow = static_cast <QGraphicsDropShadowEffect *>(eff);
236 effect.setAttribute("type", "shadow");
237 effect.setAttribute("blurradius", shadow->blurRadius());
238 effect.setAttribute("xoffset", shadow->xOffset());
239 effect.setAttribute("yoffset", shadow->yOffset());
242 e.appendChild(effect);
247 e.appendChild(content);
248 if (item->zValue() > -1000) main.appendChild(e);
250 if (startv && endv) {
251 QDomElement endp = doc.createElement("endviewport");
252 QDomElement startp = doc.createElement("startviewport");
253 QRectF r(endv->pos().x(), endv->pos().y(), endv->rect().width(), endv->rect().height());
254 endp.setAttribute("rect", rectFToString(r));
255 QRectF r2(startv->pos().x(), startv->pos().y(), startv->rect().width(), startv->rect().height());
256 startp.setAttribute("rect", rectFToString(r2));
258 main.appendChild(startp);
259 main.appendChild(endp);
261 QDomElement backgr = doc.createElement("background");
262 QColor color = getBackgroundColor();
263 backgr.setAttribute("color", colorToString(color));
264 main.appendChild(backgr);
269 /** \brief Get the background color (incl. alpha) from the document, if possibly
270 * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */
271 QColor TitleDocument::getBackgroundColor()
273 QColor color(0, 0, 0, 0);
275 QList<QGraphicsItem *> items = m_scene->items();
276 for (int i = 0; i < items.size(); i++) {
277 if (items.at(i)->zValue() == -1100) {
278 color = ((QGraphicsRectItem *)items.at(i))->brush().color();
287 bool TitleDocument::saveDocument(const KUrl& url, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int out, bool embed)
292 QDomDocument doc = xml(startv, endv, embed);
293 doc.documentElement().setAttribute("out", out);
294 KTemporaryFile tmpfile;
295 if (!tmpfile.open()) {
296 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
299 QFile xmlf(tmpfile.fileName());
300 xmlf.open(QIODevice::WriteOnly);
301 xmlf.write(doc.toString().toUtf8());
302 if (xmlf.error() != QFile::NoError) {
307 return KIO::NetAccess::upload(tmpfile.fileName(), url, 0);
310 int TitleDocument::loadFromXml(QDomDocument doc, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int *out, const QString& projectpath)
312 m_projectPath = projectpath;
313 QDomNodeList titles = doc.elementsByTagName("kdenlivetitle");
314 //TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
315 if (doc.documentElement().hasAttribute("width") && doc.documentElement().hasAttribute("height")) {
316 int doc_width = doc.documentElement().attribute("width").toInt();
317 int doc_height = doc.documentElement().attribute("height").toInt();
318 if (doc_width != m_width || doc_height != m_height) {
319 KMessageBox::information(kapp->activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
320 //TODO: convert using QTransform
322 m_height = doc_height;
325 // Document has no size info, it is likely an old version title, so ignore viewport data
326 QDomNodeList viewportlist = doc.documentElement().elementsByTagName("startviewport");
327 if (!viewportlist.isEmpty()) {
328 doc.documentElement().removeChild(viewportlist.at(0));
330 viewportlist = doc.documentElement().elementsByTagName("endviewport");
331 if (!viewportlist.isEmpty()) {
332 doc.documentElement().removeChild(viewportlist.at(0));
335 //TODO: get default title duration instead of hardcoded one
336 if (doc.documentElement().hasAttribute("out"))
337 *out = doc.documentElement().attribute("out").toInt();
344 QDomNodeList items = titles.item(0).childNodes();
345 for (int i = 0; i < items.count(); i++) {
346 QGraphicsItem *gitem = NULL;
347 kDebug() << items.item(i).attributes().namedItem("type").nodeValue();
348 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
349 if (zValue > -1000) {
350 if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
351 QDomNamedNodeMap txtProperties = items.item(i).namedItem("content").attributes();
352 QFont font(txtProperties.namedItem("font").nodeValue());
354 QDomNode node = txtProperties.namedItem("font-bold");
355 if (!node.isNull()) {
356 // Old: Bold/Not bold.
357 font.setBold(node.nodeValue().toInt());
359 // New: Font weight (QFont::)
360 font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
362 //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
363 font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
364 font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());
365 // Older Kdenlive version did not store pixel size but point size
366 if (txtProperties.namedItem("font-pixel-size").isNull()) {
367 KMessageBox::information(kapp->activeWindow(), i18n("Some of your text clips were saved with size in points, which means different sizes on different displays. They will be converted to pixel size, making them portable, but you could have to adjust their size."), i18n("Text Clips Updated"));
369 f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
370 font.setPixelSize(QFontInfo(f2).pixelSize());
372 font.setPixelSize(txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
373 QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
374 QGraphicsTextItem *txt = m_scene->addText(items.item(i).namedItem("content").firstChild().nodeValue(), font);
375 QTextCursor cursor(txt->document());
376 cursor.select(QTextCursor::Document);
377 QTextCharFormat format;
378 if (txtProperties.namedItem("font-outline").nodeValue().toDouble() > 0.0) {
379 txt->setData(101, txtProperties.namedItem("font-outline").nodeValue().toDouble());
380 txt->setData(102, stringToColor(txtProperties.namedItem("font-outline-color").nodeValue()));
381 format.setTextOutline(
382 QPen(QColor(stringToColor(txtProperties.namedItem("font-outline-color").nodeValue())),
383 txtProperties.namedItem("font-outline").nodeValue().toDouble(),
384 Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
388 format.setForeground(QBrush(col));
389 cursor.mergeCharFormat(format);
390 txt->setTextInteractionFlags(Qt::NoTextInteraction);
391 if (txtProperties.namedItem("alignment").isNull() == false) {
392 txt->setTextWidth(txt->boundingRect().width());
393 QTextCursor cur = txt->textCursor();
394 QTextBlockFormat format = cur.blockFormat();
395 format.setAlignment((Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
396 cur.select(QTextCursor::Document);
397 cur.setBlockFormat(format);
398 txt->setTextCursor(cur);
399 cur.clearSelection();
400 txt->setTextCursor(cur);
403 if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
404 txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
406 if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
407 txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
411 if (!txtProperties.namedItem("typewriter").isNull()) {
412 QStringList effData = QStringList() << "typewriter" << txtProperties.namedItem("typewriter").nodeValue();
413 txt->setData(100, effData);
417 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsRectItem") {
418 QString rect = items.item(i).namedItem("content").attributes().namedItem("rect").nodeValue();
419 QString br_str = items.item(i).namedItem("content").attributes().namedItem("brushcolor").nodeValue();
420 QString pen_str = items.item(i).namedItem("content").attributes().namedItem("pencolor").nodeValue();
421 double penwidth = items.item(i).namedItem("content").attributes().namedItem("penwidth").nodeValue().toDouble();
422 QGraphicsRectItem *rec = m_scene->addRect(stringToRect(rect), QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin), QBrush(stringToColor(br_str)));
424 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
425 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
426 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
428 if (base64.isEmpty()) {
431 pix.loadFromData(QByteArray::fromBase64(base64.toAscii()));
433 QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
434 rec->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
435 rec->setData(Qt::UserRole, url);
436 if (!base64.isEmpty()) {
437 rec->setData(Qt::UserRole + 1, base64);
440 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
441 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
442 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
443 QGraphicsSvgItem *rec = NULL;
444 if (base64.isEmpty()) {
445 rec = new QGraphicsSvgItem(url);
447 rec = new QGraphicsSvgItem();
448 QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toAscii()), rec);
449 rec->setSharedRenderer(renderer);
450 //QString elem=rec->elementId();
451 //QRectF bounds = renderer->boundsOnElement(elem);
454 m_scene->addItem(rec);
455 rec->setData(Qt::UserRole, url);
456 if (!base64.isEmpty()) {
457 rec->setData(Qt::UserRole + 1, base64);
465 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
466 items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
468 QDomElement trans = items.item(i).namedItem("position").firstChild().toElement();
469 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue()));
470 QString rotate = trans.attribute("rotation");
471 if (!rotate.isEmpty()) gitem->setData(ROTATEFACTOR, stringToList(rotate));
472 QString zoom = trans.attribute("zoom");
473 if (!zoom.isEmpty()) gitem->setData(ZOOMFACTOR, zoom.toInt());
474 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
475 if (zValue > maxZValue) maxZValue = zValue;
476 gitem->setZValue(zValue);
477 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
479 #if QT_VERSION >= 0x040600
481 QDomNode eff = items.item(i).namedItem("effect");
483 QDomElement e = eff.toElement();
484 if (e.attribute("type") == "blur") {
485 QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
486 blur->setBlurRadius(e.attribute("blurradius").toInt());
487 gitem->setGraphicsEffect(blur);
488 } else if (e.attribute("type") == "shadow") {
489 QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
490 shadow->setBlurRadius(e.attribute("blurradius").toInt());
491 shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
492 gitem->setGraphicsEffect(shadow);
498 if (items.item(i).nodeName() == "background") {
499 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
500 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
501 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
502 QList<QGraphicsItem *> items = m_scene->items();
503 for (int i = 0; i < items.size(); i++) {
504 if (items.at(i)->zValue() == -1100) {
505 ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
509 } else if (items.item(i).nodeName() == "startviewport" && startv) {
510 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
511 QRectF r = stringToRect(rect);
512 startv->setRect(0, 0, r.width(), r.height());
513 startv->setPos(r.topLeft());
514 } else if (items.item(i).nodeName() == "endviewport" && endv) {
515 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
516 QRectF r = stringToRect(rect);
517 endv->setRect(0, 0, r.width(), r.height());
518 endv->setPos(r.topLeft());
525 QString TitleDocument::colorToString(const QColor& c)
527 QString ret = "%1,%2,%3,%4";
528 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
532 QString TitleDocument::rectFToString(const QRectF& c)
534 QString ret = "%1,%2,%3,%4";
535 ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
539 QRectF TitleDocument::stringToRect(const QString & s)
542 QStringList l = s.split(',');
545 return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
548 QColor TitleDocument::stringToColor(const QString & s)
550 QStringList l = s.split(',');
553 return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
556 QTransform TitleDocument::stringToTransform(const QString& s)
558 QStringList l = s.split(',');
562 l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
563 l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
564 l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
568 QList<QVariant> TitleDocument::stringToList(const QString & s)
570 QStringList l = s.split(',');
572 return QList<QVariant>();
573 return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
576 int TitleDocument::frameWidth() const
581 int TitleDocument::frameHeight() const