File indexing completed on 2024-04-14 04:47:24

0001 /*
0002     SPDX-FileCopyrightText: 2008 Marco Gittler <g.marco@freenet.de>
0003     SPDX-FileCopyrightText: Rafał Lalik
0004 
0005     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 /*
0009  *                                                                         *
0010  *   Modifications by Rafał Lalik to implement Patterns mechanism          *
0011  *                                                                         *
0012  ***************************************************************************/
0013 
0014 #include "titlewidget.h"
0015 #include "bin/bin.h"
0016 #include "core.h"
0017 #include "doc/kthumb.h"
0018 #include "gradientwidget.h"
0019 #include "kdenlivesettings.h"
0020 #include "mainwindow.h"
0021 #include "monitor/monitor.h"
0022 #include "profiles/profilemodel.hpp"
0023 #include "titler/patternsmodel.h"
0024 #include "widgets/timecodedisplay.h"
0025 #include "xml/xml.hpp"
0026 
0027 #include <cmath>
0028 
0029 #include "utils/KMessageBox_KdenliveCompat.h"
0030 #include <KLocalizedString>
0031 #include <KMessageBox>
0032 #include <KMessageWidget>
0033 #include <KRecentDirs>
0034 
0035 #include "kdenlive_debug.h"
0036 #include <QButtonGroup>
0037 #include <QCryptographicHash>
0038 #include <QDomDocument>
0039 #include <QFileDialog>
0040 #include <QFontDatabase>
0041 #include <QGraphicsBlurEffect>
0042 #include <QGraphicsDropShadowEffect>
0043 #include <QGraphicsEffect>
0044 #include <QGraphicsItem>
0045 #include <QGraphicsSvgItem>
0046 #include <QImageReader>
0047 #include <QKeyEvent>
0048 #include <QMenu>
0049 #include <QSpinBox>
0050 #include <QTextBlockFormat>
0051 #include <QTextCursor>
0052 #include <QTimer>
0053 #include <QToolBar>
0054 
0055 #include <QStandardPaths>
0056 #include <iostream>
0057 #include <mlt++/MltProfile.h>
0058 #include <utility>
0059 
0060 static QList<TitleTemplate> titleTemplates;
0061 
0062 // TODO What exactly is this variable good for?
0063 int settingUp = 0;
0064 
0065 static int TITLERVERSION = 0;
0066 const int IMAGEITEM = 7;
0067 const int RECTITEM = QGraphicsRectItem::Type;
0068 const int TEXTITEM = QGraphicsTextItem::Type;
0069 const int ELLIPSEITEM = QGraphicsEllipseItem::Type;
0070 
0071 /*
0072 const int NOEFFECT = 0;
0073 const int BLUREFFECT = 1;
0074 const int SHADOWEFFECT = 2;
0075 const int TYPEWRITEREFFECT = 3;
0076 */
0077 
0078 void TitleWidget::refreshTemplateBoxContents()
0079 {
0080     templateBox->clear();
0081     templateBox->addItem(QString());
0082     for (const TitleTemplate &t : qAsConst(titleTemplates)) {
0083         templateBox->addItem(t.icon, t.name, t.file);
0084     }
0085 }
0086 
0087 TitleWidget::TitleWidget(const QUrl &url, QString projectTitlePath, Monitor *monitor, QWidget *parent)
0088     : QDialog(parent)
0089     , Ui::TitleWidget_UI()
0090     , m_startViewport(nullptr)
0091     , m_endViewport(nullptr)
0092     , m_count(0)
0093     , m_unicodeDialog(new UnicodeDialog(UnicodeDialog::InputHex))
0094     , m_missingMessage(nullptr)
0095     , m_projectTitlePath(std::move(projectTitlePath))
0096     , m_fps(pCore->getCurrentFps())
0097     , m_guides(QList<QGraphicsLineItem *>())
0098 {
0099     setupUi(this);
0100     if (TITLERVERSION == 0) {
0101         if (KdenliveSettings::titlerVersion() < 300) {
0102             // Check version of the titler module
0103             QScopedPointer<Mlt::Properties> metadata(pCore->getMltRepository()->metadata(mlt_service_producer_type, "kdenlivetitle"));
0104             KdenliveSettings::setTitlerVersion(int(ceil(100 * metadata->get_double("version"))));
0105         }
0106         TITLERVERSION = KdenliveSettings::titlerVersion();
0107     }
0108     setMinimumSize(200, 200);
0109     setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
0110     frame_properties->setEnabled(false);
0111     frame_properties->setFixedHeight(frame_toolbar->height());
0112     int size = style()->pixelMetric(QStyle::PM_SmallIconSize);
0113     QSize iconSize(size, size);
0114 
0115     rectBColor->setAlphaChannelEnabled(true);
0116     rectFColor->setAlphaChannelEnabled(true);
0117     fontColorButton->setAlphaChannelEnabled(true);
0118     textOutlineColor->setAlphaChannelEnabled(true);
0119     shadowColor->setAlphaChannelEnabled(true);
0120 
0121     auto *colorGroup = new QButtonGroup(this);
0122     colorGroup->addButton(gradient_color);
0123     colorGroup->addButton(plain_color);
0124 
0125     m_textAlignGroup = new QButtonGroup(this);
0126     m_textAlignGroup->addButton(buttonAlignLeft, 0);
0127     m_textAlignGroup->addButton(buttonAlignCenter, 1);
0128     m_textAlignGroup->addButton(buttonAlignRight, 2);
0129     QAbstractButton *selectedButton = m_textAlignGroup->button(KdenliveSettings::titlerAlign());
0130     if (selectedButton) {
0131         selectedButton->setChecked(true);
0132     }
0133 
0134     textOutline->setMinimum(0);
0135     textOutline->setMaximum(200);
0136     // textOutline->setDecimals(0);
0137     textOutline->setValue(0);
0138     textOutline->setToolTip(i18n("Outline width"));
0139 
0140     backgroundAlpha->setMinimum(0);
0141     backgroundAlpha->setMaximum(255);
0142     bgAlphaSlider->setMinimum(0);
0143     bgAlphaSlider->setMaximum(255);
0144     backgroundAlpha->setValue(0);
0145     backgroundAlpha->setToolTip(i18n("Background color opacity"));
0146 
0147     itemrotatex->setMinimum(-360);
0148     itemrotatex->setMaximum(360);
0149     // itemrotatex->setDecimals(0);
0150     itemrotatex->setValue(0);
0151     itemrotatex->setToolTip(i18n("Rotation around the X axis"));
0152 
0153     itemrotatey->setMinimum(-360);
0154     itemrotatey->setMaximum(360);
0155     // itemrotatey->setDecimals(0);
0156     itemrotatey->setValue(0);
0157     itemrotatey->setToolTip(i18n("Rotation around the Y axis"));
0158 
0159     itemrotatez->setMinimum(-360);
0160     itemrotatez->setMaximum(360);
0161     // itemrotatez->setDecimals(0);
0162     itemrotatez->setValue(0);
0163     itemrotatez->setToolTip(i18n("Rotation around the Z axis"));
0164 
0165     rectLineWidth->setMinimum(0);
0166     rectLineWidth->setMaximum(500);
0167     // rectLineWidth->setDecimals(0);
0168     rectLineWidth->setValue(0);
0169     rectLineWidth->setToolTip(i18n("Border width"));
0170 
0171     itemzoom->setSuffix(i18n("%"));
0172     QSize profileSize = pCore->getCurrentFrameSize();
0173     m_frameWidth = qRound(profileSize.height() * pCore->getCurrentDar());
0174     m_frameHeight = profileSize.height();
0175     showToolbars(TITLE_SELECT);
0176 
0177     splitter->setStretchFactor(0, 20);
0178 
0179     m_duration->setValue(KdenliveSettings::title_duration());
0180 
0181     connect(backgroundColor, &KColorButton::changed, this, &TitleWidget::slotChangeBackground);
0182     connect(backgroundAlpha, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::slotChangeBackground);
0183 
0184     connect(shadowBox, &QGroupBox::toggled, this, &TitleWidget::slotUpdateShadow);
0185     connect(shadowColor, &KColorButton::changed, this, &TitleWidget::slotUpdateShadow);
0186     connect(blur_radius, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateShadow);
0187     connect(shadowX, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateShadow);
0188     connect(shadowY, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateShadow);
0189 
0190     connect(typewriterBox, &QGroupBox::toggled, this, &TitleWidget::slotUpdateTW);
0191     connect(tw_sb_step, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateTW);
0192     connect(tw_sb_sigma, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateTW);
0193     connect(tw_sb_seed, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateTW);
0194     connect(tw_rd_char, &QRadioButton::toggled, this, &TitleWidget::slotUpdateTW);
0195     connect(tw_rd_word, &QRadioButton::toggled, this, &TitleWidget::slotUpdateTW);
0196     connect(tw_rd_line, &QRadioButton::toggled, this, &TitleWidget::slotUpdateTW);
0197     connect(tw_rd_custom, &QRadioButton::toggled, this, &TitleWidget::slotUpdateTW);
0198     tw_rd_custom->setEnabled(false);
0199 
0200     connect(fontColorButton, &KColorButton::changed, this, &TitleWidget::slotUpdateText);
0201     connect(plain_color, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText);
0202     connect(gradient_color, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText);
0203     connect(gradients_combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &TitleWidget::slotUpdateText);
0204 
0205     connect(textOutlineColor, &KColorButton::changed, this, &TitleWidget::slotUpdateText);
0206     connect(font_family, &QFontComboBox::currentFontChanged, this, &TitleWidget::slotUpdateText);
0207     connect(font_size, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateText);
0208     connect(letter_spacing, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateText);
0209     connect(line_spacing, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateText);
0210     connect(textOutline, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateText);
0211     connect(font_weight_box, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &TitleWidget::slotUpdateText);
0212 
0213     connect(rectFColor, &KColorButton::changed, this, &TitleWidget::rectChanged);
0214     connect(rectBColor, &KColorButton::changed, this, &TitleWidget::rectChanged);
0215     connect(plain_rect, &QAbstractButton::clicked, this, &TitleWidget::rectChanged);
0216     connect(gradient_rect, &QAbstractButton::clicked, this, &TitleWidget::rectChanged);
0217     connect(gradients_rect_combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &TitleWidget::rectChanged);
0218     connect(rectLineWidth, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::rectChanged);
0219 
0220     connect(zValue, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::zIndexChanged);
0221     connect(itemzoom, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::itemScaled);
0222     connect(itemrotatex, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::itemRotateX);
0223     connect(itemrotatey, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::itemRotateY);
0224     connect(itemrotatez, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::itemRotateZ);
0225     connect(itemhcenter, &QAbstractButton::clicked, this, &TitleWidget::itemHCenter);
0226     connect(itemvcenter, &QAbstractButton::clicked, this, &TitleWidget::itemVCenter);
0227     connect(itemtop, &QAbstractButton::clicked, this, &TitleWidget::itemTop);
0228     connect(itembottom, &QAbstractButton::clicked, this, &TitleWidget::itemBottom);
0229     connect(itemleft, &QAbstractButton::clicked, this, &TitleWidget::itemLeft);
0230     connect(itemright, &QAbstractButton::clicked, this, &TitleWidget::itemRight);
0231 
0232     connect(origin_x_left, &QAbstractButton::clicked, this, &TitleWidget::slotOriginXClicked);
0233     connect(origin_y_top, &QAbstractButton::clicked, this, &TitleWidget::slotOriginYClicked);
0234 
0235     connect(monitor, &Monitor::frameUpdated, this, &TitleWidget::slotGotBackground);
0236     connect(this, &TitleWidget::requestBackgroundFrame, monitor, &Monitor::slotGetCurrentImage);
0237 
0238     // Position and size
0239     connect(value_w, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this](int) { slotValueChanged(ValueWidth); });
0240     connect(value_h, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this](int) { slotValueChanged(ValueHeight); });
0241     connect(value_x, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this](int) { slotValueChanged(ValueX); });
0242     connect(value_y, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this](int) { slotValueChanged(ValueY); });
0243 
0244     connect(buttonFitZoom, &QAbstractButton::clicked, this, &TitleWidget::slotAdjustZoom);
0245     connect(buttonRealSize, &QAbstractButton::clicked, this, &TitleWidget::slotZoomOneToOne);
0246     connect(buttonItalic, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText);
0247     connect(buttonUnder, &QAbstractButton::clicked, this, &TitleWidget::slotUpdateText);
0248     connect(m_textAlignGroup, QOverload<QAbstractButton *>::of(&QButtonGroup::buttonClicked), this, [this]() {
0249         KdenliveSettings::setTitlerAlign(m_textAlignGroup->checkedId());
0250         slotUpdateText();
0251     });
0252     connect(edit_gradient, &QAbstractButton::clicked, this, &TitleWidget::slotEditGradient);
0253     connect(edit_rect_gradient, &QAbstractButton::clicked, this, &TitleWidget::slotEditGradient);
0254     connect(preserveAspectRatio, static_cast<void (QCheckBox::*)(int)>(&QCheckBox::stateChanged), this, [&]() { slotValueChanged(ValueWidth); });
0255 
0256     displayBg->setChecked(KdenliveSettings::titlerShowbg());
0257     connect(displayBg, static_cast<void (QCheckBox::*)(int)>(&QCheckBox::stateChanged), this, [&](int state) {
0258         KdenliveSettings::setTitlerShowbg(state == Qt::Checked);
0259         displayBackgroundFrame();
0260     });
0261 
0262     connect(m_unicodeDialog, &UnicodeDialog::charSelected, this, &TitleWidget::slotInsertUnicodeString);
0263 
0264     // mbd
0265     connect(this, &QDialog::accepted, this, &TitleWidget::slotAccepted);
0266 
0267     font_weight_box->blockSignals(true);
0268     font_weight_box->addItem(i18nc("Font style", "Light"), QFont::Light);
0269     font_weight_box->addItem(i18nc("Font style", "Normal"), QFont::Normal);
0270     font_weight_box->addItem(i18nc("Font style", "Demi-Bold"), QFont::DemiBold);
0271     font_weight_box->addItem(i18nc("Font style", "Bold"), QFont::Bold);
0272     font_weight_box->addItem(i18nc("Font style", "Black"), QFont::Black);
0273     font_weight_box->setToolTip(i18n("Font weight"));
0274     font_weight_box->setCurrentIndex(1);
0275     font_weight_box->blockSignals(false);
0276 
0277     buttonFitZoom->setIconSize(iconSize);
0278     buttonRealSize->setIconSize(iconSize);
0279     buttonItalic->setIconSize(iconSize);
0280     buttonUnder->setIconSize(iconSize);
0281     buttonAlignCenter->setIconSize(iconSize);
0282     buttonAlignLeft->setIconSize(iconSize);
0283     buttonAlignRight->setIconSize(iconSize);
0284 
0285     m_unicodeAction = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-insert-unicode")), QString(), this);
0286     m_unicodeAction->setShortcut(Qt::SHIFT | Qt::CTRL | Qt::Key_U);
0287     m_unicodeAction->setToolTip(getTooltipWithShortcut(i18n("Insert Unicode character"), m_unicodeAction));
0288     connect(m_unicodeAction, &QAction::triggered, this, &TitleWidget::slotInsertUnicode);
0289     buttonInsertUnicode->setDefaultAction(m_unicodeAction);
0290 
0291     m_zUp = new QAction(QIcon::fromTheme(QStringLiteral("object-order-raise")), QString(), this);
0292     m_zUp->setShortcut(Qt::Key_PageUp);
0293     m_zUp->setToolTip(i18n("Raise object"));
0294     connect(m_zUp, &QAction::triggered, this, &TitleWidget::slotZIndexUp);
0295     zUp->setDefaultAction(m_zUp);
0296 
0297     m_zDown = new QAction(QIcon::fromTheme(QStringLiteral("object-order-lower")), QString(), this);
0298     m_zDown->setShortcut(Qt::Key_PageDown);
0299     m_zDown->setToolTip(i18n("Lower object"));
0300     connect(m_zDown, &QAction::triggered, this, &TitleWidget::slotZIndexDown);
0301     zDown->setDefaultAction(m_zDown);
0302 
0303     m_zTop = new QAction(QIcon::fromTheme(QStringLiteral("object-order-front")), QString(), this);
0304     // TODO mbt 1414: Shortcut should change z index only if
0305     // cursor is NOT in a text field ...
0306     // m_zTop->setShortcut(Qt::Key_Home);
0307     m_zTop->setToolTip(i18n("Raise object to top"));
0308     connect(m_zTop, &QAction::triggered, this, &TitleWidget::slotZIndexTop);
0309     zTop->setDefaultAction(m_zTop);
0310 
0311     m_zBottom = new QAction(QIcon::fromTheme(QStringLiteral("object-order-back")), QString(), this);
0312     // TODO mbt 1414
0313     // m_zBottom->setShortcut(Qt::Key_End);
0314     m_zBottom->setToolTip(i18n("Lower object to bottom"));
0315     connect(m_zBottom, &QAction::triggered, this, &TitleWidget::slotZIndexBottom);
0316     zBottom->setDefaultAction(m_zBottom);
0317 
0318     m_selectAll = new QAction(QIcon::fromTheme(QStringLiteral("edit-select-all")), QString(), this);
0319     m_selectAll->setShortcut(Qt::CTRL | Qt::Key_A);
0320     m_selectAll->setToolTip(i18n("Select All"));
0321     connect(m_selectAll, &QAction::triggered, this, &TitleWidget::slotSelectAll);
0322     buttonSelectAll->setDefaultAction(m_selectAll);
0323 
0324     m_selectText = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-select-texts")), QString(), this);
0325     m_selectText->setShortcut(Qt::CTRL | Qt::Key_T);
0326     m_selectText->setToolTip(i18n("Keep only text items selected"));
0327     connect(m_selectText, &QAction::triggered, this, &TitleWidget::slotSelectText);
0328     buttonSelectText->setDefaultAction(m_selectText);
0329     buttonSelectText->setEnabled(false);
0330 
0331     m_selectRects = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-select-rects")), QString(), this);
0332     m_selectRects->setShortcut(Qt::CTRL | Qt::Key_R);
0333     m_selectRects->setToolTip(i18n("Keep only rect items selected"));
0334     connect(m_selectRects, &QAction::triggered, this, &TitleWidget::slotSelectRects);
0335     buttonSelectRects->setDefaultAction(m_selectRects);
0336     buttonSelectRects->setEnabled(false);
0337 
0338     m_selectImages = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-select-images")), QString(), this);
0339     m_selectImages->setShortcut(Qt::CTRL | Qt::Key_I);
0340     m_selectImages->setToolTip(i18n("Keep only image items selected"));
0341     connect(m_selectImages, &QAction::triggered, this, &TitleWidget::slotSelectImages);
0342     buttonSelectImages->setDefaultAction(m_selectImages);
0343     buttonSelectImages->setEnabled(false);
0344 
0345     m_unselectAll = new QAction(QIcon::fromTheme(QStringLiteral("edit-select-none")), QString(), this);
0346     m_unselectAll->setShortcut(Qt::SHIFT | Qt::CTRL | Qt::Key_A);
0347     m_unselectAll->setToolTip(i18n("Deselect"));
0348     connect(m_unselectAll, &QAction::triggered, this, &TitleWidget::slotSelectNone);
0349     buttonUnselectAll->setDefaultAction(m_unselectAll);
0350     buttonUnselectAll->setEnabled(false);
0351 
0352     origin_x_left->setToolTip(i18n("Invert x axis and change 0 point"));
0353     origin_y_top->setToolTip(i18n("Invert y axis and change 0 point"));
0354     rectBColor->setToolTip(i18n("Select fill color"));
0355     rectFColor->setToolTip(i18n("Select border color"));
0356     zoom_slider->setToolTip(i18n("Zoom"));
0357     buttonRealSize->setToolTip(i18n("Original size (1:1)"));
0358     buttonFitZoom->setToolTip(i18n("Fit zoom"));
0359     backgroundColor->setToolTip(i18n("Select background color"));
0360     backgroundAlpha->setToolTip(i18n("Background opacity"));
0361     buttonSelectAll->setToolTip(getTooltipWithShortcut(i18n("Select all"), m_selectAll));
0362     buttonSelectText->setToolTip(getTooltipWithShortcut(i18n("Select text items in current selection"), m_selectText));
0363     buttonSelectRects->setToolTip(getTooltipWithShortcut(i18n("Select rect items in current selection"), m_selectRects));
0364     buttonSelectImages->setToolTip(getTooltipWithShortcut(i18n("Select image items in current selection"), m_selectImages));
0365     buttonUnselectAll->setToolTip(getTooltipWithShortcut(i18n("Unselect all"), m_unselectAll));
0366 
0367     itemhcenter->setIconSize(iconSize);
0368     itemvcenter->setIconSize(iconSize);
0369     itemtop->setIconSize(iconSize);
0370     itembottom->setIconSize(iconSize);
0371     itemright->setIconSize(iconSize);
0372     itemleft->setIconSize(iconSize);
0373 
0374     auto *layout = new QHBoxLayout;
0375     frame_toolbar->setLayout(layout);
0376     layout->setContentsMargins(0, 0, 0, 0);
0377     QToolBar *m_toolbar = new QToolBar(QStringLiteral("titleToolBar"), this);
0378     m_toolbar->setIconSize(iconSize);
0379 
0380     m_buttonCursor = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("transform-move")), i18n("Selection Tool"));
0381     m_buttonCursor->setCheckable(true);
0382     m_buttonCursor->setShortcut(Qt::ALT | Qt::Key_S);
0383     m_buttonCursor->setToolTip(i18n("Selection Tool") + QLatin1Char(' ') + m_buttonCursor->shortcut().toString());
0384     m_buttonCursor->setWhatsThis(xi18nc("@info:whatsthis", "When selected, a click on an asset in the timeline selects the asset (e.g. clip, composition)."));
0385     connect(m_buttonCursor, &QAction::triggered, this, &TitleWidget::slotSelectTool);
0386 
0387     m_buttonText = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("insert-text")), i18n("Add Text"));
0388     m_buttonText->setCheckable(true);
0389     m_buttonText->setShortcut(Qt::ALT | Qt::Key_T);
0390     m_buttonText->setToolTip(i18n("Add Text") + QLatin1Char(' ') + m_buttonText->shortcut().toString());
0391     connect(m_buttonText, &QAction::triggered, this, &TitleWidget::slotTextTool);
0392 
0393     m_buttonRect = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("kdenlive-insert-rect")), i18n("Add Rectangle"));
0394     m_buttonRect->setCheckable(true);
0395     m_buttonRect->setShortcut(Qt::ALT | Qt::Key_R);
0396     m_buttonRect->setToolTip(i18n("Add Rectangle") + QLatin1Char(' ') + m_buttonRect->shortcut().toString());
0397     connect(m_buttonRect, &QAction::triggered, this, &TitleWidget::slotRectTool);
0398 
0399     m_buttonEllipse = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("draw-ellipse")), i18n("Add Ellipse"));
0400     m_buttonEllipse->setCheckable(true);
0401     m_buttonEllipse->setShortcut(Qt::ALT | Qt::Key_E);
0402     m_buttonEllipse->setToolTip(i18n("Add Ellipse") + QLatin1Char(' ') + m_buttonEllipse->shortcut().toString());
0403     connect(m_buttonEllipse, &QAction::triggered, this, &TitleWidget::slotEllipseTool);
0404 
0405     m_buttonImage = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("insert-image")), i18n("Add Image"));
0406     m_buttonImage->setCheckable(false);
0407     m_buttonImage->setShortcut(Qt::ALT | Qt::Key_I);
0408     m_buttonImage->setToolTip(i18n("Add Image") + QLatin1Char(' ') + m_buttonImage->shortcut().toString());
0409     connect(m_buttonImage, &QAction::triggered, this, &TitleWidget::slotImageTool);
0410 
0411     m_toolbar->addSeparator();
0412 
0413     m_buttonLoad = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open Document"));
0414     m_buttonLoad->setCheckable(false);
0415     m_buttonLoad->setShortcut(Qt::CTRL | Qt::Key_O);
0416     m_buttonLoad->setToolTip(i18n("Open Document") + QLatin1Char(' ') + m_buttonLoad->shortcut().toString());
0417     connect(m_buttonLoad, SIGNAL(triggered()), this, SLOT(loadTitle()));
0418 
0419     m_buttonSave = m_toolbar->addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), i18n("Save As"));
0420     m_buttonSave->setCheckable(false);
0421     m_buttonSave->setShortcut(Qt::CTRL | Qt::Key_S);
0422     m_buttonSave->setToolTip(i18n("Save As") + QLatin1Char(' ') + m_buttonSave->shortcut().toString());
0423     connect(m_buttonSave, &QAction::triggered, [this]() { saveTitle(); });
0424 
0425     m_buttonDownload = new KNSWidgets::Action(i18n("Download New Title Templates..."), QStringLiteral(":data/kdenlive_titles.knsrc"), this);
0426     m_buttonDownload->setShortcut(Qt::ALT | Qt::Key_D);
0427     m_buttonDownload->setToolTip(i18n("Download New Title Templates...") + QLatin1Char(' ') + m_buttonDownload->shortcut().toString());
0428     m_toolbar->addAction(m_buttonDownload);
0429     connect(m_buttonDownload, &KNSWidgets::Action::dialogFinished, this, [&](const QList<KNSCore::Entry> &changedEntries) {
0430         if (changedEntries.count() > 0) {
0431             refreshTitleTemplates(m_projectTitlePath);
0432             refreshTemplateBoxContents();
0433         }
0434     });
0435 
0436     layout->addWidget(m_toolbar);
0437 
0438     // initialize graphic scene
0439     m_scene = new GraphicsSceneRectMove(TITLERVERSION, this);
0440     graphicsView->setScene(m_scene);
0441     graphicsView->setMouseTracking(true);
0442     graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
0443     graphicsView->setDragMode(QGraphicsView::RubberBandDrag);
0444     graphicsView->setRubberBandSelectionMode(Qt::ContainsItemBoundingRect);
0445     m_titledocument.setScene(m_scene, m_frameWidth, m_frameHeight);
0446     connect(m_scene, &QGraphicsScene::changed, this, &TitleWidget::slotChanged);
0447     connect(font_size, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), m_scene, &GraphicsSceneRectMove::slotUpdateFontSize);
0448     connect(use_grid, &QAbstractButton::toggled, m_scene, &GraphicsSceneRectMove::slotUseGrid);
0449 
0450     // Video frame rect
0451     QPen framepen;
0452     framepen.setColor(Qt::red);
0453     m_frameBorder = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight));
0454     m_frameBorder->setPen(framepen);
0455     m_frameBorder->setZValue(1000);
0456     m_frameBorder->setBrush(Qt::transparent);
0457     m_frameBorder->setData(-1, -1);
0458     graphicsView->scene()->addItem(m_frameBorder);
0459 
0460     // Guides
0461     connect(show_guides, &QCheckBox::stateChanged, this, &TitleWidget::showGuides);
0462     show_guides->setChecked(KdenliveSettings::titlerShowGuides());
0463     hguides->setValue(KdenliveSettings::titlerHGuides());
0464     vguides->setValue(KdenliveSettings::titlerVGuides());
0465     guideColor->setColor(KdenliveSettings::titleGuideColor());
0466     connect(guideColor, &KColorButton::changed, this, &TitleWidget::guideColorChanged);
0467     connect(hguides, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::updateGuides);
0468     connect(vguides, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::updateGuides);
0469     updateGuides(0);
0470 
0471     // semi transparent safe zones
0472     framepen.setColor(QColor(255, 0, 0, 100));
0473     QGraphicsRectItem *safe1 = new QGraphicsRectItem(QRectF(m_frameWidth * 0.05, m_frameHeight * 0.05, m_frameWidth * 0.9, m_frameHeight * 0.9), m_frameBorder);
0474     safe1->setBrush(Qt::transparent);
0475     safe1->setPen(framepen);
0476     safe1->setData(-1, -1);
0477     QGraphicsRectItem *safe2 = new QGraphicsRectItem(QRectF(m_frameWidth * 0.1, m_frameHeight * 0.1, m_frameWidth * 0.8, m_frameHeight * 0.8), m_frameBorder);
0478     safe2->setBrush(Qt::transparent);
0479     safe2->setPen(framepen);
0480     safe2->setData(-1, -1);
0481 
0482     m_frameBackground = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight));
0483     m_frameBackground->setZValue(-1100);
0484     m_frameBackground->setBrush(Qt::transparent);
0485     graphicsView->scene()->addItem(m_frameBackground);
0486 
0487     m_frameImage = new QGraphicsPixmapItem();
0488     QTransform qtrans;
0489     qtrans.scale(2.0, 2.0);
0490     m_frameImage->setTransform(qtrans);
0491     m_frameImage->setZValue(-1200);
0492     displayBackgroundFrame();
0493     graphicsView->scene()->addItem(m_frameImage);
0494 
0495     bgBox->setCurrentIndex(KdenliveSettings::titlerbg());
0496     connect(bgBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [&](int ix) {
0497         KdenliveSettings::setTitlerbg(ix);
0498         displayBackgroundFrame();
0499     });
0500 
0501     connect(m_scene, &QGraphicsScene::selectionChanged, this, &TitleWidget::selectionChanged);
0502     connect(m_scene, &GraphicsSceneRectMove::itemMoved, this, &TitleWidget::selectionChanged);
0503     connect(m_scene, &GraphicsSceneRectMove::sceneZoom, this, &TitleWidget::slotZoom);
0504     connect(m_scene, &GraphicsSceneRectMove::actionFinished, this, &TitleWidget::slotSelectTool);
0505     connect(m_scene, &GraphicsSceneRectMove::newRect, this, &TitleWidget::slotNewRect);
0506     connect(m_scene, &GraphicsSceneRectMove::newEllipse, this, &TitleWidget::slotNewEllipse);
0507     connect(m_scene, &GraphicsSceneRectMove::newText, this, &TitleWidget::slotNewText);
0508     connect(zoom_slider, &QAbstractSlider::valueChanged, this, &TitleWidget::slotUpdateZoom);
0509     connect(zoom_spin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TitleWidget::slotUpdateZoom);
0510 
0511     // mbd: load saved settings
0512     loadGradients();
0513     readChoices();
0514 
0515     graphicsView->show();
0516     graphicsView->setInteractive(true);
0517     // qCDebug(KDENLIVE_LOG) << "// TITLE WIDGWT: " << graphicsView->viewport()->width() << 'x' << graphicsView->viewport()->height();
0518     m_startViewport = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight));
0519     // Setting data at -1 so that the item is recognized as undeletable by graphicsscenerectmove
0520     m_startViewport->setData(-1, -1);
0521     m_endViewport = new QGraphicsRectItem(QRectF(0, 0, m_frameWidth, m_frameHeight));
0522     m_endViewport->setData(-1, -1);
0523     m_startViewport->setData(0, m_frameWidth);
0524     m_startViewport->setData(1, m_frameHeight);
0525     m_endViewport->setData(0, m_frameWidth);
0526     m_endViewport->setData(1, m_frameHeight);
0527 
0528     // scale the view so that the title widget is not too big at startup
0529     graphicsView->scale(.5, .5);
0530     if (url.isValid()) {
0531         loadTitle(url);
0532     } else {
0533         prepareTools(nullptr);
0534         slotTextTool();
0535         QTimer::singleShot(200, this, &TitleWidget::slotAdjustZoom);
0536     }
0537     initAnimation();
0538     QColor color = backgroundColor->color();
0539     m_scene->setBackgroundBrush(QBrush(color));
0540     color.setAlpha(backgroundAlpha->value());
0541     m_frameBackground->setBrush(color);
0542     connect(anim_start, &QAbstractButton::toggled, this, &TitleWidget::slotAnimStart);
0543     connect(anim_end, &QAbstractButton::toggled, this, &TitleWidget::slotAnimEnd);
0544     connect(templateBox, SIGNAL(currentIndexChanged(int)), this, SLOT(templateIndexChanged(int)));
0545 
0546     createButton->setEnabled(KdenliveSettings::producerslist().contains(QStringLiteral("kdenlivetitle")));
0547     auto *addMenu = new QMenu(this);
0548     addMenu->addAction(i18n("Save and add to project"));
0549     m_createTitleAction = new QAction(i18n("Create Title"), this);
0550     createButton->setMenu(addMenu);
0551     connect(addMenu, &QMenu::triggered, this, [this]() {
0552         const QUrl url = saveTitle();
0553         if (!url.isEmpty()) {
0554             pCore->bin()->slotAddClipToProject(url);
0555             done(QDialog::Rejected);
0556         }
0557     });
0558     createButton->setDefaultAction(m_createTitleAction);
0559     connect(m_createTitleAction, &QAction::triggered, this, [this]() { done(QDialog::Accepted); });
0560     connect(cancelButton, &QPushButton::clicked, this, [this]() { done(QDialog::Rejected); });
0561     refreshTitleTemplates(m_projectTitlePath);
0562 
0563     // patterns
0564 
0565     m_patternsModel = new PatternsModel(this);
0566     m_patternsModel->setBackgroundPixmap(m_frameImage);
0567     connect(this, &TitleWidget::updatePatternsBackgroundFrame, m_patternsModel, &PatternsModel::repaintScenes);
0568 
0569     connect(scaleSlider, &QSlider::valueChanged, this, &TitleWidget::slotPatternsTileWidth);
0570     connect(patternsList, &QListView::doubleClicked, this, &TitleWidget::slotPatternDblClicked);
0571 
0572     connect(btn_add, &QToolButton::clicked, this, &TitleWidget::slotPatternBtnAddClicked);
0573     connect(btn_remove, &QToolButton::clicked, this, &TitleWidget::slotPatternBtnRemoveClicked);
0574     connect(btn_removeAll, &QToolButton::clicked, this, [&]() {
0575         m_patternsModel->removeAll();
0576         btn_remove->setEnabled(false);
0577         btn_removeAll->setEnabled(false);
0578     });
0579 
0580     scaleSlider->setRange(6, 16);
0581     patternsList->setModel(m_patternsModel);
0582     connect(patternsList->selectionModel(), &QItemSelectionModel::currentChanged, this,
0583             [&](const QModelIndex &cur, const QModelIndex &prev) { btn_remove->setEnabled(cur != prev && cur.isValid()); });
0584 
0585     readPatterns();
0586 
0587     // templateBox->setIconSize(QSize(60,60));
0588     refreshTemplateBoxContents();
0589     m_lastDocumentHash = QCryptographicHash::hash(xml().toString().toLatin1(), QCryptographicHash::Md5).toHex();
0590 }
0591 
0592 TitleWidget::~TitleWidget()
0593 {
0594     writePatterns();
0595     delete m_patternsModel;
0596 
0597     m_scene->blockSignals(true);
0598     delete m_buttonRect;
0599     delete m_buttonEllipse;
0600     delete m_buttonText;
0601     delete m_buttonImage;
0602     delete m_buttonCursor;
0603     delete m_buttonSave;
0604     delete m_buttonLoad;
0605     delete m_unicodeAction;
0606     delete m_zUp;
0607     delete m_zDown;
0608     delete m_zTop;
0609     delete m_zBottom;
0610     delete m_selectAll;
0611     delete m_selectText;
0612     delete m_selectRects;
0613     delete m_selectImages;
0614     delete m_unselectAll;
0615 
0616     delete m_unicodeDialog;
0617     delete m_frameBorder;
0618     delete m_frameImage;
0619     delete m_startViewport;
0620     delete m_endViewport;
0621     delete m_scene;
0622 }
0623 
0624 // static
0625 QStringList TitleWidget::extractImageList(QString &xml, const QString &root)
0626 {
0627     QStringList result;
0628     if (xml.isEmpty()) {
0629         return result;
0630     }
0631     QDomDocument doc;
0632     doc.setContent(xml);
0633     QDomNodeList images = doc.elementsByTagName(QStringLiteral("content"));
0634     for (int i = 0; i < images.count(); ++i) {
0635         QDomElement image = images.at(i).toElement();
0636         if (image.hasAttribute(QStringLiteral("url"))) {
0637             QString filePath = image.attribute(QStringLiteral("url"));
0638             if (!QFileInfo(filePath).isAbsolute()) {
0639                 // Ensure we return absolute paths
0640                 filePath.prepend(root);
0641             }
0642             result.append(filePath);
0643         }
0644     }
0645     return result;
0646 }
0647 
0648 // static
0649 QPair<QStringList, QStringList> TitleWidget::extractAndFixImageList(QDomElement &e, const QString &root)
0650 {
0651     QString xml = Xml::getXmlProperty(e, QStringLiteral("xmldata"));
0652     if (xml.isEmpty()) {
0653         return {};
0654     }
0655     QStringList fontsList = extractFontList(xml);
0656     QStringList imageList;
0657     QDomDocument doc;
0658     doc.setContent(xml);
0659     bool updated = false;
0660     QDomNodeList images = doc.elementsByTagName(QStringLiteral("content"));
0661     for (int i = 0; i < images.count(); ++i) {
0662         QDomElement image = images.at(i).toElement();
0663         if (image.hasAttribute(QStringLiteral("url"))) {
0664             QString filePath = image.attribute(QStringLiteral("url"));
0665             if (!QFileInfo(filePath).isAbsolute()) {
0666                 // Ensure Title images have absolute paths, since it does not handle relative paths internally
0667                 filePath.prepend(root);
0668                 image.setAttribute(QStringLiteral("url"), filePath);
0669                 updated = true;
0670             }
0671             imageList.append(filePath);
0672         }
0673     }
0674     if (updated) {
0675         Xml::setXmlProperty(e, QStringLiteral("xmldata"), doc.toString());
0676     }
0677     return {imageList, fontsList};
0678 }
0679 
0680 // static
0681 QStringList TitleWidget::extractFontList(const QString &xml)
0682 {
0683     QStringList result;
0684     if (xml.isEmpty()) {
0685         return result;
0686     }
0687     QDomDocument doc;
0688     doc.setContent(xml);
0689     QDomNodeList elements = doc.elementsByTagName(QStringLiteral("content"));
0690     for (int i = 0; i < elements.count(); ++i) {
0691         QDomElement element = elements.at(i).toElement();
0692         if (element.hasAttribute(QStringLiteral("font"))) {
0693             result.append(element.attribute(QStringLiteral("font")));
0694         }
0695     }
0696     return result;
0697 }
0698 // static
0699 void TitleWidget::refreshTitleTemplates(const QString &projectPath)
0700 {
0701     QStringList filters = QStringList() << QStringLiteral("*.kdenlivetitle");
0702     titleTemplates.clear();
0703 
0704     // project templates
0705     QDir dir(projectPath);
0706     QStringList templateFiles = dir.entryList(filters, QDir::Files);
0707     for (const QString &fname : qAsConst(templateFiles)) {
0708         TitleTemplate t;
0709         t.name = fname;
0710         t.file = dir.absoluteFilePath(fname);
0711         t.icon = QIcon(KThumb::getImage(QUrl::fromLocalFile(t.file), 0, 60, -1));
0712         titleTemplates.append(t);
0713     }
0714 
0715     // system templates
0716     QStringList currentTitleTemplates =
0717         QStandardPaths::locateAll(QStandardPaths::AppLocalDataLocation, QStringLiteral("titles/"), QStandardPaths::LocateDirectory);
0718     currentTitleTemplates.removeDuplicates();
0719     for (const QString &folderpath : qAsConst(currentTitleTemplates)) {
0720         QDir folder(folderpath);
0721         QStringList filesnames = folder.entryList(filters, QDir::Files);
0722         for (const QString &fname : qAsConst(filesnames)) {
0723             TitleTemplate t;
0724             t.name = fname;
0725             t.file = folder.absoluteFilePath(fname);
0726             t.icon = QIcon(KThumb::getImage(QUrl::fromLocalFile(t.file), 0, 60, -1));
0727             titleTemplates.append(t);
0728         }
0729     }
0730 }
0731 
0732 void TitleWidget::templateIndexChanged(int index)
0733 {
0734     QString item = templateBox->itemData(index).toString();
0735     if (!item.isEmpty()) {
0736         if (m_lastDocumentHash != QCryptographicHash::hash(xml().toString().toLatin1(), QCryptographicHash::Md5).toHex()) {
0737             if (KMessageBox::warningContinueCancel(this, i18n("Do you really want to load a new template? Changes in this title will be lost!")) !=
0738                 KMessageBox::Continue) {
0739                 return;
0740             }
0741         }
0742         loadTitle(QUrl::fromLocalFile(item));
0743 
0744         // mbt 1607: Add property to distinguish between unchanged template titles and user titles.
0745         // Text of unchanged template titles should be selected when clicked.
0746         QList<QGraphicsItem *> list = graphicsView->scene()->items();
0747         for (QGraphicsItem *qgItem : qAsConst(list)) {
0748             if (qgItem->type() == TEXTITEM) {
0749                 auto *i = static_cast<MyTextItem *>(qgItem);
0750                 i->setProperty("isTemplate", "true");
0751                 i->setProperty("templateText", i->toHtml());
0752             }
0753         }
0754         m_lastDocumentHash = QCryptographicHash::hash(xml().toString().toLatin1(), QCryptographicHash::Md5).toHex();
0755     }
0756 }
0757 // virtual
0758 void TitleWidget::resizeEvent(QResizeEvent * /*event*/)
0759 {
0760     // slotAdjustZoom();
0761 }
0762 // virtual
0763 void TitleWidget::keyPressEvent(QKeyEvent *e)
0764 {
0765     if (e->key() != Qt::Key_Escape && e->key() != Qt::Key_Return && e->key() != Qt::Key_Enter) {
0766         QDialog::keyPressEvent(e);
0767     }
0768 }
0769 
0770 void TitleWidget::slotTextTool()
0771 {
0772     m_scene->setTool(TITLE_TEXT);
0773     showToolbars(TITLE_TEXT);
0774     checkButton(TITLE_TEXT);
0775 }
0776 
0777 void TitleWidget::slotRectTool()
0778 {
0779     m_scene->setTool(TITLE_RECTANGLE);
0780     showToolbars(TITLE_RECTANGLE);
0781     checkButton(TITLE_RECTANGLE);
0782 
0783     // Disable dragging mode, would make dragging a rect impossible otherwise ;)
0784     graphicsView->setDragMode(QGraphicsView::NoDrag);
0785 }
0786 
0787 void TitleWidget::slotEllipseTool()
0788 {
0789     m_scene->setTool(TITLE_ELLIPSE);
0790     showToolbars(TITLE_ELLIPSE);
0791     checkButton(TITLE_ELLIPSE);
0792 
0793     // Disable dragging mode, would make dragging a ellipse impossible otherwise ;)
0794     graphicsView->setDragMode(QGraphicsView::NoDrag);
0795 }
0796 
0797 void TitleWidget::slotSelectTool()
0798 {
0799     m_scene->setTool(TITLE_SELECT);
0800 
0801     // Enable rubberband selecting mode.
0802     graphicsView->setDragMode(QGraphicsView::RubberBandDrag);
0803 
0804     // Find out which toolbars need to be shown, depending on selected item
0805     TITLETOOL t = TITLE_SELECT;
0806     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
0807     if (!l.isEmpty()) {
0808         switch (l.at(0)->type()) {
0809         case TEXTITEM:
0810             t = TITLE_TEXT;
0811             break;
0812         case RECTITEM:
0813             t = TITLE_RECTANGLE;
0814             break;
0815         case ELLIPSEITEM:
0816             t = TITLE_ELLIPSE;
0817             break;
0818         case IMAGEITEM:
0819             t = TITLE_IMAGE;
0820             break;
0821         }
0822     }
0823     btn_add->setEnabled(!l.isEmpty());
0824 
0825     enableToolbars(t);
0826     if (t == TITLE_RECTANGLE && (l.at(0) == m_endViewport || l.at(0) == m_startViewport)) {
0827         // graphicsView->centerOn(l.at(0));
0828         t = TITLE_SELECT;
0829     }
0830     showToolbars(t);
0831 
0832     if (!l.isEmpty()) {
0833         updateCoordinates(l.at(0));
0834         updateDimension(l.at(0));
0835         updateRotZoom(l.at(0));
0836     }
0837 
0838     checkButton(TITLE_SELECT);
0839 }
0840 
0841 void TitleWidget::slotImageTool()
0842 {
0843     QList<QByteArray> supported = QImageReader::supportedImageFormats();
0844     QStringList mimeTypeFilters;
0845     QString allExtensions = i18n("All Images") + QStringLiteral(" (");
0846     for (const QByteArray &mimeType : qAsConst(supported)) {
0847         mimeTypeFilters.append(i18n("%1 Image", QString(mimeType)) + QStringLiteral("( *.") + QString(mimeType) + QLatin1Char(')'));
0848         allExtensions.append(QStringLiteral("*.") + mimeType + QLatin1Char(' '));
0849     }
0850     mimeTypeFilters.sort();
0851     allExtensions.append(QLatin1Char(')'));
0852     mimeTypeFilters.prepend(allExtensions);
0853     QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveImageFolder"));
0854     if (clipFolder.isEmpty()) {
0855         clipFolder = QDir::homePath();
0856     }
0857     QFileDialog dialog(this, i18n("Add Image"), clipFolder);
0858     dialog.setAcceptMode(QFileDialog::AcceptOpen);
0859     dialog.setNameFilters(mimeTypeFilters);
0860     if (dialog.exec() != QDialog::Accepted) {
0861         return;
0862     }
0863     QUrl url = QUrl::fromLocalFile(dialog.selectedFiles().at(0));
0864     if (url.isValid()) {
0865         KRecentDirs::add(QStringLiteral(":KdenliveImageFolder"), url.adjusted(QUrl::RemoveFilename).toLocalFile());
0866         if (url.toLocalFile().endsWith(QLatin1String(".svg"))) {
0867             MySvgItem *svg = new MySvgItem(url.toLocalFile());
0868             svg->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges);
0869             svg->setZValue(m_count++);
0870             svg->setData(Qt::UserRole, url.toLocalFile());
0871             m_scene->addNewItem(svg);
0872             prepareTools(svg);
0873         } else {
0874             QPixmap pix(url.toLocalFile());
0875             auto *image = new MyPixmapItem(pix);
0876             image->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
0877             image->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemSendsGeometryChanges);
0878             image->setData(Qt::UserRole, url.toLocalFile());
0879             image->setZValue(m_count++);
0880             m_scene->addNewItem(image);
0881             prepareTools(image);
0882         }
0883     }
0884     m_scene->setTool(TITLE_SELECT);
0885     showToolbars(TITLE_SELECT);
0886     checkButton(TITLE_SELECT);
0887 }
0888 
0889 void TitleWidget::showToolbars(TITLETOOL toolType)
0890 {
0891     toolbar_stack->setEnabled(toolType != TITLE_SELECT);
0892     switch (toolType) {
0893     case TITLE_IMAGE:
0894         toolbar_stack->setCurrentIndex(2);
0895         break;
0896     case TITLE_ELLIPSE:
0897     case TITLE_RECTANGLE:
0898         toolbar_stack->setCurrentIndex(1);
0899         break;
0900     case TITLE_TEXT:
0901     default:
0902         toolbar_stack->setCurrentIndex(0);
0903         break;
0904     }
0905 }
0906 
0907 void TitleWidget::enableToolbars(TITLETOOL toolType)
0908 {
0909     // TITLETOOL is defined in effectstack/graphicsscenerectmove.h
0910     bool enable = false;
0911     if (toolType == TITLE_RECTANGLE || toolType == TITLE_ELLIPSE || toolType == TITLE_IMAGE) {
0912         enable = true;
0913     }
0914     value_w->setEnabled(enable);
0915     value_h->setEnabled(enable);
0916 }
0917 
0918 void TitleWidget::checkButton(TITLETOOL toolType)
0919 {
0920     bool bSelect = false;
0921     bool bText = false;
0922     bool bRect = false;
0923     bool bEllipse = false;
0924     bool bImage = false;
0925 
0926     switch (toolType) {
0927     case TITLE_SELECT:
0928         bSelect = true;
0929         break;
0930     case TITLE_TEXT:
0931         bText = true;
0932         break;
0933     case TITLE_RECTANGLE:
0934         bRect = true;
0935         break;
0936     case TITLE_ELLIPSE:
0937         bEllipse = true;
0938         break;
0939     case TITLE_IMAGE:
0940         bImage = true;
0941         break;
0942     default:
0943         break;
0944     }
0945 
0946     m_buttonCursor->setChecked(bSelect);
0947     m_buttonText->setChecked(bText);
0948     m_buttonRect->setChecked(bRect);
0949     m_buttonEllipse->setChecked(bEllipse);
0950     m_buttonImage->setChecked(bImage);
0951 }
0952 
0953 void TitleWidget::displayBackgroundFrame()
0954 {
0955     QRectF r = m_frameBorder->sceneBoundingRect();
0956     if (!displayBg->isChecked()) {
0957         switch (KdenliveSettings::titlerbg()) {
0958         case 0: {
0959             QPixmap pattern(20, 20);
0960             pattern.fill(Qt::gray);
0961             QColor bgcolor(180, 180, 180);
0962             QPainter p(&pattern);
0963             p.fillRect(QRect(0, 0, 10, 10), bgcolor);
0964             p.fillRect(QRect(10, 10, 20, 20), bgcolor);
0965             p.end();
0966             QBrush br(pattern);
0967             QPixmap bg(int(r.width() / 2), int(r.height() / 2));
0968             QPainter p2(&bg);
0969             p2.fillRect(bg.rect(), br);
0970             p2.end();
0971             m_frameImage->setPixmap(bg);
0972             break;
0973         }
0974         default: {
0975             QColor col = KdenliveSettings::titlerbg() == 1 ? Qt::black : Qt::white;
0976             QPixmap bg(int(r.width() / 2), int(r.height() / 2));
0977             QPainter p2(&bg);
0978             p2.fillRect(bg.rect(), col);
0979             p2.end();
0980             m_frameImage->setPixmap(bg);
0981         }
0982         }
0983         Q_EMIT updatePatternsBackgroundFrame();
0984     } else {
0985         Q_EMIT requestBackgroundFrame(true);
0986     }
0987 }
0988 
0989 void TitleWidget::slotGotBackground(const QImage &img)
0990 {
0991     QRectF r = m_frameBorder->sceneBoundingRect();
0992     m_frameImage->setPixmap(QPixmap::fromImage(img.scaled(int(r.width() / 2), int(r.height() / 2))));
0993     Q_EMIT requestBackgroundFrame(false);
0994     Q_EMIT updatePatternsBackgroundFrame();
0995 }
0996 
0997 void TitleWidget::initAnimation()
0998 {
0999     align_box->setEnabled(false);
1000     QPen startpen(Qt::DotLine);
1001     QPen endpen(Qt::DashDotLine);
1002     startpen.setColor(QColor(100, 200, 100, 140));
1003     endpen.setColor(QColor(200, 100, 100, 140));
1004 
1005     m_startViewport->setPen(startpen);
1006     m_endViewport->setPen(endpen);
1007 
1008     m_startViewport->setZValue(-1000);
1009     m_endViewport->setZValue(-1000);
1010 
1011     m_startViewport->setFlag(QGraphicsItem::ItemIsMovable, false);
1012     m_startViewport->setFlag(QGraphicsItem::ItemIsSelectable, false);
1013     m_endViewport->setFlag(QGraphicsItem::ItemIsMovable, false);
1014     m_endViewport->setFlag(QGraphicsItem::ItemIsSelectable, false);
1015 
1016     graphicsView->scene()->addItem(m_startViewport);
1017     graphicsView->scene()->addItem(m_endViewport);
1018 
1019     connect(keep_aspect, &QAbstractButton::toggled, this, &TitleWidget::slotKeepAspect);
1020     connect(resize50, &QAbstractButton::clicked, this, [&]() { slotResize(50); });
1021     connect(resize100, &QAbstractButton::clicked, this, [&]() { slotResize(100); });
1022     connect(resize200, &QAbstractButton::clicked, this, [&]() { slotResize(200); });
1023 }
1024 
1025 void TitleWidget::slotUpdateZoom(int pos)
1026 {
1027     zoom_spin->setValue(pos);
1028     zoom_slider->setValue(pos);
1029     m_scene->setZoom(pos / 100.);
1030 }
1031 
1032 void TitleWidget::slotZoom(bool up)
1033 {
1034     int pos = zoom_slider->value();
1035     if (up) {
1036         pos++;
1037     } else {
1038         pos--;
1039     }
1040     zoom_slider->setValue(pos);
1041 }
1042 
1043 void TitleWidget::slotAdjustZoom()
1044 {
1045     /*double scalex = graphicsView->width() / (double)(m_frameWidth * 1.2);
1046     double scaley = graphicsView->height() / (double)(m_frameHeight * 1.2);
1047     if (scalex > scaley) scalex = scaley;
1048     int zoompos = qRound(scalex * 7);*/
1049     graphicsView->fitInView(m_frameBorder, Qt::KeepAspectRatio);
1050     int zoompos = int(graphicsView->transform().m11() * 100);
1051     zoom_slider->setValue(zoompos);
1052     graphicsView->centerOn(m_frameBorder);
1053 }
1054 
1055 void TitleWidget::slotZoomOneToOne()
1056 {
1057     zoom_slider->setValue(100);
1058     graphicsView->centerOn(m_frameBorder);
1059 }
1060 
1061 void TitleWidget::slotNewRect(QGraphicsRectItem *rect)
1062 {
1063     updateAxisButtons(rect); // back to default
1064 
1065     if (rectLineWidth->value() == 0) {
1066         rect->setPen(Qt::NoPen);
1067     } else {
1068         QPen penf(rectFColor->color());
1069         penf.setWidth(rectLineWidth->value());
1070         penf.setJoinStyle(Qt::RoundJoin);
1071         rect->setPen(penf);
1072     }
1073     if (plain_rect->isChecked()) {
1074         rect->setBrush(QBrush(rectBColor->color()));
1075         rect->setData(TitleDocument::Gradient, QVariant());
1076     } else {
1077         // gradient
1078         QString gradientData = gradients_rect_combo->currentData().toString();
1079         rect->setData(TitleDocument::Gradient, gradientData);
1080         QLinearGradient gr = GradientWidget::gradientFromString(gradientData, int(rect->boundingRect().width()), int(rect->boundingRect().height()));
1081         rect->setBrush(QBrush(gr));
1082     }
1083     rect->setZValue(m_count++);
1084     rect->setData(TitleDocument::ZoomFactor, 100);
1085     prepareTools(rect);
1086     // setCurrentItem(rect);
1087     // graphicsView->setFocus();
1088 }
1089 
1090 void TitleWidget::slotNewEllipse(QGraphicsEllipseItem *ellipse)
1091 {
1092     updateAxisButtons(ellipse); // back to default
1093 
1094     if (rectLineWidth->value() == 0) {
1095         ellipse->setPen(Qt::NoPen);
1096     } else {
1097         QPen penf(rectFColor->color());
1098         penf.setWidth(rectLineWidth->value());
1099         penf.setJoinStyle(Qt::RoundJoin);
1100         ellipse->setPen(penf);
1101     }
1102     if (plain_rect->isChecked()) {
1103         ellipse->setBrush(QBrush(rectBColor->color()));
1104         ellipse->setData(TitleDocument::Gradient, QVariant());
1105     } else {
1106         // gradient
1107         QString gradientData = gradients_rect_combo->currentData().toString();
1108         ellipse->setData(TitleDocument::Gradient, gradientData);
1109         QLinearGradient gr = GradientWidget::gradientFromString(gradientData, int(ellipse->boundingRect().width()), int(ellipse->boundingRect().height()));
1110         ellipse->setBrush(QBrush(gr));
1111     }
1112     ellipse->setZValue(m_count++);
1113     ellipse->setData(TitleDocument::ZoomFactor, 100);
1114     prepareTools(ellipse);
1115     // setCurrentItem(rect);
1116     // graphicsView->setFocus();
1117 }
1118 
1119 void TitleWidget::slotNewText(MyTextItem *tt)
1120 {
1121     updateAxisButtons(tt); // back to default
1122 
1123     letter_spacing->blockSignals(true);
1124     line_spacing->blockSignals(true);
1125     letter_spacing->setValue(0);
1126     line_spacing->setValue(0);
1127     letter_spacing->blockSignals(false);
1128     line_spacing->blockSignals(false);
1129     letter_spacing->setEnabled(true);
1130     line_spacing->setEnabled(true);
1131     QFont font = font_family->currentFont();
1132     font.setPixelSize(font_size->value());
1133     // mbd: issue 551:
1134     font.setWeight(QFont::Weight(font_weight_box->itemData(font_weight_box->currentIndex()).toInt()));
1135     font.setItalic(buttonItalic->isChecked());
1136     font.setUnderline(buttonUnder->isChecked());
1137 
1138     tt->setFont(font);
1139     QColor color = fontColorButton->color();
1140     QColor outlineColor = textOutlineColor->color();
1141     tt->setTextColor(color);
1142     tt->document()->setDocumentMargin(0);
1143 
1144     QTextCursor cur(tt->document());
1145     cur.select(QTextCursor::Document);
1146     QTextBlockFormat format = cur.blockFormat();
1147     QTextCharFormat cformat = cur.charFormat();
1148     double outlineWidth = textOutline->value();
1149 
1150     tt->setData(TitleDocument::OutlineWidth, outlineWidth);
1151     tt->setData(TitleDocument::OutlineColor, outlineColor);
1152     if (outlineWidth > 0.0) {
1153         cformat.setTextOutline(QPen(outlineColor, outlineWidth));
1154     }
1155     tt->updateShadow(shadowBox->isChecked(), blur_radius->value(), shadowX->value(), shadowY->value(), shadowColor->color());
1156     if (gradient_color->isChecked()) {
1157         QString gradientData = gradients_combo->currentData().toString();
1158         tt->setData(TitleDocument::Gradient, gradientData);
1159         QLinearGradient gr = GradientWidget::gradientFromString(gradientData, int(tt->boundingRect().width()), int(tt->boundingRect().height()));
1160         cformat.setForeground(QBrush(gr));
1161     } else {
1162         cformat.setForeground(QBrush(color));
1163     }
1164     cur.setCharFormat(cformat);
1165     cur.setBlockFormat(format);
1166     tt->setTextCursor(cur);
1167     tt->setZValue(m_count++);
1168     setCurrentItem(tt);
1169     prepareTools(tt);
1170 }
1171 
1172 void TitleWidget::setFontBoxWeight(int weight)
1173 {
1174     int index = font_weight_box->findData(weight);
1175     if (index < 0) {
1176         index = font_weight_box->findData(QFont::Normal);
1177     }
1178     font_weight_box->setCurrentIndex(index);
1179 }
1180 
1181 void TitleWidget::setCurrentItem(QGraphicsItem *item)
1182 {
1183     m_scene->setSelectedItem(item);
1184     btn_add->setEnabled(item != nullptr);
1185 }
1186 
1187 void TitleWidget::zIndexChanged(int v)
1188 {
1189     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
1190     for (auto &i : l) {
1191         i->setZValue(v);
1192     }
1193 }
1194 
1195 void TitleWidget::selectionChanged()
1196 {
1197     if (m_scene->tool() != TITLE_SELECT) {
1198         return;
1199     }
1200 
1201     // qCDebug(KDENLIVE_LOG) << "Number of selected items: " << graphicsView->scene()->selectedItems().length() << '\n';
1202 
1203     QList<QGraphicsItem *> l;
1204 
1205     // mbt 1607: One text item might have grabbed the keyboard.
1206     // Ungrab it for all items that are not selected, otherwise
1207     // text input would only work for the text item that grabbed
1208     // the keyboard last.
1209     l = graphicsView->scene()->items();
1210     for (QGraphicsItem *item : qAsConst(l)) {
1211         if (item->type() == TEXTITEM && !item->isSelected()) {
1212             auto *i = static_cast<MyTextItem *>(item);
1213             i->clearFocus();
1214         }
1215     }
1216 
1217     l = graphicsView->scene()->selectedItems();
1218 
1219     if (!l.isEmpty()) {
1220         buttonUnselectAll->setEnabled(true);
1221         // Enable all z index buttons if items selected.
1222         // We can selectively disable them later.
1223         zUp->setEnabled(true);
1224         zDown->setEnabled(true);
1225         zTop->setEnabled(true);
1226         zBottom->setEnabled(true);
1227     } else {
1228         buttonUnselectAll->setEnabled(false);
1229     }
1230     if (l.size() >= 2) {
1231         buttonSelectText->setEnabled(true);
1232         buttonSelectRects->setEnabled(true);
1233         buttonSelectImages->setEnabled(true);
1234     } else {
1235         buttonSelectText->setEnabled(false);
1236         buttonSelectRects->setEnabled(false);
1237         buttonSelectImages->setEnabled(false);
1238     }
1239 
1240     if (l.size() == 0) {
1241         prepareTools(nullptr);
1242     } else if (l.size() == 1) {
1243         prepareTools(l.at(0));
1244     } else {
1245         /*
1246         For multiple selected objects we need to decide which tools to show.
1247         */
1248         int firstType = l.at(0)->type();
1249         bool allEqual = true;
1250         for (auto i : qAsConst(l)) {
1251             if (i->type() != firstType) {
1252                 allEqual = false;
1253                 break;
1254             }
1255         }
1256         // qCDebug(KDENLIVE_LOG) << "All equal? " << allEqual << ".\n";
1257         if (allEqual) {
1258             prepareTools(l.at(0));
1259         } else {
1260             // Get the default toolset, but enable the property frame (x,y,w,h)
1261             prepareTools(nullptr);
1262             frame_properties->setEnabled(true);
1263 
1264             // Enable x/y/w/h if it makes sense.
1265             value_x->setEnabled(true);
1266             value_y->setEnabled(true);
1267             bool containsTextitem = false;
1268             for (auto i : qAsConst(l)) {
1269                 if (i->type() == TEXTITEM) {
1270                     containsTextitem = true;
1271                     break;
1272                 }
1273             }
1274             if (!containsTextitem) {
1275                 value_w->setEnabled(true);
1276                 value_h->setEnabled(true);
1277             }
1278         }
1279 
1280         // Disable z index buttons if they don't make sense for the current selection
1281         int firstZindex = int(l.at(0)->zValue());
1282         allEqual = true;
1283         for (auto &i : l) {
1284             if (int(i->zValue()) != firstZindex) {
1285                 allEqual = false;
1286                 break;
1287             }
1288         }
1289         if (!allEqual) {
1290             zUp->setEnabled(false);
1291             zDown->setEnabled(false);
1292         }
1293     }
1294 }
1295 
1296 void TitleWidget::slotValueChanged(int type)
1297 {
1298     /*
1299     type tells us which QSpinBox value has changed.
1300     */
1301 
1302     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
1303     // qCDebug(KDENLIVE_LOG) << l.size() << " items to be resized\n";
1304 
1305     // Get the updated value here already to do less coding afterwards
1306     int val = 0;
1307     switch (type) {
1308     case ValueWidth:
1309         val = value_w->value();
1310         break;
1311     case ValueHeight:
1312         val = value_h->value();
1313         break;
1314     case ValueX:
1315         val = value_x->value();
1316         break;
1317     case ValueY:
1318         val = value_y->value();
1319         break;
1320     }
1321 
1322     for (int k = 0; k < l.size(); ++k) {
1323         // qCDebug(KDENLIVE_LOG) << "Type of item " << k << ": " << l.at(k)->type() << '\n';
1324 
1325         if (l.at(k)->type() == TEXTITEM) {
1326             // Just update the position. We don't allow setting width/height for text items yet.
1327             switch (type) {
1328             case ValueX:
1329                 updatePosition(l.at(k), val, int(l.at(k)->pos().y()));
1330                 break;
1331             case ValueY:
1332                 updatePosition(l.at(k), int(l.at(k)->pos().x()), val);
1333                 break;
1334             }
1335 
1336         } else if (l.at(k)->type() == RECTITEM) {
1337             auto *rec = static_cast<QGraphicsRectItem *>(l.at(k));
1338             switch (type) {
1339             case ValueX:
1340                 updatePosition(l.at(k), val, int(l.at(k)->pos().y()));
1341                 break;
1342             case ValueY:
1343                 updatePosition(l.at(k), int(l.at(k)->pos().x()), val);
1344                 break;
1345             case ValueWidth:
1346                 rec->setRect(QRect(0, 0, val, int(rec->rect().height())));
1347                 break;
1348             case ValueHeight:
1349                 rec->setRect(QRect(0, 0, int(rec->rect().width()), val));
1350                 break;
1351             }
1352 
1353         } else if (l.at(k)->type() == ELLIPSEITEM) {
1354             auto *ellipse = static_cast<QGraphicsEllipseItem *>(l.at(k));
1355             switch (type) {
1356             case ValueX:
1357                 updatePosition(l.at(k), val, int(l.at(k)->pos().y()));
1358                 break;
1359             case ValueY:
1360                 updatePosition(l.at(k), int(l.at(k)->pos().x()), val);
1361                 break;
1362             case ValueWidth:
1363                 ellipse->setRect(QRect(0, 0, val, int(ellipse->rect().height())));
1364                 break;
1365             case ValueHeight:
1366                 ellipse->setRect(QRect(0, 0, int(ellipse->rect().width()), val));
1367                 break;
1368             }
1369 
1370         } else if (l.at(k)->type() == IMAGEITEM) {
1371 
1372             if (type == ValueX) {
1373                 updatePosition(l.at(k), val, int(l.at(k)->pos().y()));
1374 
1375             } else if (type == ValueY) {
1376                 updatePosition(l.at(k), int(l.at(k)->pos().x()), val);
1377 
1378             } else {
1379                 // Width/height has changed. This is more complex.
1380 
1381                 QGraphicsItem *i = l.at(k);
1382                 Transform t = m_transformations.value(i);
1383 
1384                 // Ratio width:height
1385                 double phi = i->boundingRect().width() / i->boundingRect().height();
1386                 // TODO: proper calculation for rotation around 3 axes
1387                 double alpha = t.rotatez / 180.0 * M_PI;
1388 
1389                 // New length
1390                 double length;
1391 
1392                 // Scaling factor
1393                 double scalex = t.scalex;
1394                 double scaley = t.scaley;
1395 
1396                 // We want to keep the aspect ratio of the image as the user does not yet have the possibility
1397                 // to restore the original ratio. You rarely want to change it anyway.
1398                 switch (type) {
1399                 case ValueWidth:
1400                     // Add 0.5 because otherwise incrementing by 1 might have no effect
1401                     length = val / (cos(alpha) + 1 / phi * sin(alpha)) + 0.5;
1402                     scalex = length / i->boundingRect().width();
1403                     if (preserveAspectRatio->isChecked()) {
1404                         scaley = scalex;
1405                     }
1406                     break;
1407                 case ValueHeight:
1408                     length = val / (phi * sin(alpha) + cos(alpha)) + 0.5;
1409                     scaley = length / i->boundingRect().height();
1410                     if (preserveAspectRatio->isChecked()) {
1411                         scalex = scaley;
1412                     }
1413                     break;
1414                 }
1415                 t.scalex = scalex;
1416                 t.scaley = scaley;
1417                 QTransform qtrans;
1418                 qtrans.scale(scalex, scaley);
1419                 qtrans.rotate(t.rotatex, Qt::XAxis);
1420                 qtrans.rotate(t.rotatey, Qt::YAxis);
1421                 qtrans.rotate(t.rotatez, Qt::ZAxis);
1422                 i->setTransform(qtrans);
1423                 // qCDebug(KDENLIVE_LOG) << "scale is: " << scale << '\n';
1424                 // qCDebug(KDENLIVE_LOG) << i->boundingRect().width() << ": new width\n";
1425                 m_transformations[i] = t;
1426 
1427                 if (l.size() == 1) {
1428                     // Only update the w/h values if the selection contains just one item.
1429                     // Otherwise, what should we do? ;)
1430                     // (Use the values of the first item? Of the second? Of the x-th?)
1431                     updateDimension(i);
1432                     // Update rotation/zoom values.
1433                     // These values are not yet able to handle multiple items!
1434                     updateRotZoom(i);
1435                 }
1436             }
1437         }
1438     }
1439 }
1440 
1441 void TitleWidget::updateDimension(QGraphicsItem *i)
1442 {
1443     bool wBlocked = value_w->signalsBlocked();
1444     bool hBlocked = value_h->signalsBlocked();
1445     bool zBlocked = zValue->signalsBlocked();
1446     value_w->blockSignals(true);
1447     value_h->blockSignals(true);
1448     zValue->blockSignals(true);
1449 
1450     zValue->setValue(int(i->zValue()));
1451     if (i->type() == IMAGEITEM) {
1452         // Get multipliers for rotation/scaling
1453 
1454         /*Transform t = m_transformations.value(i);
1455         QRectF r = i->boundingRect();
1456         int width = (int) ( abs(r.width()*t.scalex * cos(t.rotate/180.0*M_PI))
1457                     + abs(r.height()*t.scaley * sin(t.rotate/180.0*M_PI)) );
1458         int height = (int) ( abs(r.height()*t.scaley * cos(t.rotate/180*M_PI))
1459                     + abs(r.width()*t.scalex * sin(t.rotate/180*M_PI)) );*/
1460 
1461         value_w->setValue(int(i->sceneBoundingRect().width()));
1462         value_h->setValue(int(i->sceneBoundingRect().height()));
1463     } else if (i->type() == RECTITEM || i->type() == ELLIPSEITEM) {
1464         auto *r = static_cast<QGraphicsRectItem *>(i);
1465         // qCDebug(KDENLIVE_LOG) << "Rect width is: " << r->rect().width() << ", was: " << value_w->value() << '\n';
1466         value_w->setValue(int(r->rect().width()));
1467         value_h->setValue(int(r->rect().height()));
1468     } else if (i->type() == TEXTITEM) {
1469         auto *t = static_cast<MyTextItem *>(i);
1470         value_w->setValue(int(t->boundingRect().width()));
1471         value_h->setValue(int(t->boundingRect().height()));
1472     }
1473 
1474     zValue->blockSignals(zBlocked);
1475     value_w->blockSignals(wBlocked);
1476     value_h->blockSignals(hBlocked);
1477 }
1478 
1479 void TitleWidget::updateCoordinates(QGraphicsItem *i)
1480 {
1481     // Block signals emitted by this method
1482     value_x->blockSignals(true);
1483     value_y->blockSignals(true);
1484 
1485     if (i->type() == TEXTITEM) {
1486 
1487         auto *rec = static_cast<MyTextItem *>(i);
1488 
1489         // Set the correct x coordinate value
1490         if (origin_x_left->isChecked()) {
1491             // Origin (0 point) is at m_frameWidth, coordinate axis is inverted
1492             value_x->setValue(int(m_frameWidth - rec->pos().x() - rec->boundingRect().width()));
1493         } else {
1494             // Origin is at 0 (default)
1495             value_x->setValue(int(rec->pos().x()));
1496         }
1497 
1498         // Same for y
1499         if (origin_y_top->isChecked()) {
1500             value_y->setValue(int(m_frameHeight - rec->pos().y() - rec->boundingRect().height()));
1501         } else {
1502             value_y->setValue(int(rec->pos().y()));
1503         }
1504 
1505     } else if (i->type() == RECTITEM) {
1506 
1507         auto *rec = static_cast<QGraphicsRectItem *>(i);
1508 
1509         if (origin_x_left->isChecked()) {
1510             // Origin (0 point) is at m_frameWidth
1511             value_x->setValue(int(m_frameWidth - rec->pos().x() - rec->rect().width()));
1512         } else {
1513             // Origin is at 0 (default)
1514             value_x->setValue(int(rec->pos().x()));
1515         }
1516 
1517         if (origin_y_top->isChecked()) {
1518             value_y->setValue(int(m_frameHeight - rec->pos().y() - rec->rect().height()));
1519         } else {
1520             value_y->setValue(int(rec->pos().y()));
1521         }
1522 
1523     } else if (i->type() == ELLIPSEITEM) {
1524 
1525         auto *rec = static_cast<QGraphicsEllipseItem *>(i);
1526 
1527         if (origin_x_left->isChecked()) {
1528             // Origin (0 point) is at m_frameWidth
1529             value_x->setValue(int(m_frameWidth - rec->pos().x() - rec->rect().width()));
1530         } else {
1531             // Origin is at 0 (default)
1532             value_x->setValue(int(rec->pos().x()));
1533         }
1534 
1535         if (origin_y_top->isChecked()) {
1536             value_y->setValue(int(m_frameHeight - rec->pos().y() - rec->rect().height()));
1537         } else {
1538             value_y->setValue(int(rec->pos().y()));
1539         }
1540 
1541     } else if (i->type() == IMAGEITEM) {
1542 
1543         if (origin_x_left->isChecked()) {
1544             value_x->setValue(int(m_frameWidth - i->pos().x() - i->sceneBoundingRect().width()));
1545         } else {
1546             value_x->setValue(int(i->pos().x()));
1547         }
1548 
1549         if (origin_y_top->isChecked()) {
1550             value_y->setValue(int(m_frameHeight - i->pos().y() - i->sceneBoundingRect().height()));
1551         } else {
1552             value_y->setValue(int(i->pos().y()));
1553         }
1554     }
1555 
1556     // Stop blocking signals now
1557     value_x->blockSignals(false);
1558     value_y->blockSignals(false);
1559 }
1560 
1561 void TitleWidget::updateRotZoom(QGraphicsItem *i)
1562 {
1563     itemzoom->blockSignals(true);
1564     itemrotatex->blockSignals(true);
1565     itemrotatey->blockSignals(true);
1566     itemrotatez->blockSignals(true);
1567 
1568     Transform t = m_transformations.value(i);
1569 
1570     if (!i->data(TitleDocument::ZoomFactor).isNull()) {
1571         itemzoom->setValue(i->data(TitleDocument::ZoomFactor).toInt());
1572     } else {
1573         itemzoom->setValue(qRound(t.scalex * 100.0));
1574     }
1575 
1576     itemrotatex->setValue(int(t.rotatex));
1577     itemrotatey->setValue(int(t.rotatey));
1578     itemrotatez->setValue(int(t.rotatez));
1579 
1580     itemzoom->blockSignals(false);
1581     itemrotatex->blockSignals(false);
1582     itemrotatey->blockSignals(false);
1583     itemrotatez->blockSignals(false);
1584 }
1585 
1586 void TitleWidget::updatePosition(QGraphicsItem *i)
1587 {
1588     updatePosition(i, value_x->value(), value_y->value());
1589 }
1590 
1591 void TitleWidget::updatePosition(QGraphicsItem *i, int x, int y)
1592 {
1593     if (i->type() == TEXTITEM) {
1594         auto *rec = static_cast<MyTextItem *>(i);
1595 
1596         int posX;
1597         if (origin_x_left->isChecked()) {
1598             /*
1599              * Origin of the X axis is at m_frameWidth, and distance from right
1600              * border of the item to the right border of the frame is taken. See
1601              * comment to slotOriginXClicked().
1602              */
1603             posX = m_frameWidth - x - int(rec->boundingRect().width());
1604         } else {
1605             posX = x;
1606         }
1607 
1608         int posY;
1609         if (origin_y_top->isChecked()) {
1610             /* Same for y axis */
1611             posY = m_frameHeight - y - int(rec->boundingRect().height());
1612         } else {
1613             posY = y;
1614         }
1615 
1616         rec->setPos(posX, posY);
1617 
1618     } else if (i->type() == RECTITEM) {
1619 
1620         auto *rec = static_cast<QGraphicsRectItem *>(i);
1621 
1622         int posX;
1623         if (origin_x_left->isChecked()) {
1624             posX = m_frameWidth - x - int(rec->rect().width());
1625         } else {
1626             posX = x;
1627         }
1628 
1629         int posY;
1630         if (origin_y_top->isChecked()) {
1631             posY = m_frameHeight - y - int(rec->rect().height());
1632         } else {
1633             posY = y;
1634         }
1635 
1636         rec->setPos(posX, posY);
1637 
1638     } else if (i->type() == ELLIPSEITEM) {
1639 
1640         auto *rec = static_cast<QGraphicsEllipseItem *>(i);
1641 
1642         int posX;
1643         if (origin_x_left->isChecked()) {
1644             posX = m_frameWidth - x - int(rec->rect().width());
1645         } else {
1646             posX = x;
1647         }
1648 
1649         int posY;
1650         if (origin_y_top->isChecked()) {
1651             posY = m_frameHeight - y - int(rec->rect().height());
1652         } else {
1653             posY = y;
1654         }
1655 
1656         rec->setPos(posX, posY);
1657 
1658     } else if (i->type() == IMAGEITEM) {
1659         int posX;
1660         if (origin_x_left->isChecked()) {
1661             // Use the sceneBoundingRect because this also regards transformations like zoom
1662             posX = m_frameWidth - x - int(i->sceneBoundingRect().width());
1663         } else {
1664             posX = x;
1665         }
1666 
1667         int posY;
1668         if (origin_y_top->isChecked()) {
1669             posY = m_frameHeight - y - int(i->sceneBoundingRect().height());
1670         } else {
1671             posY = y;
1672         }
1673 
1674         i->setPos(posX, posY);
1675     }
1676 }
1677 
1678 void TitleWidget::updateTextOriginX()
1679 {
1680     if (origin_x_left->isChecked()) {
1681         origin_x_left->setText(i18n("\u2212X"));
1682     } else {
1683         origin_x_left->setText(i18n("+X"));
1684     }
1685 }
1686 
1687 void TitleWidget::slotOriginXClicked()
1688 {
1689     // Update the text displayed on the button.
1690     updateTextOriginX();
1691 
1692     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
1693     if (l.size() >= 1) {
1694         updateCoordinates(l.at(0));
1695 
1696         // Remember x axis setting
1697         l.at(0)->setData(TitleDocument::OriginXLeft, origin_x_left->isChecked() ? TitleDocument::AxisInverted : TitleDocument::AxisDefault);
1698     }
1699     graphicsView->setFocus();
1700 }
1701 
1702 void TitleWidget::updateTextOriginY()
1703 {
1704     if (origin_y_top->isChecked()) {
1705         origin_y_top->setText(i18n("\u2212Y"));
1706     } else {
1707         origin_y_top->setText(i18n("+Y"));
1708     }
1709 }
1710 
1711 void TitleWidget::slotOriginYClicked()
1712 {
1713     // Update the text displayed on the button.
1714     updateTextOriginY();
1715 
1716     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
1717     if (l.size() >= 1) {
1718         updateCoordinates(l.at(0));
1719 
1720         l.at(0)->setData(TitleDocument::OriginYTop, origin_y_top->isChecked() ? TitleDocument::AxisInverted : TitleDocument::AxisDefault);
1721     }
1722     graphicsView->setFocus();
1723 }
1724 
1725 void TitleWidget::updateAxisButtons(QGraphicsItem *i)
1726 {
1727     int xAxis = i->data(TitleDocument::OriginXLeft).toInt();
1728     int yAxis = i->data(TitleDocument::OriginYTop).toInt();
1729     origin_x_left->blockSignals(true);
1730     origin_y_top->blockSignals(true);
1731 
1732     if (xAxis == TitleDocument::AxisInverted) {
1733         origin_x_left->setChecked(true);
1734     } else {
1735         origin_x_left->setChecked(false);
1736     }
1737     updateTextOriginX();
1738 
1739     if (yAxis == TitleDocument::AxisInverted) {
1740         origin_y_top->setChecked(true);
1741     } else {
1742         origin_y_top->setChecked(false);
1743     }
1744     updateTextOriginY();
1745 
1746     origin_x_left->blockSignals(false);
1747     origin_y_top->blockSignals(false);
1748 }
1749 
1750 void TitleWidget::slotChangeBackground()
1751 {
1752     QColor color = backgroundColor->color();
1753     m_scene->setBackgroundBrush(QBrush(color));
1754     color.setAlpha(backgroundAlpha->value());
1755     m_frameBackground->setBrush(QBrush(color));
1756 }
1757 
1758 void TitleWidget::slotChanged()
1759 {
1760     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
1761     if (l.size() >= 1 && l.at(0)->type() == TEXTITEM) {
1762         textChanged(static_cast<MyTextItem *>(l.at(0)));
1763     }
1764 }
1765 
1766 void TitleWidget::textChanged(MyTextItem *i)
1767 {
1768     /*
1769      * If the user has set origin_x_left (the same for y), we need to look
1770      * whether a text element has been selected. If yes, we need to ensure that
1771      * the right border of the text field remains fixed also when some text has
1772      * been entered.
1773      *
1774      * This is also known as right-justified, with the difference that it is not
1775      * valid for text but for its boundingRect. Text may still be
1776      * left-justified.
1777      */
1778     updateDimension(i);
1779 
1780     if (origin_x_left->isChecked() || origin_y_top->isChecked()) {
1781         if (!i->document()->isEmpty()) {
1782             updatePosition(i);
1783         } else {
1784             /*
1785              * Don't do anything if the string is empty. If the position were
1786              * updated here, a newly created text field would be set to the
1787              * position of the last selected text field.
1788              */
1789         }
1790     }
1791 
1792     // mbt 1607: Template text has changed; don't auto-select content anymore.
1793     if (i->property("isTemplate").isValid()) {
1794         if (i->property("templateText").isValid()) {
1795             if (i->property("templateText") == i->toHtml()) {
1796                 // Unchanged, do nothing.
1797             } else {
1798                 i->setProperty("isTemplate", QVariant::Invalid);
1799                 i->setProperty("templateText", QVariant::Invalid);
1800             }
1801         }
1802     }
1803 }
1804 
1805 void TitleWidget::slotInsertUnicode()
1806 {
1807     m_unicodeDialog->exec();
1808 }
1809 
1810 void TitleWidget::slotInsertUnicodeString(const QString &string)
1811 {
1812     const QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
1813     if (!l.isEmpty()) {
1814         if (l.at(0)->type() == TEXTITEM) {
1815             auto *t = static_cast<MyTextItem *>(l.at(0));
1816             t->textCursor().insertText(string);
1817         }
1818     }
1819 }
1820 
1821 void TitleWidget::slotUpdateText()
1822 {
1823     QFont font = font_family->currentFont();
1824     QString selected = font.family();
1825     if (!QFontDatabase().families().contains(selected)) {
1826         QSignalBlocker bk(font_family);
1827         font = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
1828         font_family->setCurrentFont(font);
1829     }
1830     font.setPixelSize(font_size->value());
1831     font.setItalic(buttonItalic->isChecked());
1832     font.setUnderline(buttonUnder->isChecked());
1833     font.setWeight(QFont::Weight(font_weight_box->itemData(font_weight_box->currentIndex()).toInt()));
1834     if (letter_spacing->value() != 0) {
1835         font.setLetterSpacing(QFont::AbsoluteSpacing, letter_spacing->value());
1836     }
1837     QColor color = fontColorButton->color();
1838     QColor outlineColor = textOutlineColor->color();
1839     QString gradientData;
1840     if (gradient_color->isChecked()) {
1841         // user wants a gradient
1842         gradientData = gradients_combo->currentData().toString();
1843     }
1844 
1845     double outlineWidth = textOutline->value();
1846 
1847     int i;
1848     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
1849     for (i = 0; i < l.length(); ++i) {
1850         MyTextItem *item = nullptr;
1851         if (l.at(i)->type() == TEXTITEM) {
1852             item = static_cast<MyTextItem *>(l.at(i));
1853         }
1854         if (!item) {
1855             // No text item, try next one.
1856             continue;
1857         }
1858 
1859         // Set alignment of all text in the text item
1860         QTextCursor cur(item->document());
1861         cur.select(QTextCursor::Document);
1862         QTextBlockFormat format = cur.blockFormat();
1863         item->setData(TitleDocument::LineSpacing, line_spacing->value());
1864         format.setLineHeight(line_spacing->value(), QTextBlockFormat::LineDistanceHeight);
1865         if (buttonAlignLeft->isChecked() || buttonAlignCenter->isChecked() || buttonAlignRight->isChecked()) {
1866             if (buttonAlignCenter->isChecked()) {
1867                 item->setAlignment(Qt::AlignHCenter);
1868             } else if (buttonAlignRight->isChecked()) {
1869                 item->setAlignment(Qt::AlignRight);
1870             } else if (buttonAlignLeft->isChecked()) {
1871                 item->setAlignment(Qt::AlignLeft);
1872             }
1873         } else {
1874             item->setAlignment(qApp->isLeftToRight() ? Qt::AlignRight : Qt::AlignLeft);
1875         }
1876 
1877         // Set font properties
1878         item->setFont(font);
1879         QTextCharFormat cformat = cur.charFormat();
1880 
1881         item->setData(TitleDocument::OutlineWidth, outlineWidth);
1882         item->setData(TitleDocument::OutlineColor, outlineColor);
1883         if (outlineWidth > 0.0) {
1884             cformat.setTextOutline(QPen(outlineColor, outlineWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
1885         }
1886 
1887         if (gradientData.isEmpty()) {
1888             cformat.setForeground(QBrush(color));
1889         } else {
1890             QLinearGradient gr = GradientWidget::gradientFromString(gradientData, int(item->boundingRect().width()), int(item->boundingRect().height()));
1891             cformat.setForeground(QBrush(gr));
1892         }
1893         // Store gradient in item properties
1894         item->setData(TitleDocument::Gradient, gradientData);
1895         cur.setCharFormat(cformat);
1896         cur.setBlockFormat(format);
1897         //  item->setTextCursor(cur);
1898         cur.clearSelection();
1899         item->setTextCursor(cur);
1900         item->setTextColor(color);
1901     }
1902 }
1903 
1904 void TitleWidget::rectChanged()
1905 {
1906     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
1907     for (auto i : qAsConst(l)) {
1908         if (i->type() == RECTITEM && (settingUp == 0)) {
1909             auto *rec = static_cast<QGraphicsRectItem *>(i);
1910             QColor f = rectFColor->color();
1911             if (rectLineWidth->value() == 0) {
1912                 rec->setPen(Qt::NoPen);
1913             } else {
1914                 QPen penf(f);
1915                 penf.setWidth(rectLineWidth->value());
1916                 penf.setJoinStyle(Qt::RoundJoin);
1917                 rec->setPen(penf);
1918             }
1919             if (plain_rect->isChecked()) {
1920                 rec->setBrush(QBrush(rectBColor->color()));
1921                 rec->setData(TitleDocument::Gradient, QVariant());
1922             } else {
1923                 // gradient
1924                 QString gradientData = gradients_rect_combo->currentData().toString();
1925                 rec->setData(TitleDocument::Gradient, gradientData);
1926                 QLinearGradient gr = GradientWidget::gradientFromString(gradientData, int(rec->boundingRect().width()), int(rec->boundingRect().height()));
1927                 rec->setBrush(QBrush(gr));
1928             }
1929         } else if (i->type() == ELLIPSEITEM && (settingUp == 0)) {
1930             auto *ellipse = static_cast<QGraphicsEllipseItem *>(i);
1931             QColor f = rectFColor->color();
1932             if (rectLineWidth->value() == 0) {
1933                 ellipse->setPen(Qt::NoPen);
1934             } else {
1935                 QPen penf(f);
1936                 penf.setWidth(rectLineWidth->value());
1937                 penf.setJoinStyle(Qt::RoundJoin);
1938                 ellipse->setPen(penf);
1939             }
1940             if (plain_rect->isChecked()) {
1941                 ellipse->setBrush(QBrush(rectBColor->color()));
1942                 ellipse->setData(TitleDocument::Gradient, QVariant());
1943             } else {
1944                 // gradient
1945                 QString gradientData = gradients_rect_combo->currentData().toString();
1946                 ellipse->setData(TitleDocument::Gradient, gradientData);
1947                 QLinearGradient gr =
1948                     GradientWidget::gradientFromString(gradientData, int(ellipse->boundingRect().width()), int(ellipse->boundingRect().height()));
1949                 ellipse->setBrush(QBrush(gr));
1950             }
1951         }
1952     }
1953 }
1954 
1955 void TitleWidget::itemScaled(int val)
1956 {
1957     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
1958     if (l.size() == 1) {
1959         Transform x = m_transformations.value(l.at(0));
1960         x.scalex = val / 100.0;
1961         x.scaley = val / 100.0;
1962         QTransform qtrans;
1963         qtrans.scale(x.scalex, x.scaley);
1964         qtrans.rotate(x.rotatex, Qt::XAxis);
1965         qtrans.rotate(x.rotatey, Qt::YAxis);
1966         qtrans.rotate(x.rotatez, Qt::ZAxis);
1967         l[0]->setTransform(qtrans);
1968         l[0]->setData(TitleDocument::ZoomFactor, val);
1969         m_transformations[l.at(0)] = x;
1970         updateDimension(l.at(0));
1971     }
1972 }
1973 
1974 void TitleWidget::itemRotateX(int val)
1975 {
1976     itemRotate(val, 0);
1977 }
1978 
1979 void TitleWidget::itemRotateY(int val)
1980 {
1981     itemRotate(val, 1);
1982 }
1983 
1984 void TitleWidget::itemRotateZ(int val)
1985 {
1986     itemRotate(val, 2);
1987 }
1988 
1989 void TitleWidget::itemRotate(int val, int axis)
1990 {
1991     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
1992     if (l.size() == 1) {
1993         Transform x = m_transformations[l.at(0)];
1994         switch (axis) {
1995         case 0:
1996             x.rotatex = val;
1997             break;
1998         case 1:
1999             x.rotatey = val;
2000             break;
2001         case 2:
2002             x.rotatez = val;
2003             break;
2004         }
2005 
2006         l[0]->setData(TitleDocument::RotateFactor, QList<QVariant>() << QVariant(x.rotatex) << QVariant(x.rotatey) << QVariant(x.rotatez));
2007 
2008         QTransform qtrans;
2009         qtrans.scale(x.scalex, x.scaley);
2010         qtrans.rotate(x.rotatex, Qt::XAxis);
2011         qtrans.rotate(x.rotatey, Qt::YAxis);
2012         qtrans.rotate(x.rotatez, Qt::ZAxis);
2013         l[0]->setTransform(qtrans);
2014         m_transformations[l.at(0)] = x;
2015         if (l[0]->data(TitleDocument::ZoomFactor).isNull()) {
2016             l[0]->setData(TitleDocument::ZoomFactor, 100);
2017         }
2018         updateDimension(l.at(0));
2019     }
2020 }
2021 
2022 void TitleWidget::itemHCenter()
2023 {
2024     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
2025     if (l.size() == 1) {
2026         QGraphicsItem *item = l.at(0);
2027         QRectF br = item->sceneBoundingRect();
2028         int width = int(br.width());
2029         int newPos = (m_frameWidth - width) / 2;
2030         newPos += int(item->pos().x() - br.left()); // Check item transformation
2031         item->setPos(newPos, item->pos().y());
2032         updateCoordinates(item);
2033         slotAdjustZoom();
2034         graphicsView->centerOn(m_frameBorder);
2035         slotAdjustZoom();
2036         graphicsView->centerOn(m_frameBorder);
2037     }
2038 }
2039 
2040 void TitleWidget::itemVCenter()
2041 {
2042     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
2043     if (l.size() == 1) {
2044         QGraphicsItem *item = l.at(0);
2045         QRectF br = item->sceneBoundingRect();
2046         int height = int(br.height());
2047         int newPos = (m_frameHeight - height) / 2;
2048         newPos += int(item->pos().y() - br.top()); // Check item transformation
2049         item->setPos(item->pos().x(), newPos);
2050         updateCoordinates(item);
2051     }
2052 }
2053 
2054 void TitleWidget::itemTop()
2055 {
2056     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
2057     if (l.size() == 1) {
2058         QList<double> margins{m_frameHeight * 0.05, m_frameHeight * 0.1};
2059         QGraphicsItem *item = l.at(0);
2060         QRectF br = item->sceneBoundingRect();
2061         double diff;
2062         if (br.top() < 0.) {
2063             // align with big margin
2064             diff = margins.at(1) - br.top();
2065         } else if (qFuzzyIsNull(br.top())) {
2066             // align right with frame border
2067             diff = -br.bottom();
2068         } else if (br.top() <= margins.at(0)) {
2069             // align with 0
2070             diff = -br.top();
2071         } else if (br.top() <= margins.at(1)) {
2072             // align with small margin
2073             diff = margins.at(0) - br.top();
2074         } else {
2075             // align with big margin
2076             diff = margins.at(1) - br.top();
2077         }
2078         item->moveBy(0, diff);
2079         updateCoordinates(item);
2080     }
2081 }
2082 
2083 void TitleWidget::itemBottom()
2084 {
2085     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
2086     if (l.size() == 1) {
2087         QList<double> margins{m_frameHeight * 0.9, m_frameHeight * 0.95};
2088         QGraphicsItem *item = l.at(0);
2089         QRectF br = item->sceneBoundingRect();
2090         double diff;
2091         if (br.bottom() < margins.at(0)) {
2092             // align with small margin
2093             diff = margins.at(0) - br.bottom();
2094         } else if (br.bottom() < margins.at(1)) {
2095             // align big margin
2096             diff = margins.at(1) - br.bottom();
2097         } else if (br.bottom() < m_frameHeight) {
2098             // align with frame
2099             diff = m_frameHeight - br.bottom();
2100         } else if (br.top() < m_frameHeight) {
2101             // align left with frame
2102             diff = m_frameHeight - br.top();
2103         } else {
2104             // align with big margin
2105             diff = margins.at(0) - br.bottom();
2106         }
2107         item->moveBy(0, diff);
2108         updateCoordinates(item);
2109     }
2110 }
2111 
2112 void TitleWidget::itemLeft()
2113 {
2114     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
2115     if (l.size() == 1) {
2116         QList<double> margins{m_frameWidth * 0.05, m_frameWidth * 0.1};
2117         QGraphicsItem *item = l.at(0);
2118         QRectF br = item->sceneBoundingRect();
2119         double diff;
2120         if (br.left() < 0.) {
2121             // align with big margin
2122             diff = margins.at(1) - br.left();
2123         } else if (qFuzzyIsNull(br.left())) {
2124             // align right with frame border
2125             diff = -br.right();
2126         } else if (br.left() <= margins.at(0)) {
2127             // align with 0
2128             diff = -br.left();
2129         } else if (br.left() <= margins.at(1)) {
2130             // align with small margin
2131             diff = margins.at(0) - br.left();
2132         } else {
2133             // align with big margin
2134             diff = margins.at(1) - br.left();
2135         }
2136         item->moveBy(diff, 0);
2137         updateCoordinates(item);
2138     }
2139 }
2140 
2141 void TitleWidget::itemRight()
2142 {
2143     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
2144     if (l.size() == 1) {
2145         QList<double> margins{m_frameWidth * 0.9, m_frameWidth * 0.95};
2146         QGraphicsItem *item = l.at(0);
2147         QRectF br = item->sceneBoundingRect();
2148         double diff;
2149         if (br.right() < margins.at(0)) {
2150             // align with small margin
2151             diff = margins.at(0) - br.right();
2152         } else if (br.right() < margins.at(1)) {
2153             // align big margin
2154             diff = margins.at(1) - br.right();
2155         } else if (br.right() < m_frameWidth) {
2156             // align with frame
2157             diff = m_frameWidth - br.right();
2158         } else if (br.left() < m_frameWidth) {
2159             // align left with frame
2160             diff = m_frameWidth - br.left();
2161         } else {
2162             // align with big margin
2163             diff = margins.at(0) - br.right();
2164         }
2165         item->moveBy(diff, 0);
2166         updateCoordinates(item);
2167     }
2168 }
2169 
2170 void TitleWidget::loadTitle(QUrl url)
2171 {
2172     if (!url.isValid()) {
2173         QString startFolder = KRecentDirs::dir(QStringLiteral(":KdenliveProjectsTitles"));
2174         url = QFileDialog::getOpenFileUrl(this, i18n("Load Title"), QUrl::fromLocalFile(startFolder.isEmpty() ? m_projectTitlePath : startFolder),
2175                                           i18n("Kdenlive title") + QStringLiteral(" (*.kdenlivetitle)"));
2176     }
2177     if (url.isValid()) {
2178         if (anim_start->isChecked()) {
2179             anim_start->setChecked(false);
2180         }
2181         if (anim_end->isChecked()) {
2182             anim_end->setChecked(false);
2183         }
2184         // make sure we don't delete the guides
2185         qDeleteAll(m_guides);
2186         m_guides.clear();
2187         QList<QGraphicsItem *> items = m_scene->items();
2188         items.removeAll(m_frameBorder);
2189         items.removeAll(m_frameBackground);
2190         items.removeAll(m_frameImage);
2191         for (auto item : qAsConst(items)) {
2192             if (item->zValue() > -1000) {
2193                 delete item;
2194             }
2195         }
2196         m_scene->clearTextSelection();
2197         QDomDocument doc;
2198         if (!Xml::docContentFromFile(doc, url.toLocalFile(), false)) {
2199             return;
2200         }
2201         setXml(doc);
2202         updateGuides(0);
2203         m_projectTitlePath = QFileInfo(url.toLocalFile()).dir().absolutePath();
2204         KRecentDirs::add(QStringLiteral(":KdenliveProjectsTitles"), m_projectTitlePath);
2205     }
2206 }
2207 
2208 QUrl TitleWidget::saveTitle(QUrl url)
2209 {
2210     if (anim_start->isChecked()) {
2211         slotAnimStart(false);
2212     }
2213     if (anim_end->isChecked()) {
2214         slotAnimEnd(false);
2215     }
2216     // If we have images in the title, ask for embed
2217     QList<QGraphicsItem *> list = graphicsView->scene()->items();
2218     auto is_embedable = [&](QGraphicsItem *item) { return item->type() == QGraphicsPixmapItem::Type && item != m_frameImage; };
2219     bool embed_image = std::any_of(list.begin(), list.end(), is_embedable);
2220     if (embed_image &&
2221         KMessageBox::questionTwoActions(this, i18n("Do you want to embed Images into this TitleDocument?\nThis is most needed for sharing Titles."), {},
2222                                         KGuiItem(i18nc("@action:button", "Embed Images")),
2223                                         KGuiItem(i18nc("@action:button", "Continue without"))) != KMessageBox::PrimaryAction) {
2224         embed_image = false;
2225     }
2226     if (!url.isValid()) {
2227         QPointer<QFileDialog> fs = new QFileDialog(this, i18n("Save Title"), m_projectTitlePath);
2228         fs->setMimeTypeFilters(QStringList() << QStringLiteral("application/x-kdenlivetitle"));
2229         fs->setFileMode(QFileDialog::AnyFile);
2230         fs->setAcceptMode(QFileDialog::AcceptSave);
2231         fs->setDefaultSuffix(QStringLiteral("kdenlivetitle"));
2232 
2233         if ((fs->exec() != 0) && !fs->selectedUrls().isEmpty()) {
2234             url = fs->selectedUrls().constFirst();
2235         }
2236         delete fs;
2237     }
2238     if (url.isValid()) {
2239         if (!m_titledocument.saveDocument(url, m_startViewport, m_endViewport, m_duration->getValue(), embed_image)) {
2240             KMessageBox::error(this, i18n("Cannot write to file %1", url.toLocalFile()));
2241         } else {
2242             return url;
2243         }
2244     }
2245     return QUrl();
2246 }
2247 
2248 QDomDocument TitleWidget::xml()
2249 {
2250     QDomDocument doc = m_titledocument.xml(m_startViewport, m_endViewport);
2251     int duration = m_duration->getValue();
2252     doc.documentElement().setAttribute(QStringLiteral("duration"), duration);
2253     doc.documentElement().setAttribute(QStringLiteral("out"), duration - 1);
2254     return doc;
2255 }
2256 
2257 int TitleWidget::duration() const
2258 {
2259     return m_duration->getValue();
2260 }
2261 
2262 void TitleWidget::setXml(const QDomDocument &doc, const QString &id)
2263 {
2264     m_clipId = id;
2265     int duration;
2266     if (m_missingMessage) {
2267         delete m_missingMessage;
2268         m_missingMessage = nullptr;
2269     }
2270     m_count = m_titledocument.loadFromXml(doc, m_scene, m_startViewport, m_endViewport, &duration, m_projectTitlePath);
2271     adjustFrameSize();
2272     if (m_titledocument.invalidCount() > 0) {
2273         m_missingMessage = new KMessageWidget(this);
2274         m_missingMessage->setCloseButtonVisible(true);
2275         m_missingMessage->setWordWrap(true);
2276         m_missingMessage->setMessageType(KMessageWidget::Warning);
2277         m_missingMessage->setText(i18np("This title has 1 missing element", "This title has %1 missing elements", m_titledocument.invalidCount()));
2278         QAction *action = new QAction(i18n("Details"));
2279         m_missingMessage->addAction(action);
2280         connect(action, &QAction::triggered, this, &TitleWidget::showMissingItems);
2281         action = new QAction(i18n("Delete missing elements"));
2282         m_missingMessage->addAction(action);
2283         connect(action, &QAction::triggered, this, &TitleWidget::deleteMissingItems);
2284         messageLayout->addWidget(m_missingMessage);
2285         m_missingMessage->animatedShow();
2286     }
2287     m_duration->setValue(GenTime(duration, m_fps));
2288 
2289     QDomElement e = doc.documentElement();
2290     m_transformations.clear();
2291     QList<QGraphicsItem *> items = graphicsView->scene()->items();
2292     const double PI = 4.0 * atan(1.0);
2293     for (int i = 0; i < items.count(); ++i) {
2294         QTransform t = items.at(i)->transform();
2295         Transform x;
2296         x.scalex = t.m11();
2297         x.scaley = t.m22();
2298         if (!items.at(i)->data(TitleDocument::RotateFactor).isNull()) {
2299             QList<QVariant> rotlist = items.at(i)->data(TitleDocument::RotateFactor).toList();
2300             if (rotlist.count() >= 3) {
2301                 x.rotatex = rotlist[0].toInt();
2302                 x.rotatey = rotlist[1].toInt();
2303                 x.rotatez = rotlist[2].toInt();
2304 
2305                 // Try to adjust zoom
2306                 t.rotate(x.rotatex * (-1), Qt::XAxis);
2307                 t.rotate(x.rotatey * (-1), Qt::YAxis);
2308                 t.rotate(x.rotatez * (-1), Qt::ZAxis);
2309                 x.scalex = t.m11();
2310                 x.scaley = t.m22();
2311             } else {
2312                 x.rotatex = 0;
2313                 x.rotatey = 0;
2314                 x.rotatez = 0;
2315             }
2316         } else {
2317             x.rotatex = 0;
2318             x.rotatey = 0;
2319             x.rotatez = int(180. / PI * atan2(-t.m21(), t.m11()));
2320         }
2321         m_transformations[items.at(i)] = x;
2322     }
2323     // mbd: Update the GUI color selectors to match the stuff from the loaded document
2324     QColor background_color = m_titledocument.getBackgroundColor();
2325     backgroundAlpha->blockSignals(true);
2326     backgroundColor->blockSignals(true);
2327     backgroundAlpha->setValue(background_color.alpha());
2328     bgAlphaSlider->setValue(background_color.alpha());
2329     background_color.setAlpha(255);
2330     backgroundColor->setColor(background_color);
2331     backgroundAlpha->blockSignals(false);
2332     backgroundColor->blockSignals(false);
2333 
2334     /*startViewportX->setValue(m_startViewport->data(0).toInt());
2335     startViewportY->setValue(m_startViewport->data(1).toInt());
2336     startViewportSize->setValue(m_startViewport->data(2).toInt());
2337     endViewportX->setValue(m_endViewport->data(0).toInt());
2338     endViewportY->setValue(m_endViewport->data(1).toInt());
2339     endViewportSize->setValue(m_endViewport->data(2).toInt());*/
2340 
2341     m_createTitleAction->setText(i18n("Update Title"));
2342 
2343     auto *addMenu = new QMenu(this);
2344     addMenu->addAction(i18n("Add as new Title"));
2345     createButton->setMenu(addMenu);
2346     connect(addMenu, &QMenu::triggered, this, [this]() { done(QDialog::Accepted + 1); });
2347 
2348     QTimer::singleShot(200, this, &TitleWidget::slotAdjustZoom);
2349     slotSelectTool();
2350     selectionChanged();
2351 }
2352 
2353 void TitleWidget::slotAccepted()
2354 {
2355     if (anim_start->isChecked()) {
2356         slotAnimStart(false);
2357     }
2358     if (anim_end->isChecked()) {
2359         slotAnimEnd(false);
2360     }
2361     writeChoices();
2362 }
2363 
2364 void TitleWidget::deleteMissingItems()
2365 {
2366     m_missingMessage->animatedHide();
2367     QList<QGraphicsItem *> items = graphicsView->scene()->items();
2368     QList<QGraphicsItem *> toDelete;
2369     for (int i = 0; i < items.count(); ++i) {
2370         if (items.at(i)->data(Qt::UserRole + 2).toInt() == 1) {
2371             // We found a missing item
2372             toDelete << items.at(i);
2373         }
2374     }
2375     if (toDelete.size() != m_titledocument.invalidCount()) {
2376         qDebug() << "/// WARNING, INCOHERENT MISSING ELEMENTS in title: " << toDelete.size() << " != " << m_titledocument.invalidCount();
2377     }
2378     while (!toDelete.isEmpty()) {
2379         QGraphicsItem *item = toDelete.takeFirst();
2380         if (m_scene) {
2381             m_scene->removeItem(item);
2382         }
2383     }
2384     m_missingMessage->deleteLater();
2385 }
2386 
2387 void TitleWidget::showMissingItems()
2388 {
2389     QList<QGraphicsItem *> items = graphicsView->scene()->items();
2390     QStringList missingUrls;
2391     for (int i = 0; i < items.count(); ++i) {
2392         if (items.at(i)->data(Qt::UserRole + 2).toInt() == 1) {
2393             // We found a missing item
2394             missingUrls << items.at(i)->data(Qt::UserRole).toString();
2395         }
2396     }
2397     missingUrls.removeDuplicates();
2398     KMessageBox::informationList(QApplication::activeWindow(), i18n("The following files are missing:"), missingUrls);
2399 }
2400 
2401 void TitleWidget::writeChoices()
2402 {
2403     // Get a pointer to a shared configuration instance, then get the TitleWidget group.
2404     KSharedConfigPtr config = KSharedConfig::openConfig();
2405     KConfigGroup titleConfig(config, "TitleWidget");
2406     // Write the entries
2407     titleConfig.writeEntry("dialog_geometry", saveGeometry().toBase64());
2408     titleConfig.writeEntry("font_family", font_family->currentFont());
2409     // titleConfig.writeEntry("font_size", font_size->value());
2410     titleConfig.writeEntry("font_pixel_size", font_size->value());
2411     titleConfig.writeEntry("font_color", fontColorButton->color());
2412     titleConfig.writeEntry("font_outline_color", textOutlineColor->color());
2413     titleConfig.writeEntry("font_outline", textOutline->value() * 10);
2414     titleConfig.writeEntry("font_weight", font_weight_box->itemData(font_weight_box->currentIndex()).toInt());
2415     titleConfig.writeEntry("font_italic", buttonItalic->isChecked());
2416     titleConfig.writeEntry("font_underlined", buttonUnder->isChecked());
2417 
2418     titleConfig.writeEntry("rect_background_color", rectBColor->color());
2419     titleConfig.writeEntry("rect_foreground_color", rectFColor->color());
2420 
2421     titleConfig.writeEntry("rect_background_alpha", rectBColor->color().alpha());
2422     titleConfig.writeEntry("rect_foreground_alpha", rectFColor->color().alpha());
2423 
2424     titleConfig.writeEntry("rect_line_width", rectLineWidth->value());
2425 
2426     titleConfig.writeEntry("background_color", backgroundColor->color());
2427     titleConfig.writeEntry("background_alpha", backgroundAlpha->value());
2428 
2429     titleConfig.writeEntry("use_grid", use_grid->isChecked());
2430 
2431     //! \todo Not sure if I should sync - it is probably safe to do it
2432     config->sync();
2433 }
2434 
2435 void TitleWidget::readChoices()
2436 {
2437     // Get a pointer to a shared configuration instance, then get the TitleWidget group.
2438     KSharedConfigPtr config = KSharedConfig::openConfig();
2439     KConfigGroup titleConfig(config, "TitleWidget");
2440     // read the entries
2441     const QByteArray geometry = titleConfig.readEntry("dialog_geometry", QByteArray());
2442     restoreGeometry(QByteArray::fromBase64(geometry));
2443     QFont font = titleConfig.readEntry("font_family", font_family->currentFont());
2444     if (!QFontDatabase().families().contains(font.family())) {
2445         font = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
2446     }
2447     font_family->setCurrentFont(font);
2448     font_size->setValue(titleConfig.readEntry("font_pixel_size", m_frameHeight > 0 ? m_frameHeight / 20 : font_size->value()));
2449     m_scene->slotUpdateFontSize(font_size->value());
2450     QColor fontColor = QColor(titleConfig.readEntry("font_color", fontColorButton->color()));
2451     QColor outlineColor = QColor(titleConfig.readEntry("font_outline_color", textOutlineColor->color()));
2452     fontColor.setAlpha(titleConfig.readEntry("font_alpha", fontColor.alpha()));
2453     outlineColor.setAlpha(titleConfig.readEntry("font_outline_alpha", outlineColor.alpha()));
2454     fontColorButton->setColor(fontColor);
2455     textOutlineColor->setColor(outlineColor);
2456     textOutline->setValue(titleConfig.readEntry("font_outline", textOutline->value()) / 10.0);
2457 
2458     int weight;
2459     if (titleConfig.readEntry("font_bold", false)) {
2460         weight = QFont::Bold;
2461     } else {
2462         weight = titleConfig.readEntry("font_weight", font_weight_box->itemData(font_weight_box->currentIndex()).toInt());
2463     }
2464     setFontBoxWeight(weight);
2465     buttonItalic->setChecked(titleConfig.readEntry("font_italic", buttonItalic->isChecked()));
2466     buttonUnder->setChecked(titleConfig.readEntry("font_underlined", buttonUnder->isChecked()));
2467 
2468     QColor fgColor = QColor(titleConfig.readEntry("rect_foreground_color", rectFColor->color()));
2469     QColor bgColor = QColor(titleConfig.readEntry("rect_background_color", rectBColor->color()));
2470 
2471     fgColor.setAlpha(titleConfig.readEntry("rect_foreground_alpha", fgColor.alpha()));
2472     bgColor.setAlpha(titleConfig.readEntry("rect_background_alpha", bgColor.alpha()));
2473     rectFColor->setColor(fgColor);
2474     rectBColor->setColor(bgColor);
2475 
2476     rectLineWidth->setValue(titleConfig.readEntry("rect_line_width", rectLineWidth->value()));
2477 
2478     backgroundColor->setColor(titleConfig.readEntry("background_color", backgroundColor->color()));
2479     backgroundAlpha->setValue(titleConfig.readEntry("background_alpha", backgroundAlpha->value()));
2480     use_grid->setChecked(titleConfig.readEntry("use_grid", false));
2481     m_scene->slotUseGrid(use_grid->isChecked());
2482 }
2483 
2484 void TitleWidget::adjustFrameSize()
2485 {
2486     m_frameWidth = m_titledocument.frameWidth();
2487     m_frameHeight = m_titledocument.frameHeight();
2488     m_frameBorder->setRect(0, 0, m_frameWidth, m_frameHeight);
2489     displayBackgroundFrame();
2490 }
2491 
2492 void TitleWidget::slotAnimStart(bool anim)
2493 {
2494     if (anim && anim_end->isChecked()) {
2495         anim_end->setChecked(false);
2496         m_endViewport->setZValue(-1000);
2497         m_endViewport->setBrush(QBrush());
2498     }
2499     slotSelectTool();
2500     QList<QGraphicsItem *> list = m_scene->items();
2501     for (int i = 0; i < list.count(); ++i) {
2502         if (list.at(i)->zValue() > -1000) {
2503             if (!list.at(i)->data(-1).isNull()) {
2504                 continue;
2505             }
2506             list.at(i)->setFlag(QGraphicsItem::ItemIsMovable, !anim);
2507             list.at(i)->setFlag(QGraphicsItem::ItemIsSelectable, !anim);
2508         }
2509     }
2510     align_box->setEnabled(anim);
2511     itemzoom->setEnabled(!anim);
2512     itemrotatex->setEnabled(!anim);
2513     itemrotatey->setEnabled(!anim);
2514     itemrotatez->setEnabled(!anim);
2515     frame_toolbar->setEnabled(!anim);
2516     toolbar_stack->setEnabled(!anim);
2517     if (anim) {
2518         keep_aspect->setChecked(!m_startViewport->data(0).isNull());
2519         m_startViewport->setZValue(1100);
2520         QColor col = m_startViewport->pen().color();
2521         col.setAlpha(100);
2522         m_startViewport->setBrush(col);
2523         m_startViewport->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
2524         m_startViewport->setSelected(true);
2525         selectionChanged();
2526         slotSelectTool();
2527         if (m_startViewport->childItems().isEmpty()) {
2528             addAnimInfoText();
2529         }
2530     } else {
2531         m_startViewport->setZValue(-1000);
2532         m_startViewport->setBrush(QBrush());
2533         if (!anim_end->isChecked()) {
2534             deleteAnimInfoText();
2535         }
2536     }
2537 }
2538 
2539 void TitleWidget::slotAnimEnd(bool anim)
2540 {
2541     if (anim && anim_start->isChecked()) {
2542         anim_start->setChecked(false);
2543         m_startViewport->setZValue(-1000);
2544         m_startViewport->setBrush(QBrush());
2545     }
2546     slotSelectTool();
2547     QList<QGraphicsItem *> list = m_scene->items();
2548     for (int i = 0; i < list.count(); ++i) {
2549         if (list.at(i)->zValue() > -1000) {
2550             if (!list.at(i)->data(-1).isNull()) {
2551                 continue;
2552             }
2553             list.at(i)->setFlag(QGraphicsItem::ItemIsMovable, !anim);
2554             list.at(i)->setFlag(QGraphicsItem::ItemIsSelectable, !anim);
2555         }
2556     }
2557     align_box->setEnabled(anim);
2558     itemzoom->setEnabled(!anim);
2559     itemrotatex->setEnabled(!anim);
2560     itemrotatey->setEnabled(!anim);
2561     itemrotatez->setEnabled(!anim);
2562     frame_toolbar->setEnabled(!anim);
2563     toolbar_stack->setEnabled(!anim);
2564     if (anim) {
2565         keep_aspect->setChecked(!m_endViewport->data(0).isNull());
2566         m_endViewport->setZValue(1100);
2567         QColor col = m_endViewport->pen().color();
2568         col.setAlpha(100);
2569         m_endViewport->setBrush(col);
2570         m_endViewport->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
2571         m_endViewport->setSelected(true);
2572         m_startViewport->setSelected(false);
2573         selectionChanged();
2574         slotSelectTool();
2575         if (m_endViewport->childItems().isEmpty()) {
2576             addAnimInfoText();
2577         }
2578     } else {
2579         m_endViewport->setZValue(-1000);
2580         m_endViewport->setBrush(QBrush());
2581         m_endViewport->setFlag(QGraphicsItem::ItemIsMovable, false);
2582         m_endViewport->setFlag(QGraphicsItem::ItemIsSelectable, false);
2583         if (!anim_start->isChecked()) {
2584             deleteAnimInfoText();
2585         }
2586     }
2587 }
2588 
2589 void TitleWidget::addAnimInfoText()
2590 {
2591     // add text to anim viewport
2592     QGraphicsTextItem *t = new QGraphicsTextItem(i18nc("Indicates the start of an animation", "Start Viewport"), m_startViewport);
2593     QGraphicsTextItem *t2 = new QGraphicsTextItem(i18nc("Indicates the end of an animation", "End Viewport"), m_endViewport);
2594     QFont font = t->font();
2595     font.setPixelSize(int(m_startViewport->rect().width() / 10));
2596     QColor col = m_startViewport->pen().color();
2597     col.setAlpha(255);
2598     t->setDefaultTextColor(col);
2599     t->setFont(font);
2600     font.setPixelSize(int(m_endViewport->rect().width() / 10));
2601     col = m_endViewport->pen().color();
2602     col.setAlpha(255);
2603     t2->setDefaultTextColor(col);
2604     t2->setFont(font);
2605 }
2606 
2607 void TitleWidget::updateInfoText()
2608 {
2609     // update info text font
2610     if (!m_startViewport->childItems().isEmpty()) {
2611         MyTextItem *item = static_cast<MyTextItem *>(m_startViewport->childItems().at(0));
2612         if (item) {
2613             QFont font = item->font();
2614             font.setPixelSize(int(m_startViewport->rect().width() / 10));
2615             item->setFont(font);
2616         }
2617     }
2618     if (!m_endViewport->childItems().isEmpty()) {
2619         MyTextItem *item = static_cast<MyTextItem *>(m_endViewport->childItems().at(0));
2620         if (item) {
2621             QFont font = item->font();
2622             font.setPixelSize(int(m_endViewport->rect().width() / 10));
2623             item->setFont(font);
2624         }
2625     }
2626 }
2627 
2628 void TitleWidget::deleteAnimInfoText()
2629 {
2630     // end animation editing, remove info text
2631     while (!m_startViewport->childItems().isEmpty()) {
2632         QGraphicsItem *item = m_startViewport->childItems().at(0);
2633         if (m_scene) {
2634             m_scene->removeItem(item);
2635         }
2636     }
2637     while (!m_endViewport->childItems().isEmpty()) {
2638         QGraphicsItem *item = m_endViewport->childItems().at(0);
2639         if (m_scene) {
2640             m_scene->removeItem(item);
2641         }
2642     }
2643 }
2644 
2645 void TitleWidget::slotKeepAspect(bool keep)
2646 {
2647     if (int(m_endViewport->zValue()) == 1100) {
2648         m_endViewport->setData(0, keep ? m_frameWidth : QVariant());
2649         m_endViewport->setData(1, keep ? m_frameHeight : QVariant());
2650     } else {
2651         m_startViewport->setData(0, keep ? m_frameWidth : QVariant());
2652         m_startViewport->setData(1, keep ? m_frameHeight : QVariant());
2653     }
2654 }
2655 
2656 void TitleWidget::slotResize(int percentSize)
2657 {
2658     int w, h;
2659     if (percentSize < 100) {
2660         w = m_frameWidth / (100 / percentSize);
2661         h = m_frameHeight / (100 / percentSize);
2662     } else {
2663         w = m_frameWidth * (percentSize / 100);
2664         h = m_frameHeight * (percentSize / 100);
2665     }
2666     if (int(m_endViewport->zValue()) == 1100) {
2667         m_endViewport->setRect(0, 0, w, h);
2668     } else {
2669         m_startViewport->setRect(0, 0, w, h);
2670     }
2671     updateInfoText();
2672 }
2673 
2674 void TitleWidget::slotAddEffect(int /*ix*/)
2675 {
2676     /*
2677         QList<QGraphicsItem *> list = graphicsView->scene()->selectedItems();
2678         int effect = effect_list->itemData(ix).toInt();
2679         if (list.size() == 1) {
2680             if (effect == NOEFFECT)
2681                 effect_stack->setHidden(true);
2682             else {
2683                 effect_stack->setCurrentIndex(effect - 1);
2684                 effect_stack->setHidden(false);
2685             }
2686         } else // Hide the effects stack when more than one element is selected.
2687             effect_stack->setHidden(true);
2688         for (QGraphicsItem * item :  list) {
2689             switch (effect) {
2690             case NOEFFECT:
2691                 item->setData(100, QVariant());
2692                 item->setGraphicsEffect(0);
2693                 break;
2694             case TYPEWRITEREFFECT:
2695                 if (item->type() == TEXTITEM) {
2696                     QStringList effdata = QStringList() << QStringLiteral("typewriter") << QString::number(typewriter_delay->value()) + QLatin1Char(';') +
2697        QString::number(typewriter_start->value());
2698                     item->setData(100, effdata);
2699                 }
2700                 break;
2701                 // Do not remove the non-QGraphicsEffects.
2702             case BLUREFFECT:
2703                 item->setGraphicsEffect(new QGraphicsBlurEffect());
2704                 break;
2705             case SHADOWEFFECT:
2706                 item->setGraphicsEffect(new QGraphicsDropShadowEffect());
2707                 break;
2708             }
2709         }*/
2710 }
2711 
2712 qreal TitleWidget::zIndexBounds(bool maxBound, bool intersectingOnly)
2713 {
2714     qreal bound = maxBound ? -99 : 99;
2715     const QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
2716     if (!l.isEmpty()) {
2717         QList<QGraphicsItem *> lItems;
2718         // Get items (all or intersecting only)
2719         if (intersectingOnly) {
2720             lItems = graphicsView->scene()->items(l[0]->sceneBoundingRect(), Qt::IntersectsItemShape);
2721         } else {
2722             lItems = graphicsView->scene()->items();
2723         }
2724         if (!lItems.isEmpty()) {
2725             int n = lItems.size();
2726             qreal z;
2727             if (maxBound) {
2728                 for (int i = 0; i < n; ++i) {
2729                     z = lItems[i]->zValue();
2730                     if (z > bound && !lItems[i]->isSelected()) {
2731                         bound = z;
2732                     } else if (z - 1 > bound) {
2733                         // To get the maximum index even if it is of an item of the current selection.
2734                         // Used when updating multiple items, to get all to the same level.
2735                         // Otherwise, the maximum would stay at -99 if the highest item is in the selection.
2736                         bound = z - 1;
2737                     }
2738                 }
2739             } else {
2740                 // Get minimum z index.
2741                 for (int i = 0; i < n; ++i) {
2742                     z = lItems[i]->zValue();
2743                     if (z < bound && !lItems[i]->isSelected() && z > -999) {
2744                         // There are items at the very bottom (background e.g.) with z-index < -1000.
2745                         bound = z;
2746                     } else if (z + 1 < bound && z > -999) {
2747                         bound = z + 1;
2748                     }
2749                 }
2750             }
2751         }
2752     }
2753     return bound;
2754 }
2755 
2756 void TitleWidget::slotZIndexUp()
2757 {
2758     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
2759     if (l.size() >= 1) {
2760         qreal currentZ = l[0]->zValue();
2761         qreal max = zIndexBounds(true, true);
2762         if (currentZ <= max) {
2763             l[0]->setZValue(currentZ + 1);
2764             updateDimension(l[0]);
2765         }
2766     }
2767 }
2768 
2769 void TitleWidget::slotZIndexTop()
2770 {
2771     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
2772     qreal max = zIndexBounds(true, false);
2773     // qCDebug(KDENLIVE_LOG) << "Max z-index is " << max << ".\n";
2774     for (auto &i : l) {
2775         qreal currentZ = i->zValue();
2776         if (currentZ <= max) {
2777             // qCDebug(KDENLIVE_LOG) << "Updating item " << i << ", is " << currentZ << ".\n";
2778             i->setZValue(max + 1);
2779         } else {
2780             // qCDebug(KDENLIVE_LOG) << "Not updating " << i << ", is " << currentZ << ".\n";
2781         }
2782     }
2783     // Update the z index value in the GUI
2784     if (!l.isEmpty()) {
2785         updateDimension(l[0]);
2786     }
2787 }
2788 
2789 void TitleWidget::slotZIndexDown()
2790 {
2791     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
2792     if (l.size() >= 1) {
2793         qreal currentZ = l[0]->zValue();
2794         qreal min = zIndexBounds(false, true);
2795         if (currentZ >= min) {
2796             l[0]->setZValue(currentZ - 1);
2797             updateDimension(l[0]);
2798         }
2799     }
2800 }
2801 
2802 void TitleWidget::slotZIndexBottom()
2803 {
2804     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
2805     qreal min = zIndexBounds(false, false);
2806     for (auto &i : l) {
2807         qreal currentZ = i->zValue();
2808         if (currentZ >= min) {
2809             i->setZValue(min - 1);
2810         }
2811     }
2812     // Update the z index value in the GUI
2813     if (!l.isEmpty()) {
2814         updateDimension(l[0]);
2815     }
2816 }
2817 
2818 void TitleWidget::slotSelectAll()
2819 {
2820     QList<QGraphicsItem *> l = graphicsView->scene()->items();
2821     for (auto i : qAsConst(l)) {
2822         i->setSelected(true);
2823     }
2824 }
2825 
2826 void TitleWidget::selectItems(int itemType)
2827 {
2828     QList<QGraphicsItem *> l;
2829     if (!graphicsView->scene()->selectedItems().isEmpty()) {
2830         l = graphicsView->scene()->selectedItems();
2831         for (auto i : qAsConst(l)) {
2832             if (i->type() != itemType) {
2833                 i->setSelected(false);
2834             }
2835         }
2836     } else {
2837         l = graphicsView->scene()->items();
2838         for (auto i : qAsConst(l)) {
2839             if (i->type() == itemType) {
2840                 i->setSelected(true);
2841             }
2842         }
2843     }
2844 }
2845 
2846 void TitleWidget::slotSelectText()
2847 {
2848     selectItems(TEXTITEM);
2849 }
2850 
2851 void TitleWidget::slotSelectRects()
2852 {
2853     selectItems(RECTITEM);
2854 }
2855 
2856 void TitleWidget::slotSelectEllipses()
2857 {
2858     selectItems(ELLIPSEITEM);
2859 }
2860 
2861 void TitleWidget::slotSelectImages()
2862 {
2863     selectItems(IMAGEITEM);
2864 }
2865 
2866 void TitleWidget::slotSelectNone()
2867 {
2868     graphicsView->blockSignals(true);
2869     QList<QGraphicsItem *> l = graphicsView->scene()->items();
2870     for (auto i : qAsConst(l)) {
2871         i->setSelected(false);
2872     }
2873     graphicsView->blockSignals(false);
2874     selectionChanged();
2875 }
2876 
2877 QString TitleWidget::getTooltipWithShortcut(const QString &tipText, QAction *button)
2878 {
2879     return tipText + QStringLiteral("  <b>") + button->shortcut().toString() + QStringLiteral("</b>");
2880 }
2881 
2882 void TitleWidget::prepareTools(QGraphicsItem *referenceItem)
2883 {
2884     // Let some GUI elements block signals. We may want to change their values without any sideeffects.
2885     // Additionally, store the previous blocking state to avoid side effects when this function is called from within another one.
2886     // Note: Disabling an element also blocks signals. So disabled elements don't need to be set to blocking too.
2887     bool blockOX = origin_x_left->signalsBlocked();
2888     bool blockOY = origin_y_top->signalsBlocked();
2889     bool blockRX = itemrotatex->signalsBlocked();
2890     bool blockRY = itemrotatey->signalsBlocked();
2891     bool blockRZ = itemrotatez->signalsBlocked();
2892     bool blockZoom = itemzoom->signalsBlocked();
2893     bool blockX = value_x->signalsBlocked();
2894     bool blockY = value_y->signalsBlocked();
2895     bool blockW = value_w->signalsBlocked();
2896     bool blockH = value_h->signalsBlocked();
2897     origin_x_left->blockSignals(true);
2898     origin_y_top->blockSignals(true);
2899     itemrotatex->blockSignals(true);
2900     itemrotatey->blockSignals(true);
2901     itemrotatez->blockSignals(true);
2902     itemzoom->blockSignals(true);
2903     value_x->blockSignals(true);
2904     value_y->blockSignals(true);
2905     value_w->blockSignals(true);
2906     value_h->blockSignals(true);
2907 
2908     if (referenceItem == nullptr) {
2909         // qCDebug(KDENLIVE_LOG) << "nullptr item.\n";
2910         origin_x_left->setChecked(false);
2911         origin_y_top->setChecked(false);
2912         updateTextOriginX();
2913         updateTextOriginY();
2914         enableToolbars(TITLE_SELECT);
2915         showToolbars(TITLE_SELECT);
2916 
2917         itemzoom->setEnabled(false);
2918         itemrotatex->setEnabled(false);
2919         itemrotatey->setEnabled(false);
2920         itemrotatez->setEnabled(false);
2921         frame_properties->setEnabled(false);
2922         toolbar_stack->setEnabled(false);
2923         /*letter_spacing->setEnabled(false);
2924         line_spacing->setEnabled(false);
2925         letter_spacing->setValue(0);
2926         line_spacing->setValue(0);*/
2927     } else {
2928         toolbar_stack->setEnabled(true);
2929         frame_properties->setEnabled(true);
2930         if (referenceItem != m_startViewport && referenceItem != m_endViewport) {
2931             itemzoom->setEnabled(true);
2932             itemrotatex->setEnabled(true);
2933             itemrotatey->setEnabled(true);
2934             itemrotatez->setEnabled(true);
2935         } else {
2936             itemzoom->setEnabled(false);
2937             itemrotatex->setEnabled(false);
2938             itemrotatey->setEnabled(false);
2939             itemrotatez->setEnabled(false);
2940             updateInfoText();
2941         }
2942 
2943         letter_spacing->setEnabled(referenceItem->type() == TEXTITEM);
2944         line_spacing->setEnabled(referenceItem->type() == TEXTITEM);
2945 
2946         if (referenceItem->type() == TEXTITEM) {
2947             showToolbars(TITLE_TEXT);
2948             auto *i = static_cast<MyTextItem *>(referenceItem);
2949             if (!i->document()->isEmpty()) {
2950                 // We have an existing text item selected
2951                 if (!i->data(100).isNull()) {
2952                     // Item has an effect
2953                     /*QStringList effdata = i->data(100).toStringList();
2954                     QString effectName = effdata.takeFirst();
2955                     if (effectName == QLatin1String("typewriter")) {
2956                         QStringList params = effdata.at(0).split(QLatin1Char(';'));
2957                         typewriter_delay->setValue(params.at(0).toInt());
2958                         typewriter_start->setValue(params.at(1).toInt());
2959                         effect_list->setCurrentIndex(effect_list->findData((int)TYPEWRITEREFFECT));
2960                     }*/
2961                 } else {
2962                     /*if (i->graphicsEffect()) {
2963                         QGraphicsBlurEffect *blur = static_cast <QGraphicsBlurEffect *>(i->graphicsEffect());
2964                         if (blur) {
2965                             effect_list->setCurrentIndex(effect_list->findData((int) BLUREFFECT));
2966                             int rad = (int) blur->blurRadius();
2967                             blur_radius->setValue(rad);
2968                             effect_stack->setHidden(false);
2969                         } else {
2970                             QGraphicsDropShadowEffect *shad = static_cast <QGraphicsDropShadowEffect *>(i->graphicsEffect());
2971                             if (shad) {
2972                                 effect_list->setCurrentIndex(effect_list->findData((int) SHADOWEFFECT));
2973                                 shadow_radius->setValue(shad->blurRadius());
2974                                 shadow_x->setValue(shad->xOffset());
2975                                 shadow_y->setValue(shad->yOffset());
2976                                 effect_stack->setHidden(false);
2977                             }
2978                         }
2979                     } else {
2980                         effect_list->setCurrentIndex(effect_list->findData((int) NOEFFECT));
2981                         effect_stack->setHidden(true);
2982                     }*/
2983                 }
2984                 font_size->blockSignals(true);
2985                 font_family->blockSignals(true);
2986                 font_weight_box->blockSignals(true);
2987                 buttonItalic->blockSignals(true);
2988                 buttonUnder->blockSignals(true);
2989                 fontColorButton->blockSignals(true);
2990                 buttonAlignLeft->blockSignals(true);
2991                 buttonAlignRight->blockSignals(true);
2992                 buttonAlignCenter->blockSignals(true);
2993 
2994                 QFont font = i->font();
2995                 font_family->setCurrentFont(font);
2996                 font_size->setValue(font.pixelSize());
2997                 m_scene->slotUpdateFontSize(font.pixelSize());
2998                 buttonItalic->setChecked(font.italic());
2999                 buttonUnder->setChecked(font.underline());
3000                 setFontBoxWeight(font.weight());
3001 
3002                 QTextCursor cursor(i->document());
3003                 cursor.select(QTextCursor::Document);
3004                 QColor color = cursor.charFormat().foreground().color();
3005                 fontColorButton->setColor(color);
3006 
3007                 if (!i->data(TitleDocument::OutlineWidth).isNull()) {
3008                     textOutline->blockSignals(true);
3009                     textOutline->setValue(int(i->data(TitleDocument::OutlineWidth).toDouble()));
3010                     textOutline->blockSignals(false);
3011                 } else {
3012                     textOutline->blockSignals(true);
3013                     textOutline->setValue(0);
3014                     textOutline->blockSignals(false);
3015                 }
3016                 if (!i->data(TitleDocument::OutlineColor).isNull()) {
3017                     textOutlineColor->blockSignals(true);
3018                     QVariant variant = i->data(TitleDocument::OutlineColor);
3019                     color = variant.value<QColor>();
3020                     textOutlineColor->setColor(color);
3021                     textOutlineColor->blockSignals(false);
3022                 }
3023                 if (!i->data(TitleDocument::Gradient).isNull()) {
3024                     gradients_combo->blockSignals(true);
3025                     gradient_color->setChecked(true);
3026                     QString gradientData = i->data(TitleDocument::Gradient).toString();
3027                     int ix = gradients_combo->findData(gradientData);
3028                     if (ix == -1) {
3029                         // This gradient does not exist in our settings, store it
3030                         storeGradient(gradientData);
3031                         ix = gradients_combo->findData(gradientData);
3032                     }
3033                     gradients_combo->setCurrentIndex(ix);
3034                     gradients_combo->blockSignals(false);
3035                 } else {
3036                     plain_color->setChecked(true);
3037                 }
3038                 if (i->alignment() == Qt::AlignHCenter) {
3039                     buttonAlignCenter->setChecked(true);
3040                 } else if (i->alignment() == Qt::AlignRight) {
3041                     buttonAlignRight->setChecked(true);
3042                 } else if (i->alignment() == Qt::AlignLeft) {
3043                     buttonAlignLeft->setChecked(true);
3044                 } else {
3045                     QAbstractButton *selectedButton = m_textAlignGroup->button(KdenliveSettings::titlerAlign());
3046                     if (selectedButton) {
3047                         selectedButton->setChecked(true);
3048                     }
3049                 }
3050 
3051                 QStringList sInfo = i->shadowInfo();
3052                 if (sInfo.count() >= 5) {
3053                     QSignalBlocker bk(shadowBox);
3054                     shadowBox->setChecked(static_cast<bool>(sInfo.at(0).toInt()));
3055                     shadowBox->blockSignals(true);
3056                     shadowColor->setColor(QColor(sInfo.at(1)));
3057                     blur_radius->setValue(sInfo.at(2).toInt());
3058                     shadowX->setValue(sInfo.at(3).toInt());
3059                     shadowY->setValue(sInfo.at(4).toInt());
3060                 }
3061 
3062                 sInfo = i->twInfo();
3063                 if (sInfo.count() >= 5) {
3064                     QSignalBlocker bk(typewriterBox);
3065                     typewriterBox->setChecked(static_cast<bool>(sInfo.at(0).toInt()));
3066                     tw_sb_step->setValue(sInfo.at(1).toInt());
3067                     switch (sInfo.at(2).toInt()) {
3068                     case 1:
3069                         tw_rd_char->setChecked(true);
3070                         break;
3071                     case 2:
3072                         tw_rd_word->setChecked(true);
3073                         break;
3074                     case 3:
3075                         tw_rd_line->setChecked(true);
3076                         break;
3077                     default:
3078                         tw_rd_custom->setChecked(true);
3079                         break;
3080                     }
3081                     tw_sb_sigma->setValue(sInfo.at(3).toInt());
3082                     tw_sb_seed->setValue(sInfo.at(4).toInt());
3083                 }
3084 
3085                 letter_spacing->blockSignals(true);
3086                 line_spacing->blockSignals(true);
3087                 QTextCursor cur = i->textCursor();
3088                 QTextBlockFormat format = cur.blockFormat();
3089                 letter_spacing->setValue(int(font.letterSpacing()));
3090                 line_spacing->setValue(int(format.lineHeight()));
3091                 letter_spacing->blockSignals(false);
3092                 line_spacing->blockSignals(false);
3093 
3094                 font_size->blockSignals(false);
3095                 font_family->blockSignals(false);
3096                 font_weight_box->blockSignals(false);
3097                 buttonItalic->blockSignals(false);
3098                 buttonUnder->blockSignals(false);
3099                 fontColorButton->blockSignals(false);
3100                 buttonAlignLeft->blockSignals(false);
3101                 buttonAlignRight->blockSignals(false);
3102                 buttonAlignCenter->blockSignals(false);
3103 
3104                 // mbt 1607: Select text if the text item is an unchanged template item.
3105                 if (i->property("isTemplate").isValid()) {
3106                     cur.setPosition(0, QTextCursor::MoveAnchor);
3107                     cur.select(QTextCursor::Document);
3108                     i->setTextCursor(cur);
3109                     // Make text editable now.
3110                     i->grabKeyboard();
3111                     i->setTextInteractionFlags(Qt::TextEditorInteraction);
3112                 }
3113             }
3114 
3115             updateAxisButtons(i);
3116             updateCoordinates(i);
3117             updateDimension(i);
3118             enableToolbars(TITLE_TEXT);
3119 
3120         } else if ((referenceItem)->type() == RECTITEM) {
3121             showToolbars(TITLE_RECTANGLE);
3122             settingUp = 1;
3123             auto *rec = static_cast<QGraphicsRectItem *>(referenceItem);
3124             if (rec == m_startViewport || rec == m_endViewport) {
3125                 enableToolbars(TITLE_SELECT);
3126             } else {
3127                 QColor fcol = rec->pen().color();
3128                 QColor bcol = rec->brush().color();
3129                 rectFColor->setColor(fcol);
3130                 QString gradientData = rec->data(TitleDocument::Gradient).toString();
3131                 if (gradientData.isEmpty()) {
3132                     plain_rect->setChecked(true);
3133                     rectBColor->setColor(bcol);
3134                 } else {
3135                     gradient_rect->setChecked(true);
3136                     gradients_rect_combo->blockSignals(true);
3137                     int ix = gradients_rect_combo->findData(gradientData);
3138                     if (ix == -1) {
3139                         storeGradient(gradientData);
3140                         ix = gradients_rect_combo->findData(gradientData);
3141                     }
3142                     gradients_rect_combo->setCurrentIndex(ix);
3143                     gradients_rect_combo->blockSignals(false);
3144                 }
3145                 settingUp = 0;
3146                 if (rec->pen() == Qt::NoPen) {
3147                     rectLineWidth->setValue(0);
3148                 } else {
3149                     rectLineWidth->setValue(rec->pen().width());
3150                 }
3151                 enableToolbars(TITLE_RECTANGLE);
3152             }
3153 
3154             updateAxisButtons(referenceItem);
3155             updateCoordinates(rec);
3156             updateDimension(rec);
3157 
3158         } else if ((referenceItem)->type() == ELLIPSEITEM) {
3159             showToolbars(TITLE_RECTANGLE);
3160             settingUp = 1;
3161             auto *ellipse = static_cast<QGraphicsEllipseItem *>(referenceItem);
3162             QColor fcol = ellipse->pen().color();
3163             QColor bcol = ellipse->brush().color();
3164             rectFColor->setColor(fcol);
3165             QString gradientData = ellipse->data(TitleDocument::Gradient).toString();
3166             if (gradientData.isEmpty()) {
3167                 plain_rect->setChecked(true);
3168                 rectBColor->setColor(bcol);
3169             } else {
3170                 gradient_rect->setChecked(true);
3171                 gradients_rect_combo->blockSignals(true);
3172                 int ix = gradients_rect_combo->findData(gradientData);
3173                 if (ix == -1) {
3174                     storeGradient(gradientData);
3175                     ix = gradients_rect_combo->findData(gradientData);
3176                 }
3177                 gradients_rect_combo->setCurrentIndex(ix);
3178                 gradients_rect_combo->blockSignals(false);
3179             }
3180             settingUp = 0;
3181             if (ellipse->pen() == Qt::NoPen) {
3182                 rectLineWidth->setValue(0);
3183             } else {
3184                 rectLineWidth->setValue(ellipse->pen().width());
3185             }
3186             enableToolbars(TITLE_ELLIPSE);
3187 
3188             updateAxisButtons(referenceItem);
3189             updateCoordinates(ellipse);
3190             updateDimension(ellipse);
3191 
3192         } else if (referenceItem->type() == IMAGEITEM) {
3193             showToolbars(TITLE_IMAGE);
3194 
3195             updateCoordinates(referenceItem);
3196             updateDimension(referenceItem);
3197             enableToolbars(TITLE_IMAGE);
3198             QSignalBlocker bk(preserveAspectRatio);
3199             Transform t = m_transformations.value(referenceItem);
3200             preserveAspectRatio->setChecked(qFuzzyCompare(t.scalex, t.scaley));
3201 
3202         } else {
3203             showToolbars(TITLE_SELECT);
3204             enableToolbars(TITLE_SELECT);
3205             frame_properties->setEnabled(false);
3206         }
3207         zValue->setValue(int(referenceItem->zValue()));
3208         if (!referenceItem->data(TitleDocument::ZoomFactor).isNull()) {
3209             itemzoom->setValue(referenceItem->data(TitleDocument::ZoomFactor).toInt());
3210         } else {
3211             itemzoom->setValue(qRound(m_transformations.value(referenceItem).scalex * 100.0));
3212         }
3213         itemrotatex->setValue(int(m_transformations.value(referenceItem).rotatex));
3214         itemrotatey->setValue(int(m_transformations.value(referenceItem).rotatey));
3215         itemrotatez->setValue(int(m_transformations.value(referenceItem).rotatez));
3216     }
3217 
3218     itemrotatex->blockSignals(blockRX);
3219     itemrotatey->blockSignals(blockRY);
3220     itemrotatez->blockSignals(blockRZ);
3221     itemzoom->blockSignals(blockZoom);
3222     origin_x_left->blockSignals(blockOX);
3223     origin_y_top->blockSignals(blockOY);
3224     value_x->blockSignals(blockX);
3225     value_y->blockSignals(blockY);
3226     value_w->blockSignals(blockW);
3227     value_h->blockSignals(blockH);
3228 }
3229 
3230 void TitleWidget::slotEditGradient()
3231 {
3232     auto *caller = qobject_cast<QToolButton *>(QObject::sender());
3233     if (!caller) {
3234         return;
3235     }
3236     QComboBox *combo = nullptr;
3237     if (caller == edit_gradient) {
3238         combo = gradients_combo;
3239     } else {
3240         combo = gradients_rect_combo;
3241     }
3242     QMap<QString, QString> gradients;
3243     for (int i = 0; i < combo->count(); i++) {
3244         gradients.insert(combo->itemText(i), combo->itemData(i).toString());
3245     }
3246     GradientWidget d(gradients, combo->currentIndex());
3247     if (d.exec() == QDialog::Accepted) {
3248         // Save current gradients
3249         QMap<QString, QString> gradMap = d.gradients();
3250         QList<QIcon> icons = d.icons();
3251         QMap<QString, QString>::const_iterator i = gradMap.constBegin();
3252         KSharedConfigPtr config = KSharedConfig::openConfig();
3253         KConfigGroup group(config, "TitleGradients");
3254         group.deleteGroup();
3255         combo->clear();
3256         gradients_rect_combo->clear();
3257         int ix = 0;
3258         while (i != gradMap.constEnd()) {
3259             group.writeEntry(i.key(), i.value());
3260             gradients_combo->addItem(icons.at(ix), i.key(), i.value());
3261             gradients_rect_combo->addItem(icons.at(ix), i.key(), i.value());
3262             ++i;
3263             ix++;
3264         }
3265         group.sync();
3266         combo->setCurrentIndex(d.selectedGradient());
3267     }
3268 }
3269 
3270 void TitleWidget::storeGradient(const QString &gradientData)
3271 {
3272     KSharedConfigPtr config = KSharedConfig::openConfig();
3273     KConfigGroup group(config, "TitleGradients");
3274     QMap<QString, QString> values = group.entryMap();
3275     int ix = qMax(1, values.count());
3276     QString gradName = i18n("Gradient %1", ix);
3277     while (values.contains(gradName)) {
3278         ix++;
3279         gradName = i18n("Gradient %1", ix);
3280     }
3281     group.writeEntry(gradName, gradientData);
3282     group.sync();
3283     QPixmap pix(30, 30);
3284     pix.fill(Qt::transparent);
3285     QLinearGradient gr = GradientWidget::gradientFromString(gradientData, pix.width(), pix.height());
3286     gr.setStart(0, pix.height() / 2);
3287     gr.setFinalStop(pix.width(), pix.height() / 2);
3288     QPainter painter(&pix);
3289     painter.fillRect(0, 0, pix.width(), pix.height(), QBrush(gr));
3290     painter.end();
3291     QIcon icon(pix);
3292     gradients_combo->addItem(icon, gradName, gradientData);
3293     gradients_rect_combo->addItem(icon, gradName, gradientData);
3294 }
3295 
3296 void TitleWidget::loadGradients()
3297 {
3298     gradients_combo->blockSignals(true);
3299     gradients_rect_combo->blockSignals(true);
3300     QString grad_data = gradients_combo->currentData().toString();
3301     QString rect_data = gradients_rect_combo->currentData().toString();
3302     gradients_combo->clear();
3303     gradients_rect_combo->clear();
3304     KSharedConfigPtr config = KSharedConfig::openConfig();
3305     KConfigGroup group(config, "TitleGradients");
3306     QMap<QString, QString> values = group.entryMap();
3307     if (values.isEmpty()) {
3308         // Ensure we at least always have one sample black to white gradient
3309         values.insert(i18n("Gradient"), QStringLiteral("#ffffffff;#ff000000;0;100;90"));
3310     }
3311     QMapIterator<QString, QString> k(values);
3312     while (k.hasNext()) {
3313         k.next();
3314         QPixmap pix(30, 30);
3315         pix.fill(Qt::transparent);
3316         QLinearGradient gr = GradientWidget::gradientFromString(k.value(), pix.width(), pix.height());
3317         gr.setStart(0, pix.height() / 2);
3318         gr.setFinalStop(pix.width(), pix.height() / 2);
3319         QPainter painter(&pix);
3320         painter.fillRect(0, 0, pix.width(), pix.height(), QBrush(gr));
3321         painter.end();
3322         QIcon icon(pix);
3323         gradients_combo->addItem(icon, k.key(), k.value());
3324         gradients_rect_combo->addItem(icon, k.key(), k.value());
3325     }
3326     int ix = gradients_combo->findData(grad_data);
3327     if (ix >= 0) {
3328         gradients_combo->setCurrentIndex(ix);
3329     }
3330     ix = gradients_rect_combo->findData(rect_data);
3331     if (ix >= 0) {
3332         gradients_rect_combo->setCurrentIndex(ix);
3333     }
3334     gradients_combo->blockSignals(false);
3335     gradients_rect_combo->blockSignals(false);
3336 }
3337 
3338 void TitleWidget::slotUpdateShadow()
3339 {
3340     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
3341     for (int i = 0; i < graphicsView->scene()->selectedItems().length(); ++i) {
3342         MyTextItem *item = nullptr;
3343         if (l.at(i)->type() == TEXTITEM) {
3344             item = static_cast<MyTextItem *>(l.at(i));
3345         }
3346         if (!item) {
3347             // No text item, try next one.
3348             continue;
3349         }
3350         item->updateShadow(shadowBox->isChecked(), blur_radius->value(), shadowX->value(), shadowY->value(), shadowColor->color());
3351     }
3352 }
3353 
3354 void TitleWidget::slotUpdateTW()
3355 {
3356     QList<QGraphicsItem *> l = graphicsView->scene()->selectedItems();
3357     for (int i = 0; i < graphicsView->scene()->selectedItems().length(); ++i) {
3358         MyTextItem *item = nullptr;
3359         if (l.at(i)->type() == TEXTITEM) {
3360             item = static_cast<MyTextItem *>(l.at(i));
3361         }
3362         if (!item) {
3363             // No text item, try next one.
3364             continue;
3365         }
3366         int mode = 0;
3367         if (tw_rd_char->isChecked())
3368             mode = 1;
3369         else if (tw_rd_word->isChecked())
3370             mode = 2;
3371         else if (tw_rd_line->isChecked())
3372             mode = 3;
3373 
3374         item->updateTW(typewriterBox->isChecked(), tw_sb_step->value(), mode, tw_sb_sigma->value(), tw_sb_seed->value());
3375     }
3376 }
3377 
3378 const QString TitleWidget::titleSuggest()
3379 {
3380     // Find top item to extract title proposal
3381     QList<QGraphicsItem *> list = graphicsView->scene()->items();
3382     int y = m_frameHeight;
3383     QString title;
3384     for (QGraphicsItem *qgItem : qAsConst(list)) {
3385         if (qgItem->pos().y() < y && qgItem->type() == TEXTITEM) {
3386             auto *i = static_cast<MyTextItem *>(qgItem);
3387             QString currentTitle = i->toPlainText().simplified();
3388             if (currentTitle.length() > 2) {
3389                 title = currentTitle.length() > 12 ? currentTitle.left(12) + QStringLiteral("...") : currentTitle;
3390                 y = int(qgItem->pos().y());
3391             }
3392         }
3393     }
3394     return title;
3395 }
3396 
3397 void TitleWidget::showGuides(int state)
3398 {
3399     for (QGraphicsLineItem *it : qAsConst(m_guides)) {
3400         it->setVisible(state == Qt::Checked);
3401     }
3402     KdenliveSettings::setTitlerShowGuides(state == Qt::Checked);
3403 }
3404 
3405 void TitleWidget::updateGuides(int)
3406 {
3407     KdenliveSettings::setTitlerHGuides(hguides->value());
3408     KdenliveSettings::setTitlerVGuides(vguides->value());
3409     if (!m_guides.isEmpty()) {
3410         qDeleteAll(m_guides);
3411         m_guides.clear();
3412     }
3413     QPen framepen;
3414     QColor gColor(KdenliveSettings::titleGuideColor());
3415     framepen.setColor(gColor);
3416 
3417     // Guides
3418     // Horizontal guides
3419     int max = hguides->value();
3420     bool guideVisible = show_guides->checkState() == Qt::Checked;
3421     for (int i = 0; i < max; i++) {
3422         auto *line1 = new QGraphicsLineItem(0, (i + 1) * m_frameHeight / (max + 1), m_frameWidth, (i + 1) * m_frameHeight / (max + 1), m_frameBorder);
3423         line1->setPen(framepen);
3424         line1->setFlags({});
3425         line1->setData(-1, -1);
3426         line1->setVisible(guideVisible);
3427         m_guides << line1;
3428     }
3429     max = vguides->value();
3430     for (int i = 0; i < max; i++) {
3431         auto *line1 = new QGraphicsLineItem((i + 1) * m_frameWidth / (max + 1), 0, (i + 1) * m_frameWidth / (max + 1), m_frameHeight, m_frameBorder);
3432         line1->setPen(framepen);
3433         line1->setFlags({});
3434         line1->setData(-1, -1);
3435         line1->setVisible(guideVisible);
3436         m_guides << line1;
3437     }
3438 
3439     gColor.setAlpha(160);
3440     framepen.setColor(gColor);
3441 
3442     auto *line6 = new QGraphicsLineItem(0, 0, m_frameWidth, m_frameHeight, m_frameBorder);
3443     line6->setPen(framepen);
3444     line6->setFlags({});
3445     line6->setData(-1, -1);
3446     line6->setVisible(guideVisible);
3447     m_guides << line6;
3448 
3449     auto *line7 = new QGraphicsLineItem(m_frameWidth, 0, 0, m_frameHeight, m_frameBorder);
3450     line7->setPen(framepen);
3451     line7->setFlags({});
3452     line7->setData(-1, -1);
3453     line7->setVisible(guideVisible);
3454     m_guides << line7;
3455 }
3456 
3457 void TitleWidget::guideColorChanged(const QColor &col)
3458 {
3459     KdenliveSettings::setTitleGuideColor(col);
3460     QColor guideCol(col);
3461     for (QGraphicsLineItem *it : qAsConst(m_guides)) {
3462         int alpha = it->pen().color().alpha();
3463         guideCol.setAlpha(alpha);
3464         QPen framePen(guideCol);
3465         it->setPen(framePen);
3466     }
3467 }
3468 
3469 void TitleWidget::slotPatternsTileWidth(int width)
3470 {
3471     int w = width * pCore->getCurrentProfile()->display_aspect_num();
3472     int h = width * pCore->getCurrentProfile()->display_aspect_den();
3473     m_patternsModel->setTileSize(QSize(w, h));
3474     patternsList->setGridSize(QSize(w + 4, h + 4));
3475     m_patternsModel->repaintScenes();
3476 }
3477 
3478 void TitleWidget::slotPatternDblClicked(const QModelIndex &idx)
3479 {
3480     if (!idx.isValid()) return;
3481 
3482     QString data = m_patternsModel->data(idx, Qt::UserRole).toString();
3483 
3484     QDomDocument doc;
3485     doc.setContent(data);
3486 
3487     QList<QGraphicsItem *> items;
3488     int width, height, duration, missing;
3489     TitleDocument::loadFromXml(doc, items, width, height, nullptr, nullptr, nullptr, &duration, missing);
3490 
3491     for (QGraphicsItem *item : qAsConst(items)) {
3492         item->setZValue(m_count++);
3493         updateAxisButtons(item);
3494         prepareTools(item);
3495         m_scene->addNewItem(item);
3496     }
3497     m_scene->clearSelection();
3498     for (QGraphicsItem *item : qAsConst(items)) {
3499         item->setSelected(true);
3500     }
3501 }
3502 
3503 void TitleWidget::slotPatternBtnAddClicked()
3504 {
3505     QDomDocument doc = TitleDocument::xml(graphicsView->scene()->selectedItems(), m_frameWidth, m_frameHeight, nullptr, nullptr, false);
3506 
3507     m_patternsModel->addScene(doc.toString());
3508     btn_removeAll->setEnabled(m_patternsModel->rowCount(QModelIndex()) != 0);
3509 }
3510 
3511 void TitleWidget::slotPatternBtnRemoveClicked()
3512 {
3513     QModelIndexList items = patternsList->selectionModel()->selectedIndexes();
3514     std::sort(items.begin(), items.end());
3515     std::reverse(items.begin(), items.end());
3516     for (auto idx : qAsConst(items)) {
3517         m_patternsModel->removeScene(idx);
3518     }
3519     btn_removeAll->setEnabled(m_patternsModel->rowCount(QModelIndex()) != 0);
3520 }
3521 
3522 void TitleWidget::readPatterns()
3523 {
3524     KSharedConfigPtr config = KSharedConfig::openConfig();
3525     KConfigGroup titleConfig(config, "TitlePatterns");
3526 
3527     // Read the entries
3528     scaleSlider->setValue(titleConfig.readEntry("scale_factor", scaleSlider->minimum()));
3529     m_patternsModel->deserialize(titleConfig.readEntry("patterns", QByteArray()));
3530 
3531     btn_remove->setEnabled(false);
3532     btn_removeAll->setEnabled(m_patternsModel->rowCount(QModelIndex()) != 0);
3533 }
3534 
3535 void TitleWidget::writePatterns()
3536 {
3537 
3538     KSharedConfigPtr config = KSharedConfig::openConfig();
3539     KConfigGroup titleConfig(config, "TitlePatterns");
3540 
3541     int sf = titleConfig.readEntry("scale_factor", scaleSlider->minimum());
3542 
3543     // check whether the model was updated or scale slider differs
3544     if ((!m_patternsModel->getModifiedCounter()) && (sf == scaleSlider->value())) return;
3545 
3546     // Write the entries
3547     titleConfig.writeEntry("scale_factor", scaleSlider->value());
3548     QByteArray ba = m_patternsModel->serialize();
3549     titleConfig.writeEntry("patterns", ba);
3550 
3551     config->sync();
3552 }