]> git.sesse.net Git - kdenlive/blob - src/titledocument.cpp
Add an "effect start" param to typewriter that allows to set a delay before the effec...
[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             content.setAttribute("font-color", colorToString(t->defaultTextColor()));
100
101             if (!t->data(100).isNull()) {
102                 QStringList effectParams = t->data(100).toStringList();
103                 content.setAttribute(effectParams.takeFirst(), effectParams.join(";"));
104             }
105
106             // Only save when necessary.
107             if (t->data(OriginXLeft).toInt() == AxisInverted) {
108                 content.setAttribute("kdenlive-axis-x-inverted", t->data(OriginXLeft).toInt());
109             }
110             if (t->data(OriginYTop).toInt() == AxisInverted) {
111                 content.setAttribute("kdenlive-axis-y-inverted", t->data(OriginYTop).toInt());
112             }
113             if (t->textWidth() != -1) {
114                 content.setAttribute("alignment", t->textCursor().blockFormat().alignment());
115             }
116             break;
117         default:
118             continue;
119         }
120
121         // position
122         QDomElement pos = doc.createElement("position");
123         pos.setAttribute("x", item->pos().x());
124         pos.setAttribute("y", item->pos().y());
125         QTransform transform = item->transform();
126         QDomElement tr = doc.createElement("transform");
127         tr.appendChild(doc.createTextNode(
128                            QString("%1,%2,%3,%4,%5,%6,%7,%8,%9").arg(
129                                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())
130                        )
131                       );
132         e.setAttribute("z-index", item->zValue());
133         pos.appendChild(tr);
134
135 #if QT_VERSION >= 0x040600
136         // effects
137         QGraphicsEffect *eff = item->graphicsEffect();
138         if (eff) {
139             QGraphicsBlurEffect *blur = static_cast <QGraphicsBlurEffect *>(eff);
140             QDomElement effect = doc.createElement("effect");
141             if (blur) {
142                 effect.setAttribute("type", "blur");
143                 effect.setAttribute("blurradius", blur->blurRadius());
144             } else {
145                 QGraphicsDropShadowEffect *shadow = static_cast <QGraphicsDropShadowEffect *>(eff);
146                 if (shadow) {
147                     effect.setAttribute("type", "shadow");
148                     effect.setAttribute("blurradius", shadow->blurRadius());
149                     effect.setAttribute("xoffset", shadow->xOffset());
150                     effect.setAttribute("yoffset", shadow->yOffset());
151                 }
152             }
153             e.appendChild(effect);
154         }
155 #endif
156
157         e.appendChild(pos);
158         e.appendChild(content);
159         if (item->zValue() > -1000) main.appendChild(e);
160     }
161     if (startv && endv) {
162         QDomElement endp = doc.createElement("endviewport");
163         QDomElement startp = doc.createElement("startviewport");
164         QRectF r(endv->pos().x(), endv->pos().y(), endv->rect().width(), endv->rect().height());
165         endp.setAttribute("rect", rectFToString(r));
166         QRectF r2(startv->pos().x(), startv->pos().y(), startv->rect().width(), startv->rect().height());
167         startp.setAttribute("rect", rectFToString(r2));
168
169         main.appendChild(startp);
170         main.appendChild(endp);
171     }
172     QDomElement backgr = doc.createElement("background");
173     QColor color = getBackgroundColor();
174     backgr.setAttribute("color", colorToString(color));
175     main.appendChild(backgr);
176
177     return doc;
178 }
179
180 /** \brief Get the background color (incl. alpha) from the document, if possibly
181   * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */
182 QColor TitleDocument::getBackgroundColor()
183 {
184     QColor color(0, 0, 0, 0);
185     if (m_scene) {
186         QList<QGraphicsItem *> items = m_scene->items();
187         for (int i = 0; i < items.size(); i++) {
188             if (items.at(i)->zValue() == -1100) {
189                 color = ((QGraphicsRectItem *)items.at(i))->brush().color();
190                 return color;
191             }
192         }
193     }
194     return color;
195 }
196
197
198 bool TitleDocument::saveDocument(const KUrl& url, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int out)
199 {
200     if (!m_scene)
201         return false;
202
203     QDomDocument doc = xml(startv, endv);
204     doc.documentElement().setAttribute("out", out);
205     KTemporaryFile tmpfile;
206     if (!tmpfile.open()) {
207         kWarning() << "/////  CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
208         return false;
209     }
210     QFile xmlf(tmpfile.fileName());
211     xmlf.open(QIODevice::WriteOnly);
212     xmlf.write(doc.toString().toUtf8());
213     if (xmlf.error() != QFile::NoError) {
214         xmlf.close();
215         return false;
216     }
217     xmlf.close();
218     return KIO::NetAccess::upload(tmpfile.fileName(), url, 0);
219 }
220
221 int TitleDocument::loadFromXml(QDomDocument doc, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int *out)
222 {
223     QDomNodeList titles = doc.elementsByTagName("kdenlivetitle");
224     //TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
225     if (doc.documentElement().hasAttribute("width") && doc.documentElement().hasAttribute("height")) {
226         int doc_width = doc.documentElement().attribute("width").toInt();
227         int doc_height = doc.documentElement().attribute("height").toInt();
228         if (doc_width != m_width || doc_height != m_height) {
229             KMessageBox::information(kapp->activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
230             //TODO: convert using QTransform
231             m_width = doc_width;
232             m_height = doc_height;
233         }
234     }
235     //TODO: get default title duration instead of hardcoded one
236     if (doc.documentElement().hasAttribute("out"))
237         *out = doc.documentElement().attribute("out").toInt();
238     else
239         *out = 125;
240
241     int maxZValue = 0;
242     if (titles.size()) {
243
244         QDomNodeList items = titles.item(0).childNodes();
245         for (int i = 0; i < items.count(); i++) {
246             QGraphicsItem *gitem = NULL;
247             kDebug() << items.item(i).attributes().namedItem("type").nodeValue();
248             int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
249             if (zValue > -1000) {
250                 if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
251                     QDomNamedNodeMap txtProperties = items.item(i).namedItem("content").attributes();
252                     QFont font(txtProperties.namedItem("font").nodeValue());
253
254                     QDomNode node = txtProperties.namedItem("font-bold");
255                     if (!node.isNull()) {
256                         // Old: Bold/Not bold.
257                         font.setBold(node.nodeValue().toInt());
258                     } else {
259                         // New: Font weight (QFont::)
260                         font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
261                     }
262                     //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
263                     font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
264                     font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());
265                     // Older Kdenlive version did not store pixel size but point size
266                     if (txtProperties.namedItem("font-pixel-size").isNull()) {
267                         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"));
268                         QFont f2;
269                         f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
270                         font.setPixelSize(QFontInfo(f2).pixelSize());
271                     } else
272                         font.setPixelSize(txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
273                     QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
274                     QGraphicsTextItem *txt = m_scene->addText(items.item(i).namedItem("content").firstChild().nodeValue(), font);
275                     txt->setDefaultTextColor(col);
276                     txt->setTextInteractionFlags(Qt::NoTextInteraction);
277                     if (txtProperties.namedItem("alignment").isNull() == false) {
278                         txt->setTextWidth(txt->boundingRect().width());
279                         QTextCursor cur = txt->textCursor();
280                         QTextBlockFormat format = cur.blockFormat();
281                         format.setAlignment((Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
282                         cur.select(QTextCursor::Document);
283                         cur.setBlockFormat(format);
284                         txt->setTextCursor(cur);
285                         cur.clearSelection();
286                         txt->setTextCursor(cur);
287                     }
288
289                     if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
290                         txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
291                     }
292                     if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
293                         txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
294                     }
295
296                     // Effects
297                     if (!txtProperties.namedItem("typewriter").isNull()) {
298                         QStringList effData = QStringList() << "typewriter" << txtProperties.namedItem("typewriter").nodeValue();
299                         txt->setData(100, effData);
300                     }
301
302                     gitem = txt;
303                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsRectItem") {
304                     QString rect = items.item(i).namedItem("content").attributes().namedItem("rect").nodeValue();
305                     QString br_str = items.item(i).namedItem("content").attributes().namedItem("brushcolor").nodeValue();
306                     QString pen_str = items.item(i).namedItem("content").attributes().namedItem("pencolor").nodeValue();
307                     double penwidth = items.item(i).namedItem("content").attributes().namedItem("penwidth").nodeValue().toDouble();
308                     QGraphicsRectItem *rec = m_scene->addRect(stringToRect(rect), QPen(QBrush(stringToColor(pen_str)), penwidth), QBrush(stringToColor(br_str)));
309                     gitem = rec;
310                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
311                     QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
312                     QPixmap pix(url);
313                     QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
314                     rec->setData(Qt::UserRole, url);
315                     gitem = rec;
316                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
317                     QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
318                     QGraphicsSvgItem *rec = new QGraphicsSvgItem(url);
319                     m_scene->addItem(rec);
320                     rec->setData(Qt::UserRole, url);
321                     gitem = rec;
322                 }
323             }
324             //pos and transform
325             if (gitem) {
326                 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
327                           items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
328                 gitem->setPos(p);
329                 gitem->setTransform(stringToTransform(items.item(i).namedItem("position").firstChild().firstChild().nodeValue()));
330                 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
331                 if (zValue > maxZValue) maxZValue = zValue;
332                 gitem->setZValue(zValue);
333                 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
334
335 #if QT_VERSION >= 0x040600
336                 // effects
337                 QDomNode eff = items.item(i).namedItem("effect");
338                 if (!eff.isNull()) {
339                     QDomElement e = eff.toElement();
340                     if (e.attribute("type") == "blur") {
341                         QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
342                         blur->setBlurRadius(e.attribute("blurradius").toInt());
343                         gitem->setGraphicsEffect(blur);
344                     } else if (e.attribute("type") == "shadow") {
345                         QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
346                         shadow->setBlurRadius(e.attribute("blurradius").toInt());
347                         shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
348                         gitem->setGraphicsEffect(shadow);
349                     }
350                 }
351 #endif
352             }
353
354             if (items.item(i).nodeName() == "background") {
355                 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
356                 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
357                 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
358                 QList<QGraphicsItem *> items = m_scene->items();
359                 for (int i = 0; i < items.size(); i++) {
360                     if (items.at(i)->zValue() == -1100) {
361                         ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
362                         break;
363                     }
364                 }
365             } else if (items.item(i).nodeName() == "startviewport" && startv) {
366                 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
367                 QRectF r = stringToRect(rect);
368                 startv->setRect(0, 0, r.width(), r.height());
369                 startv->setPos(r.topLeft());
370             } else if (items.item(i).nodeName() == "endviewport" && endv) {
371                 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
372                 QRectF r = stringToRect(rect);
373                 endv->setRect(0, 0, r.width(), r.height());
374                 endv->setPos(r.topLeft());
375             }
376         }
377     }
378     return maxZValue;
379 }
380
381 QString TitleDocument::colorToString(const QColor& c)
382 {
383     QString ret = "%1,%2,%3,%4";
384     ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
385     return ret;
386 }
387
388 QString TitleDocument::rectFToString(const QRectF& c)
389 {
390     QString ret = "%1,%2,%3,%4";
391     ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
392     return ret;
393 }
394
395 QRectF TitleDocument::stringToRect(const QString & s)
396 {
397
398     QStringList l = s.split(',');
399     if (l.size() < 4)
400         return QRectF();
401     return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
402 }
403
404 QColor TitleDocument::stringToColor(const QString & s)
405 {
406     QStringList l = s.split(',');
407     if (l.size() < 4)
408         return QColor();
409     return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
410 }
411 QTransform TitleDocument::stringToTransform(const QString& s)
412 {
413     QStringList l = s.split(',');
414     if (l.size() < 9)
415         return QTransform();
416     return QTransform(
417                l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
418                l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
419                l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
420            );
421 }
422
423 int TitleDocument::frameWidth() const
424 {
425     return m_width;
426 }
427
428 int TitleDocument::frameHeight() const
429 {
430     return m_height;
431 }
432
433