]> git.sesse.net Git - kdenlive/blob - src/titledocument.cpp
Fix title duration, keeping compatibility with Kdenlive 0.92
[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 duration, bool embed)
288 {
289     if (!m_scene)
290         return false;
291
292     QDomDocument doc = xml(startv, endv, embed);
293     doc.documentElement().setAttribute("duration", duration);
294     // keep some time for backwards compatibility (opening projects with older versions) - 26/12/12
295     doc.documentElement().setAttribute("out", duration);
296     KTemporaryFile tmpfile;
297     if (!tmpfile.open()) {
298         kWarning() << "/////  CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
299         return false;
300     }
301     QFile xmlf(tmpfile.fileName());
302     if (!xmlf.open(QIODevice::WriteOnly))
303         return false;
304     xmlf.write(doc.toString().toUtf8());
305     if (xmlf.error() != QFile::NoError) {
306         xmlf.close();
307         return false;
308     }
309     xmlf.close();
310     return KIO::NetAccess::upload(tmpfile.fileName(), url, 0);
311 }
312
313 int TitleDocument::loadFromXml(QDomDocument doc, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int *duration, const QString& projectpath)
314 {
315     m_projectPath = projectpath;
316     QDomNodeList titles = doc.elementsByTagName("kdenlivetitle");
317     //TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
318     if (doc.documentElement().hasAttribute("width") && doc.documentElement().hasAttribute("height")) {
319         int doc_width = doc.documentElement().attribute("width").toInt();
320         int doc_height = doc.documentElement().attribute("height").toInt();
321         if (doc_width != m_width || doc_height != m_height) {
322             KMessageBox::information(kapp->activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
323             //TODO: convert using QTransform
324             m_width = doc_width;
325             m_height = doc_height;
326         }
327     } else {
328         // Document has no size info, it is likely an old version title, so ignore viewport data
329         QDomNodeList viewportlist = doc.documentElement().elementsByTagName("startviewport");
330         if (!viewportlist.isEmpty()) {
331             doc.documentElement().removeChild(viewportlist.at(0));
332         }
333         viewportlist = doc.documentElement().elementsByTagName("endviewport");
334         if (!viewportlist.isEmpty()) {
335             doc.documentElement().removeChild(viewportlist.at(0));
336         }
337     }
338     //TODO: get default title duration instead of hardcoded one
339     if (doc.documentElement().hasAttribute("duration"))
340         *duration = doc.documentElement().attribute("duration").toInt();
341     if (doc.documentElement().hasAttribute("out"))
342         *duration = doc.documentElement().attribute("out").toInt();
343     else
344         *duration = 125;
345
346     int maxZValue = 0;
347     if (titles.size()) {
348
349         QDomNodeList items = titles.item(0).childNodes();
350         for (int i = 0; i < items.count(); i++) {
351             QGraphicsItem *gitem = NULL;
352             kDebug() << items.item(i).attributes().namedItem("type").nodeValue();
353             int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
354             if (zValue > -1000) {
355                 if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
356                     QDomNamedNodeMap txtProperties = items.item(i).namedItem("content").attributes();
357                     QFont font(txtProperties.namedItem("font").nodeValue());
358
359                     QDomNode node = txtProperties.namedItem("font-bold");
360                     if (!node.isNull()) {
361                         // Old: Bold/Not bold.
362                         font.setBold(node.nodeValue().toInt());
363                     } else {
364                         // New: Font weight (QFont::)
365                         font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
366                     }
367                     //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
368                     font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
369                     font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());
370                     // Older Kdenlive version did not store pixel size but point size
371                     if (txtProperties.namedItem("font-pixel-size").isNull()) {
372                         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"));
373                         QFont f2;
374                         f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
375                         font.setPixelSize(QFontInfo(f2).pixelSize());
376                     } else
377                         font.setPixelSize(txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
378                     QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
379                     QGraphicsTextItem *txt = m_scene->addText(items.item(i).namedItem("content").firstChild().nodeValue(), font);
380                     QTextCursor cursor(txt->document());
381                     cursor.select(QTextCursor::Document);
382                     QTextCharFormat format;
383                     if (txtProperties.namedItem("font-outline").nodeValue().toDouble() > 0.0) {
384                         txt->setData(101, txtProperties.namedItem("font-outline").nodeValue().toDouble());
385                         txt->setData(102, stringToColor(txtProperties.namedItem("font-outline-color").nodeValue()));
386                         format.setTextOutline(
387                             QPen(QColor(stringToColor(txtProperties.namedItem("font-outline-color").nodeValue())),
388                                  txtProperties.namedItem("font-outline").nodeValue().toDouble(),
389                                  Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
390                         );
391
392                     }
393                     format.setForeground(QBrush(col));
394                     cursor.mergeCharFormat(format);
395                     txt->setTextInteractionFlags(Qt::NoTextInteraction);
396                     if (txtProperties.namedItem("alignment").isNull() == false) {
397                         txt->setTextWidth(txt->boundingRect().width());
398                         QTextCursor cur = txt->textCursor();
399                         QTextBlockFormat format = cur.blockFormat();
400                         format.setAlignment((Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
401                         cur.select(QTextCursor::Document);
402                         cur.setBlockFormat(format);
403                         txt->setTextCursor(cur);
404                         cur.clearSelection();
405                         txt->setTextCursor(cur);
406                     }
407
408                     if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
409                         txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
410                     }
411                     if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
412                         txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
413                     }
414
415                     // Effects
416                     if (!txtProperties.namedItem("typewriter").isNull()) {
417                         QStringList effData = QStringList() << "typewriter" << txtProperties.namedItem("typewriter").nodeValue();
418                         txt->setData(100, effData);
419                     }
420
421                     gitem = txt;
422                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsRectItem") {
423                     QString rect = items.item(i).namedItem("content").attributes().namedItem("rect").nodeValue();
424                     QString br_str = items.item(i).namedItem("content").attributes().namedItem("brushcolor").nodeValue();
425                     QString pen_str = items.item(i).namedItem("content").attributes().namedItem("pencolor").nodeValue();
426                     double penwidth = items.item(i).namedItem("content").attributes().namedItem("penwidth").nodeValue().toDouble();
427                     QGraphicsRectItem *rec = m_scene->addRect(stringToRect(rect), QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin), QBrush(stringToColor(br_str)));
428                     gitem = rec;
429                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
430                     QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
431                     QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
432                     QPixmap pix;
433                     if (base64.isEmpty()) {
434                         pix.load(url);
435                     } else {
436                         pix.loadFromData(QByteArray::fromBase64(base64.toAscii()));
437                     }
438                     QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
439                     rec->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
440                     rec->setData(Qt::UserRole, url);
441                     if (!base64.isEmpty()) {
442                         rec->setData(Qt::UserRole + 1, base64);
443                     }
444                     gitem = rec;
445                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
446                     QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
447                     QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
448                     QGraphicsSvgItem *rec = NULL;
449                     if (base64.isEmpty()) {
450                         rec = new QGraphicsSvgItem(url);
451                     } else {
452                         rec = new QGraphicsSvgItem();
453                         QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toAscii()), rec);
454                         rec->setSharedRenderer(renderer);
455                         //QString elem=rec->elementId();
456                         //QRectF bounds = renderer->boundsOnElement(elem);
457                     }
458                     if (rec) {
459                         m_scene->addItem(rec);
460                         rec->setData(Qt::UserRole, url);
461                         if (!base64.isEmpty()) {
462                             rec->setData(Qt::UserRole + 1, base64);
463                         }
464                         gitem = rec;
465                     }
466                 }
467             }
468             //pos and transform
469             if (gitem) {
470                 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
471                           items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
472                 gitem->setPos(p);
473                 QDomElement trans = items.item(i).namedItem("position").firstChild().toElement();
474                 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue()));
475                 QString rotate = trans.attribute("rotation");
476                 if (!rotate.isEmpty()) gitem->setData(ROTATEFACTOR, stringToList(rotate));
477                 QString zoom = trans.attribute("zoom");
478                 if (!zoom.isEmpty()) gitem->setData(ZOOMFACTOR, zoom.toInt());
479                 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
480                 if (zValue > maxZValue) maxZValue = zValue;
481                 gitem->setZValue(zValue);
482                 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
483
484 #if QT_VERSION >= 0x040600
485                 // effects
486                 QDomNode eff = items.item(i).namedItem("effect");
487                 if (!eff.isNull()) {
488                     QDomElement e = eff.toElement();
489                     if (e.attribute("type") == "blur") {
490                         QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
491                         blur->setBlurRadius(e.attribute("blurradius").toInt());
492                         gitem->setGraphicsEffect(blur);
493                     } else if (e.attribute("type") == "shadow") {
494                         QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
495                         shadow->setBlurRadius(e.attribute("blurradius").toInt());
496                         shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
497                         gitem->setGraphicsEffect(shadow);
498                     }
499                 }
500 #endif
501             }
502
503             if (items.item(i).nodeName() == "background") {
504                 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
505                 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
506                 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
507                 QList<QGraphicsItem *> items = m_scene->items();
508                 for (int i = 0; i < items.size(); i++) {
509                     if (items.at(i)->zValue() == -1100) {
510                         ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
511                         break;
512                     }
513                 }
514             } else if (items.item(i).nodeName() == "startviewport" && startv) {
515                 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
516                 QRectF r = stringToRect(rect);
517                 startv->setRect(0, 0, r.width(), r.height());
518                 startv->setPos(r.topLeft());
519             } else if (items.item(i).nodeName() == "endviewport" && endv) {
520                 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
521                 QRectF r = stringToRect(rect);
522                 endv->setRect(0, 0, r.width(), r.height());
523                 endv->setPos(r.topLeft());
524             }
525         }
526     }
527     return maxZValue;
528 }
529
530 QString TitleDocument::colorToString(const QColor& c)
531 {
532     QString ret = "%1,%2,%3,%4";
533     ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
534     return ret;
535 }
536
537 QString TitleDocument::rectFToString(const QRectF& c)
538 {
539     QString ret = "%1,%2,%3,%4";
540     ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
541     return ret;
542 }
543
544 QRectF TitleDocument::stringToRect(const QString & s)
545 {
546
547     QStringList l = s.split(',');
548     if (l.size() < 4)
549         return QRectF();
550     return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
551 }
552
553 QColor TitleDocument::stringToColor(const QString & s)
554 {
555     QStringList l = s.split(',');
556     if (l.size() < 4)
557         return QColor();
558     return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
559 }
560
561 QTransform TitleDocument::stringToTransform(const QString& s)
562 {
563     QStringList l = s.split(',');
564     if (l.size() < 9)
565         return QTransform();
566     return QTransform(
567                l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
568                l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
569                l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
570            );
571 }
572
573 QList<QVariant> TitleDocument::stringToList(const QString & s)
574 {
575     QStringList l = s.split(',');
576     if (l.size() < 3)
577         return QList<QVariant>();
578     return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
579 }
580
581 int TitleDocument::frameWidth() const
582 {
583     return m_width;
584 }
585
586 int TitleDocument::frameHeight() const
587 {
588     return m_height;
589 }
590
591