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()
65 void TitleDocument::setScene(QGraphicsScene* _scene, int width, int height)
72 int TitleDocument::base64ToUrl(QGraphicsItem* item, QDomElement& content, bool embed)
75 if (!item->data(Qt::UserRole + 1).toString().isEmpty()) {
76 content.setAttribute("base64", item->data(Qt::UserRole + 1).toString());
77 } else if (!item->data(Qt::UserRole).toString().isEmpty()) {
78 content.setAttribute("base64", fileToByteArray(item->data(Qt::UserRole).toString()).toBase64().data());
80 content.removeAttribute("url");
82 // save for project files to disk
83 QString base64 = item->data(Qt::UserRole + 1).toString();
84 if (!base64.isEmpty()) {
86 if (!m_projectPath.isEmpty()) {
87 titlePath = m_projectPath;
89 titlePath = "/tmp/titles";
91 QString filename = extractBase64Image(titlePath, base64);
92 if (!filename.isEmpty()) {
93 content.setAttribute("url", filename);
94 content.removeAttribute("base64");
106 const QString TitleDocument::extractBase64Image(const QString &titlePath, const QString &data)
108 QString filename = titlePath + QString(QCryptographicHash::hash(data.toAscii(), QCryptographicHash::Md5).toHex().append(".titlepart"));
109 KStandardDirs::makeDir(titlePath);
111 if (f.open(QIODevice::WriteOnly)) {
112 f.write(QByteArray::fromBase64(data.toAscii())) ;
119 QDomDocument TitleDocument::xml(QGraphicsRectItem* startv, QGraphicsRectItem* endv, bool embed)
123 QDomElement main = doc.createElement("kdenlivetitle");
124 main.setAttribute("width", m_width);
125 main.setAttribute("height", m_height);
127 const char *locale = setlocale(LC_NUMERIC, NULL);
128 main.setAttribute("LC_NUMERIC", locale);
129 doc.appendChild(main);
131 foreach(QGraphicsItem * item, m_scene->items()) {
132 QDomElement e = doc.createElement("item");
133 QDomElement content = doc.createElement("content");
135 QGraphicsTextItem *t;
137 switch (item->type()) {
139 e.setAttribute("type", "QGraphicsPixmapItem");
140 content.setAttribute("url", item->data(Qt::UserRole).toString());
141 base64ToUrl(item, content, embed);
144 e.setAttribute("type", "QGraphicsSvgItem");
145 content.setAttribute("url", item->data(Qt::UserRole).toString());
146 base64ToUrl(item, content, embed);
149 e.setAttribute("type", "QGraphicsRectItem");
150 content.setAttribute("rect", rectFToString(((QGraphicsRectItem*)item)->rect().normalized()));
151 content.setAttribute("pencolor", colorToString(((QGraphicsRectItem*)item)->pen().color()));
152 content.setAttribute("penwidth", ((QGraphicsRectItem*)item)->pen().width());
153 content.setAttribute("brushcolor", colorToString(((QGraphicsRectItem*)item)->brush().color()));
156 e.setAttribute("type", "QGraphicsTextItem");
157 t = static_cast<QGraphicsTextItem *>(item);
158 // Don't save empty text nodes
159 if (t->toPlainText().simplified().isEmpty()) continue;
160 //content.appendChild(doc.createTextNode(((QGraphicsTextItem*)item)->toHtml()));
161 content.appendChild(doc.createTextNode(t->toPlainText()));
163 content.setAttribute("font", font.family());
164 content.setAttribute("font-weight", font.weight());
165 content.setAttribute("font-pixel-size", font.pixelSize());
166 content.setAttribute("font-italic", font.italic());
167 content.setAttribute("font-underline", font.underline());
169 QTextCursor cursor(t->document());
170 cursor.select(QTextCursor::Document);
171 QColor fontcolor = cursor.charFormat().foreground().color();
172 content.setAttribute("font-color", colorToString(fontcolor));
173 if (!t->data(101).isNull()) content.setAttribute("font-outline", t->data(101).toDouble());
174 if (!t->data(102).isNull()) content.setAttribute("font-outline-color", colorToString(QColor(t->data(102).toString())));
176 if (!t->data(100).isNull()) {
177 QStringList effectParams = t->data(100).toStringList();
178 QString effectName = effectParams.takeFirst();
179 content.setAttribute(effectName, effectParams.join(";"));
182 // Only save when necessary.
183 if (t->data(OriginXLeft).toInt() == AxisInverted) {
184 content.setAttribute("kdenlive-axis-x-inverted", t->data(OriginXLeft).toInt());
186 if (t->data(OriginYTop).toInt() == AxisInverted) {
187 content.setAttribute("kdenlive-axis-y-inverted", t->data(OriginYTop).toInt());
189 if (t->textWidth() != -1) {
190 content.setAttribute("alignment", t->textCursor().blockFormat().alignment());
198 QDomElement pos = doc.createElement("position");
199 pos.setAttribute("x", item->pos().x());
200 pos.setAttribute("y", item->pos().y());
201 QTransform transform = item->transform();
202 QDomElement tr = doc.createElement("transform");
203 if (!item->data(ZOOMFACTOR).isNull()) {
204 tr.setAttribute("zoom", QString::number(item->data(ZOOMFACTOR).toInt()));
206 if (!item->data(ROTATEFACTOR).isNull()) {
207 QList<QVariant> rotlist = item->data(ROTATEFACTOR).toList();
208 tr.setAttribute("rotation", QString("%1,%2,%3").arg(rotlist[0].toDouble()).arg(rotlist[1].toDouble()).arg(rotlist[2].toDouble()));
210 tr.appendChild(doc.createTextNode(
211 QString("%1,%2,%3,%4,%5,%6,%7,%8,%9").arg(
212 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())
215 e.setAttribute("z-index", item->zValue());
218 #if QT_VERSION >= 0x040600
220 QGraphicsEffect *eff = item->graphicsEffect();
222 QGraphicsBlurEffect *blur = static_cast <QGraphicsBlurEffect *>(eff);
223 QDomElement effect = doc.createElement("effect");
225 effect.setAttribute("type", "blur");
226 effect.setAttribute("blurradius", blur->blurRadius());
228 QGraphicsDropShadowEffect *shadow = static_cast <QGraphicsDropShadowEffect *>(eff);
230 effect.setAttribute("type", "shadow");
231 effect.setAttribute("blurradius", shadow->blurRadius());
232 effect.setAttribute("xoffset", shadow->xOffset());
233 effect.setAttribute("yoffset", shadow->yOffset());
236 e.appendChild(effect);
241 e.appendChild(content);
242 if (item->zValue() > -1000) main.appendChild(e);
244 if (startv && endv) {
245 QDomElement endp = doc.createElement("endviewport");
246 QDomElement startp = doc.createElement("startviewport");
247 QRectF r(endv->pos().x(), endv->pos().y(), endv->rect().width(), endv->rect().height());
248 endp.setAttribute("rect", rectFToString(r));
249 QRectF r2(startv->pos().x(), startv->pos().y(), startv->rect().width(), startv->rect().height());
250 startp.setAttribute("rect", rectFToString(r2));
252 main.appendChild(startp);
253 main.appendChild(endp);
255 QDomElement backgr = doc.createElement("background");
256 QColor color = getBackgroundColor();
257 backgr.setAttribute("color", colorToString(color));
258 main.appendChild(backgr);
263 /** \brief Get the background color (incl. alpha) from the document, if possibly
264 * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */
265 QColor TitleDocument::getBackgroundColor()
267 QColor color(0, 0, 0, 0);
269 QList<QGraphicsItem *> items = m_scene->items();
270 for (int i = 0; i < items.size(); i++) {
271 if (items.at(i)->zValue() == -1100) {
272 color = ((QGraphicsRectItem *)items.at(i))->brush().color();
281 bool TitleDocument::saveDocument(const KUrl& url, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int out, bool embed)
286 QDomDocument doc = xml(startv, endv, embed);
287 doc.documentElement().setAttribute("out", out);
288 KTemporaryFile tmpfile;
289 if (!tmpfile.open()) {
290 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
293 QFile xmlf(tmpfile.fileName());
294 xmlf.open(QIODevice::WriteOnly);
295 xmlf.write(doc.toString().toUtf8());
296 if (xmlf.error() != QFile::NoError) {
301 return KIO::NetAccess::upload(tmpfile.fileName(), url, 0);
304 int TitleDocument::loadFromXml(QDomDocument doc, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int *out, const QString& projectpath)
306 m_projectPath = projectpath;
307 QDomNodeList titles = doc.elementsByTagName("kdenlivetitle");
308 //TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
309 if (doc.documentElement().hasAttribute("width") && doc.documentElement().hasAttribute("height")) {
310 int doc_width = doc.documentElement().attribute("width").toInt();
311 int doc_height = doc.documentElement().attribute("height").toInt();
312 if (doc_width != m_width || doc_height != m_height) {
313 KMessageBox::information(kapp->activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
314 //TODO: convert using QTransform
316 m_height = doc_height;
319 // Document has no size info, it is likely an old version title, so ignore viewport data
320 QDomNodeList viewportlist = doc.documentElement().elementsByTagName("startviewport");
321 if (!viewportlist.isEmpty()) {
322 doc.documentElement().removeChild(viewportlist.at(0));
324 viewportlist = doc.documentElement().elementsByTagName("endviewport");
325 if (!viewportlist.isEmpty()) {
326 doc.documentElement().removeChild(viewportlist.at(0));
329 //TODO: get default title duration instead of hardcoded one
330 if (doc.documentElement().hasAttribute("out"))
331 *out = doc.documentElement().attribute("out").toInt();
338 QDomNodeList items = titles.item(0).childNodes();
339 for (int i = 0; i < items.count(); i++) {
340 QGraphicsItem *gitem = NULL;
341 kDebug() << items.item(i).attributes().namedItem("type").nodeValue();
342 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
343 if (zValue > -1000) {
344 if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
345 QDomNamedNodeMap txtProperties = items.item(i).namedItem("content").attributes();
346 QFont font(txtProperties.namedItem("font").nodeValue());
348 QDomNode node = txtProperties.namedItem("font-bold");
349 if (!node.isNull()) {
350 // Old: Bold/Not bold.
351 font.setBold(node.nodeValue().toInt());
353 // New: Font weight (QFont::)
354 font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
356 //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
357 font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
358 font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());
359 // Older Kdenlive version did not store pixel size but point size
360 if (txtProperties.namedItem("font-pixel-size").isNull()) {
361 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"));
363 f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
364 font.setPixelSize(QFontInfo(f2).pixelSize());
366 font.setPixelSize(txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
367 QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
368 QGraphicsTextItem *txt = m_scene->addText(items.item(i).namedItem("content").firstChild().nodeValue(), font);
369 QTextCursor cursor(txt->document());
370 cursor.select(QTextCursor::Document);
371 QTextCharFormat format;
372 if (txtProperties.namedItem("font-outline").nodeValue().toDouble() > 0.0) {
373 txt->setData(101, txtProperties.namedItem("font-outline").nodeValue().toDouble());
374 txt->setData(102, stringToColor(txtProperties.namedItem("font-outline-color").nodeValue()));
375 format.setTextOutline(
376 QPen(QColor(stringToColor(txtProperties.namedItem("font-outline-color").nodeValue())),
377 txtProperties.namedItem("font-outline").nodeValue().toDouble(),
378 Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
382 format.setForeground(QBrush(col));
383 cursor.mergeCharFormat(format);
384 txt->setTextInteractionFlags(Qt::NoTextInteraction);
385 if (txtProperties.namedItem("alignment").isNull() == false) {
386 txt->setTextWidth(txt->boundingRect().width());
387 QTextCursor cur = txt->textCursor();
388 QTextBlockFormat format = cur.blockFormat();
389 format.setAlignment((Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
390 cur.select(QTextCursor::Document);
391 cur.setBlockFormat(format);
392 txt->setTextCursor(cur);
393 cur.clearSelection();
394 txt->setTextCursor(cur);
397 if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
398 txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
400 if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
401 txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
405 if (!txtProperties.namedItem("typewriter").isNull()) {
406 QStringList effData = QStringList() << "typewriter" << txtProperties.namedItem("typewriter").nodeValue();
407 txt->setData(100, effData);
411 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsRectItem") {
412 QString rect = items.item(i).namedItem("content").attributes().namedItem("rect").nodeValue();
413 QString br_str = items.item(i).namedItem("content").attributes().namedItem("brushcolor").nodeValue();
414 QString pen_str = items.item(i).namedItem("content").attributes().namedItem("pencolor").nodeValue();
415 double penwidth = items.item(i).namedItem("content").attributes().namedItem("penwidth").nodeValue().toDouble();
416 QGraphicsRectItem *rec = m_scene->addRect(stringToRect(rect), QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin), QBrush(stringToColor(br_str)));
418 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
419 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
420 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
422 if (base64.isEmpty()) {
425 pix.loadFromData(QByteArray::fromBase64(base64.toAscii()));
427 QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
428 rec->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
429 rec->setData(Qt::UserRole, url);
430 if (!base64.isEmpty()) {
431 rec->setData(Qt::UserRole + 1, base64);
434 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
435 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
436 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
437 QGraphicsSvgItem *rec = NULL;
438 if (base64.isEmpty()) {
439 rec = new QGraphicsSvgItem(url);
441 rec = new QGraphicsSvgItem();
442 QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toAscii()), rec);
443 rec->setSharedRenderer(renderer);
444 //QString elem=rec->elementId();
445 //QRectF bounds = renderer->boundsOnElement(elem);
448 m_scene->addItem(rec);
449 rec->setData(Qt::UserRole, url);
450 if (!base64.isEmpty()) {
451 rec->setData(Qt::UserRole + 1, base64);
459 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
460 items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
462 QDomElement trans = items.item(i).namedItem("position").firstChild().toElement();
463 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue()));
464 QString rotate = trans.attribute("rotation");
465 if (!rotate.isEmpty()) gitem->setData(ROTATEFACTOR, stringToList(rotate));
466 QString zoom = trans.attribute("zoom");
467 if (!zoom.isEmpty()) gitem->setData(ZOOMFACTOR, zoom.toInt());
468 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
469 if (zValue > maxZValue) maxZValue = zValue;
470 gitem->setZValue(zValue);
471 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
473 #if QT_VERSION >= 0x040600
475 QDomNode eff = items.item(i).namedItem("effect");
477 QDomElement e = eff.toElement();
478 if (e.attribute("type") == "blur") {
479 QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
480 blur->setBlurRadius(e.attribute("blurradius").toInt());
481 gitem->setGraphicsEffect(blur);
482 } else if (e.attribute("type") == "shadow") {
483 QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
484 shadow->setBlurRadius(e.attribute("blurradius").toInt());
485 shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
486 gitem->setGraphicsEffect(shadow);
492 if (items.item(i).nodeName() == "background") {
493 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
494 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
495 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
496 QList<QGraphicsItem *> items = m_scene->items();
497 for (int i = 0; i < items.size(); i++) {
498 if (items.at(i)->zValue() == -1100) {
499 ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
503 } else if (items.item(i).nodeName() == "startviewport" && startv) {
504 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
505 QRectF r = stringToRect(rect);
506 startv->setRect(0, 0, r.width(), r.height());
507 startv->setPos(r.topLeft());
508 } else if (items.item(i).nodeName() == "endviewport" && endv) {
509 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
510 QRectF r = stringToRect(rect);
511 endv->setRect(0, 0, r.width(), r.height());
512 endv->setPos(r.topLeft());
519 QString TitleDocument::colorToString(const QColor& c)
521 QString ret = "%1,%2,%3,%4";
522 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
526 QString TitleDocument::rectFToString(const QRectF& c)
528 QString ret = "%1,%2,%3,%4";
529 ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
533 QRectF TitleDocument::stringToRect(const QString & s)
536 QStringList l = s.split(',');
539 return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
542 QColor TitleDocument::stringToColor(const QString & s)
544 QStringList l = s.split(',');
547 return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
550 QTransform TitleDocument::stringToTransform(const QString& s)
552 QStringList l = s.split(',');
556 l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
557 l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
558 l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
562 QList<QVariant> TitleDocument::stringToList(const QString & s)
564 QStringList l = s.split(',');
566 return QList<QVariant>();
567 return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
570 int TitleDocument::frameWidth() const
575 int TitleDocument::frameHeight() const