File indexing completed on 2024-04-21 04:51:21

0001 /*
0002     SPDX-FileCopyrightText: 2016 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003 
0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "profilesdialog.h"
0008 #include "core.h"
0009 #include "effects/effectsrepository.hpp"
0010 #include "kdenlivesettings.h"
0011 #include "profiles/profilemodel.hpp"
0012 #include "profiles/profilerepository.hpp"
0013 
0014 #include "klocalizedstring.h"
0015 #include "utils/KMessageBox_KdenliveCompat.h"
0016 #include <KMessageBox>
0017 #include <KMessageWidget>
0018 
0019 #include "kdenlive_debug.h"
0020 #include <QCloseEvent>
0021 #include <QDir>
0022 #include <QStandardPaths>
0023 
0024 ProfilesDialog::ProfilesDialog(const QString &profileDescription, QWidget *parent)
0025     : QDialog(parent)
0026 {
0027     // ask profile repository for a refresh
0028     ProfileRepository::get()->refresh();
0029 
0030     m_view.setupUi(this);
0031     showMessage();
0032 
0033     // Fill colorspace list (see mlt_profile.h)
0034     m_view.colorspace->addItem(ProfileRepository::getColorspaceDescription(601), 601);
0035     m_view.colorspace->addItem(ProfileRepository::getColorspaceDescription(709), 709);
0036     m_view.colorspace->addItem(ProfileRepository::getColorspaceDescription(240), 240);
0037     m_view.colorspace->addItem(ProfileRepository::getColorspaceDescription(0), 0);
0038 
0039     QStringList profilesFilter;
0040     profilesFilter << QStringLiteral("*");
0041 
0042     fillList(profileDescription);
0043     slotUpdateDisplay();
0044     connectDialog();
0045 }
0046 
0047 void ProfilesDialog::connectDialog()
0048 {
0049     connect(m_view.profiles_list, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
0050             [&](int ix) { slotUpdateDisplay(m_view.profiles_list->itemData(ix).toString()); });
0051     connect(m_view.button_create, &QAbstractButton::clicked, this, &ProfilesDialog::slotCreateProfile);
0052     connect(m_view.button_save, &QAbstractButton::clicked, this, &ProfilesDialog::slotSaveProfile);
0053     connect(m_view.button_delete, &QAbstractButton::clicked, this, &ProfilesDialog::slotDeleteProfile);
0054     connect(m_view.button_default, &QAbstractButton::clicked, this, &ProfilesDialog::slotSetDefaultProfile);
0055 
0056     connect(m_view.description, &QLineEdit::textChanged, this, &ProfilesDialog::slotProfileEdited);
0057     connect(m_view.frame_num, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ProfilesDialog::slotProfileEdited);
0058     connect(m_view.frame_den, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ProfilesDialog::slotProfileEdited);
0059     connect(m_view.aspect_num, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ProfilesDialog::slotProfileEdited);
0060     connect(m_view.aspect_den, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ProfilesDialog::slotProfileEdited);
0061     connect(m_view.display_num, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ProfilesDialog::slotProfileEdited);
0062     connect(m_view.display_den, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ProfilesDialog::slotProfileEdited);
0063     connect(m_view.scanning, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ProfilesDialog::slotProfileEdited);
0064     connect(m_view.scanning, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ProfilesDialog::slotScanningChanged);
0065     connect(m_view.size_h, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ProfilesDialog::slotProfileEdited);
0066     connect(m_view.size_h, &QAbstractSpinBox::editingFinished, this, &ProfilesDialog::slotAdjustHeight);
0067     m_view.size_h->setSingleStep(2);
0068     connect(m_view.size_w, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ProfilesDialog::slotProfileEdited);
0069     connect(m_view.size_w, &QAbstractSpinBox::editingFinished, this, &ProfilesDialog::slotAdjustWidth);
0070     m_view.size_w->setSingleStep(2);
0071 }
0072 
0073 ProfilesDialog::ProfilesDialog(const QString &profilePath, bool, QWidget *parent)
0074     : QDialog(parent)
0075     , m_isCustomProfile(true)
0076     , m_customProfilePath(profilePath)
0077 {
0078     m_view.setupUi(this);
0079     showMessage();
0080 
0081     // Fill colorspace list (see mlt_profile.h)
0082     m_view.colorspace->addItem(ProfileRepository::getColorspaceDescription(601), 601);
0083     m_view.colorspace->addItem(ProfileRepository::getColorspaceDescription(709), 709);
0084     m_view.colorspace->addItem(ProfileRepository::getColorspaceDescription(240), 240);
0085     m_view.colorspace->addItem(ProfileRepository::getColorspaceDescription(0), 0);
0086 
0087     QStringList profilesFilter;
0088     profilesFilter << QStringLiteral("*");
0089 
0090     m_view.button_create->setHidden(true);
0091     m_view.profiles_list->setHidden(true);
0092     m_view.button_delete->setHidden(true);
0093     m_view.button_default->setHidden(true);
0094     m_view.description->setEnabled(false);
0095 
0096     slotUpdateDisplay(profilePath);
0097     connectDialog();
0098 }
0099 
0100 void ProfilesDialog::slotAdjustWidth()
0101 {
0102     // A profile's width should always be a multiple of 2
0103     QSignalBlocker blk(m_view.size_w);
0104     int val = m_view.size_w->value();
0105     int correctedWidth = val + (val % 2);
0106     if (val == correctedWidth) {
0107         // Ok, no action required, width is a multiple of 2
0108         showMessage();
0109     } else {
0110         m_view.size_w->setValue(correctedWidth);
0111         showMessage(i18n("Profile width must be a multiple of 2. It was adjusted to %1", correctedWidth));
0112     }
0113 }
0114 
0115 void ProfilesDialog::slotAdjustHeight()
0116 {
0117     // A profile's height should always be a multiple of 2
0118     QSignalBlocker blk(m_view.size_h);
0119     int val = m_view.size_h->value();
0120     int correctedHeight = val + (val % 2);
0121     if (val == correctedHeight) {
0122         // Ok, no action required, height is a multiple of 2
0123         showMessage();
0124     } else {
0125         m_view.size_h->setValue(correctedHeight);
0126         showMessage(i18n("Profile height must be a multiple of 2. It was adjusted to %1", correctedHeight));
0127     }
0128 }
0129 
0130 void ProfilesDialog::slotScanningChanged(int ix)
0131 {
0132     m_view.field_order->setEnabled(ix == 0);
0133     m_view.label_field_order->setEnabled(ix == 0);
0134     if (ix == 0 && !EffectsRepository::get()->hasInternalEffect(QStringLiteral("avfilter.fieldorder"))) {
0135         m_view.effect_warning->show();
0136     } else {
0137         m_view.effect_warning->hide();
0138     }
0139 }
0140 
0141 void ProfilesDialog::slotProfileEdited()
0142 {
0143     m_profileIsModified = true;
0144 }
0145 
0146 void ProfilesDialog::fillList(const QString &selectedProfile)
0147 {
0148     m_view.profiles_list->clear();
0149     // Retrieve the list from the repository
0150     QVector<QPair<QString, QString>> profiles = ProfileRepository::get()->getAllProfiles();
0151     for (const auto &p : qAsConst(profiles)) {
0152         m_view.profiles_list->addItem(p.first, p.second);
0153     }
0154 
0155     if (!KdenliveSettings::default_profile().isEmpty()) {
0156         int ix = m_view.profiles_list->findData(KdenliveSettings::default_profile());
0157         if (ix > -1) {
0158             m_view.profiles_list->setCurrentIndex(ix);
0159         } else {
0160             // Error, profile not found
0161             qCWarning(KDENLIVE_LOG) << "Project profile not found, disable  editing";
0162         }
0163     }
0164     int ix = m_view.profiles_list->findText(selectedProfile);
0165     if (ix != -1) {
0166         m_view.profiles_list->setCurrentIndex(ix);
0167     }
0168     m_selectedProfileIndex = m_view.profiles_list->currentIndex();
0169 }
0170 
0171 void ProfilesDialog::accept()
0172 {
0173     if (askForSave()) {
0174         QDialog::accept();
0175     }
0176 }
0177 
0178 void ProfilesDialog::reject()
0179 {
0180     if (askForSave()) {
0181         QDialog::reject();
0182     }
0183 }
0184 
0185 void ProfilesDialog::closeEvent(QCloseEvent *event)
0186 {
0187     if (askForSave()) {
0188         event->accept();
0189     } else {
0190         event->ignore();
0191     }
0192 }
0193 
0194 bool ProfilesDialog::askForSave()
0195 {
0196     if (!m_profileIsModified) {
0197         return true;
0198     }
0199     if (KMessageBox::questionTwoActions(this, i18n("The custom profile was modified, do you want to save it?"), {}, KStandardGuiItem::save(),
0200                                         KStandardGuiItem::discard()) != KMessageBox::PrimaryAction) {
0201         return true;
0202     }
0203     return slotSaveProfile();
0204 }
0205 
0206 void ProfilesDialog::slotCreateProfile()
0207 {
0208     m_view.button_delete->setEnabled(false);
0209     m_view.button_create->setEnabled(false);
0210     m_view.button_save->setEnabled(true);
0211     m_view.properties->setEnabled(true);
0212     m_view.description->blockSignals(true);
0213     m_view.description->setText(m_view.description->text() + " " + i18n("(copy)"));
0214     m_view.description->blockSignals(false);
0215 }
0216 
0217 void ProfilesDialog::slotSetDefaultProfile()
0218 {
0219     if (m_profileIsModified) {
0220         showMessage(i18n("Save your profile before setting it to default"));
0221         return;
0222     }
0223     int ix = m_view.profiles_list->currentIndex();
0224     QString path = m_view.profiles_list->itemData(ix).toString();
0225     if (!path.isEmpty()) {
0226         KdenliveSettings::setDefault_profile(path);
0227     }
0228 }
0229 
0230 bool ProfilesDialog::slotSaveProfile()
0231 {
0232     slotAdjustWidth();
0233 
0234     if (!m_customProfilePath.isEmpty()) {
0235         saveProfile(m_customProfilePath);
0236         return true;
0237     }
0238     const QString profileDesc = m_view.description->text();
0239     int ix = m_view.profiles_list->findText(profileDesc);
0240     if (ix != -1) {
0241         // this profile name already exists
0242         const QString path = m_view.profiles_list->itemData(ix).toString();
0243         if (!path.contains(QLatin1Char('/'))) {
0244             KMessageBox::error(
0245                 this, i18n("A profile with same name already exists in MLT's default profiles, please choose another description for your custom profile."));
0246             return false;
0247         }
0248         saveProfile(path);
0249     } else {
0250         int i = 0;
0251         QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/"));
0252         if (!dir.exists()) {
0253             dir.mkpath(QStringLiteral("."));
0254         }
0255         QString customName = QStringLiteral("customprofile");
0256         QString profilePath = dir.absoluteFilePath(customName + QString::number(i));
0257         while (QFile::exists(profilePath)) {
0258             ++i;
0259             profilePath = dir.absoluteFilePath(customName + QString::number(i));
0260         }
0261         saveProfile(profilePath);
0262     }
0263     m_profileIsModified = false;
0264     fillList(profileDesc);
0265     m_view.button_create->setEnabled(true);
0266     m_profilesChanged = true;
0267     return true;
0268 }
0269 
0270 void ProfilesDialog::saveProfile(const QString &path)
0271 {
0272     std::unique_ptr<ProfileParam> profile(new ProfileParam(pCore->getCurrentProfile().get()));
0273     profile->m_description = m_view.description->text();
0274     profile->m_frame_rate_num = m_view.frame_num->value();
0275     profile->m_frame_rate_den = m_view.frame_den->value();
0276     profile->m_width = m_view.size_w->value();
0277     profile->m_height = m_view.size_h->value();
0278     profile->m_progressive = m_view.scanning->currentIndex() == 1;
0279     profile->m_bottom_field_first = m_view.field_order->currentIndex() == 1;
0280     profile->m_sample_aspect_num = m_view.aspect_num->value();
0281     profile->m_sample_aspect_den = m_view.aspect_den->value();
0282     profile->m_display_aspect_num = m_view.display_num->value();
0283     profile->m_display_aspect_den = m_view.display_den->value();
0284     profile->m_colorspace = m_view.colorspace->itemData(m_view.colorspace->currentIndex()).toInt();
0285     ProfileRepository::get()->saveProfile(profile.get(), path);
0286 }
0287 
0288 void ProfilesDialog::slotDeleteProfile()
0289 {
0290     const QString path = m_view.profiles_list->itemData(m_view.profiles_list->currentIndex()).toString();
0291     bool success = ProfileRepository::get()->deleteProfile(path);
0292     if (success) {
0293         m_profilesChanged = true;
0294         fillList();
0295     }
0296 }
0297 
0298 void ProfilesDialog::slotUpdateDisplay(QString currentProfilePath)
0299 {
0300     qDebug() << "/ / / /UPDATING DISPLAY FOR PROFILE: " << currentProfilePath;
0301     if (!askForSave()) {
0302         m_view.profiles_list->blockSignals(true);
0303         m_view.profiles_list->setCurrentIndex(m_selectedProfileIndex);
0304         m_view.profiles_list->blockSignals(false);
0305         return;
0306     }
0307     QLocale locale; // Used for UI output → OK
0308     locale.setNumberOptions(QLocale::OmitGroupSeparator);
0309     m_selectedProfileIndex = m_view.profiles_list->currentIndex();
0310     if (currentProfilePath.isEmpty()) {
0311         currentProfilePath = m_view.profiles_list->itemData(m_view.profiles_list->currentIndex()).toString();
0312     }
0313     m_isCustomProfile = currentProfilePath.contains(QLatin1Char('/'));
0314     // Don't allow editing of the current Project, since this produces crashes at the moment
0315     bool isCurrentlyUsed = pCore->getCurrentProfilePath() == currentProfilePath;
0316     showMessage(isCurrentlyUsed ? i18n("The profile of the current project cannot be edited while the project is open.") : QString());
0317     m_view.button_create->setEnabled(true);
0318     m_view.button_delete->setEnabled(m_isCustomProfile && !isCurrentlyUsed);
0319     m_view.properties->setEnabled(m_isCustomProfile && !isCurrentlyUsed);
0320     m_view.button_save->setEnabled(m_isCustomProfile && !isCurrentlyUsed);
0321     std::unique_ptr<ProfileModel> &curProfile = ProfileRepository::get()->getProfile(currentProfilePath);
0322     m_view.description->setText(curProfile->description());
0323     m_view.size_w->setValue(curProfile->width());
0324     m_view.size_h->setValue(curProfile->height());
0325     m_view.aspect_num->setValue(curProfile->sample_aspect_num());
0326     m_view.aspect_den->setValue(curProfile->sample_aspect_den());
0327     m_view.display_num->setValue(curProfile->display_aspect_num());
0328     m_view.display_den->setValue(curProfile->display_aspect_den());
0329     m_view.frame_num->setValue(curProfile->frame_rate_num());
0330     m_view.frame_den->setValue(curProfile->frame_rate_den());
0331     m_view.scanning->setCurrentIndex(curProfile->progressive() ? 1 : 0);
0332     m_view.field_order->setCurrentIndex(curProfile->bottom_field_first() ? 1 : 0);
0333     slotScanningChanged(m_view.scanning->currentIndex());
0334     if (curProfile->progressive() != 0) {
0335         m_view.fields->setText(locale.toString(double(curProfile->frame_rate_num() / curProfile->frame_rate_den()), 'f', 2));
0336     } else {
0337         m_view.fields->setText(locale.toString(2.0 * curProfile->frame_rate_num() / curProfile->frame_rate_den(), 'f', 2));
0338     }
0339 
0340     int colorix = m_view.colorspace->findData(curProfile->colorspace());
0341     if (colorix > -1) {
0342         m_view.colorspace->setCurrentIndex(colorix);
0343     }
0344     m_profileIsModified = false;
0345 }
0346 
0347 bool ProfilesDialog::profileTreeChanged() const
0348 {
0349     return m_profilesChanged;
0350 }
0351 
0352 void ProfilesDialog::showMessage(const QString &text, KMessageWidget::MessageType type)
0353 {
0354     if (text.isEmpty()) {
0355         m_view.info_message->hide();
0356     } else {
0357         m_view.info_message->setText(text);
0358         m_view.info_message->setMessageType(type);
0359         m_view.info_message->animatedShow();
0360     }
0361 }