]> git.sesse.net Git - kdenlive/blob - src/renderwidget.cpp
Fix breakage of render parameters:
[kdenlive] / src / renderwidget.cpp
1 /***************************************************************************
2  *   Copyright (C) 2008 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 "renderwidget.h"
22 #include "kdenlivesettings.h"
23 #include "ui_saveprofile_ui.h"
24
25 #include <KStandardDirs>
26 #include <KDebug>
27 #include <KMessageBox>
28 #include <KComboBox>
29 #include <KRun>
30 #include <KIO/NetAccess>
31 #include <KColorScheme>
32 #include <KNotification>
33 // #include <knewstuff2/engine.h>
34
35 #include <QDomDocument>
36 #include <QItemDelegate>
37 #include <QTreeWidgetItem>
38 #include <QListWidgetItem>
39 #include <QHeaderView>
40 #include <QMenu>
41 #include <QProcess>
42 #include <QInputDialog>
43
44 const int GroupRole = Qt::UserRole;
45 const int ExtensionRole = GroupRole + 1;
46 const int StandardRole = GroupRole + 2;
47 const int RenderRole = GroupRole + 3;
48 const int ParamsRole = GroupRole + 4;
49 const int EditableRole = GroupRole + 5;
50 const int MetaGroupRole = GroupRole + 6;
51 const int ExtraRole = GroupRole + 7;
52
53 // Running job status
54 const int WAITINGJOB = 0;
55 const int RUNNINGJOB = 1;
56 const int FINISHEDJOB = 2;
57
58
59 RenderWidget::RenderWidget(const QString &projectfolder, QWidget * parent) :
60         QDialog(parent),
61         m_projectFolder(projectfolder),
62         m_blockProcessing(false)
63 {
64     m_view.setupUi(this);
65     setWindowTitle(i18n("Rendering"));
66     m_view.buttonDelete->setIcon(KIcon("trash-empty"));
67     m_view.buttonDelete->setToolTip(i18n("Delete profile"));
68     m_view.buttonDelete->setEnabled(false);
69
70     m_view.buttonEdit->setIcon(KIcon("document-properties"));
71     m_view.buttonEdit->setToolTip(i18n("Edit profile"));
72     m_view.buttonEdit->setEnabled(false);
73
74     m_view.buttonSave->setIcon(KIcon("document-new"));
75     m_view.buttonSave->setToolTip(i18n("Create new profile"));
76
77     m_view.buttonInfo->setIcon(KIcon("help-about"));
78     m_view.hide_log->setIcon(KIcon("go-down"));
79
80     if (KdenliveSettings::showrenderparams()) {
81         m_view.buttonInfo->setDown(true);
82     } else m_view.advanced_params->hide();
83
84     m_view.rescale_size->setInputMask("0099\\x0099");
85     m_view.rescale_size->setText("320x240");
86
87
88     QMenu *renderMenu = new QMenu(i18n("Start Rendering"), this);
89     QAction *renderAction = renderMenu->addAction(KIcon("video-x-generic"), i18n("Render to File"));
90     connect(renderAction, SIGNAL(triggered()), this, SLOT(slotPrepareExport()));
91     QAction *scriptAction = renderMenu->addAction(KIcon("application-x-shellscript"), i18n("Generate Script"));
92     connect(scriptAction, SIGNAL(triggered()), this, SLOT(slotGenerateScript()));
93
94     m_view.buttonStart->setMenu(renderMenu);
95     m_view.buttonStart->setPopupMode(QToolButton::MenuButtonPopup);
96     m_view.buttonStart->setDefaultAction(renderAction);
97     m_view.buttonStart->setToolButtonStyle(Qt::ToolButtonTextOnly);
98     m_view.abort_job->setEnabled(false);
99     m_view.start_script->setEnabled(false);
100     m_view.delete_script->setEnabled(false);
101
102     m_view.format_list->setAlternatingRowColors(true);
103     m_view.size_list->setAlternatingRowColors(true);
104
105     parseProfiles();
106     parseScriptFiles();
107
108     connect(m_view.start_script, SIGNAL(clicked()), this, SLOT(slotStartScript()));
109     connect(m_view.delete_script, SIGNAL(clicked()), this, SLOT(slotDeleteScript()));
110     connect(m_view.scripts_list, SIGNAL(itemSelectionChanged()), this, SLOT(slotCheckScript()));
111     connect(m_view.running_jobs, SIGNAL(itemSelectionChanged()), this, SLOT(slotCheckJob()));
112     connect(m_view.running_jobs, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(slotPlayRendering(QTreeWidgetItem *, int)));
113
114     connect(m_view.buttonInfo, SIGNAL(clicked()), this, SLOT(showInfoPanel()));
115
116     connect(m_view.buttonSave, SIGNAL(clicked()), this, SLOT(slotSaveProfile()));
117     connect(m_view.buttonEdit, SIGNAL(clicked()), this, SLOT(slotEditProfile()));
118     connect(m_view.buttonDelete, SIGNAL(clicked()), this, SLOT(slotDeleteProfile()));
119     connect(m_view.abort_job, SIGNAL(clicked()), this, SLOT(slotAbortCurrentJob()));
120     connect(m_view.clean_up, SIGNAL(clicked()), this, SLOT(slotCLeanUpJobs()));
121     connect(m_view.hide_log, SIGNAL(clicked()), this, SLOT(slotHideLog()));
122
123     connect(m_view.buttonClose, SIGNAL(clicked()), this, SLOT(hide()));
124     connect(m_view.buttonClose2, SIGNAL(clicked()), this, SLOT(hide()));
125     connect(m_view.buttonClose3, SIGNAL(clicked()), this, SLOT(hide()));
126     connect(m_view.rescale, SIGNAL(toggled(bool)), m_view.rescale_size, SLOT(setEnabled(bool)));
127     connect(m_view.destination_list, SIGNAL(activated(int)), this, SLOT(refreshView()));
128     connect(m_view.out_file, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButtons()));
129     connect(m_view.out_file, SIGNAL(urlSelected(const KUrl &)), this, SLOT(slotUpdateButtons(const KUrl &)));
130     connect(m_view.format_list, SIGNAL(currentRowChanged(int)), this, SLOT(refreshView()));
131     connect(m_view.size_list, SIGNAL(currentRowChanged(int)), this, SLOT(refreshParams()));
132
133     connect(m_view.size_list, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(slotEditItem(QListWidgetItem *)));
134
135     connect(m_view.render_guide, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
136     connect(m_view.render_zone, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
137     connect(m_view.render_full, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
138
139     connect(m_view.guide_end, SIGNAL(activated(int)), this, SLOT(slotCheckStartGuidePosition()));
140     connect(m_view.guide_start, SIGNAL(activated(int)), this, SLOT(slotCheckEndGuidePosition()));
141
142     connect(m_view.format_selection, SIGNAL(activated(int)), this, SLOT(refreshView()));
143
144     m_view.buttonStart->setEnabled(false);
145     m_view.rescale_size->setEnabled(false);
146     m_view.guides_box->setVisible(false);
147     m_view.open_dvd->setVisible(false);
148     m_view.create_chapter->setVisible(false);
149     m_view.open_browser->setVisible(false);
150     m_view.error_box->setVisible(false);
151
152     m_view.splitter->setStretchFactor(1, 5);
153     m_view.splitter->setStretchFactor(0, 2);
154
155     m_view.out_file->setMode(KFile::File);
156
157     m_view.running_jobs->setHeaderLabels(QStringList() << QString() << i18n("File") << i18n("Progress"));
158     m_view.running_jobs->setItemDelegate(new RenderViewDelegate(this));
159
160     QHeaderView *header = m_view.running_jobs->header();
161     QFontMetrics fm = fontMetrics();
162     //header->resizeSection(0, fm.width("typical-name-for-a-torrent.torrent"));
163     header->setResizeMode(0, QHeaderView::Fixed);
164     header->resizeSection(0, 30);
165     header->setResizeMode(1, QHeaderView::Interactive);
166     header->resizeSection(1, fm.width("typical-name-for-a-file.torrent"));
167     header->setResizeMode(2, QHeaderView::Fixed);
168     header->resizeSection(1, width() * 2 / 3);
169     header->setResizeMode(2, QHeaderView::Interactive);
170     //header->setResizeMode(1, QHeaderView::Fixed);
171
172     m_view.scripts_list->setHeaderLabels(QStringList() << i18n("Script Files"));
173     m_view.scripts_list->setItemDelegate(new RenderScriptDelegate(this));
174
175
176     focusFirstVisibleItem();
177 }
178
179 void RenderWidget::slotEditItem(QListWidgetItem *item)
180 {
181     QString edit = item->data(EditableRole).toString();
182     if (edit.isEmpty() || !edit.endsWith("customprofiles.xml")) slotSaveProfile();
183     else slotEditProfile();
184 }
185
186 void RenderWidget::showInfoPanel()
187 {
188     if (m_view.advanced_params->isVisible()) {
189         m_view.advanced_params->setVisible(false);
190         m_view.buttonInfo->setDown(false);
191         KdenliveSettings::setShowrenderparams(false);
192     } else {
193         m_view.advanced_params->setVisible(true);
194         m_view.buttonInfo->setDown(true);
195         KdenliveSettings::setShowrenderparams(true);
196     }
197 }
198
199 void RenderWidget::setDocumentPath(const QString path)
200 {
201     m_projectFolder = path;
202     const QString fileName = m_view.out_file->url().fileName();
203     m_view.out_file->setUrl(KUrl(m_projectFolder + fileName));
204     parseScriptFiles();
205 }
206
207 void RenderWidget::slotUpdateGuideBox()
208 {
209     m_view.guides_box->setVisible(m_view.render_guide->isChecked());
210 }
211
212 void RenderWidget::slotCheckStartGuidePosition()
213 {
214     if (m_view.guide_start->currentIndex() > m_view.guide_end->currentIndex())
215         m_view.guide_start->setCurrentIndex(m_view.guide_end->currentIndex());
216 }
217
218 void RenderWidget::slotCheckEndGuidePosition()
219 {
220     if (m_view.guide_end->currentIndex() < m_view.guide_start->currentIndex())
221         m_view.guide_end->setCurrentIndex(m_view.guide_start->currentIndex());
222 }
223
224 void RenderWidget::setGuides(QDomElement guidesxml, double duration)
225 {
226     m_view.guide_start->clear();
227     m_view.guide_end->clear();
228     QDomNodeList nodes = guidesxml.elementsByTagName("guide");
229     if (nodes.count() > 0) {
230         m_view.guide_start->addItem(i18n("Beginning"), "0");
231         m_view.render_guide->setEnabled(true);
232         m_view.create_chapter->setEnabled(true);
233     } else {
234         m_view.render_guide->setEnabled(false);
235         m_view.create_chapter->setEnabled(false);
236     }
237     for (int i = 0; i < nodes.count(); i++) {
238         QDomElement e = nodes.item(i).toElement();
239         if (!e.isNull()) {
240             m_view.guide_start->addItem(e.attribute("comment"), e.attribute("time").toDouble());
241             m_view.guide_end->addItem(e.attribute("comment"), e.attribute("time").toDouble());
242         }
243     }
244     if (nodes.count() > 0)
245         m_view.guide_end->addItem(i18n("End"), QString::number(duration));
246 }
247
248 /**
249  * Will be called when the user selects an output file via the file dialog.
250  * File extension will be added automatically.
251  */
252 void RenderWidget::slotUpdateButtons(KUrl url)
253 {
254     if (m_view.out_file->url().isEmpty()) m_view.buttonStart->setEnabled(false);
255     else {
256         updateButtons(); // This also checks whether the selected format is available
257         //m_view.buttonStart->setEnabled(true);
258     }
259     if (url != 0) {
260         QListWidgetItem *item = m_view.size_list->currentItem();
261         QString extension = item->data(ExtensionRole).toString();
262         url = filenameWithExtension(url, extension);
263         m_view.out_file->setUrl(url);
264     }
265 }
266
267 /**
268  * Will be called when the user changes the output file path in the text line.
269  * File extension must NOT be added, would make editing impossible!
270  */
271 void RenderWidget::slotUpdateButtons()
272 {
273     if (m_view.out_file->url().isEmpty()) m_view.buttonStart->setEnabled(false);
274     else updateButtons(); // This also checks whether the selected format is available
275     //else m_view.buttonStart->setEnabled(true);
276 }
277
278 void RenderWidget::slotSaveProfile()
279 {
280     //TODO: update to correctly use metagroups
281     Ui::SaveProfile_UI ui;
282     QDialog *d = new QDialog(this);
283     ui.setupUi(d);
284
285     for (int i = 0; i < m_view.destination_list->count(); i++)
286         ui.destination_list->addItem(m_view.destination_list->itemIcon(i), m_view.destination_list->itemText(i), m_view.destination_list->itemData(i, Qt::UserRole));
287
288     ui.destination_list->setCurrentIndex(m_view.destination_list->currentIndex());
289     QString dest = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
290
291     QString customGroup = m_view.format_list->currentItem()->text();
292     if (customGroup.isEmpty()) customGroup = i18n("Custom");
293     ui.group_name->setText(customGroup);
294
295     ui.parameters->setText(m_view.advanced_params->toPlainText());
296     ui.extension->setText(m_view.size_list->currentItem()->data(ExtensionRole).toString());
297     ui.profile_name->setFocus();
298
299     if (d->exec() == QDialog::Accepted && !ui.profile_name->text().simplified().isEmpty()) {
300         QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
301         QDomDocument doc;
302         QFile file(exportFile);
303         doc.setContent(&file, false);
304         file.close();
305         QDomElement documentElement;
306         QDomElement profiles = doc.documentElement();
307         if (profiles.isNull() || profiles.tagName() != "profiles") {
308             doc.clear();
309             profiles = doc.createElement("profiles");
310             profiles.setAttribute("version", 1);
311             doc.appendChild(profiles);
312         }
313         int version = profiles.attribute("version", 0).toInt();
314         if (version < 1) {
315             kDebug() << "// OLD profile version";
316             doc.clear();
317             profiles = doc.createElement("profiles");
318             profiles.setAttribute("version", 1);
319             doc.appendChild(profiles);
320         }
321
322         QString newProfileName = ui.profile_name->text().simplified();
323         QString newGroupName = ui.group_name->text().simplified();
324         if (newGroupName.isEmpty()) newGroupName = i18n("Custom");
325         QString newMetaGroupId = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
326         QDomNodeList profilelist = doc.elementsByTagName("profile");
327         int i = 0;
328         while (!profilelist.item(i).isNull()) {
329             // make sure a profile with same name doesn't exist
330             documentElement = profilelist.item(i).toElement();
331             QString profileName = documentElement.attribute("name");
332             if (profileName == newProfileName) {
333                 // a profile with that same name already exists
334                 bool ok;
335                 newProfileName = QInputDialog::getText(this, i18n("Profile already exists"), i18n("This profile name already exists. Change the name if you don't want to overwrite it."), QLineEdit::Normal, newProfileName, &ok);
336                 if (!ok) return;
337                 if (profileName == newProfileName) {
338                     profiles.removeChild(profilelist.item(i));
339                     break;
340                 }
341             }
342             i++;
343         }
344
345         QDomElement profileElement = doc.createElement("profile");
346         profileElement.setAttribute("name", newProfileName);
347         profileElement.setAttribute("category", newGroupName);
348         profileElement.setAttribute("destinationid", newMetaGroupId);
349         profileElement.setAttribute("extension", ui.extension->text().simplified());
350         profileElement.setAttribute("args", ui.parameters->toPlainText().simplified());
351         profiles.appendChild(profileElement);
352
353         //QCString save = doc.toString().utf8();
354
355         if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
356             KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
357             delete d;
358             return;
359         }
360         QTextStream out(&file);
361         out << doc.toString();
362         if (file.error() != QFile::NoError) {
363             KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
364             file.close();
365             delete d;
366             return;
367         }
368         file.close();
369         parseProfiles(newMetaGroupId, newGroupName, newProfileName);
370     }
371     delete d;
372 }
373
374 void RenderWidget::slotEditProfile()
375 {
376     QListWidgetItem *item = m_view.size_list->currentItem();
377     if (!item) return;
378     QString currentGroup = m_view.format_list->currentItem()->text();
379
380     QString params = item->data(ParamsRole).toString();
381     QString extension = item->data(ExtensionRole).toString();
382     QString currentProfile = item->text();
383
384     Ui::SaveProfile_UI ui;
385     QDialog *d = new QDialog(this);
386     ui.setupUi(d);
387
388     for (int i = 0; i < m_view.destination_list->count(); i++)
389         ui.destination_list->addItem(m_view.destination_list->itemIcon(i), m_view.destination_list->itemText(i), m_view.destination_list->itemData(i, Qt::UserRole));
390
391     ui.destination_list->setCurrentIndex(m_view.destination_list->currentIndex());
392     QString dest = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
393
394     QString customGroup = m_view.format_list->currentItem()->text();
395     if (customGroup.isEmpty()) customGroup = i18n("Custom");
396     ui.group_name->setText(customGroup);
397
398     ui.profile_name->setText(currentProfile);
399     ui.extension->setText(extension);
400     ui.parameters->setText(params);
401     ui.profile_name->setFocus();
402     d->setWindowTitle(i18n("Edit Profile"));
403     if (d->exec() == QDialog::Accepted) {
404         slotDeleteProfile(false);
405         QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
406         QDomDocument doc;
407         QFile file(exportFile);
408         doc.setContent(&file, false);
409         file.close();
410         QDomElement documentElement;
411         QDomElement profiles = doc.documentElement();
412
413         if (profiles.isNull() || profiles.tagName() != "profiles") {
414             doc.clear();
415             profiles = doc.createElement("profiles");
416             profiles.setAttribute("version", 1);
417             doc.appendChild(profiles);
418         }
419
420         int version = profiles.attribute("version", 0).toInt();
421         if (version < 1) {
422             kDebug() << "// OLD profile version";
423             doc.clear();
424             profiles = doc.createElement("profiles");
425             profiles.setAttribute("version", 1);
426             doc.appendChild(profiles);
427         }
428
429         QString newProfileName = ui.profile_name->text().simplified();
430         QString newGroupName = ui.group_name->text().simplified();
431         if (newGroupName.isEmpty()) newGroupName = i18n("Custom");
432         QString newMetaGroupId = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
433         QDomNodeList profilelist = doc.elementsByTagName("profile");
434         int i = 0;
435         while (!profilelist.item(i).isNull()) {
436             // make sure a profile with same name doesn't exist
437             documentElement = profilelist.item(i).toElement();
438             QString profileName = documentElement.attribute("name");
439             if (profileName == newProfileName) {
440                 // a profile with that same name already exists
441                 bool ok;
442                 newProfileName = QInputDialog::getText(this, i18n("Profile already exists"), i18n("This profile name already exists. Change the name if you don't want to overwrite it."), QLineEdit::Normal, newProfileName, &ok);
443                 if (!ok) return;
444                 if (profileName == newProfileName) {
445                     profiles.removeChild(profilelist.item(i));
446                     break;
447                 }
448             }
449             i++;
450         }
451
452         QDomElement profileElement = doc.createElement("profile");
453         profileElement.setAttribute("name", newProfileName);
454         profileElement.setAttribute("category", newGroupName);
455         profileElement.setAttribute("destinationid", newMetaGroupId);
456         profileElement.setAttribute("extension", ui.extension->text().simplified());
457         profileElement.setAttribute("args", ui.parameters->toPlainText().simplified());
458         profiles.appendChild(profileElement);
459
460         //QCString save = doc.toString().utf8();
461         delete d;
462         if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
463             KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
464             return;
465         }
466         QTextStream out(&file);
467         out << doc.toString();
468         if (file.error() != QFile::NoError) {
469             KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
470             file.close();
471             return;
472         }
473         file.close();
474         parseProfiles(newMetaGroupId, newGroupName, newProfileName);
475     } else delete d;
476 }
477
478 void RenderWidget::slotDeleteProfile(bool refresh)
479 {
480     //TODO: delete a profile installed by KNewStuff the easy way
481     /*
482     QString edit = m_view.size_list->currentItem()->data(EditableRole).toString();
483     if (!edit.endsWith("customprofiles.xml")) {
484         // This is a KNewStuff installed file, process through KNS
485         KNS::Engine engine(0);
486         if (engine.init("kdenlive_render.knsrc")) {
487             KNS::Entry::List entries;
488         }
489         return;
490     }*/
491     QString currentGroup = m_view.format_list->currentItem()->text();
492     QString currentProfile = m_view.size_list->currentItem()->text();
493     QString metaGroupId = m_view.destination_list->itemData(m_view.destination_list->currentIndex(), Qt::UserRole).toString();
494
495     QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
496     QDomDocument doc;
497     QFile file(exportFile);
498     doc.setContent(&file, false);
499     file.close();
500
501     QDomElement documentElement;
502     QDomNodeList profiles = doc.elementsByTagName("profile");
503     int i = 0;
504     QString groupName;
505     QString profileName;
506     QString destination;
507
508     while (!profiles.item(i).isNull()) {
509         documentElement = profiles.item(i).toElement();
510         profileName = documentElement.attribute("name");
511         groupName = documentElement.attribute("category");
512         destination = documentElement.attribute("destinationid");
513
514         if (profileName == currentProfile && groupName == currentGroup && destination == metaGroupId) {
515             kDebug() << "// GOT it: " << profileName;
516             doc.documentElement().removeChild(profiles.item(i));
517             break;
518         }
519         i++;
520     }
521
522     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
523         KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
524         return;
525     }
526     QTextStream out(&file);
527     out << doc.toString();
528     if (file.error() != QFile::NoError) {
529         KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
530         file.close();
531         return;
532     }
533     file.close();
534     if (refresh) {
535         parseProfiles(metaGroupId, currentGroup);
536         focusFirstVisibleItem();
537     }
538 }
539
540 void RenderWidget::updateButtons()
541 {
542     if (!m_view.size_list->currentItem() || m_view.size_list->currentItem()->isHidden()) {
543         m_view.buttonSave->setEnabled(false);
544         m_view.buttonDelete->setEnabled(false);
545         m_view.buttonEdit->setEnabled(false);
546         m_view.buttonStart->setEnabled(false);
547     } else {
548         m_view.buttonSave->setEnabled(true);
549         m_view.buttonStart->setEnabled(m_view.size_list->currentItem()->toolTip().isEmpty());
550         QString edit = m_view.size_list->currentItem()->data(EditableRole).toString();
551         if (edit.isEmpty() || !edit.endsWith("customprofiles.xml")) {
552             m_view.buttonDelete->setEnabled(false);
553             m_view.buttonEdit->setEnabled(false);
554         } else {
555             m_view.buttonDelete->setEnabled(true);
556             m_view.buttonEdit->setEnabled(true);
557         }
558     }
559 }
560
561
562 void RenderWidget::focusFirstVisibleItem()
563 {
564     if (m_view.size_list->currentItem() && !m_view.size_list->currentItem()->isHidden()) {
565         updateButtons();
566         return;
567     }
568     for (int ix = 0; ix < m_view.size_list->count(); ix++) {
569         QListWidgetItem *item = m_view.size_list->item(ix);
570         if (item && !item->isHidden()) {
571             m_view.size_list->setCurrentRow(ix);
572             break;
573         }
574     }
575     if (!m_view.size_list->currentItem()) m_view.size_list->setCurrentRow(0);
576     updateButtons();
577 }
578
579 void RenderWidget::slotPrepareExport(bool scriptExport)
580 {
581     if (!QFile::exists(KdenliveSettings::rendererpath())) {
582         KMessageBox::sorry(this, i18n("Cannot find the melt program required for rendering (part of Mlt)"));
583         return;
584     }
585     if (m_view.play_after->isChecked() && KdenliveSettings::defaultplayerapp().isEmpty())
586         KMessageBox::sorry(this, i18n("Cannot play video after rendering because the default video player application is not set.\nPlease define it in Kdenlive settings dialog."));
587     QString chapterFile;
588     if (m_view.create_chapter->isChecked()) chapterFile = m_view.out_file->url().path() + ".dvdchapter";
589     emit prepareRenderingData(scriptExport, m_view.render_zone->isChecked(), chapterFile);
590 }
591
592
593 void RenderWidget::slotExport(bool scriptExport, int zoneIn, int zoneOut, const QString &playlistPath, const QString &scriptPath)
594 {
595     QListWidgetItem *item = m_view.size_list->currentItem();
596     if (!item) return;
597
598     QString dest = m_view.out_file->url().path();
599     if (dest.isEmpty()) return;
600
601     // Check whether target file has an extension.
602     // If not, ask whether extension should be added or not.
603     QString extension = item->data(ExtensionRole).toString();
604     if (!dest.endsWith(extension)) {
605         if (KMessageBox::questionYesNo(this, i18n("File has no extension. Add extension (%1)?", extension)) == KMessageBox::Yes) {
606             dest.append("." + extension);
607         }
608     }
609
610     QFile f(dest);
611     if (f.exists()) {
612         if (KMessageBox::warningYesNo(this, i18n("Output file already exists. Do you want to overwrite it?")) != KMessageBox::Yes)
613             return;
614     }
615
616     QStringList overlayargs;
617     if (m_view.tc_overlay->isChecked()) {
618         QString filterFile = KStandardDirs::locate("appdata", "metadata.properties");
619         overlayargs << "meta.attr.timecode=1" << "meta.attr.timecode.markup=#timecode";
620         overlayargs << "-attach" << "data_feed:attr_check" << "-attach";
621         overlayargs << "data_show:" + filterFile << "_loader=1" << "dynamic=1";
622     }
623
624     QStringList render_process_args;
625
626     if (!scriptExport) render_process_args << "-erase";
627     if (KdenliveSettings::usekuiserver()) render_process_args << "-kuiserver";
628
629     double guideStart = 0;
630     double guideEnd = 0;
631
632     if (m_view.render_zone->isChecked()) render_process_args << "in=" + QString::number(zoneIn) << "out=" + QString::number(zoneOut);
633     else if (m_view.render_guide->isChecked()) {
634         double fps = (double) m_profile.frame_rate_num / m_profile.frame_rate_den;
635         guideStart = m_view.guide_start->itemData(m_view.guide_start->currentIndex()).toDouble();
636         guideEnd = m_view.guide_end->itemData(m_view.guide_end->currentIndex()).toDouble();
637         render_process_args << "in=" + QString::number(GenTime(guideStart).frames(fps)) << "out=" + QString::number(GenTime(guideEnd).frames(fps));
638     }
639
640     render_process_args << overlayargs;
641     render_process_args << KdenliveSettings::rendererpath() << m_profile.path << item->data(RenderRole).toString();
642     if (m_view.play_after->isChecked()) render_process_args << KdenliveSettings::KdenliveSettings::defaultplayerapp();
643     else render_process_args << "-";
644
645     QString renderArgs = m_view.advanced_params->toPlainText().simplified();
646
647     // Adjust frame scale
648     int width;
649     int height;
650     if (m_view.rescale->isChecked() && m_view.rescale->isEnabled()) {
651         width = m_view.rescale_size->text().section('x', 0, 0).toInt();
652         height = m_view.rescale_size->text().section('x', 1, 1).toInt();
653     } else {
654         width = m_profile.width;
655         height = m_profile.height;
656     }
657     renderArgs.replace("%dar", '@' + QString::number(m_profile.display_aspect_num) + '/' + QString::number(m_profile.display_aspect_den));
658
659     // Adjust scanning
660     if (m_view.scanning_list->currentIndex() == 1) renderArgs.append(" progressive=1");
661     else if (m_view.scanning_list->currentIndex() == 2) renderArgs.append(" progressive=0");
662
663     // disable audio if requested
664     if (!m_view.export_audio->isChecked())
665         renderArgs.append(" an=1 ");
666
667     // Check if the rendering profile is different from project profile,
668     // in which case we need to use the producer_comsumer from MLT
669     QString std = renderArgs;
670     QString destination = m_view.destination_list->itemData(m_view.destination_list->currentIndex()).toString();
671     const QString currentSize = QString::number(width) + 'x' + QString::number(height);
672     QString subsize = currentSize;
673     if (std.startsWith("s=")) {
674         subsize = std.section(' ', 0, 0).toLower();
675         subsize = subsize.section("=", 1, 1);
676     } else if (std.contains(" s=")) {
677         subsize = std.section(" s=", 1, 1);
678         subsize = subsize.section(' ', 0, 0).toLower();
679     } else if (destination != "audioonly" && m_view.rescale->isChecked() && m_view.rescale->isEnabled()) {
680         subsize = QString(" s=%1x%2").arg(width).arg(height);
681         // Add current size parameter
682         renderArgs.append(subsize);
683     }
684     bool resizeProfile = (subsize != currentSize);
685     QStringList paramsList = renderArgs.split(" ", QString::SkipEmptyParts);
686     for (int i = 0; i < paramsList.count(); i++) {
687         if (paramsList.at(i).startsWith("profile=")) {
688             if (paramsList.at(i).section('=', 1) != m_profile.path) resizeProfile = true;
689             break;
690         }
691     }
692
693     if (resizeProfile) render_process_args << "consumer:" + playlistPath;
694     else render_process_args << playlistPath;
695     render_process_args << dest;
696     render_process_args << paramsList;
697
698     QString group = m_view.size_list->currentItem()->data(MetaGroupRole).toString();
699
700     QStringList renderParameters;
701     renderParameters << dest << item->data(RenderRole).toString() << renderArgs.simplified();
702     renderParameters << QString::number(zoneIn) << QString::number(zoneOut) << QString::number(m_view.play_after->isChecked());
703     renderParameters << QString::number(guideStart) << QString::number(guideEnd) << QString::number(resizeProfile);
704
705     QString scriptName;
706     if (scriptExport) {
707
708         /*renderParameters << scriptName;
709         if (group == "dvd") renderParameters << QString::number(m_view.create_chapter->isChecked());
710         else renderParameters << QString::number(false);
711         emit doRender(renderParameters, overlayargs);*/
712
713         // Generate script file
714         QFile file(scriptPath);
715         if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
716             KMessageBox::error(this, i18n("Cannot write to file %1", scriptPath));
717             return;
718         }
719         QString renderer = QCoreApplication::applicationDirPath() + QString("/kdenlive_render");
720         if (!QFile::exists(renderer)) renderer = "kdenlive_render";
721         QTextStream outStream(&file);
722         outStream << "#! /bin/sh" << "\n" << "\n";
723         outStream << "SOURCE=" << "\"" + playlistPath + "\"" << "\n";
724         outStream << "TARGET=" << "\"" + dest + "\"" << "\n";
725         outStream << renderer << " " << render_process_args.join(" ") << "\n" << "\n";
726         if (file.error() != QFile::NoError) {
727             KMessageBox::error(this, i18n("Cannot write to file %1", scriptPath));
728             file.close();
729             return;
730         }
731         file.close();
732         QFile::setPermissions(scriptPath, file.permissions() | QFile::ExeUser);
733
734         QTimer::singleShot(400, this, SLOT(parseScriptFiles()));
735         m_view.tabWidget->setCurrentIndex(2);
736         return;
737     }
738     renderParameters << scriptName;
739     m_view.tabWidget->setCurrentIndex(1);
740
741     // Save rendering profile to document
742     emit selectedRenderProfile(m_view.size_list->currentItem()->data(MetaGroupRole).toString(), m_view.size_list->currentItem()->text());
743
744     // insert item in running jobs list
745     QTreeWidgetItem *renderItem;
746     QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1);
747     if (!existing.isEmpty()) {
748         renderItem = existing.at(0);
749         if (renderItem->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB) {
750             KMessageBox::information(this, i18n("There is already a job writing file:<br><b>%1</b><br>Abort the job if you want to overwrite it...", dest), i18n("Already running"));
751             return;
752         }
753         renderItem->setData(1, Qt::UserRole + 4, QString());
754     } else {
755         renderItem = new QTreeWidgetItem(m_view.running_jobs, QStringList() << QString() << dest << QString());
756     }
757     renderItem->setData(1, Qt::UserRole + 2, WAITINGJOB);
758     renderItem->setIcon(0, KIcon("media-playback-pause"));
759     renderItem->setData(1, Qt::UserRole, i18n("Waiting..."));
760     renderItem->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
761     renderItem->setData(1, Qt::UserRole + 1, QTime::currentTime());
762
763     // Set rendering type
764     if (group == "dvd") {
765         renderParameters << QString::number(m_view.create_chapter->isChecked());
766         if (m_view.open_dvd->isChecked()) {
767             renderItem->setData(0, Qt::UserRole, group);
768             if (renderArgs.contains("profile=")) {
769                 // rendering profile contains an MLT profile, so pass it to the running jog item, useful for dvd
770                 QString prof = renderArgs.section("profile=", 1, 1);
771                 prof = prof.section(' ', 0, 0);
772                 kDebug() << "// render profile: " << prof;
773                 renderItem->setData(0, Qt::UserRole + 1, prof);
774             }
775         }
776     } else {
777         renderParameters << QString::number(false);
778         if (group == "websites" && m_view.open_browser->isChecked()) {
779             renderItem->setData(0, Qt::UserRole, group);
780             // pass the url
781             QString url = m_view.size_list->currentItem()->data(ExtraRole).toString();
782             renderItem->setData(0, Qt::UserRole + 1, url);
783         }
784     }
785     renderItem->setData(1, Qt::UserRole + 3, render_process_args);
786     checkRenderStatus();
787 }
788
789 void RenderWidget::checkRenderStatus()
790 {
791     if (m_blockProcessing) return;
792     QTreeWidgetItem *item = m_view.running_jobs->topLevelItem(0);
793     while (item) {
794         if (item->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB) break;
795         else if (item->data(1, Qt::UserRole + 2).toInt() == WAITINGJOB) {
796             item->setData(1, Qt::UserRole + 1, QTime::currentTime());
797             if (item->data(1, Qt::UserRole + 4).isNull()) {
798                 QString renderer = QCoreApplication::applicationDirPath() + QString("/kdenlive_render");
799                 if (!QFile::exists(renderer)) renderer = "kdenlive_render";
800                 QProcess::startDetached(renderer, item->data(1, Qt::UserRole + 3).toStringList());
801                 KNotification::event("RenderStarted", i18n("Rendering <i>%1</i> started", item->text(1)), QPixmap(), this);
802                 //emit doRender(item->data(1, Qt::UserRole + 3).toStringList(), item->data(1, Qt::UserRole + 2).toStringList());
803             } else {
804                 // Script item
805                 QProcess::startDetached(item->data(1, Qt::UserRole + 3).toString());
806             }
807             break;
808         }
809         item = m_view.running_jobs->itemBelow(item);
810     }
811 }
812
813 int RenderWidget::waitingJobsCount() const
814 {
815     int count = 0;
816     QTreeWidgetItem *item = m_view.running_jobs->topLevelItem(0);
817     while (item) {
818         if (item->data(1, Qt::UserRole + 2).toInt() == WAITINGJOB) count++;
819         item = m_view.running_jobs->itemBelow(item);
820     }
821     return count;
822 }
823
824 void RenderWidget::setProfile(MltVideoProfile profile)
825 {
826     m_profile = profile;
827     //WARNING: this way to tell the video standard is a bit hackish...
828     if (m_profile.description.contains("pal", Qt::CaseInsensitive) || m_profile.description.contains("25", Qt::CaseInsensitive) || m_profile.description.contains("50", Qt::CaseInsensitive)) m_view.format_selection->setCurrentIndex(0);
829     else m_view.format_selection->setCurrentIndex(1);
830     m_view.scanning_list->setCurrentIndex(0);
831     refreshView();
832 }
833
834 void RenderWidget::refreshView()
835 {
836     m_view.size_list->blockSignals(true);
837     QListWidgetItem *sizeItem;
838
839     QString destination;
840     KIcon brokenIcon("dialog-close");
841     if (m_view.destination_list->currentIndex() > 0)
842         destination = m_view.destination_list->itemData(m_view.destination_list->currentIndex()).toString();
843
844
845     if (destination == "dvd") {
846         m_view.open_dvd->setVisible(true);
847         m_view.create_chapter->setVisible(true);
848     } else {
849         m_view.open_dvd->setVisible(false);
850         m_view.create_chapter->setVisible(false);
851     }
852     if (destination == "websites") m_view.open_browser->setVisible(true);
853     else m_view.open_browser->setVisible(false);
854     if (!destination.isEmpty() && QString("dvd websites audioonly").contains(destination))
855         m_view.rescale->setEnabled(false);
856     else m_view.rescale->setEnabled(true);
857     // hide groups that are not in the correct destination
858     for (int i = 0; i < m_view.format_list->count(); i++) {
859         sizeItem = m_view.format_list->item(i);
860         if (sizeItem->data(MetaGroupRole).toString() == destination) {
861             sizeItem->setHidden(false);
862             //kDebug() << "// SET GRP:: " << sizeItem->text() << ", METY:" << sizeItem->data(MetaGroupRole).toString();
863         } else sizeItem->setHidden(true);
864     }
865
866     // activate first visible item
867     QListWidgetItem * item = m_view.format_list->currentItem();
868     if (!item || item->isHidden()) {
869         for (int i = 0; i < m_view.format_list->count(); i++) {
870             if (!m_view.format_list->item(i)->isHidden()) {
871                 m_view.format_list->setCurrentRow(i);
872                 break;
873             }
874         }
875         item = m_view.format_list->currentItem();
876     }
877     if (!item || item->isHidden()) {
878         m_view.format_list->setEnabled(false);
879         m_view.size_list->setEnabled(false);
880         return;
881     } else {
882         m_view.format_list->setEnabled(true);
883         m_view.size_list->setEnabled(true);
884     }
885     int count = 0;
886     for (int i = 0; i < m_view.format_list->count() && count < 2; i++) {
887         if (!m_view.format_list->isRowHidden(i)) count++;
888     }
889     if (count > 1) m_view.format_list->setVisible(true);
890     else m_view.format_list->setVisible(false);
891     QString std;
892     QString group = item->text();
893     bool firstSelected = false;
894     const QStringList formatsList = KdenliveSettings::supportedformats();
895     const QStringList vcodecsList = KdenliveSettings::videocodecs();
896     const QStringList acodecsList = KdenliveSettings::audiocodecs();
897
898     KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window);
899     const QColor disabled = scheme.foreground(KColorScheme::InactiveText).color();
900     const QColor disabledbg = scheme.background(KColorScheme::NegativeBackground).color();
901
902     for (int i = 0; i < m_view.size_list->count(); i++) {
903         sizeItem = m_view.size_list->item(i);
904         if ((sizeItem->data(GroupRole).toString() == group || sizeItem->data(GroupRole).toString().isEmpty()) && sizeItem->data(MetaGroupRole).toString() == destination) {
905             std = sizeItem->data(StandardRole).toString();
906             if (!std.isEmpty()) {
907                 if (std.contains("PAL", Qt::CaseInsensitive)) sizeItem->setHidden(m_view.format_selection->currentIndex() != 0);
908                 else if (std.contains("NTSC", Qt::CaseInsensitive)) sizeItem->setHidden(m_view.format_selection->currentIndex() != 1);
909             } else {
910                 sizeItem->setHidden(false);
911                 if (!firstSelected) m_view.size_list->setCurrentItem(sizeItem);
912                 firstSelected = true;
913             }
914
915             if (!sizeItem->isHidden()) {
916                 // Make sure the selected profile uses an installed avformat codec / format
917                 std = sizeItem->data(ParamsRole).toString();
918
919                 if (!formatsList.isEmpty()) {
920                     QString format;
921                     if (std.startsWith("f=")) format = std.section("f=", 1, 1);
922                     else if (std.contains(" f=")) format = std.section(" f=", 1, 1);
923                     if (!format.isEmpty()) {
924                         format = format.section(' ', 0, 0).toLower();
925                         if (!formatsList.contains(format)) {
926                             kDebug() << "***** UNSUPPORTED F: " << format;
927                             //sizeItem->setHidden(true);
928                             //sizeItem->setFlags(Qt::ItemIsSelectable);
929                             sizeItem->setToolTip(i18n("Unsupported video format: %1", format));
930                             sizeItem->setIcon(brokenIcon);
931                             sizeItem->setForeground(disabled);
932                         }
933                     }
934                 }
935                 if (!acodecsList.isEmpty() && !sizeItem->isHidden()) {
936                     QString format;
937                     if (std.startsWith("acodec=")) format = std.section("acodec=", 1, 1);
938                     else if (std.contains(" acodec=")) format = std.section(" acodec=", 1, 1);
939                     if (!format.isEmpty()) {
940                         format = format.section(' ', 0, 0).toLower();
941                         if (!acodecsList.contains(format)) {
942                             kDebug() << "*****  UNSUPPORTED ACODEC: " << format;
943                             //sizeItem->setHidden(true);
944                             //sizeItem->setFlags(Qt::ItemIsSelectable);
945                             sizeItem->setToolTip(i18n("Unsupported audio codec: %1", format));
946                             sizeItem->setIcon(brokenIcon);
947                             sizeItem->setForeground(disabled);
948                             sizeItem->setBackground(disabledbg);
949                         }
950                     }
951                 }
952                 if (!vcodecsList.isEmpty() && !sizeItem->isHidden()) {
953                     QString format;
954                     if (std.startsWith("vcodec=")) format = std.section("vcodec=", 1, 1);
955                     else if (std.contains(" vcodec=")) format = std.section(" vcodec=", 1, 1);
956                     if (!format.isEmpty()) {
957                         format = format.section(' ', 0, 0).toLower();
958                         if (!vcodecsList.contains(format)) {
959                             kDebug() << "*****  UNSUPPORTED VCODEC: " << format;
960                             //sizeItem->setHidden(true);
961                             //sizeItem->setFlags(Qt::ItemIsSelectable);
962                             sizeItem->setToolTip(i18n("Unsupported video codec: %1", format));
963                             sizeItem->setIcon(brokenIcon);
964                             sizeItem->setForeground(disabled);
965                         }
966                     }
967                 }
968             }
969         } else sizeItem->setHidden(true);
970     }
971     focusFirstVisibleItem();
972     m_view.size_list->blockSignals(false);
973     refreshParams();
974 }
975
976 KUrl RenderWidget::filenameWithExtension(KUrl url, QString extension)
977 {
978     QString path;
979     if (!url.isEmpty()) {
980         path = url.path();
981         int pos = path.lastIndexOf('.') + 1;
982         if (pos == 0) path.append('.' + extension);
983         else path = path.left(pos) + extension;
984
985     } else {
986         path = m_projectFolder + "untitled." + extension;
987     }
988     return KUrl(path);
989 }
990
991
992 /**
993  * Called when a new format or size has been selected.
994  */
995 void RenderWidget::refreshParams()
996 {
997     // Format not available (e.g. codec not installed); Disable start button
998     QListWidgetItem *item = m_view.size_list->currentItem();
999     if (!item || item->isHidden()) {
1000         m_view.advanced_params->clear();
1001         m_view.buttonStart->setEnabled(false);
1002         return;
1003     }
1004     QString params = item->data(ParamsRole).toString();
1005     QString extension = item->data(ExtensionRole).toString();
1006     m_view.advanced_params->setPlainText(params);
1007     QString destination = m_view.destination_list->itemData(m_view.destination_list->currentIndex()).toString();
1008     if (params.contains(" s=") || destination == "audioonly") {
1009         // profile has a fixed size, do not allow resize
1010         m_view.rescale->setEnabled(false);
1011         m_view.rescale_size->setEnabled(false);
1012     } else {
1013         m_view.rescale->setEnabled(true);
1014         m_view.rescale_size->setEnabled(true);
1015     }
1016     KUrl url = filenameWithExtension(m_view.out_file->url(), extension);
1017     m_view.out_file->setUrl(url);
1018 //     if (!url.isEmpty()) {
1019 //         QString path = url.path();
1020 //         int pos = path.lastIndexOf('.') + 1;
1021 //  if (pos == 0) path.append('.' + extension);
1022 //         else path = path.left(pos) + extension;
1023 //         m_view.out_file->setUrl(KUrl(path));
1024 //     } else {
1025 //         m_view.out_file->setUrl(KUrl(QDir::homePath() + "/untitled." + extension));
1026 //     }
1027     m_view.out_file->setFilter("*." + extension);
1028     QString edit = item->data(EditableRole).toString();
1029     if (edit.isEmpty() || !edit.endsWith("customprofiles.xml")) {
1030         m_view.buttonDelete->setEnabled(false);
1031         m_view.buttonEdit->setEnabled(false);
1032     } else {
1033         m_view.buttonDelete->setEnabled(true);
1034         m_view.buttonEdit->setEnabled(true);
1035     }
1036
1037     m_view.buttonStart->setEnabled(m_view.size_list->currentItem()->toolTip().isEmpty());
1038 }
1039
1040 void RenderWidget::reloadProfiles()
1041 {
1042     parseProfiles();
1043 }
1044
1045 void RenderWidget::parseProfiles(QString meta, QString group, QString profile)
1046 {
1047     m_view.size_list->clear();
1048     m_view.format_list->clear();
1049     m_view.destination_list->clear();
1050     m_view.destination_list->addItem(KIcon("video-x-generic"), i18n("File rendering"));
1051     m_view.destination_list->addItem(KIcon("media-optical"), i18n("DVD"), "dvd");
1052     m_view.destination_list->addItem(KIcon("audio-x-generic"), i18n("Audio only"), "audioonly");
1053     m_view.destination_list->addItem(KIcon("applications-internet"), i18n("Web sites"), "websites");
1054     m_view.destination_list->addItem(KIcon("applications-multimedia"), i18n("Media players"), "mediaplayers");
1055     m_view.destination_list->addItem(KIcon("drive-harddisk"), i18n("Lossless / HQ"), "lossless");
1056     m_view.destination_list->addItem(KIcon("pda"), i18n("Mobile devices"), "mobile");
1057
1058     QString exportFile = KStandardDirs::locate("appdata", "export/profiles.xml");
1059     parseFile(exportFile, false);
1060
1061
1062     QString exportFolder = KStandardDirs::locateLocal("appdata", "export/");
1063     QDir directory = QDir(exportFolder);
1064     QStringList filter;
1065     filter << "*.xml";
1066     QStringList fileList = directory.entryList(filter, QDir::Files);
1067     // We should parse customprofiles.xml in last position, so that user profiles
1068     // can also override profiles installed by KNewStuff
1069     fileList.removeAll("customprofiles.xml");
1070     foreach(const QString &filename, fileList)
1071     parseFile(exportFolder + '/' + filename, true);
1072     if (QFile::exists(exportFolder + "/customprofiles.xml")) parseFile(exportFolder + "/customprofiles.xml", true);
1073
1074     if (!meta.isEmpty()) {
1075         m_view.destination_list->blockSignals(true);
1076         m_view.destination_list->setCurrentIndex(m_view.destination_list->findData(meta));
1077         m_view.destination_list->blockSignals(false);
1078     }
1079     refreshView();
1080     QList<QListWidgetItem *> child;
1081     if (!group.isEmpty()) child = m_view.format_list->findItems(group, Qt::MatchExactly);
1082     if (!child.isEmpty()) {
1083         for (int i = 0; i < child.count(); i++) {
1084             if (child.at(i)->data(MetaGroupRole).toString() == meta) {
1085                 m_view.format_list->setCurrentItem(child.at(i));
1086                 break;
1087             }
1088         }
1089     }
1090     child.clear();
1091     if (!profile.isEmpty()) child = m_view.size_list->findItems(profile, Qt::MatchExactly);
1092     if (!child.isEmpty()) m_view.size_list->setCurrentItem(child.at(0));
1093 }
1094
1095 void RenderWidget::parseFile(QString exportFile, bool editable)
1096 {
1097     kDebug() << "// Parsing file: " << exportFile;
1098     kDebug() << "------------------------------";
1099     QDomDocument doc;
1100     QFile file(exportFile);
1101     doc.setContent(&file, false);
1102     file.close();
1103     QDomElement documentElement;
1104     QDomElement profileElement;
1105     QString extension;
1106     QListWidgetItem *item;
1107     QDomNodeList groups = doc.elementsByTagName("group");
1108
1109     if (editable || groups.count() == 0) {
1110         QDomElement profiles = doc.documentElement();
1111         if (editable && profiles.attribute("version", 0).toInt() < 1) {
1112             kDebug() << "// OLD profile version";
1113             // this is an old profile version, update it
1114             QDomDocument newdoc;
1115             QDomElement newprofiles = newdoc.createElement("profiles");
1116             newprofiles.setAttribute("version", 1);
1117             newdoc.appendChild(newprofiles);
1118             QDomNodeList profilelist = doc.elementsByTagName("profile");
1119             for (int i = 0; i < profilelist.count(); i++) {
1120                 QString category = i18n("Custom");
1121                 QString extension;
1122                 QDomNode parent = profilelist.at(i).parentNode();
1123                 if (!parent.isNull()) {
1124                     QDomElement parentNode = parent.toElement();
1125                     if (parentNode.hasAttribute("name")) category = parentNode.attribute("name");
1126                     extension = parentNode.attribute("extension");
1127                 }
1128                 profilelist.at(i).toElement().setAttribute("category", category);
1129                 if (!extension.isEmpty()) profilelist.at(i).toElement().setAttribute("extension", extension);
1130                 QDomNode n = profilelist.at(i).cloneNode();
1131                 newprofiles.appendChild(newdoc.importNode(n, true));
1132             }
1133             if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
1134                 KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
1135                 return;
1136             }
1137             QTextStream out(&file);
1138             out << newdoc.toString();
1139             file.close();
1140             parseFile(exportFile, editable);
1141             return;
1142         }
1143
1144         QDomNode node = doc.elementsByTagName("profile").at(0);
1145         if (node.isNull()) {
1146             kDebug() << "// Export file: " << exportFile << " IS BROKEN";
1147             return;
1148         }
1149         int count = 1;
1150         while (!node.isNull()) {
1151             QDomElement profile = node.toElement();
1152             QString profileName = profile.attribute("name");
1153             QString standard = profile.attribute("standard");
1154             QString params = profile.attribute("args");
1155             QString category = profile.attribute("category", i18n("Custom"));
1156             QString dest = profile.attribute("destinationid");
1157             QString prof_extension = profile.attribute("extension");
1158             if (!prof_extension.isEmpty()) extension = prof_extension;
1159
1160             QList <QListWidgetItem *> list = m_view.format_list->findItems(category, Qt::MatchExactly);
1161             bool exists = false;
1162             for (int j = 0; j < list.count(); j++) {
1163                 if (list.at(j)->data(MetaGroupRole) == dest) {
1164                     exists = true;
1165                     break;
1166                 }
1167             }
1168             if (!exists) {
1169                 item = new QListWidgetItem(category, m_view.format_list);
1170                 item->setData(MetaGroupRole, dest);
1171             }
1172
1173             // Check if item with same name already exists and replace it,
1174             // allowing to override default profiles
1175
1176             list = m_view.size_list->findItems(profileName, Qt::MatchExactly);
1177
1178             for (int j = 0; j < list.count(); j++) {
1179                 if (list.at(j)->data(MetaGroupRole) == dest) {
1180                     QListWidgetItem *duplicate = list.takeAt(j);
1181                     delete duplicate;
1182                     j--;
1183                 }
1184             }
1185
1186             item = new QListWidgetItem(profileName, m_view.size_list);
1187             //kDebug() << "// ADDINg item with name: " << profileName << ", GRP" << category << ", DEST:" << dest ;
1188             item->setData(GroupRole, category);
1189             item->setData(MetaGroupRole, dest);
1190             item->setData(ExtensionRole, extension);
1191             item->setData(RenderRole, "avformat");
1192             item->setData(StandardRole, standard);
1193             item->setData(ParamsRole, params);
1194             if (profile.hasAttribute("url")) item->setData(ExtraRole, profile.attribute("url"));
1195             if (editable) {
1196                 item->setData(EditableRole, exportFile);
1197                 if (exportFile.endsWith("customprofiles.xml")) item->setIcon(KIcon("emblem-favorite"));
1198                 else item->setIcon(KIcon("applications-internet"));
1199             }
1200             node = doc.elementsByTagName("profile").at(count);
1201             count++;
1202         }
1203         return;
1204     }
1205
1206     int i = 0;
1207     QString groupName;
1208     QString profileName;
1209
1210     QString prof_extension;
1211     QString renderer;
1212     QString params;
1213     QString standard;
1214     KIcon icon;
1215
1216     while (!groups.item(i).isNull()) {
1217         documentElement = groups.item(i).toElement();
1218         QDomNode gname = documentElement.elementsByTagName("groupname").at(0);
1219         QString metagroupName;
1220         QString metagroupId;
1221         if (!gname.isNull()) {
1222             metagroupName = gname.firstChild().nodeValue();
1223             metagroupId = gname.toElement().attribute("id");
1224             if (!metagroupName.isEmpty() && !m_view.destination_list->contains(metagroupName)) {
1225                 if (metagroupId == "dvd") icon = KIcon("media-optical");
1226                 else if (metagroupId == "audioonly") icon = KIcon("audio-x-generic");
1227                 else if (metagroupId == "websites") icon = KIcon("applications-internet");
1228                 else if (metagroupId == "mediaplayers") icon = KIcon("applications-multimedia");
1229                 else if (metagroupId == "lossless") icon = KIcon("drive-harddisk");
1230                 else if (metagroupId == "mobile") icon = KIcon("pda");
1231                 m_view.destination_list->addItem(icon, i18n(metagroupName.toUtf8().data()), metagroupId);
1232             }
1233         }
1234         groupName = documentElement.attribute("name", i18n("Custom"));
1235         extension = documentElement.attribute("extension", QString());
1236         renderer = documentElement.attribute("renderer", QString());
1237         QList <QListWidgetItem *> list = m_view.format_list->findItems(groupName, Qt::MatchExactly);
1238         bool exists = false;
1239         for (int j = 0; j < list.count(); j++) {
1240             if (list.at(j)->data(MetaGroupRole) == metagroupId) {
1241                 exists = true;
1242                 break;
1243             }
1244         }
1245         if (!exists) {
1246             item = new QListWidgetItem(groupName, m_view.format_list);
1247             item->setData(MetaGroupRole, metagroupId);
1248         }
1249
1250         QDomNode n = groups.item(i).firstChild();
1251         while (!n.isNull()) {
1252             if (n.toElement().tagName() != "profile") {
1253                 n = n.nextSibling();
1254                 continue;
1255             }
1256             profileElement = n.toElement();
1257             profileName = profileElement.attribute("name");
1258             standard = profileElement.attribute("standard");
1259             params = profileElement.attribute("args");
1260             prof_extension = profileElement.attribute("extension");
1261             if (!prof_extension.isEmpty()) extension = prof_extension;
1262             item = new QListWidgetItem(profileName, m_view.size_list);
1263             item->setData(GroupRole, groupName);
1264             item->setData(MetaGroupRole, metagroupId);
1265             item->setData(ExtensionRole, extension);
1266             item->setData(RenderRole, renderer);
1267             item->setData(StandardRole, standard);
1268             item->setData(ParamsRole, params);
1269             if (profileElement.hasAttribute("url")) item->setData(ExtraRole, profileElement.attribute("url"));
1270             if (editable) item->setData(EditableRole, exportFile);
1271             n = n.nextSibling();
1272         }
1273
1274         i++;
1275     }
1276 }
1277
1278 void RenderWidget::setRenderJob(const QString &dest, int progress)
1279 {
1280     QTreeWidgetItem *item;
1281     QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1);
1282     if (!existing.isEmpty()) item = existing.at(0);
1283     else {
1284         item = new QTreeWidgetItem(m_view.running_jobs, QStringList() << QString() << dest << QString());
1285         item->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1286         if (progress == 0) {
1287             item->setData(1, Qt::UserRole + 2, WAITINGJOB);
1288             item->setIcon(0, KIcon("media-playback-pause"));
1289             item->setData(1, Qt::UserRole, i18n("Waiting..."));
1290         }
1291     }
1292     item->setData(2, Qt::UserRole, progress);
1293     item->setData(1, Qt::UserRole + 2, RUNNINGJOB);
1294     if (progress == 0) {
1295         item->setIcon(0, KIcon("system-run"));
1296         item->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1297         item->setData(1, Qt::UserRole + 1, QTime::currentTime());
1298         slotCheckJob();
1299     } else {
1300         QTime startTime = item->data(1, Qt::UserRole + 1).toTime();
1301         int seconds = startTime.secsTo(QTime::currentTime());;
1302         const QString t = i18n("Estimated time %1", QTime().addSecs(seconds * (100 - progress) / progress).toString("hh:mm:ss"));
1303         item->setData(1, Qt::UserRole, t);
1304     }
1305 }
1306
1307 void RenderWidget::setRenderStatus(const QString &dest, int status, const QString &error)
1308 {
1309     QTreeWidgetItem *item;
1310     QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1);
1311     if (!existing.isEmpty()) item = existing.at(0);
1312     else {
1313         item = new QTreeWidgetItem(m_view.running_jobs, QStringList() << QString() << dest << QString());
1314         item->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1315     }
1316     item->setData(1, Qt::UserRole + 2, FINISHEDJOB);
1317     if (status == -1) {
1318         // Job finished successfully
1319         item->setIcon(0, KIcon("dialog-ok"));
1320         item->setData(2, Qt::UserRole, 100);
1321         QTime startTime = item->data(1, Qt::UserRole + 1).toTime();
1322         int seconds = startTime.secsTo(QTime::currentTime());
1323         const QTime tm = QTime().addSecs(seconds);
1324         const QString t = i18n("Rendering finished in %1", tm.toString("hh:mm:ss"));
1325         item->setData(1, Qt::UserRole, t);
1326         QString itemGroup = item->data(0, Qt::UserRole).toString();
1327         if (itemGroup == "dvd") {
1328             emit openDvdWizard(item->text(1), item->data(0, Qt::UserRole + 1).toString());
1329         } else if (itemGroup == "websites") {
1330             QString url = item->data(0, Qt::UserRole + 1).toString();
1331             if (!url.isEmpty()) new KRun(url, this);
1332         }
1333     } else if (status == -2) {
1334         // Rendering crashed
1335         item->setData(1, Qt::UserRole, i18n("Rendering crashed"));
1336         item->setIcon(0, KIcon("dialog-close"));
1337         item->setData(2, Qt::UserRole, 100);
1338         m_view.error_log->append(i18n("<strong>Rendering of %1 crashed</strong><br />", dest));
1339         m_view.error_log->append(error);
1340         m_view.error_log->append("<hr />");
1341         m_view.error_box->setVisible(true);
1342     } else if (status == -3) {
1343         // User aborted job
1344         item->setData(1, Qt::UserRole, i18n("Rendering aborted"));
1345         item->setIcon(0, KIcon("dialog-cancel"));
1346         item->setData(2, Qt::UserRole, 100);
1347     }
1348     slotCheckJob();
1349     checkRenderStatus();
1350 }
1351
1352 void RenderWidget::slotAbortCurrentJob()
1353 {
1354     QTreeWidgetItem *current = m_view.running_jobs->currentItem();
1355     if (current) {
1356         if (current->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB)
1357             emit abortProcess(current->text(1));
1358         else {
1359             delete current;
1360             slotCheckJob();
1361         }
1362     }
1363 }
1364
1365 void RenderWidget::slotCheckJob()
1366 {
1367     bool activate = false;
1368     QTreeWidgetItem *current = m_view.running_jobs->currentItem();
1369     if (current) {
1370         if (current->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB)
1371             m_view.abort_job->setText(i18n("Abort Job"));
1372         else m_view.abort_job->setText(i18n("Remove Job"));
1373         activate = true;
1374     }
1375     m_view.abort_job->setEnabled(activate);
1376 }
1377
1378 void RenderWidget::slotCLeanUpJobs()
1379 {
1380     int ix = 0;
1381     QTreeWidgetItem *current = m_view.running_jobs->topLevelItem(ix);
1382     while (current) {
1383         if (current->data(1, Qt::UserRole + 2).toInt() == FINISHEDJOB)
1384             delete current;
1385         else ix++;
1386         current = m_view.running_jobs->topLevelItem(ix);
1387     }
1388     slotCheckJob();
1389 }
1390
1391 void RenderWidget::parseScriptFiles()
1392 {
1393     QStringList scriptsFilter;
1394     scriptsFilter << "*.sh";
1395     m_view.scripts_list->clear();
1396
1397     QTreeWidgetItem *item;
1398     // List the project scripts
1399     QStringList scriptFiles = QDir(m_projectFolder + "scripts").entryList(scriptsFilter, QDir::Files);
1400     for (int i = 0; i < scriptFiles.size(); ++i) {
1401         KUrl scriptpath(m_projectFolder + "scripts/" + scriptFiles.at(i));
1402         item = new QTreeWidgetItem(m_view.scripts_list, QStringList() << scriptpath.fileName());
1403         QString target;
1404         QFile file(scriptpath.path());
1405         if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
1406             while (!file.atEnd()) {
1407                 QByteArray line = file.readLine();
1408                 if (line.startsWith("TARGET=")) {
1409                     target = QString(line).section("TARGET=", 1);
1410                     target.remove(QChar('"'));
1411                     break;
1412                 }
1413             }
1414             file.close();
1415         }
1416         item->setSizeHint(0, QSize(m_view.scripts_list->columnWidth(0), fontMetrics().height() * 2));
1417         item->setData(0, Qt::UserRole, target.simplified());
1418         item->setData(0, Qt::UserRole + 1, scriptpath.path());
1419     }
1420     bool activate = false;
1421     QTreeWidgetItem *script = m_view.scripts_list->topLevelItem(0);
1422     if (script) {
1423         script->setSelected(true);
1424         m_view.scripts_list->setCurrentItem(script);
1425         activate = true;
1426     }
1427 //    m_view.start_script->setEnabled(activate);
1428 //    m_view.delete_script->setEnabled(activate);
1429 }
1430
1431 void RenderWidget::slotCheckScript()
1432 {
1433     bool activate = false;
1434     QTreeWidgetItemIterator it(m_view.scripts_list);
1435     if (*it) activate = true;
1436     m_view.start_script->setEnabled(activate);
1437     m_view.delete_script->setEnabled(activate);
1438 }
1439
1440 void RenderWidget::slotStartScript()
1441 {
1442     QTreeWidgetItem *item = m_view.scripts_list->currentItem();
1443     if (item) {
1444         QString destination = item->data(0, Qt::UserRole).toString();
1445         QString path = item->data(0, Qt::UserRole + 1).toString();
1446         // Insert new job in queue
1447         QTreeWidgetItem *renderItem;
1448         QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(destination, Qt::MatchExactly, 1);
1449         kDebug() << "------  START SCRIPT";
1450         if (!existing.isEmpty()) {
1451             renderItem = existing.at(0);
1452             if (renderItem->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB) {
1453                 KMessageBox::information(this, i18n("There is already a job writing file:<br><b>%1</b><br>Abort the job if you want to overwrite it...", destination), i18n("Already running"));
1454                 return;
1455             }
1456         } else renderItem = new QTreeWidgetItem(m_view.running_jobs, QStringList() << QString() << destination << QString());
1457         kDebug() << "------  START SCRIPT 2";
1458         renderItem->setData(2, Qt::UserRole, 0);
1459         renderItem->setData(1, Qt::UserRole + 2, WAITINGJOB);
1460         renderItem->setIcon(0, KIcon("media-playback-pause"));
1461         renderItem->setData(1, Qt::UserRole, i18n("Waiting..."));
1462         renderItem->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1463         renderItem->setData(1, Qt::UserRole + 1, QTime::currentTime());
1464         renderItem->setData(1, Qt::UserRole + 3, path);
1465         renderItem->setData(1, Qt::UserRole + 4, '1');
1466         checkRenderStatus();
1467         m_view.tabWidget->setCurrentIndex(1);
1468     }
1469 }
1470
1471 void RenderWidget::slotDeleteScript()
1472 {
1473     QTreeWidgetItem *item = m_view.scripts_list->currentItem();
1474     if (item) {
1475         QString path = item->data(0, Qt::UserRole + 1).toString();
1476         KIO::NetAccess::del(path + ".mlt", this);
1477         KIO::NetAccess::del(path, this);
1478         parseScriptFiles();
1479     }
1480 }
1481
1482 void RenderWidget::slotGenerateScript()
1483 {
1484     slotPrepareExport(true);
1485 }
1486
1487 void RenderWidget::slotHideLog()
1488 {
1489     m_view.error_box->setVisible(false);
1490 }
1491
1492 void RenderWidget::setRenderProfile(const QString &dest, const QString &name)
1493 {
1494     m_view.destination_list->blockSignals(true);
1495     m_view.format_list->blockSignals(true);
1496     m_view.size_list->blockSignals(true);
1497     for (int i = 0; i < m_view.destination_list->count(); i++) {
1498         if (m_view.destination_list->itemData(i, Qt::UserRole) == dest) {
1499             m_view.destination_list->setCurrentIndex(i);
1500             break;
1501         }
1502     }
1503     QList<QListWidgetItem *> childs = m_view.size_list->findItems(name, Qt::MatchExactly);
1504     if (!childs.isEmpty()) {
1505         QListWidgetItem *profile = childs.at(0);
1506         if (profile->isHidden()) {
1507             QString group = profile->data(GroupRole).toString();
1508             childs = m_view.format_list->findItems(group, Qt::MatchExactly);
1509             if (!childs.isEmpty()) {
1510                 m_view.format_list->setCurrentItem(childs.at(0));
1511             }
1512         }
1513         refreshView();
1514         m_view.size_list->blockSignals(false);
1515         m_view.size_list->setCurrentItem(profile);
1516     } else m_view.size_list->blockSignals(false);
1517     m_view.destination_list->blockSignals(false);
1518     m_view.format_list->blockSignals(false);
1519
1520 }
1521
1522 bool RenderWidget::startWaitingRenderJobs()
1523 {
1524     m_blockProcessing = true;
1525     QString autoscriptFile = getFreeScriptName("auto");
1526     QFile file(autoscriptFile);
1527     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
1528         kWarning() << "//////  ERROR writing to file: " << autoscriptFile;
1529         KMessageBox::error(0, i18n("Cannot write to file %1", autoscriptFile));
1530         return false;
1531     }
1532
1533     QString renderer = QCoreApplication::applicationDirPath() + QString("/kdenlive_render");
1534     if (!QFile::exists(renderer)) renderer = "kdenlive_render";
1535     QTextStream outStream(&file);
1536     outStream << "#! /bin/sh" << "\n" << "\n";
1537     QTreeWidgetItem *item = m_view.running_jobs->topLevelItem(0);
1538     while (item) {
1539         if (item->data(1, Qt::UserRole + 2).toInt() == WAITINGJOB) {
1540             // Add render process for item
1541             const QString params = item->data(1, Qt::UserRole + 3).toStringList().join(" ");
1542             outStream << renderer << " " << params << "\n";
1543         }
1544         item = m_view.running_jobs->itemBelow(item);
1545     }
1546     // erase itself when rendering is finished
1547     outStream << "rm " << autoscriptFile << "\n" << "\n";
1548     if (file.error() != QFile::NoError) {
1549         KMessageBox::error(0, i18n("Cannot write to file %1", autoscriptFile));
1550         file.close();
1551         return false;
1552     }
1553     file.close();
1554     QFile::setPermissions(autoscriptFile, file.permissions() | QFile::ExeUser);
1555     QProcess::startDetached(autoscriptFile, QStringList());
1556     return true;
1557 }
1558
1559 QString RenderWidget::getFreeScriptName(const QString &prefix)
1560 {
1561     int ix = 0;
1562     QString scriptsFolder = m_projectFolder + "scripts/";
1563     KStandardDirs::makeDir(scriptsFolder);
1564     QString path;
1565     while (path.isEmpty() || QFile::exists(path)) {
1566         ix++;
1567         path = scriptsFolder + prefix + i18n("script") + QString::number(ix).rightJustified(3, '0', false) + ".sh";
1568     }
1569     return path;
1570 }
1571
1572 void RenderWidget::slotPlayRendering(QTreeWidgetItem *item, int)
1573 {
1574     if (KdenliveSettings::defaultplayerapp().isEmpty() || item->data(1, Qt::UserRole + 2).toInt() != FINISHEDJOB) return;
1575     QProcess::startDetached(KdenliveSettings::defaultplayerapp(), QStringList() << item->text(1));
1576 }
1577
1578
1579