1 /***************************************************************************
2 titledocument.h - description
5 copyright : (C) 2008 by Marco Gittler
6 email : g.marco@freenet.de
7 ***************************************************************************/
9 /***************************************************************************
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. *
16 ***************************************************************************/
18 #include "titledocument.h"
21 #include <KTemporaryFile>
22 #include <kio/netaccess.h>
23 #include <KApplication>
25 #include <KMessageBox>
27 #include <QGraphicsScene>
28 #include <QDomElement>
29 #include <QGraphicsItem>
30 #include <QGraphicsRectItem>
31 #include <QGraphicsTextItem>
32 #include <QGraphicsSvgItem>
35 #include <QTextCursor>
37 #if QT_VERSION >= 0x040600
38 #include <QGraphicsEffect>
39 #include <QGraphicsBlurEffect>
40 #include <QGraphicsDropShadowEffect>
43 TitleDocument::TitleDocument()
48 void TitleDocument::setScene(QGraphicsScene* _scene, int width, int height)
55 QDomDocument TitleDocument::xml(QGraphicsRectItem* startv, QGraphicsRectItem* endv)
59 QDomElement main = doc.createElement("kdenlivetitle");
60 main.setAttribute("width", m_width);
61 main.setAttribute("height", m_height);
62 doc.appendChild(main);
64 foreach(QGraphicsItem* item, m_scene->items()) {
65 QDomElement e = doc.createElement("item");
66 QDomElement content = doc.createElement("content");
70 switch (item->type()) {
72 e.setAttribute("type", "QGraphicsPixmapItem");
73 content.setAttribute("url", item->data(Qt::UserRole).toString());
76 e.setAttribute("type", "QGraphicsSvgItem");
77 content.setAttribute("url", item->data(Qt::UserRole).toString());
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()));
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()));
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());
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())));
107 if (!t->data(100).isNull()) {
108 QStringList effectParams = t->data(100).toStringList();
109 QString effectName = effectParams.takeFirst();
110 content.setAttribute(effectName, effectParams.join(";"));
113 // Only save when necessary.
114 if (t->data(OriginXLeft).toInt() == AxisInverted) {
115 content.setAttribute("kdenlive-axis-x-inverted", t->data(OriginXLeft).toInt());
117 if (t->data(OriginYTop).toInt() == AxisInverted) {
118 content.setAttribute("kdenlive-axis-y-inverted", t->data(OriginYTop).toInt());
120 if (t->textWidth() != -1) {
121 content.setAttribute("alignment", t->textCursor().blockFormat().alignment());
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()));
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()));
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())
146 e.setAttribute("z-index", item->zValue());
149 #if QT_VERSION >= 0x040600
151 QGraphicsEffect *eff = item->graphicsEffect();
153 QGraphicsBlurEffect *blur = static_cast <QGraphicsBlurEffect *>(eff);
154 QDomElement effect = doc.createElement("effect");
156 effect.setAttribute("type", "blur");
157 effect.setAttribute("blurradius", blur->blurRadius());
159 QGraphicsDropShadowEffect *shadow = static_cast <QGraphicsDropShadowEffect *>(eff);
161 effect.setAttribute("type", "shadow");
162 effect.setAttribute("blurradius", shadow->blurRadius());
163 effect.setAttribute("xoffset", shadow->xOffset());
164 effect.setAttribute("yoffset", shadow->yOffset());
167 e.appendChild(effect);
172 e.appendChild(content);
173 if (item->zValue() > -1000) main.appendChild(e);
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));
183 main.appendChild(startp);
184 main.appendChild(endp);
186 QDomElement backgr = doc.createElement("background");
187 QColor color = getBackgroundColor();
188 backgr.setAttribute("color", colorToString(color));
189 main.appendChild(backgr);
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()
198 QColor color(0, 0, 0, 0);
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();
212 bool TitleDocument::saveDocument(const KUrl& url, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int out)
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();
224 QFile xmlf(tmpfile.fileName());
225 xmlf.open(QIODevice::WriteOnly);
226 xmlf.write(doc.toString().toUtf8());
227 if (xmlf.error() != QFile::NoError) {
232 return KIO::NetAccess::upload(tmpfile.fileName(), url, 0);
235 int TitleDocument::loadFromXml(QDomDocument doc, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int *out)
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
246 m_height = doc_height;
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));
254 viewportlist = doc.documentElement().elementsByTagName("endviewport");
255 if (!viewportlist.isEmpty()) {
256 doc.documentElement().removeChild(viewportlist.at(0));
259 //TODO: get default title duration instead of hardcoded one
260 if (doc.documentElement().hasAttribute("out"))
261 *out = doc.documentElement().attribute("out").toInt();
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());
278 QDomNode node = txtProperties.namedItem("font-bold");
279 if (!node.isNull()) {
280 // Old: Bold/Not bold.
281 font.setBold(node.nodeValue().toInt());
283 // New: Font weight (QFont::)
284 font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
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"));
293 f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
294 font.setPixelSize(QFontInfo(f2).pixelSize());
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)
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);
327 if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
328 txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
330 if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
331 txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
335 if (!txtProperties.namedItem("typewriter").isNull()) {
336 QStringList effData = QStringList() << "typewriter" << txtProperties.namedItem("typewriter").nodeValue();
337 txt->setData(100, effData);
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)));
348 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
349 QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
351 QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
352 rec->setData(Qt::UserRole, url);
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);
364 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
365 items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
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);
378 #if QT_VERSION >= 0x040600
380 QDomNode eff = items.item(i).namedItem("effect");
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);
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));
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());
424 QString TitleDocument::colorToString(const QColor& c)
426 QString ret = "%1,%2,%3,%4";
427 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
431 QString TitleDocument::rectFToString(const QRectF& c)
433 QString ret = "%1,%2,%3,%4";
434 ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
438 QRectF TitleDocument::stringToRect(const QString & s)
441 QStringList l = s.split(',');
444 return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
447 QColor TitleDocument::stringToColor(const QString & s)
449 QStringList l = s.split(',');
452 return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
455 QTransform TitleDocument::stringToTransform(const QString& s)
457 QStringList l = s.split(',');
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()
467 QList<QVariant> TitleDocument::stringToList(const QString & s)
469 QStringList l = s.split(',');
471 return QList<QVariant>();
472 return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
475 int TitleDocument::frameWidth() const
480 int TitleDocument::frameHeight() const