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