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 QString filename=titlePath+QString( QCryptographicHash::hash(base64.toAscii(), QCryptographicHash::Md5).toHex().append(".titlepart"));
95 KStandardDirs::makeDir(titlePath);
97 if (f.open(QIODevice::WriteOnly)){
98 f.write(QByteArray::fromBase64(base64.toAscii()) ) ;
100 content.setAttribute("url",filename);
101 content.removeAttribute("base64");
111 QDomDocument TitleDocument::xml(QGraphicsRectItem* startv, QGraphicsRectItem* endv, bool embed)
115 QDomElement main = doc.createElement("kdenlivetitle");
116 main.setAttribute("width", m_width);
117 main.setAttribute("height", m_height);
118 doc.appendChild(main);
120 foreach(QGraphicsItem* item, m_scene->items()) {
121 QDomElement e = doc.createElement("item");
122 QDomElement content = doc.createElement("content");
124 QGraphicsTextItem *t;
126 switch (item->type()) {
128 e.setAttribute("type", "QGraphicsPixmapItem");
129 content.setAttribute("url", item->data(Qt::UserRole).toString());
130 base64ToUrl (item, content, embed );
133 e.setAttribute("type", "QGraphicsSvgItem");
134 content.setAttribute("url", item->data(Qt::UserRole).toString());
135 base64ToUrl (item, content, embed );
138 e.setAttribute("type", "QGraphicsRectItem");
139 content.setAttribute("rect", rectFToString(((QGraphicsRectItem*)item)->rect().normalized()));
140 content.setAttribute("pencolor", colorToString(((QGraphicsRectItem*)item)->pen().color()));
141 content.setAttribute("penwidth", ((QGraphicsRectItem*)item)->pen().width());
142 content.setAttribute("brushcolor", colorToString(((QGraphicsRectItem*)item)->brush().color()));
145 e.setAttribute("type", "QGraphicsTextItem");
146 t = static_cast<QGraphicsTextItem *>(item);
147 // Don't save empty text nodes
148 if (t->toPlainText().simplified().isEmpty()) continue;
149 //content.appendChild(doc.createTextNode(((QGraphicsTextItem*)item)->toHtml()));
150 content.appendChild(doc.createTextNode(t->toPlainText()));
152 content.setAttribute("font", font.family());
153 content.setAttribute("font-weight", font.weight());
154 content.setAttribute("font-pixel-size", font.pixelSize());
155 content.setAttribute("font-italic", font.italic());
156 content.setAttribute("font-underline", font.underline());
158 QTextCursor cursor(t->document());
159 cursor.select(QTextCursor::Document);
160 QColor fontcolor = cursor.charFormat().foreground().color();
161 content.setAttribute("font-color", colorToString(fontcolor));
162 if (!t->data(101).isNull()) content.setAttribute("font-outline", t->data(101).toDouble());
163 if (!t->data(102).isNull()) content.setAttribute("font-outline-color", colorToString(QColor(t->data(102).toString())));
165 if (!t->data(100).isNull()) {
166 QStringList effectParams = t->data(100).toStringList();
167 QString effectName = effectParams.takeFirst();
168 content.setAttribute(effectName, effectParams.join(";"));
171 // Only save when necessary.
172 if (t->data(OriginXLeft).toInt() == AxisInverted) {
173 content.setAttribute("kdenlive-axis-x-inverted", t->data(OriginXLeft).toInt());
175 if (t->data(OriginYTop).toInt() == AxisInverted) {
176 content.setAttribute("kdenlive-axis-y-inverted", t->data(OriginYTop).toInt());
178 if (t->textWidth() != -1) {
179 content.setAttribute("alignment", t->textCursor().blockFormat().alignment());
187 QDomElement pos = doc.createElement("position");
188 pos.setAttribute("x", item->pos().x());
189 pos.setAttribute("y", item->pos().y());
190 QTransform transform = item->transform();
191 QDomElement tr = doc.createElement("transform");
192 if (!item->data(ZOOMFACTOR).isNull()) {
193 tr.setAttribute("zoom", QString::number(item->data(ZOOMFACTOR).toInt()));
195 if (!item->data(ROTATEFACTOR).isNull()) {
196 QList<QVariant> rotlist = item->data(ROTATEFACTOR).toList();
197 tr.setAttribute("rotation", QString("%1,%2,%3").arg(rotlist[0].toDouble()).arg(rotlist[1].toDouble()).arg(rotlist[2].toDouble()));
199 tr.appendChild(doc.createTextNode(
200 QString("%1,%2,%3,%4,%5,%6,%7,%8,%9").arg(
201 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())
204 e.setAttribute("z-index", item->zValue());
207 #if QT_VERSION >= 0x040600
209 QGraphicsEffect *eff = item->graphicsEffect();
211 QGraphicsBlurEffect *blur = static_cast <QGraphicsBlurEffect *>(eff);
212 QDomElement effect = doc.createElement("effect");
214 effect.setAttribute("type", "blur");
215 effect.setAttribute("blurradius", blur->blurRadius());
217 QGraphicsDropShadowEffect *shadow = static_cast <QGraphicsDropShadowEffect *>(eff);
219 effect.setAttribute("type", "shadow");
220 effect.setAttribute("blurradius", shadow->blurRadius());
221 effect.setAttribute("xoffset", shadow->xOffset());
222 effect.setAttribute("yoffset", shadow->yOffset());
225 e.appendChild(effect);
230 e.appendChild(content);
231 if (item->zValue() > -1000) main.appendChild(e);
233 if (startv && endv) {
234 QDomElement endp = doc.createElement("endviewport");
235 QDomElement startp = doc.createElement("startviewport");
236 QRectF r(endv->pos().x(), endv->pos().y(), endv->rect().width(), endv->rect().height());
237 endp.setAttribute("rect", rectFToString(r));
238 QRectF r2(startv->pos().x(), startv->pos().y(), startv->rect().width(), startv->rect().height());
239 startp.setAttribute("rect", rectFToString(r2));
241 main.appendChild(startp);
242 main.appendChild(endp);
244 QDomElement backgr = doc.createElement("background");
245 QColor color = getBackgroundColor();
246 backgr.setAttribute("color", colorToString(color));
247 main.appendChild(backgr);
252 /** \brief Get the background color (incl. alpha) from the document, if possibly
253 * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */
254 QColor TitleDocument::getBackgroundColor()
256 QColor color(0, 0, 0, 0);
258 QList<QGraphicsItem *> items = m_scene->items();
259 for (int i = 0; i < items.size(); i++) {
260 if (items.at(i)->zValue() == -1100) {
261 color = ((QGraphicsRectItem *)items.at(i))->brush().color();
270 bool TitleDocument::saveDocument(const KUrl& url, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int out, bool embed)
275 QDomDocument doc = xml(startv, endv, embed);
276 doc.documentElement().setAttribute("out", out);
277 KTemporaryFile tmpfile;
278 if (!tmpfile.open()) {
279 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
282 QFile xmlf(tmpfile.fileName());
283 xmlf.open(QIODevice::WriteOnly);
284 xmlf.write(doc.toString().toUtf8());
285 if (xmlf.error() != QFile::NoError) {
290 return KIO::NetAccess::upload(tmpfile.fileName(), url, 0);
293 int TitleDocument::loadFromXml(QDomDocument doc, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int *out, const QString& projectpath)
295 m_projectPath=projectpath;
296 QDomNodeList titles = doc.elementsByTagName("kdenlivetitle");
297 //TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
298 if (doc.documentElement().hasAttribute("width") && doc.documentElement().hasAttribute("height")) {
299 int doc_width = doc.documentElement().attribute("width").toInt();
300 int doc_height = doc.documentElement().attribute("height").toInt();
301 if (doc_width != m_width || doc_height != m_height) {
302 KMessageBox::information(kapp->activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
303 //TODO: convert using QTransform
305 m_height = doc_height;
308 // Document has no size info, it is likely an old version title, so ignore viewport data
309 QDomNodeList viewportlist = doc.documentElement().elementsByTagName("startviewport");
310 if (!viewportlist.isEmpty()) {
311 doc.documentElement().removeChild(viewportlist.at(0));
313 viewportlist = doc.documentElement().elementsByTagName("endviewport");
314 if (!viewportlist.isEmpty()) {
315 doc.documentElement().removeChild(viewportlist.at(0));
318 //TODO: get default title duration instead of hardcoded one
319 if (doc.documentElement().hasAttribute("out"))
320 *out = doc.documentElement().attribute("out").toInt();
327 QDomNodeList items = titles.item(0).childNodes();
328 for (int i = 0; i < items.count(); i++) {
329 QGraphicsItem *gitem = NULL;
330 kDebug() << items.item(i).attributes().namedItem("type").nodeValue();
331 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
332 if (zValue > -1000) {
333 if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
334 QDomNamedNodeMap txtProperties = items.item(i).namedItem("content").attributes();
335 QFont font(txtProperties.namedItem("font").nodeValue());
337 QDomNode node = txtProperties.namedItem("font-bold");
338 if (!node.isNull()) {
339 // Old: Bold/Not bold.
340 font.setBold(node.nodeValue().toInt());
342 // New: Font weight (QFont::)
343 font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
345 //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
346 font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
347 font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());
348 // Older Kdenlive version did not store pixel size but point size
349 if (txtProperties.namedItem("font-pixel-size").isNull()) {
350 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"));
352 f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
353 font.setPixelSize(QFontInfo(f2).pixelSize());
355 font.setPixelSize(txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
356 QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
357 QGraphicsTextItem *txt = m_scene->addText(items.item(i).namedItem("content").firstChild().nodeValue(), font);
358 QTextCursor cursor(txt->document());
359 cursor.select(QTextCursor::Document);
360 QTextCharFormat format;
361 if (txtProperties.namedItem("font-outline").nodeValue().toDouble() > 0.0) {
362 txt->setData(101, txtProperties.namedItem("font-outline").nodeValue().toDouble());
363 txt->setData(102, stringToColor(txtProperties.namedItem("font-outline-color").nodeValue()));
364 format.setTextOutline(
365 QPen(QColor(stringToColor(txtProperties.namedItem("font-outline-color").nodeValue())),
366 txtProperties.namedItem("font-outline").nodeValue().toDouble(),
367 Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
371 format.setForeground(QBrush(col));
372 cursor.mergeCharFormat(format);
373 txt->setTextInteractionFlags(Qt::NoTextInteraction);
374 if (txtProperties.namedItem("alignment").isNull() == false) {
375 txt->setTextWidth(txt->boundingRect().width());
376 QTextCursor cur = txt->textCursor();
377 QTextBlockFormat format = cur.blockFormat();
378 format.setAlignment((Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
379 cur.select(QTextCursor::Document);
380 cur.setBlockFormat(format);
381 txt->setTextCursor(cur);
382 cur.clearSelection();
383 txt->setTextCursor(cur);
386 if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
387 txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
389 if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
390 txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
394 if (!txtProperties.namedItem("typewriter").isNull()) {
395 QStringList effData = QStringList() << "typewriter" << txtProperties.namedItem("typewriter").nodeValue();
396 txt->setData(100, effData);
400 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsRectItem") {
401 QString rect = items.item(i).namedItem("content").attributes().namedItem("rect").nodeValue();
402 QString br_str = items.item(i).namedItem("content").attributes().namedItem("brushcolor").nodeValue();
403 QString pen_str = items.item(i).namedItem("content").attributes().namedItem("pencolor").nodeValue();
404 double penwidth = items.item(i).namedItem("content").attributes().namedItem("penwidth").nodeValue().toDouble();
405 QGraphicsRectItem *rec = m_scene->addRect(stringToRect(rect), QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin), QBrush(stringToColor(br_str)));
407 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
408 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
409 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
411 if (base64.isEmpty()){
414 pix.loadFromData(QByteArray::fromBase64(base64.toAscii()));
416 QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
417 rec->setData(Qt::UserRole, url);
418 if (!base64.isEmpty()){
419 rec->setData(Qt::UserRole+1, base64);
422 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
423 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
424 QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
425 QGraphicsSvgItem *rec = NULL;
426 if (base64.isEmpty()){
427 rec = new QGraphicsSvgItem(url);
429 rec = new QGraphicsSvgItem();
430 QSvgRenderer *renderer= new QSvgRenderer(QByteArray::fromBase64(base64.toAscii()), rec );
431 rec->setSharedRenderer(renderer);
432 //QString elem=rec->elementId();
433 //QRectF bounds = renderer->boundsOnElement(elem);
436 m_scene->addItem(rec);
437 rec->setData(Qt::UserRole, url);
438 if (!base64.isEmpty()){
439 rec->setData(Qt::UserRole+1, base64);
447 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
448 items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
450 QDomElement trans = items.item(i).namedItem("position").firstChild().toElement();
451 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue()));
452 QString rotate = trans.attribute("rotation");
453 if (!rotate.isEmpty()) gitem->setData(ROTATEFACTOR, stringToList(rotate));
454 QString zoom = trans.attribute("zoom");
455 if (!zoom.isEmpty()) gitem->setData(ZOOMFACTOR, zoom.toInt());
456 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
457 if (zValue > maxZValue) maxZValue = zValue;
458 gitem->setZValue(zValue);
459 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
461 #if QT_VERSION >= 0x040600
463 QDomNode eff = items.item(i).namedItem("effect");
465 QDomElement e = eff.toElement();
466 if (e.attribute("type") == "blur") {
467 QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
468 blur->setBlurRadius(e.attribute("blurradius").toInt());
469 gitem->setGraphicsEffect(blur);
470 } else if (e.attribute("type") == "shadow") {
471 QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
472 shadow->setBlurRadius(e.attribute("blurradius").toInt());
473 shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
474 gitem->setGraphicsEffect(shadow);
480 if (items.item(i).nodeName() == "background") {
481 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
482 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
483 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
484 QList<QGraphicsItem *> items = m_scene->items();
485 for (int i = 0; i < items.size(); i++) {
486 if (items.at(i)->zValue() == -1100) {
487 ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
491 } else if (items.item(i).nodeName() == "startviewport" && startv) {
492 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
493 QRectF r = stringToRect(rect);
494 startv->setRect(0, 0, r.width(), r.height());
495 startv->setPos(r.topLeft());
496 } else if (items.item(i).nodeName() == "endviewport" && endv) {
497 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
498 QRectF r = stringToRect(rect);
499 endv->setRect(0, 0, r.width(), r.height());
500 endv->setPos(r.topLeft());
507 QString TitleDocument::colorToString(const QColor& c)
509 QString ret = "%1,%2,%3,%4";
510 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
514 QString TitleDocument::rectFToString(const QRectF& c)
516 QString ret = "%1,%2,%3,%4";
517 ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
521 QRectF TitleDocument::stringToRect(const QString & s)
524 QStringList l = s.split(',');
527 return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
530 QColor TitleDocument::stringToColor(const QString & s)
532 QStringList l = s.split(',');
535 return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
538 QTransform TitleDocument::stringToTransform(const QString& s)
540 QStringList l = s.split(',');
544 l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
545 l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
546 l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
550 QList<QVariant> TitleDocument::stringToList(const QString & s)
552 QStringList l = s.split(',');
554 return QList<QVariant>();
555 return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
558 int TitleDocument::frameWidth() const
563 int TitleDocument::frameHeight() const