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 if (!xmlf.open(QIODevice::WriteOnly))
302 xmlf.write(doc.toString().toUtf8());
303 if (xmlf.error() != QFile::NoError) {
308 return KIO::NetAccess::upload(tmpfile.fileName(), url, 0);
311 int TitleDocument::loadFromXml(QDomDocument doc, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int *out, const QString& projectpath)
313 m_projectPath = projectpath;
314 QDomNodeList titles = doc.elementsByTagName("kdenlivetitle");
315 //TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
316 if (doc.documentElement().hasAttribute("width") && doc.documentElement().hasAttribute("height")) {
317 int doc_width = doc.documentElement().attribute("width").toInt();
318 int doc_height = doc.documentElement().attribute("height").toInt();
319 if (doc_width != m_width || doc_height != m_height) {
320 KMessageBox::information(kapp->activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
321 //TODO: convert using QTransform
323 m_height = doc_height;
326 // Document has no size info, it is likely an old version title, so ignore viewport data
327 QDomNodeList viewportlist = doc.documentElement().elementsByTagName("startviewport");
328 if (!viewportlist.isEmpty()) {
329 doc.documentElement().removeChild(viewportlist.at(0));
331 viewportlist = doc.documentElement().elementsByTagName("endviewport");
332 if (!viewportlist.isEmpty()) {
333 doc.documentElement().removeChild(viewportlist.at(0));
336 //TODO: get default title duration instead of hardcoded one
337 if (doc.documentElement().hasAttribute("out"))
338 *out = doc.documentElement().attribute("out").toInt();
345 QDomNodeList items = titles.item(0).childNodes();
346 for (int i = 0; i < items.count(); i++) {
347 QGraphicsItem *gitem = NULL;
348 kDebug() << items.item(i).attributes().namedItem("type").nodeValue();
349 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
350 if (zValue > -1000) {
351 if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
352 QDomNamedNodeMap txtProperties = items.item(i).namedItem("content").attributes();
353 QFont font(txtProperties.namedItem("font").nodeValue());
355 QDomNode node = txtProperties.namedItem("font-bold");
356 if (!node.isNull()) {
357 // Old: Bold/Not bold.
358 font.setBold(node.nodeValue().toInt());
360 // New: Font weight (QFont::)
361 font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
363 //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
364 font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
365 font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());
366 // Older Kdenlive version did not store pixel size but point size
367 if (txtProperties.namedItem("font-pixel-size").isNull()) {
368 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"));
370 f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
371 font.setPixelSize(QFontInfo(f2).pixelSize());
373 font.setPixelSize(txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
374 QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
375 QGraphicsTextItem *txt = m_scene->addText(items.item(i).namedItem("content").firstChild().nodeValue(), font);
376 QTextCursor cursor(txt->document());
377 cursor.select(QTextCursor::Document);
378 QTextCharFormat format;
379 if (txtProperties.namedItem("font-outline").nodeValue().toDouble() > 0.0) {
380 txt->setData(101, txtProperties.namedItem("font-outline").nodeValue().toDouble());
381 txt->setData(102, stringToColor(txtProperties.namedItem("font-outline-color").nodeValue()));
382 format.setTextOutline(
383 QPen(QColor(stringToColor(txtProperties.namedItem("font-outline-color").nodeValue())),
384 txtProperties.namedItem("font-outline").nodeValue().toDouble(),
385 Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
389 format.setForeground(QBrush(col));
390 cursor.mergeCharFormat(format);
391 txt->setTextInteractionFlags(Qt::NoTextInteraction);
392 if (txtProperties.namedItem("alignment").isNull() == false) {
393 txt->setTextWidth(txt->boundingRect().width());
394 QTextCursor cur = txt->textCursor();
395 QTextBlockFormat format = cur.blockFormat();
396 format.setAlignment((Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
397 cur.select(QTextCursor::Document);
398 cur.setBlockFormat(format);
399 txt->setTextCursor(cur);
400 cur.clearSelection();
401 txt->setTextCursor(cur);
404 if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
405 txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
407 if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
408 txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
412 if (!txtProperties.namedItem("typewriter").isNull()) {
413 QStringList effData = QStringList() << "typewriter" << txtProperties.namedItem("typewriter").nodeValue();
414 txt->setData(100, effData);
418 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsRectItem") {
419 QString rect = items.item(i).namedItem("content").attributes().namedItem("rect").nodeValue();
420 QString br_str = items.item(i).namedItem("content").attributes().namedItem("brushcolor").nodeValue();
421 QString pen_str = items.item(i).namedItem("content").attributes().namedItem("pencolor").nodeValue();
422 double penwidth = items.item(i).namedItem("content").attributes().namedItem("penwidth").nodeValue().toDouble();
423 QGraphicsRectItem *rec = m_scene->addRect(stringToRect(rect), QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin), QBrush(stringToColor(br_str)));
425 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
426 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
427 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
429 if (base64.isEmpty()) {
432 pix.loadFromData(QByteArray::fromBase64(base64.toAscii()));
434 QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
435 rec->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
436 rec->setData(Qt::UserRole, url);
437 if (!base64.isEmpty()) {
438 rec->setData(Qt::UserRole + 1, base64);
441 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
442 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
443 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
444 QGraphicsSvgItem *rec = NULL;
445 if (base64.isEmpty()) {
446 rec = new QGraphicsSvgItem(url);
448 rec = new QGraphicsSvgItem();
449 QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toAscii()), rec);
450 rec->setSharedRenderer(renderer);
451 //QString elem=rec->elementId();
452 //QRectF bounds = renderer->boundsOnElement(elem);
455 m_scene->addItem(rec);
456 rec->setData(Qt::UserRole, url);
457 if (!base64.isEmpty()) {
458 rec->setData(Qt::UserRole + 1, base64);
466 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
467 items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
469 QDomElement trans = items.item(i).namedItem("position").firstChild().toElement();
470 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue()));
471 QString rotate = trans.attribute("rotation");
472 if (!rotate.isEmpty()) gitem->setData(ROTATEFACTOR, stringToList(rotate));
473 QString zoom = trans.attribute("zoom");
474 if (!zoom.isEmpty()) gitem->setData(ZOOMFACTOR, zoom.toInt());
475 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
476 if (zValue > maxZValue) maxZValue = zValue;
477 gitem->setZValue(zValue);
478 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
480 #if QT_VERSION >= 0x040600
482 QDomNode eff = items.item(i).namedItem("effect");
484 QDomElement e = eff.toElement();
485 if (e.attribute("type") == "blur") {
486 QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
487 blur->setBlurRadius(e.attribute("blurradius").toInt());
488 gitem->setGraphicsEffect(blur);
489 } else if (e.attribute("type") == "shadow") {
490 QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
491 shadow->setBlurRadius(e.attribute("blurradius").toInt());
492 shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
493 gitem->setGraphicsEffect(shadow);
499 if (items.item(i).nodeName() == "background") {
500 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
501 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
502 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
503 QList<QGraphicsItem *> items = m_scene->items();
504 for (int i = 0; i < items.size(); i++) {
505 if (items.at(i)->zValue() == -1100) {
506 ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
510 } else if (items.item(i).nodeName() == "startviewport" && startv) {
511 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
512 QRectF r = stringToRect(rect);
513 startv->setRect(0, 0, r.width(), r.height());
514 startv->setPos(r.topLeft());
515 } else if (items.item(i).nodeName() == "endviewport" && endv) {
516 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
517 QRectF r = stringToRect(rect);
518 endv->setRect(0, 0, r.width(), r.height());
519 endv->setPos(r.topLeft());
526 QString TitleDocument::colorToString(const QColor& c)
528 QString ret = "%1,%2,%3,%4";
529 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
533 QString TitleDocument::rectFToString(const QRectF& c)
535 QString ret = "%1,%2,%3,%4";
536 ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
540 QRectF TitleDocument::stringToRect(const QString & s)
543 QStringList l = s.split(',');
546 return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
549 QColor TitleDocument::stringToColor(const QString & s)
551 QStringList l = s.split(',');
554 return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
557 QTransform TitleDocument::stringToTransform(const QString& s)
559 QStringList l = s.split(',');
563 l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
564 l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
565 l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
569 QList<QVariant> TitleDocument::stringToList(const QString & s)
571 QStringList l = s.split(',');
573 return QList<QVariant>();
574 return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
577 int TitleDocument::frameWidth() const
582 int TitleDocument::frameHeight() const