1 /*
2 SPDX-FileCopyrightText: 2015 Jean-Baptiste Mardelle <jb@kdenlive.org>
3 This file is part of Kdenlive. See www.kdenlive.org.
4 
5 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
6 */
7 
8 #include "clipcreationdialog.h"
9 #include "bin/bin.h"
10 #include "bin/bincommands.h"
11 #include "bin/clipcreator.hpp"
12 #include "bin/projectclip.h"
13 #include "bin/projectitemmodel.h"
14 #include "core.h"
15 #include "definitions.h"
16 #include "doc/docundostack.hpp"
17 #include "doc/kdenlivedoc.h"
18 #include "kdenlive_debug.h"
19 #include "kdenlivesettings.h"
20 #include "project/dialogs/slideshowclip.h"
21 #include "timecodedisplay.h"
22 #include "titler/titlewidget.h"
23 #include "titletemplatedialog.h"
24 #include "ui_colorclip_ui.h"
25 #include "ui_qtextclip_ui.h"
26 #include "utils/devices.hpp"
27 #include "xml/xml.hpp"
28 
29 #include "klocalizedstring.h"
30 #include <KFileWidget>
31 #include <KMessageBox>
32 #include <KRecentDirs>
33 #include <KWindowConfig>
34 #include <KDirOperator>
35 
36 #include <QDialog>
37 #include <QDir>
38 #include <QMimeDatabase>
39 #include <QPointer>
40 #include <QPushButton>
41 #include <QStandardPaths>
42 #include <QUndoCommand>
43 #include <QWindow>
44 #include <unordered_map>
45 #include <utility>
46 
47 // static
getExtensions()48 QStringList ClipCreationDialog::getExtensions()
49 {
50     // Build list of MIME types
51     QStringList mimeTypes = QStringList() << QLatin1String("") << QStringLiteral("application/x-kdenlivetitle") << QStringLiteral("video/mlt-playlist")
52                                           << QStringLiteral("text/plain") << QStringLiteral("application/x-kdenlive");
53 
54     // Video MIMEs
55     mimeTypes << QStringLiteral("video/x-flv") << QStringLiteral("application/vnd.rn-realmedia") << QStringLiteral("video/x-dv") << QStringLiteral("video/dv")
56               << QStringLiteral("video/x-msvideo") << QStringLiteral("video/x-matroska") << QStringLiteral("video/mpeg") << QStringLiteral("video/ogg")
57               << QStringLiteral("video/x-ms-wmv") << QStringLiteral("video/mp4") << QStringLiteral("video/quicktime") << QStringLiteral("video/webm")
58               << QStringLiteral("video/3gpp") << QStringLiteral("video/mp2t");
59 
60     // Audio MIMEs
61     mimeTypes << QStringLiteral("audio/AMR") << QStringLiteral("audio/x-flac") << QStringLiteral("audio/x-matroska") << QStringLiteral("audio/mp4")
62               << QStringLiteral("audio/mpeg") << QStringLiteral("audio/x-mp3") << QStringLiteral("audio/ogg") << QStringLiteral("audio/x-wav")
63               << QStringLiteral("audio/x-aiff") << QStringLiteral("audio/aiff") << QStringLiteral("application/ogg") << QStringLiteral("application/mxf")
64               << QStringLiteral("application/x-shockwave-flash") << QStringLiteral("audio/ac3") << QStringLiteral("audio/aac");
65 
66     // Image MIMEs
67     mimeTypes << QStringLiteral("image/gif") << QStringLiteral("image/jpeg") << QStringLiteral("image/png") << QStringLiteral("image/x-tga")
68               << QStringLiteral("image/x-bmp") << QStringLiteral("image/svg+xml") << QStringLiteral("image/tiff") << QStringLiteral("image/x-xcf")
69               << QStringLiteral("image/x-xcf-gimp") << QStringLiteral("image/x-vnd.adobe.photoshop") << QStringLiteral("image/x-pcx")
70               << QStringLiteral("image/x-exr") << QStringLiteral("image/x-portable-pixmap") << QStringLiteral("application/x-krita") << QStringLiteral("image/webp");
71 
72     QMimeDatabase db;
73     QStringList allExtensions;
74     for (const QString &mimeType : qAsConst(mimeTypes)) {
75         QMimeType mime = db.mimeTypeForName(mimeType);
76         if (mime.isValid()) {
77             allExtensions.append(mime.globPatterns());
78         }
79     }
80     // process custom user extensions
81 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
82     const QStringList customs = KdenliveSettings::addedExtensions().split(' ', QString::SkipEmptyParts);
83 #else
84     const QStringList customs = KdenliveSettings::addedExtensions().split(' ', Qt::SkipEmptyParts);
85 #endif
86     if (!customs.isEmpty()) {
87         for (const QString &ext : customs) {
88             if (ext.startsWith(QLatin1String("*."))) {
89                 allExtensions << ext;
90             } else if (ext.startsWith(QLatin1String("."))) {
91                 allExtensions << QStringLiteral("*") + ext;
92             } else if (!ext.contains(QLatin1Char('.'))) {
93                 allExtensions << QStringLiteral("*.") + ext;
94             } else {
95                 // Unrecognized format
96                 qCDebug(KDENLIVE_LOG) << "Unrecognized custom format: " << ext;
97             }
98         }
99     }
100     allExtensions.removeDuplicates();
101     return allExtensions;
102 }
103 
getExtensionsFilter(const QStringList & additionalFilters)104 QString ClipCreationDialog::getExtensionsFilter(const QStringList& additionalFilters)
105 {
106     const QString allExtensions = ClipCreationDialog::getExtensions().join(QLatin1Char(' '));
107     QString filter = i18n("All Supported Files") + " (" + allExtensions + ')';
108     if (!additionalFilters.isEmpty()) {
109         filter += ";;";
110         filter.append(additionalFilters.join(";;"));
111     }
112     return filter;
113 }
114 
115 // static
createColorClip(KdenliveDoc * doc,const QString & parentFolder,std::shared_ptr<ProjectItemModel> model)116 void ClipCreationDialog::createColorClip(KdenliveDoc *doc, const QString &parentFolder, std::shared_ptr<ProjectItemModel> model)
117 {
118     QScopedPointer<QDialog> dia(new QDialog(qApp->activeWindow()));
119     Ui::ColorClip_UI dia_ui;
120     dia_ui.setupUi(dia.data());
121     dia->setWindowTitle(i18n("Color Clip"));
122     dia_ui.clip_name->setText(i18n("Color Clip"));
123 
124     QScopedPointer<TimecodeDisplay> t(new TimecodeDisplay(doc->timecode(), dia.get()));
125     t->setValue(KdenliveSettings::color_duration());
126     dia_ui.clip_durationBox->addWidget(t.data());
127     dia_ui.clip_color->setColor(KdenliveSettings::colorclipcolor());
128 
129     if (dia->exec() == QDialog::Accepted) {
130         QString color = dia_ui.clip_color->color().name();
131         KdenliveSettings::setColorclipcolor(color);
132         color = color.replace(0, 1, QStringLiteral("0x")) + "ff";
133         int duration = doc->getFramePos(doc->timecode().getTimecode(t->gentime()));
134         QString name = dia_ui.clip_name->text();
135 
136         ClipCreator::createColorClip(color, duration, name, parentFolder, std::move(model));
137     }
138 }
139 
createQTextClip(KdenliveDoc * doc,const QString & parentId,Bin * bin,ProjectClip * clip)140 void ClipCreationDialog::createQTextClip(KdenliveDoc *doc, const QString &parentId, Bin *bin, ProjectClip *clip)
141 {
142     KSharedConfigPtr config = KSharedConfig::openConfig();
143     KConfigGroup titleConfig(config, "TitleWidget");
144     QScopedPointer<QDialog> dia(new QDialog(bin));
145     Ui::QTextClip_UI dia_ui;
146     dia_ui.setupUi(dia.data());
147     dia->setWindowTitle(i18n("Text Clip"));
148     dia_ui.fgColor->setAlphaChannelEnabled(true);
149     dia_ui.lineColor->setAlphaChannelEnabled(true);
150     dia_ui.bgColor->setAlphaChannelEnabled(true);
151     if (clip) {
152         dia_ui.name->setText(clip->getProducerProperty(QStringLiteral("kdenlive:clipname")));
153         dia_ui.text->setPlainText(clip->getProducerProperty(QStringLiteral("text")));
154         dia_ui.fgColor->setColor(clip->getProducerProperty(QStringLiteral("fgcolour")));
155         dia_ui.bgColor->setColor(clip->getProducerProperty(QStringLiteral("bgcolour")));
156         dia_ui.pad->setValue(clip->getProducerProperty(QStringLiteral("pad")).toInt());
157         dia_ui.lineColor->setColor(clip->getProducerProperty(QStringLiteral("olcolour")));
158         dia_ui.lineWidth->setValue(clip->getProducerProperty(QStringLiteral("outline")).toInt());
159         dia_ui.font->setCurrentFont(QFont(clip->getProducerProperty(QStringLiteral("family"))));
160         dia_ui.fontSize->setValue(clip->getProducerProperty(QStringLiteral("size")).toInt());
161         dia_ui.weight->setValue(clip->getProducerProperty(QStringLiteral("weight")).toInt());
162         dia_ui.italic->setChecked(clip->getProducerProperty(QStringLiteral("style")) == QStringLiteral("italic"));
163         dia_ui.duration->setText(doc->timecode().getTimecodeFromFrames(clip->getProducerProperty(QStringLiteral("out")).toInt()));
164     } else {
165         dia_ui.name->setText(i18n("Text Clip"));
166         dia_ui.fgColor->setColor(titleConfig.readEntry(QStringLiteral("font_color")));
167         dia_ui.bgColor->setColor(titleConfig.readEntry(QStringLiteral("background_color")));
168         dia_ui.lineColor->setColor(titleConfig.readEntry(QStringLiteral("font_outline_color")));
169         dia_ui.lineWidth->setValue(titleConfig.readEntry(QStringLiteral("font_outline")).toInt());
170         dia_ui.font->setCurrentFont(QFont(titleConfig.readEntry(QStringLiteral("font_family"))));
171         dia_ui.fontSize->setValue(titleConfig.readEntry(QStringLiteral("font_pixel_size")).toInt());
172         dia_ui.weight->setValue(titleConfig.readEntry(QStringLiteral("font_weight")).toInt());
173         dia_ui.italic->setChecked(titleConfig.readEntry(QStringLiteral("font_italic")).toInt() != 0);
174         dia_ui.duration->setText(titleConfig.readEntry(QStringLiteral("title_duration")));
175     }
176     if (dia->exec() == QDialog::Accepted) {
177         // KdenliveSettings::setColorclipcolor(color);
178         QDomDocument xml;
179         QDomElement prod = xml.createElement(QStringLiteral("producer"));
180         xml.appendChild(prod);
181         prod.setAttribute(QStringLiteral("type"), int(ClipType::QText));
182         int id = pCore->projectItemModel()->getFreeClipId();
183         prod.setAttribute(QStringLiteral("id"), QString::number(id));
184 
185         prod.setAttribute(QStringLiteral("in"), QStringLiteral("0"));
186         prod.setAttribute(QStringLiteral("out"), doc->timecode().getFrameCount(dia_ui.duration->text()));
187 
188         QMap<QString, QString> properties;
189         properties.insert(QStringLiteral("kdenlive:clipname"), dia_ui.name->text());
190         if (!parentId.isEmpty()) {
191             properties.insert(QStringLiteral("kdenlive:folderid"), parentId);
192         }
193 
194         properties.insert(QStringLiteral("mlt_service"), QStringLiteral("qtext"));
195         properties.insert(QStringLiteral("out"), QString::number(doc->timecode().getFrameCount(dia_ui.duration->text())));
196         properties.insert(QStringLiteral("length"), dia_ui.duration->text());
197         // properties.insert(QStringLiteral("scale"), QStringLiteral("off"));
198         // properties.insert(QStringLiteral("fill"), QStringLiteral("0"));
199         properties.insert(QStringLiteral("text"), dia_ui.text->document()->toPlainText());
200         properties.insert(QStringLiteral("fgcolour"), dia_ui.fgColor->color().name(QColor::HexArgb));
201         properties.insert(QStringLiteral("bgcolour"), dia_ui.bgColor->color().name(QColor::HexArgb));
202         properties.insert(QStringLiteral("olcolour"), dia_ui.lineColor->color().name(QColor::HexArgb));
203         properties.insert(QStringLiteral("outline"), QString::number(dia_ui.lineWidth->value()));
204         properties.insert(QStringLiteral("pad"), QString::number(dia_ui.pad->value()));
205         properties.insert(QStringLiteral("family"), dia_ui.font->currentFont().family());
206         properties.insert(QStringLiteral("size"), QString::number(dia_ui.fontSize->value()));
207         properties.insert(QStringLiteral("style"), dia_ui.italic->isChecked() ? QStringLiteral("italic") : QStringLiteral("normal"));
208         properties.insert(QStringLiteral("weight"), QString::number(dia_ui.weight->value()));
209         if (clip) {
210             QMap<QString, QString> oldProperties;
211             oldProperties.insert(QStringLiteral("out"), clip->getProducerProperty(QStringLiteral("out")));
212             oldProperties.insert(QStringLiteral("length"), clip->getProducerProperty(QStringLiteral("length")));
213             oldProperties.insert(QStringLiteral("kdenlive:clipname"), clip->name());
214             oldProperties.insert(QStringLiteral("ttl"), clip->getProducerProperty(QStringLiteral("ttl")));
215             oldProperties.insert(QStringLiteral("loop"), clip->getProducerProperty(QStringLiteral("loop")));
216             oldProperties.insert(QStringLiteral("crop"), clip->getProducerProperty(QStringLiteral("crop")));
217             oldProperties.insert(QStringLiteral("fade"), clip->getProducerProperty(QStringLiteral("fade")));
218             oldProperties.insert(QStringLiteral("luma_duration"), clip->getProducerProperty(QStringLiteral("luma_duration")));
219             oldProperties.insert(QStringLiteral("luma_file"), clip->getProducerProperty(QStringLiteral("luma_file")));
220             oldProperties.insert(QStringLiteral("softness"), clip->getProducerProperty(QStringLiteral("softness")));
221             oldProperties.insert(QStringLiteral("animation"), clip->getProducerProperty(QStringLiteral("animation")));
222             bin->slotEditClipCommand(clip->AbstractProjectItem::clipId(), oldProperties, properties);
223         } else {
224             Xml::addXmlProperties(prod, properties);
225             QString clipId = QString::number(id);
226             pCore->projectItemModel()->requestAddBinClip(clipId, xml.documentElement(), parentId, i18n("Create Title clip"));
227         }
228     }
229 }
230 
231 // static
createSlideshowClip(KdenliveDoc * doc,const QString & parentId,std::shared_ptr<ProjectItemModel> model)232 void ClipCreationDialog::createSlideshowClip(KdenliveDoc *doc, const QString &parentId, std::shared_ptr<ProjectItemModel> model)
233 {
234     QScopedPointer<SlideshowClip> dia(
235         new SlideshowClip(doc->timecode(), KRecentDirs::dir(QStringLiteral(":KdenliveSlideShowFolder")), nullptr, QApplication::activeWindow()));
236 
237     if (dia->exec() == QDialog::Accepted) {
238         // Ready, create xml
239         KRecentDirs::add(QStringLiteral(":KdenliveSlideShowFolder"), QUrl::fromLocalFile(dia->selectedPath()).adjusted(QUrl::RemoveFilename).toLocalFile());
240         std::unordered_map<QString, QString> properties;
241         properties[QStringLiteral("ttl")] = QString::number(doc->getFramePos(dia->clipDuration()));
242         properties[QStringLiteral("loop")] = QString::number(static_cast<int>(dia->loop()));
243         properties[QStringLiteral("crop")] = QString::number(static_cast<int>(dia->crop()));
244         properties[QStringLiteral("fade")] = QString::number(static_cast<int>(dia->fade()));
245         properties[QStringLiteral("luma_duration")] = QString::number(doc->getFramePos(dia->lumaDuration()));
246         properties[QStringLiteral("luma_file")] = dia->lumaFile();
247         properties[QStringLiteral("softness")] = QString::number(dia->softness());
248         properties[QStringLiteral("animation")] = dia->animation();
249 
250         int duration = doc->getFramePos(dia->clipDuration()) * dia->imageCount();
251         ClipCreator::createSlideshowClip(dia->selectedPath(), duration, dia->clipName(), parentId, properties, std::move(model));
252     }
253 }
254 
createTitleClip(KdenliveDoc * doc,const QString & parentFolder,const QString & templatePath,std::shared_ptr<ProjectItemModel> model)255 void ClipCreationDialog::createTitleClip(KdenliveDoc *doc, const QString &parentFolder, const QString &templatePath, std::shared_ptr<ProjectItemModel> model)
256 {
257     // Make sure the titles folder exists
258     QDir dir(doc->projectDataFolder() + QStringLiteral("/titles"));
259     dir.mkpath(QStringLiteral("."));
260     QPointer<TitleWidget> dia_ui =
261         new TitleWidget(QUrl::fromLocalFile(templatePath), dir.absolutePath(), pCore->getMonitor(Kdenlive::ProjectMonitor), pCore->bin());
262     if (dia_ui->exec() == QDialog::Accepted) {
263         // Ready, create clip xml
264         std::unordered_map<QString, QString> properties;
265         properties[QStringLiteral("xmldata")] = dia_ui->xml().toString();
266         QString titleSuggestion = dia_ui->titleSuggest();
267 
268         ClipCreator::createTitleClip(properties, dia_ui->duration(), titleSuggestion.isEmpty() ? i18n("Title clip") : titleSuggestion, parentFolder,
269                                      std::move(model));
270     }
271     delete dia_ui;
272 }
273 
createTitleTemplateClip(KdenliveDoc * doc,const QString & parentFolder,std::shared_ptr<ProjectItemModel> model)274 void ClipCreationDialog::createTitleTemplateClip(KdenliveDoc *doc, const QString &parentFolder, std::shared_ptr<ProjectItemModel> model)
275 {
276 
277     QScopedPointer<TitleTemplateDialog> dia(new TitleTemplateDialog(doc->projectDataFolder(), QApplication::activeWindow()));
278 
279     if (dia->exec() == QDialog::Accepted) {
280         ClipCreator::createTitleTemplate(dia->selectedTemplate(), dia->selectedText(), i18n("Template title clip"), parentFolder, std::move(model));
281     }
282 }
283 
284 // void ClipCreationDialog::createClipsCommand(KdenliveDoc *doc, const QList<QUrl> &urls, const QStringList &groupInfo, Bin *bin,
285 //                                             const QMap<QString, QString> &data)
286 // {
287 //     auto *addClips = new QUndoCommand();
288 
289 // TODO: check files on removable volume
290 /*listRemovableVolumes();
291 for (const QUrl &file :  urls) {
292     if (QFile::exists(file.path())) {
293         //TODO check for duplicates
294         if (!data.contains("bypassDuplicate") && !getClipByResource(file.path()).empty()) {
295             if (KMessageBox::warningContinueCancel(QApplication::activeWindow(), i18n("Clip <b>%1</b><br />already exists in project, what do you want to
296 do?", file.path()), i18n("Clip already exists")) == KMessageBox::Cancel)
297                 continue;
298         }
299         if (isOnRemovableDevice(file) && !isOnRemovableDevice(m_doc->projectFolder())) {
300             int answer = KMessageBox::warningYesNoCancel(QApplication::activeWindow(), i18n("Clip <b>%1</b><br /> is on a removable device, will not be
301 available when device is unplugged", file.path()), i18n("File on a Removable Device"), KGuiItem(i18n("Copy file to project folder")),
302 KGuiItem(i18n("Continue")), KStandardGuiItem::cancel(), QString("copyFilesToProjectFolder"));
303 
304             if (answer == KMessageBox::Cancel) continue;
305             else if (answer == KMessageBox::Yes) {
306                 // Copy files to project folder
307                 QDir sourcesFolder(m_doc->projectFolder().toLocalFile());
308                 sourcesFolder.cd("clips");
309                 KIO::MkdirJob *mkdirJob = KIO::mkdir(QUrl::fromLocalFile(sourcesFolder.absolutePath()));
310                 KJobWidgets::setWindow(mkdirJob, QApplication::activeWindow());
311                 if (!mkdirJob->exec()) {
312                     KMessageBox::sorry(QApplication::activeWindow(), i18n("Cannot create directory %1", sourcesFolder.absolutePath()));
313                     continue;
314                 }
315                 //KIO::filesize_t m_requestedSize;
316                 KIO::CopyJob *copyjob = KIO::copy(file, QUrl::fromLocalFile(sourcesFolder.absolutePath()));
317                 //TODO: for some reason, passing metadata does not work...
318                 copyjob->addMetaData("group", data.value("group"));
319                 copyjob->addMetaData("groupId", data.value("groupId"));
320                 copyjob->addMetaData("comment", data.value("comment"));
321                 KJobWidgets::setWindow(copyjob, QApplication::activeWindow());
322                 connect(copyjob, &KIO::CopyJob::copyingDone, this, &ClipManager::slotAddCopiedClip);
323                 continue;
324             }
325         }*/
326 
327 // TODO check folders
328 /*QList< QList<QUrl> > foldersList;
329 QMimeDatabase db;
330 for (const QUrl & file :  list) {
331     // Check there is no folder here
332     QMimeType type = db.mimeTypeForUrl(file);
333     if (type.inherits("inode/directory")) {
334         // user dropped a folder, import its files
335         list.removeAll(file);
336         QDir dir(file.path());
337         QStringList result = dir.entryList(QDir::Files);
338         QList<QUrl> folderFiles;
339         folderFiles << file;
340         for (const QString & path :  result) {
341             // TODO: create folder command
342             folderFiles.append(QUrl::fromLocalFile(dir.absoluteFilePath(path)));
343         }
344         if (folderFiles.count() > 1) foldersList.append(folderFiles);
345     }
346 }*/
347 //}
348 
createClipsCommand(KdenliveDoc * doc,const QString & parentFolder,const std::shared_ptr<ProjectItemModel> & model)349 void ClipCreationDialog::createClipsCommand(KdenliveDoc *doc, const QString &parentFolder, const std::shared_ptr<ProjectItemModel> &model)
350 {
351     qDebug() << "/////////// starting to add bin clips";
352     QList<QUrl> list;
353     QString allExtensions = getExtensions().join(QLatin1Char(' '));
354     QString dialogFilter = allExtensions + QLatin1Char('|') + i18n("All Supported Files") + QStringLiteral("\n*|") + i18n("All Files");
355     QCheckBox *b = new QCheckBox(i18n("Import image sequence"));
356     b->setChecked(KdenliveSettings::autoimagesequence());
357     QCheckBox *bf = new QCheckBox(i18n("Ignore subfolder structure"));
358     bf->setChecked(KdenliveSettings::ignoresubdirstructure());
359     QFrame *f = new QFrame();
360     f->setFrameShape(QFrame::NoFrame);
361     auto *l = new QHBoxLayout;
362     l->addWidget(b);
363     l->addWidget(bf);
364     l->addStretch(5);
365     f->setLayout(l);
366     QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder"));
367     QScopedPointer<QDialog> dlg(new QDialog(static_cast<QWidget *>(doc->parent())));
368     QScopedPointer<KFileWidget> fileWidget(new KFileWidget(QUrl::fromLocalFile(clipFolder), dlg.data()));
369     auto *layout = new QVBoxLayout;
370     layout->addWidget(fileWidget.data());
371     fileWidget->setCustomWidget(f);
372     fileWidget->okButton()->show();
373     fileWidget->cancelButton()->show();
374     QObject::connect(fileWidget->okButton(), &QPushButton::clicked, fileWidget.data(), &KFileWidget::slotOk);
375     QObject::connect(fileWidget.data(), &KFileWidget::accepted, fileWidget.data(), &KFileWidget::accept);
376     QObject::connect(fileWidget.data(), &KFileWidget::accepted, dlg.data(), &QDialog::accept);
377     QObject::connect(fileWidget->cancelButton(), &QPushButton::clicked, dlg.data(), &QDialog::reject);
378     dlg->setLayout(layout);
379     fileWidget->setFilter(dialogFilter);
380     fileWidget->setMode(KFile::Files | KFile::ExistingOnly | KFile::LocalOnly | KFile::Directory);
381     KSharedConfig::Ptr conf = KSharedConfig::openConfig();
382     QWindow *handle = dlg->windowHandle();
383     if ((handle != nullptr) && conf->hasGroup("FileDialogSize")) {
384         KWindowConfig::restoreWindowSize(handle, conf->group("FileDialogSize"));
385         dlg->resize(handle->size());
386     }
387     int result = dlg->exec();
388     if (result == QDialog::Accepted) {
389         KdenliveSettings::setAutoimagesequence(b->isChecked());
390         KdenliveSettings::setIgnoresubdirstructure(bf->isChecked());
391         list = fileWidget->selectedUrls();
392         if (!list.isEmpty()) {
393             KRecentDirs::add(QStringLiteral(":KdenliveClipFolder"), list.constFirst().adjusted(QUrl::RemoveFilename).toLocalFile());
394         }
395         if (KdenliveSettings::autoimagesequence() && list.count() >= 1) {
396             // Check for image sequence
397             const QUrl &url = list.at(0);
398             QString fileName = url.fileName().section(QLatin1Char('.'), 0, -2);
399             if (!fileName.isEmpty() && fileName.at(fileName.size() - 1).isDigit()) {
400                 KFileItem item(url);
401                 if (item.mimetype().startsWith(QLatin1String("image"))) {
402                     // import as sequence if we found more than one image in the sequence
403                     QStringList patternlist;
404                     QString pattern = SlideshowClip::selectedPath(url, false, QString(), &patternlist);
405                     qCDebug(KDENLIVE_LOG) << " / // IMPORT PATTERN: " << pattern << " COUNT: " << patternlist.count();
406                     int count = patternlist.count();
407                     if (count > 1) {
408                         // get image sequence base name
409                         while (fileName.size() > 0 && fileName.at(fileName.size() - 1).isDigit()) {
410                             fileName.chop(1);
411                         }
412 
413                         QString duration = doc->timecode().reformatSeparators(KdenliveSettings::sequence_duration());
414                         std::unordered_map<QString, QString> properties;
415                         properties[QStringLiteral("ttl")] = QString::number(doc->getFramePos(duration));
416                         properties[QStringLiteral("loop")] = QString::number(0);
417                         properties[QStringLiteral("crop")] = QString::number(0);
418                         properties[QStringLiteral("fade")] = QString::number(0);
419                         properties[QStringLiteral("luma_duration")] =
420                             QString::number(doc->getFramePos(doc->timecode().getTimecodeFromFrames(int(ceil(doc->timecode().fps())))));
421                         int frame_duration = doc->getFramePos(duration) * count;
422                         ClipCreator::createSlideshowClip(pattern, frame_duration, fileName, parentFolder, properties, model);
423                         return;
424                     }
425                 }
426             }
427         }
428     }
429     qDebug() << "/////////// found list" << list;
430     KConfigGroup group = conf->group("FileDialogSize");
431     if (handle) {
432         KWindowConfig::saveWindowSize(handle, group);
433     }
434     Fun undo = []() { return true; };
435     Fun redo = []() { return true; };
436     const QString id = ClipCreator::createClipsFromList(list, true, parentFolder, model, undo, redo);
437 
438     // We reset the state of the "don't ask again" for the question about removable devices
439     KMessageBox::enableMessage(QStringLiteral("removable"));
440     if (!id.isEmpty()) {
441         pCore->pushUndo(undo, redo, i18np("Add clip", "Add clips", list.size()));
442     }
443 }
444 
clipWidget(QDockWidget * m_DockClipWidget)445 void ClipCreationDialog::clipWidget(QDockWidget* m_DockClipWidget)
446 {
447     QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder"));
448     KFileWidget* fileWidget = new KFileWidget(QUrl::fromLocalFile(clipFolder), m_DockClipWidget);
449     fileWidget->setMode(KFile::Files | KFile::ExistingOnly | KFile::LocalOnly | KFile::Directory);
450     QString allExtensions = getExtensions().join(QLatin1Char(' '));
451     QString dialogFilter = allExtensions + QLatin1Char('|') + i18n("All Supported Files") + QStringLiteral("\n*|") + i18n("All Files");
452 
453     QPushButton* importseq = new QPushButton(i18n("Import image sequence"));
454     // Make importseq checkable so that we can differentiate between a double click in filewidget and a click on the pushbutton
455     importseq->setCheckable(true);
456     QCheckBox *b = new QCheckBox(i18n("Ignore subfolder structure"));
457     b->setChecked(KdenliveSettings::ignoresubdirstructure());
458     QFrame *f = new QFrame();
459     f->setFrameShape(QFrame::NoFrame);
460     auto *l = new QHBoxLayout;
461     l->addWidget(b);
462     l->addStretch(5);
463     l->addWidget(importseq);
464     f->setLayout(l);
465     fileWidget->setCustomWidget(f);
466     // Required to only add file on double click and not on single click
467     fileWidget->setOperationMode(KFileWidget::Saving);
468     QObject::connect(fileWidget, &KFileWidget::accepted, fileWidget, [fileWidget, importseq]() {
469         if (importseq->isChecked()) {
470             // We are importing an image sequence, abort
471             return;
472         }
473         fileWidget->accept();
474         QList <QUrl> urls = fileWidget->selectedUrls();
475         pCore->bin()->droppedUrls(urls);
476     });
477     fileWidget->setFilter(dialogFilter);
478     QObject::connect(b, &QCheckBox::toggled , [](bool checked) {
479         KdenliveSettings::setIgnoresubdirstructure(checked);
480     });
481     QObject::connect(importseq, &QPushButton::clicked, fileWidget, [=]{
482         fileWidget->slotOk();
483         emit fileWidget->accepted();
484         fileWidget->accept();
485         QUrl url = fileWidget->selectedUrl();
486         QStringList patternlist;
487         QString pattern = SlideshowClip::selectedPath(url, false, QString(), &patternlist);
488         int count = patternlist.size();
489 
490         QString fileName = url.fileName().section(QLatin1Char('.'), 0, -2);
491         importseq->setChecked(false);
492         if (count >= 1) {
493             while (fileName.size() > 0 && fileName.at(fileName.size() - 1).isDigit()) {
494                   fileName.chop(1);
495             }
496             QString parentFolder = "-1";
497             KdenliveDoc *doc = pCore->currentDoc();
498             QString duration = doc->timecode().reformatSeparators(KdenliveSettings::sequence_duration());
499             std::unordered_map<QString, QString> properties;
500             properties[QStringLiteral("ttl")] = QString::number(doc->getFramePos(duration));
501             properties[QStringLiteral("loop")] = QString::number(0);
502             properties[QStringLiteral("crop")] = QString::number(0);
503             properties[QStringLiteral("fade")] = QString::number(0);
504             properties[QStringLiteral("luma_duration")] =
505                 QString::number(doc->getFramePos(doc->timecode().getTimecodeFromFrames(int(ceil(doc->timecode().fps())))));
506             int frame_duration = doc->getFramePos(duration) * count;
507             ClipCreator::createSlideshowClip(pattern, frame_duration, fileName, parentFolder, properties, pCore->projectItemModel());
508             return;
509 
510         }
511     });
512     m_DockClipWidget->setWidget(fileWidget);
513 }
514