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