renderwidget.cpp 53.4 KB
Newer Older
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/***************************************************************************
 *   Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
 ***************************************************************************/

20

21 22 23
#include "renderwidget.h"
#include "kdenlivesettings.h"
#include "ui_saveprofile_ui.h"
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
24 25 26

#include <KStandardDirs>
#include <KDebug>
27
#include <KMessageBox>
28
#include <KComboBox>
29
#include <KRun>
30
#include <KIO/NetAccess>
31
// #include <knewstuff2/engine.h>
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
32

33 34 35 36 37 38 39 40
#include <QDomDocument>
#include <QItemDelegate>
#include <QTreeWidgetItem>
#include <QListWidgetItem>
#include <QHeaderView>
#include <QMenu>
#include <QProcess>
#include <QInputDialog>
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
41

42 43 44 45 46
const int GroupRole = Qt::UserRole;
const int ExtensionRole = GroupRole + 1;
const int StandardRole = GroupRole + 2;
const int RenderRole = GroupRole + 3;
const int ParamsRole = GroupRole + 4;
47
const int EditableRole = GroupRole + 5;
48
const int MetaGroupRole = GroupRole + 6;
49
const int ExtraRole = GroupRole + 7;
50

51 52 53
RenderWidget::RenderWidget(const QString &projectfolder, QWidget * parent) :
        QDialog(parent),
        m_projectFolder(projectfolder)
54
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
55
    m_view.setupUi(this);
56
    setWindowTitle(i18n("Rendering"));
57 58 59 60
    m_view.buttonDelete->setIcon(KIcon("trash-empty"));
    m_view.buttonDelete->setToolTip(i18n("Delete profile"));
    m_view.buttonDelete->setEnabled(false);

61 62 63 64
    m_view.buttonEdit->setIcon(KIcon("document-properties"));
    m_view.buttonEdit->setToolTip(i18n("Edit profile"));
    m_view.buttonEdit->setEnabled(false);

65 66 67
    m_view.buttonSave->setIcon(KIcon("document-new"));
    m_view.buttonSave->setToolTip(i18n("Create new profile"));

68 69 70 71 72 73
    m_view.buttonInfo->setIcon(KIcon("help-about"));

    if (KdenliveSettings::showrenderparams()) {
        m_view.buttonInfo->setDown(true);
    } else m_view.advanced_params->hide();

74 75
    m_view.rescale_size->setInputMask("0099\\x0099");
    m_view.rescale_size->setText("320x240");
76

77 78

    QMenu *renderMenu = new QMenu(i18n("Start Rendering"), this);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
79
    QAction *renderAction = renderMenu->addAction(KIcon("video-x-generic"), i18n("Render to File"));
80
    connect(renderAction, SIGNAL(triggered()), this, SLOT(slotExport()));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
81
    QAction *scriptAction = renderMenu->addAction(KIcon("application-x-shellscript"), i18n("Generate Script"));
82 83 84 85 86 87 88 89 90 91
    connect(scriptAction, SIGNAL(triggered()), this, SLOT(slotGenerateScript()));

    m_view.buttonStart->setMenu(renderMenu);
    m_view.buttonStart->setPopupMode(QToolButton::MenuButtonPopup);
    m_view.buttonStart->setDefaultAction(renderAction);
    m_view.buttonStart->setToolButtonStyle(Qt::ToolButtonTextOnly);
    m_view.abort_job->setEnabled(false);
    m_view.start_script->setEnabled(false);
    m_view.delete_script->setEnabled(false);

92 93
    m_view.format_list->setAlternatingRowColors(true);
    m_view.size_list->setAlternatingRowColors(true);
94

95
    parseProfiles();
96 97 98 99 100 101
    parseScriptFiles();

    connect(m_view.start_script, SIGNAL(clicked()), this, SLOT(slotStartScript()));
    connect(m_view.delete_script, SIGNAL(clicked()), this, SLOT(slotDeleteScript()));
    connect(m_view.scripts_list, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this, SLOT(slotCheckScript()));
    connect(m_view.running_jobs, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this, SLOT(slotCheckJob()));
102

103 104
    connect(m_view.buttonInfo, SIGNAL(clicked()), this, SLOT(showInfoPanel()));

105
    connect(m_view.buttonSave, SIGNAL(clicked()), this, SLOT(slotSaveProfile()));
106
    connect(m_view.buttonEdit, SIGNAL(clicked()), this, SLOT(slotEditProfile()));
107
    connect(m_view.buttonDelete, SIGNAL(clicked()), this, SLOT(slotDeleteProfile()));
108
    connect(m_view.abort_job, SIGNAL(clicked()), this, SLOT(slotAbortCurrentJob()));
109 110
    connect(m_view.buttonClose, SIGNAL(clicked()), this, SLOT(hide()));
    connect(m_view.buttonClose2, SIGNAL(clicked()), this, SLOT(hide()));
111
    connect(m_view.buttonClose3, SIGNAL(clicked()), this, SLOT(hide()));
112 113
    connect(m_view.rescale, SIGNAL(toggled(bool)), m_view.rescale_size, SLOT(setEnabled(bool)));
    connect(m_view.destination_list, SIGNAL(activated(int)), this, SLOT(refreshView()));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
114
    connect(m_view.out_file, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButtons()));
115
    connect(m_view.out_file, SIGNAL(urlSelected(const KUrl &)), this, SLOT(slotUpdateButtons(const KUrl &)));
116 117
    connect(m_view.format_list, SIGNAL(currentRowChanged(int)), this, SLOT(refreshView()));
    connect(m_view.size_list, SIGNAL(currentRowChanged(int)), this, SLOT(refreshParams()));
118

119 120
    connect(m_view.size_list, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(slotEditItem(QListWidgetItem *)));

121 122 123 124 125 126 127
    connect(m_view.render_guide, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
    connect(m_view.render_zone, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
    connect(m_view.render_full, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));

    connect(m_view.guide_end, SIGNAL(activated(int)), this, SLOT(slotCheckStartGuidePosition()));
    connect(m_view.guide_start, SIGNAL(activated(int)), this, SLOT(slotCheckEndGuidePosition()));

128
    connect(m_view.format_selection, SIGNAL(activated(int)), this, SLOT(refreshView()));
129

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
130
    m_view.buttonStart->setEnabled(false);
131
    m_view.rescale_size->setEnabled(false);
132
    m_view.guides_box->setVisible(false);
133 134 135 136
    m_view.open_dvd->setVisible(false);
    m_view.open_browser->setVisible(false);
    m_view.error_box->setVisible(false);

137 138
    m_view.splitter->setStretchFactor(1, 5);
    m_view.splitter->setStretchFactor(0, 2);
139

140
    m_view.out_file->setMode(KFile::File);
141

142
    m_view.running_jobs->setHeaderLabels(QStringList() << QString() << i18n("File") << i18n("Progress"));
143
    m_view.running_jobs->setItemDelegate(new RenderViewDelegate(this));
144

145 146 147
    QHeaderView *header = m_view.running_jobs->header();
    QFontMetrics fm = fontMetrics();
    //header->resizeSection(0, fm.width("typical-name-for-a-torrent.torrent"));
148 149
    header->setResizeMode(0, QHeaderView::Fixed);
    header->resizeSection(0, 30);
150
    header->setResizeMode(1, QHeaderView::Interactive);
151 152 153 154
    header->resizeSection(1, fm.width("typical-name-for-a-file.torrent"));
    header->setResizeMode(2, QHeaderView::Fixed);
    header->resizeSection(1, width() * 2 / 3);
    header->setResizeMode(2, QHeaderView::Interactive);
155 156
    //header->setResizeMode(1, QHeaderView::Fixed);

157 158 159 160
    m_view.scripts_list->setHeaderLabels(QStringList() << i18n("Script Files"));
    m_view.scripts_list->setItemDelegate(new RenderScriptDelegate(this));


161
    focusFirstVisibleItem();
162 163
}

164 165
void RenderWidget::slotEditItem(QListWidgetItem *item)
{
166 167
    QString edit = item->data(EditableRole).toString();
    if (edit.isEmpty() || !edit.endsWith("customprofiles.xml")) slotSaveProfile();
168 169 170
    else slotEditProfile();
}

171 172
void RenderWidget::showInfoPanel()
{
173 174 175 176 177 178 179 180 181
    if (m_view.advanced_params->isVisible()) {
        m_view.advanced_params->setVisible(false);
        m_view.buttonInfo->setDown(false);
        KdenliveSettings::setShowrenderparams(false);
    } else {
        m_view.advanced_params->setVisible(true);
        m_view.buttonInfo->setDown(true);
        KdenliveSettings::setShowrenderparams(true);
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
182 183
}

184 185
void RenderWidget::setDocumentPath(const QString path)
{
186 187 188
    m_projectFolder = path;
    const QString fileName = m_view.out_file->url().fileName();
    m_view.out_file->setUrl(KUrl(m_projectFolder + '/' + fileName));
189
    parseScriptFiles();
190 191
}

192 193
void RenderWidget::slotUpdateGuideBox()
{
194 195 196
    m_view.guides_box->setVisible(m_view.render_guide->isChecked());
}

197 198
void RenderWidget::slotCheckStartGuidePosition()
{
199 200 201 202
    if (m_view.guide_start->currentIndex() > m_view.guide_end->currentIndex())
        m_view.guide_start->setCurrentIndex(m_view.guide_end->currentIndex());
}

203 204
void RenderWidget::slotCheckEndGuidePosition()
{
205 206 207 208
    if (m_view.guide_end->currentIndex() < m_view.guide_start->currentIndex())
        m_view.guide_end->setCurrentIndex(m_view.guide_start->currentIndex());
}

209 210
void RenderWidget::setGuides(QDomElement guidesxml, double duration)
{
211 212 213 214
    m_view.guide_start->clear();
    m_view.guide_end->clear();
    QDomNodeList nodes = guidesxml.elementsByTagName("guide");
    if (nodes.count() > 0) {
215
        m_view.guide_start->addItem(i18n("Render"), "0");
216 217 218 219 220 221 222 223 224 225 226 227 228
        m_view.render_guide->setEnabled(true);
    } else m_view.render_guide->setEnabled(false);
    for (int i = 0; i < nodes.count(); i++) {
        QDomElement e = nodes.item(i).toElement();
        if (!e.isNull()) {
            m_view.guide_start->addItem(e.attribute("comment"), e.attribute("time").toDouble());
            m_view.guide_end->addItem(e.attribute("comment"), e.attribute("time").toDouble());
        }
    }
    if (nodes.count() > 0)
        m_view.guide_end->addItem(i18n("End"), QString::number(duration));
}

229 230
// Will be called when the user selects an output file via the file dialog.
// File extension will be added automatically.
231 232
void RenderWidget::slotUpdateButtons(KUrl url)
{
233
    if (m_view.out_file->url().isEmpty()) m_view.buttonStart->setEnabled(false);
234
    else m_view.buttonStart->setEnabled(true);
235
    if (url != 0) {
236 237 238 239
        QListWidgetItem *item = m_view.size_list->currentItem();
        QString extension = item->data(ExtensionRole).toString();
        url = filenameWithExtension(url, extension);
        m_view.out_file->setUrl(url);
240 241 242
    }
}

243
// Will be called when the user changes the output file path in the text line.
244
// File extension must NOT be added, would make editing impossible!
245 246
void RenderWidget::slotUpdateButtons()
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
247
    if (m_view.out_file->url().isEmpty()) m_view.buttonStart->setEnabled(false);
248
    else m_view.buttonStart->setEnabled(true);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
249 250
}

251 252
void RenderWidget::slotSaveProfile()
{
253
    //TODO: update to correctly use metagroups
254 255 256
    Ui::SaveProfile_UI ui;
    QDialog *d = new QDialog(this);
    ui.setupUi(d);
257 258

    for (int i = 0; i < m_view.destination_list->count(); i++)
259
        ui.destination_list->addItem(m_view.destination_list->itemIcon(i), m_view.destination_list->itemText(i), m_view.destination_list->itemData(i, Qt::UserRole));
260 261

    ui.destination_list->setCurrentIndex(m_view.destination_list->currentIndex());
262
    QString dest = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
263

264 265 266
    QString customGroup = m_view.format_list->currentItem()->text();
    if (customGroup.isEmpty()) customGroup = i18n("Custom");
    ui.group_name->setText(customGroup);
267

268
    ui.parameters->setText(m_view.advanced_params->toPlainText());
269 270
    ui.extension->setText(m_view.size_list->currentItem()->data(ExtensionRole).toString());
    ui.profile_name->setFocus();
271
    if (d->exec() == QDialog::Accepted && !ui.profile_name->text().simplified().isEmpty()) {
272
        QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
273 274 275 276 277
        QDomDocument doc;
        QFile file(exportFile);
        doc.setContent(&file, false);
        file.close();
        QDomElement documentElement;
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
        QDomElement profiles = doc.documentElement();
        if (profiles.isNull() || profiles.tagName() != "profiles") {
            doc.clear();
            profiles = doc.createElement("profiles");
            profiles.setAttribute("version", 1);
            doc.appendChild(profiles);
        }
        int version = profiles.attribute("version", 0).toInt();
        if (version < 1) {
            kDebug() << "// OLD profile version";
            doc.clear();
            profiles = doc.createElement("profiles");
            profiles.setAttribute("version", 1);
            doc.appendChild(profiles);
        }

294
        QString newProfileName = ui.profile_name->text().simplified();
295 296 297 298
        QString newGroupName = ui.group_name->text().simplified();
        if (newGroupName.isEmpty()) newGroupName = i18n("Custom");
        QString newMetaGroupId = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
        QDomNodeList profilelist = doc.elementsByTagName("profile");
299
        int i = 0;
300 301 302 303 304 305 306 307 308 309 310
        while (!profilelist.item(i).isNull()) {
            // make sure a profile with same name doesn't exist
            documentElement = profilelist.item(i).toElement();
            QString profileName = documentElement.attribute("name");
            if (profileName == newProfileName) {
                // a profile with that same name already exists
                bool ok;
                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);
                if (!ok) return;
                if (profileName == newProfileName) {
                    profiles.removeChild(profilelist.item(i));
311 312 313
                    break;
                }
            }
314
            i++;
315
        }
316

317 318
        QDomElement profileElement = doc.createElement("profile");
        profileElement.setAttribute("name", newProfileName);
319 320
        profileElement.setAttribute("category", newGroupName);
        profileElement.setAttribute("destinationid", newMetaGroupId);
321
        profileElement.setAttribute("extension", ui.extension->text().simplified());
322
        profileElement.setAttribute("args", ui.parameters->toPlainText().simplified());
323
        profiles.appendChild(profileElement);
324 325 326 327 328 329 330 331 332 333

        //QCString save = doc.toString().utf8();

        if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
            KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
            delete d;
            return;
        }
        QTextStream out(&file);
        out << doc.toString();
334 335 336 337 338
        if (file.error() != QFile::NoError) {
            KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
            file.close();
            return;
        }
339
        file.close();
340
        parseProfiles(newMetaGroupId, newGroupName, newProfileName);
341 342 343 344
    }
    delete d;
}

345 346
void RenderWidget::slotEditProfile()
{
347 348 349 350 351 352 353 354 355 356 357
    QListWidgetItem *item = m_view.size_list->currentItem();
    if (!item) return;
    QString currentGroup = m_view.format_list->currentItem()->text();

    QString params = item->data(ParamsRole).toString();
    QString extension = item->data(ExtensionRole).toString();
    QString currentProfile = item->text();

    Ui::SaveProfile_UI ui;
    QDialog *d = new QDialog(this);
    ui.setupUi(d);
358 359 360 361 362 363 364 365 366 367 368

    for (int i = 0; i < m_view.destination_list->count(); i++)
        ui.destination_list->addItem(m_view.destination_list->itemIcon(i), m_view.destination_list->itemText(i), m_view.destination_list->itemData(i, Qt::UserRole));

    ui.destination_list->setCurrentIndex(m_view.destination_list->currentIndex());
    QString dest = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();

    QString customGroup = m_view.format_list->currentItem()->text();
    if (customGroup.isEmpty()) customGroup = i18n("Custom");
    ui.group_name->setText(customGroup);

369 370 371 372
    ui.profile_name->setText(currentProfile);
    ui.extension->setText(extension);
    ui.parameters->setText(params);
    ui.profile_name->setFocus();
373
    d->setWindowTitle(i18n("Edit Profile"));
374
    if (d->exec() == QDialog::Accepted) {
375
        slotDeleteProfile(false);
376
        QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
377 378 379 380 381
        QDomDocument doc;
        QFile file(exportFile);
        doc.setContent(&file, false);
        file.close();
        QDomElement documentElement;
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
        QDomElement profiles = doc.documentElement();

        if (profiles.isNull() || profiles.tagName() != "profiles") {
            doc.clear();
            profiles = doc.createElement("profiles");
            profiles.setAttribute("version", 1);
            doc.appendChild(profiles);
        }

        int version = profiles.attribute("version", 0).toInt();
        if (version < 1) {
            kDebug() << "// OLD profile version";
            doc.clear();
            profiles = doc.createElement("profiles");
            profiles.setAttribute("version", 1);
397
            doc.appendChild(profiles);
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
        }

        QString newProfileName = ui.profile_name->text().simplified();
        QString newGroupName = ui.group_name->text().simplified();
        if (newGroupName.isEmpty()) newGroupName = i18n("Custom");
        QString newMetaGroupId = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
        QDomNodeList profilelist = doc.elementsByTagName("profile");
        int i = 0;
        while (!profilelist.item(i).isNull()) {
            // make sure a profile with same name doesn't exist
            documentElement = profilelist.item(i).toElement();
            QString profileName = documentElement.attribute("name");
            if (profileName == newProfileName) {
                // a profile with that same name already exists
                bool ok;
                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);
                if (!ok) return;
                if (profileName == newProfileName) {
                    profiles.removeChild(profilelist.item(i));
417 418 419
                    break;
                }
            }
420
            i++;
421
        }
422

423 424
        QDomElement profileElement = doc.createElement("profile");
        profileElement.setAttribute("name", newProfileName);
425 426
        profileElement.setAttribute("category", newGroupName);
        profileElement.setAttribute("destinationid", newMetaGroupId);
427
        profileElement.setAttribute("extension", ui.extension->text().simplified());
428
        profileElement.setAttribute("args", ui.parameters->toPlainText().simplified());
429
        profiles.appendChild(profileElement);
430 431

        //QCString save = doc.toString().utf8();
432
        delete d;
433
        if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
434
            KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
435 436 437 438
            return;
        }
        QTextStream out(&file);
        out << doc.toString();
439 440 441 442 443
        if (file.error() != QFile::NoError) {
            KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
            file.close();
            return;
        }
444
        file.close();
445
        parseProfiles(newMetaGroupId, newGroupName, newProfileName);
446
    } else delete d;
447 448
}

449 450
void RenderWidget::slotDeleteProfile(bool refresh)
{
451 452 453 454 455 456 457 458 459 460 461
    //TODO: delete a profile installed by KNewStuff the easy way
    /*
    QString edit = m_view.size_list->currentItem()->data(EditableRole).toString();
    if (!edit.endsWith("customprofiles.xml")) {
        // This is a KNewStuff installed file, process through KNS
        KNS::Engine engine(0);
        if (engine.init("kdenlive_render.knsrc")) {
            KNS::Entry::List entries;
        }
        return;
    }*/
462 463
    QString currentGroup = m_view.format_list->currentItem()->text();
    QString currentProfile = m_view.size_list->currentItem()->text();
464
    QString metaGroupId = m_view.destination_list->itemData(m_view.destination_list->currentIndex(), Qt::UserRole).toString();
465

466
    QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
467 468 469 470 471 472
    QDomDocument doc;
    QFile file(exportFile);
    doc.setContent(&file, false);
    file.close();

    QDomElement documentElement;
473
    QDomNodeList profiles = doc.elementsByTagName("profile");
474
    int i = 0;
475 476 477
    QString groupName;
    QString profileName;
    QString destination;
478

479 480 481 482 483 484 485 486 487
    while (!profiles.item(i).isNull()) {
        documentElement = profiles.item(i).toElement();
        profileName = documentElement.attribute("name");
        groupName = documentElement.attribute("category");
        destination = documentElement.attribute("destinationid");

        if (profileName == currentProfile && groupName == currentGroup && destination == metaGroupId) {
            kDebug() << "// GOT it: " << profileName;
            doc.documentElement().removeChild(profiles.item(i));
488 489 490 491 492 493 494 495 496 497 498
            break;
        }
        i++;
    }

    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
        return;
    }
    QTextStream out(&file);
    out << doc.toString();
499 500 501 502 503
    if (file.error() != QFile::NoError) {
        KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
        file.close();
        return;
    }
504
    file.close();
505 506 507 508
    if (refresh) {
        parseProfiles(metaGroupId, currentGroup);
        focusFirstVisibleItem();
    }
509 510
}

511 512
void RenderWidget::updateButtons()
{
513 514 515 516
    if (!m_view.size_list->currentItem() || m_view.size_list->currentItem()->isHidden()) {
        m_view.buttonSave->setEnabled(false);
        m_view.buttonDelete->setEnabled(false);
        m_view.buttonEdit->setEnabled(false);
517
        m_view.buttonStart->setEnabled(false);
518 519
    } else {
        m_view.buttonSave->setEnabled(true);
520
        m_view.buttonStart->setEnabled(m_view.size_list->currentItem()->flags() & Qt::ItemIsEnabled);
521
        QString edit = m_view.size_list->currentItem()->data(EditableRole).toString();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
522
        if (edit.isEmpty() || !edit.endsWith("customprofiles.xml")) {
523 524 525
            m_view.buttonDelete->setEnabled(false);
            m_view.buttonEdit->setEnabled(false);
        } else {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
526 527
            m_view.buttonDelete->setEnabled(true);
            m_view.buttonEdit->setEnabled(true);
528 529 530 531 532
        }
    }
}


533 534
void RenderWidget::focusFirstVisibleItem()
{
535 536 537 538
    if (m_view.size_list->currentItem() && !m_view.size_list->currentItem()->isHidden()) {
        updateButtons();
        return;
    }
539
    for (int ix = 0; ix < m_view.size_list->count(); ix++) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
540 541 542 543 544
        QListWidgetItem *item = m_view.size_list->item(ix);
        if (item && !item->isHidden()) {
            m_view.size_list->setCurrentRow(ix);
            break;
        }
545 546
    }
    if (!m_view.size_list->currentItem()) m_view.size_list->setCurrentRow(0);
547
    updateButtons();
548 549
}

550 551
void RenderWidget::slotExport(bool scriptExport)
{
552 553
    QListWidgetItem *item = m_view.size_list->currentItem();
    if (!item) return;
554

555 556 557
    const QString dest = m_view.out_file->url().path();
    if (dest.isEmpty()) return;
    QFile f(dest);
558
    if (f.exists()) {
559
        if (KMessageBox::warningYesNo(this, i18n("Output file already exists. Do you want to overwrite it ?")) != KMessageBox::Yes)
560 561
            return;
    }
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583

    QString scriptName;
    if (scriptExport) {
        bool ok;
        int ix = 0;
        QString scriptsFolder = m_projectFolder + "/scripts/";
        KStandardDirs::makeDir(scriptsFolder);
        QString path = scriptsFolder + i18n("script") + QString::number(ix).rightJustified(3, '0', false) + ".sh";
        while (QFile::exists(path)) {
            ix++;
            path = scriptsFolder + i18n("script") + QString::number(ix).rightJustified(3, '0', false) + ".sh";
        }
        scriptName = QInputDialog::getText(this, i18n("Create Render Script"), i18n("Script name (will be saved in: %1)", scriptsFolder), QLineEdit::Normal, KUrl(path).fileName(), &ok);
        if (!ok || scriptName.isEmpty()) return;
        scriptName.prepend(scriptsFolder);
        QFile f(scriptName);
        if (f.exists()) {
            if (KMessageBox::warningYesNo(this, i18n("Script file already exists. Do you want to overwrite it ?")) != KMessageBox::Yes)
                return;
        }
    }

584 585 586 587 588 589 590
    QStringList overlayargs;
    if (m_view.tc_overlay->isChecked()) {
        QString filterFile = KStandardDirs::locate("appdata", "metadata.properties");
        overlayargs << "meta.attr.timecode=1" << "meta.attr.timecode.markup=#timecode";
        overlayargs << "-attach" << "data_feed:attr_check" << "-attach";
        overlayargs << "data_show:" + filterFile << "_fezzik=1" << "dynamic=1";
    }
591 592 593 594 595 596
    double startPos = -1;
    double endPos = -1;
    if (m_view.render_guide->isChecked()) {
        startPos = m_view.guide_start->itemData(m_view.guide_start->currentIndex()).toDouble();
        endPos = m_view.guide_end->itemData(m_view.guide_end->currentIndex()).toDouble();
    }
597
    QString renderArgs = m_view.advanced_params->toPlainText().simplified();
598 599 600 601 602 603 604 605 606 607 608

    // Adjust frame scale
    int width;
    int height;
    if (m_view.rescale->isChecked() && m_view.rescale->isEnabled()) {
        width = m_view.rescale_size->text().section('x', 0, 0).toInt();
        height = m_view.rescale_size->text().section('x', 1, 1).toInt();
    } else {
        width = m_profile.width;
        height = m_profile.height;
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
609
    renderArgs.replace("%dar", '@' + QString::number(m_profile.display_aspect_num) + '/' + QString::number(m_profile.display_aspect_den));
610 611 612 613 614 615 616 617

    // Adjust scanning
    if (m_view.scanning_list->currentIndex() == 1) renderArgs.append(" progressive=1");
    else if (m_view.scanning_list->currentIndex() == 2) renderArgs.append(" progressive=0");

    // disable audio if requested
    if (!m_view.export_audio->isChecked())
        renderArgs.append(" an=1 ");
618 619 620 621 622

    // Check if the rendering profile is different from project profile,
    // in which case we need to use the producer_comsumer from MLT
    bool resizeProfile = false;

623
    QString std = renderArgs;
624
    QString destination = m_view.destination_list->itemData(m_view.destination_list->currentIndex()).toString();
625 626 627 628 629 630
    if (std.startsWith("s=")) {
        QString subsize = std.section(' ', 0, 0).toLower();
        subsize = subsize.section("=", 1, 1);
        const QString currentSize = QString::number(width) + 'x' + QString::number(height);
        if (subsize != currentSize) resizeProfile = true;
    } else if (std.contains(" s=")) {
631 632
        QString subsize = std.section(" s=", 1, 1);
        subsize = subsize.section(' ', 0, 0).toLower();
633
        const QString currentSize = QString::number(width) + 'x' + QString::number(height);
634
        if (subsize != currentSize) resizeProfile = true;
635
    } else if (destination != "audioonly") {
636
        // Add current size parametrer
637
        renderArgs.append(QString(" s=%1x%2").arg(width).arg(height));
638 639
    }

640 641
    // insert item in running jobs list
    QTreeWidgetItem *renderItem;
642
    QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1);
643
    if (!existing.isEmpty()) renderItem = existing.at(0);
644 645
    else renderItem = new QTreeWidgetItem(m_view.running_jobs, QStringList() << QString() << dest << QString());
    renderItem->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
646
    renderItem->setData(1, Qt::UserRole + 1, QTime::currentTime());
647

648
    // Set rendering type
649 650 651 652
    QString group = m_view.size_list->currentItem()->data(MetaGroupRole).toString();
    if (group == "dvd" && m_view.open_dvd->isChecked()) {
        renderItem->setData(0, Qt::UserRole, group);
        if (renderArgs.contains("profile=")) {
653
            // rendering profile contains an MLT profile, so pass it to the running jog item, useful for dvd
654 655 656 657 658
            QString prof = renderArgs.section("profile=", 1, 1);
            prof = prof.section(' ', 0, 0);
            kDebug() << "// render profile: " << prof;
            renderItem->setData(0, Qt::UserRole + 1, prof);
        }
659 660 661 662 663
    } else if (group == "websites" && m_view.open_browser->isChecked()) {
        renderItem->setData(0, Qt::UserRole, group);
        // pass the url
        QString url = m_view.size_list->currentItem()->data(ExtraRole).toString();
        renderItem->setData(0, Qt::UserRole + 1, url);
664
    }
665

666 667 668 669 670 671
    emit doRender(dest, item->data(RenderRole).toString(), overlayargs, renderArgs.simplified().split(' '), m_view.render_zone->isChecked(), m_view.play_after->isChecked(), startPos, endPos, resizeProfile, scriptName);
    if (scriptName.isEmpty()) m_view.tabWidget->setCurrentIndex(1);
    else {
        QTimer::singleShot(400, this, SLOT(parseScriptFiles()));
        m_view.tabWidget->setCurrentIndex(2);
    }
672 673
}

674 675
void RenderWidget::setProfile(MltVideoProfile profile)
{
676 677 678
    m_profile = profile;
    //WARNING: this way to tell the video standard is a bit hackish...
    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);
679
    else m_view.format_selection->setCurrentIndex(1);
680
    m_view.scanning_list->setCurrentIndex(0);
681 682 683
    refreshView();
}

684 685
void RenderWidget::refreshView()
{
686
    m_view.size_list->blockSignals(true);
687 688 689
    QListWidgetItem *sizeItem;

    QString destination;
690
    KIcon brokenIcon("dialog-close");
691 692 693
    if (m_view.destination_list->currentIndex() > 0)
        destination = m_view.destination_list->itemData(m_view.destination_list->currentIndex()).toString();

694

695 696 697 698 699 700 701 702 703 704
    if (destination == "dvd") m_view.open_dvd->setVisible(true);
    else m_view.open_dvd->setVisible(false);
    if (destination == "websites") m_view.open_browser->setVisible(true);
    else m_view.open_browser->setVisible(false);
    if (!destination.isEmpty() && QString("dvd websites audioonly").contains(destination))
        m_view.rescale->setEnabled(false);
    else m_view.rescale->setEnabled(true);
    // hide groups that are not in the correct destination
    for (int i = 0; i < m_view.format_list->count(); i++) {
        sizeItem = m_view.format_list->item(i);
705
        if (sizeItem->data(MetaGroupRole).toString() == destination) {
706
            sizeItem->setHidden(false);
707
            //kDebug() << "// SET GRP:: " << sizeItem->text() << ", METY:" << sizeItem->data(MetaGroupRole).toString();
708
        } else sizeItem->setHidden(true);
709 710 711 712 713 714 715 716 717 718 719
    }

    // activate first visible item
    QListWidgetItem * item = m_view.format_list->currentItem();
    if (!item || item->isHidden()) {
        for (int i = 0; i < m_view.format_list->count(); i++) {
            if (!m_view.format_list->item(i)->isHidden()) {
                m_view.format_list->setCurrentRow(i);
                break;
            }
        }
720 721
        item = m_view.format_list->currentItem();
    }
722 723 724 725 726 727 728 729
    if (!item || item->isHidden()) {
        m_view.format_list->setEnabled(false);
        m_view.size_list->setEnabled(false);
        return;
    } else {
        m_view.format_list->setEnabled(true);
        m_view.size_list->setEnabled(true);
    }
730 731 732 733 734 735
    int count = 0;
    for (int i = 0; i < m_view.format_list->count() && count < 2; i++) {
        if (!m_view.format_list->isRowHidden(i)) count++;
    }
    if (count > 1) m_view.format_list->setVisible(true);
    else m_view.format_list->setVisible(false);
736 737 738
    QString std;
    QString group = item->text();
    bool firstSelected = false;
739 740 741
    const QStringList formatsList = KdenliveSettings::supportedformats();
    const QStringList vcodecsList = KdenliveSettings::videocodecs();
    const QStringList acodecsList = KdenliveSettings::audiocodecs();
742

743 744
    for (int i = 0; i < m_view.size_list->count(); i++) {
        sizeItem = m_view.size_list->item(i);
745
        if ((sizeItem->data(GroupRole) == group || sizeItem->data(GroupRole).toString().isEmpty()) && sizeItem->data(MetaGroupRole) == destination) {
746
            std = sizeItem->data(StandardRole).toString();
747 748 749 750 751 752 753 754
            if (!std.isEmpty()) {
                if (std.contains("PAL", Qt::CaseInsensitive)) sizeItem->setHidden(m_view.format_selection->currentIndex() != 0);
                else if (std.contains("NTSC", Qt::CaseInsensitive)) sizeItem->setHidden(m_view.format_selection->currentIndex() != 1);
            } else {
                sizeItem->setHidden(false);
                if (!firstSelected) m_view.size_list->setCurrentItem(sizeItem);
                firstSelected = true;
            }
755

756 757 758 759 760 761 762 763 764 765 766
            if (!sizeItem->isHidden()) {
                // Make sure the selected profile uses an installed avformat codec / format
                std = sizeItem->data(ParamsRole).toString();

                if (!formatsList.isEmpty()) {
                    QString format;
                    if (std.startsWith("f=")) format = std.section("f=", 1, 1);
                    else if (std.contains(" f=")) format = std.section(" f=", 1, 1);
                    if (!format.isEmpty()) {
                        format = format.section(' ', 0, 0).toLower();
                        if (!formatsList.contains(format)) {
767
                            kDebug() << "***** UNSUPPORTED F: " << format;
768
                            //sizeItem->setHidden(true);
769
                            sizeItem->setFlags(Qt::ItemIsSelectable);
770 771
                            sizeItem->setToolTip(i18n("Unsupported video format: %1", format));
                            sizeItem->setIcon(brokenIcon);
772 773 774 775 776 777 778 779 780 781 782
                        }
                    }
                }
                if (!acodecsList.isEmpty() && !sizeItem->isHidden()) {
                    QString format;
                    if (std.startsWith("acodec=")) format = std.section("acodec=", 1, 1);
                    else if (std.contains(" acodec=")) format = std.section(" acodec=", 1, 1);
                    if (!format.isEmpty()) {
                        format = format.section(' ', 0, 0).toLower();
                        if (!acodecsList.contains(format)) {
                            kDebug() << "*****  UNSUPPORTED ACODEC: " << format;
783
                            //sizeItem->setHidden(true);
784
                            sizeItem->setFlags(Qt::ItemIsSelectable);
785 786
                            sizeItem->setToolTip(i18n("Unsupported audio codec: %1", format));
                            sizeItem->setIcon(brokenIcon);
787 788 789 790 791 792 793 794 795 796 797
                        }
                    }
                }
                if (!vcodecsList.isEmpty() && !sizeItem->isHidden()) {
                    QString format;
                    if (std.startsWith("vcodec=")) format = std.section("vcodec=", 1, 1);
                    else if (std.contains(" vcodec=")) format = std.section(" vcodec=", 1, 1);
                    if (!format.isEmpty()) {
                        format = format.section(' ', 0, 0).toLower();
                        if (!vcodecsList.contains(format)) {
                            kDebug() << "*****  UNSUPPORTED VCODEC: " << format;
798
                            //sizeItem->setHidden(true);
799
                            sizeItem->setFlags(Qt::ItemIsSelectable);
800 801
                            sizeItem->setToolTip(i18n("Unsupported video codec: %1", format));
                            sizeItem->setIcon(brokenIcon);
802 803 804 805
                        }
                    }
                }
            }
806 807
        } else sizeItem->setHidden(true);
    }
808
    focusFirstVisibleItem();
809 810
    m_view.size_list->blockSignals(false);
    refreshParams();
811 812
}

813 814
KUrl RenderWidget::filenameWithExtension(KUrl url, QString extension)
{
815 816 817 818 819 820 821 822
    QString path;
    if (!url.isEmpty()) {
        path = url.path();
        int pos = path.lastIndexOf('.') + 1;
        if (pos == 0) path.append('.' + extension);
        else path = path.left(pos) + extension;

    } else {
823
        path = m_projectFolder + "/untitled." + extension;
824 825 826 827 828
    }
    return KUrl(path);
}


829 830
void RenderWidget::refreshParams()
{
831
    QListWidgetItem *item = m_view.size_list->currentItem();
832 833 834 835 836
    if (!item || item->isHidden()) {
        m_view.advanced_params->clear();
        m_view.buttonStart->setEnabled(false);
        return;
    }
837 838
    QString params = item->data(ParamsRole).toString();
    QString extension = item->data(ExtensionRole).toString();
839
    m_view.advanced_params->setPlainText(params);
840 841 842 843 844 845 846 847 848
    QString destination = m_view.destination_list->itemData(m_view.destination_list->currentIndex()).toString();
    if (params.contains(" s=") || destination == "audioonly") {
        // profile has a fixed size, do not allow resize
        m_view.rescale->setEnabled(false);
        m_view.rescale_size->setEnabled(false);
    } else {
        m_view.rescale->setEnabled(true);
        m_view.rescale_size->setEnabled(true);
    }
849 850 851 852 853 854 855 856 857 858 859
    KUrl url = filenameWithExtension(m_view.out_file->url(), extension);
    m_view.out_file->setUrl(url);
//     if (!url.isEmpty()) {
//         QString path = url.path();
//         int pos = path.lastIndexOf('.') + 1;
//  if (pos == 0) path.append('.' + extension);
//         else path = path.left(pos) + extension;
//         m_view.out_file->setUrl(KUrl(path));
//     } else {
//         m_view.out_file->setUrl(KUrl(QDir::homePath() + "/untitled." + extension));
//     }
860
    m_view.out_file->setFilter("*." + extension);
861
    QString edit = item->data(EditableRole).toString();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
862
    if (edit.isEmpty() || !edit.endsWith("customprofiles.xml")) {
863 864 865
        m_view.buttonDelete->setEnabled(false);
        m_view.buttonEdit->setEnabled(false);
    } else {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
866 867
        m_view.buttonDelete->setEnabled(true);
        m_view.buttonEdit->setEnabled(true);
868
    }
869 870

    m_view.buttonStart->setEnabled(m_view.size_list->currentItem()->flags() & Qt::ItemIsEnabled);
871 872
}

873 874
void RenderWidget::reloadProfiles()
{
875 876 877
    parseProfiles();
}

878 879
void RenderWidget::parseProfiles(QString meta, QString group, QString profile)
{
880 881
    m_view.size_list->clear();
    m_view.format_list->clear();
882 883
    m_view.destination_list->clear();
    m_view.destination_list->addItem(KIcon("video-x-generic"), i18n("File rendering"));
884 885 886 887 888 889 890
    m_view.destination_list->addItem(KIcon("media-optical"), i18n("DVD"), "dvd");
    m_view.destination_list->addItem(KIcon("audio-x-generic"), i18n("Audio only"), "audioonly");
    m_view.destination_list->addItem(KIcon("applications-internet"), i18n("Web sites"), "websites");
    m_view.destination_list->addItem(KIcon("applications-multimedia"), i18n("Media players"), "mediaplayers");
    m_view.destination_list->addItem(KIcon("drive-harddisk"), i18n("Lossless / HQ"), "lossless");
    m_view.destination_list->addItem(KIcon("pda"), i18n("Mobile devices"), "mobile");

891
    QString exportFile = KStandardDirs::locate("appdata", "export/profiles.xml");
892
    parseFile(exportFile, false);
893 894 895 896 897 898


    QString exportFolder = KStandardDirs::locateLocal("appdata", "export/");
    QDir directory = QDir(exportFolder);
    QStringList filter;
    filter << "*.xml";
899 900 901 902
    QStringList fileList = directory.entryList(filter, QDir::Files);
    // We should parse customprofiles.xml in last position, so that user profiles
    // can also override profiles installed by KNewStuff
    fileList.removeAll("customprofiles.xml");
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
903
    foreach(const QString &filename, fileList)
904
    parseFile(exportFolder + '/' + filename, true);
905
    if (QFile::exists(exportFolder + "/customprofiles.xml")) parseFile(exportFolder + "/customprofiles.xml", true);
906

907 908 909 910 911
    if (!meta.isEmpty()) {
        m_view.destination_list->blockSignals(true);
        m_view.destination_list->setCurrentIndex(m_view.destination_list->findData(meta));
        m_view.destination_list->blockSignals(false);
    }
912
    refreshView();
913
    QList<QListWidgetItem *> child;
914
    if (!group.isEmpty()) child = m_view.format_list->findItems(group, Qt::MatchExactly);
915 916 917 918 919 920 921 922
    if (!child.isEmpty()) {
        for (int i = 0; i < child.count(); i++) {
            if (child.at(i)->data(MetaGroupRole).toString() == meta) {
                m_view.format_list->setCurrentItem(child.at(i));
                break;
            }
        }
    }
923 924
    child.clear();
    if (!profile.isEmpty()) child = m_view.size_list->findItems(profile, Qt::MatchExactly);
925
    if (!child.isEmpty()) m_view.size_list->setCurrentItem(child.at(0));
926 927
}

928 929
void RenderWidget::parseFile(QString exportFile, bool editable)
{
930 931
    kDebug() << "// Parsing file: " << exportFile;
    kDebug() << "------------------------------";
932 933 934
    QDomDocument doc;
    QFile file(exportFile);
    doc.setContent(&file, false);
935
    file.close();
936 937
    QDomElement documentElement;
    QDomElement profileElement;
938 939
    QString extension;
    QListWidgetItem *item;
940 941
    QDomNodeList groups = doc.elementsByTagName("group");

942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976
    if (editable || groups.count() == 0) {
        QDomElement profiles = doc.documentElement();
        if (editable && profiles.attribute("version", 0).toInt() < 1) {
            kDebug() << "// OLD profile version";
            // this is an old profile version, update it
            QDomDocument newdoc;
            QDomElement newprofiles = newdoc.createElement("profiles");
            newprofiles.setAttribute("version", 1);
            newdoc.appendChild(newprofiles);
            QDomNodeList profilelist = doc.elementsByTagName("profile");
            for (int i = 0; i < profilelist.count(); i++) {
                QString category = i18n("Custom");
                QString extension;
                QDomNode parent = profilelist.at(i).parentNode();
                if (!parent.isNull()) {
                    QDomElement parentNode = parent.toElement();
                    if (parentNode.hasAttribute("name")) category = parentNode.attribute("name");
                    extension = parentNode.attribute("extension");
                }
                profilelist.at(i).toElement().setAttribute("category", category);
                if (!extension.isEmpty()) profilelist.at(i).toElement().setAttribute("extension", extension);
                QDomNode n = profilelist.at(i).cloneNode();
                newprofiles.appendChild(newdoc.importNode(n, true));
            }
            if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
                KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
                return;
            }
            QTextStream out(&file);
            out << newdoc.toString();
            file.close();
            parseFile(exportFile, editable);
            return;
        }

977 978 979 980 981
        QDomNode node = doc.elementsByTagName("profile").at(0);
        if (node.isNull()) {
            kDebug() << "// Export file: " << exportFile << " IS BROKEN";
            return;
        }
982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998