]> git.sesse.net Git - kdenlive/blob - src/titledocument.cpp
Round recangle border in titles (patch from Till Theato) + small cleanup in titlewidg...
[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     }
249     //TODO: get default title duration instead of hardcoded one
250     if (doc.documentElement().hasAttribute("out"))
251         *out = doc.documentElement().attribute("out").toInt();
252     else
253         *out = 125;
254
255     int maxZValue = 0;
256     if (titles.size()) {
257
258         QDomNodeList items = titles.item(0).childNodes();
259         for (int i = 0; i < items.count(); i++) {
260             QGraphicsItem *gitem = NULL;
261             kDebug() << items.item(i).attributes().namedItem("type").nodeValue();
262             int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
263             if (zValue > -1000) {
264                 if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
265                     QDomNamedNodeMap txtProperties = items.item(i).namedItem("content").attributes();
266                     QFont font(txtProperties.namedItem("font").nodeValue());
267
268                     QDomNode node = txtProperties.namedItem("font-bold");
269                     if (!node.isNull()) {
270                         // Old: Bold/Not bold.
271                         font.setBold(node.nodeValue().toInt());
272                     } else {
273                         // New: Font weight (QFont::)
274                         font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
275                     }
276                     //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
277                     font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
278                     font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());
279                     // Older Kdenlive version did not store pixel size but point size
280                     if (txtProperties.namedItem("font-pixel-size").isNull()) {
281                         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"));
282                         QFont f2;
283                         f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
284                         font.setPixelSize(QFontInfo(f2).pixelSize());
285                     } else
286                         font.setPixelSize(txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
287                     QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
288                     QGraphicsTextItem *txt = m_scene->addText(items.item(i).namedItem("content").firstChild().nodeValue(), font);
289                     QTextCursor cursor(txt->document());
290                     cursor.select(QTextCursor::Document);
291                     QTextCharFormat format;
292                     if (txtProperties.namedItem("font-outline").nodeValue().toDouble() > 0.0) {
293                         txt->setData(101, txtProperties.namedItem("font-outline").nodeValue().toDouble());
294                         txt->setData(102, stringToColor(txtProperties.namedItem("font-outline-color").nodeValue()));
295                         format.setTextOutline(
296                             QPen(QColor(stringToColor(txtProperties.namedItem("font-outline-color").nodeValue())),
297                                  txtProperties.namedItem("font-outline").nodeValue().toDouble(),
298                                  Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
299                         );
300
301                     }
302                     format.setForeground(QBrush(col));
303                     cursor.mergeCharFormat(format);
304                     txt->setTextInteractionFlags(Qt::NoTextInteraction);
305                     if (txtProperties.namedItem("alignment").isNull() == false) {
306                         txt->setTextWidth(txt->boundingRect().width());
307                         QTextCursor cur = txt->textCursor();
308                         QTextBlockFormat format = cur.blockFormat();
309                         format.setAlignment((Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
310                         cur.select(QTextCursor::Document);
311                         cur.setBlockFormat(format);
312                         txt->setTextCursor(cur);
313                         cur.clearSelection();
314                         txt->setTextCursor(cur);
315                     }
316
317                     if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
318                         txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
319                     }
320                     if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
321                         txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
322                     }
323
324                     // Effects
325                     if (!txtProperties.namedItem("typewriter").isNull()) {
326                         QStringList effData = QStringList() << "typewriter" << txtProperties.namedItem("typewriter").nodeValue();
327                         txt->setData(100, effData);
328                     }
329
330                     gitem = txt;
331                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsRectItem") {
332                     QString rect = items.item(i).namedItem("content").attributes().namedItem("rect").nodeValue();
333                     QString br_str = items.item(i).namedItem("content").attributes().namedItem("brushcolor").nodeValue();
334                     QString pen_str = items.item(i).namedItem("content").attributes().namedItem("pencolor").nodeValue();
335                     double penwidth = items.item(i).namedItem("content").attributes().namedItem("penwidth").nodeValue().toDouble();
336                     QGraphicsRectItem *rec = m_scene->addRect(stringToRect(rect), QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin), QBrush(stringToColor(br_str)));
337                     gitem = rec;
338                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
339                     QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
340                     QPixmap pix(url);
341                     QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
342                     rec->setData(Qt::UserRole, url);
343                     gitem = rec;
344                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
345                     QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
346                     QGraphicsSvgItem *rec = new QGraphicsSvgItem(url);
347                     m_scene->addItem(rec);
348                     rec->setData(Qt::UserRole, url);
349                     gitem = rec;
350                 }
351             }
352             //pos and transform
353             if (gitem) {
354                 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
355                           items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
356                 gitem->setPos(p);
357                 QDomElement trans = items.item(i).namedItem("position").firstChild().toElement();
358                 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue()));
359                 QString rotate = trans.attribute("rotation");
360                 if (!rotate.isEmpty()) gitem->setData(ROTATEFACTOR, stringToList(rotate));
361                 QString zoom = trans.attribute("zoom");
362                 if (!zoom.isEmpty()) gitem->setData(ZOOMFACTOR, zoom.toInt());
363                 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
364                 if (zValue > maxZValue) maxZValue = zValue;
365                 gitem->setZValue(zValue);
366                 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
367
368 #if QT_VERSION >= 0x040600
369                 // effects
370                 QDomNode eff = items.item(i).namedItem("effect");
371                 if (!eff.isNull()) {
372                     QDomElement e = eff.toElement();
373                     if (e.attribute("type") == "blur") {
374                         QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
375                         blur->setBlurRadius(e.attribute("blurradius").toInt());
376                         gitem->setGraphicsEffect(blur);
377                     } else if (e.attribute("type") == "shadow") {
378                         QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
379                         shadow->setBlurRadius(e.attribute("blurradius").toInt());
380                         shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
381                         gitem->setGraphicsEffect(shadow);
382                     }
383                 }
384 #endif
385             }
386
387             if (items.item(i).nodeName() == "background") {
388                 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
389                 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
390                 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
391                 QList<QGraphicsItem *> items = m_scene->items();
392                 for (int i = 0; i < items.size(); i++) {
393                     if (items.at(i)->zValue() == -1100) {
394                         ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
395                         break;
396                     }
397                 }
398             } else if (items.item(i).nodeName() == "startviewport" && startv) {
399                 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
400                 QRectF r = stringToRect(rect);
401                 startv->setRect(0, 0, r.width(), r.height());
402                 startv->setPos(r.topLeft());
403             } else if (items.item(i).nodeName() == "endviewport" && endv) {
404                 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
405                 QRectF r = stringToRect(rect);
406                 endv->setRect(0, 0, r.width(), r.height());
407                 endv->setPos(r.topLeft());
408             }
409         }
410     }
411     return maxZValue;
412 }
413
414 QString TitleDocument::colorToString(const QColor& c)
415 {
416     QString ret = "%1,%2,%3,%4";
417     ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
418     return ret;
419 }
420
421 QString TitleDocument::rectFToString(const QRectF& c)
422 {
423     QString ret = "%1,%2,%3,%4";
424     ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
425     return ret;
426 }
427
428 QRectF TitleDocument::stringToRect(const QString & s)
429 {
430
431     QStringList l = s.split(',');
432     if (l.size() < 4)
433         return QRectF();
434     return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
435 }
436
437 QColor TitleDocument::stringToColor(const QString & s)
438 {
439     QStringList l = s.split(',');
440     if (l.size() < 4)
441         return QColor();
442     return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
443 }
444
445 QTransform TitleDocument::stringToTransform(const QString& s)
446 {
447     QStringList l = s.split(',');
448     if (l.size() < 9)
449         return QTransform();
450     return QTransform(
451                l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
452                l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
453                l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
454            );
455 }
456
457 QList<QVariant> TitleDocument::stringToList(const QString & s)
458 {
459     QStringList l = s.split(',');
460     if (l.size() < 3)
461         return QList<QVariant>();
462     return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
463 }
464
465 int TitleDocument::frameWidth() const
466 {
467     return m_width;
468 }
469
470 int TitleDocument::frameHeight() const
471 {
472     return m_height;
473 }
474
475