File indexing completed on 2024-03-03 05:04:10

0001 /*
0002 SPDX-FileCopyrightText: 2016 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003 SPDX-FileCopyrightText: 2017 Nicolas Carion
0004 This file is part of Kdenlive. See www.kdenlive.org.
0005 
0006 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0007 */
0008 
0009 #include "profilewidget.h"
0010 #include "kdenlivesettings.h"
0011 #include "kxmlgui_version.h"
0012 #include "profiles/profilemodel.hpp"
0013 #include "profiles/profilerepository.hpp"
0014 #include "profiles/tree/profilefilter.hpp"
0015 #include "profiles/tree/profiletreemodel.hpp"
0016 
0017 #include <KLocalizedString>
0018 #include <QComboBox>
0019 #include <QHeaderView>
0020 #include <QLabel>
0021 #include <QSplitter>
0022 #include <QTextEdit>
0023 #include <QTreeView>
0024 
0025 ProfileWidget::ProfileWidget(QWidget *parent)
0026     : QWidget(parent)
0027     , m_originalProfile(QStringLiteral("invalid"))
0028 {
0029     setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
0030     auto *lay = new QVBoxLayout;
0031     lay->setContentsMargins(0, 0, 0, 0);
0032     auto *labelLay = new QHBoxLayout;
0033     auto *fpsLabel = new QLabel(i18n("Fps:"), this);
0034     m_fpsFilt = new QComboBox(this);
0035     fpsLabel->setBuddy(m_fpsFilt);
0036     labelLay->addWidget(fpsLabel);
0037     labelLay->addWidget(m_fpsFilt);
0038 
0039     auto *scanningLabel = new QLabel(i18n("Scanning:"), this);
0040     m_scanningFilt = new QComboBox(this);
0041     scanningLabel->setBuddy(m_scanningFilt);
0042     labelLay->addWidget(scanningLabel);
0043     labelLay->addWidget(m_scanningFilt);
0044     labelLay->addStretch(1);
0045 
0046     auto *manage_profiles = new QToolButton(this);
0047     labelLay->addWidget(manage_profiles);
0048     manage_profiles->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
0049     manage_profiles->setToolTip(i18n("Manage project profiles"));
0050     manage_profiles->setWhatsThis(
0051         xi18nc("@info:whatsthis",
0052                "Opens the profile dialog window in which you can change project profiles. Note: The profile used in the open project cannot be changed."));
0053     connect(manage_profiles, &QAbstractButton::clicked, this, &ProfileWidget::slotEditProfiles);
0054     lay->addLayout(labelLay);
0055 
0056     auto *profileSplitter = new QSplitter;
0057 
0058     m_treeView = new QTreeView(this);
0059     m_treeModel = ProfileTreeModel::construct(this);
0060     m_filter = new ProfileFilter(this);
0061     m_filter->setSourceModel(m_treeModel.get());
0062     m_treeView->setModel(m_filter);
0063     for (int i = 1; i < m_treeModel->columnCount(); ++i) {
0064         m_treeView->hideColumn(i);
0065     }
0066     m_treeView->header()->hide();
0067     QItemSelectionModel *selectionModel = m_treeView->selectionModel();
0068     connect(selectionModel, &QItemSelectionModel::currentRowChanged, this, &ProfileWidget::slotChangeSelection);
0069     connect(selectionModel, &QItemSelectionModel::selectionChanged, this, [&](const QItemSelection &selected, const QItemSelection &deselected) {
0070         QModelIndex current;
0071         QModelIndex old;
0072         if (!selected.indexes().isEmpty()) {
0073             current = selected.indexes().front();
0074         }
0075         if (!deselected.indexes().isEmpty()) {
0076             old = deselected.indexes().front();
0077         }
0078         slotChangeSelection(current, old);
0079     });
0080     int treeViewFontHeight = QFontInfo(m_treeView->font()).pixelSize();
0081     m_treeView->setMinimumHeight(treeViewFontHeight);
0082     profileSplitter->addWidget(m_treeView);
0083     m_treeView->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
0084     m_descriptionPanel = new QTextEdit(this);
0085     m_descriptionPanel->setReadOnly(true);
0086     m_descriptionPanel->viewport()->setCursor(Qt::ArrowCursor);
0087     m_descriptionPanel->viewport()->setBackgroundRole(QPalette::Mid);
0088     m_descriptionPanel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
0089     m_descriptionPanel->setFrameStyle(QFrame::NoFrame);
0090     m_descriptionPanel->setMinimumHeight(treeViewFontHeight);
0091     profileSplitter->addWidget(m_descriptionPanel);
0092 
0093     lay->addWidget(profileSplitter);
0094     profileSplitter->setStretchFactor(0, 4);
0095     profileSplitter->setStretchFactor(1, 3);
0096 
0097     refreshFpsCombo();
0098     auto updateFps = [&]() {
0099         double current = m_fpsFilt->currentData().toDouble();
0100         KdenliveSettings::setProfile_fps_filter(m_fpsFilt->currentText());
0101         m_filter->setFilterFps(current > 0, current);
0102         slotFilterChanged();
0103     };
0104     connect(m_fpsFilt, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), updateFps);
0105     int ix = m_fpsFilt->findText(KdenliveSettings::profile_fps_filter());
0106     if (ix > -1) {
0107         m_fpsFilt->setCurrentIndex(ix);
0108     }
0109     m_scanningFilt->addItem(i18n("Any"), -1);
0110     m_scanningFilt->addItem(i18n("Interlaced"), 0);
0111     m_scanningFilt->addItem(i18n("Progressive"), 1);
0112 
0113     auto updateScanning = [&]() {
0114         int current = m_scanningFilt->currentData().toInt();
0115         KdenliveSettings::setProfile_scanning_filter(m_scanningFilt->currentText());
0116         m_filter->setFilterInterlaced(current != -1, current == 0);
0117         slotFilterChanged();
0118     };
0119     connect(m_scanningFilt, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), updateScanning);
0120 
0121     ix = m_scanningFilt->findText(KdenliveSettings::profile_scanning_filter());
0122     if (ix > -1) {
0123         m_scanningFilt->setCurrentIndex(ix);
0124     }
0125     setLayout(lay);
0126 }
0127 
0128 ProfileWidget::~ProfileWidget() = default;
0129 
0130 void ProfileWidget::refreshFpsCombo()
0131 {
0132     QLocale locale; // Used for UI → OK
0133     QVariant currentValue;
0134     if (m_fpsFilt->count() > 1) {
0135         // remember last selected value
0136         currentValue = m_fpsFilt->currentData();
0137     }
0138     m_fpsFilt->clear();
0139     locale.setNumberOptions(QLocale::OmitGroupSeparator);
0140     m_fpsFilt->addItem(i18n("Any"), -1);
0141     auto all_fps = ProfileRepository::get()->getAllFps();
0142     for (double fps : qAsConst(all_fps)) {
0143         m_fpsFilt->addItem(locale.toString(fps), fps);
0144     }
0145     if (currentValue.isValid()) {
0146         int ix = m_fpsFilt->findData(currentValue);
0147         if (ix > -1) {
0148             m_fpsFilt->setCurrentIndex(ix);
0149         }
0150     }
0151 }
0152 
0153 void ProfileWidget::loadProfile(const QString &profile)
0154 {
0155     auto index = m_treeModel->findProfile(profile);
0156     if (index.isValid()) {
0157         m_originalProfile = m_currentProfile = m_lastValidProfile = profile;
0158         if (!trySelectProfile(profile)) {
0159             // When loading a profile, ensure it is visible so reset filters if necessary
0160             m_fpsFilt->setCurrentIndex(0);
0161             m_scanningFilt->setCurrentIndex(0);
0162         }
0163     }
0164 }
0165 
0166 const QString ProfileWidget::selectedProfile() const
0167 {
0168     return m_currentProfile;
0169 }
0170 
0171 void ProfileWidget::slotEditProfiles()
0172 {
0173     auto *w = new ProfilesDialog(ProfileRepository::get()->getProfile(m_currentProfile)->description());
0174     w->exec();
0175     if (w->profileTreeChanged()) {
0176         // Rebuild profiles tree
0177         m_treeModel.reset();
0178         m_treeModel = ProfileTreeModel::construct(this);
0179         m_filter->setSourceModel(m_treeModel.get());
0180         refreshFpsCombo();
0181         loadProfile(m_currentProfile);
0182     }
0183     delete w;
0184 }
0185 
0186 void ProfileWidget::fillDescriptionPanel(const QString &profile_path)
0187 {
0188     QString description;
0189     if (profile_path.isEmpty()) {
0190         description += i18n("No profile selected");
0191     } else {
0192         std::unique_ptr<ProfileModel> &profile = ProfileRepository::get()->getProfile(profile_path);
0193 
0194         description += QStringLiteral("<h5>%1</h5>").arg(i18n("Video Settings"));
0195         description +=
0196             QStringLiteral("<p style='font-size:small'>%1<br/>")
0197                 .arg(i18n("Frame size: %1 x %2 (%3:%4)", profile->width(), profile->height(), profile->display_aspect_num(), profile->display_aspect_den()));
0198         description += i18n("Frame rate: %1 fps", profile->fps());
0199         description += QStringLiteral("<br/>");
0200         description += i18n("Pixel aspect ratio: %1", profile->sar());
0201         description += QStringLiteral("<br/>");
0202         description += i18n("Color space: %1", profile->colorspaceDescription());
0203         description += QStringLiteral("<br/>");
0204         description += i18n("Interlaced: %1", profile->progressive() ? i18n("no") : i18n("yes"));
0205         if (!profile->progressive()) {
0206             description += QStringLiteral("<br/>");
0207             description += i18n("Field order: %1", profile->bottom_field_first() ? i18n("Bottom field first") : i18n("Top field first"));
0208         }
0209         description += QStringLiteral("</p>");
0210     }
0211     m_descriptionPanel->setHtml(description);
0212 }
0213 
0214 void ProfileWidget::slotChangeSelection(const QModelIndex &current, const QModelIndex &previous)
0215 {
0216     auto originalIndex = m_filter->mapToSource(current);
0217     if (m_treeModel->parent(originalIndex) == QModelIndex()) {
0218         // in that case, we have selected a category, which we don't want
0219         QItemSelectionModel *selection = m_treeView->selectionModel();
0220         selection->select(previous, QItemSelectionModel::Select);
0221         return;
0222     }
0223     m_currentProfile = m_treeModel->getProfile(originalIndex);
0224     if (!m_currentProfile.isEmpty()) {
0225         m_lastValidProfile = m_currentProfile;
0226     }
0227     if (m_originalProfile != m_currentProfile) {
0228         Q_EMIT profileChanged();
0229     }
0230     fillDescriptionPanel(m_currentProfile);
0231 }
0232 
0233 bool ProfileWidget::trySelectProfile(const QString &profile)
0234 {
0235     auto index = m_treeModel->findProfile(profile);
0236     if (index.isValid()) {
0237         // check if element is visible
0238         if (m_filter->isVisible(index)) {
0239             // reselect
0240             QItemSelectionModel *selection = m_treeView->selectionModel();
0241             selection->select(m_filter->mapFromSource(index), QItemSelectionModel::Select);
0242             // expand corresponding category
0243             auto parent = m_treeModel->parent(index);
0244             m_treeView->expand(m_filter->mapFromSource(parent));
0245             m_treeView->scrollTo(m_filter->mapFromSource(index), QAbstractItemView::PositionAtCenter);
0246             return true;
0247         }
0248     }
0249     return false;
0250 }
0251 
0252 void ProfileWidget::slotFilterChanged()
0253 {
0254     // When filtering change, we must check if the current profile is still visible.
0255     if (!trySelectProfile(m_currentProfile)) {
0256         // we try to back-up the last valid profile
0257         if (!trySelectProfile(m_lastValidProfile)) {
0258             // Everything fails, we don't have any profile
0259             m_currentProfile = QString();
0260             Q_EMIT profileChanged();
0261             fillDescriptionPanel(QString());
0262         }
0263     }
0264 }