Commit 765ee784 authored by Rafał Lalik's avatar Rafał Lalik Committed by Jean-Baptiste Mardelle
Browse files

Add patterns to the titler widget

parent 2a7d3afa
Pipeline #45809 canceled with stage
......@@ -5,5 +5,6 @@ set(kdenlive_SRCS
titler/gradientwidget.cpp
titler/graphicsscenerectmove.cpp
titler/unicodedialog.cpp
titler/patternsmodel.cpp
PARENT_SCOPE)
/*
* Kdenlive TitleClip Pattern
* Copyright (C) 2020 Rafał Lalik <rafallalik@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "patternsmodel.h"
#include "titledocument.h"
#include <QGraphicsItem>
#include <QGraphicsScene>
#include <QPainter>
PatternsModel::PatternsModel(QObject *parent) : QAbstractListModel(parent)
{
}
QVariant PatternsModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
if (role == Qt::DecorationRole)
return pixmaps[index.row()].scaled(m_tileSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
else if (role == Qt::SizeHintRole)
return m_tileSize;
else if (role == Qt::UserRole)
return patterns.value(index.row());
return QVariant();
}
int PatternsModel::rowCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : patterns.size();
}
void PatternsModel::addScene(const QString & pattern)
{
int row = patterns.size();
beginInsertRows(QModelIndex(), row, row);
patterns.append(pattern);
pixmaps.append(paintScene(pattern));
++modified_counter;
endInsertRows();
}
QPixmap PatternsModel::paintScene(const QString & pattern)
{
QDomDocument doc;
doc.setContent(pattern);
QList<QGraphicsItem *> items;
int width, height, duration, missing;
TitleDocument::loadFromXml(doc, items, width, height, nullptr, nullptr, &duration, missing);
QGraphicsScene scene(0, 0, width, height);
if (bkg) {
QGraphicsPixmapItem * bkg_frame = new QGraphicsPixmapItem();
bkg_frame->setTransform(bkg->transform());
bkg_frame->setZValue(bkg->zValue());
bkg_frame->setPixmap(bkg->pixmap());
scene.addItem(bkg_frame);
}
for (QGraphicsItem *item : qAsConst(items)) {
scene.addItem(item);
}
QPixmap pxm(width, height);
QPainter painter(&pxm);
painter.setRenderHint(QPainter::Antialiasing);
scene.render(&painter);
return pxm;
}
void PatternsModel::repaintScenes()
{
for (int i = 0; i < patterns.size(); ++i) {
pixmaps[i] = paintScene(patterns[i]);
}
emit dataChanged(index(0), index(patterns.size()-1));
}
void PatternsModel::removeScene(const QModelIndex & index)
{
beginRemoveRows(QModelIndex(), index.row(), index.row());
patterns.remove(index.row());
pixmaps.remove(index.row());
++modified_counter;
endRemoveRows();
}
void PatternsModel::removeAll()
{
beginRemoveRows(QModelIndex(), 0, patterns.size()-1);
patterns.clear();
pixmaps.clear();
++modified_counter;
endRemoveRows();
}
QByteArray PatternsModel::serialize()
{
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
for (auto p : patterns) {
stream << p;
}
modified_counter = 0;
return encodedData;
}
void PatternsModel::deserialize(const QByteArray& data)
{
removeAll();
QByteArray ba = data;
QDataStream stream(&ba, QIODevice::ReadOnly);
while (!stream.atEnd()) {
QString p;
stream >> p;
addScene(p);
}
modified_counter = 0;
}
/*
* Kdenlive TitleClip Pattern
* Copyright (C) 2020 Rafał Lalik <rafallalik@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef PATTERNSMODEL_H
#define PATTERNSMODEL_H
#include <QAbstractListModel>
#include <QSize>
class QGraphicsItem;
class QGraphicsPixmapItem;
/**
* @todo write docs
*/
class PatternsModel : public QAbstractListModel
{
public:
/**
* Default constructor
*/
explicit PatternsModel(QObject *parent = nullptr);
virtual QVariant data(const QModelIndex& index, int role) const override;
virtual int rowCount(const QModelIndex& parent) const override;
/**
* @brief Change size of the image to be displayed in the list
* @param size tile size
*/
virtual void setTileSize(const QSize & size) { m_tileSize = size; }
/**
* @brief Set background for the list tiles. Should be called with TitleWidget::m_frameImage as parameter.
* @param pxm background pixmap
*/
virtual void setBackgroundPixmap(QGraphicsPixmapItem * pxm) { bkg = pxm; }
/**
* @brief Add new xml pattern to the model
* @param pattern the xml from TitleWidget::xml() containing items.
*/
virtual void addScene(const QString & pattern);
/**
* @brief Serialzie all patterns
* @reurn byte QByteArray
*/
virtual QByteArray serialize();
/**
* @brief Deserialize byte array with patterns
* @param data data
*/
virtual void deserialize(const QByteArray & data);
/**
* @brief Return number of modification. Counter is incremented after each addScene() and removeScene() call. Is cleared after serialize() and deserialize() is called.
* @return number of modifications
*/
int getModifiedCounter() const { return modified_counter; }
/**
* @brief Repaint all scenes. Usefull when e.g. background was changed.
*/
virtual void repaintScenes();
private:
/**
* @brief paint scene for given pattern
* @param pattern xml pattern
* @return rendered pixmap
*/
virtual QPixmap paintScene(const QString & pattern);
public slots:
/**
* @brief Remove scene.
* @param index scene index
*/
virtual void removeScene(const QModelIndex & index);
/**
* @brief Remove all scenes
*/
virtual void removeAll();
private:
QVector<QString> patterns;
QVector<QPixmap> pixmaps;
QGraphicsPixmapItem * bkg{nullptr};
int modified_counter{0};
QSize m_tileSize{QSize(16,9)};
};
#endif // PATTERNSMODEL_H
......@@ -15,6 +15,12 @@
* *
***************************************************************************/
/***************************************************************************
* *
* Modifications by Rafał Lalik to implement Patterns mechanism *
* *
***************************************************************************/
#include "titledocument.h"
#include "gradientwidget.h"
......@@ -78,7 +84,7 @@ void TitleDocument::setScene(QGraphicsScene *_scene, int width, int height)
m_height = height;
}
int TitleDocument::base64ToUrl(QGraphicsItem *item, QDomElement &content, bool embed)
int TitleDocument::base64ToUrl(QGraphicsItem *item, QDomElement &content, bool embed, const QString & pojectPath)
{
if (embed) {
if (!item->data(Qt::UserRole + 1).toString().isEmpty()) {
......@@ -92,8 +98,8 @@ int TitleDocument::base64ToUrl(QGraphicsItem *item, QDomElement &content, bool e
QString base64 = item->data(Qt::UserRole + 1).toString();
if (!base64.isEmpty()) {
QString titlePath;
if (!m_projectPath.isEmpty()) {
titlePath = m_projectPath;
if (!pojectPath.isEmpty()) {
titlePath = pojectPath;
} else {
titlePath = QStringLiteral("/tmp/titles");
}
......@@ -126,12 +132,17 @@ const QString TitleDocument::extractBase64Image(const QString &titlePath, const
}
QDomDocument TitleDocument::xml(QGraphicsRectItem *startv, QGraphicsRectItem *endv, bool embed)
{
return xml(m_scene->items(), m_width, m_height, startv, endv, embed, m_projectPath);
}
QDomDocument TitleDocument::xml(const QList<QGraphicsItem *> & items, int width, int height, QGraphicsRectItem *startv, QGraphicsRectItem *endv, bool embed, const QString & projectPath)
{
QDomDocument doc;
QDomElement main = doc.createElement(QStringLiteral("kdenlivetitle"));
main.setAttribute(QStringLiteral("width"), m_width);
main.setAttribute(QStringLiteral("height"), m_height);
main.setAttribute(QStringLiteral("width"), width);
main.setAttribute(QStringLiteral("height"), height);
// Save locale. Since 20.08, we always use the C locale for serialising.
main.setAttribute(QStringLiteral("LC_NUMERIC"), "C");
......@@ -139,7 +150,7 @@ QDomDocument TitleDocument::xml(QGraphicsRectItem *startv, QGraphicsRectItem *en
QTextCursor cur;
QTextBlockFormat format;
for (QGraphicsItem *item : m_scene->items()) {
for (QGraphicsItem *item : items) {
if (!(item->flags() & QGraphicsItem::ItemIsSelectable)) {
continue;
}
......@@ -154,12 +165,12 @@ QDomDocument TitleDocument::xml(QGraphicsRectItem *startv, QGraphicsRectItem *en
case 7:
e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsPixmapItem"));
content.setAttribute(QStringLiteral("url"), item->data(Qt::UserRole).toString());
base64ToUrl(item, content, embed);
base64ToUrl(item, content, embed, projectPath);
break;
case 13:
e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsSvgItem"));
content.setAttribute(QStringLiteral("url"), item->data(Qt::UserRole).toString());
base64ToUrl(item, content, embed);
base64ToUrl(item, content, embed, projectPath);
break;
case 3:
e.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsRectItem"));
......@@ -204,7 +215,7 @@ QDomDocument TitleDocument::xml(QGraphicsRectItem *startv, QGraphicsRectItem *en
if (t->alignment() == Qt::AlignHCenter) {
// grow dimensions on both sides
double xcenter = item->pos().x() + (t->baseBoundingRect().width()) / 2;
double offset = qMin(xcenter, m_width - xcenter);
double offset = qMin(xcenter, width - xcenter);
xPosition = xcenter - offset;
content.setAttribute(QStringLiteral("box-width"), QString::number(2 * offset));
} else if (t->alignment() == Qt::AlignRight) {
......@@ -214,7 +225,7 @@ QDomDocument TitleDocument::xml(QGraphicsRectItem *startv, QGraphicsRectItem *en
content.setAttribute(QStringLiteral("box-width"), QString::number(offset));
} else {
// left align, grow on right side
double offset = m_width - item->pos().x();
double offset = width - item->pos().x();
content.setAttribute(QStringLiteral("box-width"), QString::number(offset));
}
} else {
......@@ -306,7 +317,7 @@ QDomDocument TitleDocument::xml(QGraphicsRectItem *startv, QGraphicsRectItem *en
main.appendChild(endport);
}
QDomElement backgr = doc.createElement(QStringLiteral("background"));
QColor color = getBackgroundColor();
QColor color = getBackgroundColor(items);
backgr.setAttribute(QStringLiteral("color"), colorToString(color));
main.appendChild(backgr);
......@@ -319,12 +330,20 @@ QColor TitleDocument::getBackgroundColor() const
{
QColor color(0, 0, 0, 0);
if (m_scene) {
QList<QGraphicsItem *> items = m_scene->items();
for (auto item : qAsConst(items)) {
if ((int)item->zValue() == -1100) {
color = static_cast<QGraphicsRectItem *>(item)->brush().color();
return color;
}
return getBackgroundColor(m_scene->items());
}
return color;
}
/** \brief Get the background color (incl. alpha) from list of items, if possibly
* \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */
QColor TitleDocument::getBackgroundColor(const QList<QGraphicsItem *> & items)
{
QColor color(0, 0, 0, 0);
for (auto item : qAsConst(items)) {
if ((int)item->zValue() == -1100) {
color = static_cast<QGraphicsRectItem *>(item)->brush().color();
return color;
}
}
return color;
......@@ -364,18 +383,37 @@ bool TitleDocument::saveDocument(const QUrl &url, QGraphicsRectItem *startv, QGr
int TitleDocument::loadFromXml(const QDomDocument &doc, QGraphicsRectItem *startv, QGraphicsRectItem *endv, int *duration, const QString &projectpath)
{
m_projectPath = projectpath;
m_missingElements = 0;
QList<QGraphicsItem *> items;
int width, height;
int res = loadFromXml(doc, items, width, height, startv, endv, duration, m_missingElements);
if (m_width != width || m_height != height) {
KMessageBox::information(QApplication::activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
// TODO: convert using QTransform
m_width = width;
m_height = height;
}
for (auto i : items) {
m_scene->addItem(i);
}
return res;
}
int TitleDocument::loadFromXml(const QDomDocument &doc, QList<QGraphicsItem *> & gitems, int & width, int & height, QGraphicsRectItem *startv, QGraphicsRectItem *endv, int *duration, int & missingElements)
{
for (auto * i : gitems) {
delete i;
}
gitems.clear();
missingElements = 0;
QDomNodeList titles = doc.elementsByTagName(QStringLiteral("kdenlivetitle"));
// TODO: Check if the opened title size is equal to project size, otherwise warn user and rescale
if (doc.documentElement().hasAttribute(QStringLiteral("width")) && doc.documentElement().hasAttribute(QStringLiteral("height"))) {
int doc_width = doc.documentElement().attribute(QStringLiteral("width")).toInt();
int doc_height = doc.documentElement().attribute(QStringLiteral("height")).toInt();
if (doc_width != m_width || doc_height != m_height) {
KMessageBox::information(QApplication::activeWindow(), i18n("This title clip was created with a different frame size."), i18n("Title Profile"));
// TODO: convert using QTransform
m_width = doc_width;
m_height = doc_height;
}
width = doc.documentElement().attribute(QStringLiteral("width")).toInt();
height = doc.documentElement().attribute(QStringLiteral("height")).toInt();
} else {
// Document has no size info, it is likely an old version title, so ignore viewport data
QDomNodeList viewportlist = doc.documentElement().elementsByTagName(QStringLiteral("startviewport"));
......@@ -436,7 +474,7 @@ int TitleDocument::loadFromXml(const QDomDocument &doc, QGraphicsRectItem *start
font.setLetterSpacing(QFont::AbsoluteSpacing, txtProperties.namedItem(QStringLiteral("letter-spacing")).nodeValue().toInt());
QColor col(stringToColor(txtProperties.namedItem(QStringLiteral("font-color")).nodeValue()));
MyTextItem *txt = new MyTextItem(itemNode.namedItem(QStringLiteral("content")).firstChild().nodeValue(), nullptr);
m_scene->addItem(txt);
gitems.append(txt);
txt->setFont(font);
txt->setTextInteractionFlags(Qt::NoTextInteraction);
QTextCursor cursor(txt->document());
......@@ -528,7 +566,7 @@ int TitleDocument::loadFromXml(const QDomDocument &doc, QGraphicsRectItem *start
} else {
rec->setBrush(QBrush(stringToColor(br_str)));
}
m_scene->addItem(rec);
gitems.append(rec);
gitem = rec;
} else if (itemNode.attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsPixmapItem")) {
......@@ -539,8 +577,8 @@ int TitleDocument::loadFromXml(const QDomDocument &doc, QGraphicsRectItem *start
if (base64.isEmpty()) {
pix.load(url);
if (pix.isNull()) {
pix = createInvalidPixmap(url);
m_missingElements++;
pix = createInvalidPixmap(url, height);
missingElements++;
missing = true;
}
} else {
......@@ -550,7 +588,7 @@ int TitleDocument::loadFromXml(const QDomDocument &doc, QGraphicsRectItem *start
if (missing) {
rec->setData(Qt::UserRole + 2, 1);
}
m_scene->addItem(rec);
gitems.append(rec);
rec->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
rec->setData(Qt::UserRole, url);
if (!base64.isEmpty()) {
......@@ -573,18 +611,18 @@ int TitleDocument::loadFromXml(const QDomDocument &doc, QGraphicsRectItem *start
// QRectF bounds = renderer->boundsOnElement(elem);
}
if (rec) {
m_scene->addItem(rec);
gitems.append(rec);
rec->setData(Qt::UserRole, url);
if (!base64.isEmpty()) {
rec->setData(Qt::UserRole + 1, base64);
}
gitem = rec;
} else {
QPixmap pix = createInvalidPixmap(url);
m_missingElements++;
QPixmap pix = createInvalidPixmap(url, height);
missingElements++;
auto *rec2 = new MyPixmapItem(pix);
rec2->setData(Qt::UserRole + 2, 1);
m_scene->addItem(rec2);
gitems.append(rec2);
rec2->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
rec2->setData(Qt::UserRole, url);
gitem = rec2;
......@@ -632,8 +670,7 @@ int TitleDocument::loadFromXml(const QDomDocument &doc, QGraphicsRectItem *start
// qCDebug(KDENLIVE_LOG) << items.item(i).attributes().namedItem("color").nodeValue();
QColor color = QColor(stringToColor(itemNode.attributes().namedItem(QStringLiteral("color")).nodeValue()));
// color.setAlpha(itemNode.attributes().namedItem("alpha").nodeValue().toInt());
QList<QGraphicsItem *> sceneItems = m_scene->items();
for (auto sceneItem : qAsConst(sceneItems)) {
for (auto sceneItem : qAsConst(gitems)) {
if ((int)sceneItem->zValue() == -1100) {
static_cast<QGraphicsRectItem *>(sceneItem)->setBrush(QBrush(color));
break;
......@@ -660,9 +697,9 @@ int TitleDocument::invalidCount() const
return m_missingElements;
}
QPixmap TitleDocument::createInvalidPixmap(const QString &url)
QPixmap TitleDocument::createInvalidPixmap(const QString &url, int height)
{
int missingHeight = m_height / 10;
int missingHeight = height / 10;
QPixmap pix(missingHeight, missingHeight);
QIcon icon = QIcon::fromTheme(QStringLiteral("messagebox_warning"));
pix.fill(QColor(255, 0, 0, 50));
......
......@@ -14,6 +14,13 @@
* (at your option) any later version. *
* *
***************************************************************************/
/***************************************************************************
* *
* Modifications by Rafał Lalik to implement Patterns mechanism *
* *
***************************************************************************/
#ifndef TITLEDOCUMENT_H
#define TITLEDOCUMENT_H
......@@ -36,11 +43,16 @@ public:
enum TitleProperties { OutlineWidth = 101, OutlineColor, LineSpacing, Gradient, RotateFactor, ZoomFactor };
void setScene(QGraphicsScene *scene, int width, int height);
bool saveDocument(const QUrl &url, QGraphicsRectItem *startv, QGraphicsRectItem *endv, int duration, bool embed_images = false);
/** @brief Save XML for this title. It calls static version fo the function.
*/
QDomDocument xml(QGraphicsRectItem *startv, QGraphicsRectItem *endv, bool embed_images = false);
/** @brief Load XML for this title. It calls static version fo the function.
*/
int loadFromXml(const QDomDocument &doc, QGraphicsRectItem *startv, QGraphicsRectItem *endv, int *duration, const QString &projectpath = QString());
/** \brief Get the background color (incl. alpha) from the document, if possibly
* \returns The background color of the document, inclusive alpha. If none found, returns (0,0,0,0) */
QColor getBackgroundColor() const;
static QColor getBackgroundColor(const QList<QGraphicsItem *> & items);
int frameWidth() const;
int frameHeight() const;
/** \brief Extract embedded images in project titles folder. */
......@@ -51,20 +63,30 @@ public:
enum ItemOrigin { OriginXLeft = 0, OriginYTop = 1 };
enum AxisPosition { AxisDefault = 0, AxisInverted = 1 };
/**
* @brief General static function to store items in xml format.
*/
static QDomDocument xml(const QList<QGraphicsItem *> & items, int width, int height, QGraphicsRectItem *startv, QGraphicsRectItem *endv, bool embed_images = false, const QString & projectPath = QString());
/**
* @brief General static function to load items into list from a xml file.
*/
static int loadFromXml(const QDomDocument &doc, QList<QGraphicsItem *> & gitems, int & width, int & height, QGraphicsRectItem *startv, QGraphicsRectItem *endv, int *duration, int & missingElements);
private:
QGraphicsScene *m_scene;
QString m_projectPath;
int m_missingElements;
int m_width;
int m_height;
QString colorToString(const QColor &);
QString rectFToString(const QRectF &);
QRectF stringToRect(const QString &);
QColor stringToColor(const QString &);
QTransform stringToTransform(const QString &);
QList<QVariant> stringToList(const QString &);
int base64ToUrl(QGraphicsItem *item, QDomElement &content, bool embed);
QPixmap createInvalidPixmap(const QString &url);
static QString colorToString(const QColor &);
static QString rectFToString(const QRectF &);
static QRectF stringToRect(const QString &);
static QColor stringToColor(const QString &);
static QTransform stringToTransform(const QString &);
static QList<QVariant> stringToList(const QString &);
static int base64ToUrl(QGraphicsItem *item, QDomElement &content, bool embed, const QString & pojectPath);
static QPixmap createInvalidPixmap(const QString &url, int height);
};
#endif
......@@ -14,6 +14,11 @@
* (at your option) any later version. *
* *
***************************************************************************/
/***************************************************************************
* *
* Modifications by Rafał Lalik to implement Patterns mechanism *
* *
***************************************************************************/
#include "titlewidget.h"
#include "core.h"
......@@ -22,6 +27,8 @@
#include "gradientwidget.h"
#include "kdenlivesettings.h"
#include "monitor/monitor.h"