File indexing completed on 2024-04-14 04:46:17

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