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