]> git.sesse.net Git - kdenlive/blob - src/titledocument.cpp
6ab7d19e9cf89ff83b494f868a30b40cf1ab1f10
[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 <KStandardDirs>
25 #include <KLocale>
26 #include <KMessageBox>
27
28 #include <QGraphicsScene>
29 #include <QDomElement>
30 #include <QGraphicsItem>
31 #include <QGraphicsRectItem>
32 #include <QGraphicsTextItem>
33 #include <QGraphicsSvgItem>
34 #include <QCryptographicHash>
35 #include <QSvgRenderer>
36 #include <QFontInfo>
37 #include <QFile>
38 #include <QTextCursor>
39
40 #include <locale.h>
41
42 #if QT_VERSION >= 0x040600
43 #include <QGraphicsEffect>
44 #include <QGraphicsBlurEffect>
45 #include <QGraphicsDropShadowEffect>
46 #endif
47
48 QByteArray fileToByteArray(const QString& filename)
49 {
50     QByteArray ret;
51     QFile file(filename);
52     if (file.open(QIODevice::ReadOnly)) {
53         while (!file.atEnd()) {
54             ret.append(file.readLine());
55         }
56     }
57     return ret;
58 }
59
60 TitleDocument::TitleDocument()
61 {
62     m_scene = NULL;
63     m_width = 0;
64     m_height = 0;
65 }
66
67 void TitleDocument::setScene(QGraphicsScene* _scene, int width, int height)
68 {
69     m_scene = _scene;
70     m_width = width;
71     m_height = height;
72 }
73
74 int TitleDocument::base64ToUrl(QGraphicsItem* item, QDomElement& content, bool embed)
75 {
76     if (embed) {
77         if (!item->data(Qt::UserRole + 1).toString().isEmpty()) {
78             content.setAttribute("base64", item->data(Qt::UserRole + 1).toString());
79         } else if (!item->data(Qt::UserRole).toString().isEmpty()) {
80             content.setAttribute("base64", fileToByteArray(item->data(Qt::UserRole).toString()).toBase64().data());
81         }
82         content.removeAttribute("url");
83     } else {
84         // save for project files to disk
85         QString base64 = item->data(Qt::UserRole + 1).toString();
86         if (!base64.isEmpty()) {
87             QString titlePath;
88             if (!m_projectPath.isEmpty()) {
89                 titlePath = m_projectPath;
90             } else {
91                 titlePath = "/tmp/titles";
92             }
93             QString filename = extractBase64Image(titlePath, base64);
94             if (!filename.isEmpty()) {
95                 content.setAttribute("url", filename);
96                 content.removeAttribute("base64");
97             }
98
99         } else {
100             return 1;
101         }
102     }
103     return 0;
104 }
105
106
107 //static
108 const QString TitleDocument::extractBase64Image(const QString &titlePath, const QString &data)
109 {
110     QString filename = titlePath + QString(QCryptographicHash::hash(data.toAscii(), QCryptographicHash::Md5).toHex().append(".titlepart"));
111     KStandardDirs::makeDir(titlePath);
112     QFile f(filename);
113     if (f.open(QIODevice::WriteOnly)) {
114         f.write(QByteArray::fromBase64(data.toAscii())) ;
115         f.close();
116         return filename;
117     }
118     return QString();
119 }
120
121 QDomDocument TitleDocument::xml(QGraphicsRectItem* startv, QGraphicsRectItem* endv, bool embed)
122 {
123     QDomDocument doc;
124
125     QDomElement main = doc.createElement("kdenlivetitle");
126     main.setAttribute("width", m_width);
127     main.setAttribute("height", m_height);
128     // Save locale
129     const char *locale = setlocale(LC_NUMERIC, NULL);
130     main.setAttribute("LC_NUMERIC", locale);
131     doc.appendChild(main);
132
133     foreach(QGraphicsItem * item, m_scene->items()) {
134         QDomElement e = doc.createElement("item");
135         QDomElement content = doc.createElement("content");
136         QFont font;
137         QGraphicsTextItem *t;
138
139         switch (item->type()) {
140         case 7:
141             e.setAttribute("type", "QGraphicsPixmapItem");
142             content.setAttribute("url", item->data(Qt::UserRole).toString());
143             base64ToUrl(item, content, embed);
144             break;
145         case 13:
146             e.setAttribute("type", "QGraphicsSvgItem");
147             content.setAttribute("url", item->data(Qt::UserRole).toString());
148             base64ToUrl(item, content, embed);
149             break;
150         case 3:
151             e.setAttribute("type", "QGraphicsRectItem");
152             content.setAttribute("rect", rectFToString(((QGraphicsRectItem*)item)->rect().normalized()));
153             content.setAttribute("pencolor", colorToString(((QGraphicsRectItem*)item)->pen().color()));
154             content.setAttribute("penwidth", ((QGraphicsRectItem*)item)->pen().width());
155             content.setAttribute("brushcolor", colorToString(((QGraphicsRectItem*)item)->brush().color()));
156             break;
157         case 8:
158             e.setAttribute("type", "QGraphicsTextItem");
159             t = static_cast<QGraphicsTextItem *>(item);
160             // Don't save empty text nodes
161             if (t->toPlainText().simplified().isEmpty()) continue;
162             //content.appendChild(doc.createTextNode(((QGraphicsTextItem*)item)->toHtml()));
163             content.appendChild(doc.createTextNode(t->toPlainText()));
164             font = t->font();
165             content.setAttribute("font", font.family());
166             content.setAttribute("font-weight", font.weight());
167             content.setAttribute("font-pixel-size", font.pixelSize());
168             content.setAttribute("font-italic", font.italic());
169             content.setAttribute("font-underline", font.underline());
170             {
171                 QTextCursor cursor(t->document());
172                 cursor.select(QTextCursor::Document);
173                 QColor fontcolor = cursor.charFormat().foreground().color();
174                 content.setAttribute("font-color", colorToString(fontcolor));
175                 if (!t->data(101).isNull()) content.setAttribute("font-outline", t->data(101).toDouble());
176                 if (!t->data(102).isNull()) {
177                     QVariant variant = t->data(102);
178                     QColor outlineColor = variant.value<QColor>();
179                     content.setAttribute("font-outline-color", colorToString(outlineColor));
180                 }
181             }
182             if (!t->data(100).isNull()) {
183                 QStringList effectParams = t->data(100).toStringList();
184                 QString effectName = effectParams.takeFirst();
185                 content.setAttribute(effectName, effectParams.join(";"));
186             }
187
188             // Only save when necessary.
189             if (t->data(OriginXLeft).toInt() == AxisInverted) {
190                 content.setAttribute("kdenlive-axis-x-inverted", t->data(OriginXLeft).toInt());
191             }
192             if (t->data(OriginYTop).toInt() == AxisInverted) {
193                 content.setAttribute("kdenlive-axis-y-inverted", t->data(OriginYTop).toInt());
194             }
195             if (t->textWidth() != -1) {
196                 content.setAttribute("alignment", t->textCursor().blockFormat().alignment());
197             }
198             break;
199         default:
200             continue;
201         }
202
203         // position
204         QDomElement pos = doc.createElement("position");
205         pos.setAttribute("x", item->pos().x());
206         pos.setAttribute("y", item->pos().y());
207         QTransform transform = item->transform();
208         QDomElement tr = doc.createElement("transform");
209         if (!item->data(ZOOMFACTOR).isNull()) {
210             tr.setAttribute("zoom", QString::number(item->data(ZOOMFACTOR).toInt()));
211         }
212         if (!item->data(ROTATEFACTOR).isNull()) {
213             QList<QVariant> rotlist = item->data(ROTATEFACTOR).toList();
214             tr.setAttribute("rotation", QString("%1,%2,%3").arg(rotlist[0].toDouble()).arg(rotlist[1].toDouble()).arg(rotlist[2].toDouble()));
215         }
216         tr.appendChild(doc.createTextNode(
217                            QString("%1,%2,%3,%4,%5,%6,%7,%8,%9").arg(
218                                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())
219                        )
220                       );
221         e.setAttribute("z-index", item->zValue());
222         pos.appendChild(tr);
223
224 #if QT_VERSION >= 0x040600
225         // effects
226         QGraphicsEffect *eff = item->graphicsEffect();
227         if (eff) {
228             QGraphicsBlurEffect *blur = static_cast <QGraphicsBlurEffect *>(eff);
229             QDomElement effect = doc.createElement("effect");
230             if (blur) {
231                 effect.setAttribute("type", "blur");
232                 effect.setAttribute("blurradius", blur->blurRadius());
233             } else {
234                 QGraphicsDropShadowEffect *shadow = static_cast <QGraphicsDropShadowEffect *>(eff);
235                 if (shadow) {
236                     effect.setAttribute("type", "shadow");
237                     effect.setAttribute("blurradius", shadow->blurRadius());
238                     effect.setAttribute("xoffset", shadow->xOffset());
239                     effect.setAttribute("yoffset", shadow->yOffset());
240                 }
241             }
242             e.appendChild(effect);
243         }
244 #endif
245
246         e.appendChild(pos);
247         e.appendChild(content);
248         if (item->zValue() > -1000) main.appendChild(e);
249     }
250     if (startv && endv) {
251         QDomElement endp = doc.createElement("endviewport");
252         QDomElement startp = doc.createElement("startviewport");
253         QRectF r(endv->pos().x(), endv->pos().y(), endv->rect().width(), endv->rect().height());
254         endp.setAttribute("rect", rectFToString(r));
255         QRectF r2(startv->pos().x(), startv->pos().y(), startv->rect().width(), startv->rect().height());
256         startp.setAttribute("rect", rectFToString(r2));
257
258         main.appendChild(startp);
259         main.appendChild(endp);
260     }
261     QDomElement backgr = doc.createElement("background");
262     QColor color = getBackgroundColor();
263     backgr.setAttribute("color", colorToString(color));
264     main.appendChild(backgr);
265
266     return doc;
267 }
268
269 /** \brief Get the background color (incl. alpha) from the document, if possibly
270   * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */
271 QColor TitleDocument::getBackgroundColor()
272 {
273     QColor color(0, 0, 0, 0);
274     if (m_scene) {
275         QList<QGraphicsItem *> items = m_scene->items();
276         for (int i = 0; i < items.size(); i++) {
277             if (items.at(i)->zValue() == -1100) {
278                 color = ((QGraphicsRectItem *)items.at(i))->brush().color();
279                 return color;
280             }
281         }
282     }
283     return color;
284 }
285
286
287 bool TitleDocument::saveDocument(const KUrl& url, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int out, bool embed)
288 {
289     if (!m_scene)
290         return false;
291
292     QDomDocument doc = xml(startv, endv, embed);
293     doc.documentElement().setAttribute("out", out);
294     KTemporaryFile tmpfile;
295     if (!tmpfile.open()) {
296         kWarning() << "/////  CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
297         return false;
298     }
299     QFile xmlf(tmpfile.fileName());
300     if (!xmlf.open(QIODevice::WriteOnly))
301         return false;
302     xmlf.write(doc.toString().toUtf8());
303     if (xmlf.error() != QFile::NoError) {
304         xmlf.close();
305         return false;
306     }
307     xmlf.close();
308     return KIO::NetAccess::upload(tmpfile.fileName(), url, 0);
309 }
310
311 int TitleDocument::loadFromXml(QDomDocument doc, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int *out, const QString& projectpath)
312 {
313     m_projectPath = projectpath;
314     QDomNodeList titles = doc.elementsByTagName("kdenlivetitle");
315     //TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
316     if (doc.documentElement().hasAttribute("width") && doc.documentElement().hasAttribute("height")) {
317         int doc_width = doc.documentElement().attribute("width").toInt();
318         int doc_height = doc.documentElement().attribute("height").toInt();
319         if (doc_width != m_width || doc_height != m_height) {
320             KMessageBox::information(kapp->activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
321             //TODO: convert using QTransform
322             m_width = doc_width;
323             m_height = doc_height;
324         }
325     } else {
326         // Document has no size info, it is likely an old version title, so ignore viewport data
327         QDomNodeList viewportlist = doc.documentElement().elementsByTagName("startviewport");
328         if (!viewportlist.isEmpty()) {
329             doc.documentElement().removeChild(viewportlist.at(0));
330         }
331         viewportlist = doc.documentElement().elementsByTagName("endviewport");
332         if (!viewportlist.isEmpty()) {
333             doc.documentElement().removeChild(viewportlist.at(0));
334         }
335     }
336     //TODO: get default title duration instead of hardcoded one
337     if (doc.documentElement().hasAttribute("out"))
338         *out = doc.documentElement().attribute("out").toInt();
339     else
340         *out = 125;
341
342     int maxZValue = 0;
343     if (titles.size()) {
344
345         QDomNodeList items = titles.item(0).childNodes();
346         for (int i = 0; i < items.count(); i++) {
347             QGraphicsItem *gitem = NULL;
348             kDebug() << items.item(i).attributes().namedItem("type").nodeValue();
349             int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
350             if (zValue > -1000) {
351                 if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
352                     QDomNamedNodeMap txtProperties = items.item(i).namedItem("content").attributes();
353                     QFont font(txtProperties.namedItem("font").nodeValue());
354
355                     QDomNode node = txtProperties.namedItem("font-bold");
356                     if (!node.isNull()) {
357                         // Old: Bold/Not bold.
358                         font.setBold(node.nodeValue().toInt());
359                     } else {
360                         // New: Font weight (QFont::)
361                         font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
362                     }
363                     //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
364                     font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
365                     font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());
366                     // Older Kdenlive version did not store pixel size but point size
367                     if (txtProperties.namedItem("font-pixel-size").isNull()) {
368                         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"));
369                         QFont f2;
370                         f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
371                         font.setPixelSize(QFontInfo(f2).pixelSize());
372                     } else
373                         font.setPixelSize(txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
374                     QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
375                     QGraphicsTextItem *txt = m_scene->addText(items.item(i).namedItem("content").firstChild().nodeValue(), font);
376                     QTextCursor cursor(txt->document());
377                     cursor.select(QTextCursor::Document);
378                     QTextCharFormat format;
379                     if (txtProperties.namedItem("font-outline").nodeValue().toDouble() > 0.0) {
380                         txt->setData(101, txtProperties.namedItem("font-outline").nodeValue().toDouble());
381                         txt->setData(102, stringToColor(txtProperties.namedItem("font-outline-color").nodeValue()));
382                         format.setTextOutline(
383                             QPen(QColor(stringToColor(txtProperties.namedItem("font-outline-color").nodeValue())),
384                                  txtProperties.namedItem("font-outline").nodeValue().toDouble(),
385                                  Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
386                         );
387
388                     }
389                     format.setForeground(QBrush(col));
390                     cursor.mergeCharFormat(format);
391                     txt->setTextInteractionFlags(Qt::NoTextInteraction);
392                     if (txtProperties.namedItem("alignment").isNull() == false) {
393                         txt->setTextWidth(txt->boundingRect().width());
394                         QTextCursor cur = txt->textCursor();
395                         QTextBlockFormat format = cur.blockFormat();
396                         format.setAlignment((Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
397                         cur.select(QTextCursor::Document);
398                         cur.setBlockFormat(format);
399                         txt->setTextCursor(cur);
400                         cur.clearSelection();
401                         txt->setTextCursor(cur);
402                     }
403
404                     if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
405                         txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
406                     }
407                     if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
408                         txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
409                     }
410
411                     // Effects
412                     if (!txtProperties.namedItem("typewriter").isNull()) {
413                         QStringList effData = QStringList() << "typewriter" << txtProperties.namedItem("typewriter").nodeValue();
414                         txt->setData(100, effData);
415                     }
416
417                     gitem = txt;
418                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsRectItem") {
419                     QString rect = items.item(i).namedItem("content").attributes().namedItem("rect").nodeValue();
420                     QString br_str = items.item(i).namedItem("content").attributes().namedItem("brushcolor").nodeValue();
421                     QString pen_str = items.item(i).namedItem("content").attributes().namedItem("pencolor").nodeValue();
422                     double penwidth = items.item(i).namedItem("content").attributes().namedItem("penwidth").nodeValue().toDouble();
423                     QGraphicsRectItem *rec = m_scene->addRect(stringToRect(rect), QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin), QBrush(stringToColor(br_str)));
424                     gitem = rec;
425                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
426                     QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
427                     QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
428                     QPixmap pix;
429                     if (base64.isEmpty()) {
430                         pix.load(url);
431                     } else {
432                         pix.loadFromData(QByteArray::fromBase64(base64.toAscii()));
433                     }
434                     QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
435                     rec->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
436                     rec->setData(Qt::UserRole, url);
437                     if (!base64.isEmpty()) {
438                         rec->setData(Qt::UserRole + 1, base64);
439                     }
440                     gitem = rec;
441                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
442                     QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
443                     QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
444                     QGraphicsSvgItem *rec = NULL;
445                     if (base64.isEmpty()) {
446                         rec = new QGraphicsSvgItem(url);
447                     } else {
448                         rec = new QGraphicsSvgItem();
449                         QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toAscii()), rec);
450                         rec->setSharedRenderer(renderer);
451                         //QString elem=rec->elementId();
452                         //QRectF bounds = renderer->boundsOnElement(elem);
453                     }
454                     if (rec) {
455                         m_scene->addItem(rec);
456                         rec->setData(Qt::UserRole, url);
457                         if (!base64.isEmpty()) {
458                             rec->setData(Qt::UserRole + 1, base64);
459                         }
460                         gitem = rec;
461                     }
462                 }
463             }
464             //pos and transform
465             if (gitem) {
466                 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
467                           items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
468                 gitem->setPos(p);
469                 QDomElement trans = items.item(i).namedItem("position").firstChild().toElement();
470                 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue()));
471                 QString rotate = trans.attribute("rotation");
472                 if (!rotate.isEmpty()) gitem->setData(ROTATEFACTOR, stringToList(rotate));
473                 QString zoom = trans.attribute("zoom");
474                 if (!zoom.isEmpty()) gitem->setData(ZOOMFACTOR, zoom.toInt());
475                 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
476                 if (zValue > maxZValue) maxZValue = zValue;
477                 gitem->setZValue(zValue);
478                 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
479
480 #if QT_VERSION >= 0x040600
481                 // effects
482                 QDomNode eff = items.item(i).namedItem("effect");
483                 if (!eff.isNull()) {
484                     QDomElement e = eff.toElement();
485                     if (e.attribute("type") == "blur") {
486                         QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
487                         blur->setBlurRadius(e.attribute("blurradius").toInt());
488                         gitem->setGraphicsEffect(blur);
489                     } else if (e.attribute("type") == "shadow") {
490                         QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
491                         shadow->setBlurRadius(e.attribute("blurradius").toInt());
492                         shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
493                         gitem->setGraphicsEffect(shadow);
494                     }
495                 }
496 #endif
497             }
498
499             if (items.item(i).nodeName() == "background") {
500                 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
501                 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
502                 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
503                 QList<QGraphicsItem *> items = m_scene->items();
504                 for (int i = 0; i < items.size(); i++) {
505                     if (items.at(i)->zValue() == -1100) {
506                         ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
507                         break;
508                     }
509                 }
510             } else if (items.item(i).nodeName() == "startviewport" && startv) {
511                 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
512                 QRectF r = stringToRect(rect);
513                 startv->setRect(0, 0, r.width(), r.height());
514                 startv->setPos(r.topLeft());
515             } else if (items.item(i).nodeName() == "endviewport" && endv) {
516                 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
517                 QRectF r = stringToRect(rect);
518                 endv->setRect(0, 0, r.width(), r.height());
519                 endv->setPos(r.topLeft());
520             }
521         }
522     }
523     return maxZValue;
524 }
525
526 QString TitleDocument::colorToString(const QColor& c)
527 {
528     QString ret = "%1,%2,%3,%4";
529     ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
530     return ret;
531 }
532
533 QString TitleDocument::rectFToString(const QRectF& c)
534 {
535     QString ret = "%1,%2,%3,%4";
536     ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
537     return ret;
538 }
539
540 QRectF TitleDocument::stringToRect(const QString & s)
541 {
542
543     QStringList l = s.split(',');
544     if (l.size() < 4)
545         return QRectF();
546     return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
547 }
548
549 QColor TitleDocument::stringToColor(const QString & s)
550 {
551     QStringList l = s.split(',');
552     if (l.size() < 4)
553         return QColor();
554     return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
555 }
556
557 QTransform TitleDocument::stringToTransform(const QString& s)
558 {
559     QStringList l = s.split(',');
560     if (l.size() < 9)
561         return QTransform();
562     return QTransform(
563                l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
564                l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
565                l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
566            );
567 }
568
569 QList<QVariant> TitleDocument::stringToList(const QString & s)
570 {
571     QStringList l = s.split(',');
572     if (l.size() < 3)
573         return QList<QVariant>();
574     return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
575 }
576
577 int TitleDocument::frameWidth() const
578 {
579     return m_width;
580 }
581
582 int TitleDocument::frameHeight() const
583 {
584     return m_height;
585 }
586
587