]> git.sesse.net Git - kdenlive/blob - src/titledocument.cpp
Fix older titles size changed on loading:
[kdenlive] / src / titledocument.cpp
1 /***************************************************************************
2                           titledocument.h  -  description
3                              -------------------
4     begin                : Feb 28 2008
5     copyright            : (C) 2008 by Marco Gittler
6     email                : g.marco@freenet.de
7  ***************************************************************************/
8
9 /***************************************************************************
10  *                                                                         *
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.                                   *
15  *                                                                         *
16  ***************************************************************************/
17
18 #include "titledocument.h"
19
20 #include <KDebug>
21 #include <KTemporaryFile>
22 #include <kio/netaccess.h>
23 #include <KApplication>
24 #include <KLocale>
25 #include <KMessageBox>
26
27 #include <QGraphicsScene>
28 #include <QDomElement>
29 #include <QGraphicsItem>
30 #include <QGraphicsRectItem>
31 #include <QGraphicsTextItem>
32 #include <QGraphicsSvgItem>
33 #include <QFontInfo>
34 #include <QFile>
35 #include <QTextCursor>
36
37 #if QT_VERSION >= 0x040600
38 #include <QGraphicsEffect>
39 #include <QGraphicsBlurEffect>
40 #include <QGraphicsDropShadowEffect>
41 #endif
42
43 TitleDocument::TitleDocument()
44 {
45     m_scene = NULL;
46 }
47
48 void TitleDocument::setScene(QGraphicsScene* _scene, int width, int height)
49 {
50     m_scene = _scene;
51     m_width = width;
52     m_height = height;
53 }
54
55 QDomDocument TitleDocument::xml(QGraphicsRectItem* startv, QGraphicsRectItem* endv)
56 {
57     QDomDocument doc;
58
59     QDomElement main = doc.createElement("kdenlivetitle");
60     main.setAttribute("width", m_width);
61     main.setAttribute("height", m_height);
62     doc.appendChild(main);
63
64     foreach(QGraphicsItem* item, m_scene->items()) {
65         QDomElement e = doc.createElement("item");
66         QDomElement content = doc.createElement("content");
67         QFont font;
68         QGraphicsTextItem *t;
69
70         switch (item->type()) {
71         case 7:
72             e.setAttribute("type", "QGraphicsPixmapItem");
73             content.setAttribute("url", item->data(Qt::UserRole).toString());
74             break;
75         case 13:
76             e.setAttribute("type", "QGraphicsSvgItem");
77             content.setAttribute("url", item->data(Qt::UserRole).toString());
78             break;
79         case 3:
80             e.setAttribute("type", "QGraphicsRectItem");
81             content.setAttribute("rect", rectFToString(((QGraphicsRectItem*)item)->rect().normalized()));
82             content.setAttribute("pencolor", colorToString(((QGraphicsRectItem*)item)->pen().color()));
83             content.setAttribute("penwidth", ((QGraphicsRectItem*)item)->pen().width());
84             content.setAttribute("brushcolor", colorToString(((QGraphicsRectItem*)item)->brush().color()));
85             break;
86         case 8:
87             e.setAttribute("type", "QGraphicsTextItem");
88             t = static_cast<QGraphicsTextItem *>(item);
89             // Don't save empty text nodes
90             if (t->toPlainText().simplified().isEmpty()) continue;
91             //content.appendChild(doc.createTextNode(((QGraphicsTextItem*)item)->toHtml()));
92             content.appendChild(doc.createTextNode(t->toPlainText()));
93             font = t->font();
94             content.setAttribute("font", font.family());
95             content.setAttribute("font-weight", font.weight());
96             content.setAttribute("font-pixel-size", font.pixelSize());
97             content.setAttribute("font-italic", font.italic());
98             content.setAttribute("font-underline", font.underline());
99             {
100                 QTextCursor cursor(t->document());
101                 cursor.select(QTextCursor::Document);
102                 QColor fontcolor = cursor.charFormat().foreground().color();
103                 content.setAttribute("font-color", colorToString(fontcolor));
104                 if (!t->data(101).isNull()) content.setAttribute("font-outline", t->data(101).toDouble());
105                 if (!t->data(102).isNull()) content.setAttribute("font-outline-color", colorToString(QColor(t->data(102).toString())));
106             }
107             if (!t->data(100).isNull()) {
108                 QStringList effectParams = t->data(100).toStringList();
109                 QString effectName = effectParams.takeFirst();
110                 content.setAttribute(effectName, effectParams.join(";"));
111             }
112
113             // Only save when necessary.
114             if (t->data(OriginXLeft).toInt() == AxisInverted) {
115                 content.setAttribute("kdenlive-axis-x-inverted", t->data(OriginXLeft).toInt());
116             }
117             if (t->data(OriginYTop).toInt() == AxisInverted) {
118                 content.setAttribute("kdenlive-axis-y-inverted", t->data(OriginYTop).toInt());
119             }
120             if (t->textWidth() != -1) {
121                 content.setAttribute("alignment", t->textCursor().blockFormat().alignment());
122             }
123             break;
124         default:
125             continue;
126         }
127
128         // position
129         QDomElement pos = doc.createElement("position");
130         pos.setAttribute("x", item->pos().x());
131         pos.setAttribute("y", item->pos().y());
132         QTransform transform = item->transform();
133         QDomElement tr = doc.createElement("transform");
134         if (!item->data(ZOOMFACTOR).isNull()) {
135             tr.setAttribute("zoom", QString::number(item->data(ZOOMFACTOR).toInt()));
136         }
137         if (!item->data(ROTATEFACTOR).isNull()) {
138             QList<QVariant> rotlist = item->data(ROTATEFACTOR).toList();
139             tr.setAttribute("rotation", QString("%1,%2,%3").arg(rotlist[0].toDouble()).arg(rotlist[1].toDouble()).arg(rotlist[2].toDouble()));
140         }
141         tr.appendChild(doc.createTextNode(
142                            QString("%1,%2,%3,%4,%5,%6,%7,%8,%9").arg(
143                                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())
144                        )
145                       );
146         e.setAttribute("z-index", item->zValue());
147         pos.appendChild(tr);
148
149 #if QT_VERSION >= 0x040600
150         // effects
151         QGraphicsEffect *eff = item->graphicsEffect();
152         if (eff) {
153             QGraphicsBlurEffect *blur = static_cast <QGraphicsBlurEffect *>(eff);
154             QDomElement effect = doc.createElement("effect");
155             if (blur) {
156                 effect.setAttribute("type", "blur");
157                 effect.setAttribute("blurradius", blur->blurRadius());
158             } else {
159                 QGraphicsDropShadowEffect *shadow = static_cast <QGraphicsDropShadowEffect *>(eff);
160                 if (shadow) {
161                     effect.setAttribute("type", "shadow");
162                     effect.setAttribute("blurradius", shadow->blurRadius());
163                     effect.setAttribute("xoffset", shadow->xOffset());
164                     effect.setAttribute("yoffset", shadow->yOffset());
165                 }
166             }
167             e.appendChild(effect);
168         }
169 #endif
170
171         e.appendChild(pos);
172         e.appendChild(content);
173         if (item->zValue() > -1000) main.appendChild(e);
174     }
175     if (startv && endv) {
176         QDomElement endp = doc.createElement("endviewport");
177         QDomElement startp = doc.createElement("startviewport");
178         QRectF r(endv->pos().x(), endv->pos().y(), endv->rect().width(), endv->rect().height());
179         endp.setAttribute("rect", rectFToString(r));
180         QRectF r2(startv->pos().x(), startv->pos().y(), startv->rect().width(), startv->rect().height());
181         startp.setAttribute("rect", rectFToString(r2));
182
183         main.appendChild(startp);
184         main.appendChild(endp);
185     }
186     QDomElement backgr = doc.createElement("background");
187     QColor color = getBackgroundColor();
188     backgr.setAttribute("color", colorToString(color));
189     main.appendChild(backgr);
190
191     return doc;
192 }
193
194 /** \brief Get the background color (incl. alpha) from the document, if possibly
195   * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */
196 QColor TitleDocument::getBackgroundColor()
197 {
198     QColor color(0, 0, 0, 0);
199     if (m_scene) {
200         QList<QGraphicsItem *> items = m_scene->items();
201         for (int i = 0; i < items.size(); i++) {
202             if (items.at(i)->zValue() == -1100) {
203                 color = ((QGraphicsRectItem *)items.at(i))->brush().color();
204                 return color;
205             }
206         }
207     }
208     return color;
209 }
210
211
212 bool TitleDocument::saveDocument(const KUrl& url, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int out)
213 {
214     if (!m_scene)
215         return false;
216
217     QDomDocument doc = xml(startv, endv);
218     doc.documentElement().setAttribute("out", out);
219     KTemporaryFile tmpfile;
220     if (!tmpfile.open()) {
221         kWarning() << "/////  CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
222         return false;
223     }
224     QFile xmlf(tmpfile.fileName());
225     xmlf.open(QIODevice::WriteOnly);
226     xmlf.write(doc.toString().toUtf8());
227     if (xmlf.error() != QFile::NoError) {
228         xmlf.close();
229         return false;
230     }
231     xmlf.close();
232     return KIO::NetAccess::upload(tmpfile.fileName(), url, 0);
233 }
234
235 int TitleDocument::loadFromXml(QDomDocument doc, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int *out)
236 {
237     QDomNodeList titles = doc.elementsByTagName("kdenlivetitle");
238     //TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
239     if (doc.documentElement().hasAttribute("width") && doc.documentElement().hasAttribute("height")) {
240         int doc_width = doc.documentElement().attribute("width").toInt();
241         int doc_height = doc.documentElement().attribute("height").toInt();
242         if (doc_width != m_width || doc_height != m_height) {
243             KMessageBox::information(kapp->activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
244             //TODO: convert using QTransform
245             m_width = doc_width;
246             m_height = doc_height;
247         }
248     } else {
249         // Document has no size info, it is likely an old version title, so ignore viewport data
250         QDomNodeList viewportlist = doc.documentElement().elementsByTagName("startviewport");
251         if (!viewportlist.isEmpty()) {
252             doc.documentElement().removeChild(viewportlist.at(0));
253         }
254         viewportlist = doc.documentElement().elementsByTagName("endviewport");
255         if (!viewportlist.isEmpty()) {
256             doc.documentElement().removeChild(viewportlist.at(0));
257         }
258     }
259     //TODO: get default title duration instead of hardcoded one
260     if (doc.documentElement().hasAttribute("out"))
261         *out = doc.documentElement().attribute("out").toInt();
262     else
263         *out = 125;
264
265     int maxZValue = 0;
266     if (titles.size()) {
267
268         QDomNodeList items = titles.item(0).childNodes();
269         for (int i = 0; i < items.count(); i++) {
270             QGraphicsItem *gitem = NULL;
271             kDebug() << items.item(i).attributes().namedItem("type").nodeValue();
272             int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
273             if (zValue > -1000) {
274                 if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
275                     QDomNamedNodeMap txtProperties = items.item(i).namedItem("content").attributes();
276                     QFont font(txtProperties.namedItem("font").nodeValue());
277
278                     QDomNode node = txtProperties.namedItem("font-bold");
279                     if (!node.isNull()) {
280                         // Old: Bold/Not bold.
281                         font.setBold(node.nodeValue().toInt());
282                     } else {
283                         // New: Font weight (QFont::)
284                         font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
285                     }
286                     //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
287                     font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
288                     font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());
289                     // Older Kdenlive version did not store pixel size but point size
290                     if (txtProperties.namedItem("font-pixel-size").isNull()) {
291                         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"));
292                         QFont f2;
293                         f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
294                         font.setPixelSize(QFontInfo(f2).pixelSize());
295                     } else
296                         font.setPixelSize(txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
297                     QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
298                     QGraphicsTextItem *txt = m_scene->addText(items.item(i).namedItem("content").firstChild().nodeValue(), font);
299                     QTextCursor cursor(txt->document());
300                     cursor.select(QTextCursor::Document);
301                     QTextCharFormat format;
302                     if (txtProperties.namedItem("font-outline").nodeValue().toDouble() > 0.0) {
303                         txt->setData(101, txtProperties.namedItem("font-outline").nodeValue().toDouble());
304                         txt->setData(102, stringToColor(txtProperties.namedItem("font-outline-color").nodeValue()));
305                         format.setTextOutline(
306                             QPen(QColor(stringToColor(txtProperties.namedItem("font-outline-color").nodeValue())),
307                                  txtProperties.namedItem("font-outline").nodeValue().toDouble(),
308                                  Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
309                         );
310
311                     }
312                     format.setForeground(QBrush(col));
313                     cursor.mergeCharFormat(format);
314                     txt->setTextInteractionFlags(Qt::NoTextInteraction);
315                     if (txtProperties.namedItem("alignment").isNull() == false) {
316                         txt->setTextWidth(txt->boundingRect().width());
317                         QTextCursor cur = txt->textCursor();
318                         QTextBlockFormat format = cur.blockFormat();
319                         format.setAlignment((Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
320                         cur.select(QTextCursor::Document);
321                         cur.setBlockFormat(format);
322                         txt->setTextCursor(cur);
323                         cur.clearSelection();
324                         txt->setTextCursor(cur);
325                     }
326
327                     if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
328                         txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
329                     }
330                     if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
331                         txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
332                     }
333
334                     // Effects
335                     if (!txtProperties.namedItem("typewriter").isNull()) {
336                         QStringList effData = QStringList() << "typewriter" << txtProperties.namedItem("typewriter").nodeValue();
337                         txt->setData(100, effData);
338                     }
339
340                     gitem = txt;
341                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsRectItem") {
342                     QString rect = items.item(i).namedItem("content").attributes().namedItem("rect").nodeValue();
343                     QString br_str = items.item(i).namedItem("content").attributes().namedItem("brushcolor").nodeValue();
344                     QString pen_str = items.item(i).namedItem("content").attributes().namedItem("pencolor").nodeValue();
345                     double penwidth = items.item(i).namedItem("content").attributes().namedItem("penwidth").nodeValue().toDouble();
346                     QGraphicsRectItem *rec = m_scene->addRect(stringToRect(rect), QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin), QBrush(stringToColor(br_str)));
347                     gitem = rec;
348                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
349                     QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
350                     QPixmap pix(url);
351                     QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
352                     rec->setData(Qt::UserRole, url);
353                     gitem = rec;
354                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
355                     QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
356                     QGraphicsSvgItem *rec = new QGraphicsSvgItem(url);
357                     m_scene->addItem(rec);
358                     rec->setData(Qt::UserRole, url);
359                     gitem = rec;
360                 }
361             }
362             //pos and transform
363             if (gitem) {
364                 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
365                           items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
366                 gitem->setPos(p);
367                 QDomElement trans = items.item(i).namedItem("position").firstChild().toElement();
368                 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue()));
369                 QString rotate = trans.attribute("rotation");
370                 if (!rotate.isEmpty()) gitem->setData(ROTATEFACTOR, stringToList(rotate));
371                 QString zoom = trans.attribute("zoom");
372                 if (!zoom.isEmpty()) gitem->setData(ZOOMFACTOR, zoom.toInt());
373                 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
374                 if (zValue > maxZValue) maxZValue = zValue;
375                 gitem->setZValue(zValue);
376                 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
377
378 #if QT_VERSION >= 0x040600
379                 // effects
380                 QDomNode eff = items.item(i).namedItem("effect");
381                 if (!eff.isNull()) {
382                     QDomElement e = eff.toElement();
383                     if (e.attribute("type") == "blur") {
384                         QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
385                         blur->setBlurRadius(e.attribute("blurradius").toInt());
386                         gitem->setGraphicsEffect(blur);
387                     } else if (e.attribute("type") == "shadow") {
388                         QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
389                         shadow->setBlurRadius(e.attribute("blurradius").toInt());
390                         shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
391                         gitem->setGraphicsEffect(shadow);
392                     }
393                 }
394 #endif
395             }
396
397             if (items.item(i).nodeName() == "background") {
398                 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
399                 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
400                 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
401                 QList<QGraphicsItem *> items = m_scene->items();
402                 for (int i = 0; i < items.size(); i++) {
403                     if (items.at(i)->zValue() == -1100) {
404                         ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
405                         break;
406                     }
407                 }
408             } else if (items.item(i).nodeName() == "startviewport" && startv) {
409                 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
410                 QRectF r = stringToRect(rect);
411                 startv->setRect(0, 0, r.width(), r.height());
412                 startv->setPos(r.topLeft());
413             } else if (items.item(i).nodeName() == "endviewport" && endv) {
414                 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
415                 QRectF r = stringToRect(rect);
416                 endv->setRect(0, 0, r.width(), r.height());
417                 endv->setPos(r.topLeft());
418             }
419         }
420     }
421     return maxZValue;
422 }
423
424 QString TitleDocument::colorToString(const QColor& c)
425 {
426     QString ret = "%1,%2,%3,%4";
427     ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
428     return ret;
429 }
430
431 QString TitleDocument::rectFToString(const QRectF& c)
432 {
433     QString ret = "%1,%2,%3,%4";
434     ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
435     return ret;
436 }
437
438 QRectF TitleDocument::stringToRect(const QString & s)
439 {
440
441     QStringList l = s.split(',');
442     if (l.size() < 4)
443         return QRectF();
444     return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
445 }
446
447 QColor TitleDocument::stringToColor(const QString & s)
448 {
449     QStringList l = s.split(',');
450     if (l.size() < 4)
451         return QColor();
452     return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
453 }
454
455 QTransform TitleDocument::stringToTransform(const QString& s)
456 {
457     QStringList l = s.split(',');
458     if (l.size() < 9)
459         return QTransform();
460     return QTransform(
461                l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
462                l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
463                l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
464            );
465 }
466
467 QList<QVariant> TitleDocument::stringToList(const QString & s)
468 {
469     QStringList l = s.split(',');
470     if (l.size() < 3)
471         return QList<QVariant>();
472     return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
473 }
474
475 int TitleDocument::frameWidth() const
476 {
477     return m_width;
478 }
479
480 int TitleDocument::frameHeight() const
481 {
482     return m_height;
483 }
484
485