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("textwidth", QString::number(t->sceneBoundingRect().width()));
186 content.setAttribute(effectName, effectParams.join(";"));
189 // Only save when necessary.
190 if (t->data(OriginXLeft).toInt() == AxisInverted) {
191 content.setAttribute("kdenlive-axis-x-inverted", t->data(OriginXLeft).toInt());
193 if (t->data(OriginYTop).toInt() == AxisInverted) {
194 content.setAttribute("kdenlive-axis-y-inverted", t->data(OriginYTop).toInt());
196 if (t->textWidth() != -1) {
197 content.setAttribute("alignment", t->textCursor().blockFormat().alignment());
205 QDomElement pos = doc.createElement("position");
206 pos.setAttribute("x", item->pos().x());
207 pos.setAttribute("y", item->pos().y());
208 QTransform transform = item->transform();
209 QDomElement tr = doc.createElement("transform");
210 if (!item->data(ZOOMFACTOR).isNull()) {
211 tr.setAttribute("zoom", QString::number(item->data(ZOOMFACTOR).toInt()));
213 if (!item->data(ROTATEFACTOR).isNull()) {
214 QList<QVariant> rotlist = item->data(ROTATEFACTOR).toList();
215 tr.setAttribute("rotation", QString("%1,%2,%3").arg(rotlist[0].toDouble()).arg(rotlist[1].toDouble()).arg(rotlist[2].toDouble()));
217 tr.appendChild(doc.createTextNode(
218 QString("%1,%2,%3,%4,%5,%6,%7,%8,%9").arg(
219 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())
222 e.setAttribute("z-index", item->zValue());
225 #if QT_VERSION >= 0x040600
227 QGraphicsEffect *eff = item->graphicsEffect();
229 QGraphicsBlurEffect *blur = static_cast <QGraphicsBlurEffect *>(eff);
230 QDomElement effect = doc.createElement("effect");
232 effect.setAttribute("type", "blur");
233 effect.setAttribute("blurradius", blur->blurRadius());
235 QGraphicsDropShadowEffect *shadow = static_cast <QGraphicsDropShadowEffect *>(eff);
237 effect.setAttribute("type", "shadow");
238 effect.setAttribute("blurradius", shadow->blurRadius());
239 effect.setAttribute("xoffset", shadow->xOffset());
240 effect.setAttribute("yoffset", shadow->yOffset());
243 e.appendChild(effect);
248 e.appendChild(content);
249 if (item->zValue() > -1000) main.appendChild(e);
251 if (startv && endv) {
252 QDomElement endp = doc.createElement("endviewport");
253 QDomElement startp = doc.createElement("startviewport");
254 QRectF r(endv->pos().x(), endv->pos().y(), endv->rect().width(), endv->rect().height());
255 endp.setAttribute("rect", rectFToString(r));
256 QRectF r2(startv->pos().x(), startv->pos().y(), startv->rect().width(), startv->rect().height());
257 startp.setAttribute("rect", rectFToString(r2));
259 main.appendChild(startp);
260 main.appendChild(endp);
262 QDomElement backgr = doc.createElement("background");
263 QColor color = getBackgroundColor();
264 backgr.setAttribute("color", colorToString(color));
265 main.appendChild(backgr);
270 /** \brief Get the background color (incl. alpha) from the document, if possibly
271 * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */
272 QColor TitleDocument::getBackgroundColor()
274 QColor color(0, 0, 0, 0);
276 QList<QGraphicsItem *> items = m_scene->items();
277 for (int i = 0; i < items.size(); i++) {
278 if (items.at(i)->zValue() == -1100) {
279 color = ((QGraphicsRectItem *)items.at(i))->brush().color();
288 bool TitleDocument::saveDocument(const KUrl& url, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int duration, bool embed)
293 QDomDocument doc = xml(startv, endv, embed);
294 doc.documentElement().setAttribute("duration", duration);
295 // keep some time for backwards compatibility (opening projects with older versions) - 26/12/12
296 doc.documentElement().setAttribute("out", duration);
297 KTemporaryFile tmpfile;
298 if (!tmpfile.open()) {
299 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
302 QFile xmlf(tmpfile.fileName());
303 if (!xmlf.open(QIODevice::WriteOnly))
305 xmlf.write(doc.toString().toUtf8());
306 if (xmlf.error() != QFile::NoError) {
311 return KIO::NetAccess::upload(tmpfile.fileName(), url, 0);
314 int TitleDocument::loadFromXml(QDomDocument doc, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int *duration, const QString& projectpath)
316 m_projectPath = projectpath;
317 QDomNodeList titles = doc.elementsByTagName("kdenlivetitle");
318 //TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
319 if (doc.documentElement().hasAttribute("width") && doc.documentElement().hasAttribute("height")) {
320 int doc_width = doc.documentElement().attribute("width").toInt();
321 int doc_height = doc.documentElement().attribute("height").toInt();
322 if (doc_width != m_width || doc_height != m_height) {
323 KMessageBox::information(kapp->activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
324 //TODO: convert using QTransform
326 m_height = doc_height;
329 // Document has no size info, it is likely an old version title, so ignore viewport data
330 QDomNodeList viewportlist = doc.documentElement().elementsByTagName("startviewport");
331 if (!viewportlist.isEmpty()) {
332 doc.documentElement().removeChild(viewportlist.at(0));
334 viewportlist = doc.documentElement().elementsByTagName("endviewport");
335 if (!viewportlist.isEmpty()) {
336 doc.documentElement().removeChild(viewportlist.at(0));
339 //TODO: get default title duration instead of hardcoded one
340 if (doc.documentElement().hasAttribute("duration"))
341 *duration = doc.documentElement().attribute("duration").toInt();
342 else if (doc.documentElement().hasAttribute("out"))
343 *duration = doc.documentElement().attribute("out").toInt();
350 QDomNodeList items = titles.item(0).childNodes();
351 for (int i = 0; i < items.count(); i++) {
352 QGraphicsItem *gitem = NULL;
353 kDebug() << items.item(i).attributes().namedItem("type").nodeValue();
354 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
355 if (zValue > -1000) {
356 if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
357 QDomNamedNodeMap txtProperties = items.item(i).namedItem("content").attributes();
358 QFont font(txtProperties.namedItem("font").nodeValue());
360 QDomNode node = txtProperties.namedItem("font-bold");
361 if (!node.isNull()) {
362 // Old: Bold/Not bold.
363 font.setBold(node.nodeValue().toInt());
365 // New: Font weight (QFont::)
366 font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
368 //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
369 font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
370 font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());
371 // Older Kdenlive version did not store pixel size but point size
372 if (txtProperties.namedItem("font-pixel-size").isNull()) {
373 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"));
375 f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
376 font.setPixelSize(QFontInfo(f2).pixelSize());
378 font.setPixelSize(txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
379 QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
380 QGraphicsTextItem *txt = m_scene->addText(items.item(i).namedItem("content").firstChild().nodeValue(), font);
381 QTextCursor cursor(txt->document());
382 cursor.select(QTextCursor::Document);
383 QTextCharFormat format;
384 if (txtProperties.namedItem("font-outline").nodeValue().toDouble() > 0.0) {
385 txt->setData(101, txtProperties.namedItem("font-outline").nodeValue().toDouble());
386 txt->setData(102, stringToColor(txtProperties.namedItem("font-outline-color").nodeValue()));
387 format.setTextOutline(
388 QPen(QColor(stringToColor(txtProperties.namedItem("font-outline-color").nodeValue())),
389 txtProperties.namedItem("font-outline").nodeValue().toDouble(),
390 Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
394 format.setForeground(QBrush(col));
395 cursor.mergeCharFormat(format);
396 txt->setTextInteractionFlags(Qt::NoTextInteraction);
397 if (txtProperties.namedItem("alignment").isNull() == false) {
398 txt->setTextWidth(txt->boundingRect().width());
399 QTextCursor cur = txt->textCursor();
400 QTextBlockFormat format = cur.blockFormat();
401 format.setAlignment((Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
402 cur.select(QTextCursor::Document);
403 cur.setBlockFormat(format);
404 txt->setTextCursor(cur);
405 cur.clearSelection();
406 txt->setTextCursor(cur);
409 if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
410 txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
412 if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
413 txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
417 if (!txtProperties.namedItem("typewriter").isNull()) {
418 QStringList effData = QStringList() << "typewriter" << txtProperties.namedItem("typewriter").nodeValue();
419 txt->setData(100, effData);
423 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsRectItem") {
424 QString rect = items.item(i).namedItem("content").attributes().namedItem("rect").nodeValue();
425 QString br_str = items.item(i).namedItem("content").attributes().namedItem("brushcolor").nodeValue();
426 QString pen_str = items.item(i).namedItem("content").attributes().namedItem("pencolor").nodeValue();
427 double penwidth = items.item(i).namedItem("content").attributes().namedItem("penwidth").nodeValue().toDouble();
428 QGraphicsRectItem *rec = m_scene->addRect(stringToRect(rect), QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin), QBrush(stringToColor(br_str)));
430 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
431 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
432 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
434 if (base64.isEmpty()) {
437 pix.loadFromData(QByteArray::fromBase64(base64.toAscii()));
439 QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
440 rec->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
441 rec->setData(Qt::UserRole, url);
442 if (!base64.isEmpty()) {
443 rec->setData(Qt::UserRole + 1, base64);
446 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
447 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
448 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
449 QGraphicsSvgItem *rec = NULL;
450 if (base64.isEmpty()) {
451 rec = new QGraphicsSvgItem(url);
453 rec = new QGraphicsSvgItem();
454 QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toAscii()), rec);
455 rec->setSharedRenderer(renderer);
456 //QString elem=rec->elementId();
457 //QRectF bounds = renderer->boundsOnElement(elem);
460 m_scene->addItem(rec);
461 rec->setData(Qt::UserRole, url);
462 if (!base64.isEmpty()) {
463 rec->setData(Qt::UserRole + 1, base64);
471 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
472 items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
474 QDomElement trans = items.item(i).namedItem("position").firstChild().toElement();
475 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue()));
476 QString rotate = trans.attribute("rotation");
477 if (!rotate.isEmpty()) gitem->setData(ROTATEFACTOR, stringToList(rotate));
478 QString zoom = trans.attribute("zoom");
479 if (!zoom.isEmpty()) gitem->setData(ZOOMFACTOR, zoom.toInt());
480 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
481 if (zValue > maxZValue) maxZValue = zValue;
482 gitem->setZValue(zValue);
483 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
485 #if QT_VERSION >= 0x040600
487 QDomNode eff = items.item(i).namedItem("effect");
489 QDomElement e = eff.toElement();
490 if (e.attribute("type") == "blur") {
491 QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
492 blur->setBlurRadius(e.attribute("blurradius").toInt());
493 gitem->setGraphicsEffect(blur);
494 } else if (e.attribute("type") == "shadow") {
495 QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
496 shadow->setBlurRadius(e.attribute("blurradius").toInt());
497 shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
498 gitem->setGraphicsEffect(shadow);
504 if (items.item(i).nodeName() == "background") {
505 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
506 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
507 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
508 QList<QGraphicsItem *> items = m_scene->items();
509 for (int i = 0; i < items.size(); i++) {
510 if (items.at(i)->zValue() == -1100) {
511 ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
515 } else if (items.item(i).nodeName() == "startviewport" && startv) {
516 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
517 QRectF r = stringToRect(rect);
518 startv->setRect(0, 0, r.width(), r.height());
519 startv->setPos(r.topLeft());
520 } else if (items.item(i).nodeName() == "endviewport" && endv) {
521 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
522 QRectF r = stringToRect(rect);
523 endv->setRect(0, 0, r.width(), r.height());
524 endv->setPos(r.topLeft());
531 QString TitleDocument::colorToString(const QColor& c)
533 QString ret = "%1,%2,%3,%4";
534 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
538 QString TitleDocument::rectFToString(const QRectF& c)
540 QString ret = "%1,%2,%3,%4";
541 ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
545 QRectF TitleDocument::stringToRect(const QString & s)
548 QStringList l = s.split(',');
551 return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
554 QColor TitleDocument::stringToColor(const QString & s)
556 QStringList l = s.split(',');
559 return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
562 QTransform TitleDocument::stringToTransform(const QString& s)
564 QStringList l = s.split(',');
568 l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
569 l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
570 l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
574 QList<QVariant> TitleDocument::stringToList(const QString & s)
576 QStringList l = s.split(',');
578 return QList<QVariant>();
579 return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
582 int TitleDocument::frameWidth() const
587 int TitleDocument::frameHeight() const