]> git.sesse.net Git - kdenlive/blob - src/renderwidget.cpp
Allow to select between timecode (hh:mm:ss:ff) and frame count overlay when rendering
[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 #include "timecode.h"
25
26 #include <KStandardDirs>
27 #include <KDebug>
28 #include <KMessageBox>
29 #include <KComboBox>
30 #include <KRun>
31 #include <KIO/NetAccess>
32 #include <KColorScheme>
33 #include <KNotification>
34 #include <KStartupInfo>
35
36 #include <QDomDocument>
37 #include <QItemDelegate>
38 #include <QTreeWidgetItem>
39 #include <QListWidgetItem>
40 #include <QHeaderView>
41 #include <QMenu>
42 #include <QInputDialog>
43 #include <QProcess>
44 #include <QDBusConnectionInterface>
45 #include <QDBusInterface>
46 #include <QThread>
47
48 const int GroupRole = Qt::UserRole;
49 const int ExtensionRole = GroupRole + 1;
50 const int StandardRole = GroupRole + 2;
51 const int RenderRole = GroupRole + 3;
52 const int ParamsRole = GroupRole + 4;
53 const int EditableRole = GroupRole + 5;
54 const int MetaGroupRole = GroupRole + 6;
55 const int ExtraRole = GroupRole + 7;
56
57 // Running job status
58 const int WAITINGJOB = 0;
59 const int RUNNINGJOB = 1;
60 const int FINISHEDJOB = 2;
61
62
63 RenderWidget::RenderWidget(const QString &projectfolder, bool enableProxy, QWidget * parent) :
64         QDialog(parent),
65         m_projectFolder(projectfolder),
66         m_blockProcessing(false)
67 {
68     m_view.setupUi(this);
69     setWindowTitle(i18n("Rendering"));
70     m_view.buttonDelete->setIcon(KIcon("trash-empty"));
71     m_view.buttonDelete->setToolTip(i18n("Delete profile"));
72     m_view.buttonDelete->setEnabled(false);
73
74     m_view.buttonEdit->setIcon(KIcon("document-properties"));
75     m_view.buttonEdit->setToolTip(i18n("Edit profile"));
76     m_view.buttonEdit->setEnabled(false);
77
78     m_view.buttonSave->setIcon(KIcon("document-new"));
79     m_view.buttonSave->setToolTip(i18n("Create new profile"));
80
81     m_view.buttonInfo->setIcon(KIcon("help-about"));
82     m_view.hide_log->setIcon(KIcon("go-down"));
83
84     m_view.buttonFavorite->setIcon(KIcon("favorites"));
85     m_view.buttonFavorite->setToolTip(i18n("Copy profile to favorites"));
86
87     if (KdenliveSettings::showrenderparams()) {
88         m_view.buttonInfo->setDown(true);
89     } else m_view.advanced_params->hide();
90     
91     m_view.proxy_render->setHidden(!enableProxy);
92
93         m_view.encoder_threads->setMaximum(QThread::idealThreadCount());
94         m_view.encoder_threads->setValue(KdenliveSettings::encodethreads());
95         connect(m_view.encoder_threads, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateEncodeThreads(int)));
96         
97     m_view.rescale_keep->setChecked(KdenliveSettings::rescalekeepratio());
98     connect(m_view.rescale_width, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateRescaleWidth(int)));
99     connect(m_view.rescale_height, SIGNAL(valueChanged(int)), this, SLOT(slotUpdateRescaleHeight(int)));
100     m_view.rescale_keep->setIcon(KIcon("insert-link"));
101     m_view.rescale_keep->setToolTip(i18n("Preserve aspect ratio"));
102     connect(m_view.rescale_keep, SIGNAL(clicked()), this, SLOT(slotSwitchAspectRatio()));
103
104     connect(m_view.buttonRender, SIGNAL(clicked()), this, SLOT(slotPrepareExport()));
105     connect(m_view.buttonGenerateScript, SIGNAL(clicked()), this, SLOT(slotGenerateScript()));
106
107     m_view.abort_job->setEnabled(false);
108     m_view.start_script->setEnabled(false);
109     m_view.delete_script->setEnabled(false);
110
111     m_view.format_list->setAlternatingRowColors(true);
112     m_view.size_list->setAlternatingRowColors(true);
113
114     KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window, KSharedConfig::openConfig(KdenliveSettings::colortheme()));
115     QColor bg = scheme.background(KColorScheme::NegativeBackground).color();
116     m_view.errorBox->setStyleSheet(QString("QGroupBox { background-color: rgb(%1, %2, %3); border-radius: 5px;}; ").arg(bg.red()).arg(bg.green()).arg(bg.blue()));
117     int height = QFontInfo(font()).pixelSize();
118     m_view.errorIcon->setPixmap(KIcon("dialog-warning").pixmap(height, height));
119     m_view.errorBox->setHidden(true);
120
121     connect(m_view.export_audio, SIGNAL(stateChanged(int)), this, SLOT(slotUpdateAudioLabel(int)));
122     m_view.export_audio->setCheckState(Qt::PartiallyChecked);
123
124     parseProfiles();
125     parseScriptFiles();
126     m_view.running_jobs->setUniformRowHeights(false);
127     m_view.scripts_list->setUniformRowHeights(false);
128     connect(m_view.start_script, SIGNAL(clicked()), this, SLOT(slotStartScript()));
129     connect(m_view.delete_script, SIGNAL(clicked()), this, SLOT(slotDeleteScript()));
130     connect(m_view.scripts_list, SIGNAL(itemSelectionChanged()), this, SLOT(slotCheckScript()));
131     connect(m_view.running_jobs, SIGNAL(itemSelectionChanged()), this, SLOT(slotCheckJob()));
132     connect(m_view.running_jobs, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(slotPlayRendering(QTreeWidgetItem *, int)));
133
134     connect(m_view.buttonInfo, SIGNAL(clicked()), this, SLOT(showInfoPanel()));
135
136     connect(m_view.buttonSave, SIGNAL(clicked()), this, SLOT(slotSaveProfile()));
137     connect(m_view.buttonEdit, SIGNAL(clicked()), this, SLOT(slotEditProfile()));
138     connect(m_view.buttonDelete, SIGNAL(clicked()), this, SLOT(slotDeleteProfile()));
139     connect(m_view.buttonFavorite, SIGNAL(clicked()), this, SLOT(slotCopyToFavorites()));
140
141     connect(m_view.abort_job, SIGNAL(clicked()), this, SLOT(slotAbortCurrentJob()));
142     connect(m_view.start_job, SIGNAL(clicked()), this, SLOT(slotStartCurrentJob()));
143     connect(m_view.clean_up, SIGNAL(clicked()), this, SLOT(slotCLeanUpJobs()));
144     connect(m_view.hide_log, SIGNAL(clicked()), this, SLOT(slotHideLog()));
145
146     connect(m_view.buttonClose, SIGNAL(clicked()), this, SLOT(hide()));
147     connect(m_view.buttonClose2, SIGNAL(clicked()), this, SLOT(hide()));
148     connect(m_view.buttonClose3, SIGNAL(clicked()), this, SLOT(hide()));
149     connect(m_view.rescale, SIGNAL(toggled(bool)), m_view.rescale_box, SLOT(setEnabled(bool)));
150     connect(m_view.destination_list, SIGNAL(activated(int)), this, SLOT(refreshCategory()));
151     connect(m_view.out_file, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButtons()));
152     connect(m_view.out_file, SIGNAL(urlSelected(const KUrl &)), this, SLOT(slotUpdateButtons(const KUrl &)));
153     connect(m_view.format_list, SIGNAL(currentRowChanged(int)), this, SLOT(refreshView()));
154     connect(m_view.size_list, SIGNAL(currentRowChanged(int)), this, SLOT(refreshParams()));
155
156     connect(m_view.size_list, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(slotEditItem(QListWidgetItem *)));
157
158     connect(m_view.render_guide, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
159     connect(m_view.render_zone, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
160     connect(m_view.render_full, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
161
162     connect(m_view.guide_end, SIGNAL(activated(int)), this, SLOT(slotCheckStartGuidePosition()));
163     connect(m_view.guide_start, SIGNAL(activated(int)), this, SLOT(slotCheckEndGuidePosition()));
164
165     connect(m_view.format_selection, SIGNAL(activated(int)), this, SLOT(refreshView()));
166     connect(m_view.tc_overlay, SIGNAL(toggled(bool)), m_view.tc_type, SLOT(setEnabled(bool)));
167
168     m_view.buttonRender->setEnabled(false);
169     m_view.buttonGenerateScript->setEnabled(false);
170     m_view.rescale_box->setEnabled(false);
171     m_view.guides_box->setVisible(false);
172     m_view.open_dvd->setVisible(false);
173     m_view.create_chapter->setVisible(false);
174     m_view.open_browser->setVisible(false);
175     m_view.error_box->setVisible(false);
176     m_view.tc_type->setEnabled(false);
177
178     m_view.splitter->setStretchFactor(1, 5);
179     m_view.splitter->setStretchFactor(0, 2);
180
181     m_view.out_file->setMode(KFile::File);
182
183     m_view.running_jobs->setHeaderLabels(QStringList() << QString() << i18n("File") << i18n("Progress"));
184     m_jobsDelegate = new RenderViewDelegate(this);
185     m_view.running_jobs->setItemDelegate(m_jobsDelegate);
186
187     QHeaderView *header = m_view.running_jobs->header();
188     header->setResizeMode(0, QHeaderView::Fixed);
189     header->resizeSection(0, 30);
190     header->setResizeMode(1, QHeaderView::Interactive);
191     header->setResizeMode(2, QHeaderView::Fixed);
192     header->resizeSection(1, width() * 2 / 3 - 15);
193     header->setResizeMode(2, QHeaderView::Interactive);
194     //header->setResizeMode(1, QHeaderView::Fixed);
195
196
197     m_view.scripts_list->setHeaderLabels(QStringList() << QString() << i18n("Script Files"));
198     m_scriptsDelegate = new RenderViewDelegate(this);
199     m_view.scripts_list->setItemDelegate(m_scriptsDelegate);
200     header = m_view.scripts_list->header();
201     header->setResizeMode(0, QHeaderView::Fixed);
202     header->resizeSection(0, 30);
203
204     // Find path for Kdenlive renderer
205     m_renderer = QCoreApplication::applicationDirPath() + QString("/kdenlive_render");
206     if (!QFile::exists(m_renderer)) {
207         m_renderer = KStandardDirs::findExe("kdenlive_render");
208         if (m_renderer.isEmpty()) m_renderer = KStandardDirs::locate("exe", "kdenlive_render");
209         if (m_renderer.isEmpty()) m_renderer = "kdenlive_render";
210     }
211
212     QDBusConnectionInterface* interface = QDBusConnection::sessionBus().interface();
213     if (!interface || (!interface->isServiceRegistered("org.kde.ksmserver") && !interface->isServiceRegistered("org.gnome.SessionManager")))
214         m_view.shutdown->setEnabled(false);
215
216     focusFirstVisibleItem();
217 }
218
219 RenderWidget::~RenderWidget()
220 {
221     m_view.running_jobs->blockSignals(true);
222     m_view.scripts_list->blockSignals(true);
223     m_view.running_jobs->clear();
224     m_view.scripts_list->clear();
225     delete m_jobsDelegate;
226     delete m_scriptsDelegate;
227 }
228
229 void RenderWidget::slotEditItem(QListWidgetItem *item)
230 {
231     QString edit = item->data(EditableRole).toString();
232     if (edit.isEmpty() || !edit.endsWith("customprofiles.xml")) slotSaveProfile();
233     else slotEditProfile();
234 }
235
236 void RenderWidget::showInfoPanel()
237 {
238     if (m_view.advanced_params->isVisible()) {
239         m_view.advanced_params->setVisible(false);
240         m_view.buttonInfo->setDown(false);
241         KdenliveSettings::setShowrenderparams(false);
242     } else {
243         m_view.advanced_params->setVisible(true);
244         m_view.buttonInfo->setDown(true);
245         KdenliveSettings::setShowrenderparams(true);
246     }
247 }
248
249 void RenderWidget::setDocumentPath(const QString path)
250 {
251     if (m_view.out_file->url().directory() == KUrl(m_projectFolder).directory()) {
252         const QString fileName = m_view.out_file->url().fileName();
253         m_view.out_file->setUrl(KUrl(path + fileName));
254     }
255     m_projectFolder = path;
256     parseScriptFiles();
257
258 }
259
260 void RenderWidget::slotUpdateGuideBox()
261 {
262     m_view.guides_box->setVisible(m_view.render_guide->isChecked());
263 }
264
265 void RenderWidget::slotCheckStartGuidePosition()
266 {
267     if (m_view.guide_start->currentIndex() > m_view.guide_end->currentIndex())
268         m_view.guide_start->setCurrentIndex(m_view.guide_end->currentIndex());
269 }
270
271 void RenderWidget::slotCheckEndGuidePosition()
272 {
273     if (m_view.guide_end->currentIndex() < m_view.guide_start->currentIndex())
274         m_view.guide_end->setCurrentIndex(m_view.guide_start->currentIndex());
275 }
276
277 void RenderWidget::setGuides(QDomElement guidesxml, double duration)
278 {
279     m_view.guide_start->clear();
280     m_view.guide_end->clear();
281     QDomNodeList nodes = guidesxml.elementsByTagName("guide");
282     if (nodes.count() > 0) {
283         m_view.guide_start->addItem(i18n("Beginning"), "0");
284         m_view.render_guide->setEnabled(true);
285         m_view.create_chapter->setEnabled(true);
286     } else {
287         m_view.render_guide->setEnabled(false);
288         m_view.create_chapter->setEnabled(false);
289     }
290     double fps = (double) m_profile.frame_rate_num / m_profile.frame_rate_den;
291     for (int i = 0; i < nodes.count(); i++) {
292         QDomElement e = nodes.item(i).toElement();
293         if (!e.isNull()) {
294             GenTime pos = GenTime(e.attribute("time").toDouble());
295             const QString guidePos = Timecode::getStringTimecode(pos.frames(fps), fps);
296             m_view.guide_start->addItem(e.attribute("comment") + '/' + guidePos, e.attribute("time").toDouble());
297             m_view.guide_end->addItem(e.attribute("comment") + '/' + guidePos, e.attribute("time").toDouble());
298         }
299     }
300     if (nodes.count() > 0)
301         m_view.guide_end->addItem(i18n("End"), QString::number(duration));
302 }
303
304 /**
305  * Will be called when the user selects an output file via the file dialog.
306  * File extension will be added automatically.
307  */
308 void RenderWidget::slotUpdateButtons(KUrl url)
309 {
310     if (m_view.out_file->url().isEmpty()) {
311         m_view.buttonGenerateScript->setEnabled(false);
312         m_view.buttonRender->setEnabled(false);
313     } else {
314         updateButtons(); // This also checks whether the selected format is available
315     }
316     if (url != 0) {
317         QListWidgetItem *item = m_view.size_list->currentItem();
318         if (!item) {
319             m_view.buttonRender->setEnabled(false);
320             m_view.buttonGenerateScript->setEnabled(false);
321             return;
322         }
323         QString extension = item->data(ExtensionRole).toString();
324         url = filenameWithExtension(url, extension);
325         m_view.out_file->setUrl(url);
326     }
327 }
328
329 /**
330  * Will be called when the user changes the output file path in the text line.
331  * File extension must NOT be added, would make editing impossible!
332  */
333 void RenderWidget::slotUpdateButtons()
334 {
335     if (m_view.out_file->url().isEmpty()) {
336         m_view.buttonRender->setEnabled(false);
337         m_view.buttonGenerateScript->setEnabled(false);
338     } else {
339         updateButtons(); // This also checks whether the selected format is available
340     }
341 }
342
343 void RenderWidget::slotSaveProfile()
344 {
345     //TODO: update to correctly use metagroups
346     Ui::SaveProfile_UI ui;
347     QDialog *d = new QDialog(this);
348     ui.setupUi(d);
349
350     for (int i = 0; i < m_view.destination_list->count(); i++)
351         ui.destination_list->addItem(m_view.destination_list->itemIcon(i), m_view.destination_list->itemText(i), m_view.destination_list->itemData(i, Qt::UserRole));
352
353     ui.destination_list->setCurrentIndex(m_view.destination_list->currentIndex());
354     QString dest = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
355
356     QString customGroup = m_view.format_list->currentItem()->text();
357     if (customGroup.isEmpty()) customGroup = i18nc("Group Name", "Custom");
358     ui.group_name->setText(customGroup);
359
360     ui.parameters->setText(m_view.advanced_params->toPlainText());
361     ui.extension->setText(m_view.size_list->currentItem()->data(ExtensionRole).toString());
362     ui.profile_name->setFocus();
363
364     if (d->exec() == QDialog::Accepted && !ui.profile_name->text().simplified().isEmpty()) {
365         QString newProfileName = ui.profile_name->text().simplified();
366         QString newGroupName = ui.group_name->text().simplified();
367         if (newGroupName.isEmpty()) newGroupName = i18nc("Group Name", "Custom");
368         QString newMetaGroupId = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
369
370         QDomDocument doc;
371         QDomElement profileElement = doc.createElement("profile");
372         profileElement.setAttribute("name", newProfileName);
373         profileElement.setAttribute("category", newGroupName);
374         profileElement.setAttribute("destinationid", newMetaGroupId);
375         profileElement.setAttribute("extension", ui.extension->text().simplified());
376         profileElement.setAttribute("args", ui.parameters->toPlainText().simplified());
377         doc.appendChild(profileElement);
378         saveProfile(doc.documentElement());
379
380         parseProfiles(newMetaGroupId, newGroupName, newProfileName);
381     }
382     delete d;
383 }
384
385
386 void RenderWidget::saveProfile(QDomElement newprofile)
387 {
388     QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
389     QDomDocument doc;
390     QFile file(exportFile);
391     doc.setContent(&file, false);
392     file.close();
393     QDomElement documentElement;
394     QDomElement profiles = doc.documentElement();
395     if (profiles.isNull() || profiles.tagName() != "profiles") {
396         doc.clear();
397         profiles = doc.createElement("profiles");
398         profiles.setAttribute("version", 1);
399         doc.appendChild(profiles);
400     }
401     int version = profiles.attribute("version", 0).toInt();
402     if (version < 1) {
403         kDebug() << "// OLD profile version";
404         doc.clear();
405         profiles = doc.createElement("profiles");
406         profiles.setAttribute("version", 1);
407         doc.appendChild(profiles);
408     }
409
410
411     QDomNodeList profilelist = doc.elementsByTagName("profile");
412     int i = 0;
413     while (!profilelist.item(i).isNull()) {
414         // make sure a profile with same name doesn't exist
415         documentElement = profilelist.item(i).toElement();
416         QString profileName = documentElement.attribute("name");
417         if (profileName == newprofile.attribute("name")) {
418             // a profile with that same name already exists
419             bool ok;
420             QString 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, profileName, &ok);
421             if (!ok) return;
422             if (profileName == newProfileName) {
423                 profiles.removeChild(profilelist.item(i));
424                 break;
425             }
426         }
427         i++;
428     }
429
430     profiles.appendChild(newprofile);
431
432     //QCString save = doc.toString().utf8();
433
434     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
435         KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
436         return;
437     }
438     QTextStream out(&file);
439     out << doc.toString();
440     if (file.error() != QFile::NoError) {
441         KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
442         file.close();
443         return;
444     }
445     file.close();
446 }
447
448 void RenderWidget::slotCopyToFavorites()
449 {
450     QListWidgetItem *item = m_view.size_list->currentItem();
451     if (!item) return;
452     QString currentGroup = m_view.format_list->currentItem()->text();
453
454     QString params = item->data(ParamsRole).toString();
455     QString extension = item->data(ExtensionRole).toString();
456     QString currentProfile = item->text();
457     QDomDocument doc;
458     QDomElement profileElement = doc.createElement("profile");
459     profileElement.setAttribute("name", currentProfile);
460     profileElement.setAttribute("category", i18nc("Category Name", "Custom"));
461     profileElement.setAttribute("destinationid", "favorites");
462     profileElement.setAttribute("extension", extension);
463     profileElement.setAttribute("args", params);
464     doc.appendChild(profileElement);
465     saveProfile(doc.documentElement());
466     parseProfiles(m_view.destination_list->itemData(m_view.destination_list->currentIndex(), Qt::UserRole).toString(), currentGroup, currentProfile);
467 }
468
469 void RenderWidget::slotEditProfile()
470 {
471     QListWidgetItem *item = m_view.size_list->currentItem();
472     if (!item) return;
473     QString currentGroup = m_view.format_list->currentItem()->text();
474
475     QString params = item->data(ParamsRole).toString();
476     QString extension = item->data(ExtensionRole).toString();
477     QString currentProfile = item->text();
478
479     Ui::SaveProfile_UI ui;
480     QDialog *d = new QDialog(this);
481     ui.setupUi(d);
482
483     for (int i = 0; i < m_view.destination_list->count(); i++)
484         ui.destination_list->addItem(m_view.destination_list->itemIcon(i), m_view.destination_list->itemText(i), m_view.destination_list->itemData(i, Qt::UserRole));
485
486     ui.destination_list->setCurrentIndex(m_view.destination_list->currentIndex());
487     QString dest = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
488
489     QString customGroup = m_view.format_list->currentItem()->text();
490     if (customGroup.isEmpty()) customGroup = i18nc("Group Name", "Custom");
491     ui.group_name->setText(customGroup);
492
493     ui.profile_name->setText(currentProfile);
494     ui.extension->setText(extension);
495     ui.parameters->setText(params);
496     ui.profile_name->setFocus();
497     d->setWindowTitle(i18n("Edit Profile"));
498     if (d->exec() == QDialog::Accepted) {
499         slotDeleteProfile(false);
500         QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
501         QDomDocument doc;
502         QFile file(exportFile);
503         doc.setContent(&file, false);
504         file.close();
505         QDomElement documentElement;
506         QDomElement profiles = doc.documentElement();
507
508         if (profiles.isNull() || profiles.tagName() != "profiles") {
509             doc.clear();
510             profiles = doc.createElement("profiles");
511             profiles.setAttribute("version", 1);
512             doc.appendChild(profiles);
513         }
514
515         int version = profiles.attribute("version", 0).toInt();
516         if (version < 1) {
517             kDebug() << "// OLD profile version";
518             doc.clear();
519             profiles = doc.createElement("profiles");
520             profiles.setAttribute("version", 1);
521             doc.appendChild(profiles);
522         }
523
524         QString newProfileName = ui.profile_name->text().simplified();
525         QString newGroupName = ui.group_name->text().simplified();
526         if (newGroupName.isEmpty()) newGroupName = i18nc("Group Name", "Custom");
527         QString newMetaGroupId = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
528         QDomNodeList profilelist = doc.elementsByTagName("profile");
529         int i = 0;
530         while (!profilelist.item(i).isNull()) {
531             // make sure a profile with same name doesn't exist
532             documentElement = profilelist.item(i).toElement();
533             QString profileName = documentElement.attribute("name");
534             if (profileName == newProfileName) {
535                 // a profile with that same name already exists
536                 bool ok;
537                 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);
538                 if (!ok) return;
539                 if (profileName == newProfileName) {
540                     profiles.removeChild(profilelist.item(i));
541                     break;
542                 }
543             }
544             i++;
545         }
546
547         QDomElement profileElement = doc.createElement("profile");
548         profileElement.setAttribute("name", newProfileName);
549         profileElement.setAttribute("category", newGroupName);
550         profileElement.setAttribute("destinationid", newMetaGroupId);
551         profileElement.setAttribute("extension", ui.extension->text().simplified());
552         profileElement.setAttribute("args", ui.parameters->toPlainText().simplified());
553         profiles.appendChild(profileElement);
554
555         //QCString save = doc.toString().utf8();
556         delete d;
557         if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
558             KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
559             return;
560         }
561         QTextStream out(&file);
562         out << doc.toString();
563         if (file.error() != QFile::NoError) {
564             KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
565             file.close();
566             return;
567         }
568         file.close();
569         parseProfiles(newMetaGroupId, newGroupName, newProfileName);
570     } else delete d;
571 }
572
573 void RenderWidget::slotDeleteProfile(bool refresh)
574 {
575     //TODO: delete a profile installed by KNewStuff the easy way
576     /*
577     QString edit = m_view.size_list->currentItem()->data(EditableRole).toString();
578     if (!edit.endsWith("customprofiles.xml")) {
579         // This is a KNewStuff installed file, process through KNS
580         KNS::Engine engine(0);
581         if (engine.init("kdenlive_render.knsrc")) {
582             KNS::Entry::List entries;
583         }
584         return;
585     }*/
586     QString currentGroup = m_view.format_list->currentItem()->text();
587     QString currentProfile = m_view.size_list->currentItem()->text();
588     QString metaGroupId = m_view.destination_list->itemData(m_view.destination_list->currentIndex(), Qt::UserRole).toString();
589
590     QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
591     QDomDocument doc;
592     QFile file(exportFile);
593     doc.setContent(&file, false);
594     file.close();
595
596     QDomElement documentElement;
597     QDomNodeList profiles = doc.elementsByTagName("profile");
598     int i = 0;
599     QString groupName;
600     QString profileName;
601     QString destination;
602
603     while (!profiles.item(i).isNull()) {
604         documentElement = profiles.item(i).toElement();
605         profileName = documentElement.attribute("name");
606         groupName = documentElement.attribute("category");
607         destination = documentElement.attribute("destinationid");
608
609         if (profileName == currentProfile && groupName == currentGroup && destination == metaGroupId) {
610             kDebug() << "// GOT it: " << profileName;
611             doc.documentElement().removeChild(profiles.item(i));
612             break;
613         }
614         i++;
615     }
616
617     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
618         KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
619         return;
620     }
621     QTextStream out(&file);
622     out << doc.toString();
623     if (file.error() != QFile::NoError) {
624         KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
625         file.close();
626         return;
627     }
628     file.close();
629     if (refresh) {
630         parseProfiles(metaGroupId, currentGroup);
631         focusFirstVisibleItem();
632     }
633 }
634
635 void RenderWidget::updateButtons()
636 {
637     if (!m_view.size_list->currentItem() || m_view.size_list->currentItem()->isHidden()) {
638         m_view.buttonSave->setEnabled(false);
639         m_view.buttonDelete->setEnabled(false);
640         m_view.buttonEdit->setEnabled(false);
641         m_view.buttonRender->setEnabled(false);
642         m_view.buttonGenerateScript->setEnabled(false);
643     } else {
644         m_view.buttonSave->setEnabled(true);
645         m_view.buttonRender->setEnabled(m_view.size_list->currentItem()->toolTip().isEmpty());
646         m_view.buttonGenerateScript->setEnabled(m_view.size_list->currentItem()->toolTip().isEmpty());
647         QString edit = m_view.size_list->currentItem()->data(EditableRole).toString();
648         if (edit.isEmpty() || !edit.endsWith("customprofiles.xml")) {
649             m_view.buttonDelete->setEnabled(false);
650             m_view.buttonEdit->setEnabled(false);
651         } else {
652             m_view.buttonDelete->setEnabled(true);
653             m_view.buttonEdit->setEnabled(true);
654         }
655     }
656 }
657
658
659 void RenderWidget::focusFirstVisibleItem()
660 {
661     if (m_view.size_list->currentItem()) {
662         updateButtons();
663         return;
664     }
665     m_view.size_list->setCurrentRow(0);
666     updateButtons();
667 }
668
669 void RenderWidget::slotPrepareExport(bool scriptExport)
670 {
671     if (!QFile::exists(KdenliveSettings::rendererpath())) {
672         KMessageBox::sorry(this, i18n("Cannot find the melt program required for rendering (part of Mlt)"));
673         return;
674     }
675     if (m_view.play_after->isChecked() && KdenliveSettings::defaultplayerapp().isEmpty()) {
676         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."));
677     }
678     QString chapterFile;
679     if (m_view.create_chapter->isChecked()) chapterFile = m_view.out_file->url().path() + ".dvdchapter";
680
681     // mantisbt 1051
682     if (!KStandardDirs::makeDir(m_view.out_file->url().directory())) {
683         KMessageBox::sorry(this, i18n("The directory %1, could not be created.\nPlease make sure you have the required permissions.", m_view.out_file->url().directory()));
684         return;
685     }
686
687     emit prepareRenderingData(scriptExport, m_view.render_zone->isChecked(), chapterFile);
688 }
689
690
691 void RenderWidget::slotExport(bool scriptExport, int zoneIn, int zoneOut, const QString &playlistPath, const QString &scriptPath, bool exportAudio)
692 {
693     QListWidgetItem *item = m_view.size_list->currentItem();
694     if (!item) return;
695
696     QString dest = m_view.out_file->url().path();
697     if (dest.isEmpty()) return;
698
699     // Check whether target file has an extension.
700     // If not, ask whether extension should be added or not.
701     QString extension = item->data(ExtensionRole).toString();
702     if (!dest.endsWith(extension, Qt::CaseInsensitive)) {
703         if (KMessageBox::questionYesNo(this, i18n("File has no extension. Add extension (%1)?", extension)) == KMessageBox::Yes) {
704             dest.append("." + extension);
705         }
706     }
707
708     QFile f(dest);
709     if (f.exists()) {
710         if (KMessageBox::warningYesNo(this, i18n("Output file already exists. Do you want to overwrite it?")) != KMessageBox::Yes)
711             return;
712     }
713
714     QStringList overlayargs;
715     if (m_view.tc_overlay->isChecked()) {
716         QString filterFile = KStandardDirs::locate("appdata", "metadata.properties");
717         overlayargs << "meta.attr.timecode=1" << "meta.attr.timecode.markup=#" + QString(m_view.tc_type->currentIndex() ? "frame" : "timecode");
718         overlayargs << "-attach" << "data_feed:attr_check" << "-attach";
719         overlayargs << "data_show:" + filterFile << "_loader=1" << "dynamic=1";
720     }
721
722     QStringList render_process_args;
723
724     if (!scriptExport) render_process_args << "-erase";
725     if (KdenliveSettings::usekuiserver()) render_process_args << "-kuiserver";
726
727     double guideStart = 0;
728     double guideEnd = 0;
729
730     if (m_view.render_zone->isChecked()) render_process_args << "in=" + QString::number(zoneIn) << "out=" + QString::number(zoneOut);
731     else if (m_view.render_guide->isChecked()) {
732         double fps = (double) m_profile.frame_rate_num / m_profile.frame_rate_den;
733         guideStart = m_view.guide_start->itemData(m_view.guide_start->currentIndex()).toDouble();
734         guideEnd = m_view.guide_end->itemData(m_view.guide_end->currentIndex()).toDouble();
735         render_process_args << "in=" + QString::number(GenTime(guideStart).frames(fps)) << "out=" + QString::number(GenTime(guideEnd).frames(fps));
736     }
737
738     if (!overlayargs.isEmpty()) render_process_args << "preargs=" + overlayargs.join(" ");
739
740     render_process_args << KdenliveSettings::rendererpath() << m_profile.path << item->data(RenderRole).toString();
741     if (m_view.play_after->isChecked()) render_process_args << KdenliveSettings::KdenliveSettings::defaultplayerapp();
742     else render_process_args << "-";
743
744     QString renderArgs = m_view.advanced_params->toPlainText().simplified();
745
746     // Adjust frame scale
747     int width;
748     int height;
749     if (m_view.rescale->isChecked() && m_view.rescale->isEnabled()) {
750         width = m_view.rescale_width->value();
751         height = m_view.rescale_height->value();
752     } else {
753         width = m_profile.width;
754         height = m_profile.height;
755     }
756     renderArgs.replace("%dar", '@' + QString::number(m_profile.display_aspect_num) + '/' + QString::number(m_profile.display_aspect_den));
757
758     // Adjust scanning
759     if (m_view.scanning_list->currentIndex() == 1) renderArgs.append(" progressive=1");
760     else if (m_view.scanning_list->currentIndex() == 2) renderArgs.append(" progressive=0");
761
762     // disable audio if requested
763     if (!exportAudio) renderArgs.append(" an=1 ");
764
765         // Set the thread counts
766         renderArgs.append(QString(" threads=%1").arg(KdenliveSettings::encodethreads()));
767         renderArgs.append(QString(" real_time=-%1").arg(KdenliveSettings::mltthreads()));
768
769     // Check if the rendering profile is different from project profile,
770     // in which case we need to use the producer_comsumer from MLT
771     QString std = renderArgs;
772     QString destination = m_view.destination_list->itemData(m_view.destination_list->currentIndex()).toString();
773     const QString currentSize = QString::number(width) + 'x' + QString::number(height);
774     QString subsize = currentSize;
775     if (std.startsWith("s=")) {
776         subsize = std.section(' ', 0, 0).toLower();
777         subsize = subsize.section('=', 1, 1);
778     } else if (std.contains(" s=")) {
779         subsize = std.section(" s=", 1, 1);
780         subsize = subsize.section(' ', 0, 0).toLower();
781     } else if (destination != "audioonly" && m_view.rescale->isChecked() && m_view.rescale->isEnabled()) {
782         subsize = QString(" s=%1x%2").arg(width).arg(height);
783         // Add current size parameter
784         renderArgs.append(subsize);
785     }
786     bool resizeProfile = (subsize != currentSize);
787     QStringList paramsList = renderArgs.split(" ", QString::SkipEmptyParts);
788     for (int i = 0; i < paramsList.count(); i++) {
789         if (paramsList.at(i).startsWith("profile=")) {
790             if (paramsList.at(i).section('=', 1) != m_profile.path) resizeProfile = true;
791             break;
792         }
793     }
794         
795     if (resizeProfile)
796         render_process_args << "consumer:" + (scriptExport ? "$SOURCE" : playlistPath);
797     else
798         render_process_args <<  (scriptExport ? "$SOURCE" : playlistPath);
799     render_process_args << (scriptExport ? "$TARGET" : dest);
800     render_process_args << paramsList;
801
802     QString group = m_view.size_list->currentItem()->data(MetaGroupRole).toString();
803
804     QStringList renderParameters;
805     renderParameters << dest << item->data(RenderRole).toString() << renderArgs.simplified();
806     renderParameters << QString::number(zoneIn) << QString::number(zoneOut) << QString::number(m_view.play_after->isChecked());
807     renderParameters << QString::number(guideStart) << QString::number(guideEnd) << QString::number(resizeProfile);
808
809     QString scriptName;
810     if (scriptExport) {
811         // Generate script file
812         QFile file(scriptPath);
813         if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
814             KMessageBox::error(this, i18n("Cannot write to file %1", scriptPath));
815             return;
816         }
817         QTextStream outStream(&file);
818         outStream << "#! /bin/sh" << "\n" << "\n";
819         outStream << "SOURCE=" << "\"" + playlistPath + "\"" << "\n";
820         outStream << "TARGET=" << "\"" + dest + "\"" << "\n";
821         outStream << "RENDERER=" << "\"" + m_renderer + "\"" << "\n";
822         outStream << "MELT=" << "\"" + render_process_args.takeFirst() + "\"" << "\n";
823         outStream << "PARAMETERS=" << "\"" + render_process_args.join(" ") + "\"" << "\n";
824         outStream << "$RENDERER $MELT $PARAMETERS" << "\n" << "\n";
825         if (file.error() != QFile::NoError) {
826             KMessageBox::error(this, i18n("Cannot write to file %1", scriptPath));
827             file.close();
828             return;
829         }
830         file.close();
831         QFile::setPermissions(scriptPath, file.permissions() | QFile::ExeUser);
832
833         QTimer::singleShot(400, this, SLOT(parseScriptFiles()));
834         m_view.tabWidget->setCurrentIndex(2);
835         return;
836     }
837     renderParameters << scriptName;
838
839     // Save rendering profile to document
840     QMap <QString, QString> renderProps;
841     renderProps.insert("renderdestination", m_view.size_list->currentItem()->data(MetaGroupRole).toString());
842     renderProps.insert("rendercategory", m_view.size_list->currentItem()->data(GroupRole).toString());
843     renderProps.insert("renderprofile", m_view.size_list->currentItem()->text());
844     renderProps.insert("renderurl", dest);
845     renderProps.insert("renderzone", QString::number(m_view.render_zone->isChecked()));
846     renderProps.insert("renderguide", QString::number(m_view.render_guide->isChecked()));
847     renderProps.insert("renderstartguide", QString::number(m_view.guide_start->currentIndex()));
848     renderProps.insert("renderendguide", QString::number(m_view.guide_end->currentIndex()));
849     renderProps.insert("renderendguide", QString::number(m_view.guide_end->currentIndex()));
850     renderProps.insert("renderscanning", QString::number(m_view.scanning_list->currentIndex()));
851     int export_audio = 0;
852     if (m_view.export_audio->checkState() == Qt::Checked) export_audio = 2;
853     else if (m_view.export_audio->checkState() == Qt::Unchecked) export_audio = 1;
854     renderProps.insert("renderexportaudio", QString::number(export_audio));
855     renderProps.insert("renderrescale", QString::number(m_view.rescale->isChecked()));
856     renderProps.insert("renderrescalewidth", QString::number(m_view.rescale_width->value()));
857     renderProps.insert("renderrescaleheight", QString::number(m_view.rescale_height->value()));
858     renderProps.insert("rendertcoverlay", QString::number(m_view.tc_overlay->isChecked()));
859     renderProps.insert("rendertctype", QString::number(m_view.tc_type->currentIndex()));
860     renderProps.insert("renderratio", QString::number(m_view.rescale_keep->isChecked()));
861     renderProps.insert("renderplay", QString::number(m_view.play_after->isChecked()));
862
863     emit selectedRenderProfile(renderProps);
864
865     // insert item in running jobs list
866     QTreeWidgetItem *renderItem;
867     QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1);
868     if (!existing.isEmpty()) {
869         renderItem = existing.at(0);
870         if (renderItem->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB) {
871             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"));
872             return;
873         }
874         renderItem->setData(1, Qt::UserRole + 4, QString());
875     } else {
876         renderItem = new QTreeWidgetItem(m_view.running_jobs, QStringList() << QString() << dest << QString());
877     }
878     renderItem->setData(1, Qt::UserRole + 2, WAITINGJOB);
879     renderItem->setIcon(0, KIcon("media-playback-pause"));
880     renderItem->setData(1, Qt::UserRole, i18n("Waiting..."));
881     renderItem->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
882     renderItem->setData(1, Qt::UserRole + 1, QTime::currentTime());
883
884     // Set rendering type
885     if (group == "dvd") {
886         if (m_view.open_dvd->isChecked()) {
887             renderItem->setData(0, Qt::UserRole, group);
888             if (renderArgs.contains("profile=")) {
889                 // rendering profile contains an MLT profile, so pass it to the running jog item, useful for dvd
890                 QString prof = renderArgs.section("profile=", 1, 1);
891                 prof = prof.section(' ', 0, 0);
892                 kDebug() << "// render profile: " << prof;
893                 renderItem->setData(0, Qt::UserRole + 1, prof);
894             }
895         }
896     } else {
897         if (group == "websites" && m_view.open_browser->isChecked()) {
898             renderItem->setData(0, Qt::UserRole, group);
899             // pass the url
900             QString url = m_view.size_list->currentItem()->data(ExtraRole).toString();
901             renderItem->setData(0, Qt::UserRole + 1, url);
902         }
903     }
904     renderItem->setData(1, Qt::UserRole + 3, render_process_args);
905     if (exportAudio == false) renderItem->setData(1, Qt::UserRole + 5, i18n("Video without audio track"));
906     else  renderItem->setData(1, Qt::UserRole + 5, QString());
907     m_view.running_jobs->setCurrentItem(renderItem);
908     m_view.tabWidget->setCurrentIndex(1);
909     checkRenderStatus();
910 }
911
912 void RenderWidget::checkRenderStatus()
913 {
914     // check if we have a job waiting to render
915     if (m_blockProcessing) return;
916     QTreeWidgetItem *item = m_view.running_jobs->topLevelItem(0);
917     while (item) {
918         if (item->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB) return;
919         item = m_view.running_jobs->itemBelow(item);
920     }
921     item = m_view.running_jobs->topLevelItem(0);
922     bool waitingJob = false;
923     while (item) {
924         if (item->data(1, Qt::UserRole + 2).toInt() == WAITINGJOB) {
925             item->setData(1, Qt::UserRole + 1, QTime::currentTime());
926             waitingJob = true;
927             startRendering(item);
928             break;
929         }
930         item = m_view.running_jobs->itemBelow(item);
931     }
932     if (waitingJob == false && m_view.shutdown->isChecked()) emit shutdown();
933 }
934
935 void RenderWidget::startRendering(QTreeWidgetItem *item)
936 {
937     if (item->data(1, Qt::UserRole + 4).isNull()) {
938         // Normal render process
939         if (QProcess::startDetached(m_renderer, item->data(1, Qt::UserRole + 3).toStringList()) == false) {
940             item->setData(1, Qt::UserRole + 2, FINISHEDJOB);
941             item->setData(1, Qt::UserRole, i18n("Rendering crashed"));
942             item->setIcon(0, KIcon("dialog-close"));
943             item->setData(2, Qt::UserRole, 100);
944         } else KNotification::event("RenderStarted", i18n("Rendering <i>%1</i> started", item->text(1)), QPixmap(), this);
945     } else {
946         // Script item
947         if (QProcess::startDetached(item->data(1, Qt::UserRole + 3).toString()) == false) {
948             item->setData(1, Qt::UserRole + 2, FINISHEDJOB);
949             item->setData(1, Qt::UserRole, i18n("Rendering crashed"));
950             item->setIcon(0, KIcon("dialog-close"));
951             item->setData(2, Qt::UserRole, 100);
952         }
953     }
954 }
955
956 int RenderWidget::waitingJobsCount() const
957 {
958     int count = 0;
959     QTreeWidgetItem *item = m_view.running_jobs->topLevelItem(0);
960     while (item) {
961         if (item->data(1, Qt::UserRole + 2).toInt() == WAITINGJOB) count++;
962         item = m_view.running_jobs->itemBelow(item);
963     }
964     return count;
965 }
966
967 void RenderWidget::setProfile(MltVideoProfile profile)
968 {
969     m_profile = profile;
970     //WARNING: this way to tell the video standard is a bit hackish...
971     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);
972     else m_view.format_selection->setCurrentIndex(1);
973     m_view.scanning_list->setCurrentIndex(0);
974     m_view.rescale_width->setValue(KdenliveSettings::defaultrescalewidth());
975     if (!m_view.rescale_keep->isChecked()) {
976         m_view.rescale_height->blockSignals(true);
977         m_view.rescale_height->setValue(KdenliveSettings::defaultrescaleheight());
978         m_view.rescale_height->blockSignals(false);
979     }
980     refreshView();
981 }
982
983 void RenderWidget::refreshCategory()
984 {
985     m_view.format_list->blockSignals(true);
986     m_view.format_list->clear();
987     QListWidgetItem *sizeItem;
988
989     QString destination;
990     if (m_view.destination_list->currentIndex() > 0)
991         destination = m_view.destination_list->itemData(m_view.destination_list->currentIndex()).toString();
992
993
994     if (destination == "dvd") {
995         m_view.open_dvd->setVisible(true);
996         m_view.create_chapter->setVisible(true);
997     } else {
998         m_view.open_dvd->setVisible(false);
999         m_view.create_chapter->setVisible(false);
1000     }
1001
1002     if (destination == "websites")
1003         m_view.open_browser->setVisible(true);
1004     else
1005         m_view.open_browser->setVisible(false);
1006
1007     // hide groups that are not in the correct destination
1008     for (int i = 0; i < m_renderCategory.count(); i++) {
1009         sizeItem = m_renderCategory.at(i);
1010         if (sizeItem->data(MetaGroupRole).toString() == destination) {
1011             m_view.format_list->addItem(sizeItem->clone());
1012             //kDebug() << "// SET GRP:: " << sizeItem->text() << ", METY:" << sizeItem->data(MetaGroupRole).toString();
1013         }
1014     }
1015
1016     // activate first visible item
1017     QListWidgetItem * item = m_view.format_list->currentItem();
1018     if (!item) {
1019         m_view.format_list->setCurrentRow(0);
1020         item = m_view.format_list->currentItem();
1021     }
1022     if (!item) {
1023         m_view.format_list->setEnabled(false);
1024         m_view.size_list->setEnabled(false);
1025         m_view.size_list->blockSignals(false);
1026         m_view.format_list->blockSignals(false);
1027         return;
1028     } else {
1029         m_view.format_list->setEnabled(true);
1030         m_view.size_list->setEnabled(true);
1031     }
1032
1033     if (m_view.format_list->count() > 1)
1034         m_view.format_list->setVisible(true);
1035     else
1036         m_view.format_list->setVisible(false);
1037     refreshView();
1038 }
1039
1040 void RenderWidget::refreshView()
1041 {
1042     if (!m_view.format_list->currentItem()) return;
1043     m_view.size_list->blockSignals(true);
1044     m_view.size_list->clear();
1045     QListWidgetItem *sizeItem;
1046     QString std;
1047     QString group = m_view.format_list->currentItem()->text();
1048     QString destination;
1049     if (m_view.destination_list->currentIndex() > 0)
1050         destination = m_view.destination_list->itemData(m_view.destination_list->currentIndex()).toString();
1051     KIcon brokenIcon("dialog-close");
1052
1053     const QStringList formatsList = KdenliveSettings::supportedformats();
1054     const QStringList vcodecsList = KdenliveSettings::videocodecs();
1055     const QStringList acodecsList = KdenliveSettings::audiocodecs();
1056
1057     KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window);
1058     const QColor disabled = scheme.foreground(KColorScheme::InactiveText).color();
1059     const QColor disabledbg = scheme.background(KColorScheme::NegativeBackground).color();
1060
1061
1062     for (int i = 0; i < m_renderItems.count(); i++) {
1063         sizeItem = m_renderItems.at(i);
1064         QListWidgetItem *dupItem = NULL;
1065         if ((sizeItem->data(GroupRole).toString() == group || sizeItem->data(GroupRole).toString().isEmpty()) && sizeItem->data(MetaGroupRole).toString() == destination) {
1066             std = sizeItem->data(StandardRole).toString();
1067             if (!std.isEmpty()) {
1068                 if (std.contains("PAL", Qt::CaseInsensitive) && m_view.format_selection->currentIndex() == 0) dupItem = sizeItem->clone();
1069                 else if (std.contains("NTSC", Qt::CaseInsensitive) && m_view.format_selection->currentIndex() == 1)  dupItem = sizeItem->clone();
1070             } else {
1071                 dupItem = sizeItem->clone();
1072             }
1073
1074             if (dupItem) {
1075                 m_view.size_list->addItem(dupItem);
1076                 // Make sure the selected profile uses an installed avformat codec / format
1077                 std = dupItem->data(ParamsRole).toString();
1078                 if (!formatsList.isEmpty()) {
1079                     QString format;
1080                     if (std.startsWith("f=")) format = std.section("f=", 1, 1);
1081                     else if (std.contains(" f=")) format = std.section(" f=", 1, 1);
1082                     if (!format.isEmpty()) {
1083                         format = format.section(' ', 0, 0).toLower();
1084                         if (!formatsList.contains(format)) {
1085                             kDebug() << "***** UNSUPPORTED F: " << format;
1086                             //sizeItem->setHidden(true);
1087                             //sizeItem->setFlags(Qt::ItemIsSelectable);
1088                             dupItem->setToolTip(i18n("Unsupported video format: %1", format));
1089                             dupItem->setIcon(brokenIcon);
1090                             dupItem->setForeground(disabled);
1091                         }
1092                     }
1093                 }
1094                 if (!acodecsList.isEmpty()) {
1095                     QString format;
1096                     if (std.startsWith("acodec=")) format = std.section("acodec=", 1, 1);
1097                     else if (std.contains(" acodec=")) format = std.section(" acodec=", 1, 1);
1098                     if (!format.isEmpty()) {
1099                         format = format.section(' ', 0, 0).toLower();
1100                         if (!acodecsList.contains(format)) {
1101                             kDebug() << "*****  UNSUPPORTED ACODEC: " << format;
1102                             //sizeItem->setHidden(true);
1103                             //sizeItem->setFlags(Qt::ItemIsSelectable);
1104                             dupItem->setToolTip(i18n("Unsupported audio codec: %1", format));
1105                             dupItem->setIcon(brokenIcon);
1106                             dupItem->setForeground(disabled);
1107                             dupItem->setBackground(disabledbg);
1108                         }
1109                     }
1110                 }
1111                 if (!vcodecsList.isEmpty()) {
1112                     QString format;
1113                     if (std.startsWith("vcodec=")) format = std.section("vcodec=", 1, 1);
1114                     else if (std.contains(" vcodec=")) format = std.section(" vcodec=", 1, 1);
1115                     if (!format.isEmpty()) {
1116                         format = format.section(' ', 0, 0).toLower();
1117                         if (!vcodecsList.contains(format)) {
1118                             kDebug() << "*****  UNSUPPORTED VCODEC: " << format;
1119                             //sizeItem->setHidden(true);
1120                             //sizeItem->setFlags(Qt::ItemIsSelectable);
1121                             dupItem->setToolTip(i18n("Unsupported video codec: %1", format));
1122                             dupItem->setIcon(brokenIcon);
1123                             dupItem->setForeground(disabled);
1124                         }
1125                     }
1126                 }
1127             }
1128         }
1129     }
1130     // m_view.size_list->sortItems();
1131     focusFirstVisibleItem();
1132     m_view.size_list->blockSignals(false);
1133     m_view.format_list->blockSignals(false);
1134     refreshParams();
1135 }
1136
1137 KUrl RenderWidget::filenameWithExtension(KUrl url, QString extension)
1138 {
1139     if (url.isEmpty()) url = KUrl(m_projectFolder);
1140     QString directory = url.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash);
1141     QString filename = url.fileName(KUrl::ObeyTrailingSlash);
1142     QString ext;
1143
1144     if (extension.at(0) == '.') ext = extension;
1145     else ext = '.' + extension;
1146
1147     if (filename.isEmpty()) filename = i18n("untitled");
1148
1149     int pos = filename.lastIndexOf('.');
1150     if (pos == 0) filename.append(ext);
1151     else {
1152         if (!filename.endsWith(ext, Qt::CaseInsensitive)) {
1153             filename = filename.left(pos) + ext;
1154         }
1155     }
1156
1157     return KUrl(directory + filename);
1158 }
1159
1160 void RenderWidget::refreshParams()
1161 {
1162     // Format not available (e.g. codec not installed); Disable start button
1163     QListWidgetItem *item = m_view.size_list->currentItem();
1164     if (!item || item->isHidden()) {
1165         m_view.advanced_params->clear();
1166         m_view.buttonRender->setEnabled(false);
1167         m_view.buttonGenerateScript->setEnabled(false);
1168         return;
1169     }
1170     QString params = item->data(ParamsRole).toString();
1171     QString extension = item->data(ExtensionRole).toString();
1172     m_view.advanced_params->setPlainText(params);
1173     QString destination = m_view.destination_list->itemData(m_view.destination_list->currentIndex()).toString();
1174     if (params.contains(" s=") || params.startsWith("s=") || destination == "audioonly") {
1175         // profile has a fixed size, do not allow resize
1176         m_view.rescale->setEnabled(false);
1177         m_view.rescale_box->setEnabled(false);
1178     } else {
1179         m_view.rescale->setEnabled(true);
1180         m_view.rescale_box->setEnabled(m_view.rescale->isChecked());
1181     }
1182     KUrl url = filenameWithExtension(m_view.out_file->url(), extension);
1183     m_view.out_file->setUrl(url);
1184 //     if (!url.isEmpty()) {
1185 //         QString path = url.path();
1186 //         int pos = path.lastIndexOf('.') + 1;
1187 //  if (pos == 0) path.append('.' + extension);
1188 //         else path = path.left(pos) + extension;
1189 //         m_view.out_file->setUrl(KUrl(path));
1190 //     } else {
1191 //         m_view.out_file->setUrl(KUrl(QDir::homePath() + "/untitled." + extension));
1192 //     }
1193     m_view.out_file->setFilter("*." + extension);
1194     QString edit = item->data(EditableRole).toString();
1195     if (edit.isEmpty() || !edit.endsWith("customprofiles.xml")) {
1196         m_view.buttonDelete->setEnabled(false);
1197         m_view.buttonEdit->setEnabled(false);
1198     } else {
1199         m_view.buttonDelete->setEnabled(true);
1200         m_view.buttonEdit->setEnabled(true);
1201     }
1202
1203     m_view.buttonRender->setEnabled(m_view.size_list->currentItem()->toolTip().isEmpty());
1204     m_view.buttonGenerateScript->setEnabled(m_view.size_list->currentItem()->toolTip().isEmpty());
1205 }
1206
1207 void RenderWidget::reloadProfiles()
1208 {
1209     parseProfiles();
1210 }
1211
1212 void RenderWidget::parseProfiles(QString meta, QString group, QString profile)
1213 {
1214     m_view.size_list->blockSignals(true);
1215     m_view.format_list->blockSignals(true);
1216     m_view.size_list->clear();
1217     m_view.format_list->clear();
1218     m_view.destination_list->clear();
1219     qDeleteAll(m_renderItems);
1220     qDeleteAll(m_renderCategory);
1221     m_renderItems.clear();
1222     m_renderCategory.clear();
1223     m_view.destination_list->addItem(KIcon("video-x-generic"), i18n("File rendering"));
1224     m_view.destination_list->addItem(KIcon("favorites"), i18n("Favorites"), "favorites");
1225     m_view.destination_list->addItem(KIcon("media-optical"), i18n("DVD"), "dvd");
1226     m_view.destination_list->addItem(KIcon("audio-x-generic"), i18n("Audio only"), "audioonly");
1227     m_view.destination_list->addItem(KIcon("applications-internet"), i18n("Web sites"), "websites");
1228     m_view.destination_list->addItem(KIcon("applications-multimedia"), i18n("Media players"), "mediaplayers");
1229     m_view.destination_list->addItem(KIcon("drive-harddisk"), i18n("Lossless / HQ"), "lossless");
1230     m_view.destination_list->addItem(KIcon("pda"), i18n("Mobile devices"), "mobile");
1231
1232     QString exportFile = KStandardDirs::locate("appdata", "export/profiles.xml");
1233     parseFile(exportFile, false);
1234
1235
1236     QString exportFolder = KStandardDirs::locateLocal("appdata", "export/");
1237     QDir directory = QDir(exportFolder);
1238     QStringList filter;
1239     filter << "*.xml";
1240     QStringList fileList = directory.entryList(filter, QDir::Files);
1241     // We should parse customprofiles.xml in last position, so that user profiles
1242     // can also override profiles installed by KNewStuff
1243     fileList.removeAll("customprofiles.xml");
1244     foreach(const QString &filename, fileList)
1245         parseFile(exportFolder + filename, true);
1246     if (QFile::exists(exportFolder + "customprofiles.xml")) parseFile(exportFolder + "customprofiles.xml", true);
1247
1248     if (!meta.isEmpty()) {
1249         m_view.destination_list->blockSignals(true);
1250         m_view.destination_list->setCurrentIndex(m_view.destination_list->findData(meta));
1251         m_view.destination_list->blockSignals(false);
1252     }
1253     refreshCategory();
1254     QList<QListWidgetItem *> child;
1255     if (!group.isEmpty()) child = m_view.format_list->findItems(group, Qt::MatchExactly);
1256     if (!child.isEmpty()) {
1257         for (int i = 0; i < child.count(); i++) {
1258             if (child.at(i)->data(MetaGroupRole).toString() == meta) {
1259                 m_view.format_list->setCurrentItem(child.at(i));
1260                 break;
1261             }
1262         }
1263     }
1264     child.clear();
1265     m_view.size_list->blockSignals(false);
1266     m_view.format_list->blockSignals(false);
1267     if (!profile.isEmpty()) child = m_view.size_list->findItems(profile, Qt::MatchExactly);
1268     if (!child.isEmpty()) m_view.size_list->setCurrentItem(child.at(0));
1269 }
1270
1271 void RenderWidget::parseFile(QString exportFile, bool editable)
1272 {
1273     kDebug() << "// Parsing file: " << exportFile;
1274     kDebug() << "------------------------------";
1275     QDomDocument doc;
1276     QFile file(exportFile);
1277     doc.setContent(&file, false);
1278     file.close();
1279     QDomElement documentElement;
1280     QDomElement profileElement;
1281     QString extension;
1282     QDomNodeList groups = doc.elementsByTagName("group");
1283     QListWidgetItem *item = NULL;
1284     const QStringList acodecsList = KdenliveSettings::audiocodecs();
1285     bool replaceVorbisCodec = false;
1286     if (acodecsList.contains("libvorbis")) replaceVorbisCodec = true;
1287     bool replaceLibfaacCodec = false;
1288     if (!acodecsList.contains("aac") && acodecsList.contains("libfaac")) replaceLibfaacCodec = true;
1289
1290
1291     if (editable || groups.count() == 0) {
1292         QDomElement profiles = doc.documentElement();
1293         if (editable && profiles.attribute("version", 0).toInt() < 1) {
1294             kDebug() << "// OLD profile version";
1295             // this is an old profile version, update it
1296             QDomDocument newdoc;
1297             QDomElement newprofiles = newdoc.createElement("profiles");
1298             newprofiles.setAttribute("version", 1);
1299             newdoc.appendChild(newprofiles);
1300             QDomNodeList profilelist = doc.elementsByTagName("profile");
1301             for (int i = 0; i < profilelist.count(); i++) {
1302                 QString category = i18nc("Category Name", "Custom");
1303                 QString extension;
1304                 QDomNode parent = profilelist.at(i).parentNode();
1305                 if (!parent.isNull()) {
1306                     QDomElement parentNode = parent.toElement();
1307                     if (parentNode.hasAttribute("name")) category = parentNode.attribute("name");
1308                     extension = parentNode.attribute("extension");
1309                 }
1310                 profilelist.at(i).toElement().setAttribute("category", category);
1311                 if (!extension.isEmpty()) profilelist.at(i).toElement().setAttribute("extension", extension);
1312                 QDomNode n = profilelist.at(i).cloneNode();
1313                 newprofiles.appendChild(newdoc.importNode(n, true));
1314             }
1315             if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
1316                 KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
1317                 return;
1318             }
1319             QTextStream out(&file);
1320             out << newdoc.toString();
1321             file.close();
1322             parseFile(exportFile, editable);
1323             return;
1324         }
1325
1326         QDomNode node = doc.elementsByTagName("profile").at(0);
1327         if (node.isNull()) {
1328             kDebug() << "// Export file: " << exportFile << " IS BROKEN";
1329             return;
1330         }
1331         int count = 1;
1332         while (!node.isNull()) {
1333             QDomElement profile = node.toElement();
1334             QString profileName = profile.attribute("name");
1335             QString standard = profile.attribute("standard");
1336             QString params = profile.attribute("args");
1337
1338             if (replaceVorbisCodec && params.contains("acodec=vorbis")) {
1339                 // replace vorbis with libvorbis
1340                 params = params.replace("vorbis", "libvorbis");
1341             }
1342             if (replaceLibfaacCodec && params.contains("acodec=aac")) {
1343                 // replace libfaac with aac
1344                 params = params.replace("aac", "libfaac");
1345             }
1346
1347             QString category = profile.attribute("category", i18nc("Category Name", "Custom"));
1348             QString dest = profile.attribute("destinationid");
1349             QString prof_extension = profile.attribute("extension");
1350             if (!prof_extension.isEmpty()) extension = prof_extension;
1351             bool exists = false;
1352             for (int j = 0; j < m_renderCategory.count(); j++) {
1353                 if (m_renderCategory.at(j)->text() == category && m_renderCategory.at(j)->data(MetaGroupRole) == dest) {
1354                     exists = true;
1355                     break;
1356                 }
1357             }
1358
1359             if (!exists) {
1360                 QListWidgetItem *itemcat = new QListWidgetItem(category);
1361                 itemcat->setData(MetaGroupRole, dest);
1362                 m_renderCategory.append(itemcat);
1363             }
1364
1365             // Check if item with same name already exists and replace it,
1366             // allowing to override default profiles
1367
1368
1369             for (int j = 0; j < m_renderItems.count(); j++) {
1370                 if (m_renderItems.at(j)->text() == profileName && m_renderItems.at(j)->data(MetaGroupRole) == dest) {
1371                     QListWidgetItem *duplicate = m_renderItems.takeAt(j);
1372                     delete duplicate;
1373                     j--;
1374                 }
1375             }
1376
1377             item = new QListWidgetItem(profileName); // , m_view.size_list
1378             //kDebug() << "// ADDINg item with name: " << profileName << ", GRP" << category << ", DEST:" << dest ;
1379             item->setData(GroupRole, category);
1380             item->setData(MetaGroupRole, dest);
1381             item->setData(ExtensionRole, extension);
1382             item->setData(RenderRole, "avformat");
1383             item->setData(StandardRole, standard);
1384             item->setData(ParamsRole, params);
1385             if (profile.hasAttribute("url")) item->setData(ExtraRole, profile.attribute("url"));
1386             if (editable) {
1387                 item->setData(EditableRole, exportFile);
1388                 if (exportFile.endsWith("customprofiles.xml")) item->setIcon(KIcon("emblem-favorite"));
1389                 else item->setIcon(KIcon("applications-internet"));
1390             }
1391             m_renderItems.append(item);
1392             node = doc.elementsByTagName("profile").at(count);
1393             count++;
1394         }
1395         return;
1396     }
1397
1398     int i = 0;
1399     QString groupName;
1400     QString profileName;
1401
1402     QString prof_extension;
1403     QString renderer;
1404     QString params;
1405     QString standard;
1406     KIcon icon;
1407
1408     while (!groups.item(i).isNull()) {
1409         documentElement = groups.item(i).toElement();
1410         QDomNode gname = documentElement.elementsByTagName("groupname").at(0);
1411         QString metagroupName;
1412         QString metagroupId;
1413         if (!gname.isNull()) {
1414             metagroupName = gname.firstChild().nodeValue();
1415             metagroupId = gname.toElement().attribute("id");
1416
1417             if (!metagroupName.isEmpty() && m_view.destination_list->findData(metagroupId) == -1) {
1418                 if (metagroupId == "dvd") icon = KIcon("media-optical");
1419                 else if (metagroupId == "audioonly") icon = KIcon("audio-x-generic");
1420                 else if (metagroupId == "websites") icon = KIcon("applications-internet");
1421                 else if (metagroupId == "mediaplayers") icon = KIcon("applications-multimedia");
1422                 else if (metagroupId == "lossless") icon = KIcon("drive-harddisk");
1423                 else if (metagroupId == "mobile") icon = KIcon("pda");
1424                 m_view.destination_list->addItem(icon, i18n(metagroupName.toUtf8().data()), metagroupId);
1425             }
1426         }
1427         groupName = documentElement.attribute("name", i18nc("Attribute Name", "Custom"));
1428         extension = documentElement.attribute("extension", QString());
1429         renderer = documentElement.attribute("renderer", QString());
1430         bool exists = false;
1431         for (int j = 0; j < m_renderCategory.count(); j++) {
1432             if (m_renderCategory.at(j)->text() == groupName && m_renderCategory.at(j)->data(MetaGroupRole) == metagroupId) {
1433                 exists = true;
1434                 break;
1435             }
1436         }
1437         if (!exists) {
1438             QListWidgetItem *itemcat = new QListWidgetItem(groupName); //, m_view.format_list);
1439             itemcat->setData(MetaGroupRole, metagroupId);
1440             m_renderCategory.append(itemcat);
1441         }
1442
1443         QDomNode n = groups.item(i).firstChild();
1444         while (!n.isNull()) {
1445             if (n.toElement().tagName() != "profile") {
1446                 n = n.nextSibling();
1447                 continue;
1448             }
1449             profileElement = n.toElement();
1450             profileName = profileElement.attribute("name");
1451             standard = profileElement.attribute("standard");
1452             params = profileElement.attribute("args");
1453
1454             if (replaceVorbisCodec && params.contains("acodec=vorbis")) {
1455                 // replace vorbis with libvorbis
1456                 params = params.replace("vorbis", "libvorbis");
1457             }
1458             if (replaceLibfaacCodec && params.contains("acodec=aac")) {
1459                 // replace libfaac with aac
1460                 params = params.replace("aac", "libfaac");
1461             }
1462
1463             prof_extension = profileElement.attribute("extension");
1464             if (!prof_extension.isEmpty()) extension = prof_extension;
1465             item = new QListWidgetItem(profileName); //, m_view.size_list);
1466             item->setData(GroupRole, groupName);
1467             item->setData(MetaGroupRole, metagroupId);
1468             item->setData(ExtensionRole, extension);
1469             item->setData(RenderRole, renderer);
1470             item->setData(StandardRole, standard);
1471             item->setData(ParamsRole, params);
1472             if (profileElement.hasAttribute("url")) item->setData(ExtraRole, profileElement.attribute("url"));
1473             if (editable) item->setData(EditableRole, exportFile);
1474             m_renderItems.append(item);
1475             n = n.nextSibling();
1476         }
1477
1478         i++;
1479     }
1480 }
1481
1482 void RenderWidget::setRenderJob(const QString &dest, int progress)
1483 {
1484     QTreeWidgetItem *item;
1485     QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1);
1486     if (!existing.isEmpty()) item = existing.at(0);
1487     else {
1488         item = new QTreeWidgetItem(m_view.running_jobs, QStringList() << QString() << dest << QString());
1489         item->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1490         if (progress == 0) {
1491             item->setData(1, Qt::UserRole + 2, WAITINGJOB);
1492             item->setIcon(0, KIcon("media-playback-pause"));
1493             item->setData(1, Qt::UserRole, i18n("Waiting..."));
1494         }
1495     }
1496     item->setData(2, Qt::UserRole, progress);
1497     item->setData(1, Qt::UserRole + 2, RUNNINGJOB);
1498     if (progress == 0) {
1499         item->setIcon(0, KIcon("system-run"));
1500         item->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1501         item->setData(1, Qt::UserRole + 1, QTime::currentTime());
1502         slotCheckJob();
1503     } else {
1504         QTime startTime = item->data(1, Qt::UserRole + 1).toTime();
1505         int seconds = startTime.secsTo(QTime::currentTime());;
1506         const QString t = i18n("Estimated time %1", QTime().addSecs(seconds * (100 - progress) / progress).toString("hh:mm:ss"));
1507         item->setData(1, Qt::UserRole, t);
1508     }
1509 }
1510
1511 void RenderWidget::setRenderStatus(const QString &dest, int status, const QString &error)
1512 {
1513     QTreeWidgetItem *item;
1514     QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1);
1515     if (!existing.isEmpty()) item = existing.at(0);
1516     else {
1517         item = new QTreeWidgetItem(m_view.running_jobs, QStringList() << QString() << dest << QString());
1518         item->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1519     }
1520     item->setData(1, Qt::UserRole + 2, FINISHEDJOB);
1521     if (status == -1) {
1522         // Job finished successfully
1523         item->setIcon(0, KIcon("dialog-ok"));
1524         item->setData(2, Qt::UserRole, 100);
1525         QTime startTime = item->data(1, Qt::UserRole + 1).toTime();
1526         int seconds = startTime.secsTo(QTime::currentTime());
1527         const QTime tm = QTime().addSecs(seconds);
1528         const QString t = i18n("Rendering finished in %1", tm.toString("hh:mm:ss"));
1529         item->setData(1, Qt::UserRole, t);
1530         QString itemGroup = item->data(0, Qt::UserRole).toString();
1531         if (itemGroup == "dvd") {
1532             emit openDvdWizard(item->text(1), item->data(0, Qt::UserRole + 1).toString());
1533         } else if (itemGroup == "websites") {
1534             QString url = item->data(0, Qt::UserRole + 1).toString();
1535             if (!url.isEmpty()) new KRun(url, this);
1536         }
1537     } else if (status == -2) {
1538         // Rendering crashed
1539         item->setData(1, Qt::UserRole, i18n("Rendering crashed"));
1540         item->setIcon(0, KIcon("dialog-close"));
1541         item->setData(2, Qt::UserRole, 100);
1542         m_view.error_log->append(i18n("<strong>Rendering of %1 crashed</strong><br />", dest));
1543         m_view.error_log->append(error);
1544         m_view.error_log->append("<hr />");
1545         m_view.error_box->setVisible(true);
1546     } else if (status == -3) {
1547         // User aborted job
1548         item->setData(1, Qt::UserRole, i18n("Rendering aborted"));
1549         item->setIcon(0, KIcon("dialog-cancel"));
1550         item->setData(2, Qt::UserRole, 100);
1551     }
1552     slotCheckJob();
1553     checkRenderStatus();
1554 }
1555
1556 void RenderWidget::slotAbortCurrentJob()
1557 {
1558     QTreeWidgetItem *current = m_view.running_jobs->currentItem();
1559     if (current) {
1560         if (current->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB)
1561             emit abortProcess(current->text(1));
1562         else {
1563             delete current;
1564             slotCheckJob();
1565             checkRenderStatus();
1566         }
1567     }
1568 }
1569
1570 void RenderWidget::slotStartCurrentJob()
1571 {
1572     QTreeWidgetItem *current = m_view.running_jobs->currentItem();
1573     if (current && current->data(1, Qt::UserRole + 2).toInt() == WAITINGJOB)
1574         startRendering(current);
1575     m_view.start_job->setEnabled(false);
1576 }
1577
1578 void RenderWidget::slotCheckJob()
1579 {
1580     bool activate = false;
1581     QTreeWidgetItem *current = m_view.running_jobs->currentItem();
1582     if (current) {
1583         if (current->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB) {
1584             m_view.abort_job->setText(i18n("Abort Job"));
1585             m_view.start_job->setEnabled(false);
1586         } else {
1587             m_view.abort_job->setText(i18n("Remove Job"));
1588             m_view.start_job->setEnabled(current->data(1, Qt::UserRole + 2).toInt() == WAITINGJOB);
1589         }
1590         activate = true;
1591     }
1592     m_view.abort_job->setEnabled(activate);
1593     for (int i = 0; i < m_view.running_jobs->topLevelItemCount(); i++) {
1594         current = m_view.running_jobs->topLevelItem(i);
1595         if (current == m_view.running_jobs->currentItem()) {
1596             current->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 3));
1597         } else current->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1598     }
1599 }
1600
1601 void RenderWidget::slotCLeanUpJobs()
1602 {
1603     int ix = 0;
1604     QTreeWidgetItem *current = m_view.running_jobs->topLevelItem(ix);
1605     while (current) {
1606         if (current->data(1, Qt::UserRole + 2).toInt() == FINISHEDJOB)
1607             delete current;
1608         else ix++;
1609         current = m_view.running_jobs->topLevelItem(ix);
1610     }
1611     slotCheckJob();
1612 }
1613
1614 void RenderWidget::parseScriptFiles()
1615 {
1616     QStringList scriptsFilter;
1617     scriptsFilter << "*.sh";
1618     m_view.scripts_list->clear();
1619
1620     QTreeWidgetItem *item;
1621     // List the project scripts
1622     QStringList scriptFiles = QDir(m_projectFolder + "scripts").entryList(scriptsFilter, QDir::Files);
1623     for (int i = 0; i < scriptFiles.size(); ++i) {
1624         KUrl scriptpath(m_projectFolder + "scripts/" + scriptFiles.at(i));
1625         QString target;
1626         QString renderer;
1627         QString melt;
1628         QFile file(scriptpath.path());
1629         if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
1630             QTextStream stream(&file);
1631             while (!stream.atEnd()) {
1632                 QString line = stream.readLine();
1633                 if (line.startsWith("TARGET=")) {
1634                     target = line.section("TARGET=", 1).simplified();
1635                     target.remove(QChar('"'));
1636                 } else if (line.startsWith("RENDERER=")) {
1637                     renderer = line.section("RENDERER=", 1).simplified();
1638                     renderer.remove(QChar('"'));
1639                 } else if (line.startsWith("MELT=")) {
1640                     melt = line.section("MELT=", 1).simplified();
1641                     melt.remove(QChar('"'));
1642                 }
1643             }
1644             file.close();
1645         }
1646         if (target.isEmpty()) continue;
1647         item = new QTreeWidgetItem(m_view.scripts_list, QStringList() << QString() << scriptpath.fileName());
1648         if (!renderer.isEmpty() && renderer.contains('/') && !QFile::exists(renderer)) {
1649             item->setIcon(0, KIcon("dialog-cancel"));
1650             item->setToolTip(1, i18n("Script contains wrong command: %1", renderer));
1651             item->setData(0, Qt::UserRole, '1');
1652         } else if (!melt.isEmpty() && melt.contains('/') && !QFile::exists(melt)) {
1653             item->setIcon(0, KIcon("dialog-cancel"));
1654             item->setToolTip(1, i18n("Script contains wrong command: %1", melt));
1655             item->setData(0, Qt::UserRole, '1');
1656         } else item->setIcon(0, KIcon("application-x-executable-script"));
1657         item->setSizeHint(0, QSize(m_view.scripts_list->columnWidth(0), fontMetrics().height() * 2));
1658         item->setData(1, Qt::UserRole, target.simplified());
1659         item->setData(1, Qt::UserRole + 1, scriptpath.path());
1660     }
1661     bool activate = false;
1662     QTreeWidgetItem *script = m_view.scripts_list->topLevelItem(0);
1663     if (script) {
1664         m_view.scripts_list->setCurrentItem(script);
1665         script->setSelected(true);
1666         activate = true;
1667     }
1668 //    m_view.start_script->setEnabled(activate);
1669 //    m_view.delete_script->setEnabled(activate);
1670 }
1671
1672 void RenderWidget::slotCheckScript()
1673 {
1674     QTreeWidgetItem *current = m_view.scripts_list->currentItem();
1675     if (current == NULL) return;
1676     m_view.start_script->setEnabled(current->data(0, Qt::UserRole).toString().isEmpty());
1677     m_view.delete_script->setEnabled(true);
1678     for (int i = 0; i < m_view.scripts_list->topLevelItemCount(); i++) {
1679         current = m_view.scripts_list->topLevelItem(i);
1680         if (current == m_view.scripts_list->currentItem()) {
1681             current->setSizeHint(1, QSize(m_view.scripts_list->columnWidth(1), fontMetrics().height() * 3));
1682         } else current->setSizeHint(1, QSize(m_view.scripts_list->columnWidth(1), fontMetrics().height() * 2));
1683     }
1684 }
1685
1686 void RenderWidget::slotStartScript()
1687 {
1688     QTreeWidgetItem *item = m_view.scripts_list->currentItem();
1689     if (item) {
1690         QString destination = item->data(1, Qt::UserRole).toString();
1691         QString path = item->data(1, Qt::UserRole + 1).toString();
1692         // Insert new job in queue
1693         QTreeWidgetItem *renderItem;
1694         QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(destination, Qt::MatchExactly, 1);
1695         kDebug() << "------  START SCRIPT";
1696         if (!existing.isEmpty()) {
1697             renderItem = existing.at(0);
1698             if (renderItem->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB) {
1699                 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"));
1700                 return;
1701             }
1702         } else renderItem = new QTreeWidgetItem(m_view.running_jobs, QStringList() << QString() << destination << QString());
1703         kDebug() << "------  START SCRIPT 2";
1704         renderItem->setData(2, Qt::UserRole, 0);
1705         renderItem->setData(1, Qt::UserRole + 2, WAITINGJOB);
1706         renderItem->setIcon(0, KIcon("media-playback-pause"));
1707         renderItem->setData(1, Qt::UserRole, i18n("Waiting..."));
1708         renderItem->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1709         renderItem->setData(1, Qt::UserRole + 1, QTime::currentTime());
1710         renderItem->setData(1, Qt::UserRole + 3, path);
1711         renderItem->setData(1, Qt::UserRole + 4, '1');
1712         checkRenderStatus();
1713         m_view.tabWidget->setCurrentIndex(1);
1714     }
1715 }
1716
1717 void RenderWidget::slotDeleteScript()
1718 {
1719     QTreeWidgetItem *item = m_view.scripts_list->currentItem();
1720     if (item) {
1721         QString path = item->data(1, Qt::UserRole + 1).toString();
1722         KIO::NetAccess::del(path + ".mlt", this);
1723         KIO::NetAccess::del(path, this);
1724         parseScriptFiles();
1725     }
1726 }
1727
1728 void RenderWidget::slotGenerateScript()
1729 {
1730     slotPrepareExport(true);
1731 }
1732
1733 void RenderWidget::slotHideLog()
1734 {
1735     m_view.error_box->setVisible(false);
1736 }
1737
1738 void RenderWidget::setRenderProfile(QMap <QString, QString> props)
1739 {
1740     m_view.destination_list->blockSignals(true);
1741     m_view.format_list->blockSignals(true);
1742     m_view.scanning_list->setCurrentIndex(props.value("renderscanning").toInt());
1743     int exportAudio = props.value("renderexportaudio").toInt();
1744     switch (exportAudio) {
1745     case 1:
1746         m_view.export_audio->setCheckState(Qt::Unchecked);
1747         break;
1748     case 2:
1749         m_view.export_audio->setCheckState(Qt::Checked);
1750         break;
1751     default:
1752         m_view.export_audio->setCheckState(Qt::PartiallyChecked);
1753     }
1754     if (props.contains("renderrescale")) m_view.rescale->setChecked(props.value("renderrescale").toInt());
1755     if (props.contains("renderrescalewidth")) m_view.rescale_width->setValue(props.value("renderrescalewidth").toInt());
1756     if (props.contains("renderrescaleheight")) m_view.rescale_height->setValue(props.value("renderrescaleheight").toInt());
1757     if (props.contains("rendertcoverlay")) m_view.tc_overlay->setChecked(props.value("rendertcoverlay").toInt());
1758     if (props.contains("rendertctype")) m_view.tc_type->setCurrentIndex(props.value("rendertctype").toInt());
1759     if (props.contains("renderratio")) m_view.rescale_keep->setChecked(props.value("renderratio").toInt());
1760     if (props.contains("renderplay")) m_view.play_after->setChecked(props.value("renderplay").toInt());
1761
1762     if (props.value("renderzone") == "1") m_view.render_zone->setChecked(true);
1763     else if (props.value("renderguide") == "1") {
1764         m_view.render_guide->setChecked(true);
1765         m_view.guide_start->setCurrentIndex(props.value("renderstartguide").toInt());
1766         m_view.guide_end->setCurrentIndex(props.value("renderendguide").toInt());
1767     } else m_view.render_full->setChecked(true);
1768     slotUpdateGuideBox();
1769
1770     QString url = props.value("renderurl");
1771     if (!url.isEmpty()) m_view.out_file->setUrl(KUrl(url));
1772
1773     // set destination
1774     for (int i = 0; i < m_view.destination_list->count(); i++) {
1775         if (m_view.destination_list->itemData(i, Qt::UserRole) == props.value("renderdestination")) {
1776             m_view.destination_list->setCurrentIndex(i);
1777             break;
1778         }
1779     }
1780     refreshCategory();
1781
1782     // set category
1783     QString group = props.value("rendercategory");
1784     if (!group.isEmpty()) {
1785         QList<QListWidgetItem *> childs = m_view.format_list->findItems(group, Qt::MatchExactly);
1786         if (!childs.isEmpty()) {
1787             m_view.format_list->setCurrentItem(childs.at(0));
1788             m_view.format_list->scrollToItem(childs.at(0));
1789         }
1790         refreshView();
1791     }
1792
1793     // set profile
1794     QList<QListWidgetItem *> childs = m_view.size_list->findItems(props.value("renderprofile"), Qt::MatchExactly);
1795     if (!childs.isEmpty()) {
1796         m_view.size_list->setCurrentItem(childs.at(0));
1797         m_view.size_list->scrollToItem(childs.at(0));
1798     }
1799     //refreshView();
1800     m_view.destination_list->blockSignals(false);
1801     m_view.format_list->blockSignals(false);
1802
1803 }
1804
1805 bool RenderWidget::startWaitingRenderJobs()
1806 {
1807     m_blockProcessing = true;
1808     QString autoscriptFile = getFreeScriptName("auto");
1809     QFile file(autoscriptFile);
1810     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
1811         kWarning() << "//////  ERROR writing to file: " << autoscriptFile;
1812         KMessageBox::error(0, i18n("Cannot write to file %1", autoscriptFile));
1813         return false;
1814     }
1815
1816     QTextStream outStream(&file);
1817     outStream << "#! /bin/sh" << "\n" << "\n";
1818     QTreeWidgetItem *item = m_view.running_jobs->topLevelItem(0);
1819     while (item) {
1820         if (item->data(1, Qt::UserRole + 2).toInt() == WAITINGJOB) {
1821             if (item->data(1, Qt::UserRole + 4).isNull()) {
1822                 // Add render process for item
1823                 const QString params = item->data(1, Qt::UserRole + 3).toStringList().join(" ");
1824                 outStream << m_renderer << " " << params << "\n";
1825             } else {
1826                 // Script item
1827                 outStream << item->data(1, Qt::UserRole + 3).toString() << "\n";
1828             }
1829         }
1830         item = m_view.running_jobs->itemBelow(item);
1831     }
1832     // erase itself when rendering is finished
1833     outStream << "rm " << autoscriptFile << "\n" << "\n";
1834     if (file.error() != QFile::NoError) {
1835         KMessageBox::error(0, i18n("Cannot write to file %1", autoscriptFile));
1836         file.close();
1837         m_blockProcessing = false;
1838         return false;
1839     }
1840     file.close();
1841     QFile::setPermissions(autoscriptFile, file.permissions() | QFile::ExeUser);
1842     QProcess::startDetached(autoscriptFile, QStringList());
1843     return true;
1844 }
1845
1846 QString RenderWidget::getFreeScriptName(const QString &prefix)
1847 {
1848     int ix = 0;
1849     QString scriptsFolder = m_projectFolder + "scripts/";
1850     KStandardDirs::makeDir(scriptsFolder);
1851     QString path;
1852     while (path.isEmpty() || QFile::exists(path)) {
1853         ix++;
1854         path = scriptsFolder + prefix + i18n("script") + QString::number(ix).rightJustified(3, '0', false) + ".sh";
1855     }
1856     return path;
1857 }
1858
1859 void RenderWidget::slotPlayRendering(QTreeWidgetItem *item, int)
1860 {
1861     if (KdenliveSettings::defaultplayerapp().isEmpty() || item->data(1, Qt::UserRole + 2).toInt() != FINISHEDJOB) return;
1862     const QByteArray startId = KStartupInfo::createNewStartupId();
1863     const QString command = KdenliveSettings::defaultplayerapp() + ' ' + item->text(1);
1864     KRun::runCommand(command, KdenliveSettings::defaultplayerapp(), KdenliveSettings::defaultplayerapp(), this, startId);
1865 }
1866
1867 void RenderWidget::missingClips(bool hasMissing)
1868 {
1869     if (hasMissing) {
1870         m_view.errorLabel->setText(i18n("Check missing clips"));
1871         m_view.errorBox->setHidden(false);
1872     } else m_view.errorBox->setHidden(true);
1873 }
1874
1875 void RenderWidget::slotUpdateEncodeThreads(int val)
1876 {
1877         KdenliveSettings::setEncodethreads(val);
1878 }
1879
1880 void RenderWidget::slotUpdateRescaleWidth(int val)
1881 {
1882     KdenliveSettings::setDefaultrescalewidth(val);
1883     if (!m_view.rescale_keep->isChecked()) return;
1884     m_view.rescale_height->blockSignals(true);
1885     m_view.rescale_height->setValue(val * m_profile.height / m_profile.width  + 0.5);
1886     KdenliveSettings::setDefaultrescaleheight(m_view.rescale_height->value());
1887     m_view.rescale_height->blockSignals(false);
1888 }
1889
1890 void RenderWidget::slotUpdateRescaleHeight(int val)
1891 {
1892     KdenliveSettings::setDefaultrescaleheight(val);
1893     if (!m_view.rescale_keep->isChecked()) return;
1894     m_view.rescale_width->blockSignals(true);
1895     m_view.rescale_width->setValue(val * m_profile.width / m_profile.height + 0.5);
1896     KdenliveSettings::setDefaultrescaleheight(m_view.rescale_width->value());
1897     m_view.rescale_width->blockSignals(false);
1898 }
1899
1900 void RenderWidget::slotSwitchAspectRatio()
1901 {
1902     KdenliveSettings::setRescalekeepratio(m_view.rescale_keep->isChecked());
1903     if (m_view.rescale_keep->isChecked()) slotUpdateRescaleWidth(m_view.rescale_width->value());
1904 }
1905
1906 void RenderWidget::slotUpdateAudioLabel(int ix)
1907 {
1908     if (ix == Qt::PartiallyChecked)
1909         m_view.export_audio->setText(i18n("Export audio (automatic)"));
1910     else
1911         m_view.export_audio->setText(i18n("Export audio"));
1912 }
1913
1914 bool RenderWidget::automaticAudioExport() const
1915 {
1916     return (m_view.export_audio->checkState() == Qt::PartiallyChecked);
1917 }
1918
1919 bool RenderWidget::selectedAudioExport() const
1920 {
1921     return (m_view.export_audio->checkState() != Qt::Unchecked);
1922 }
1923
1924 void RenderWidget::updateProxyConfig(bool enable)
1925 {
1926     m_view.proxy_render->setHidden(!enable);
1927 }
1928
1929 bool RenderWidget::proxyRendering()
1930 {
1931     return m_view.proxy_render->isChecked();
1932 }