]> git.sesse.net Git - kdenlive/blob - src/titledocument.cpp
Fix title clips that have items outside frame rect
[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
38 TitleDocument::TitleDocument()
39 {
40     m_scene = NULL;
41 }
42
43 void TitleDocument::setScene(QGraphicsScene* _scene, int width, int height)
44 {
45     m_scene = _scene;
46     m_width = width;
47     m_height = height;
48 }
49
50 QDomDocument TitleDocument::xml(QGraphicsPolygonItem* startv, QGraphicsPolygonItem* endv)
51 {
52     QDomDocument doc;
53
54     QDomElement main = doc.createElement("kdenlivetitle");
55     main.setAttribute("width", m_width);
56     main.setAttribute("height", m_height);
57     doc.appendChild(main);
58
59     foreach(QGraphicsItem* item, m_scene->items()) {
60         QDomElement e = doc.createElement("item");
61         QDomElement content = doc.createElement("content");
62         QFont font;
63         QGraphicsTextItem *t;
64
65         switch (item->type()) {
66         case 7:
67             e.setAttribute("type", "QGraphicsPixmapItem");
68             content.setAttribute("url", item->data(Qt::UserRole).toString());
69             break;
70         case 13:
71             e.setAttribute("type", "QGraphicsSvgItem");
72             content.setAttribute("url", item->data(Qt::UserRole).toString());
73             break;
74         case 3:
75             e.setAttribute("type", "QGraphicsRectItem");
76             content.setAttribute("rect", rectFToString(((QGraphicsRectItem*)item)->rect().normalized()));
77             content.setAttribute("pencolor", colorToString(((QGraphicsRectItem*)item)->pen().color()));
78             content.setAttribute("penwidth", ((QGraphicsRectItem*)item)->pen().width());
79             content.setAttribute("brushcolor", colorToString(((QGraphicsRectItem*)item)->brush().color()));
80             break;
81         case 8:
82             e.setAttribute("type", "QGraphicsTextItem");
83             t = static_cast<QGraphicsTextItem *>(item);
84             // Don't save empty text nodes
85             if (t->toPlainText().simplified().isEmpty()) continue;
86             //content.appendChild(doc.createTextNode(((QGraphicsTextItem*)item)->toHtml()));
87             content.appendChild(doc.createTextNode(t->toPlainText()));
88             font = t->font();
89             content.setAttribute("font", font.family());
90             content.setAttribute("font-weight", font.weight());
91             content.setAttribute("font-pixel-size", font.pixelSize());
92             content.setAttribute("font-italic", font.italic());
93             content.setAttribute("font-underline", font.underline());
94             content.setAttribute("font-color", colorToString(t->defaultTextColor()));
95
96             // Only save when necessary.
97             if (t->data(OriginXLeft).toInt() == AxisInverted) {
98                 content.setAttribute("kdenlive-axis-x-inverted", t->data(OriginXLeft).toInt());
99             }
100             if (t->data(OriginYTop).toInt() == AxisInverted) {
101                 content.setAttribute("kdenlive-axis-y-inverted", t->data(OriginYTop).toInt());
102             }
103             if (t->textWidth() != -1) {
104                 content.setAttribute("alignment", t->textCursor().blockFormat().alignment());
105             }
106             break;
107         default:
108             continue;
109         }
110         QDomElement pos = doc.createElement("position");
111         pos.setAttribute("x", item->pos().x());
112         pos.setAttribute("y", item->pos().y());
113         QTransform transform = item->transform();
114         QDomElement tr = doc.createElement("transform");
115         tr.appendChild(doc.createTextNode(
116                            QString("%1,%2,%3,%4,%5,%6,%7,%8,%9").arg(
117                                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())
118                        )
119                       );
120         e.setAttribute("z-index", item->zValue());
121         pos.appendChild(tr);
122
123
124         e.appendChild(pos);
125         e.appendChild(content);
126         if (item->zValue() > -1100) main.appendChild(e);
127     }
128     if (startv && endv) {
129         QDomElement endp = doc.createElement("endviewport");
130         QDomElement startp = doc.createElement("startviewport");
131         endp.setAttribute("x", endv->data(0).toString());
132         endp.setAttribute("y", endv->data(1).toString());
133         endp.setAttribute("size", endv->data(2).toString());
134         endp.setAttribute("rect", rectFToString(endv->boundingRect()));
135
136         startp.setAttribute("x", startv->data(0).toString());
137         startp.setAttribute("y", startv->data(1).toString());
138         startp.setAttribute("size", startv->data(2).toString());
139         startp.setAttribute("rect", rectFToString(startv->boundingRect()));
140
141         startp.setAttribute("z-index", startv->zValue());
142         endp.setAttribute("z-index", endv->zValue());
143         main.appendChild(startp);
144         main.appendChild(endp);
145     }
146     QDomElement backgr = doc.createElement("background");
147     QColor color = getBackgroundColor();
148     backgr.setAttribute("color", colorToString(color));
149     main.appendChild(backgr);
150
151     return doc;
152 }
153
154 /** \brief Get the background color (incl. alpha) from the document, if possibly
155   * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */
156 QColor TitleDocument::getBackgroundColor()
157 {
158     QColor color(0, 0, 0, 0);
159     if (m_scene) {
160         QList<QGraphicsItem *> items = m_scene->items();
161         for (int i = 0; i < items.size(); i++) {
162             if (items.at(i)->zValue() == -1100) {
163                 color = ((QGraphicsRectItem *)items.at(i))->brush().color();
164                 return color;
165             }
166         }
167     }
168     return color;
169 }
170
171
172 bool TitleDocument::saveDocument(const KUrl& url, QGraphicsPolygonItem* startv, QGraphicsPolygonItem* endv, double out)
173 {
174     if (!m_scene)
175         return false;
176
177     QDomDocument doc = xml(startv, endv);
178     doc.documentElement().setAttribute("out", out);
179     KTemporaryFile tmpfile;
180     if (!tmpfile.open()) {
181         kWarning() << "/////  CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
182         return false;
183     }
184     QFile xmlf(tmpfile.fileName());
185     xmlf.open(QIODevice::WriteOnly);
186     xmlf.write(doc.toString().toUtf8());
187     if (xmlf.error() != QFile::NoError) {
188         xmlf.close();
189         return false;
190     }
191     xmlf.close();
192     return KIO::NetAccess::upload(tmpfile.fileName(), url, 0);
193 }
194
195 int TitleDocument::loadDocument(const KUrl& url, QGraphicsPolygonItem* startv, QGraphicsPolygonItem* endv, double *out)
196 {
197     QString tmpfile;
198     QDomDocument doc;
199     if (!m_scene)
200         return -1;
201
202     if (KIO::NetAccess::download(url, tmpfile, 0)) {
203         QFile file(tmpfile);
204         if (file.open(QIODevice::ReadOnly)) {
205             doc.setContent(&file, false);
206             file.close();
207         } else
208             return -1;
209         KIO::NetAccess::removeTempFile(tmpfile);
210         return loadFromXml(doc, startv, endv, out);
211     }
212     return -1;
213 }
214
215 int TitleDocument::loadFromXml(QDomDocument doc, QGraphicsPolygonItem* startv, QGraphicsPolygonItem* endv, double *out)
216 {
217     QDomNodeList titles = doc.elementsByTagName("kdenlivetitle");
218     //TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
219     //TODO: get default title duration instead of hardcoded one
220     if (doc.documentElement().hasAttribute("out"))
221         *out = doc.documentElement().attribute("out").toDouble();
222     else
223         *out = 5000;
224
225     int maxZValue = 0;
226     if (titles.size()) {
227
228         QDomNodeList items = titles.item(0).childNodes();
229         for (int i = 0; i < items.count(); i++) {
230             QGraphicsItem *gitem = NULL;
231             kDebug() << items.item(i).attributes().namedItem("type").nodeValue();
232             int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
233             if (zValue > -1000) {
234                 if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
235                     QDomNamedNodeMap txtProperties = items.item(i).namedItem("content").attributes();
236                     QFont font(txtProperties.namedItem("font").nodeValue());
237
238                     QDomNode node = txtProperties.namedItem("font-bold");
239                     if (!node.isNull()) {
240                         // Old: Bold/Not bold.
241                         font.setBold(node.nodeValue().toInt());
242                     } else {
243                         // New: Font weight (QFont::)
244                         font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
245                     }
246                     //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
247                     font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
248                     font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());
249                     // Older Kdenlive version did not store pixel size but point size
250                     if (txtProperties.namedItem("font-pixel-size").isNull()) {
251                         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"));
252                         QFont f2;
253                         f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
254                         font.setPixelSize(QFontInfo(f2).pixelSize());
255                     } else
256                         font.setPixelSize(txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
257                     QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
258                     QGraphicsTextItem *txt = m_scene->addText(items.item(i).namedItem("content").firstChild().nodeValue(), font);
259                     txt->setDefaultTextColor(col);
260                     txt->setTextInteractionFlags(Qt::NoTextInteraction);
261                     if (txtProperties.namedItem("alignment").isNull() == false) {
262                         txt->setTextWidth(txt->boundingRect().width());
263                         QTextCursor cur = txt->textCursor();
264                         QTextBlockFormat format = cur.blockFormat();
265                         format.setAlignment((Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
266                         cur.select(QTextCursor::Document);
267                         cur.setBlockFormat(format);
268                         txt->setTextCursor(cur);
269                         cur.clearSelection();
270                         txt->setTextCursor(cur);
271                     }
272
273                     if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
274                         txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
275                     }
276                     if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
277                         txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
278                     }
279
280                     gitem = txt;
281                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsRectItem") {
282                     QString rect = items.item(i).namedItem("content").attributes().namedItem("rect").nodeValue();
283                     QString br_str = items.item(i).namedItem("content").attributes().namedItem("brushcolor").nodeValue();
284                     QString pen_str = items.item(i).namedItem("content").attributes().namedItem("pencolor").nodeValue();
285                     double penwidth = items.item(i).namedItem("content").attributes().namedItem("penwidth").nodeValue().toDouble();
286                     QGraphicsRectItem *rec = m_scene->addRect(stringToRect(rect), QPen(QBrush(stringToColor(pen_str)), penwidth), QBrush(stringToColor(br_str)));
287                     gitem = rec;
288                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
289                     QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
290                     QPixmap pix(url);
291                     QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
292                     rec->setData(Qt::UserRole, url);
293                     gitem = rec;
294                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
295                     QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
296                     QGraphicsSvgItem *rec = new QGraphicsSvgItem(url);
297                     m_scene->addItem(rec);
298                     rec->setData(Qt::UserRole, url);
299                     gitem = rec;
300                 }
301             }
302             //pos and transform
303             if (gitem) {
304                 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
305                           items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
306                 gitem->setPos(p);
307                 gitem->setTransform(stringToTransform(items.item(i).namedItem("position").firstChild().firstChild().nodeValue()));
308                 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
309                 if (zValue > maxZValue) maxZValue = zValue;
310                 gitem->setZValue(zValue);
311                 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
312             }
313             if (items.item(i).nodeName() == "background") {
314                 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
315                 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
316                 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
317                 QList<QGraphicsItem *> items = m_scene->items();
318                 for (int i = 0; i < items.size(); i++) {
319                     if (items.at(i)->zValue() == -1100) {
320                         ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
321                         break;
322                     }
323                 }
324             } else if (items.item(i).nodeName() == "startviewport" && startv) {
325                 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
326                 startv->setPolygon(stringToRect(rect));
327                 int x = items.item(i).attributes().namedItem("x").nodeValue().toInt();
328                 int y = items.item(i).attributes().namedItem("y").nodeValue().toInt();
329                 int size = items.item(i).attributes().namedItem("size").nodeValue().toInt();
330                 startv->setData(0, x);
331                 startv->setData(1, y);
332                 startv->setData(2, size);
333                 //startv->setPos(p);
334             } else if (items.item(i).nodeName() == "endviewport" && endv) {
335                 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
336                 endv->setPolygon(stringToRect(rect));
337                 int x = items.item(i).attributes().namedItem("x").nodeValue().toInt();
338                 int y = items.item(i).attributes().namedItem("y").nodeValue().toInt();
339                 int size = items.item(i).attributes().namedItem("size").nodeValue().toInt();
340                 endv->setData(0, x);
341                 endv->setData(1, y);
342                 endv->setData(2, size);
343             }
344         }
345     }
346     return maxZValue;
347 }
348
349 QString TitleDocument::colorToString(const QColor& c)
350 {
351     QString ret = "%1,%2,%3,%4";
352     ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
353     return ret;
354 }
355
356 QString TitleDocument::rectFToString(const QRectF& c)
357 {
358     QString ret = "%1,%2,%3,%4";
359     ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
360     return ret;
361 }
362
363 QRectF TitleDocument::stringToRect(const QString & s)
364 {
365
366     QStringList l = s.split(',');
367     if (l.size() < 4)
368         return QRectF();
369     return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
370 }
371
372 QColor TitleDocument::stringToColor(const QString & s)
373 {
374     QStringList l = s.split(',');
375     if (l.size() < 4)
376         return QColor();
377     return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
378 }
379 QTransform TitleDocument::stringToTransform(const QString& s)
380 {
381     QStringList l = s.split(',');
382     if (l.size() < 9)
383         return QTransform();
384     return QTransform(
385                l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
386                l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
387                l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
388            );
389 }