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