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
        if (file.error() != QFile::NoError) {
            KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
            file.close();
Ray Lehtiniemi's avatar
Ray Lehtiniemi committed
337
            delete d;
338 339
            return;
        }
340
        file.close();
341
        parseProfiles(newMetaGroupId, newGroupName, newProfileName);
342 343 344 345
    }
    delete d;
}

346 347
void RenderWidget::slotEditProfile()
{
348 349 350 351 352 353 354 355 356 357 358
    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);
359 360 361 362 363 364 365 366 367 368 369

    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);

370 371 372 373
    ui.profile_name->setText(currentProfile);
    ui.extension->setText(extension);
    ui.parameters->setText(params);
    ui.profile_name->setFocus();
374
    d->setWindowTitle(i18n("Edit Profile"));
375
    if (d->exec() == QDialog::Accepted) {
376
        slotDeleteProfile(false);
377
        QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
378 379 380 381 382
        QDomDocument doc;
        QFile file(exportFile);
        doc.setContent(&file, false);
        file.close();
        QDomElement documentElement;
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
        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);
398
            doc.appendChild(profiles);
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
        }

        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));
418 419 420
                    break;
                }
            }
421
            i++;
422
        }
423

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

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

450 451
void RenderWidget::slotDeleteProfile(bool refresh)
{
452 453 454 455 456 457 458 459 460 461 462
    //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;
    }*/
463 464
    QString currentGroup = m_view.format_list->currentItem()->text();
    QString currentProfile = m_view.size_list->currentItem()->text();
465
    QString metaGroupId = m_view.destination_list->itemData(m_view.destination_list->currentIndex(), Qt::UserRole).toString();
466

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

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

480 481 482 483 484 485 486 487 488
    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));
489 490 491 492 493 494 495 496 497 498 499
            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();
500 501 502 503 504
    if (file.error() != QFile::NoError) {
        KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
        file.close();
        return;
    }
505
    file.close();
506 507 508 509
    if (refresh) {
        parseProfiles(metaGroupId, currentGroup);
        focusFirstVisibleItem();
    }
510 511
}

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


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

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

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

    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;
        }
    }

585 586 587 588 589 590 591
    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";
    }
592 593 594 595 596 597
    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();
    }
598
    QString renderArgs = m_view.advanced_params->toPlainText().simplified();
599 600 601 602 603 604 605 606 607 608 609

    // 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
610
    renderArgs.replace("%dar", '@' + QString::number(m_profile.display_aspect_num) + '/' + QString::number(m_profile.display_aspect_den));
611 612 613 614 615 616 617 618

    // 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 ");
619 620 621 622 623

    // 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;

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

641 642
    // insert item in running jobs list
    QTreeWidgetItem *renderItem;
643
    QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1);
644
    if (!existing.isEmpty()) renderItem = existing.at(0);
645 646
    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));
647
    renderItem->setData(1, Qt::UserRole + 1, QTime::currentTime());
648

649
    // Set rendering type
650 651 652 653
    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=")) {
654
            // rendering profile contains an MLT profile, so pass it to the running jog item, useful for dvd
655 656 657 658 659
            QString prof = renderArgs.section("profile=", 1, 1);
            prof = prof.section(' ', 0, 0);
            kDebug() << "// render profile: " << prof;
            renderItem->setData(0, Qt::UserRole + 1, prof);
        }
660 661 662 663 664
    } 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);
665
    }
666

667 668 669 670 671 672
    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);
    }
673 674
}

675 676
void RenderWidget::setProfile(MltVideoProfile profile)
{
677 678 679
    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);
680
    else m_view.format_selection->setCurrentIndex(1);
681
    m_view.scanning_list->setCurrentIndex(0);
682 683 684
    refreshView();
}

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

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

695

696 697 698 699 700 701 702 703 704 705
    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);
706
        if (sizeItem->data(MetaGroupRole).toString() == destination) {
707
            sizeItem->setHidden(false);
708
            //kDebug() << "// SET GRP:: " << sizeItem->text() << ", METY:" << sizeItem->data(MetaGroupRole).toString();
709
        } else sizeItem->setHidden(true);
710 711 712 713 714 715 716 717 718 719 720
    }

    // 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;
            }
        }
721 722
        item = m_view.format_list->currentItem();
    }
723 724 725 726 727 728 729 730
    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);
    }
731 732 733 734 735 736
    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);
737 738 739
    QString std;
    QString group = item->text();
    bool firstSelected = false;
740 741 742
    const QStringList formatsList = KdenliveSettings::supportedformats();
    const QStringList vcodecsList = KdenliveSettings::videocodecs();
    const QStringList acodecsList = KdenliveSettings::audiocodecs();
743

744 745
    for (int i = 0; i < m_view.size_list->count(); i++) {
        sizeItem = m_view.size_list->item(i);
746
        if ((sizeItem->data(GroupRole) == group || sizeItem->data(GroupRole).toString().isEmpty()) && sizeItem->data(MetaGroupRole) == destination) {
747
            std = sizeItem->data(StandardRole).toString();
748 749 750 751 752 753 754 755
            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;
            }
756

757 758 759 760 761 762 763 764 765 766 767
            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)) {
768
                            kDebug() << "***** UNSUPPORTED F: " << format;
769
                            //sizeItem->setHidden(true);
770
                            sizeItem->setFlags(Qt::ItemIsSelectable);
771 772
                            sizeItem->setToolTip(i18n("Unsupported video format: %1", format));
                            sizeItem->setIcon(brokenIcon);
773 774 775 776 777 778 779 780 781 782 783
                        }
                    }
                }
                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;
784
                            //sizeItem->setHidden(true);
785
                            sizeItem->setFlags(Qt::ItemIsSelectable);
786 787
                            sizeItem->setToolTip(i18n("Unsupported audio codec: %1", format));
                            sizeItem->setIcon(brokenIcon);
788 789 790 791 792 793 794 795 796 797 798
                        }
                    }
                }
                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;
799
                            //sizeItem->setHidden(true);
800
                            sizeItem->setFlags(Qt::ItemIsSelectable);
801 802
                            sizeItem->setToolTip(i18n("Unsupported video codec: %1", format));
                            sizeItem->setIcon(brokenIcon);
803 804 805 806
                        }
                    }
                }
            }
807 808
        } else sizeItem->setHidden(true);
    }
809
    focusFirstVisibleItem();
810 811
    m_view.size_list->blockSignals(false);
    refreshParams();
812 813
}

814 815
KUrl RenderWidget::filenameWithExtension(KUrl url, QString extension)
{
816 817 818 819 820 821 822 823
    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 {
824
        path = m_projectFolder + "/untitled." + extension;
825 826 827 828 829
    }
    return KUrl(path);
}


830 831
void RenderWidget::refreshParams()
{
832
    QListWidgetItem *item = m_view.size_list->currentItem();
833 834 835 836 837
    if (!item || item->isHidden()) {
        m_view.advanced_params->clear();
        m_view.buttonStart->setEnabled(false);
        return;
    }
838 839
    QString params = item->data(ParamsRole).toString();
    QString extension = item->data(ExtensionRole).toString();
840
    m_view.advanced_params->setPlainText(params);
841 842 843 844 845 846 847 848 849
    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);
    }
850 851 852 853 854 855 856 857 858 859 860
    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));
//     }
861
    m_view.out_file->setFilter("*." + extension);
862
    QString edit = item->data(EditableRole).toString();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
863
    if (edit.isEmpty() || !edit.endsWith("customprofiles.xml")) {
864 865 866
        m_view.buttonDelete->setEnabled(false);
        m_view.buttonEdit->setEnabled(false);
    } else {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
867 868
        m_view.buttonDelete->setEnabled(true);
        m_view.buttonEdit->setEnabled(true);
869
    }
870 871

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

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

879 880
void RenderWidget::parseProfiles(QString meta, QString group, QString profile)
{
881 882
    m_view.size_list->clear();
    m_view.format_list->clear();
883 884
    m_view.destination_list->clear();
    m_view.destination_list->addItem(KIcon("video-x-generic"), i18n("File rendering"));
885 886 887 888 889 890 891
    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");

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


    QString exportFolder = KStandardDirs::locateLocal("appdata", "export/");
    QDir directory = QDir(exportFolder);
    QStringList filter;
    filter << "*.xml";
900 901 902 903
    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
904
    foreach(const QString &filename, fileList)
905
    parseFile(exportFolder + '/' + filename, true);
906
    if (QFile::exists(exportFolder + "/customprofiles.xml")) parseFile(exportFolder + "/customprofiles.xml", true);
907

908 909 910 911 912
    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);
    }
913
    refreshView();
914
    QList<QListWidgetItem *> child;
915
    if (!group.isEmpty()) child = m_view.format_list->findItems(group, Qt::MatchExactly);
916 917 918 919 920 921 922 923
    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;
            }
        }
    }
924 925
    child.clear();
    if (!profile.isEmpty()) child = m_view.size_list->findItems(profile, Qt::MatchExactly);
926
    if (!child.isEmpty()) m_view.size_list->setCurrentItem(child.at(0));
927 928
}

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

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 977
    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;
        }

978 979 980 981 982
        QDomNode node = doc.elementsByTagName("profile").at(0);
        if (node.isNull()) {
            kDebug() << "// Export file: " << exportFile << " IS BROKEN";
            return;
        }
983 984 985