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->setData(Qt::UserRole, url);
424 if (!base64.isEmpty()) {
425 rec->setData(Qt::UserRole + 1, base64);
428 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
429 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
430 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
431 QGraphicsSvgItem *rec = NULL;
432 if (base64.isEmpty()) {
433 rec = new QGraphicsSvgItem(url);
435 rec = new QGraphicsSvgItem();
436 QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toAscii()), rec);
437 rec->setSharedRenderer(renderer);
438 //QString elem=rec->elementId();
439 //QRectF bounds = renderer->boundsOnElement(elem);
442 m_scene->addItem(rec);
443 rec->setData(Qt::UserRole, url);
444 if (!base64.isEmpty()) {
445 rec->setData(Qt::UserRole + 1, base64);
453 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
454 items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
456 QDomElement trans = items.item(i).namedItem("position").firstChild().toElement();
457 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue()));
458 QString rotate = trans.attribute("rotation");
459 if (!rotate.isEmpty()) gitem->setData(ROTATEFACTOR, stringToList(rotate));
460 QString zoom = trans.attribute("zoom");
461 if (!zoom.isEmpty()) gitem->setData(ZOOMFACTOR, zoom.toInt());
462 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
463 if (zValue > maxZValue) maxZValue = zValue;
464 gitem->setZValue(zValue);
465 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
467 #if QT_VERSION >= 0x040600
469 QDomNode eff = items.item(i).namedItem("effect");
471 QDomElement e = eff.toElement();
472 if (e.attribute("type") == "blur") {
473 QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
474 blur->setBlurRadius(e.attribute("blurradius").toInt());
475 gitem->setGraphicsEffect(blur);
476 } else if (e.attribute("type") == "shadow") {
477 QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
478 shadow->setBlurRadius(e.attribute("blurradius").toInt());
479 shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
480 gitem->setGraphicsEffect(shadow);
486 if (items.item(i).nodeName() == "background") {
487 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
488 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
489 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
490 QList<QGraphicsItem *> items = m_scene->items();
491 for (int i = 0; i < items.size(); i++) {
492 if (items.at(i)->zValue() == -1100) {
493 ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
497 } else if (items.item(i).nodeName() == "startviewport" && startv) {
498 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
499 QRectF r = stringToRect(rect);
500 startv->setRect(0, 0, r.width(), r.height());
501 startv->setPos(r.topLeft());
502 } else if (items.item(i).nodeName() == "endviewport" && endv) {
503 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
504 QRectF r = stringToRect(rect);
505 endv->setRect(0, 0, r.width(), r.height());
506 endv->setPos(r.topLeft());
513 QString TitleDocument::colorToString(const QColor& c)
515 QString ret = "%1,%2,%3,%4";
516 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
520 QString TitleDocument::rectFToString(const QRectF& c)
522 QString ret = "%1,%2,%3,%4";
523 ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
527 QRectF TitleDocument::stringToRect(const QString & s)
530 QStringList l = s.split(',');
533 return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
536 QColor TitleDocument::stringToColor(const QString & s)
538 QStringList l = s.split(',');
541 return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
544 QTransform TitleDocument::stringToTransform(const QString& s)
546 QStringList l = s.split(',');
550 l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
551 l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
552 l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
556 QList<QVariant> TitleDocument::stringToList(const QString & s)
558 QStringList l = s.split(',');
560 return QList<QVariant>();
561 return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
564 int TitleDocument::frameWidth() const
569 int TitleDocument::frameHeight() const