]> git.sesse.net Git - kdenlive/blob - src/titledocument.cpp
Use KLocalizedString (for i18n only, in kf5 it will necessary => use a script for...
[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 <KLocalizedString>
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("textwidth", t->sceneBoundingRect().width());
186                 content.setAttribute(effectName, effectParams.join(";"));
187             }
188
189             // Only save when necessary.
190             if (t->data(OriginXLeft).toInt() == AxisInverted) {
191                 content.setAttribute("kdenlive-axis-x-inverted", t->data(OriginXLeft).toInt());
192             }
193             if (t->data(OriginYTop).toInt() == AxisInverted) {
194                 content.setAttribute("kdenlive-axis-y-inverted", t->data(OriginYTop).toInt());
195             }
196             if (t->textWidth() != -1) {
197                 content.setAttribute("alignment", t->textCursor().blockFormat().alignment());
198             }
199             break;
200         default:
201             continue;
202         }
203
204         // position
205         QDomElement pos = doc.createElement("position");
206         pos.setAttribute("x", item->pos().x());
207         pos.setAttribute("y", item->pos().y());
208         QTransform transform = item->transform();
209         QDomElement tr = doc.createElement("transform");
210         if (!item->data(ZOOMFACTOR).isNull()) {
211             tr.setAttribute("zoom", QString::number(item->data(ZOOMFACTOR).toInt()));
212         }
213         if (!item->data(ROTATEFACTOR).isNull()) {
214             QList<QVariant> rotlist = item->data(ROTATEFACTOR).toList();
215             tr.setAttribute("rotation", QString("%1,%2,%3").arg(rotlist[0].toDouble()).arg(rotlist[1].toDouble()).arg(rotlist[2].toDouble()));
216         }
217         tr.appendChild(doc.createTextNode(
218                            QString("%1,%2,%3,%4,%5,%6,%7,%8,%9").arg(
219                                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())
220                        )
221                       );
222         e.setAttribute("z-index", item->zValue());
223         pos.appendChild(tr);
224
225 #if QT_VERSION >= 0x040600
226         // effects
227         QGraphicsEffect *eff = item->graphicsEffect();
228         if (eff) {
229             QGraphicsBlurEffect *blur = static_cast <QGraphicsBlurEffect *>(eff);
230             QDomElement effect = doc.createElement("effect");
231             if (blur) {
232                 effect.setAttribute("type", "blur");
233                 effect.setAttribute("blurradius", blur->blurRadius());
234             } /*else {
235                 //WARNING:those effects are anyways broken because they use QPixmaps which are not safe for MLT's threaded workflow
236                 QGraphicsDropShadowEffect *shadow = static_cast <QGraphicsDropShadowEffect *>(eff);
237                 if (shadow) {
238                     effect.setAttribute("type", "shadow");
239                     effect.setAttribute("blurradius", shadow->blurRadius());
240                     effect.setAttribute("xoffset", shadow->xOffset());
241                     effect.setAttribute("yoffset", shadow->yOffset());
242                 }
243             }*/
244             e.appendChild(effect);
245         }
246 #endif
247
248         e.appendChild(pos);
249         e.appendChild(content);
250         if (item->zValue() > -1000) main.appendChild(e);
251     }
252     if (startv && endv) {
253         QDomElement endp = doc.createElement("endviewport");
254         QDomElement startp = doc.createElement("startviewport");
255         QRectF r(endv->pos().x(), endv->pos().y(), endv->rect().width(), endv->rect().height());
256         endp.setAttribute("rect", rectFToString(r));
257         QRectF r2(startv->pos().x(), startv->pos().y(), startv->rect().width(), startv->rect().height());
258         startp.setAttribute("rect", rectFToString(r2));
259
260         main.appendChild(startp);
261         main.appendChild(endp);
262     }
263     QDomElement backgr = doc.createElement("background");
264     QColor color = getBackgroundColor();
265     backgr.setAttribute("color", colorToString(color));
266     main.appendChild(backgr);
267
268     return doc;
269 }
270
271 /** \brief Get the background color (incl. alpha) from the document, if possibly
272   * \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */
273 QColor TitleDocument::getBackgroundColor() const
274 {
275     QColor color(0, 0, 0, 0);
276     if (m_scene) {
277         QList<QGraphicsItem *> items = m_scene->items();
278         for (int i = 0; i < items.size(); ++i) {
279             if (items.at(i)->zValue() == -1100) {
280                 color = ((QGraphicsRectItem *)items.at(i))->brush().color();
281                 return color;
282             }
283         }
284     }
285     return color;
286 }
287
288
289 bool TitleDocument::saveDocument(const KUrl& url, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int duration, bool embed)
290 {
291     if (!m_scene)
292         return false;
293
294     QDomDocument doc = xml(startv, endv, embed);
295     doc.documentElement().setAttribute("duration", duration);
296     // keep some time for backwards compatibility (opening projects with older versions) - 26/12/12
297     doc.documentElement().setAttribute("out", duration);
298     KTemporaryFile tmpfile;
299     if (!tmpfile.open()) {
300         kWarning() << "/////  CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
301         return false;
302     }
303     QFile xmlf(tmpfile.fileName());
304     if (!xmlf.open(QIODevice::WriteOnly))
305         return false;
306     xmlf.write(doc.toString().toUtf8());
307     if (xmlf.error() != QFile::NoError) {
308         xmlf.close();
309         return false;
310     }
311     xmlf.close();
312     return KIO::NetAccess::upload(tmpfile.fileName(), url, 0);
313 }
314
315 int TitleDocument::loadFromXml(const QDomDocument& doc, QGraphicsRectItem* startv, QGraphicsRectItem* endv, int *duration, const QString& projectpath)
316 {
317     m_projectPath = projectpath;
318     QDomNodeList titles = doc.elementsByTagName("kdenlivetitle");
319     //TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
320     if (doc.documentElement().hasAttribute("width") && doc.documentElement().hasAttribute("height")) {
321         int doc_width = doc.documentElement().attribute("width").toInt();
322         int doc_height = doc.documentElement().attribute("height").toInt();
323         if (doc_width != m_width || doc_height != m_height) {
324             KMessageBox::information(kapp->activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
325             //TODO: convert using QTransform
326             m_width = doc_width;
327             m_height = doc_height;
328         }
329     } else {
330         // Document has no size info, it is likely an old version title, so ignore viewport data
331         QDomNodeList viewportlist = doc.documentElement().elementsByTagName("startviewport");
332         if (!viewportlist.isEmpty()) {
333             doc.documentElement().removeChild(viewportlist.at(0));
334         }
335         viewportlist = doc.documentElement().elementsByTagName("endviewport");
336         if (!viewportlist.isEmpty()) {
337             doc.documentElement().removeChild(viewportlist.at(0));
338         }
339     }
340     //TODO: get default title duration instead of hardcoded one
341     if (doc.documentElement().hasAttribute("duration"))
342         *duration = doc.documentElement().attribute("duration").toInt();
343     else if (doc.documentElement().hasAttribute("out"))
344         *duration = doc.documentElement().attribute("out").toInt();
345     else
346         *duration = 125;
347
348     int maxZValue = 0;
349     if (titles.size()) {
350
351         QDomNodeList items = titles.item(0).childNodes();
352         for (int i = 0; i < items.count(); ++i) {
353             QGraphicsItem *gitem = NULL;
354             kDebug() << items.item(i).attributes().namedItem("type").nodeValue();
355             int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
356             if (zValue > -1000) {
357                 if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
358                     QDomNamedNodeMap txtProperties = items.item(i).namedItem("content").attributes();
359                     QFont font(txtProperties.namedItem("font").nodeValue());
360
361                     QDomNode node = txtProperties.namedItem("font-bold");
362                     if (!node.isNull()) {
363                         // Old: Bold/Not bold.
364                         font.setBold(node.nodeValue().toInt());
365                     } else {
366                         // New: Font weight (QFont::)
367                         font.setWeight(txtProperties.namedItem("font-weight").nodeValue().toInt());
368                     }
369                     //font.setBold(txtProperties.namedItem("font-bold").nodeValue().toInt());
370                     font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt());
371                     font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt());
372                     // Older Kdenlive version did not store pixel size but point size
373                     if (txtProperties.namedItem("font-pixel-size").isNull()) {
374                         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"));
375                         QFont f2;
376                         f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt());
377                         font.setPixelSize(QFontInfo(f2).pixelSize());
378                     } else
379                         font.setPixelSize(txtProperties.namedItem("font-pixel-size").nodeValue().toInt());
380                     QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue()));
381                     QGraphicsTextItem *txt = m_scene->addText(items.item(i).namedItem("content").firstChild().nodeValue(), font);
382                     QTextCursor cursor(txt->document());
383                     cursor.select(QTextCursor::Document);
384                     QTextCharFormat format;
385                     if (txtProperties.namedItem("font-outline").nodeValue().toDouble() > 0.0) {
386                         txt->setData(101, txtProperties.namedItem("font-outline").nodeValue().toDouble());
387                         txt->setData(102, stringToColor(txtProperties.namedItem("font-outline-color").nodeValue()));
388                         format.setTextOutline(
389                             QPen(QColor(stringToColor(txtProperties.namedItem("font-outline-color").nodeValue())),
390                                  txtProperties.namedItem("font-outline").nodeValue().toDouble(),
391                                  Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)
392                         );
393
394                     }
395                     format.setForeground(QBrush(col));
396                     cursor.mergeCharFormat(format);
397                     txt->setTextInteractionFlags(Qt::NoTextInteraction);
398                     if (txtProperties.namedItem("alignment").isNull() == false) {
399                         txt->setTextWidth(txt->boundingRect().width());
400                         QTextCursor cur = txt->textCursor();
401                         QTextBlockFormat format = cur.blockFormat();
402                         format.setAlignment((Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt());
403                         cur.select(QTextCursor::Document);
404                         cur.setBlockFormat(format);
405                         txt->setTextCursor(cur);
406                         cur.clearSelection();
407                         txt->setTextCursor(cur);
408                     }
409
410                     if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) {
411                         txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt());
412                     }
413                     if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) {
414                         txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt());
415                     }
416
417                     // Effects
418                     if (!txtProperties.namedItem("typewriter").isNull()) {
419                         QStringList effData = QStringList() << "typewriter" << txtProperties.namedItem("typewriter").nodeValue();
420                         txt->setData(100, effData);
421                     }
422
423                     gitem = txt;
424                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsRectItem") {
425                     QString rect = items.item(i).namedItem("content").attributes().namedItem("rect").nodeValue();
426                     QString br_str = items.item(i).namedItem("content").attributes().namedItem("brushcolor").nodeValue();
427                     QString pen_str = items.item(i).namedItem("content").attributes().namedItem("pencolor").nodeValue();
428                     double penwidth = items.item(i).namedItem("content").attributes().namedItem("penwidth").nodeValue().toDouble();
429                     QGraphicsRectItem *rec = m_scene->addRect(stringToRect(rect), QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin), QBrush(stringToColor(br_str)));
430                     gitem = rec;
431                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsPixmapItem") {
432                     QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
433                     QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
434                     QPixmap pix;
435                     if (base64.isEmpty()) {
436                         pix.load(url);
437                     } else {
438                         pix.loadFromData(QByteArray::fromBase64(base64.toAscii()));
439                     }
440                     QGraphicsPixmapItem *rec = m_scene->addPixmap(pix);
441                     rec->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
442                     rec->setData(Qt::UserRole, url);
443                     if (!base64.isEmpty()) {
444                         rec->setData(Qt::UserRole + 1, base64);
445                     }
446                     gitem = rec;
447                 } else if (items.item(i).attributes().namedItem("type").nodeValue() == "QGraphicsSvgItem") {
448                     QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue();
449                     QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue();
450                     QGraphicsSvgItem *rec = NULL;
451                     if (base64.isEmpty()) {
452                         rec = new QGraphicsSvgItem(url);
453                     } else {
454                         rec = new QGraphicsSvgItem();
455                         QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toAscii()), rec);
456                         rec->setSharedRenderer(renderer);
457                         //QString elem=rec->elementId();
458                         //QRectF bounds = renderer->boundsOnElement(elem);
459                     }
460                     if (rec) {
461                         m_scene->addItem(rec);
462                         rec->setData(Qt::UserRole, url);
463                         if (!base64.isEmpty()) {
464                             rec->setData(Qt::UserRole + 1, base64);
465                         }
466                         gitem = rec;
467                     }
468                 }
469             }
470             //pos and transform
471             if (gitem) {
472                 QPointF p(items.item(i).namedItem("position").attributes().namedItem("x").nodeValue().toDouble(),
473                           items.item(i).namedItem("position").attributes().namedItem("y").nodeValue().toDouble());
474                 gitem->setPos(p);
475                 QDomElement trans = items.item(i).namedItem("position").firstChild().toElement();
476                 gitem->setTransform(stringToTransform(trans.firstChild().nodeValue()));
477                 QString rotate = trans.attribute("rotation");
478                 if (!rotate.isEmpty()) gitem->setData(ROTATEFACTOR, stringToList(rotate));
479                 QString zoom = trans.attribute("zoom");
480                 if (!zoom.isEmpty()) gitem->setData(ZOOMFACTOR, zoom.toInt());
481                 int zValue = items.item(i).attributes().namedItem("z-index").nodeValue().toInt();
482                 if (zValue > maxZValue) maxZValue = zValue;
483                 gitem->setZValue(zValue);
484                 gitem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
485
486 #if QT_VERSION >= 0x040600
487                 // effects
488                 QDomNode eff = items.item(i).namedItem("effect");
489                 if (!eff.isNull()) {
490                     QDomElement e = eff.toElement();
491                     if (e.attribute("type") == "blur") {
492                         QGraphicsBlurEffect *blur = new QGraphicsBlurEffect();
493                         blur->setBlurRadius(e.attribute("blurradius").toInt());
494                         gitem->setGraphicsEffect(blur);
495                     } else if (e.attribute("type") == "shadow") {
496                         QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect();
497                         shadow->setBlurRadius(e.attribute("blurradius").toInt());
498                         shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt());
499                         gitem->setGraphicsEffect(shadow);
500                     }
501                 }
502 #endif
503             }
504
505             if (items.item(i).nodeName() == "background") {
506                 kDebug() << items.item(i).attributes().namedItem("color").nodeValue();
507                 QColor color = QColor(stringToColor(items.item(i).attributes().namedItem("color").nodeValue()));
508                 //color.setAlpha(items.item(i).attributes().namedItem("alpha").nodeValue().toInt());
509                 QList<QGraphicsItem *> items = m_scene->items();
510                 for (int i = 0; i < items.size(); ++i) {
511                     if (items.at(i)->zValue() == -1100) {
512                         ((QGraphicsRectItem *)items.at(i))->setBrush(QBrush(color));
513                         break;
514                     }
515                 }
516             } else if (items.item(i).nodeName() == "startviewport" && startv) {
517                 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
518                 QRectF r = stringToRect(rect);
519                 startv->setRect(0, 0, r.width(), r.height());
520                 startv->setPos(r.topLeft());
521             } else if (items.item(i).nodeName() == "endviewport" && endv) {
522                 QString rect = items.item(i).attributes().namedItem("rect").nodeValue();
523                 QRectF r = stringToRect(rect);
524                 endv->setRect(0, 0, r.width(), r.height());
525                 endv->setPos(r.topLeft());
526             }
527         }
528     }
529     return maxZValue;
530 }
531
532 QString TitleDocument::colorToString(const QColor& c)
533 {
534     QString ret = "%1,%2,%3,%4";
535     ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
536     return ret;
537 }
538
539 QString TitleDocument::rectFToString(const QRectF& c)
540 {
541     QString ret = "%1,%2,%3,%4";
542     ret = ret.arg(c.left()).arg(c.top()).arg(c.width()).arg(c.height());
543     return ret;
544 }
545
546 QRectF TitleDocument::stringToRect(const QString & s)
547 {
548
549     QStringList l = s.split(',');
550     if (l.size() < 4)
551         return QRectF();
552     return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()).normalized();
553 }
554
555 QColor TitleDocument::stringToColor(const QString & s)
556 {
557     QStringList l = s.split(',');
558     if (l.size() < 4)
559         return QColor();
560     return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt());;
561 }
562
563 QTransform TitleDocument::stringToTransform(const QString& s)
564 {
565     QStringList l = s.split(QLatin1Char(','));
566     if (l.size() < 9)
567         return QTransform();
568     return QTransform(
569                l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(),
570                l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(),
571                l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()
572            );
573 }
574
575 QList<QVariant> TitleDocument::stringToList(const QString & s)
576 {
577     QStringList l = s.split(',');
578     if (l.size() < 3)
579         return QList<QVariant>();
580     return QList<QVariant>() << QVariant(l.at(0).toDouble()) << QVariant(l.at(1).toDouble()) << QVariant(l.at(2).toDouble());
581 }
582
583 int TitleDocument::frameWidth() const
584 {
585     return m_width;
586 }
587
588 int TitleDocument::frameHeight() const
589 {
590     return m_height;
591 }
592
593