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