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 duration, bool embed)
292 QDomDocument doc = xml(startv, endv, embed);
293 doc.documentElement().setAttribute("duration", duration);
294 // keep some time for backwards compatibility (opening projects with older versions) - 26/12/12
295 doc.documentElement().setAttribute("out", duration);
296 KTemporaryFile tmpfile;
297 if (!tmpfile.open()) {
298 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
301 QFile xmlf(tmpfile.fileName());
302 if (!xmlf.open(QIODevice::WriteOnly))
304 xmlf.write(doc.toString().toUtf8());
305 if (xmlf.error() != QFile::NoError) {
310 return KIO::NetAccess::upload(tmpfile.fileName(), url, 0);
313 int TitleDocument::loadFromXml(QDomDocument doc, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int *duration, const QString& projectpath)
315 m_projectPath = projectpath;
316 QDomNodeList titles = doc.elementsByTagName("kdenlivetitle");
317 //TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
318 if (doc.documentElement().hasAttribute("width") && doc.documentElement().hasAttribute("height")) {
319 int doc_width = doc.documentElement().attribute("width").toInt();
320 int doc_height = doc.documentElement().attribute("height").toInt();
321 if (doc_width != m_width || doc_height != m_height) {
322 KMessageBox::information(kapp->activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
323 //TODO: convert using QTransform
325 m_height = doc_height;
328 // Document has no size info, it is likely an old version title, so ignore viewport data
329 QDomNodeList viewportlist = doc.documentElement().elementsByTagName("startviewport");
330 if (!viewportlist.isEmpty()) {
331 doc.documentElement().removeChild(viewportlist.at(0));
333 viewportlist = doc.documentElement().elementsByTagName("endviewport");
334 if (!viewportlist.isEmpty()) {
335 doc.documentElement().removeChild(viewportlist.at(0));
338 //TODO: get default title duration instead of hardcoded one
339 if (doc.documentElement().hasAttribute("duration"))
340 *duration = doc.documentElement().attribute("duration").toInt();
341 else if (doc.documentElement().hasAttribute("out"))
342 *duration = doc.documentElement().attribute("out").toInt();
349 QDomNodeList items = titles.item(0).childNodes();
350 for (int i = 0; i < items.count(); i++) {
351 QGraphicsItem *gitem = NULL;
352 kDebug() << items.item(i).attributes().namedItem("type").nodeValue();
353 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
354 if (zValue > -1000) {
355 if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
356 QDomNamedNodeMap txtProperties = items.item(i).namedItem("content").attributes();
357 QFont font(txtProperties.namedItem("font").nodeValue());
359 QDomNode node = txtProperties.namedItem("font-bold");
360 if (!node.isNull()) {
361 // Old: Bold/Not bold.
362 font.setBold(node.nodeValue().toInt());
364 // New: Font weight (QFont::)
365 font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
367 //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
368 font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
369 font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());
370 // Older Kdenlive version did not store pixel size but point size
371 if (txtProperties.namedItem("font-pixel-size").isNull()) {
372 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"));
374 f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
375 font.setPixelSize(QFontInfo(f2).pixelSize());
377 font.setPixelSize(txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
378 QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
379 QGraphicsTextItem *txt = m_scene->addText(items.item(i).namedItem("content").firstChild().nodeValue(), font);
380 QTextCursor cursor(txt->document());
381 cursor.select(QTextCursor::Document);
382 QTextCharFormat format;
383 if (txtProperties.namedItem("font-outline").nodeValue().toDouble() > 0.0) {
384 txt->setData(101, txtProperties.namedItem("font-outline").nodeValue().toDouble());
385 txt->setData(102, stringToColor(txtProperties.namedItem("font-outline-color").nodeValue()));
386 format.setTextOutline(
387 QPen(QColor(stringToColor(txtProperties.namedItem("font-outline-color").nodeValue())),
388 txtProperties.namedItem("font-outline").nodeValue().toDouble(),
389 Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
393 format.setForeground(QBrush(col));
394 cursor.mergeCharFormat(format);
395 txt->setTextInteractionFlags(Qt::NoTextInteraction);
396 if (txtProperties.namedItem("alignment").isNull() == false) {
397 txt->setTextWidth(txt->boundingRect().width());
398 QTextCursor cur = txt->textCursor();
399 QTextBlockFormat format = cur.blockFormat();
400 format.setAlignment((Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
401 cur.select(QTextCursor::Document);
402 cur.setBlockFormat(format);
403 txt->setTextCursor(cur);
404 cur.clearSelection();
405 txt->setTextCursor(cur);
408 if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
409 txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
411 if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
412 txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
416 if (!txtProperties.namedItem("typewriter").isNull()) {
417 QStringList effData = QStringList() << "typewriter" << txtProperties.namedItem("typewriter").nodeValue();
418 txt->setData(100, effData);
422 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsRectItem") {
423 QString rect = items.item(i).namedItem("content").attributes().namedItem("rect").nodeValue();
424 QString br_str = items.item(i).namedItem("content").attributes().namedItem("brushcolor").nodeValue();
425 QString pen_str = items.item(i).namedItem("content").attributes().namedItem("pencolor").nodeValue();
426 double penwidth = items.item(i).namedItem("content").attributes().namedItem("penwidth").nodeValue().toDouble();
427 QGraphicsRectItem *rec = m_scene->addRect(stringToRect(rect), QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin), QBrush(stringToColor(br_str)));
429 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
430 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
431 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
433 if (base64.isEmpty()) {
436 pix.loadFromData(QByteArray::fromBase64(base64.toAscii()));
438 QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
439 rec->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
440 rec->setData(Qt::UserRole, url);
441 if (!base64.isEmpty()) {
442 rec->setData(Qt::UserRole + 1, base64);
445 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
446 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
447 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
448 QGraphicsSvgItem *rec = NULL;
449 if (base64.isEmpty()) {
450 rec = new QGraphicsSvgItem(url);
452 rec = new QGraphicsSvgItem();
453 QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toAscii()), rec);
454 rec->setSharedRenderer(renderer);
455 //QString elem=rec->elementId();
456 //QRectF bounds = renderer->boundsOnElement(elem);
459 m_scene->addItem(rec);
460 rec->setData(Qt::UserRole, url);
461 if (!base64.isEmpty()) {
462 rec->setData(Qt::UserRole + 1, base64);
470 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
471 items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
473 QDomElement trans = items.item(i).namedItem("position").firstChild().toElement();
474 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue()));
475 QString rotate = trans.attribute("rotation");
476 if (!rotate.isEmpty()) gitem->setData(ROTATEFACTOR, stringToList(rotate));
477 QString zoom = trans.attribute("zoom");
478 if (!zoom.isEmpty()) gitem->setData(ZOOMFACTOR, zoom.toInt());
479 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
480 if (zValue > maxZValue) maxZValue = zValue;
481 gitem->setZValue(zValue);
482 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
484 #if QT_VERSION >= 0x040600
486 QDomNode eff = items.item(i).namedItem("effect");
488 QDomElement e = eff.toElement();
489 if (e.attribute("type") == "blur") {
490 QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
491 blur->setBlurRadius(e.attribute("blurradius").toInt());
492 gitem->setGraphicsEffect(blur);
493 } else if (e.attribute("type") == "shadow") {
494 QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
495 shadow->setBlurRadius(e.attribute("blurradius").toInt());
496 shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
497 gitem->setGraphicsEffect(shadow);
503 if (items.item(i).nodeName() == "background") {
504 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
505 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
506 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
507 QList<QGraphicsItem *> items = m_scene->items();
508 for (int i = 0; i < items.size(); i++) {
509 if (items.at(i)->zValue() == -1100) {
510 ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
514 } else if (items.item(i).nodeName() == "startviewport" && startv) {
515 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
516 QRectF r = stringToRect(rect);
517 startv->setRect(0, 0, r.width(), r.height());
518 startv->setPos(r.topLeft());
519 } else if (items.item(i).nodeName() == "endviewport" && endv) {
520 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
521 QRectF r = stringToRect(rect);
522 endv->setRect(0, 0, r.width(), r.height());
523 endv->setPos(r.topLeft());
530 QString TitleDocument::colorToString(const QColor& c)
532 QString ret = "%1,%2,%3,%4";
533 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
537 QString TitleDocument::rectFToString(const QRectF& c)
539 QString ret = "%1,%2,%3,%4";
540 ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
544 QRectF TitleDocument::stringToRect(const QString & s)
547 QStringList l = s.split(',');
550 return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
553 QColor TitleDocument::stringToColor(const QString & s)
555 QStringList l = s.split(',');
558 return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
561 QTransform TitleDocument::stringToTransform(const QString& s)
563 QStringList l = s.split(',');
567 l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
568 l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
569 l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
573 QList<QVariant> TitleDocument::stringToList(const QString & s)
575 QStringList l = s.split(',');
577 return QList<QVariant>();
578 return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
581 int TitleDocument::frameWidth() const
586 int TitleDocument::frameHeight() const