Warning, file /multimedia/kdenlive/src/dialogs/clipcreationdialog.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002 SPDX-FileCopyrightText: 2015 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003 This file is part of Kdenlive. See www.kdenlive.org.
0004 
0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "clipcreationdialog.h"
0009 #include "bin/bin.h"
0010 #include "bin/bincommands.h"
0011 #include "bin/clipcreator.hpp"
0012 #include "bin/projectclip.h"
0013 #include "bin/projectitemmodel.h"
0014 #include "core.h"
0015 #include "doc/docundostack.hpp"
0016 #include "doc/kdenlivedoc.h"
0017 #include "glaxnimatelauncher.h"
0018 #include "kdenlive_debug.h"
0019 #include "kdenlivesettings.h"
0020 #include "project/dialogs/slideshowclip.h"
0021 #include "titler/titlewidget.h"
0022 #include "titletemplatedialog.h"
0023 #include "ui_colorclip_ui.h"
0024 #include "ui_qtextclip_ui.h"
0025 #include "utils/devices.hpp"
0026 #include "utils/qcolorutils.h"
0027 #include "widgets/timecodedisplay.h"
0028 #include "xml/xml.hpp"
0029 
0030 #include <KFileWidget>
0031 #include <KIO/RenameDialog>
0032 #include <KLocalizedString>
0033 #include <KMessageBox>
0034 #include <KRecentDirs>
0035 #include <KWindowConfig>
0036 
0037 #include <QDialog>
0038 #include <QDir>
0039 #include <QMimeDatabase>
0040 #include <QPointer>
0041 #include <QProcess>
0042 #include <QPushButton>
0043 #include <QStandardPaths>
0044 #include <QUndoCommand>
0045 #include <QWindow>
0046 
0047 #include <unordered_map>
0048 #include <utility>
0049 
0050 // static
0051 QStringList ClipCreationDialog::getExtensions()
0052 {
0053     // Build list of MIME types
0054     QStringList mimeTypes = QStringList() << QStringLiteral("application/x-kdenlivetitle") << QStringLiteral("video/mlt-playlist")
0055                                           << QStringLiteral("text/plain") << QStringLiteral("application/x-kdenlive");
0056 
0057     // Video MIMEs
0058     mimeTypes << QStringLiteral("video/x-flv") << QStringLiteral("application/vnd.rn-realmedia") << QStringLiteral("video/x-dv") << QStringLiteral("video/dv")
0059               << QStringLiteral("video/x-msvideo") << QStringLiteral("video/x-matroska") << QStringLiteral("video/mpeg") << QStringLiteral("video/ogg")
0060               << QStringLiteral("video/x-ms-wmv") << QStringLiteral("video/mp4") << QStringLiteral("video/quicktime") << QStringLiteral("video/webm")
0061               << QStringLiteral("video/3gpp") << QStringLiteral("video/mp2t");
0062 
0063     // Audio MIMEs
0064     mimeTypes << QStringLiteral("audio/AMR") << QStringLiteral("audio/x-flac") << QStringLiteral("audio/x-matroska") << QStringLiteral("audio/mp4")
0065               << QStringLiteral("audio/mpeg") << QStringLiteral("audio/x-mp3") << QStringLiteral("audio/ogg") << QStringLiteral("audio/x-wav")
0066               << QStringLiteral("audio/x-aiff") << QStringLiteral("audio/aiff") << QStringLiteral("application/ogg") << QStringLiteral("application/mxf")
0067               << QStringLiteral("application/x-shockwave-flash") << QStringLiteral("audio/ac3") << QStringLiteral("audio/aac");
0068 
0069     // Image MIMEs
0070     mimeTypes << QStringLiteral("image/gif") << QStringLiteral("image/jpeg") << QStringLiteral("image/png") << QStringLiteral("image/x-tga")
0071               << QStringLiteral("image/x-bmp") << QStringLiteral("image/svg+xml") << QStringLiteral("image/tiff") << QStringLiteral("image/x-xcf")
0072               << QStringLiteral("image/x-xcf-gimp") << QStringLiteral("image/x-vnd.adobe.photoshop") << QStringLiteral("image/x-pcx")
0073               << QStringLiteral("image/x-exr") << QStringLiteral("image/x-portable-pixmap") << QStringLiteral("application/x-krita")
0074               << QStringLiteral("image/webp") << QStringLiteral("image/jp2") << QStringLiteral("image/avif") << QStringLiteral("image/heif")
0075               << QStringLiteral("image/jxl");
0076 
0077     // Lottie animations
0078     bool allowLottie = KdenliveSettings::producerslist().contains(QLatin1String("glaxnimate"));
0079     if (allowLottie) {
0080         mimeTypes << QStringLiteral("application/json");
0081     }
0082 
0083     // Some newer mimetypes might not be registered on some older Operating Systems, so register manually
0084     QMap<QString, QString> manualMap;
0085     manualMap.insert(QStringLiteral("image/avif"), QStringLiteral("*.avif"));
0086     manualMap.insert(QStringLiteral("image/heif"), QStringLiteral("*.heif"));
0087     manualMap.insert(QStringLiteral("image/x-exr"), QStringLiteral("*.exr"));
0088     manualMap.insert(QStringLiteral("image/jp2"), QStringLiteral("*.jp2"));
0089     manualMap.insert(QStringLiteral("image/jxl"), QStringLiteral("*.jxl"));
0090 
0091     QMimeDatabase db;
0092     QStringList allExtensions;
0093     for (const QString &mimeType : qAsConst(mimeTypes)) {
0094         QMimeType mime = db.mimeTypeForName(mimeType);
0095         if (mime.isValid()) {
0096             allExtensions.append(mime.globPatterns());
0097         } else if (manualMap.contains(mimeType)) {
0098             allExtensions.append(manualMap.value(mimeType));
0099         }
0100     }
0101     if (allowLottie) {
0102         allExtensions.append(QStringLiteral("*.rawr"));
0103     }
0104     // process custom user extensions
0105     const QStringList customs = KdenliveSettings::addedExtensions().split(' ', Qt::SkipEmptyParts);
0106     if (!customs.isEmpty()) {
0107         for (const QString &ext : customs) {
0108             if (ext.startsWith(QLatin1String("*."))) {
0109                 allExtensions << ext;
0110             } else if (ext.startsWith(QLatin1String("."))) {
0111                 allExtensions << QStringLiteral("*") + ext;
0112             } else if (!ext.contains(QLatin1Char('.'))) {
0113                 allExtensions << QStringLiteral("*.") + ext;
0114             } else {
0115                 // Unrecognized format
0116                 qCDebug(KDENLIVE_LOG) << "Unrecognized custom format: " << ext;
0117             }
0118         }
0119     }
0120     allExtensions.removeDuplicates();
0121     return allExtensions;
0122 }
0123 
0124 QString ClipCreationDialog::getExtensionsFilter(const QStringList &additionalFilters)
0125 {
0126     const QString allExtensions = ClipCreationDialog::getExtensions().join(QLatin1Char(' '));
0127     QString filter = i18n("All Supported Files") + " (" + allExtensions + ')';
0128     if (!additionalFilters.isEmpty()) {
0129         filter += ";;";
0130         filter.append(additionalFilters.join(";;"));
0131     }
0132     return filter;
0133 }
0134 
0135 // static
0136 void ClipCreationDialog::createColorClip(KdenliveDoc *doc, const QString &parentFolder, std::shared_ptr<ProjectItemModel> model)
0137 {
0138     QScopedPointer<QDialog> dia(new QDialog(qApp->activeWindow()));
0139     Ui::ColorClip_UI dia_ui;
0140     dia_ui.setupUi(dia.data());
0141     dia->setWindowTitle(i18nc("@title:window", "Color Clip"));
0142     dia_ui.clip_name->setText(i18n("Color Clip"));
0143 
0144     dia_ui.clip_duration->setValue(KdenliveSettings::color_duration());
0145     dia_ui.clip_color->setColor(KdenliveSettings::colorclipcolor());
0146 
0147     if (dia->exec() == QDialog::Accepted) {
0148         QString color = dia_ui.clip_color->color().name();
0149         KdenliveSettings::setColorclipcolor(color);
0150         color = color.replace(0, 1, QStringLiteral("0x")) + "ff";
0151         int duration = doc->getFramePos(doc->timecode().getTimecode(dia_ui.clip_duration->gentime()));
0152         QString name = dia_ui.clip_name->text();
0153 
0154         ClipCreator::createColorClip(color, duration, name, parentFolder, std::move(model));
0155     }
0156 }
0157 
0158 void ClipCreationDialog::createAnimationClip(KdenliveDoc *doc, const QString &parentId)
0159 {
0160     if (!GlaxnimateLauncher::instance().checkInstalled()) {
0161         return;
0162     }
0163     QDir dir(doc->projectDataFolder());
0164     QString fileName("animation-0001.json");
0165     if (dir.cd("animations")) {
0166         int ix = 2;
0167         while (dir.exists(fileName) && ix < 9999) {
0168             QString number = QString::number(ix).rightJustified(4, '0');
0169             number.prepend(QStringLiteral("animation-"));
0170             number.append(QStringLiteral(".json"));
0171             fileName = number;
0172             ix++;
0173         }
0174     } else {
0175         dir.mkpath("animations");
0176         dir.cd("animations");
0177     }
0178     QDialog d(QApplication::activeWindow());
0179     d.setWindowTitle(i18n("Create animation"));
0180     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0181     auto *l = new QVBoxLayout;
0182     d.setLayout(l);
0183     KUrlRequester fileUrl(&d);
0184     ;
0185     fileUrl.setMode(KFile::File);
0186     fileUrl.setUrl(QUrl::fromLocalFile(dir.absoluteFilePath(fileName)));
0187     l->addWidget(new QLabel(i18n("Save animation as"), &d));
0188     l->addWidget(&fileUrl);
0189     QHBoxLayout *lay = new QHBoxLayout;
0190     lay->addWidget(new QLabel(i18n("Animation duration"), &d));
0191     TimecodeDisplay tCode(&d);
0192     tCode.setValue(QStringLiteral("00:00:05:00"));
0193     lay->addWidget(&tCode);
0194     l->addLayout(lay);
0195     l->addWidget(buttonBox);
0196     d.connect(buttonBox, &QDialogButtonBox::rejected, &d, &QDialog::reject);
0197     d.connect(buttonBox, &QDialogButtonBox::accepted, &d, &QDialog::accept);
0198     if (d.exec() != QDialog::Accepted) {
0199         return;
0200     }
0201     fileName = fileUrl.url().toLocalFile();
0202     if (QFile::exists(fileName)) {
0203         KIO::RenameDialog renameDialog(QApplication::activeWindow(), i18n("File already exists"), fileUrl.url(), fileUrl.url(),
0204                                        KIO::RenameDialog_Option::RenameDialog_Overwrite);
0205         if (renameDialog.exec() != QDialog::Rejected) {
0206             QUrl final = renameDialog.newDestUrl();
0207             if (final.isValid()) {
0208                 fileName = final.toLocalFile();
0209             }
0210         } else {
0211             return;
0212         }
0213     }
0214     int frameLength = tCode.getValue() - 1;
0215     // Params: duration, framerate, width, height
0216     const QString templateJson =
0217         QString("{\"v\":\"5.7.1\",\"ip\":0,\"op\":%1,\"nm\":\"Animation\",\"mn\":\"{c9eac49f-b1f0-482f-a8d8-302293bd1e46}\",\"fr\":%2,\"w\":%3,\"h\":%4,"
0218                 "\"assets\":[],\"layers\":[{\"ddd\":0,\"ty\":3,\"ind\":0,\"st\":0,\"ip\":0,\"op\":90,\"nm\":\"Layer\",\"mn\":\"{4d7c9721-b5ef-4075-a89c-"
0219                 "c4c5629423db}\",\"ks\":{\"a\":{\"a\":0,\"k\":[960,540]},\"p\":{\"a\":0,\"k\":[960,540]},\"s\":{\"a\":0,\"k\":[100,100]},\"r\":{\"a\":0,\"k\":"
0220                 "0},\"o\":{\"a\":0,\"k\":100}}}],\"meta\":{\"g\":\"Glaxnimate 0.5.0-52-g36d6269d\"}}")
0221             .arg(frameLength)
0222             .arg(QString::number(doc->timecode().fps()))
0223             .arg(pCore->getCurrentFrameSize().width())
0224             .arg(pCore->getCurrentFrameSize().height());
0225     QFile file(fileName);
0226     file.open(QIODevice::WriteOnly | QIODevice::Text);
0227     QTextStream out(&file);
0228     out << templateJson;
0229     file.close();
0230     GlaxnimateLauncher::instance().openFile(fileName);
0231     // Add clip to project
0232     QDomDocument xml;
0233     QDomElement prod = xml.createElement(QStringLiteral("producer"));
0234     xml.appendChild(prod);
0235     prod.setAttribute(QStringLiteral("type"), int(ClipType::Animation));
0236     int id = pCore->projectItemModel()->getFreeClipId();
0237     prod.setAttribute(QStringLiteral("id"), QString::number(id));
0238     QMap<QString, QString> properties;
0239     if (!parentId.isEmpty()) {
0240         properties.insert(QStringLiteral("kdenlive:folderid"), parentId);
0241     }
0242     properties.insert(QStringLiteral("mlt_service"), QStringLiteral("glaxnimate"));
0243     properties.insert(QStringLiteral("resource"), fileName);
0244     Xml::addXmlProperties(prod, properties);
0245     QString clipId = QString::number(id);
0246     pCore->projectItemModel()->requestAddBinClip(clipId, xml.documentElement(), parentId, i18n("Create Animation clip"));
0247 }
0248 
0249 void ClipCreationDialog::createQTextClip(KdenliveDoc *doc, const QString &parentId, Bin *bin, ProjectClip *clip)
0250 {
0251     KSharedConfigPtr config = KSharedConfig::openConfig();
0252     KConfigGroup titleConfig(config, "TitleWidget");
0253     QScopedPointer<QDialog> dia(new QDialog(bin));
0254     Ui::QTextClip_UI dia_ui;
0255     dia_ui.setupUi(dia.data());
0256     dia->setWindowTitle(i18nc("@title:window", "Text Clip"));
0257     dia_ui.fgColor->setAlphaChannelEnabled(true);
0258     dia_ui.lineColor->setAlphaChannelEnabled(true);
0259     dia_ui.bgColor->setAlphaChannelEnabled(true);
0260     if (clip) {
0261         dia_ui.name->setText(clip->clipName());
0262         dia_ui.text->setPlainText(clip->getProducerProperty(QStringLiteral("text")));
0263         dia_ui.fgColor->setColor(QColorUtils::stringToColor(clip->getProducerProperty(QStringLiteral("fgcolour"))));
0264         dia_ui.bgColor->setColor(QColorUtils::stringToColor(clip->getProducerProperty(QStringLiteral("bgcolour"))));
0265         dia_ui.pad->setValue(clip->getProducerProperty(QStringLiteral("pad")).toInt());
0266         dia_ui.lineColor->setColor(QColorUtils::stringToColor(clip->getProducerProperty(QStringLiteral("olcolour"))));
0267         dia_ui.lineWidth->setValue(clip->getProducerProperty(QStringLiteral("outline")).toInt());
0268         dia_ui.font->setCurrentFont(QFont(clip->getProducerProperty(QStringLiteral("family"))));
0269         dia_ui.fontSize->setValue(clip->getProducerProperty(QStringLiteral("size")).toInt());
0270         dia_ui.weight->setValue(clip->getProducerProperty(QStringLiteral("weight")).toInt());
0271         dia_ui.italic->setChecked(clip->getProducerProperty(QStringLiteral("style")) == QStringLiteral("italic"));
0272         dia_ui.duration->setValue(clip->frameDuration());
0273     } else {
0274         dia_ui.name->setText(i18n("Text Clip"));
0275         dia_ui.fgColor->setColor(titleConfig.readEntry(QStringLiteral("font_color")));
0276         dia_ui.bgColor->setColor(titleConfig.readEntry(QStringLiteral("background_color")));
0277         dia_ui.lineColor->setColor(titleConfig.readEntry(QStringLiteral("font_outline_color")));
0278         dia_ui.lineWidth->setValue(titleConfig.readEntry(QStringLiteral("font_outline")).toInt());
0279         dia_ui.font->setCurrentFont(QFont(titleConfig.readEntry(QStringLiteral("font_family"))));
0280         dia_ui.fontSize->setValue(titleConfig.readEntry(QStringLiteral("font_pixel_size")).toInt());
0281         dia_ui.weight->setValue(titleConfig.readEntry(QStringLiteral("font_weight")).toInt());
0282         dia_ui.italic->setChecked(titleConfig.readEntry(QStringLiteral("font_italic")).toInt() != 0);
0283     }
0284     if (dia->exec() == QDialog::Accepted) {
0285         // KdenliveSettings::setColorclipcolor(color);
0286         QMap<QString, QString> properties;
0287         properties.insert(QStringLiteral("kdenlive:clipname"), dia_ui.name->text());
0288         if (!parentId.isEmpty()) {
0289             properties.insert(QStringLiteral("kdenlive:folderid"), parentId);
0290         }
0291         int newDuration = dia_ui.duration->getValue();
0292         if (clip && (newDuration != clip->getFramePlaytime())) {
0293             // duration changed, we need to update duration
0294             properties.insert(QStringLiteral("out"), clip->framesToTime(newDuration - 1));
0295             int currentLength = clip->getProducerDuration();
0296             if (currentLength != newDuration) {
0297                 properties.insert(QStringLiteral("kdenlive:duration"), clip->framesToTime(newDuration));
0298                 properties.insert(QStringLiteral("length"), QString::number(newDuration));
0299             }
0300         }
0301 
0302         properties.insert(QStringLiteral("mlt_service"), QStringLiteral("qtext"));
0303         // properties.insert(QStringLiteral("scale"), QStringLiteral("off"));
0304         // properties.insert(QStringLiteral("fill"), QStringLiteral("0"));
0305         properties.insert(QStringLiteral("text"), dia_ui.text->document()->toPlainText());
0306         properties.insert(QStringLiteral("fgcolour"), dia_ui.fgColor->color().name(QColor::HexArgb));
0307         properties.insert(QStringLiteral("bgcolour"), dia_ui.bgColor->color().name(QColor::HexArgb));
0308         properties.insert(QStringLiteral("olcolour"), dia_ui.lineColor->color().name(QColor::HexArgb));
0309         properties.insert(QStringLiteral("outline"), QString::number(dia_ui.lineWidth->value()));
0310         properties.insert(QStringLiteral("pad"), QString::number(dia_ui.pad->value()));
0311         properties.insert(QStringLiteral("family"), dia_ui.font->currentFont().family());
0312         properties.insert(QStringLiteral("size"), QString::number(dia_ui.fontSize->value()));
0313         properties.insert(QStringLiteral("style"), dia_ui.italic->isChecked() ? QStringLiteral("italic") : QStringLiteral("normal"));
0314         properties.insert(QStringLiteral("weight"), QString::number(dia_ui.weight->value()));
0315         if (clip) {
0316             bin->slotEditClipCommand(clip->AbstractProjectItem::clipId(), clip->currentProperties(properties), properties);
0317         } else {
0318             QDomDocument xml;
0319             QDomElement prod = xml.createElement(QStringLiteral("producer"));
0320             xml.appendChild(prod);
0321             prod.setAttribute(QStringLiteral("type"), int(ClipType::QText));
0322             int id = pCore->projectItemModel()->getFreeClipId();
0323             prod.setAttribute(QStringLiteral("id"), QString::number(id));
0324 
0325             prod.setAttribute(QStringLiteral("in"), QStringLiteral("0"));
0326             prod.setAttribute(QStringLiteral("out"), newDuration);
0327             Xml::addXmlProperties(prod, properties);
0328             QString clipId = QString::number(id);
0329             pCore->projectItemModel()->requestAddBinClip(clipId, xml.documentElement(), parentId, i18n("Create Text clip"));
0330         }
0331     }
0332 }
0333 
0334 // static
0335 void ClipCreationDialog::createSlideshowClip(KdenliveDoc *doc, const QString &parentId, std::shared_ptr<ProjectItemModel> model)
0336 {
0337     QScopedPointer<SlideshowClip> dia(
0338         new SlideshowClip(doc->timecode(), KRecentDirs::dir(QStringLiteral(":KdenliveSlideShowFolder")), nullptr, QApplication::activeWindow()));
0339 
0340     if (dia->exec() == QDialog::Accepted) {
0341         // Ready, create xml
0342         KRecentDirs::add(QStringLiteral(":KdenliveSlideShowFolder"), QUrl::fromLocalFile(dia->selectedPath()).adjusted(QUrl::RemoveFilename).toLocalFile());
0343         KdenliveSettings::setSlideshowmimeextension(dia->extension());
0344         std::unordered_map<QString, QString> properties;
0345         properties[QStringLiteral("ttl")] = QString::number(doc->getFramePos(dia->clipDuration()));
0346         properties[QStringLiteral("loop")] = QString::number(static_cast<int>(dia->loop()));
0347         properties[QStringLiteral("crop")] = QString::number(static_cast<int>(dia->crop()));
0348         properties[QStringLiteral("fade")] = QString::number(static_cast<int>(dia->fade()));
0349         properties[QStringLiteral("luma_duration")] = QString::number(doc->getFramePos(dia->lumaDuration()));
0350         properties[QStringLiteral("luma_file")] = dia->lumaFile();
0351         properties[QStringLiteral("softness")] = QString::number(dia->softness());
0352         properties[QStringLiteral("animation")] = dia->animation();
0353         properties[QStringLiteral("low-pass")] = QString::number(dia->lowPass());
0354 
0355         int duration = doc->getFramePos(dia->clipDuration()) * dia->imageCount();
0356         ClipCreator::createSlideshowClip(dia->selectedPath(), duration, dia->clipName(), parentId, properties, std::move(model));
0357     }
0358 }
0359 
0360 void ClipCreationDialog::createTitleClip(KdenliveDoc *doc, const QString &parentFolder, const QString &templatePath, std::shared_ptr<ProjectItemModel> model)
0361 {
0362     // Make sure the titles folder exists
0363     QDir dir(doc->projectDataFolder() + QStringLiteral("/titles"));
0364     dir.mkpath(QStringLiteral("."));
0365     QPointer<TitleWidget> dia_ui =
0366         new TitleWidget(QUrl::fromLocalFile(templatePath), dir.absolutePath(), pCore->getMonitor(Kdenlive::ProjectMonitor), pCore->bin());
0367     if (dia_ui->exec() == QDialog::Accepted) {
0368         // Ready, create clip xml
0369         std::unordered_map<QString, QString> properties;
0370         properties[QStringLiteral("xmldata")] = dia_ui->xml().toString();
0371         QString titleSuggestion = dia_ui->titleSuggest();
0372 
0373         ClipCreator::createTitleClip(properties, dia_ui->duration(), titleSuggestion.isEmpty() ? i18n("Title clip") : titleSuggestion, parentFolder,
0374                                      std::move(model));
0375     }
0376     delete dia_ui;
0377 }
0378 
0379 void ClipCreationDialog::createTitleTemplateClip(KdenliveDoc *doc, const QString &parentFolder, std::shared_ptr<ProjectItemModel> model)
0380 {
0381 
0382     QScopedPointer<TitleTemplateDialog> dia(new TitleTemplateDialog(doc->projectDataFolder(), QApplication::activeWindow()));
0383 
0384     if (dia->exec() == QDialog::Accepted) {
0385         ClipCreator::createTitleTemplate(dia->selectedTemplate(), dia->selectedText(), i18n("Template title clip"), parentFolder, std::move(model));
0386     }
0387 }
0388 
0389 // void ClipCreationDialog::createClipsCommand(KdenliveDoc *doc, const QList<QUrl> &urls, const QStringList &groupInfo, Bin *bin,
0390 //                                             const QMap<QString, QString> &data)
0391 // {
0392 //     auto *addClips = new QUndoCommand();
0393 
0394 // TODO: check files on removable volume
0395 /*listRemovableVolumes();
0396 for (const QUrl &file :  urls) {
0397     if (QFile::exists(file.path())) {
0398         //TODO check for duplicates
0399         if (!data.contains("bypassDuplicate") && !getClipByResource(file.path()).empty()) {
0400             if (KMessageBox::warningContinueCancel(QApplication::activeWindow(), i18n("Clip <b>%1</b><br />already exists in project, what do you want to
0401 do?", file.path()), i18n("Clip already exists")) == KMessageBox::Cancel)
0402                 continue;
0403         }
0404         if (isOnRemovableDevice(file) && !isOnRemovableDevice(m_doc->projectFolder())) {
0405             int answer = KMessageBox::warningYesNoCancel(QApplication::activeWindow(), i18n("Clip <b>%1</b><br /> is on a removable device, will not be
0406 available when device is unplugged", file.path()), i18n("File on a Removable Device"), KGuiItem(i18n("Copy file to project folder")),
0407 KGuiItem(i18n("Continue")), KStandardGuiItem::cancel(), QString("copyFilesToProjectFolder"));
0408 
0409             if (answer == KMessageBox::Cancel) continue;
0410             else if (answer == KMessageBox::Yes) {
0411                 // Copy files to project folder
0412                 QDir sourcesFolder(m_doc->projectFolder().toLocalFile());
0413                 sourcesFolder.cd("clips");
0414                 KIO::MkdirJob *mkdirJob = KIO::mkdir(QUrl::fromLocalFile(sourcesFolder.absolutePath()));
0415                 KJobWidgets::setWindow(mkdirJob, QApplication::activeWindow());
0416                 if (!mkdirJob->exec()) {
0417                     KMessageBox::error(QApplication::activeWindow(), i18n("Cannot create directory %1", sourcesFolder.absolutePath()));
0418                     continue;
0419                 }
0420                 //KIO::filesize_t m_requestedSize;
0421                 KIO::CopyJob *copyjob = KIO::copy(file, QUrl::fromLocalFile(sourcesFolder.absolutePath()));
0422                 //TODO: for some reason, passing metadata does not work...
0423                 copyjob->addMetaData("group", data.value("group"));
0424                 copyjob->addMetaData("groupId", data.value("groupId"));
0425                 copyjob->addMetaData("comment", data.value("comment"));
0426                 KJobWidgets::setWindow(copyjob, QApplication::activeWindow());
0427                 connect(copyjob, &KIO::CopyJob::copyingDone, this, &ClipManager::slotAddCopiedClip);
0428                 continue;
0429             }
0430         }*/
0431 
0432 // TODO check folders
0433 /*QList< QList<QUrl> > foldersList;
0434 QMimeDatabase db;
0435 for (const QUrl & file :  list) {
0436     // Check there is no folder here
0437     QMimeType type = db.mimeTypeForUrl(file);
0438     if (type.inherits("inode/directory")) {
0439         // user dropped a folder, import its files
0440         list.removeAll(file);
0441         QDir dir(file.path());
0442         QStringList result = dir.entryList(QDir::Files);
0443         QList<QUrl> folderFiles;
0444         folderFiles << file;
0445         for (const QString & path :  result) {
0446             // TODO: create folder command
0447             folderFiles.append(QUrl::fromLocalFile(dir.absoluteFilePath(path)));
0448         }
0449         if (folderFiles.count() > 1) foldersList.append(folderFiles);
0450     }
0451 }*/
0452 //}
0453 
0454 void ClipCreationDialog::createClipsCommand(KdenliveDoc *doc, const QString &parentFolder, const std::shared_ptr<ProjectItemModel> &model)
0455 {
0456     qDebug() << "/////////// starting to add bin clips";
0457     QList<QUrl> list;
0458     QString allExtensions = getExtensions().join(QLatin1Char(' '));
0459     QString dialogFilter = allExtensions + QLatin1Char('|') + i18n("All Supported Files") + QStringLiteral("\n*|") + i18n("All Files");
0460     QCheckBox *b = new QCheckBox(i18n("Import image sequence"));
0461     b->setChecked(KdenliveSettings::autoimagesequence());
0462     QCheckBox *bf = new QCheckBox(i18n("Ignore subfolder structure"));
0463     bf->setChecked(KdenliveSettings::ignoresubdirstructure());
0464     QFrame *f = new QFrame();
0465     f->setFrameShape(QFrame::NoFrame);
0466     auto *l = new QHBoxLayout;
0467     l->addWidget(b);
0468     l->addWidget(bf);
0469     l->addStretch(5);
0470     f->setLayout(l);
0471     QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder"));
0472     QScopedPointer<QDialog> dlg(new QDialog(static_cast<QWidget *>(doc->parent())));
0473     QScopedPointer<KFileWidget> fileWidget(new KFileWidget(QUrl::fromLocalFile(clipFolder), dlg.data()));
0474     auto *layout = new QVBoxLayout;
0475     layout->addWidget(fileWidget.data());
0476     fileWidget->setCustomWidget(f);
0477     fileWidget->okButton()->show();
0478     fileWidget->cancelButton()->show();
0479     QObject::connect(fileWidget->okButton(), &QPushButton::clicked, fileWidget.data(), &KFileWidget::slotOk);
0480     QObject::connect(fileWidget.data(), &KFileWidget::accepted, fileWidget.data(), &KFileWidget::accept);
0481     QObject::connect(fileWidget.data(), &KFileWidget::accepted, dlg.data(), &QDialog::accept);
0482     QObject::connect(fileWidget->cancelButton(), &QPushButton::clicked, dlg.data(), &QDialog::reject);
0483     dlg->setLayout(layout);
0484     fileWidget->setFilter(dialogFilter);
0485     fileWidget->setMode(KFile::Files | KFile::ExistingOnly | KFile::LocalOnly | KFile::Directory);
0486     KSharedConfig::Ptr conf = KSharedConfig::openConfig();
0487     QWindow *handle = dlg->windowHandle();
0488     if ((handle != nullptr) && conf->hasGroup("FileDialogSize")) {
0489         KWindowConfig::restoreWindowSize(handle, conf->group("FileDialogSize"));
0490         dlg->resize(handle->size());
0491     }
0492     int result = dlg->exec();
0493     if (result == QDialog::Accepted) {
0494         KdenliveSettings::setAutoimagesequence(b->isChecked());
0495         KdenliveSettings::setIgnoresubdirstructure(bf->isChecked());
0496         list = fileWidget->selectedUrls();
0497         if (!list.isEmpty()) {
0498             KRecentDirs::add(QStringLiteral(":KdenliveClipFolder"), list.constFirst().adjusted(QUrl::RemoveFilename).toLocalFile());
0499         }
0500         if (KdenliveSettings::autoimagesequence() && list.count() >= 1) {
0501             // Check for image sequence
0502             const QUrl &url = list.at(0);
0503             QString fileName = url.fileName().section(QLatin1Char('.'), 0, -2);
0504             if (!fileName.isEmpty() && fileName.at(fileName.size() - 1).isDigit()) {
0505                 KFileItem item(url);
0506                 if (item.mimetype().startsWith(QLatin1String("image"))) {
0507                     // import as sequence if we found more than one image in the sequence
0508                     QStringList patternlist;
0509                     QString pattern = SlideshowClip::selectedPath(url, false, QString(), &patternlist);
0510                     qCDebug(KDENLIVE_LOG) << " / // IMPORT PATTERN: " << pattern << " COUNT: " << patternlist.count();
0511                     int count = patternlist.count();
0512                     if (count > 1) {
0513                         // get image sequence base name
0514                         while (fileName.size() > 0 && fileName.at(fileName.size() - 1).isDigit()) {
0515                             fileName.chop(1);
0516                         }
0517 
0518                         QString duration = doc->timecode().reformatSeparators(KdenliveSettings::sequence_duration());
0519                         std::unordered_map<QString, QString> properties;
0520                         properties[QStringLiteral("ttl")] = QString::number(doc->getFramePos(duration));
0521                         properties[QStringLiteral("loop")] = QString::number(0);
0522                         properties[QStringLiteral("crop")] = QString::number(0);
0523                         properties[QStringLiteral("fade")] = QString::number(0);
0524                         properties[QStringLiteral("luma_duration")] =
0525                             QString::number(doc->getFramePos(doc->timecode().getTimecodeFromFrames(int(ceil(doc->timecode().fps())))));
0526                         int frame_duration = doc->getFramePos(duration) * count;
0527                         ClipCreator::createSlideshowClip(pattern, frame_duration, fileName, parentFolder, properties, model);
0528                         return;
0529                     }
0530                 }
0531             }
0532         }
0533     }
0534     qDebug() << "/////////// found list" << list;
0535     KConfigGroup group = conf->group("FileDialogSize");
0536     if (handle) {
0537         KWindowConfig::saveWindowSize(handle, group);
0538     }
0539     Fun undo = []() { return true; };
0540     Fun redo = []() { return true; };
0541     const QString id = ClipCreator::createClipsFromList(list, true, parentFolder, model, undo, redo);
0542 
0543     // We reset the state of the "don't ask again" for the question about removable devices
0544     KMessageBox::enableMessage(QStringLiteral("removable"));
0545     if (!id.isEmpty()) {
0546         pCore->pushUndo(undo, redo, i18np("Add clip", "Add clips", list.size()));
0547     }
0548 }
0549 
0550 void ClipCreationDialog::clipWidget(QDockWidget *m_DockClipWidget)
0551 {
0552     QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder"));
0553     KFileWidget *fileWidget = new KFileWidget(QUrl::fromLocalFile(clipFolder), m_DockClipWidget);
0554     fileWidget->setMode(KFile::Files | KFile::ExistingOnly | KFile::LocalOnly | KFile::Directory);
0555     QString allExtensions = getExtensions().join(QLatin1Char(' '));
0556     QString dialogFilter = allExtensions + QLatin1Char('|') + i18n("All Supported Files") + QStringLiteral("\n*|") + i18n("All Files");
0557 
0558     QPushButton *importseq = new QPushButton(i18n("Import image sequence"));
0559     // Make importseq checkable so that we can differentiate between a double click in filewidget and a click on the pushbutton
0560     importseq->setCheckable(true);
0561     QCheckBox *b = new QCheckBox(i18n("Ignore subfolder structure"));
0562     b->setChecked(KdenliveSettings::ignoresubdirstructure());
0563     QFrame *f = new QFrame();
0564     f->setFrameShape(QFrame::NoFrame);
0565     auto *l = new QHBoxLayout;
0566     l->addWidget(b);
0567     l->addStretch(5);
0568     l->addWidget(importseq);
0569     f->setLayout(l);
0570     fileWidget->setCustomWidget(f);
0571     // Required to only add file on double click and not on single click
0572     fileWidget->setOperationMode(KFileWidget::Saving);
0573     QObject::connect(fileWidget, &KFileWidget::accepted, fileWidget, [fileWidget, importseq]() {
0574         if (importseq->isChecked()) {
0575             // We are importing an image sequence, abort
0576             return;
0577         }
0578         fileWidget->accept();
0579         QList<QUrl> urls = fileWidget->selectedUrls();
0580         pCore->bin()->droppedUrls(urls);
0581     });
0582     fileWidget->setFilter(dialogFilter);
0583     QObject::connect(b, &QCheckBox::toggled, [](bool checked) { KdenliveSettings::setIgnoresubdirstructure(checked); });
0584     QObject::connect(importseq, &QPushButton::clicked, fileWidget, [=] {
0585         fileWidget->slotOk();
0586         emit fileWidget->accepted();
0587         fileWidget->accept();
0588         QUrl url = fileWidget->selectedUrl();
0589         QStringList patternlist;
0590         QString pattern = SlideshowClip::selectedPath(url, false, QString(), &patternlist);
0591         int count = patternlist.size();
0592 
0593         QString fileName = url.fileName().section(QLatin1Char('.'), 0, -2);
0594         importseq->setChecked(false);
0595         if (count >= 1) {
0596             while (fileName.size() > 0 && fileName.at(fileName.size() - 1).isDigit()) {
0597                 fileName.chop(1);
0598             }
0599             QString parentFolder = "-1";
0600             KdenliveDoc *doc = pCore->currentDoc();
0601             QString duration = doc->timecode().reformatSeparators(KdenliveSettings::sequence_duration());
0602             std::unordered_map<QString, QString> properties;
0603             properties[QStringLiteral("ttl")] = QString::number(doc->getFramePos(duration));
0604             properties[QStringLiteral("loop")] = QString::number(0);
0605             properties[QStringLiteral("crop")] = QString::number(0);
0606             properties[QStringLiteral("fade")] = QString::number(0);
0607             properties[QStringLiteral("luma_duration")] =
0608                 QString::number(doc->getFramePos(doc->timecode().getTimecodeFromFrames(int(ceil(doc->timecode().fps())))));
0609             int frame_duration = doc->getFramePos(duration) * count;
0610             ClipCreator::createSlideshowClip(pattern, frame_duration, fileName, parentFolder, properties, pCore->projectItemModel());
0611             return;
0612         }
0613     });
0614     m_DockClipWidget->setWidget(fileWidget);
0615 }