]> git.sesse.net Git - kdenlive/blob - src/documentvalidator.cpp
Merge branch 'master' into stabilize
[kdenlive] / src / documentvalidator.cpp
1 /***************************************************************************
2  *   Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
3  *                                                                         *
4  *   This program is free software; you can redistribute it and/or modify  *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This program is distributed in the hope that it will be useful,       *
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
12  *   GNU General Public License for more details.                          *
13  *                                                                         *
14  *   You should have received a copy of the GNU General Public License     *
15  *   along with this program; if not, write to the                         *
16  *   Free Software Foundation, Inc.,                                       *
17  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
18  ***************************************************************************/
19
20
21 #include "documentvalidator.h"
22 #include "definitions.h"
23 #include "initeffects.h"
24 #include "mainwindow.h"
25
26 #include <KDebug>
27 #include <KMessageBox>
28 #include <KApplication>
29 #include <KLocale>
30 #include <KUrl>
31 #include <KStandardDirs>
32
33 #include <QFile>
34 #include <QColor>
35 #include <QString>
36 #include <QDir>
37 #include <QScriptEngine>
38
39 #include <mlt++/Mlt.h>
40
41 #include "locale.h"
42
43
44 DocumentValidator::DocumentValidator(QDomDocument doc):
45         m_doc(doc),
46         m_modified(false)
47 {}
48
49 bool DocumentValidator::validate(const double currentVersion)
50 {
51     QDomElement mlt = m_doc.firstChildElement("mlt");
52     // At least the root element must be there
53     if (mlt.isNull())
54         return false;
55
56     QDomElement kdenliveDoc = mlt.firstChildElement("kdenlivedoc");
57     // Check if we're validating a Kdenlive project
58     if (kdenliveDoc.isNull())
59         return false;
60
61     // Previous MLT / Kdenlive versions used C locale by default
62     QLocale documentLocale = QLocale::c();
63     
64     if (mlt.hasAttribute("LC_NUMERIC")) {
65         // Set locale for the document
66         // WARNING: what should be done in case the locale does not exist on the system?
67         setlocale(LC_NUMERIC, mlt.attribute("LC_NUMERIC").toUtf8().constData());
68         documentLocale = QLocale(mlt.attribute("LC_NUMERIC"));
69         
70         // Make sure Qt locale and C++ locale have the same numeric separator, might not be the case
71         // With some locales since C++ and Qt use a different database for locales
72         char *separator = localeconv()->decimal_point;
73         if (separator != documentLocale.decimalPoint()) {
74             kDebug()<<"------\n!!! system locale is not similar to Qt's locale... be prepared for bugs!!!\n------";
75             // HACK: There is a locale conflict, so set locale to at least have correct decimal point
76             if (strncmp(separator, ".", 1) == 0) documentLocale = QLocale::c();
77             else if (strncmp(separator, ",", 1) == 0) documentLocale = QLocale("fr_FR.UTF-8");
78         }
79     }
80     
81     documentLocale.setNumberOptions(QLocale::OmitGroupSeparator);
82     if (documentLocale.decimalPoint() != QLocale().decimalPoint()) {
83         // If loading an older MLT file without LC_NUMERIC, set locale to C which was previously the default
84         if (!mlt.hasAttribute("LC_NUMERIC")) setlocale(LC_NUMERIC, "C");
85
86         QLocale::setDefault(documentLocale);
87         // locale conversion might need to be redone
88         initEffects::parseEffectFiles();
89     }
90
91     // Upgrade the document to the latest version
92     if (!upgrade(documentLocale.toDouble(kdenliveDoc.attribute("version")), currentVersion))
93         return false;
94
95     /*
96      * Check the syntax (this will be replaced by XSD validation with Qt 4.6)
97      * and correct some errors
98      */
99     {
100         // Return (or create) the tractor
101         QDomElement tractor = mlt.firstChildElement("tractor");
102         if (tractor.isNull()) {
103             m_modified = true;
104             tractor = m_doc.createElement("tractor");
105             tractor.setAttribute("global_feed", "1");
106             tractor.setAttribute("in", "0");
107             tractor.setAttribute("out", "-1");
108             tractor.setAttribute("id", "maintractor");
109             mlt.appendChild(tractor);
110         }
111
112         /*
113          * Make sure at least one track exists, and they're equal in number to
114          * to the maximum between MLT and Kdenlive playlists and tracks
115          */
116         QDomNodeList playlists = m_doc.elementsByTagName("playlist");
117         int tracksMax = playlists.count() - 1; // Remove the black track
118         QDomNodeList tracks = tractor.elementsByTagName("track");
119         tracksMax = qMax(tracks.count() - 1, tracksMax);
120         QDomNodeList tracksinfo = kdenliveDoc.elementsByTagName("trackinfo");
121         tracksMax = qMax(tracksinfo.count(), tracksMax);
122         tracksMax = qMax(1, tracksMax); // Force existance of one track
123         if (playlists.count() - 1 < tracksMax ||
124                 tracks.count() - 1 < tracksMax ||
125                 tracksinfo.count() < tracksMax) {
126             kDebug() << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK";
127             m_modified = true;
128             int difference;
129             // use the MLT tracks as reference
130             if (tracks.count() - 1 < tracksMax) {
131                 // Looks like one MLT track is missing, remove the extra Kdenlive track if there is one.
132                 if (tracksinfo.count() != tracks.count() - 1) {
133                     // The Kdenlive tracks are not ok, clear and rebuild them
134                     QDomNode tinfo = kdenliveDoc.firstChildElement("tracksinfo");
135                     QDomNode tnode = tinfo.firstChild();
136                     while (!tnode.isNull()) {
137                         tinfo.removeChild(tnode);
138                         tnode = tinfo.firstChild();
139                     }
140
141                     for (int i = 1; i < tracks.count(); i++) {
142                         QString hide = tracks.at(i).toElement().attribute("hide");
143                         QDomElement newTrack = m_doc.createElement("trackinfo");
144                         if (hide == "video") {
145                             // audio track;
146                             newTrack.setAttribute("type", "audio");
147                             newTrack.setAttribute("blind", 1);
148                             newTrack.setAttribute("mute", 0);
149                             newTrack.setAttribute("lock", 0);
150                         } else {
151                             newTrack.setAttribute("blind", 0);
152                             newTrack.setAttribute("mute", 0);
153                             newTrack.setAttribute("lock", 0);
154                         }
155                         tinfo.appendChild(newTrack);
156                     }
157                 }
158             }
159
160             if (playlists.count() - 1 < tracksMax) {
161                 difference = tracksMax - (playlists.count() - 1);
162                 for (int i = 0; i < difference; ++i) {
163                     QDomElement playlist = m_doc.createElement("playlist");
164                     mlt.appendChild(playlist);
165                 }
166             }
167             if (tracks.count() - 1 < tracksMax) {
168                 difference = tracksMax - (tracks.count() - 1);
169                 for (int i = 0; i < difference; ++i) {
170                     QDomElement track = m_doc.createElement("track");
171                     tractor.appendChild(track);
172                 }
173             }
174             if (tracksinfo.count() < tracksMax) {
175                 QDomElement tracksinfoElm = kdenliveDoc.firstChildElement("tracksinfo");
176                 if (tracksinfoElm.isNull()) {
177                     tracksinfoElm = m_doc.createElement("tracksinfo");
178                     kdenliveDoc.appendChild(tracksinfoElm);
179                 }
180                 difference = tracksMax - tracksinfo.count();
181                 for (int i = 0; i < difference; ++i) {
182                     QDomElement trackinfo = m_doc.createElement("trackinfo");
183                     trackinfo.setAttribute("mute", "0");
184                     trackinfo.setAttribute("locked", "0");
185                     tracksinfoElm.appendChild(trackinfo);
186                 }
187             }
188         }        
189
190         // TODO: check the tracks references
191         // TODO: check internal mix transitions
192         
193     }
194
195     updateEffects();
196
197     return true;
198 }
199
200 bool DocumentValidator::upgrade(double version, const double currentVersion)
201 {
202     kDebug() << "Opening a document with version " << version;
203
204     // No conversion needed
205     if (version == currentVersion) {
206         return true;
207     }
208
209     // The document is too new
210     if (version > currentVersion) {
211         kDebug() << "Unable to open document with version " << version;
212         KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.\nPlease consider upgrading your Kdenlive version.", version), i18n("Unable to open project"));
213         return false;
214     }
215
216     // Unsupported document versions
217     if (version == 0.5 || version == 0.7) {
218         kDebug() << "Unable to open document with version " << version;
219         KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project"));
220         return false;
221     }
222
223     // <kdenlivedoc />
224     QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
225     QDomElement infoXml = infoXmlNode.toElement();
226     infoXml.setAttribute("upgraded", "1");
227
228     if (version <= 0.6) {
229         QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
230         QDomNode westley = m_doc.elementsByTagName("westley").at(1);
231         QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
232         QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
233         QDomNodeList playlists = m_doc.elementsByTagName("playlist");
234
235         QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
236         QString profile = props.toElement().attribute("videoprofile");
237         int startPos = props.toElement().attribute("timeline_position").toInt();
238         if (profile == "dv_wide")
239             profile = "dv_pal_wide";
240
241         // move playlists outside of tractor and add the tracks instead
242         int max = playlists.count();
243         if (westley.isNull()) {
244             westley = m_doc.createElement("westley");
245             m_doc.documentElement().appendChild(westley);
246         }
247         if (tractor.isNull()) {
248             kDebug() << "// NO MLT PLAYLIST, building empty one";
249             QDomElement blank_tractor = m_doc.createElement("tractor");
250             westley.appendChild(blank_tractor);
251             QDomElement blank_playlist = m_doc.createElement("playlist");
252             blank_playlist.setAttribute("id", "black_track");
253             westley.insertBefore(blank_playlist, QDomNode());
254             QDomElement blank_track = m_doc.createElement("track");
255             blank_track.setAttribute("producer", "black_track");
256             blank_tractor.appendChild(blank_track);
257
258             QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
259             for (int i = 0; i < kdenlivetracks.count(); i++) {
260                 blank_playlist = m_doc.createElement("playlist");
261                 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
262                 westley.insertBefore(blank_playlist, QDomNode());
263                 blank_track = m_doc.createElement("track");
264                 blank_track.setAttribute("producer", "playlist" + QString::number(i));
265                 blank_tractor.appendChild(blank_track);
266                 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
267                     blank_playlist.setAttribute("hide", "video");
268                     blank_track.setAttribute("hide", "video");
269                 }
270             }
271         } else for (int i = 0; i < max; i++) {
272                 QDomNode n = playlists.at(i);
273                 westley.insertBefore(n, QDomNode());
274                 QDomElement pl = n.toElement();
275                 QDomElement track = m_doc.createElement("track");
276                 QString trackType = pl.attribute("hide");
277                 if (!trackType.isEmpty())
278                     track.setAttribute("hide", trackType);
279                 QString playlist_id =  pl.attribute("id");
280                 if (playlist_id.isEmpty()) {
281                     playlist_id = "black_track";
282                     pl.setAttribute("id", playlist_id);
283                 }
284                 track.setAttribute("producer", playlist_id);
285                 //tractor.appendChild(track);
286 #define KEEP_TRACK_ORDER 1
287 #ifdef KEEP_TRACK_ORDER
288                 tractor.insertAfter(track, QDomNode());
289 #else
290                 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
291                 // insertion sort - O( tracks*tracks )
292                 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
293                 QDomElement tractor_elem = tractor.toElement();
294                 if (! tractor_elem.isNull()) {
295                     QDomNodeList tracks = tractor_elem.elementsByTagName("track");
296                     int size = tracks.size();
297                     if (size == 0) {
298                         tractor.insertAfter(track, QDomNode());
299                     } else {
300                         bool inserted = false;
301                         for (int i = 0; i < size; ++i) {
302                             QDomElement track_elem = tracks.at(i).toElement();
303                             if (track_elem.isNull()) {
304                                 tractor.insertAfter(track, QDomNode());
305                                 inserted = true;
306                                 break;
307                             } else {
308                                 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
309                                 if (playlist_id < track_elem.attribute("producer")) {
310                                     tractor.insertBefore(track, track_elem);
311                                     inserted = true;
312                                     break;
313                                 }
314                             }
315                         }
316                         // Reach here, no insertion, insert last
317                         if (!inserted) {
318                             tractor.insertAfter(track, QDomNode());
319                         }
320                     }
321                 } else {
322                     kWarning() << "tractor was not a QDomElement";
323                     tractor.insertAfter(track, QDomNode());
324                 }
325 #endif
326             }
327         tractor.removeChild(multitrack);
328
329         // audio track mixing transitions should not be added to track view, so add required attribute
330         QDomNodeList transitions = m_doc.elementsByTagName("transition");
331         max = transitions.count();
332         for (int i = 0; i < max; i++) {
333             QDomElement tr = transitions.at(i).toElement();
334             if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
335                 QDomElement property = m_doc.createElement("property");
336                 property.setAttribute("name", "internal_added");
337                 QDomText value = m_doc.createTextNode("237");
338                 property.appendChild(value);
339                 tr.appendChild(property);
340                 property = m_doc.createElement("property");
341                 property.setAttribute("name", "mlt_service");
342                 value = m_doc.createTextNode("mix");
343                 property.appendChild(value);
344                 tr.appendChild(property);
345             } else {
346                 // convert transition
347                 QDomNamedNodeMap attrs = tr.attributes();
348                 for (int j = 0; j < attrs.count(); j++) {
349                     QString attrName = attrs.item(j).nodeName();
350                     if (attrName != "in" && attrName != "out" && attrName != "id") {
351                         QDomElement property = m_doc.createElement("property");
352                         property.setAttribute("name", attrName);
353                         QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
354                         property.appendChild(value);
355                         tr.appendChild(property);
356                     }
357                 }
358             }
359         }
360
361         // move transitions after tracks
362         for (int i = 0; i < max; i++) {
363             tractor.insertAfter(transitions.at(0), QDomNode());
364         }
365
366         // Fix filters format
367         QDomNodeList entries = m_doc.elementsByTagName("entry");
368         max = entries.count();
369         for (int i = 0; i < max; i++) {
370             QString last_id;
371             int effectix = 0;
372             QDomNode m = entries.at(i).firstChild();
373             while (!m.isNull()) {
374                 if (m.toElement().tagName() == "filter") {
375                     QDomElement filt = m.toElement();
376                     QDomNamedNodeMap attrs = filt.attributes();
377                     QString current_id = filt.attribute("kdenlive_id");
378                     if (current_id != last_id) {
379                         effectix++;
380                         last_id = current_id;
381                     }
382                     QDomElement e = m_doc.createElement("property");
383                     e.setAttribute("name", "kdenlive_ix");
384                     QDomText value = m_doc.createTextNode(QString::number(effectix));
385                     e.appendChild(value);
386                     filt.appendChild(e);
387                     for (int j = 0; j < attrs.count(); j++) {
388                         QDomAttr a = attrs.item(j).toAttr();
389                         if (!a.isNull()) {
390                             kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
391                             QDomElement e = m_doc.createElement("property");
392                             e.setAttribute("name", a.name());
393                             QDomText value = m_doc.createTextNode(a.value());
394                             e.appendChild(value);
395                             filt.appendChild(e);
396
397                         }
398                     }
399                 }
400                 m = m.nextSibling();
401             }
402         }
403
404         /*
405             QDomNodeList filters = m_doc.elementsByTagName("filter");
406             max = filters.count();
407             QString last_id;
408             int effectix = 0;
409             for (int i = 0; i < max; i++) {
410                 QDomElement filt = filters.at(i).toElement();
411                 QDomNamedNodeMap attrs = filt.attributes();
412         QString current_id = filt.attribute("kdenlive_id");
413         if (current_id != last_id) {
414             effectix++;
415             last_id = current_id;
416         }
417         QDomElement e = m_doc.createElement("property");
418                 e.setAttribute("name", "kdenlive_ix");
419                 QDomText value = m_doc.createTextNode(QString::number(1));
420                 e.appendChild(value);
421                 filt.appendChild(e);
422                 for (int j = 0; j < attrs.count(); j++) {
423                     QDomAttr a = attrs.item(j).toAttr();
424                     if (!a.isNull()) {
425                         kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
426                         QDomElement e = m_doc.createElement("property");
427                         e.setAttribute("name", a.name());
428                         QDomText value = m_doc.createTextNode(a.value());
429                         e.appendChild(value);
430                         filt.appendChild(e);
431                     }
432                 }
433             }*/
434
435         // fix slowmotion
436         QDomNodeList producers = westley.toElement().elementsByTagName("producer");
437         max = producers.count();
438         for (int i = 0; i < max; i++) {
439             QDomElement prod = producers.at(i).toElement();
440             if (prod.attribute("mlt_service") == "framebuffer") {
441                 QString slowmotionprod = prod.attribute("resource");
442                 slowmotionprod.replace(':', '?');
443                 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
444                 prod.setAttribute("resource", slowmotionprod);
445             }
446         }
447         // move producers to correct place, markers to a global list, fix clip descriptions
448         QDomElement markers = m_doc.createElement("markers");
449         // This will get the xml producers:
450         producers = m_doc.elementsByTagName("producer");
451         max = producers.count();
452         for (int i = 0; i < max; i++) {
453             QDomElement prod = producers.at(0).toElement();
454             // add resource also as a property (to allow path correction in setNewResource())
455             // TODO: will it work with slowmotion? needs testing
456             /*if (!prod.attribute("resource").isEmpty()) {
457                 QDomElement prop_resource = m_doc.createElement("property");
458                 prop_resource.setAttribute("name", "resource");
459                 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
460                 prop_resource.appendChild(resource);
461                 prod.appendChild(prop_resource);
462             }*/
463             QDomNode m = prod.firstChild();
464             if (!m.isNull()) {
465                 if (m.toElement().tagName() == "markers") {
466                     QDomNodeList prodchilds = m.childNodes();
467                     int maxchild = prodchilds.count();
468                     for (int k = 0; k < maxchild; k++) {
469                         QDomElement mark = prodchilds.at(0).toElement();
470                         mark.setAttribute("id", prod.attribute("id"));
471                         markers.insertAfter(mark, QDomNode());
472                     }
473                     prod.removeChild(m);
474                 } else if (prod.attribute("type").toInt() == TEXT) {
475                     // convert title clip
476                     if (m.toElement().tagName() == "textclip") {
477                         QDomDocument tdoc;
478                         QDomElement titleclip = m.toElement();
479                         QDomElement title = tdoc.createElement("kdenlivetitle");
480                         tdoc.appendChild(title);
481                         QDomNodeList objects = titleclip.childNodes();
482                         int maxchild = objects.count();
483                         for (int k = 0; k < maxchild; k++) {
484                             QString objectxml;
485                             QDomElement ob = objects.at(k).toElement();
486                             if (ob.attribute("type") == "3") {
487                                 // text object - all of this goes into "xmldata"...
488                                 QDomElement item = tdoc.createElement("item");
489                                 item.setAttribute("z-index", ob.attribute("z"));
490                                 item.setAttribute("type", "QGraphicsTextItem");
491                                 QDomElement position = tdoc.createElement("position");
492                                 position.setAttribute("x", ob.attribute("x"));
493                                 position.setAttribute("y", ob.attribute("y"));
494                                 QDomElement content = tdoc.createElement("content");
495                                 content.setAttribute("font", ob.attribute("font_family"));
496                                 content.setAttribute("font-size", ob.attribute("font_size"));
497                                 content.setAttribute("font-bold", ob.attribute("bold"));
498                                 content.setAttribute("font-italic", ob.attribute("italic"));
499                                 content.setAttribute("font-underline", ob.attribute("underline"));
500                                 QString col = ob.attribute("color");
501                                 QColor c(col);
502                                 content.setAttribute("font-color", colorToString(c));
503                                 // todo: These fields are missing from the newly generated xmldata:
504                                 // transform, startviewport, endviewport, background
505
506                                 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
507                                 content.appendChild(conttxt);
508                                 item.appendChild(position);
509                                 item.appendChild(content);
510                                 title.appendChild(item);
511                             } else if (ob.attribute("type") == "5") {
512                                 // rectangle object
513                                 QDomElement item = tdoc.createElement("item");
514                                 item.setAttribute("z-index", ob.attribute("z"));
515                                 item.setAttribute("type", "QGraphicsRectItem");
516                                 QDomElement position = tdoc.createElement("position");
517                                 position.setAttribute("x", ob.attribute("x"));
518                                 position.setAttribute("y", ob.attribute("y"));
519                                 QDomElement content = tdoc.createElement("content");
520                                 QString col = ob.attribute("color");
521                                 QColor c(col);
522                                 content.setAttribute("brushcolor", colorToString(c));
523                                 QString rect = "0,0,";
524                                 rect.append(ob.attribute("width"));
525                                 rect.append(",");
526                                 rect.append(ob.attribute("height"));
527                                 content.setAttribute("rect", rect);
528                                 item.appendChild(position);
529                                 item.appendChild(content);
530                                 title.appendChild(item);
531                             }
532                         }
533                         prod.setAttribute("xmldata", tdoc.toString());
534                         // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
535                         // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
536                         // prod.setAttribute("titlename", titleInfo.at(0));
537                         // prod.setAttribute("resource", titleInfo.at(1));
538                         //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
539                         prod.removeChild(m);
540                     } // End conversion of title clips.
541
542                 } else if (m.isText()) {
543                     QString comment = m.nodeValue();
544                     if (!comment.isEmpty()) {
545                         prod.setAttribute("description", comment);
546                     }
547                     prod.removeChild(m);
548                 }
549             }
550             int duration = prod.attribute("duration").toInt();
551             if (duration > 0) prod.setAttribute("out", QString::number(duration));
552             // The clip goes back in, but text clips should not go back in, at least not modified
553             westley.insertBefore(prod, QDomNode());
554         }
555
556         QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
557         if (!markers.firstChild().isNull()) westley0.appendChild(markers);
558
559         /*
560          * Convert as much of the kdenlivedoc as possible. Use the producer in
561          * westley. First, remove the old stuff from westley, and add a new
562          * empty one. Also, track the max id in order to use it for the adding
563          * of groups/folders
564          */
565         int max_kproducer_id = 0;
566         westley0.removeChild(infoXmlNode);
567         QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
568         infoXml_new.setAttribute("profile", profile);
569         infoXml.setAttribute("position", startPos);
570
571         // Add all the producers that has a resource in westley
572         QDomElement westley_element = westley0.toElement();
573         if (westley_element.isNull()) {
574             kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
575         } else {
576             QDomNodeList wproducers = westley_element.elementsByTagName("producer");
577             int kmax = wproducers.count();
578             for (int i = 0; i < kmax; i++) {
579                 QDomElement wproducer = wproducers.at(i).toElement();
580                 if (wproducer.isNull()) {
581                     kWarning() << "Found producer in westley0, that was not a QDomElement";
582                     continue;
583                 }
584                 if (wproducer.attribute("id") == "black") continue;
585                 // We have to do slightly different things, depending on the type
586                 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
587                 if (wproducer.attribute("type").toInt() == TEXT) {
588                     kDebug() << "Found TEXT element in producer" << endl;
589                     QDomElement kproducer = wproducer.cloneNode(true).toElement();
590                     kproducer.setTagName("kdenlive_producer");
591                     infoXml_new.appendChild(kproducer);
592                     /*
593                      * TODO: Perhaps needs some more changes here to
594                      * "frequency", aspect ratio as a float, frame_size,
595                      * channels, and later, resource and title name
596                      */
597                 } else {
598                     QDomElement kproducer = m_doc.createElement("kdenlive_producer");
599                     kproducer.setAttribute("id", wproducer.attribute("id"));
600                     if (!wproducer.attribute("description").isEmpty())
601                         kproducer.setAttribute("description", wproducer.attribute("description"));
602                     kproducer.setAttribute("resource", wproducer.attribute("resource"));
603                     kproducer.setAttribute("type", wproducer.attribute("type"));
604                     // Testing fix for 358
605                     if (!wproducer.attribute("aspect_ratio").isEmpty()) {
606                         kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
607                     }
608                     if (!wproducer.attribute("source_fps").isEmpty()) {
609                         kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
610                     }
611                     if (!wproducer.attribute("length").isEmpty()) {
612                         kproducer.setAttribute("duration", wproducer.attribute("length"));
613                     }
614                     infoXml_new.appendChild(kproducer);
615                 }
616                 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
617                     max_kproducer_id = wproducer.attribute("id").toInt();
618                 }
619             }
620         }
621 #define LOOKUP_FOLDER 1
622 #ifdef LOOKUP_FOLDER
623         /*
624          * Look through all the folder elements of the old doc, for each folder,
625          * for each producer, get the id, look it up in the new doc, set the
626          * groupname and groupid. Note, this does not work at the moment - at
627          * least one folder shows up missing, and clips with no folder does not
628          * show up.
629          */
630         //QDomElement infoXml_old = infoXmlNode.toElement();
631         if (!infoXml_old.isNull()) {
632             QDomNodeList folders = infoXml_old.elementsByTagName("folder");
633             int fsize = folders.size();
634             int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
635             for (int i = 0; i < fsize; ++i) {
636                 QDomElement folder = folders.at(i).toElement();
637                 if (!folder.isNull()) {
638                     QString groupName = folder.attribute("name");
639                     kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
640                     QDomNodeList fproducers = folder.elementsByTagName("producer");
641                     int psize = fproducers.size();
642                     for (int j = 0; j < psize; ++j) {
643                         QDomElement fproducer = fproducers.at(j).toElement();
644                         if (!fproducer.isNull()) {
645                             QString id = fproducer.attribute("id");
646                             // This is not very effective, but compared to loading the clips, its a breeze
647                             QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
648                             int kpsize = kdenlive_producers.size();
649                             for (int k = 0; k < kpsize; ++k) {
650                                 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
651                                 if (id == kproducer.attribute("id")) {
652                                     // We do not check that it already is part of a folder
653                                     kproducer.setAttribute("groupid", groupId);
654                                     kproducer.setAttribute("groupname", groupName);
655                                     break;
656                                 }
657                             }
658                         }
659                     }
660                     ++groupId;
661                 }
662             }
663         }
664 #endif
665         QDomNodeList elements = westley.childNodes();
666         max = elements.count();
667         for (int i = 0; i < max; i++) {
668             QDomElement prod = elements.at(0).toElement();
669             westley0.insertAfter(prod, QDomNode());
670         }
671
672         westley0.appendChild(infoXml_new);
673
674         westley0.removeChild(westley);
675
676         // adds <avfile /> information to <kdenlive_producer />
677         QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
678         QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
679         kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
680         for (int i = 0; i < avfiles.count(); ++i) {
681             QDomElement avfile = avfiles.at(i).toElement();
682             QDomElement kproducer;
683             if (avfile.isNull())
684                 kWarning() << "found an <avfile /> that is not a QDomElement";
685             else {
686                 QString id = avfile.attribute("id");
687                 // this is horrible, must be rewritten, it's just for test
688                 for (int j = 0; j < kproducers.count(); ++j) {
689                     //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
690                     if (kproducers.at(j).toElement().attribute("id") == id) {
691                         kproducer = kproducers.at(j).toElement();
692                         break;
693                     }
694                 }
695                 if (kproducer == QDomElement())
696                     kWarning() << "no match for <avfile /> with id =" << id;
697                 else {
698                     //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
699                     kproducer.setAttribute("channels", avfile.attribute("channels"));
700                     kproducer.setAttribute("duration", avfile.attribute("duration"));
701                     kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
702                     kproducer.setAttribute("frequency", avfile.attribute("frequency"));
703                     if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
704                         kproducer.setAttribute("description", avfile.attribute("description"));
705                 }
706             }
707         }
708         infoXml = infoXml_new;
709     }
710
711     if (version <= 0.81) {
712         // Add the tracks information
713         QString tracksOrder = infoXml.attribute("tracks");
714         if (tracksOrder.isEmpty()) {
715             QDomNodeList tracks = m_doc.elementsByTagName("track");
716             for (int i = 0; i < tracks.count(); i++) {
717                 QDomElement track = tracks.at(i).toElement();
718                 if (track.attribute("producer") != "black_track") {
719                     if (track.attribute("hide") == "video")
720                         tracksOrder.append('a');
721                     else
722                         tracksOrder.append('v');
723                 }
724             }
725         }
726         QDomElement tracksinfo = m_doc.createElement("tracksinfo");
727         for (int i = 0; i < tracksOrder.size(); i++) {
728             QDomElement trackinfo = m_doc.createElement("trackinfo");
729             if (tracksOrder.data()[i] == 'a') {
730                 trackinfo.setAttribute("type", "audio");
731                 trackinfo.setAttribute("blind", true);
732             } else
733                 trackinfo.setAttribute("blind", false);
734             trackinfo.setAttribute("mute", false);
735             trackinfo.setAttribute("locked", false);
736             tracksinfo.appendChild(trackinfo);
737         }
738         infoXml.appendChild(tracksinfo);
739     }
740
741     if (version <= 0.82) {
742         // Convert <westley />s in <mlt />s (MLT extreme makeover)
743         QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
744         for (int i = 0; i < westleyNodes.count(); i++) {
745             QDomElement westley = westleyNodes.at(i).toElement();
746             westley.setTagName("mlt");
747         }
748     }
749
750     if (version <= 0.83) {
751         // Replace point size with pixel size in text titles
752         if (m_doc.toString().contains("font-size")) {
753             KMessageBox::ButtonCode convert = KMessageBox::Continue;
754             QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
755             for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
756                 QDomElement kproducer = kproducerNodes.at(i).toElement();
757                 if (kproducer.attribute("type").toInt() == TEXT) {
758                     QDomDocument data;
759                     data.setContent(kproducer.attribute("xmldata"));
760                     QDomNodeList items = data.firstChild().childNodes();
761                     for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
762                         if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
763                             QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
764                             if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
765                                 // Ask the user if he wants to convert
766                                 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
767                                     convert = (KMessageBox::ButtonCode)KMessageBox::warningYesNo(kapp->activeWindow(), i18n("Some of your text clips were saved with size in points, which means different sizes on different displays. Do you want to convert them to pixel size, making them portable? It is recommended you do this on the computer they were first created on, or you could have to adjust their size."), i18n("Update Text Clips"));
768                                 if (convert == KMessageBox::Yes) {
769                                     QFont font;
770                                     font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
771                                     QDomElement content = items.at(j).namedItem("content").toElement();
772                                     content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
773                                     content.removeAttribute("font-size");
774                                     kproducer.setAttribute("xmldata", data.toString());
775                                     /*
776                                      * You may be tempted to delete the preview file
777                                      * to force its recreation: bad idea (see
778                                      * http://www.kdenlive.org/mantis/view.php?id=749)
779                                      */
780                                 }
781                             }
782                         }
783                     }
784                 }
785             }
786         }
787
788         // Fill the <documentproperties /> element
789         QDomElement docProperties = infoXml.firstChildElement("documentproperties");
790         if (docProperties.isNull()) {
791             docProperties = m_doc.createElement("documentproperties");
792             docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
793             docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
794             docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
795             docProperties.setAttribute("position", infoXml.attribute("position"));
796             infoXml.appendChild(docProperties);
797         }
798     }
799
800     if (version <= 0.84) {
801         // update the title clips to use the new MLT kdenlivetitle producer
802         QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
803         for (int i = 0; i < kproducerNodes.count(); ++i) {
804             QDomElement kproducer = kproducerNodes.at(i).toElement();
805             if (kproducer.attribute("type").toInt() == TEXT) {
806                 QString data = kproducer.attribute("xmldata");
807                 QString datafile = kproducer.attribute("resource");
808                 if (!datafile.endsWith(".kdenlivetitle")) {
809                     datafile = QString();
810                     kproducer.setAttribute("resource", QString());
811                 }
812                 QString id = kproducer.attribute("id");
813                 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
814                 bool foundData = false;
815                 bool foundResource = false;
816                 bool foundService = false;
817                 for (int j = 0; j < mltproducers.count(); j++) {
818                     QDomElement wproducer = mltproducers.at(j).toElement();
819                     if (wproducer.attribute("id") == id) {
820                         QDomNodeList props = wproducer.childNodes();
821                         for (int k = 0; k < props.count(); k++) {
822                             if (props.at(k).toElement().attribute("name") == "xmldata") {
823                                 props.at(k).firstChild().setNodeValue(data);
824                                 foundData = true;
825                             } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
826                                 props.at(k).firstChild().setNodeValue("kdenlivetitle");
827                                 foundService = true;
828                             } else if (props.at(k).toElement().attribute("name") == "resource") {
829                                 props.at(k).firstChild().setNodeValue(datafile);
830                                 foundResource = true;
831                             }
832                         }
833                         if (!foundData) {
834                             QDomElement e = m_doc.createElement("property");
835                             e.setAttribute("name", "xmldata");
836                             QDomText value = m_doc.createTextNode(data);
837                             e.appendChild(value);
838                             wproducer.appendChild(e);
839                         }
840                         if (!foundService) {
841                             QDomElement e = m_doc.createElement("property");
842                             e.setAttribute("name", "mlt_service");
843                             QDomText value = m_doc.createTextNode("kdenlivetitle");
844                             e.appendChild(value);
845                             wproducer.appendChild(e);
846                         }
847                         if (!foundResource) {
848                             QDomElement e = m_doc.createElement("property");
849                             e.setAttribute("name", "resource");
850                             QDomText value = m_doc.createTextNode(datafile);
851                             e.appendChild(value);
852                             wproducer.appendChild(e);
853                         }
854                         break;
855                     }
856                 }
857             }
858         }
859     }
860     if (version <= 0.85) {
861         // update the LADSPA effects to use the new ladspa.id format instead of external xml file
862         QDomNodeList effectNodes = m_doc.elementsByTagName("filter");
863         for (int i = 0; i < effectNodes.count(); ++i) {
864             QDomElement effect = effectNodes.at(i).toElement();
865             if (EffectsList::property(effect, "mlt_service") == "ladspa") {
866                 // Needs to be converted
867                 QStringList info = getInfoFromEffectName(EffectsList::property(effect, "kdenlive_id"));
868                 if (info.isEmpty()) continue;
869                 // info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names
870                 EffectsList::setProperty(effect, "kdenlive_id", info.at(0));
871                 EffectsList::setProperty(effect, "tag", info.at(0));
872                 EffectsList::setProperty(effect, "mlt_service", info.at(0));
873                 EffectsList::removeProperty(effect, "src");
874                 for (int j = 1; j < info.size(); j++) {
875                     QString value = EffectsList::property(effect, info.at(j).section('=', 0, 0));
876                     if (!value.isEmpty()) {
877                         // update parameter name
878                         EffectsList::renameProperty(effect, info.at(j).section('=', 0, 0), info.at(j).section('=', 1, 1));
879                     }
880                 }
881             }
882         }
883     }
884
885     if (version <= 0.86) {
886         // Make sure we don't have avformat-novalidate producers, since it caused crashes
887         QDomNodeList producers = m_doc.elementsByTagName("producer");
888         int max = producers.count();
889         for (int i = 0; i < max; i++) {
890             QDomElement prod = producers.at(i).toElement();
891             if (EffectsList::property(prod, "mlt_service") == "avformat-novalidate")
892                 EffectsList::setProperty(prod, "mlt_service", "avformat");
893         }
894
895         // There was a mistake in Geometry transitions where the last keyframe was created one frame after the end of transition, so fix it and move last keyframe to real end of transition
896
897         // Get profile info (width / height)
898         int profileWidth;
899         int profileHeight;
900         QDomElement profile = m_doc.firstChildElement("profile");
901         if (profile.isNull()) profile = infoXml.firstChildElement("profileinfo");
902         if (profile.isNull()) {
903             // could not find profile info, set PAL
904             profileWidth = 720;
905             profileHeight = 576;
906         }
907         else {
908             profileWidth = profile.attribute("width").toInt();
909             profileHeight = profile.attribute("height").toInt();
910         }
911         QDomNodeList transitions = m_doc.elementsByTagName("transition");
912         max = transitions.count();
913         int out;
914         for (int i = 0; i < max; i++) {
915             QDomElement trans = transitions.at(i).toElement();
916             out = trans.attribute("out").toInt() - trans.attribute("in").toInt();
917             QString geom = EffectsList::property(trans, "geometry");
918             Mlt::Geometry *g = new Mlt::Geometry(geom.toUtf8().data(), out, profileWidth, profileHeight);
919             Mlt::GeometryItem item;
920             if (g->next_key(&item, out) == 0) {
921                 // We have a keyframe just after last frame, try to move it to last frame
922                 if (item.frame() == out + 1) {
923                     item.frame(out);
924                     g->insert(item);
925                     g->remove(out + 1);
926                     EffectsList::setProperty(trans, "geometry", g->serialise());
927                 }
928             }
929             delete g;
930         }
931     }
932
933     if (version <= 0.87) {
934         if (!m_doc.firstChildElement("mlt").hasAttribute("LC_NUMERIC")) {
935             m_doc.firstChildElement("mlt").setAttribute("LC_NUMERIC", "C");
936         }
937     }
938
939     // The document has been converted: mark it as modified
940     infoXml.setAttribute("version", currentVersion);
941     m_modified = true;
942     return true;
943 }
944
945 QStringList DocumentValidator::getInfoFromEffectName(const QString oldName)
946 {
947     QStringList info;
948     // Returns a list to convert old Kdenlive ladspa effects
949     if (oldName == "pitch_shift") {
950         info << "ladspa.1433";
951         info << "pitch=0";
952     }
953     else if (oldName == "vinyl") {
954         info << "ladspa.1905";
955         info << "year=0";
956         info << "rpm=1";
957         info << "warping=2";
958         info << "crackle=3";
959         info << "wear=4";
960     }
961     else if (oldName == "room_reverb") {
962         info << "ladspa.1216";
963         info << "room=0";
964         info << "delay=1";
965         info << "damp=2";
966     }
967     else if (oldName == "reverb") {
968         info << "ladspa.1423";
969         info << "room=0";
970         info << "damp=1";
971     }
972     else if (oldName == "rate_scale") {
973         info << "ladspa.1417";
974         info << "rate=0";
975     }
976     else if (oldName == "pitch_scale") {
977         info << "ladspa.1193";
978         info << "coef=0";
979     }
980     else if (oldName == "phaser") {
981         info << "ladspa.1217";
982         info << "rate=0";
983         info << "depth=1";
984         info << "feedback=2";
985         info << "spread=3";
986     }
987     else if (oldName == "limiter") {
988         info << "ladspa.1913";
989         info << "gain=0";
990         info << "limit=1";
991         info << "release=2";
992     }
993     else if (oldName == "equalizer_15") {
994         info << "ladspa.1197";
995         info << "1=0";
996         info << "2=1";
997         info << "3=2";
998         info << "4=3";
999         info << "5=4";
1000         info << "6=5";
1001         info << "7=6";
1002         info << "8=7";
1003         info << "9=8";
1004         info << "10=9";
1005         info << "11=10";
1006         info << "12=11";
1007         info << "13=12";
1008         info << "14=13";
1009         info << "15=14";
1010     }
1011     else if (oldName == "equalizer") {
1012         info << "ladspa.1901";
1013         info << "logain=0";
1014         info << "midgain=1";
1015         info << "higain=2";
1016     }
1017     else if (oldName == "declipper") {
1018         info << "ladspa.1195";
1019     }
1020     return info;
1021 }
1022
1023 QString DocumentValidator::colorToString(const QColor& c)
1024 {
1025     QString ret = "%1,%2,%3,%4";
1026     ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
1027     return ret;
1028 }
1029
1030 bool DocumentValidator::isProject() const
1031 {
1032     QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
1033     return !infoXmlNode.isNull();
1034 }
1035
1036 bool DocumentValidator::isModified() const
1037 {
1038     return m_modified;
1039 }
1040
1041 void DocumentValidator::updateEffects()
1042 {
1043     // WARNING: order by findDirs will determine which js file to use (in case multiple scripts for the same filter exist)
1044     QMap <QString, KUrl> paths;
1045 #if QT_VERSION >= 0x040700
1046     QMap <QString, QScriptProgram> scripts;
1047 #else
1048     QMap <QString, QString> scripts;
1049 #endif
1050     QStringList directories = KGlobal::dirs()->findDirs("appdata", "effects/update");
1051     foreach (const QString &directoryName, directories) {
1052         QDir directory(directoryName);
1053         QStringList fileList = directory.entryList(QStringList() << "*.js", QDir::Files);
1054         foreach (const QString &fileName, fileList) {
1055             QString identifier = fileName;
1056             // remove extension (".js")
1057             identifier.chop(3);
1058             paths.insert(identifier, KUrl(directoryName + fileName));
1059         }
1060     }
1061
1062     QDomNodeList effects = m_doc.elementsByTagName("filter");
1063     int max = effects.count();
1064     QStringList safeEffects;
1065     for(int i = 0; i < max; ++i) {
1066         QDomElement effect = effects.at(i).toElement();
1067         QString effectId = EffectsList::property(effect, "kdenlive_id");
1068         if (safeEffects.contains(effectId)) {
1069             // Do not check the same effect twice if it is at the correct version
1070             // (assume we don't have different versions of the same effect in a project file)
1071             continue;
1072         }
1073         QString effectTag = EffectsList::property(effect, "tag");
1074         QString effectVersionStr = EffectsList::property(effect, "version");
1075         double effectVersion = effectVersionStr.isNull() ? -1 : effectVersionStr.toDouble();
1076
1077         QDomElement effectDescr = MainWindow::customEffects.getEffectByTag(QString(), effectId);
1078         if (effectDescr.isNull()) {
1079             effectDescr = MainWindow::videoEffects.getEffectByTag(effectTag, effectId);
1080         }
1081         if (effectDescr.isNull()) {
1082             effectDescr = MainWindow::audioEffects.getEffectByTag(effectTag, effectId);
1083         }
1084         if (!effectDescr.isNull()) {
1085             double serviceVersion = -1;
1086             QDomElement serviceVersionElem = effectDescr.firstChildElement("version");
1087             if (!serviceVersionElem.isNull()) {
1088                 serviceVersion = serviceVersionElem.text().toDouble();
1089             }
1090             if (serviceVersion != effectVersion && paths.contains(effectId)) {
1091                 if (!scripts.contains(effectId)) {
1092                     QFile scriptFile(paths.value(effectId).path());
1093                     if (!scriptFile.open(QIODevice::ReadOnly)) {
1094                         continue;
1095                     }
1096 #if QT_VERSION >= 0x040700
1097                     QScriptProgram scriptProgram(scriptFile.readAll());
1098 #else
1099                     QString scriptProgram = scriptFile.readAll();
1100 #endif
1101                     scriptFile.close();
1102                     scripts.insert(effectId, scriptProgram);
1103                 }
1104
1105                 QScriptEngine scriptEngine;
1106                 scriptEngine.importExtension("qt.core");
1107                 scriptEngine.importExtension("qt.xml");
1108                 scriptEngine.evaluate(scripts.value(effectId));
1109                 QScriptValue updateRules = scriptEngine.globalObject().property("update");
1110                 if (!updateRules.isValid())
1111                     continue;
1112                 if (updateRules.isFunction()) {
1113                     QDomDocument scriptDoc;
1114                     scriptDoc.appendChild(scriptDoc.importNode(effect, true));
1115
1116                     QString effectString = updateRules.call(QScriptValue(), QScriptValueList()  << serviceVersion << effectVersion << scriptDoc.toString()).toString();
1117
1118                     if (!effectString.isEmpty() && !scriptEngine.hasUncaughtException()) {
1119                         scriptDoc.setContent(effectString);
1120                         QDomNode updatedEffect = effect.ownerDocument().importNode(scriptDoc.documentElement(), true);
1121                         effect.parentNode().replaceChild(updatedEffect, effect);
1122                         m_modified = true;
1123                     }
1124                 } else {
1125                     m_modified = updateEffectParameters(effect.childNodes(), &updateRules, serviceVersion, effectVersion);
1126                 }
1127
1128                 // set version number since MLT won't change it (only initially set it)
1129                 QDomElement versionElem = effect.firstChildElement("version");
1130                 if (EffectsList::property(effect, "version").isNull()) {
1131                     versionElem = effect.ownerDocument().createTextNode(QLocale().toString(serviceVersion)).toElement();
1132                     versionElem.setTagName("property");
1133                     versionElem.setAttribute("name", "version");
1134                     effect.appendChild(versionElem);
1135                 } else {
1136                     EffectsList::setProperty(effect, "version", QLocale().toString(serviceVersion));
1137                 }
1138             }
1139             else safeEffects.append(effectId);
1140         }
1141     }
1142 }
1143
1144 bool DocumentValidator::updateEffectParameters(QDomNodeList parameters, const QScriptValue* updateRules, const double serviceVersion, const double effectVersion)
1145 {
1146     bool updated = false;
1147     bool isDowngrade = serviceVersion < effectVersion;
1148     for (int i = 0; i < parameters.count(); ++i) {
1149         QDomElement parameter = parameters.at(i).toElement();
1150         QScriptValue rules = updateRules->property(parameter.attribute("name"));
1151         if (rules.isValid() && rules.isArray()) {
1152             int rulesCount = rules.property("length").toInt32();
1153             if (isDowngrade) {
1154                 // start with the highest version and downgrade step by step
1155                 for (int j = rulesCount - 1; j >= 0; --j) {
1156                     double version = rules.property(j).property(0).toNumber();
1157                     if (version <= effectVersion && version > serviceVersion) {
1158                         parameter.firstChild().setNodeValue(rules.property(j).property(1).call(QScriptValue(), QScriptValueList() << parameter.text() << isDowngrade).toString());
1159                         updated = true;
1160                     }
1161                 }
1162             } else {
1163                 for (int j = 0; j < rulesCount; ++j) {
1164                     double version = rules.property(j).property(0).toNumber();
1165                     if (version > effectVersion && version <= serviceVersion) {
1166                         parameter.firstChild().setNodeValue(rules.property(j).property(1).call(QScriptValue(), QScriptValueList() << parameter.text() << isDowngrade).toString());
1167                         updated = true;
1168                     }
1169                 }
1170             }
1171         }
1172     }
1173     return updated;
1174 }