Commit 4bbc5bc0 authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle
Browse files

Guides update: allow managing categories, add new guides list widget, allow filtering categories.

Document opening still needs fix for loading project guides and a few other todos
parent 23b6e572
......@@ -8,6 +8,7 @@ set(kdenlive_SRCS
bin/filewatcher.cpp
bin/generators/generators.cpp
bin/model/markerlistmodel.cpp
bin/model/markersortmodel.cpp
bin/model/subtitlemodel.cpp
bin/projectclip.cpp
bin/projectfolder.cpp
......
......@@ -9,6 +9,7 @@
#include "core.h"
#include "dialogs/exportguidesdialog.h"
#include "dialogs/markerdialog.h"
#include "dialogs/multiplemarkerdialog.h"
#include "doc/docundostack.hpp"
#include "kdenlivesettings.h"
#include "macros.hpp"
......@@ -21,10 +22,6 @@
#include <QJsonObject>
#include <utility>
std::array<QColor, 9> MarkerListModel::markerTypes{{QColor(QLatin1String("#9b59b6")), QColor(QLatin1String("#3daee9")), QColor(QLatin1String("#1abc9c")),
QColor(QLatin1String("#1cdc9a")), QColor(QLatin1String("#c9ce3b")), QColor(QLatin1String("#fdbc4b")),
QColor(QLatin1String("#f39c1f")), QColor(QLatin1String("#f47750")), QColor(QLatin1String("#da4453"))}};
MarkerListModel::MarkerListModel(QString clipId, std::weak_ptr<DocUndoStack> undo_stack, QObject *parent)
: QAbstractListModel(parent)
, m_undoStack(std::move(undo_stack))
......@@ -57,6 +54,24 @@ void MarkerListModel::setup()
connect(this, &MarkerListModel::dataChanged, this, &MarkerListModel::modelChanged);
}
void MarkerListModel::loadCategories(const QStringList &categories)
{
pCore->markerTypes.clear();
for (const QString &cat : categories) {
if (cat.count(QLatin1Char(':')) < 2) {
// Invalid guide data found
qDebug() << "Invalid guide data found: " << cat;
continue;
}
const QColor color(cat.section(QLatin1Char(':'), -1));
const QString name = cat.section(QLatin1Char(':'), 0, -3);
int ix = cat.section(QLatin1Char(':'), -2, -2).toInt();
pCore->markerTypes.insert(ix, {color, name});
}
emit categoriesChanged();
// TODO: delete markers if their category was deleted
}
int MarkerListModel::markerIdAtFrame(int pos) const
{
if (m_markerPositions.contains(pos)) {
......@@ -101,7 +116,7 @@ bool MarkerListModel::addMarker(GenTime pos, const QString &comment, int type, F
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
if (type == -1) type = KdenliveSettings::default_marker_type();
Q_ASSERT(type >= 0 && type < int(markerTypes.size()));
Q_ASSERT(pCore->markerTypes.contains(type));
if (hasMarker(pos)) {
// In this case we simply change the comment and type
......@@ -207,7 +222,10 @@ bool MarkerListModel::editMarker(GenTime oldPos, GenTime pos, QString comment, i
if (oldPos == pos && current.comment() == comment && current.markerType() == type) return true;
Fun undo = []() { return true; };
Fun redo = []() { return true; };
bool res = removeMarker(oldPos, undo, redo);
bool res = true;
if (oldPos != pos) {
res = removeMarker(oldPos, undo, redo);
}
if (res) {
res = addMarker(pos, comment, type, undo, redo);
}
......@@ -443,11 +461,13 @@ QVariant MarkerListModel::data(const QModelIndex &index, int role) const
return it->second.time().frames(pCore->getCurrentFps());
case ColorRole:
case Qt::DecorationRole:
return markerTypes[size_t(it->second.markerType())];
return pCore->markerTypes.value(it->second.markerType()).first;
case TypeRole:
return it->second.markerType();
case IdRole:
return it->first;
case TCRole:
return pCore->timecode().getDisplayTimecode(it->second.time(), false);
}
return QVariant();
}
......@@ -568,6 +588,31 @@ void MarkerListModel::registerSnapModel(const std::weak_ptr<SnapInterface> &snap
}
}
void MarkerListModel::unregisterSnapModel(const std::weak_ptr<SnapInterface> &snapModel)
{
READ_LOCK();
// make sure ptr is valid
if (auto ptr = snapModel.lock()) {
// we now remove the already existing markers to the snap
QMap<int, int>::const_iterator i = m_markerPositions.constBegin();
while (i != m_markerPositions.constEnd()) {
ptr->removePoint(i.key());
++i;
}
auto copySnaps = m_registeredSnaps;
m_registeredSnaps.clear();
for (auto &c : copySnaps) {
if (c.lock() != ptr) {
m_registeredSnaps.push_back(c);
}
}
} else {
qDebug() << "Error: added snapmodel is null";
Q_ASSERT(false);
}
}
bool MarkerListModel::importFromJson(const QString &data, bool ignoreConflicts, bool pushUndo)
{
Fun undo = []() { return true; };
......@@ -601,7 +646,7 @@ bool MarkerListModel::importFromJson(const QString &data, bool ignoreConflicts,
int pos = entryObj[QLatin1String("pos")].toInt();
QString comment = entryObj[QLatin1String("comment")].toString(i18n("Marker"));
int type = entryObj[QLatin1String("type")].toInt(0);
if (type < 0 || type >= int(markerTypes.size())) {
if (!pCore->markerTypes.contains(type)) {
qDebug() << "Warning : invalid type found:" << type << " Defaulting to 0";
type = 0;
}
......@@ -684,6 +729,37 @@ bool MarkerListModel::editMarkerGui(const GenTime &pos, QWidget *parent, bool cr
return false;
}
bool MarkerListModel::addMultipleMarkersGui(const GenTime &pos, QWidget *parent, bool createIfNotFound, ClipController *clip)
{
bool exists;
auto marker = getMarker(pos, &exists);
if (!exists && !createIfNotFound) {
pCore->displayMessage(i18n("No guide found at current position"), InformationMessage);
}
if (!exists && createIfNotFound) {
marker = CommentedTime(pos, clip == nullptr ? i18n("guide") : QString(), KdenliveSettings::default_marker_type());
}
QScopedPointer<MultipleMarkerDialog> dialog(new MultipleMarkerDialog(clip, marker, m_guide ? i18n("Add Guides") : i18n("Add Markers"), parent));
if (dialog->exec() == QDialog::Accepted) {
int max = dialog->getOccurrences();
GenTime interval = dialog->getInterval();
KdenliveSettings::setMultipleguidesinterval(interval.seconds());
marker = dialog->startMarker();
GenTime startTime = marker.time();
QWriteLocker locker(&m_lock);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
for (int i = 0; i < max; i++) {
addMarker(startTime, marker.comment(), marker.markerType(), undo, redo);
startTime += interval;
}
PUSH_UNDO(undo, redo, m_guide ? i18n("Add guides") : i18n("Add markers"));
}
return false;
}
void MarkerListModel::exportGuidesGui(QWidget *parent, GenTime projectDuration) const
{
QScopedPointer<ExportGuidesDialog> dialog(new ExportGuidesDialog(this, projectDuration, parent));
......
......@@ -38,7 +38,7 @@ public:
/** @brief Construct a guide list (bound to the timeline) */
MarkerListModel(std::weak_ptr<DocUndoStack> undo_stack, QObject *parent = nullptr);
enum { CommentRole = Qt::UserRole + 1, PosRole, FrameRole, ColorRole, TypeRole, IdRole };
enum { CommentRole = Qt::UserRole + 1, PosRole, FrameRole, ColorRole, TypeRole, IdRole, TCRole };
/** @brief Adds a marker at the given position. If there is already one, the comment will be overridden
@param pos defines the position of the marker, relative to the clip
......@@ -83,9 +83,6 @@ public:
bool moveMarker(int mid, GenTime pos);
void moveMarkersWithoutUndo(const QVector<int> &markersId, int offset, bool updateView = true);
/** @brief This describes the available markers type and their corresponding colors */
static std::array<QColor, 9> markerTypes;
/** @brief Returns a marker data at given pos */
CommentedTime getMarker(const GenTime &pos, bool *ok) const;
CommentedTime getMarker(int frame, bool *ok) const;
......@@ -125,6 +122,7 @@ public:
Note that no deregistration is necessary, the weak_ptr will be discarded as soon as it becomes invalid.
*/
void registerSnapModel(const std::weak_ptr<SnapInterface> &snapModel);
void unregisterSnapModel(const std::weak_ptr<SnapInterface> &snapModel);
/** @brief Exports the model to json using format above */
QString toJson() const;
......@@ -137,7 +135,17 @@ public:
@return true if dialog was accepted and modification successful
*/
bool editMarkerGui(const GenTime &pos, QWidget *parent, bool createIfNotFound, ClipController *clip = nullptr, bool createOnly = false);
/** @brief Shows a dialog to add multiple markers/guide
@param pos: position of the marker to edit, or new position for a marker
@param widget: qt widget that will be the parent of the dialog
@param createIfNotFound: if true, we create a marker if none is found at pos
@param clip: pointer to the clip if we are editing a marker
@return true if dialog was accepted and modification successful
*/
bool addMultipleMarkersGui(const GenTime &pos, QWidget *parent, bool createIfNotFound, ClipController *clip = nullptr);
void exportGuidesGui(QWidget *parent, GenTime projectDuration) const;
/** @brief Load the marker categories from a stringList */
void loadCategories(const QStringList &categories);
// Mandatory overloads
QVariant data(const QModelIndex &index, int role) const override;
......@@ -193,6 +201,7 @@ private:
std::map<int, CommentedTime> m_markerList;
/** @brief A list of {marker frame,marker id}, useful to quickly find a marker */
QMap<int, int> m_markerPositions;
std::vector<std::weak_ptr<SnapInterface>> m_registeredSnaps;
int getRowfromId(int mid) const;
int getIdFromPos(const GenTime &pos) const;
......@@ -200,5 +209,6 @@ private:
signals:
void modelChanged();
void categoriesChanged();
};
Q_DECLARE_METATYPE(MarkerListModel *)
/*
SPDX-FileCopyrightText: 2014 Jean-Baptiste Mardelle <jb@kdenlive.org>
This file is part of Kdenlive. See www.kdenlive.org.
SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "markersortmodel.h"
#include "markerlistmodel.hpp"
MarkerSortModel::MarkerSortModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
setDynamicSortFilter(true);
}
// Responsible for item sorting!
bool MarkerSortModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
if (filterAcceptsRowItself(sourceRow, sourceParent)) {
return true;
}
return false;
}
bool MarkerSortModel::filterAcceptsRowItself(int sourceRow, const QModelIndex &sourceParent) const
{
if (m_filterList.isEmpty()) {
return true;
}
QModelIndex row = sourceModel()->index(sourceRow, 0, sourceParent);
if (m_filterList.contains(sourceModel()->data(row, MarkerListModel::TypeRole).toInt())) {
return true;
} else {
m_ignoredPositions << sourceModel()->data(row, MarkerListModel::FrameRole).toInt();
}
return false;
}
void MarkerSortModel::slotSetFilters(const QList<int> filters)
{
m_filterList = filters;
m_ignoredPositions.clear();
invalidateFilter();
}
void MarkerSortModel::slotClearSearchFilters()
{
m_filterList.clear();
m_ignoredPositions.clear();
invalidateFilter();
}
std::vector<int> MarkerSortModel::getIgnoredSnapPoints() const
{
std::vector<int> markers(m_ignoredPositions.cbegin(), m_ignoredPositions.cend());
return markers;
}
/*
SPDX-FileCopyrightText: 2014 Jean-Baptiste Mardelle <jb@kdenlive.org>
This file is part of Kdenlive. See www.kdenlive.org.
SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#pragma once
#include <QCollator>
#include <QSortFilterProxyModel>
/**
* @class MarkerSortModel
* @brief Acts as an filtering proxy for the Bin Views, used when triggering the lineedit filter.
*/
class MarkerSortModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit MarkerSortModel(QObject *parent = nullptr);
public slots:
/** @brief Set search tag that will filter the view */
void slotSetFilters(const QList<int> filter);
/** @brief Reset search filters */
void slotClearSearchFilters();
std::vector<int> getIgnoredSnapPoints() const;
protected:
/** @brief Decide which items should be displayed depending on the search string */
// cppcheck-suppress unusedFunction
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
bool filterAcceptsRowItself(int source_row, const QModelIndex &source_parent) const;
private:
QList<int> m_filterList;
mutable QList<int> m_ignoredPositions;
};
......@@ -23,6 +23,7 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "monitor/monitormanager.h"
#include "profiles/profilemodel.hpp"
#include "profiles/profilerepository.hpp"
#include "project/dialogs/guideslist.h"
#include "project/projectmanager.h"
#include "timeline2/model/timelineitemmodel.hpp"
#include "timeline2/view/timelinecontroller.h"
......@@ -152,6 +153,7 @@ void Core::initGUI(bool inSandbox, const QString &MltPath, const QUrl &Url, cons
m_subtitleWidget = new SubtitleEdit(m_mainWindow);
m_textEditWidget = new TextBasedEdit(m_mainWindow);
m_timeRemapWidget = new TimeRemap(m_mainWindow);
m_guidesList = new GuidesList(m_mainWindow);
connect(m_library, SIGNAL(addProjectClips(QList<QUrl>)), m_mainWindow->getBin(), SLOT(droppedUrls(QList<QUrl>)));
connect(this, &Core::updateLibraryPath, m_library, &LibraryWidget::slotUpdateLibraryPath);
m_monitorManager = new MonitorManager(this);
......@@ -388,6 +390,11 @@ LibraryWidget *Core::library()
return m_library;
}
GuidesList *Core::guidesList()
{
return m_guidesList;
}
TextBasedEdit *Core::textEditWidget()
{
return m_textEditWidget;
......
......@@ -41,6 +41,7 @@ class ProjectManager;
class SubtitleEdit;
class SubtitleModel;
class TextBasedEdit;
class GuidesList;
class TimeRemap;
namespace Mlt {
......@@ -124,6 +125,8 @@ public:
SubtitleEdit *subtitleWidget();
/** @brief Returns a pointer to the text based editing widget. */
TextBasedEdit *textEditWidget();
/** @brief Returns a pointer to the guides list widget. */
GuidesList *guidesList();
/** @brief Returns a pointer to the time remapping widget. */
TimeRemap *timeRemapWidget();
/** @brief Returns true if clip displayed in remap widget is the bin clip with id clipId. */
......@@ -288,6 +291,8 @@ public:
QString packageType() { return m_packageType; };
/** @brief Start / stop audio capture */
void switchCapture();
/** @brief A list of markers type categories {marker type, {color, category name}} */
QMap<int, std::pair<QColor, QString>> markerTypes;
private:
explicit Core(const QString &packageType);
......@@ -303,6 +308,7 @@ private:
LibraryWidget *m_library{nullptr};
SubtitleEdit *m_subtitleWidget{nullptr};
TextBasedEdit *m_textEditWidget{nullptr};
GuidesList *m_guidesList{nullptr};
TimeRemap *m_timeRemapWidget{nullptr};
MixerManager *m_mixerWidget{nullptr};
......@@ -396,4 +402,6 @@ signals:
void recordAudio(int tid, bool record);
/** @brief Inform widgets that the project profile (and possibly fps) changed */
void updateProjectTimecode();
/** @brief Visible guide categories changed, reload snaps in timeline */
void refreshActiveGuides();
};
......@@ -4,6 +4,7 @@ set(kdenlive_SRCS
dialogs/encodingprofilesdialog.cpp
dialogs/kdenlivesettingsdialog.cpp
dialogs/markerdialog.cpp
dialogs/multiplemarkerdialog.cpp
dialogs/exportguidesdialog.cpp
dialogs/profilesdialog.cpp
dialogs/proxytest.cpp
......
......@@ -16,6 +16,7 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "profiles/profilemodel.hpp"
#include "profiles/profilerepository.hpp"
#include "profilesdialog.h"
#include "project/dialogs/guidecategories.h"
#include "project/dialogs/profilewidget.h"
#include "timeline2/view/timelinecontroller.h"
#include "timeline2/view/timelinewidget.h"
......@@ -116,7 +117,10 @@ KdenliveSettingsDialog::KdenliveSettingsDialog(QMap<QString, QString> mappable_a
QWidget *p10 = new QWidget;
m_configColors.setupUi(p10);
m_page10 = addPage(p10, i18n("Colors"), QStringLiteral("color-management"));
m_page10 = addPage(p10, i18n("Colors and Guides"), QStringLiteral("color-management"));
m_guidesCategories = new GuideCategories(nullptr, this);
QVBoxLayout *guidesLayout = new QVBoxLayout(m_configColors.guides_box);
guidesLayout->addWidget(m_guidesCategories);
QWidget *p11 = new QWidget;
m_configSpeech.setupUi(p11);
......@@ -1004,6 +1008,12 @@ void KdenliveSettingsDialog::updateSettings()
bool fullReset = false;
bool updateLibrary = false;
// Guide categories
const QStringList updatedGuides = m_guidesCategories->updatedGuides();
if (KdenliveSettings::guidesCategories() != updatedGuides) {
KdenliveSettings::setGuidesCategories(updatedGuides);
}
/*if (m_configShuttle.shuttledevicelist->count() > 0) {
QString device = m_configShuttle.shuttledevicelist->itemData(m_configShuttle.shuttledevicelist->currentIndex()).toString();
if (device != KdenliveSettings::shuttledevice()) KdenliveSettings::setShuttledevice(device);
......
......@@ -27,6 +27,7 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "encodingprofilesdialog.h"
class ProfileWidget;
class GuideCategories;
class KJob;
class SpeechList : public QListWidget
......@@ -123,6 +124,7 @@ private:
Ui::ConfigProxy_UI m_configProxy;
Ui::ConfigSpeech_UI m_configSpeech;
SpeechList *m_speechListWidget;
GuideCategories *m_guidesCategories;
ProfileWidget *m_pw;
KProcess m_readProcess;
QAction *m_voskAction;
......
/*
SPDX-FileCopyrightText: 2008 Jean-Baptiste Mardelle <jb@kdenlive.org>
SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "multiplemarkerdialog.h"
#include "bin/model/markerlistmodel.hpp"
#include "core.h"
#include "doc/kthumb.h"
#include "kdenlivesettings.h"
#include "mltcontroller/clipcontroller.h"
#include "project/projectmanager.h"
#include "kdenlive_debug.h"
#include <QFontDatabase>
#include <QTimer>
#include <QWheelEvent>
#include "klocalizedstring.h"
MultipleMarkerDialog::MultipleMarkerDialog(ClipController *clip, const CommentedTime &t, const QString &caption, QWidget *parent)
: QDialog(parent)
, m_clip(clip)
{
setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
setupUi(this);
setWindowTitle(caption);
// Set up categories
marker_category->setCurrentCategory(t.markerType());
m_in->setValue(t.time());
if (m_clip != nullptr) {
m_in->setRange(0, m_clip->getFramePlaytime());
}
interval->setValue(GenTime(KdenliveSettings::multipleguidesinterval()));
marker_comment->setText(t.comment());
marker_comment->selectAll();
marker_comment->setFocus();
adjustSize();
}
MultipleMarkerDialog::~MultipleMarkerDialog() {}
CommentedTime MultipleMarkerDialog::startMarker()
{
KdenliveSettings::setDefault_marker_type(marker_category->currentCategory());
return CommentedTime(m_in->gentime(), marker_comment->text(), marker_category->currentCategory());
}
GenTime MultipleMarkerDialog::getInterval() const
{
return interval->gentime();
}
int MultipleMarkerDialog::getOccurrences() const
{
return occurrences->value();
}
/*
SPDX-FileCopyrightText: 2022 Jean-Baptiste Mardelle <jb@kdenlive.org>
SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#pragma once
#include "ui_multiplemarkerdialog_ui.h"
#include "definitions.h"
#include "utils/timecode.h"
#include "widgets/timecodedisplay.h"
class ClipController;
namespace Mlt {
}
/**
* @class MultipleMarkerDialog
* @brief A dialog for editing markers and guides.
* @author Jean-Baptiste Mardelle
*/
class MultipleMarkerDialog : public QDialog, public Ui::MultipleMarkerDialog_UI
{
Q_OBJECT
public:
explicit MultipleMarkerDialog(ClipController *clip, const CommentedTime &t, const QString &caption, QWidget *parent = nullptr);
~MultipleMarkerDialog() override;
CommentedTime startMarker();
GenTime getInterval() const;
int getOccurrences() const;
private:
ClipController *m_clip;
};
......@@ -10,6 +10,7 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "bin/binplaylist.hpp"
#include "bin/clipcreator.hpp"
#include "bin/model/markerlistmodel.hpp"
#include "bin/model/markersortmodel.h"
#include "bin/model/subtitlemodel.hpp"
#include "bin/projectclip.h"
#include "bin/projectitemmodel.h"
......@@ -78,6 +79,11 @@ KdenliveDoc::KdenliveDoc(QString projectFolder, QUndoGroup *undoGroup, const QSt
connect(m_commandStack.get(), &DocUndoStack::invalidate, this, &KdenliveDoc::checkPreviewStack, Qt::DirectConnection);
// connect(m_commandStack, SIGNAL(cleanChanged(bool)), this, SLOT(setModified(bool)));
m_guidesFilterModel.reset(new MarkerSortModel(this));
m_guidesFilterModel->setSourceModel(m_guideModel.get());
m_guidesFilterModel->setSortRole(MarkerListModel::PosRole);
m_guidesFilterModel->sort(0, Qt::AscendingOrder);
initializeProperties();
// video tracks are after audio tracks, and the UI shows them from highest position to lowest position
m_documentProperties[QStringLiteral("videoTarget")] = QString::number(tracks.second);
......@@ -103,7 +109,7 @@ KdenliveDoc::KdenliveDoc(QString projectFolder, QUndoGroup *undoGroup, const QSt
j.next();
m_documentMetadata[j.key()] = j.value();
}
m_guideModel->loadCategories(KdenliveSettings::guidesCategories());
pCore->setCurrentProfile(profileName);
m_document = createEmptyDocument(tracks.first, tracks.second);
updateProjectProfile(false);
......@@ -132,6 +138,10 @@ KdenliveDoc::KdenliveDoc(const QUrl &url, QDomDocument& newDom, QString projectF
connect(m_commandStack.get(), &DocUndoStack::invalidate, this, &KdenliveDoc::checkPreviewStack, Qt::DirectConnection);
initializeProperties();
m_guidesFilterModel.reset(new MarkerSortModel(this));
m_guidesFilterModel->setSourceModel(m_guideModel.get());
m_guidesFilterModel->setSortRole(MarkerListModel::PosRole);
m_guidesFilterModel->sort(0, Qt::AscendingOrder);
updateClipsCount();
}
......@@ -350,6 +360,21 @@ void KdenliveDoc::initializeProperties() {
m_documentProperties[QStringLiteral("zonein")] = QLatin1Char('0');
m_documentProperties[QStringLiteral("zoneout")] = QStringLiteral("75");
m_documentProperties[QStringLiteral("seekOffset")] = QString::number(TimelineModel::seekDuration);
m_documentProperties[QStringLiteral("guidesCategories")] = KdenliveSettings::guidesCategories().join(QLatin1Char('\n'));
}
const QStringList KdenliveDoc::guidesCategories() const
{
if (!m_documentProperties.contains(QStringLiteral("guidesCategories"))) {
return KdenliveSettings::guidesCategories();
}
return m_documentProperties.value(QStringLiteral("guidesCategories")).split(QLatin1Char('\n'));
}
void KdenliveDoc::updateGuideCategories(const QStringList &categories)
{
m_guideModel->loadCategories(categories);
m_documentProperties[QStringLiteral("guidesCategories")] = categories.join(QLatin1Char('\n'));