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()) {
175 QVariant variant = t->data(102);
176 QColor outlineColor = variant.value<QColor>();
177 content.setAttribute("font-outline-color", colorToString(outlineColor));
180 if (!t->data(100).isNull()) {
181 QStringList effectParams = t->data(100).toStringList();
182 QString effectName = effectParams.takeFirst();
183 content.setAttribute(effectName, effectParams.join(";"));
186 // Only save when necessary.
187 if (t->data(OriginXLeft).toInt() == AxisInverted) {
188 content.setAttribute("kdenlive-axis-x-inverted", t->data(OriginXLeft).toInt());
190 if (t->data(OriginYTop).toInt() == AxisInverted) {
191 content.setAttribute("kdenlive-axis-y-inverted", t->data(OriginYTop).toInt());
193 if (t->textWidth() != -1) {
194 content.setAttribute("alignment", t->textCursor().blockFormat().alignment());
202 QDomElement pos = doc.createElement("position");
203 pos.setAttribute("x", item->pos().x());
204 pos.setAttribute("y", item->pos().y());
205 QTransform transform = item->transform();
206 QDomElement tr = doc.createElement("transform");
207 if (!item->data(ZOOMFACTOR).isNull()) {
208 tr.setAttribute("zoom", QString::number(item->data(ZOOMFACTOR).toInt()));
210 if (!item->data(ROTATEFACTOR).isNull()) {
211 QList<QVariant> rotlist = item->data(ROTATEFACTOR).toList();
212 tr.setAttribute("rotation", QString("%1,%2,%3").arg(rotlist[0].toDouble()).arg(rotlist[1].toDouble()).arg(rotlist[2].toDouble()));
214 tr.appendChild(doc.createTextNode(
215 QString("%1,%2,%3,%4,%5,%6,%7,%8,%9").arg(
216 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())
219 e.setAttribute("z-index", item->zValue());
222 #if QT_VERSION >= 0x040600
224 QGraphicsEffect *eff = item->graphicsEffect();
226 QGraphicsBlurEffect *blur = static_cast <QGraphicsBlurEffect *>(eff);
227 QDomElement effect = doc.createElement("effect");
229 effect.setAttribute("type", "blur");
230 effect.setAttribute("blurradius", blur->blurRadius());
232 QGraphicsDropShadowEffect *shadow = static_cast <QGraphicsDropShadowEffect *>(eff);
234 effect.setAttribute("type", "shadow");
235 effect.setAttribute("blurradius", shadow->blurRadius());
236 effect.setAttribute("xoffset", shadow->xOffset());
237 effect.setAttribute("yoffset", shadow->yOffset());
240 e.appendChild(effect);
245 e.appendChild(content);
246 if (item->zValue() > -1000) main.appendChild(e);
248 if (startv && endv) {
249 QDomElement endp = doc.createElement("endviewport");
250 QDomElement startp = doc.createElement("startviewport");
251 QRectF r(endv->pos().x(), endv->pos().y(), endv->rect().width(), endv->rect().height());
252 endp.setAttribute("rect", rectFToString(r));
253 QRectF r2(startv->pos().x(), startv->pos().y(), startv->rect().width(), startv->rect().height());
254 startp.setAttribute("rect", rectFToString(r2));
256 main.appendChild(startp);
257 main.appendChild(endp);
259 QDomElement backgr = doc.createElement("background");
260 QColor color = getBackgroundColor();
261 backgr.setAttribute("color", colorToString(color));
262 main.appendChild(backgr);
267 /** \brief Get the background color (incl. alpha) from the document, if possibly
268 * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */
269 QColor TitleDocument::getBackgroundColor()
271 QColor color(0, 0, 0, 0);
273 QList<QGraphicsItem *> items = m_scene->items();
274 for (int i = 0; i < items.size(); i++) {
275 if (items.at(i)->zValue() == -1100) {
276 color = ((QGraphicsRectItem *)items.at(i))->brush().color();
285 bool TitleDocument::saveDocument(const KUrl& url, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int out, bool embed)
290 QDomDocument doc = xml(startv, endv, embed);
291 doc.documentElement().setAttribute("out", out);
292 KTemporaryFile tmpfile;
293 if (!tmpfile.open()) {
294 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
297 QFile xmlf(tmpfile.fileName());
298 xmlf.open(QIODevice::WriteOnly);
299 xmlf.write(doc.toString().toUtf8());
300 if (xmlf.error() != QFile::NoError) {
305 return KIO::NetAccess::upload(tmpfile.fileName(), url, 0);
308 int TitleDocument::loadFromXml(QDomDocument doc, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int *out, const QString& projectpath)
310 m_projectPath = projectpath;
311 QDomNodeList titles = doc.elementsByTagName("kdenlivetitle");
312 //TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
313 if (doc.documentElement().hasAttribute("width") && doc.documentElement().hasAttribute("height")) {
314 int doc_width = doc.documentElement().attribute("width").toInt();
315 int doc_height = doc.documentElement().attribute("height").toInt();
316 if (doc_width != m_width || doc_height != m_height) {
317 KMessageBox::information(kapp->activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
318 //TODO: convert using QTransform
320 m_height = doc_height;
323 // Document has no size info, it is likely an old version title, so ignore viewport data
324 QDomNodeList viewportlist = doc.documentElement().elementsByTagName("startviewport");
325 if (!viewportlist.isEmpty()) {
326 doc.documentElement().removeChild(viewportlist.at(0));
328 viewportlist = doc.documentElement().elementsByTagName("endviewport");
329 if (!viewportlist.isEmpty()) {
330 doc.documentElement().removeChild(viewportlist.at(0));
333 //TODO: get default title duration instead of hardcoded one
334 if (doc.documentElement().hasAttribute("out"))
335 *out = doc.documentElement().attribute("out").toInt();
342 QDomNodeList items = titles.item(0).childNodes();
343 for (int i = 0; i < items.count(); i++) {
344 QGraphicsItem *gitem = NULL;
345 kDebug() << items.item(i).attributes().namedItem("type").nodeValue();
346 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
347 if (zValue > -1000) {
348 if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
349 QDomNamedNodeMap txtProperties = items.item(i).namedItem("content").attributes();
350 QFont font(txtProperties.namedItem("font").nodeValue());
352 QDomNode node = txtProperties.namedItem("font-bold");
353 if (!node.isNull()) {
354 // Old: Bold/Not bold.
355 font.setBold(node.nodeValue().toInt());
357 // New: Font weight (QFont::)
358 font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
360 //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
361 font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
362 font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());
363 // Older Kdenlive version did not store pixel size but point size
364 if (txtProperties.namedItem("font-pixel-size").isNull()) {
365 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"));
367 f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
368 font.setPixelSize(QFontInfo(f2).pixelSize());
370 font.setPixelSize(txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
371 QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
372 QGraphicsTextItem *txt = m_scene->addText(items.item(i).namedItem("content").firstChild().nodeValue(), font);
373 QTextCursor cursor(txt->document());
374 cursor.select(QTextCursor::Document);
375 QTextCharFormat format;
376 if (txtProperties.namedItem("font-outline").nodeValue().toDouble() > 0.0) {
377 txt->setData(101, txtProperties.namedItem("font-outline").nodeValue().toDouble());
378 txt->setData(102, stringToColor(txtProperties.namedItem("font-outline-color").nodeValue()));
379 format.setTextOutline(
380 QPen(QColor(stringToColor(txtProperties.namedItem("font-outline-color").nodeValue())),
381 txtProperties.namedItem("font-outline").nodeValue().toDouble(),
382 Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
386 format.setForeground(QBrush(col));
387 cursor.mergeCharFormat(format);
388 txt->setTextInteractionFlags(Qt::NoTextInteraction);
389 if (txtProperties.namedItem("alignment").isNull() == false) {
390 txt->setTextWidth(txt->boundingRect().width());
391 QTextCursor cur = txt->textCursor();
392 QTextBlockFormat format = cur.blockFormat();
393 format.setAlignment((Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
394 cur.select(QTextCursor::Document);
395 cur.setBlockFormat(format);
396 txt->setTextCursor(cur);
397 cur.clearSelection();
398 txt->setTextCursor(cur);
401 if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
402 txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
404 if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
405 txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
409 if (!txtProperties.namedItem("typewriter").isNull()) {
410 QStringList effData = QStringList() << "typewriter" << txtProperties.namedItem("typewriter").nodeValue();
411 txt->setData(100, effData);
415 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsRectItem") {
416 QString rect = items.item(i).namedItem("content").attributes().namedItem("rect").nodeValue();
417 QString br_str = items.item(i).namedItem("content").attributes().namedItem("brushcolor").nodeValue();
418 QString pen_str = items.item(i).namedItem("content").attributes().namedItem("pencolor").nodeValue();
419 double penwidth = items.item(i).namedItem("content").attributes().namedItem("penwidth").nodeValue().toDouble();
420 QGraphicsRectItem *rec = m_scene->addRect(stringToRect(rect), QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin), QBrush(stringToColor(br_str)));
422 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
423 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
424 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
426 if (base64.isEmpty()) {
429 pix.loadFromData(QByteArray::fromBase64(base64.toAscii()));
431 QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
432 rec->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
433 rec->setData(Qt::UserRole, url);
434 if (!base64.isEmpty()) {
435 rec->setData(Qt::UserRole + 1, base64);
438 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
439 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
440 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
441 QGraphicsSvgItem *rec = NULL;
442 if (base64.isEmpty()) {
443 rec = new QGraphicsSvgItem(url);
445 rec = new QGraphicsSvgItem();
446 QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toAscii()), rec);
447 rec->setSharedRenderer(renderer);
448 //QString elem=rec->elementId();
449 //QRectF bounds = renderer->boundsOnElement(elem);
452 m_scene->addItem(rec);
453 rec->setData(Qt::UserRole, url);
454 if (!base64.isEmpty()) {
455 rec->setData(Qt::UserRole + 1, base64);
463 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
464 items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
466 QDomElement trans = items.item(i).namedItem("position").firstChild().toElement();
467 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue()));
468 QString rotate = trans.attribute("rotation");
469 if (!rotate.isEmpty()) gitem->setData(ROTATEFACTOR, stringToList(rotate));
470 QString zoom = trans.attribute("zoom");
471 if (!zoom.isEmpty()) gitem->setData(ZOOMFACTOR, zoom.toInt());
472 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
473 if (zValue > maxZValue) maxZValue = zValue;
474 gitem->setZValue(zValue);
475 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
477 #if QT_VERSION >= 0x040600
479 QDomNode eff = items.item(i).namedItem("effect");
481 QDomElement e = eff.toElement();
482 if (e.attribute("type") == "blur") {
483 QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
484 blur->setBlurRadius(e.attribute("blurradius").toInt());
485 gitem->setGraphicsEffect(blur);
486 } else if (e.attribute("type") == "shadow") {
487 QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
488 shadow->setBlurRadius(e.attribute("blurradius").toInt());
489 shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
490 gitem->setGraphicsEffect(shadow);
496 if (items.item(i).nodeName() == "background") {
497 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
498 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
499 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
500 QList<QGraphicsItem *> items = m_scene->items();
501 for (int i = 0; i < items.size(); i++) {
502 if (items.at(i)->zValue() == -1100) {
503 ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
507 } else if (items.item(i).nodeName() == "startviewport" && startv) {
508 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
509 QRectF r = stringToRect(rect);
510 startv->setRect(0, 0, r.width(), r.height());
511 startv->setPos(r.topLeft());
512 } else if (items.item(i).nodeName() == "endviewport" && endv) {
513 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
514 QRectF r = stringToRect(rect);
515 endv->setRect(0, 0, r.width(), r.height());
516 endv->setPos(r.topLeft());
523 QString TitleDocument::colorToString(const QColor& c)
525 QString ret = "%1,%2,%3,%4";
526 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
530 QString TitleDocument::rectFToString(const QRectF& c)
532 QString ret = "%1,%2,%3,%4";
533 ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
537 QRectF TitleDocument::stringToRect(const QString & s)
540 QStringList l = s.split(',');
543 return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
546 QColor TitleDocument::stringToColor(const QString & s)
548 QStringList l = s.split(',');
551 return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
554 QTransform TitleDocument::stringToTransform(const QString& s)
556 QStringList l = s.split(',');
560 l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
561 l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
562 l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
566 QList<QVariant> TitleDocument::stringToList(const QString & s)
568 QStringList l = s.split(',');
570 return QList<QVariant>();
571 return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
574 int TitleDocument::frameWidth() const
579 int TitleDocument::frameHeight() const