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))
52 while (!file.atEnd()){
53 ret.append(file.readLine());
59 TitleDocument::TitleDocument()
64 void TitleDocument::setScene(QGraphicsScene* _scene, int width, int height)
71 int TitleDocument::base64ToUrl(QGraphicsItem* item, QDomElement& content, bool embed)
75 if (!item->data(Qt::UserRole+1).toString().isEmpty())
77 content.setAttribute("base64",item->data(Qt::UserRole+1).toString());
78 } 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())
90 titlePath=m_projectPath;
92 titlePath="/tmp/titles";
94 qDebug() << titlePath;
95 QString filename=titlePath+QString( QCryptographicHash::hash(base64.toAscii(), QCryptographicHash::Md5).toHex().append(".titlepart"));
96 KStandardDirs::makeDir(titlePath);
98 if (f.open(QIODevice::WriteOnly)){
99 f.write(QByteArray::fromBase64(base64.toAscii()) ) ;
101 content.setAttribute("url",filename);
102 content.removeAttribute("base64");
112 QDomDocument TitleDocument::xml(QGraphicsRectItem* startv, QGraphicsRectItem* endv, bool embed)
116 QDomElement main = doc.createElement("kdenlivetitle");
117 main.setAttribute("width", m_width);
118 main.setAttribute("height", m_height);
119 doc.appendChild(main);
121 foreach(QGraphicsItem* item, m_scene->items()) {
122 QDomElement e = doc.createElement("item");
123 QDomElement content = doc.createElement("content");
125 QGraphicsTextItem *t;
127 switch (item->type()) {
129 e.setAttribute("type", "QGraphicsPixmapItem");
130 content.setAttribute("url", item->data(Qt::UserRole).toString());
131 base64ToUrl (item, content, embed );
134 e.setAttribute("type", "QGraphicsSvgItem");
135 content.setAttribute("url", item->data(Qt::UserRole).toString());
136 base64ToUrl (item, content, embed );
139 e.setAttribute("type", "QGraphicsRectItem");
140 content.setAttribute("rect", rectFToString(((QGraphicsRectItem*)item)->rect().normalized()));
141 content.setAttribute("pencolor", colorToString(((QGraphicsRectItem*)item)->pen().color()));
142 content.setAttribute("penwidth", ((QGraphicsRectItem*)item)->pen().width());
143 content.setAttribute("brushcolor", colorToString(((QGraphicsRectItem*)item)->brush().color()));
146 e.setAttribute("type", "QGraphicsTextItem");
147 t = static_cast<QGraphicsTextItem *>(item);
148 // Don't save empty text nodes
149 if (t->toPlainText().simplified().isEmpty()) continue;
150 //content.appendChild(doc.createTextNode(((QGraphicsTextItem*)item)->toHtml()));
151 content.appendChild(doc.createTextNode(t->toPlainText()));
153 content.setAttribute("font", font.family());
154 content.setAttribute("font-weight", font.weight());
155 content.setAttribute("font-pixel-size", font.pixelSize());
156 content.setAttribute("font-italic", font.italic());
157 content.setAttribute("font-underline", font.underline());
159 QTextCursor cursor(t->document());
160 cursor.select(QTextCursor::Document);
161 QColor fontcolor = cursor.charFormat().foreground().color();
162 content.setAttribute("font-color", colorToString(fontcolor));
163 if (!t->data(101).isNull()) content.setAttribute("font-outline", t->data(101).toDouble());
164 if (!t->data(102).isNull()) content.setAttribute("font-outline-color", colorToString(QColor(t->data(102).toString())));
166 if (!t->data(100).isNull()) {
167 QStringList effectParams = t->data(100).toStringList();
168 QString effectName = effectParams.takeFirst();
169 content.setAttribute(effectName, effectParams.join(";"));
172 // Only save when necessary.
173 if (t->data(OriginXLeft).toInt() == AxisInverted) {
174 content.setAttribute("kdenlive-axis-x-inverted", t->data(OriginXLeft).toInt());
176 if (t->data(OriginYTop).toInt() == AxisInverted) {
177 content.setAttribute("kdenlive-axis-y-inverted", t->data(OriginYTop).toInt());
179 if (t->textWidth() != -1) {
180 content.setAttribute("alignment", t->textCursor().blockFormat().alignment());
188 QDomElement pos = doc.createElement("position");
189 pos.setAttribute("x", item->pos().x());
190 pos.setAttribute("y", item->pos().y());
191 QTransform transform = item->transform();
192 QDomElement tr = doc.createElement("transform");
193 if (!item->data(ZOOMFACTOR).isNull()) {
194 tr.setAttribute("zoom", QString::number(item->data(ZOOMFACTOR).toInt()));
196 if (!item->data(ROTATEFACTOR).isNull()) {
197 QList<QVariant> rotlist = item->data(ROTATEFACTOR).toList();
198 tr.setAttribute("rotation", QString("%1,%2,%3").arg(rotlist[0].toDouble()).arg(rotlist[1].toDouble()).arg(rotlist[2].toDouble()));
200 tr.appendChild(doc.createTextNode(
201 QString("%1,%2,%3,%4,%5,%6,%7,%8,%9").arg(
202 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())
205 e.setAttribute("z-index", item->zValue());
208 #if QT_VERSION >= 0x040600
210 QGraphicsEffect *eff = item->graphicsEffect();
212 QGraphicsBlurEffect *blur = static_cast <QGraphicsBlurEffect *>(eff);
213 QDomElement effect = doc.createElement("effect");
215 effect.setAttribute("type", "blur");
216 effect.setAttribute("blurradius", blur->blurRadius());
218 QGraphicsDropShadowEffect *shadow = static_cast <QGraphicsDropShadowEffect *>(eff);
220 effect.setAttribute("type", "shadow");
221 effect.setAttribute("blurradius", shadow->blurRadius());
222 effect.setAttribute("xoffset", shadow->xOffset());
223 effect.setAttribute("yoffset", shadow->yOffset());
226 e.appendChild(effect);
231 e.appendChild(content);
232 if (item->zValue() > -1000) main.appendChild(e);
234 if (startv && endv) {
235 QDomElement endp = doc.createElement("endviewport");
236 QDomElement startp = doc.createElement("startviewport");
237 QRectF r(endv->pos().x(), endv->pos().y(), endv->rect().width(), endv->rect().height());
238 endp.setAttribute("rect", rectFToString(r));
239 QRectF r2(startv->pos().x(), startv->pos().y(), startv->rect().width(), startv->rect().height());
240 startp.setAttribute("rect", rectFToString(r2));
242 main.appendChild(startp);
243 main.appendChild(endp);
245 QDomElement backgr = doc.createElement("background");
246 QColor color = getBackgroundColor();
247 backgr.setAttribute("color", colorToString(color));
248 main.appendChild(backgr);
253 /** \brief Get the background color (incl. alpha) from the document, if possibly
254 * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */
255 QColor TitleDocument::getBackgroundColor()
257 QColor color(0, 0, 0, 0);
259 QList<QGraphicsItem *> items = m_scene->items();
260 for (int i = 0; i < items.size(); i++) {
261 if (items.at(i)->zValue() == -1100) {
262 color = ((QGraphicsRectItem *)items.at(i))->brush().color();
271 bool TitleDocument::saveDocument(const KUrl& url, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int out, bool embed)
276 QDomDocument doc = xml(startv, endv, embed);
277 doc.documentElement().setAttribute("out", out);
278 KTemporaryFile tmpfile;
279 if (!tmpfile.open()) {
280 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
283 QFile xmlf(tmpfile.fileName());
284 xmlf.open(QIODevice::WriteOnly);
285 xmlf.write(doc.toString().toUtf8());
286 if (xmlf.error() != QFile::NoError) {
291 return KIO::NetAccess::upload(tmpfile.fileName(), url, 0);
294 int TitleDocument::loadFromXml(QDomDocument doc, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int *out, const QString& projectpath)
296 m_projectPath=projectpath;
297 QDomNodeList titles = doc.elementsByTagName("kdenlivetitle");
298 //TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
299 if (doc.documentElement().hasAttribute("width") && doc.documentElement().hasAttribute("height")) {
300 int doc_width = doc.documentElement().attribute("width").toInt();
301 int doc_height = doc.documentElement().attribute("height").toInt();
302 if (doc_width != m_width || doc_height != m_height) {
303 KMessageBox::information(kapp->activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
304 //TODO: convert using QTransform
306 m_height = doc_height;
309 // Document has no size info, it is likely an old version title, so ignore viewport data
310 QDomNodeList viewportlist = doc.documentElement().elementsByTagName("startviewport");
311 if (!viewportlist.isEmpty()) {
312 doc.documentElement().removeChild(viewportlist.at(0));
314 viewportlist = doc.documentElement().elementsByTagName("endviewport");
315 if (!viewportlist.isEmpty()) {
316 doc.documentElement().removeChild(viewportlist.at(0));
319 //TODO: get default title duration instead of hardcoded one
320 if (doc.documentElement().hasAttribute("out"))
321 *out = doc.documentElement().attribute("out").toInt();
328 QDomNodeList items = titles.item(0).childNodes();
329 for (int i = 0; i < items.count(); i++) {
330 QGraphicsItem *gitem = NULL;
331 kDebug() << items.item(i).attributes().namedItem("type").nodeValue();
332 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
333 if (zValue > -1000) {
334 if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
335 QDomNamedNodeMap txtProperties = items.item(i).namedItem("content").attributes();
336 QFont font(txtProperties.namedItem("font").nodeValue());
338 QDomNode node = txtProperties.namedItem("font-bold");
339 if (!node.isNull()) {
340 // Old: Bold/Not bold.
341 font.setBold(node.nodeValue().toInt());
343 // New: Font weight (QFont::)
344 font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
346 //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
347 font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
348 font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());
349 // Older Kdenlive version did not store pixel size but point size
350 if (txtProperties.namedItem("font-pixel-size").isNull()) {
351 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"));
353 f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
354 font.setPixelSize(QFontInfo(f2).pixelSize());
356 font.setPixelSize(txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
357 QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
358 QGraphicsTextItem *txt = m_scene->addText(items.item(i).namedItem("content").firstChild().nodeValue(), font);
359 QTextCursor cursor(txt->document());
360 cursor.select(QTextCursor::Document);
361 QTextCharFormat format;
362 if (txtProperties.namedItem("font-outline").nodeValue().toDouble() > 0.0) {
363 txt->setData(101, txtProperties.namedItem("font-outline").nodeValue().toDouble());
364 txt->setData(102, stringToColor(txtProperties.namedItem("font-outline-color").nodeValue()));
365 format.setTextOutline(
366 QPen(QColor(stringToColor(txtProperties.namedItem("font-outline-color").nodeValue())),
367 txtProperties.namedItem("font-outline").nodeValue().toDouble(),
368 Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
372 format.setForeground(QBrush(col));
373 cursor.mergeCharFormat(format);
374 txt->setTextInteractionFlags(Qt::NoTextInteraction);
375 if (txtProperties.namedItem("alignment").isNull() == false) {
376 txt->setTextWidth(txt->boundingRect().width());
377 QTextCursor cur = txt->textCursor();
378 QTextBlockFormat format = cur.blockFormat();
379 format.setAlignment((Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
380 cur.select(QTextCursor::Document);
381 cur.setBlockFormat(format);
382 txt->setTextCursor(cur);
383 cur.clearSelection();
384 txt->setTextCursor(cur);
387 if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
388 txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
390 if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
391 txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
395 if (!txtProperties.namedItem("typewriter").isNull()) {
396 QStringList effData = QStringList() << "typewriter" << txtProperties.namedItem("typewriter").nodeValue();
397 txt->setData(100, effData);
401 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsRectItem") {
402 QString rect = items.item(i).namedItem("content").attributes().namedItem("rect").nodeValue();
403 QString br_str = items.item(i).namedItem("content").attributes().namedItem("brushcolor").nodeValue();
404 QString pen_str = items.item(i).namedItem("content").attributes().namedItem("pencolor").nodeValue();
405 double penwidth = items.item(i).namedItem("content").attributes().namedItem("penwidth").nodeValue().toDouble();
406 QGraphicsRectItem *rec = m_scene->addRect(stringToRect(rect), QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin), QBrush(stringToColor(br_str)));
408 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
409 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
410 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
412 if (base64.isEmpty()){
415 pix.loadFromData(QByteArray::fromBase64(base64.toAscii()));
417 QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
418 rec->setData(Qt::UserRole, url);
419 if (!base64.isEmpty()){
420 rec->setData(Qt::UserRole+1, base64);
423 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
424 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
425 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
426 QGraphicsSvgItem *rec = NULL;
427 if (base64.isEmpty()){
428 rec = new QGraphicsSvgItem(url);
430 rec = new QGraphicsSvgItem();
431 QSvgRenderer *renderer= new QSvgRenderer(QByteArray::fromBase64(base64.toAscii()), rec );
432 rec->setSharedRenderer(renderer);
433 //QString elem=rec->elementId();
434 //QRectF bounds = renderer->boundsOnElement(elem);
437 m_scene->addItem(rec);
438 rec->setData(Qt::UserRole, url);
439 if (!base64.isEmpty()){
440 rec->setData(Qt::UserRole+1, base64);
448 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
449 items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
451 QDomElement trans = items.item(i).namedItem("position").firstChild().toElement();
452 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue()));
453 QString rotate = trans.attribute("rotation");
454 if (!rotate.isEmpty()) gitem->setData(ROTATEFACTOR, stringToList(rotate));
455 QString zoom = trans.attribute("zoom");
456 if (!zoom.isEmpty()) gitem->setData(ZOOMFACTOR, zoom.toInt());
457 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
458 if (zValue > maxZValue) maxZValue = zValue;
459 gitem->setZValue(zValue);
460 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
462 #if QT_VERSION >= 0x040600
464 QDomNode eff = items.item(i).namedItem("effect");
466 QDomElement e = eff.toElement();
467 if (e.attribute("type") == "blur") {
468 QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
469 blur->setBlurRadius(e.attribute("blurradius").toInt());
470 gitem->setGraphicsEffect(blur);
471 } else if (e.attribute("type") == "shadow") {
472 QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
473 shadow->setBlurRadius(e.attribute("blurradius").toInt());
474 shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
475 gitem->setGraphicsEffect(shadow);
481 if (items.item(i).nodeName() == "background") {
482 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
483 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
484 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
485 QList<QGraphicsItem *> items = m_scene->items();
486 for (int i = 0; i < items.size(); i++) {
487 if (items.at(i)->zValue() == -1100) {
488 ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
492 } else if (items.item(i).nodeName() == "startviewport" && startv) {
493 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
494 QRectF r = stringToRect(rect);
495 startv->setRect(0, 0, r.width(), r.height());
496 startv->setPos(r.topLeft());
497 } else if (items.item(i).nodeName() == "endviewport" && endv) {
498 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
499 QRectF r = stringToRect(rect);
500 endv->setRect(0, 0, r.width(), r.height());
501 endv->setPos(r.topLeft());
508 QString TitleDocument::colorToString(const QColor& c)
510 QString ret = "%1,%2,%3,%4";
511 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
515 QString TitleDocument::rectFToString(const QRectF& c)
517 QString ret = "%1,%2,%3,%4";
518 ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
522 QRectF TitleDocument::stringToRect(const QString & s)
525 QStringList l = s.split(',');
528 return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
531 QColor TitleDocument::stringToColor(const QString & s)
533 QStringList l = s.split(',');
536 return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
539 QTransform TitleDocument::stringToTransform(const QString& s)
541 QStringList l = s.split(',');
545 l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
546 l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
547 l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
551 QList<QVariant> TitleDocument::stringToList(const QString & s)
553 QStringList l = s.split(',');
555 return QList<QVariant>();
556 return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
559 int TitleDocument::frameWidth() const
564 int TitleDocument::frameHeight() const