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>
40 #if QT_VERSION >= 0x040600
41 #include <QGraphicsEffect>
42 #include <QGraphicsBlurEffect>
43 #include <QGraphicsDropShadowEffect>
46 QByteArray fileToByteArray(const QString& filename)
50 if (file.open(QIODevice::ReadOnly)) {
51 while (!file.atEnd()) {
52 ret.append(file.readLine());
58 TitleDocument::TitleDocument()
63 void TitleDocument::setScene(QGraphicsScene* _scene, int width, int height)
70 int TitleDocument::base64ToUrl(QGraphicsItem* item, QDomElement& content, bool embed)
73 if (!item->data(Qt::UserRole + 1).toString().isEmpty()) {
74 content.setAttribute("base64", item->data(Qt::UserRole + 1).toString());
75 } else if (!item->data(Qt::UserRole).toString().isEmpty()) {
76 content.setAttribute("base64", fileToByteArray(item->data(Qt::UserRole).toString()).toBase64().data());
78 content.removeAttribute("url");
80 // save for project files to disk
81 QString base64 = item->data(Qt::UserRole + 1).toString();
82 if (!base64.isEmpty()) {
84 if (!m_projectPath.isEmpty()) {
85 titlePath = m_projectPath;
87 titlePath = "/tmp/titles";
89 QString filename = extractBase64Image(titlePath, base64);
90 if (!filename.isEmpty()) {
91 content.setAttribute("url", filename);
92 content.removeAttribute("base64");
104 const QString TitleDocument::extractBase64Image(const QString &titlePath, const QString &data)
106 QString filename = titlePath + QString(QCryptographicHash::hash(data.toAscii(), QCryptographicHash::Md5).toHex().append(".titlepart"));
107 KStandardDirs::makeDir(titlePath);
109 if (f.open(QIODevice::WriteOnly)) {
110 f.write(QByteArray::fromBase64(data.toAscii())) ;
117 QDomDocument TitleDocument::xml(QGraphicsRectItem* startv, QGraphicsRectItem* endv, bool embed)
121 QDomElement main = doc.createElement("kdenlivetitle");
122 main.setAttribute("width", m_width);
123 main.setAttribute("height", m_height);
124 doc.appendChild(main);
126 foreach(QGraphicsItem * item, m_scene->items()) {
127 QDomElement e = doc.createElement("item");
128 QDomElement content = doc.createElement("content");
130 QGraphicsTextItem *t;
132 switch (item->type()) {
134 e.setAttribute("type", "QGraphicsPixmapItem");
135 content.setAttribute("url", item->data(Qt::UserRole).toString());
136 base64ToUrl(item, content, embed);
139 e.setAttribute("type", "QGraphicsSvgItem");
140 content.setAttribute("url", item->data(Qt::UserRole).toString());
141 base64ToUrl(item, content, embed);
144 e.setAttribute("type", "QGraphicsRectItem");
145 content.setAttribute("rect", rectFToString(((QGraphicsRectItem*)item)->rect().normalized()));
146 content.setAttribute("pencolor", colorToString(((QGraphicsRectItem*)item)->pen().color()));
147 content.setAttribute("penwidth", ((QGraphicsRectItem*)item)->pen().width());
148 content.setAttribute("brushcolor", colorToString(((QGraphicsRectItem*)item)->brush().color()));
151 e.setAttribute("type", "QGraphicsTextItem");
152 t = static_cast<QGraphicsTextItem *>(item);
153 // Don't save empty text nodes
154 if (t->toPlainText().simplified().isEmpty()) continue;
155 //content.appendChild(doc.createTextNode(((QGraphicsTextItem*)item)->toHtml()));
156 content.appendChild(doc.createTextNode(t->toPlainText()));
158 content.setAttribute("font", font.family());
159 content.setAttribute("font-weight", font.weight());
160 content.setAttribute("font-pixel-size", font.pixelSize());
161 content.setAttribute("font-italic", font.italic());
162 content.setAttribute("font-underline", font.underline());
164 QTextCursor cursor(t->document());
165 cursor.select(QTextCursor::Document);
166 QColor fontcolor = cursor.charFormat().foreground().color();
167 content.setAttribute("font-color", colorToString(fontcolor));
168 if (!t->data(101).isNull()) content.setAttribute("font-outline", t->data(101).toDouble());
169 if (!t->data(102).isNull()) content.setAttribute("font-outline-color", colorToString(QColor(t->data(102).toString())));
171 if (!t->data(100).isNull()) {
172 QStringList effectParams = t->data(100).toStringList();
173 QString effectName = effectParams.takeFirst();
174 content.setAttribute(effectName, effectParams.join(";"));
177 // Only save when necessary.
178 if (t->data(OriginXLeft).toInt() == AxisInverted) {
179 content.setAttribute("kdenlive-axis-x-inverted", t->data(OriginXLeft).toInt());
181 if (t->data(OriginYTop).toInt() == AxisInverted) {
182 content.setAttribute("kdenlive-axis-y-inverted", t->data(OriginYTop).toInt());
184 if (t->textWidth() != -1) {
185 content.setAttribute("alignment", t->textCursor().blockFormat().alignment());
193 QDomElement pos = doc.createElement("position");
194 pos.setAttribute("x", item->pos().x());
195 pos.setAttribute("y", item->pos().y());
196 QTransform transform = item->transform();
197 QDomElement tr = doc.createElement("transform");
198 if (!item->data(ZOOMFACTOR).isNull()) {
199 tr.setAttribute("zoom", QString::number(item->data(ZOOMFACTOR).toInt()));
201 if (!item->data(ROTATEFACTOR).isNull()) {
202 QList<QVariant> rotlist = item->data(ROTATEFACTOR).toList();
203 tr.setAttribute("rotation", QString("%1,%2,%3").arg(rotlist[0].toDouble()).arg(rotlist[1].toDouble()).arg(rotlist[2].toDouble()));
205 tr.appendChild(doc.createTextNode(
206 QString("%1,%2,%3,%4,%5,%6,%7,%8,%9").arg(
207 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())
210 e.setAttribute("z-index", item->zValue());
213 #if QT_VERSION >= 0x040600
215 QGraphicsEffect *eff = item->graphicsEffect();
217 QGraphicsBlurEffect *blur = static_cast <QGraphicsBlurEffect *>(eff);
218 QDomElement effect = doc.createElement("effect");
220 effect.setAttribute("type", "blur");
221 effect.setAttribute("blurradius", blur->blurRadius());
223 QGraphicsDropShadowEffect *shadow = static_cast <QGraphicsDropShadowEffect *>(eff);
225 effect.setAttribute("type", "shadow");
226 effect.setAttribute("blurradius", shadow->blurRadius());
227 effect.setAttribute("xoffset", shadow->xOffset());
228 effect.setAttribute("yoffset", shadow->yOffset());
231 e.appendChild(effect);
236 e.appendChild(content);
237 if (item->zValue() > -1000) main.appendChild(e);
239 if (startv && endv) {
240 QDomElement endp = doc.createElement("endviewport");
241 QDomElement startp = doc.createElement("startviewport");
242 QRectF r(endv->pos().x(), endv->pos().y(), endv->rect().width(), endv->rect().height());
243 endp.setAttribute("rect", rectFToString(r));
244 QRectF r2(startv->pos().x(), startv->pos().y(), startv->rect().width(), startv->rect().height());
245 startp.setAttribute("rect", rectFToString(r2));
247 main.appendChild(startp);
248 main.appendChild(endp);
250 QDomElement backgr = doc.createElement("background");
251 QColor color = getBackgroundColor();
252 backgr.setAttribute("color", colorToString(color));
253 main.appendChild(backgr);
258 /** \brief Get the background color (incl. alpha) from the document, if possibly
259 * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */
260 QColor TitleDocument::getBackgroundColor()
262 QColor color(0, 0, 0, 0);
264 QList<QGraphicsItem *> items = m_scene->items();
265 for (int i = 0; i < items.size(); i++) {
266 if (items.at(i)->zValue() == -1100) {
267 color = ((QGraphicsRectItem *)items.at(i))->brush().color();
276 bool TitleDocument::saveDocument(const KUrl& url, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int out, bool embed)
281 QDomDocument doc = xml(startv, endv, embed);
282 doc.documentElement().setAttribute("out", out);
283 KTemporaryFile tmpfile;
284 if (!tmpfile.open()) {
285 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
288 QFile xmlf(tmpfile.fileName());
289 xmlf.open(QIODevice::WriteOnly);
290 xmlf.write(doc.toString().toUtf8());
291 if (xmlf.error() != QFile::NoError) {
296 return KIO::NetAccess::upload(tmpfile.fileName(), url, 0);
299 int TitleDocument::loadFromXml(QDomDocument doc, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int *out, const QString& projectpath)
301 m_projectPath = projectpath;
302 QDomNodeList titles = doc.elementsByTagName("kdenlivetitle");
303 //TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
304 if (doc.documentElement().hasAttribute("width") && doc.documentElement().hasAttribute("height")) {
305 int doc_width = doc.documentElement().attribute("width").toInt();
306 int doc_height = doc.documentElement().attribute("height").toInt();
307 if (doc_width != m_width || doc_height != m_height) {
308 KMessageBox::information(kapp->activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
309 //TODO: convert using QTransform
311 m_height = doc_height;
314 // Document has no size info, it is likely an old version title, so ignore viewport data
315 QDomNodeList viewportlist = doc.documentElement().elementsByTagName("startviewport");
316 if (!viewportlist.isEmpty()) {
317 doc.documentElement().removeChild(viewportlist.at(0));
319 viewportlist = doc.documentElement().elementsByTagName("endviewport");
320 if (!viewportlist.isEmpty()) {
321 doc.documentElement().removeChild(viewportlist.at(0));
324 //TODO: get default title duration instead of hardcoded one
325 if (doc.documentElement().hasAttribute("out"))
326 *out = doc.documentElement().attribute("out").toInt();
333 QDomNodeList items = titles.item(0).childNodes();
334 for (int i = 0; i < items.count(); i++) {
335 QGraphicsItem *gitem = NULL;
336 kDebug() << items.item(i).attributes().namedItem("type").nodeValue();
337 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
338 if (zValue > -1000) {
339 if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
340 QDomNamedNodeMap txtProperties = items.item(i).namedItem("content").attributes();
341 QFont font(txtProperties.namedItem("font").nodeValue());
343 QDomNode node = txtProperties.namedItem("font-bold");
344 if (!node.isNull()) {
345 // Old: Bold/Not bold.
346 font.setBold(node.nodeValue().toInt());
348 // New: Font weight (QFont::)
349 font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
351 //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
352 font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
353 font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());
354 // Older Kdenlive version did not store pixel size but point size
355 if (txtProperties.namedItem("font-pixel-size").isNull()) {
356 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"));
358 f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
359 font.setPixelSize(QFontInfo(f2).pixelSize());
361 font.setPixelSize(txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
362 QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
363 QGraphicsTextItem *txt = m_scene->addText(items.item(i).namedItem("content").firstChild().nodeValue(), font);
364 QTextCursor cursor(txt->document());
365 cursor.select(QTextCursor::Document);
366 QTextCharFormat format;
367 if (txtProperties.namedItem("font-outline").nodeValue().toDouble() > 0.0) {
368 txt->setData(101, txtProperties.namedItem("font-outline").nodeValue().toDouble());
369 txt->setData(102, stringToColor(txtProperties.namedItem("font-outline-color").nodeValue()));
370 format.setTextOutline(
371 QPen(QColor(stringToColor(txtProperties.namedItem("font-outline-color").nodeValue())),
372 txtProperties.namedItem("font-outline").nodeValue().toDouble(),
373 Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
377 format.setForeground(QBrush(col));
378 cursor.mergeCharFormat(format);
379 txt->setTextInteractionFlags(Qt::NoTextInteraction);
380 if (txtProperties.namedItem("alignment").isNull() == false) {
381 txt->setTextWidth(txt->boundingRect().width());
382 QTextCursor cur = txt->textCursor();
383 QTextBlockFormat format = cur.blockFormat();
384 format.setAlignment((Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
385 cur.select(QTextCursor::Document);
386 cur.setBlockFormat(format);
387 txt->setTextCursor(cur);
388 cur.clearSelection();
389 txt->setTextCursor(cur);
392 if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
393 txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
395 if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
396 txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
400 if (!txtProperties.namedItem("typewriter").isNull()) {
401 QStringList effData = QStringList() << "typewriter" << txtProperties.namedItem("typewriter").nodeValue();
402 txt->setData(100, effData);
406 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsRectItem") {
407 QString rect = items.item(i).namedItem("content").attributes().namedItem("rect").nodeValue();
408 QString br_str = items.item(i).namedItem("content").attributes().namedItem("brushcolor").nodeValue();
409 QString pen_str = items.item(i).namedItem("content").attributes().namedItem("pencolor").nodeValue();
410 double penwidth = items.item(i).namedItem("content").attributes().namedItem("penwidth").nodeValue().toDouble();
411 QGraphicsRectItem *rec = m_scene->addRect(stringToRect(rect), QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin), QBrush(stringToColor(br_str)));
413 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
414 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
415 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
417 if (base64.isEmpty()) {
420 pix.loadFromData(QByteArray::fromBase64(base64.toAscii()));
422 QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
423 rec->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
424 rec->setData(Qt::UserRole, url);
425 if (!base64.isEmpty()) {
426 rec->setData(Qt::UserRole + 1, base64);
429 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
430 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
431 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
432 QGraphicsSvgItem *rec = NULL;
433 if (base64.isEmpty()) {
434 rec = new QGraphicsSvgItem(url);
436 rec = new QGraphicsSvgItem();
437 QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toAscii()), rec);
438 rec->setSharedRenderer(renderer);
439 //QString elem=rec->elementId();
440 //QRectF bounds = renderer->boundsOnElement(elem);
443 m_scene->addItem(rec);
444 rec->setData(Qt::UserRole, url);
445 if (!base64.isEmpty()) {
446 rec->setData(Qt::UserRole + 1, base64);
454 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
455 items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
457 QDomElement trans = items.item(i).namedItem("position").firstChild().toElement();
458 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue()));
459 QString rotate = trans.attribute("rotation");
460 if (!rotate.isEmpty()) gitem->setData(ROTATEFACTOR, stringToList(rotate));
461 QString zoom = trans.attribute("zoom");
462 if (!zoom.isEmpty()) gitem->setData(ZOOMFACTOR, zoom.toInt());
463 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
464 if (zValue > maxZValue) maxZValue = zValue;
465 gitem->setZValue(zValue);
466 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
468 #if QT_VERSION >= 0x040600
470 QDomNode eff = items.item(i).namedItem("effect");
472 QDomElement e = eff.toElement();
473 if (e.attribute("type") == "blur") {
474 QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
475 blur->setBlurRadius(e.attribute("blurradius").toInt());
476 gitem->setGraphicsEffect(blur);
477 } else if (e.attribute("type") == "shadow") {
478 QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
479 shadow->setBlurRadius(e.attribute("blurradius").toInt());
480 shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
481 gitem->setGraphicsEffect(shadow);
487 if (items.item(i).nodeName() == "background") {
488 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
489 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
490 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
491 QList<QGraphicsItem *> items = m_scene->items();
492 for (int i = 0; i < items.size(); i++) {
493 if (items.at(i)->zValue() == -1100) {
494 ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
498 } else if (items.item(i).nodeName() == "startviewport" && startv) {
499 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
500 QRectF r = stringToRect(rect);
501 startv->setRect(0, 0, r.width(), r.height());
502 startv->setPos(r.topLeft());
503 } else if (items.item(i).nodeName() == "endviewport" && endv) {
504 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
505 QRectF r = stringToRect(rect);
506 endv->setRect(0, 0, r.width(), r.height());
507 endv->setPos(r.topLeft());
514 QString TitleDocument::colorToString(const QColor& c)
516 QString ret = "%1,%2,%3,%4";
517 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
521 QString TitleDocument::rectFToString(const QRectF& c)
523 QString ret = "%1,%2,%3,%4";
524 ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
528 QRectF TitleDocument::stringToRect(const QString & s)
531 QStringList l = s.split(',');
534 return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
537 QColor TitleDocument::stringToColor(const QString & s)
539 QStringList l = s.split(',');
542 return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
545 QTransform TitleDocument::stringToTransform(const QString& s)
547 QStringList l = s.split(',');
551 l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
552 l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
553 l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
557 QList<QVariant> TitleDocument::stringToList(const QString & s)
559 QStringList l = s.split(',');
561 return QList<QVariant>();
562 return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
565 int TitleDocument::frameWidth() const
570 int TitleDocument::frameHeight() const